Skill: matrixy/linear-skill (version 1.0.0)

--- name: linear description: Manage Linear projects, issues, and tasks via the bundled Node CLI and the official Linear API. Use when you need to read, create, update, or organize Linear issues, projects, teams, milestones, comments, cycles, labels, and documents. metadata: {"gracebot":{"always":false,"emoji":"📐","homepage":"https://github.com/MaTriXy/linear-skill","requires":{"bins":["node","npm"],"env":["LINEAR_API_KEY"]},"primaryEnv":"LINEAR_API_KEY","install":[{"id":"node-brew","kind":"brew","formula":"node","bins":["node","npm"],"label":"Install Node.js (brew)"}]},"clawdbot":{"always":false,"emoji":"📐","homepage":"https://github.com/MaTriXy/linear-skill","requires":{"bins":["node","npm"],"env":["LINEAR_API_KEY"]},"primaryEnv":"LINEAR_API_KEY","install":[{"id":"node-brew","kind":"brew","formula":"node","bins":["node","npm"],"label":"Install Node.js (brew)"}]}} --- # Linear Workflow Management Manage Linear issues and projects through the bundled CLI at `{baseDir}/scripts/linear-cli.js`. ## Scope and Runtime Model - This skill runs `node {baseDir}/scripts/linear-cli.js ...`. - The CLI uses the official `@linear/sdk`. - Authentication is `LINEAR_API_KEY` from the local environment. - Expected API destination is Linear GraphQL (`https://api.linear.app/graphql`) through the official SDK. ## Prerequisites 1. Node.js and npm are installed. 2. Install script dependencies once: - `cd {baseDir}/scripts && npm install` 3. Set your API key: - `export LINEAR_API_KEY="lin_api_..."` If dependencies or `LINEAR_API_KEY` are missing, stop and complete setup before issue/project operations. ## Authentication and Credentials - Required credential: `LINEAR_API_KEY`. - Get it from `https://linear.app/settings/api`. - Use least-privilege access and a dedicated token for automation. ## Required Workflow 1. Clarify intent and scope: - Team/project, labels, cycle, assignee, due date, priority. 2. Read current state first: - List/get issues, projects, statuses, labels, users, cycles. 3. Apply mutations second: - Create/update issues, comments, projects, milestones, labels. 4. Summarize exactly what changed: - Mention IDs, states, assignees, blockers, and follow-up actions. ## Command Coverage - Teams and projects: `teams`, `projects`, `createProject` - Issues: `issues`, `issue`, `createIssue`, `updateIssue` - Comments: `createComment` - States and labels: `states`, `labels` - User: `user` ## Quick Examples ```bash node {baseDir}/scripts/linear-cli.js teams node {baseDir}/scripts/linear-cli.js projects node {baseDir}/scripts/linear-cli.js issues node {baseDir}/scripts/linear-cli.js issue ENG-123 node {baseDir}/scripts/linear-cli.js createIssue "Title" "Description" "team-id" '{"priority":2}' node {baseDir}/scripts/linear-cli.js updateIssue "issue-id" '{"stateId":"state-id"}' ``` ## Practical Workflows - Triage urgent bugs: list high-priority open issues, assign owners, move state to `In Progress`, add triage comments. - Sprint planning: review cycle scope, create missing issues, set priorities and estimates, align assignees. - Release prep: verify blockers, update project status, create milestone tasks, add rollout comments. - Documentation cleanup: find stale docs/issues, open follow-up tasks, link related records. ## Safety and Operational Rules - Never invent IDs; fetch and confirm before updates. - Prefer narrow updates over broad bulk edits. - For bulk edits, explain grouping logic before applying changes. - Do not include secrets in issue comments or descriptions. - Do not send data to endpoints outside Linear API scope for this skill. ## References - `references/API.md` for priority values and workflow patterns.

path: README.md
content: # Linear Skill (OpenClaw Hub + Claude Code)

Manage Linear issues and projects with the bundled Node CLI (`scripts/linear-cli.js`).

## Security/Runtime Summary

