linear skill

This commit is contained in:
2026-03-19 15:21:36 +00:00
parent db41ec6e93
commit 227c1638f6
15 changed files with 832 additions and 1 deletions
+105
View File
@@ -0,0 +1,105 @@
---
name: linear
description: Access Linear issue tracker - search, view, create, update issues, list teams/projects, and manage comments. Use when the user asks about Linear issues, tasks, tickets, or project management in Linear.
---
# Linear
Manage Linear issues, projects, and teams via the Linear SDK.
## Setup
Run once before first use:
```bash
cd {baseDir} && npm install
```
Requires a `LINEAR_API_KEY` environment variable. Generate one at: https://linear.app/settings/api (Personal API keys).
Set it in your shell profile or pi settings:
```bash
export LINEAR_API_KEY=lin_api_...
```
## Current User
```bash
node {baseDir}/linear-me.js # Show authenticated user
node {baseDir}/linear-me.js --issues # Show user + their active issues
```
## Search Issues
```bash
node {baseDir}/linear-search.js "query" # Text search
node {baseDir}/linear-search.js "query" -n 20 # More results
node {baseDir}/linear-search.js "query" --team ENG # Filter by team
node {baseDir}/linear-search.js "query" --state "In Progress" # Filter by state
```
## List Issues (with filters)
```bash
node {baseDir}/linear-issues.js # All recent issues
node {baseDir}/linear-issues.js --team ENG # By team
node {baseDir}/linear-issues.js --state "In Progress" # By state
node {baseDir}/linear-issues.js --assignee me # My issues
node {baseDir}/linear-issues.js --assignee "John" # By assignee name
node {baseDir}/linear-issues.js --label "Bug" # By label
node {baseDir}/linear-issues.js --project "Q1 Goals" # By project
node {baseDir}/linear-issues.js --team ENG --state Todo -n 50 # Combined filters
```
## View Issue Details
```bash
node {baseDir}/linear-issue.js ATT-1234 # Full issue details
node {baseDir}/linear-issue.js ATT-1234 --comments # Include comments
```
## Create Issue
```bash
node {baseDir}/linear-create.js --team ENG --title "Fix login bug"
node {baseDir}/linear-create.js --team ENG --title "New feature" --description "Details here" --state Todo --priority 2 --assignee me --label "Feature"
node {baseDir}/linear-create.js --team ENG --title "Sub-task" --parent ATT-100
```
Priority values: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low
## Update Issue
```bash
node {baseDir}/linear-update.js ATT-1234 --state "In Progress"
node {baseDir}/linear-update.js ATT-1234 --assignee me --priority 2
node {baseDir}/linear-update.js ATT-1234 --title "New title" --description "Updated desc"
```
## Add Comment
```bash
node {baseDir}/linear-comment.js ATT-1234 "This is done in PR #567"
```
## List Teams
```bash
node {baseDir}/linear-teams.js
```
## List Projects
```bash
node {baseDir}/linear-projects.js # All projects
node {baseDir}/linear-projects.js --team ENG # By team
```
## Tips
- Use `--assignee me` to filter by the authenticated user
- Issue identifiers follow the pattern `TEAM-NUMBER` (e.g. `ATT-1234`, `ENG-567`)
- Descriptions support markdown formatting
- State names are case-insensitive (e.g. "todo", "Todo", "TODO" all work)
- When creating issues, the team key is required; use `linear-teams.js` to find available teams
+23
View File
@@ -0,0 +1,23 @@
import { LinearClient } from "@linear/sdk";
export function getClient() {
const apiKey = process.env.LINEAR_API_KEY;
if (!apiKey) {
console.error("Error: LINEAR_API_KEY environment variable is required.");
console.error(
"Generate one at: https://linear.app/settings/api (Personal API keys)"
);
process.exit(1);
}
return new LinearClient({ apiKey });
}
export function formatDate(date) {
if (!date) return "";
return new Date(date).toISOString().split("T")[0];
}
export function truncate(str, len = 120) {
if (!str) return "";
return str.length > len ? str.slice(0, len) + "…" : str;
}
+29
View File
@@ -0,0 +1,29 @@
#!/usr/bin/env node
// Add a comment to a Linear issue
// Usage: linear-comment.js <identifier> <body>
import { getClient } from "./lib.js";
const args = process.argv.slice(2);
const identifier = args[0];
const body = args.slice(1).join(" ");
if (!identifier || !body) {
console.log("Usage: linear-comment.js <identifier> <body>");
console.log("\nExamples:");
console.log(' linear-comment.js ATT-1234 "This is fixed in the latest PR"');
process.exit(1);
}
const client = getClient();
const results = await client.searchIssues(identifier, { first: 1 });
const issue = results.nodes[0];
if (!issue) {
console.error(`Issue '${identifier}' not found.`);
process.exit(1);
}
await client.createComment({ issueId: issue.id, body });
console.log(`Comment added to ${issue.identifier}.`);
+102
View File
@@ -0,0 +1,102 @@
#!/usr/bin/env node
// Create a new Linear issue
// Usage: linear-create.js --team <key> --title <title> [--description <desc>] [--state <name>] [--priority <0-4>] [--assignee <name|me>] [--label <name>] [--parent <identifier>]
import { getClient } from "./lib.js";
const args = process.argv.slice(2);
function extractArg(flag) {
const idx = args.indexOf(flag);
if (idx !== -1 && args[idx + 1]) {
const val = args[idx + 1];
args.splice(idx, 2);
return val;
}
return null;
}
const teamKey = extractArg("--team");
const title = extractArg("--title");
const description = extractArg("--description");
const stateName = extractArg("--state");
const priority = extractArg("--priority");
const assigneeName = extractArg("--assignee");
const labelName = extractArg("--label");
const parentId = extractArg("--parent");
if (!teamKey || !title) {
console.log("Usage: linear-create.js --team <key> --title <title> [options]");
console.log("\nRequired:");
console.log(" --team <key> Team key (e.g. ENG)");
console.log(' --title <title> Issue title');
console.log("\nOptional:");
console.log(" --description <text> Issue description (markdown)");
console.log(" --state <name> Initial state (e.g. 'Todo')");
console.log(" --priority <0-4> Priority: 0=None, 1=Urgent, 2=High, 3=Medium, 4=Low");
console.log(" --assignee <name|me> Assignee name or 'me'");
console.log(" --label <name> Label name");
console.log(" --parent <id> Parent issue identifier (e.g. ATT-100)");
process.exit(1);
}
const client = getClient();
// Resolve team
const teams = await client.teams({ filter: { key: { eq: teamKey.toUpperCase() } } });
const team = teams.nodes[0];
if (!team) {
console.error(`Team '${teamKey}' not found.`);
process.exit(1);
}
const input = {
teamId: team.id,
title,
};
if (description) input.description = description;
if (priority) input.priority = parseInt(priority, 10);
// Resolve state
if (stateName) {
const states = await team.states();
const state = states.nodes.find(
(s) => s.name.toLowerCase() === stateName.toLowerCase()
);
if (state) input.stateId = state.id;
else console.warn(`Warning: State '${stateName}' not found, using default.`);
}
// Resolve assignee
if (assigneeName) {
if (assigneeName.toLowerCase() === "me") {
const me = await client.viewer;
input.assigneeId = me.id;
} else {
const users = await client.users({ filter: { name: { containsIgnoreCase: assigneeName } } });
if (users.nodes[0]) input.assigneeId = users.nodes[0].id;
else console.warn(`Warning: User '${assigneeName}' not found.`);
}
}
// Resolve label
if (labelName) {
const labels = await client.issueLabels({ filter: { name: { eqIgnoreCase: labelName } } });
if (labels.nodes[0]) input.labelIds = [labels.nodes[0].id];
else console.warn(`Warning: Label '${labelName}' not found.`);
}
// Resolve parent
if (parentId) {
const parentSearch = await client.searchIssues(parentId, { first: 1 });
if (parentSearch.nodes[0]) input.parentId = parentSearch.nodes[0].id;
else console.warn(`Warning: Parent '${parentId}' not found.`);
}
const result = await client.createIssue(input);
const issue = await result.issue;
console.log(`Created: ${issue.identifier} - ${issue.title}`);
console.log(`URL: ${issue.url}`);
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/env node
// Get details for a specific Linear issue
// Usage: linear-issue.js <identifier> [--comments]
import { getClient, formatDate } from "./lib.js";
const args = process.argv.slice(2);
const showComments = args.includes("--comments");
const filtered = args.filter((a) => a !== "--comments");
const identifier = filtered[0];
if (!identifier) {
console.log("Usage: linear-issue.js <identifier> [--comments]");
console.log("\nExamples:");
console.log(" linear-issue.js ATT-1234");
console.log(" linear-issue.js ATT-1234 --comments");
process.exit(1);
}
const client = getClient();
// Parse team key and issue number from identifier (e.g. "SIP-1205")
const parts = identifier.match(/^([A-Za-z]+)-(\d+)$/);
if (!parts) {
console.error(`Invalid identifier format: ${identifier}. Expected format: TEAM-123`);
process.exit(1);
}
const teamKey = parts[1].toUpperCase();
const issueNumber = parseInt(parts[2], 10);
// Find the issue by team key + number
const issues = await client.issues({
filter: {
team: { key: { eq: teamKey } },
number: { eq: issueNumber },
},
first: 1,
});
const issue = issues.nodes[0];
if (!issue) {
console.error(`Issue ${identifier} not found.`);
process.exit(1);
}
const state = await issue.state;
const team = await issue.team;
const assignee = await issue.assignee;
const labels = await issue.labels();
const parent = await issue.parent;
const project = await issue.project;
const cycle = await issue.cycle;
console.log(`=== ${issue.identifier}: ${issue.title} ===`);
console.log(`URL: ${issue.url}`);
console.log(`State: ${state?.name || "Unknown"}`);
console.log(`Priority: ${issue.priorityLabel}`);
console.log(`Team: ${team?.key || "?"}`);
console.log(`Assignee: ${assignee?.name || "Unassigned"}`);
if (project) console.log(`Project: ${project.name}`);
if (cycle) console.log(`Cycle: ${cycle.name || cycle.number}`);
if (parent) console.log(`Parent: ${parent.identifier} - ${parent.title}`);
if (labels.nodes.length > 0) {
console.log(`Labels: ${labels.nodes.map((l) => l.name).join(", ")}`);
}
console.log(`Created: ${formatDate(issue.createdAt)}`);
console.log(`Updated: ${formatDate(issue.updatedAt)}`);
if (issue.dueDate) console.log(`Due: ${issue.dueDate}`);
console.log(`\nDescription:\n${issue.description || "(empty)"}`);
if (showComments) {
const comments = await issue.comments();
if (comments.nodes.length > 0) {
console.log(`\n--- Comments (${comments.nodes.length}) ---`);
for (const comment of comments.nodes) {
const author = await comment.user;
console.log(`\n[${formatDate(comment.createdAt)}] ${author?.name || "Unknown"}:`);
console.log(comment.body);
}
} else {
console.log("\nNo comments.");
}
}
+90
View File
@@ -0,0 +1,90 @@
#!/usr/bin/env node
// List Linear issues with filters
// Usage: linear-issues.js [--team <key>] [--state <name>] [--assignee <name|me>] [--label <name>] [--project <name>] [-n <num>]
import { getClient, formatDate, truncate } from "./lib.js";
const args = process.argv.slice(2);
function extractArg(flag) {
const idx = args.indexOf(flag);
if (idx !== -1 && args[idx + 1]) {
const val = args[idx + 1];
args.splice(idx, 2);
return val;
}
return null;
}
const numResults = parseInt(extractArg("-n") || "25", 10);
const teamKey = extractArg("--team");
const stateName = extractArg("--state");
const assigneeName = extractArg("--assignee");
const labelName = extractArg("--label");
const projectName = extractArg("--project");
if (args.includes("--help") || args.includes("-h")) {
console.log("Usage: linear-issues.js [options]");
console.log("\nOptions:");
console.log(" --team <key> Filter by team key (e.g. ENG)");
console.log(" --state <name> Filter by state (e.g. 'In Progress', 'Todo')");
console.log(" --assignee <name> Filter by assignee name or 'me'");
console.log(" --label <name> Filter by label name");
console.log(" --project <name> Filter by project name");
console.log(" -n <num> Number of results (default: 25)");
process.exit(0);
}
const client = getClient();
// Build filter
const filter = {};
if (teamKey) {
filter.team = { key: { eq: teamKey.toUpperCase() } };
}
if (stateName) {
filter.state = { name: { eqIgnoreCase: stateName } };
}
if (assigneeName) {
if (assigneeName.toLowerCase() === "me") {
const me = await client.viewer;
filter.assignee = { id: { eq: me.id } };
} else {
filter.assignee = { name: { containsIgnoreCase: assigneeName } };
}
}
if (labelName) {
filter.labels = { name: { eqIgnoreCase: labelName } };
}
if (projectName) {
filter.project = { name: { containsIgnoreCase: projectName } };
}
const issues = await client.issues({
filter,
first: numResults,
orderBy: "updatedAt",
});
if (issues.nodes.length === 0) {
console.log("No issues found matching filters.");
process.exit(0);
}
for (const issue of issues.nodes) {
const state = await issue.state;
const team = await issue.team;
const assignee = await issue.assignee;
console.log(
`${issue.identifier.padEnd(12)} ${(state?.name || "?").padEnd(14)} ${(issue.priorityLabel || "").padEnd(8)} ${(assignee?.name || "Unassigned").padEnd(20)} ${truncate(issue.title, 80)}`
);
}
console.log(`\n${issues.nodes.length} issue(s) shown.`);
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env node
// Show current authenticated user and their assigned issues
// Usage: linear-me.js [--issues]
import { getClient, truncate } from "./lib.js";
const showIssues = process.argv.includes("--issues");
const client = getClient();
const me = await client.viewer;
console.log(`User: ${me.name}`);
console.log(`Email: ${me.email}`);
console.log(`ID: ${me.id}`);
if (showIssues) {
const issues = await me.assignedIssues({
first: 25,
filter: {
state: { type: { nin: ["completed", "canceled"] } },
},
orderBy: "updatedAt",
});
console.log(`\n--- Active Assigned Issues (${issues.nodes.length}) ---`);
for (const issue of issues.nodes) {
const state = await issue.state;
console.log(
`${issue.identifier.padEnd(12)} ${(state?.name || "?").padEnd(14)} ${(issue.priorityLabel || "").padEnd(8)} ${truncate(issue.title, 80)}`
);
}
}
+45
View File
@@ -0,0 +1,45 @@
#!/usr/bin/env node
// List Linear projects
// Usage: linear-projects.js [--team <key>] [-n <num>]
import { getClient, formatDate } from "./lib.js";
const args = process.argv.slice(2);
function extractArg(flag) {
const idx = args.indexOf(flag);
if (idx !== -1 && args[idx + 1]) {
const val = args[idx + 1];
args.splice(idx, 2);
return val;
}
return null;
}
const numResults = parseInt(extractArg("-n") || "25", 10);
const teamKey = extractArg("--team");
const client = getClient();
const filter = {};
if (teamKey) {
filter.accessibleTeams = { key: { eq: teamKey.toUpperCase() } };
}
const projects = await client.projects({ filter, first: numResults });
if (projects.nodes.length === 0) {
console.log("No projects found.");
process.exit(0);
}
for (const project of projects.nodes) {
const lead = await project.lead;
console.log(`--- ${project.name} ---`);
console.log(`State: ${project.state} | Progress: ${Math.round(project.progress * 100)}%`);
if (lead) console.log(`Lead: ${lead.name}`);
if (project.targetDate) console.log(`Target: ${project.targetDate}`);
console.log(`URL: ${project.url}`);
console.log("");
}
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env node
// Search Linear issues by text query
// Usage: linear-search.js <query> [-n <num>] [--team <key>] [--state <name>]
import { getClient, formatDate, truncate } from "./lib.js";
const args = process.argv.slice(2);
let numResults = 10;
const nIdx = args.indexOf("-n");
if (nIdx !== -1 && args[nIdx + 1]) {
numResults = parseInt(args[nIdx + 1], 10);
args.splice(nIdx, 2);
}
let teamFilter = null;
const teamIdx = args.indexOf("--team");
if (teamIdx !== -1 && args[teamIdx + 1]) {
teamFilter = args[teamIdx + 1];
args.splice(teamIdx, 2);
}
let stateFilter = null;
const stateIdx = args.indexOf("--state");
if (stateIdx !== -1 && args[stateIdx + 1]) {
stateFilter = args[stateIdx + 1];
args.splice(stateIdx, 2);
}
const query = args.join(" ");
if (!query) {
console.log("Usage: linear-search.js <query> [-n <num>] [--team <key>] [--state <name>]");
console.log("\nOptions:");
console.log(" -n <num> Number of results (default: 10)");
console.log(" --team <key> Filter by team key (e.g. ENG)");
console.log(" --state <name> Filter by state name (e.g. 'In Progress')");
process.exit(1);
}
const client = getClient();
const results = await client.searchIssues(query, { first: numResults });
for (const issue of results.nodes) {
const state = await issue.state;
const team = await issue.team;
const assignee = await issue.assignee;
if (teamFilter && team?.key?.toLowerCase() !== teamFilter.toLowerCase()) continue;
if (stateFilter && state?.name?.toLowerCase() !== stateFilter.toLowerCase()) continue;
console.log(`--- ${issue.identifier} ---`);
console.log(`Title: ${issue.title}`);
console.log(`State: ${state?.name || "Unknown"}`);
console.log(`Priority: ${issue.priorityLabel}`);
console.log(`Team: ${team?.key || "?"} | Assignee: ${assignee?.name || "Unassigned"}`);
console.log(`Created: ${formatDate(issue.createdAt)} | Updated: ${formatDate(issue.updatedAt)}`);
if (issue.description) console.log(`Description: ${truncate(issue.description, 200)}`);
console.log(`URL: ${issue.url}`);
console.log("");
}
if (results.nodes.length === 0) {
console.log("No results found.");
}
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env node
// List all Linear teams
// Usage: linear-teams.js
import { getClient } from "./lib.js";
const client = getClient();
const teams = await client.teams();
console.log("Teams:");
for (const team of teams.nodes) {
console.log(` ${team.key.padEnd(8)} ${team.name}`);
}
+93
View File
@@ -0,0 +1,93 @@
#!/usr/bin/env node
// Update an existing Linear issue
// Usage: linear-update.js <identifier> [--title <title>] [--state <name>] [--priority <0-4>] [--assignee <name|me>] [--description <text>]
import { getClient } from "./lib.js";
const args = process.argv.slice(2);
const identifier = args[0];
if (!identifier || identifier.startsWith("--")) {
console.log("Usage: linear-update.js <identifier> [options]");
console.log("\nOptions:");
console.log(" --title <title> New title");
console.log(" --state <name> New state (e.g. 'In Progress')");
console.log(" --priority <0-4> New priority");
console.log(" --assignee <name|me> New assignee");
console.log(" --description <text> New description");
process.exit(1);
}
args.shift();
function extractArg(flag) {
const idx = args.indexOf(flag);
if (idx !== -1 && args[idx + 1]) {
const val = args[idx + 1];
args.splice(idx, 2);
return val;
}
return null;
}
const title = extractArg("--title");
const stateName = extractArg("--state");
const priority = extractArg("--priority");
const assigneeName = extractArg("--assignee");
const description = extractArg("--description");
const client = getClient();
// Find the issue
const results = await client.searchIssues(identifier, { first: 1 });
const issue = results.nodes[0];
if (!issue) {
console.error(`Issue '${identifier}' not found.`);
process.exit(1);
}
const input = {};
if (title) input.title = title;
if (description) input.description = description;
if (priority) input.priority = parseInt(priority, 10);
// Resolve state
if (stateName) {
const team = await issue.team;
const states = await team.states();
const state = states.nodes.find(
(s) => s.name.toLowerCase() === stateName.toLowerCase()
);
if (state) input.stateId = state.id;
else {
console.error(`State '${stateName}' not found. Available states:`);
for (const s of states.nodes) console.error(` - ${s.name}`);
process.exit(1);
}
}
// Resolve assignee
if (assigneeName) {
if (assigneeName.toLowerCase() === "me") {
const me = await client.viewer;
input.assigneeId = me.id;
} else {
const users = await client.users({ filter: { name: { containsIgnoreCase: assigneeName } } });
if (users.nodes[0]) input.assigneeId = users.nodes[0].id;
else {
console.error(`User '${assigneeName}' not found.`);
process.exit(1);
}
}
}
if (Object.keys(input).length === 0) {
console.log("No updates specified. Use --title, --state, --priority, --assignee, or --description.");
process.exit(1);
}
await client.updateIssue(issue.id, input);
console.log(`Updated ${issue.identifier}: ${issue.title}`);
console.log(`URL: ${issue.url}`);
+107
View File
@@ -0,0 +1,107 @@
{
"name": "linear-skill",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "linear-skill",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@linear/sdk": "^37.0.0"
}
},
"node_modules/@graphql-typed-document-node/core": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@graphql-typed-document-node/core/-/core-3.2.0.tgz",
"integrity": "sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==",
"license": "MIT",
"peerDependencies": {
"graphql": "^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0"
}
},
"node_modules/@linear/sdk": {
"version": "37.0.0",
"resolved": "https://registry.npmjs.org/@linear/sdk/-/sdk-37.0.0.tgz",
"integrity": "sha512-EAZCXtV414Nwtvrwn7Ucu3E8BbYYKsc3HqZCGf1mHUE7FhZGtfISu295DOVv89WhhXlp2N344EMg3K0nnhLxtA==",
"license": "MIT",
"dependencies": {
"@graphql-typed-document-node/core": "^3.1.0",
"graphql": "^15.4.0",
"isomorphic-unfetch": "^3.1.0"
},
"engines": {
"node": ">=12.x",
"yarn": "1.x"
}
},
"node_modules/graphql": {
"version": "15.10.1",
"resolved": "https://registry.npmjs.org/graphql/-/graphql-15.10.1.tgz",
"integrity": "sha512-BL/Xd/T9baO6NFzoMpiMD7YUZ62R6viR5tp/MULVEnbYJXZA//kRNW7J0j1w/wXArgL0sCxhDfK5dczSKn3+cg==",
"license": "MIT",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/isomorphic-unfetch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz",
"integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==",
"license": "MIT",
"dependencies": {
"node-fetch": "^2.6.1",
"unfetch": "^4.2.0"
}
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/unfetch": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz",
"integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==",
"license": "MIT"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "linear-skill",
"version": "1.0.0",
"type": "module",
"description": "Linear API skill for pi - manage issues, projects, and teams",
"license": "MIT",
"dependencies": {
"@linear/sdk": "^37.0.0"
}
}