- Runtime: local Node.js script in this repo
- Dependency: `@linear/sdk` (installed under `scripts/`)
- Credential: `LINEAR_API_KEY`
- API scope: Linear GraphQL via official SDK (`https://api.linear.app/graphql`)
- Source: [github.com/MaTriXy/linear-skill](https://github.com/MaTriXy/linear-skill)

## Install

```bash
git clone https://github.com/MaTriXy/linear-skill.git
cd linear-skill/scripts
npm install
```

## Credentials

Create a personal API key in Linear, then set:

```bash
export LINEAR_API_KEY="lin_api_..."
```

## Usage

```bash
node ./scripts/linear-cli.js teams
node ./scripts/linear-cli.js projects
node ./scripts/linear-cli.js issues
node ./scripts/linear-cli.js issue ENG-123
node ./scripts/linear-cli.js createIssue "Title" "Description" "team-id" '{"priority":2}'
node ./scripts/linear-cli.js updateIssue "issue-id" '{"stateId":"state-id"}'
```

## Notes

- The skill package includes the CLI source at `scripts/linear-cli.js`.
- If you use OpenClaw/Grace skill settings, set `skills.entries.linear.apiKey` to map into `LINEAR_API_KEY`.
- `SKILL.md` includes machine-readable metadata for install requirements and credential declaration.


path: agents/openai.yaml
content: interface:
  display_name: "Linear"
  short_description: "Manage Linear projects and issues with local CLI workflow"
  default_prompt: "Use the Linear skill workflow to triage and update issues and projects with clear state transitions and next actions."


path: package.json
content: {
  "name": "linear-skill",
  "version": "1.0.0",
  "description": "Linear project management skill for Claude Code and Clawdbot",
  "main": "scripts/linear-cli.js",
  "scripts": {
    "install": "cd scripts && npm install",
    "postinstall": "echo '\n✅ Linear skill installed successfully!\n\n📝 Next steps:\n1. Get your Linear API key from: https://linear.app/settings/api\n2. Set LINEAR_API_KEY environment variable\n3. Start using Linear with Claude Code or Clawdbot\n'"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/MaTriXy/linear-skill.git"
  },
  "keywords": [
    "claude",
    "claude-code",
    "clawdbot",
    "linear",
    "skill",
    "project-management",
    "issue-tracking"
  ],
  "author": "Yossi elkrief <[REDACTED_SECRET]>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/MaTriXy/linear-skill/issues"
  },
  "homepage": "https://github.com/MaTriXy/linear-skill#readme"
}


path: references/API.md
content: # Linear API Reference

## Configuration

This skill uses:
- `LINEAR_API_KEY` (personal API key from `https://linear.app/settings/api`)
- `scripts/linear-cli.js`
- `@linear/sdk` in `scripts/package.json`

## Getting IDs

### Teams
Use:
`node ./scripts/linear-cli.js teams`

### Projects
Use:
`node ./scripts/linear-cli.js projects`

## Priority Levels

- `0`: No priority
- `1`: Urgent
- `2`: High
- `3`: Normal
- `4`: Low

## State Types

States are team-specific. Use:
`node ./scripts/linear-cli.js states "<teamId>"`

Common state types:
- `backlog`
- `unstarted`
- `started`
- `completed`
- `canceled`

## Filter Examples

### Issues by state
```json
{
  "state": {
    "name": { "eq": "In Progress" }
  }
}
```

### Issues by assignee
```json
{
  "assignee": {
    "id": { "eq": "user-id-here" }
  }
}
```

### Issues by project
```json
{
  "project": {
    "id": { "eq": "project-id-here" }
  }
}
```

### Issues by priority
```json
{
  "priority": { "eq": 1 }
}
```

## Common Workflows

### Create an issue for a project

1. Get team/project IDs (`teams`, `projects`).
2. Create issue with `teamId` and `projectId`:

```bash
node ./scripts/linear-cli.js createIssue "Task title" "Description here" "your-team-id" '{"projectId":"your-project-id","priority":2}'
```

### Update issue state

1. Get team states:

```bash
node ./scripts/linear-cli.js states "<teamId>"
```

2. Update issue:

```bash
node ./scripts/linear-cli.js updateIssue "issue-uuid" '{"stateId":"state-uuid"}'
```

### Assign issue to user

1. Get user info:

```bash
node ./scripts/linear-cli.js user
```

2. Apply assignment:

```bash
node ./scripts/linear-cli.js updateIssue "issue-uuid" '{"assigneeId":"user-uuid"}'
```


path: scripts/linear-cli.js
content: #!/usr/bin/env node
/**
 * Linear CLI - Command-line interface for Linear API operations
 * Uses the official @linear/sdk
 */

const { LinearClient } = require('@linear/sdk');

// Require LINEAR_API_KEY environment variable
const [REDACTED_SECRET];
if (!apiKey) {
  console.error('Error: LINEAR_API_KEY environment variable is required');
  console.error('Get your API key from: https://linear.app/settings/api');
  process.exit(1);
}

const client = new LinearClient({ apiKey });

const commands = {
  // Teams
  async teams() {
    const teams = await client.teams();
    return teams.nodes.map(t => ({
      id: t.id,
      name: t.name,
      key: t.key
    }));
  },

  // Projects
  async projects(filter) {
    const projects = await client.projects(filter ? { filter } : {});
    return projects.nodes.map(p => ({
      id: p.id,
      name: p.name,
      description: p.description,
      state: p.state
    }));
  },

  async createProject(name, description, teamIds) {
    const result = await client.createProject({
      name,
      description,
      teamIds: teamIds.split(',')
    });
    const project = await result.project;
    return { id: project.id, name: project.name };
  },

  // Issues
  async issues(filter) {
    const options = {};
    if (filter) {
      options.filter = JSON.parse(filter);
    }
    const issues = await client.issues(options);
    return issues.nodes.map(i => ({
      id: i.id,
      identifier: i.identifier,
      title: i.title,
      state: i.state?.name,
      assignee: i.assignee?.name,
      priority: i.priority
    }));
  },

  async issue(identifier) {
    const issue = await client.issue(identifier);
    const comments = await issue.comments();
    return {
      id: issue.id,
      identifier: issue.identifier,
      title: issue.title,
      description: issue.description,
      state: issue.state?.name,
      assignee: issue.assignee?.name,
      priority: issue.priority,
      labels: issue.labels?.nodes?.map(l => l.name) || [],
      comments: comments.nodes.map(c => ({
        id: c.id,
        body: c.body,
        user: c.user?.name,
        createdAt: c.createdAt
      }))
    };
  },

  async createIssue(title, description, teamId, options = {}) {
    const opts = typeof options === 'string' ? JSON.parse(options) : options;
    const result = await client.createIssue({
      title,
      description,
      teamId,
      ...opts
    });
    const issue = await result.issue;
    return { 
      id: issue.id, 
      identifier: issue.identifier,
      title: issue.title 
    };
  },

  async updateIssue(issueId, updates) {
    const updateData = typeof updates === 'string' ? JSON.parse(updates) : updates;
    const result = await client.updateIssue(issueId, updateData);
    const success = await result.success;
    return { success };
  },

  async createComment(issueId, body) {
    const result = await client.createComment({
      issueId,
      body
    });
    const comment = await result.comment;
    return { id: comment.id };
  },

  // States
  async states(teamId) {
    const team = await client.team(teamId);
    const states = await team.states();
    return states.nodes.map(s => ({
      id: s.id,
      name: s.name,
      type: s.type
    }));
  },

  // Labels
  async labels(teamId) {
    const team = await client.team(teamId);
    const labels = await team.labels();
    return labels.nodes.map(l => ({
      id: l.id,
      name: l.name,
      color: l.color
    }));
  },

  // User
  async user() {
    const user = await client.viewer;
    return {
      id: user.id,
      name: user.name,
      email: user.email
    };
  }
};

// CLI execution
async function main() {
  const [,, command, ...args] = process.argv;
  
  if (!command || !commands[command]) {
    console.error('Usage: linear-cli.js <command> [args...]');
    console.error('\nAvailable commands:');
    console.error('  teams');
    console.error('  projects [filter]');
    console.error('  createProject <name> <description> <teamIds>');
    console.error('  issues [filter]');
    console.error('  issue <identifier>');
    console.error('  createIssue <title> <description> <teamId> [options]');
    console.error('  updateIssue <issueId> <updates>');
    console.error('  createComment <issueId> <body>');
    console.error('  states <teamId>');
    console.error('  labels <teamId>');
    console.error('  user');
    process.exit(1);
  }

  try {
    const result = await commands[command](...args);
    console.log(JSON.stringify(result, null, 2));
  } catch (error) {
    console.error('Error:', error.message);
    process.exit(1);
  }
}

if (require.main === module) {
  main();
}

module.exports = commands;


path: scripts/package-lock.json
content: {
  "name": "linear-skill-scripts",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "linear-skill-scripts",
      "version": "1.0.0",
      "dependencies": {
        "@linear/sdk": "^71.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": [REDACTED_SECRET],
      "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": "71.0.0",
      "resolved": "https://registry.npmjs.org/@linear/sdk/-/sdk-71.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@graphql-typed-document-node/core": "^3.2.0"
      },
      "engines": {
        "node": ">=18.x"
      }
    },
    "node_modules/graphql": {
      "version": "16.12.0",
      "resolved": "https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "peer": true,
      "engines": {
        "node": "^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0"
      }
    }
  }
}


path: scripts/package.json
content: {
  "name": "linear-skill-scripts",
  "version": "1.0.0",
  "private": true,
  "dependencies": {
    "@linear/sdk": "^71.0.0"
  }
}


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)