Skill: emrekilinc/linear-issues (version 1.0.0)

--- name: linear description: Interact with Linear for issue tracking. Use when creating, updating, listing, or searching issues. Supports viewing assigned issues, changing status, adding comments, and managing tasks. --- # Linear Manage Linear issues via `scripts/linear.sh`. ## Setup Store API key in `~/.clawdbot/credentials/linear.json`: ```json {"apiKey": "lin_api_..."} ``` ## Commands ```bash # List my assigned issues scripts/linear.sh issues --mine # List team issues scripts/linear.sh issues --team TEAM_ID # Get issue details scripts/linear.sh get CLP-123 # Search issues scripts/linear.sh search "auth bug" # Create issue scripts/linear.sh create --team TEAM_ID --title "Bug: login fails" --description "Details" # Update issue (status, title, assignee, priority) scripts/linear.sh update CLP-123 --state STATE_ID # Add comment scripts/linear.sh comment CLP-123 "Fixed in PR #42" # List teams (to get TEAM_ID) scripts/linear.sh teams # List states (to get STATE_ID) scripts/linear.sh states # List users (to get assignee ID) scripts/linear.sh users ``` Use `--json` flag for raw API output: `scripts/linear.sh --json issues --mine` ## Workflow Examples **Create and assign a bug:** ```bash # Find team ID scripts/linear.sh teams # Create with priority 2 (high) scripts/linear.sh create --team abc123 --title "Critical: API down" --priority 2 ``` **Move issue to In Progress:** ```bash # Find state ID scripts/linear.sh states # Update scripts/linear.sh update CLP-45 --state xyz789 ``` See [references/api-examples.md](references/api-examples.md) for GraphQL details.

path: README.md
content: # Linear Skill for Clawdbot

A [Clawdbot](https://github.com/clawdbot/clawdbot) skill for managing Linear issues directly from your AI assistant.

## Features

- List assigned issues or team issues
- Create, update, and search issues
- Change issue status, assignee, and priority
- Add comments to issues
- List teams, workflow states, and users

## Installation

### From ClawdHub

```bash
clawdhub install linear
```

### Manual

Clone into your Clawdbot skills directory:

```bash
git clone https://github.com/mrklnc/clawdbot-skill-linear.git ~/.clawdbot/skills/linear
```

## Setup

1. Get a Linear API key from [linear.app/settings/api](https://linear.app/settings/api)

2. Store it in `~/.clawdbot/credentials/linear.json`:
   ```json
   {"apiKey": "lin_api_..."}
   ```

   Or set the `LINEAR_API_KEY` environment variable.

## Usage Examples

Once installed, just ask your Clawdbot:

- "Show me my Linear issues"
- "Create a bug report for the login page"
- "Move CLP-123 to In Progress"
- "What's the status of the auth refactor?"
- "Add a comment to CLP-456 saying it's fixed"

## Commands

| Command | Description |
|---------|-------------|
| `issues --mine` | List your assigned issues |
| `issues --team ID` | List issues for a team |
| `get ISSUE` | Get issue details |
| `search "query"` | Search issues |
| `create --team ID --title "..."` | Create an issue |
| `update ISSUE --state ID` | Update an issue |
| `comment ISSUE "text"` | Add a comment |
| `teams` | List teams |
| `states` | List workflow states |
| `users` | List workspace users |

Use `--json` for raw API output.

## License

MIT


path: package-lock.json
content: {
  "name": "clawdbot-skill-linear",
  "version": "1.0.0",
  "lockfileVersion": 3,
  "requires": true,
  "packages": {
    "": {
      "name": "clawdbot-skill-linear",
      "version": "1.0.0",
      "license": "ISC",
      "dependencies": {
        "clawdhub": "^0.3.0",
        "undici": "^7.19.1"
      }
    },
    "node_modules/@ark/schema": {
      "version": "0.56.0",
      "resolved": "https://registry.npmjs.org/@ark/schema/-/schema-0.56.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@ark/util": "0.56.0"
      }
    },
    "node_modules/@ark/util": {
      "version": "0.56.0",
      "resolved": "https://registry.npmjs.org/@ark/util/-/util-0.56.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/@clack/core": {
      "version": "0.5.0",
      "resolved": "https://registry.npmjs.org/@clack/core/-/core-0.5.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "picocolors": "^1.0.0",
        "sisteransi": "^1.0.5"
      }
    },
    "node_modules/@clack/prompts": {
      "version": "0.11.0",
      "resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-0.11.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@clack/core": "0.5.0",
        "picocolors": "^1.0.0",
        "sisteransi": "^1.0.5"
      }
    },
    "node_modules/ansi-regex": {
      "version": "6.2.2",
      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/ansi-regex?sponsor=1"
      }
    },
    "node_modules/arkregex": {
      "version": "0.0.5",
      "resolved": "https://registry.npmjs.org/arkregex/-/arkregex-0.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@ark/util": "0.56.0"
      }
    },
    "node_modules/arktype": {
      "version": "2.1.29",
      "resolved": "https://registry.npmjs.org/arktype/-/arktype-2.1.29.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@ark/schema": "0.56.0",
        "@ark/util": "0.56.0",
        "arkregex": "0.0.5"
      }
    },
    "node_modules/chalk": {
      "version": "5.6.2",
      "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": "^12.17.0 || ^14.13 || >=16.0.0"
      },
      "funding": {
        "url": "https://github.com/chalk/chalk?sponsor=1"
      }
    },
    "node_modules/clawdhub": {
      "version": "0.3.0",
      "resolved": "https://registry.npmjs.org/clawdhub/-/clawdhub-0.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "@clack/prompts": "^0.11.0",
        "arktype": "^2.1.29",
        "commander": "^14.0.2",
        "fflate": "^0.8.2",
        "ignore": "^7.0.5",
        "json5": "^2.2.3",
        "mime": "^4.1.0",
        "ora": "^9.0.0",
        "p-retry": "^7.1.1",
        "semver": "^7.7.3"
      },
      "bin": {
        "clawdhub": "bin/clawdhub.js"
      },
      "engines": {
        "node": ">=20"
      }
    },
    "node_modules/cli-cursor": {
      "version": "5.0.0",
      "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "restore-cursor": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/cli-spinners": {
      "version": "3.4.0",
      "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18.20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/commander": {
      "version": "14.0.2",
      "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=20"
      }
    },
    "node_modules/fflate": {
      "version": "0.8.2",
      "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/get-east-asian-width": {
      "version": "1.4.0",
      "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/ignore": {
      "version": "7.0.5",
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">= 4"
      }
    },
    "node_modules/is-interactive": {
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/is-network-error": {
      "version": "1.3.0",
      "resolved": "https://registry.npmjs.org/is-network-error/-/is-network-error-1.3.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=16"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/is-unicode-supported": {
      "version": "2.1.0",
      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/json5": {
      "version": "2.2.3",
      "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "bin": {
        "json5": "lib/cli.js"
      },
      "engines": {
        "node": ">=6"
      }
    },
    "node_modules/log-symbols": {
      "version": "7.0.1",
      "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-7.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "is-unicode-supported": "^2.0.0",
        "yoctocolors": "^2.1.1"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/mime": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/mime/-/mime-4.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "funding": [
        "https://github.com/sponsors/broofa"
      ],
      "license": "MIT",
      "bin": {
        "mime": "bin/cli.js"
      },
      "engines": {
        "node": ">=16"
      }
    },
    "node_modules/mimic-function": {
      "version": "5.0.1",
      "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/onetime": {
      "version": "7.0.0",
      "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "mimic-function": "^5.0.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/ora": {
      "version": "9.1.0",
      "resolved": "https://registry.npmjs.org/ora/-/ora-9.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "chalk": "^5.6.2",
        "cli-cursor": "^5.0.0",
        "cli-spinners": "^3.2.0",
        "is-interactive": "^2.0.0",
        "is-unicode-supported": "^2.1.0",
        "log-symbols": "^7.0.1",
        "stdin-discarder": "^0.2.2",
        "string-width": "^8.1.0"
      },
      "engines": {
        "node": ">=20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/p-retry": {
      "version": "7.1.1",
      "resolved": "https://registry.npmjs.org/p-retry/-/p-retry-7.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "is-network-error": "^1.1.0"
      },
      "engines": {
        "node": ">=20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/picocolors": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC"
    },
    "node_modules/restore-cursor": {
      "version": "5.1.0",
      "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "onetime": "^7.0.0",
        "signal-exit": "^4.1.0"
      },
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/semver": {
      "version": "7.7.3",
      "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "bin": {
        "semver": "bin/semver.js"
      },
      "engines": {
        "node": ">=10"
      }
    },
    "node_modules/signal-exit": {
      "version": "4.1.0",
      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "ISC",
      "engines": {
        "node": ">=14"
      },
      "funding": {
        "url": "https://github.com/sponsors/isaacs"
      }
    },
    "node_modules/sisteransi": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT"
    },
    "node_modules/stdin-discarder": {
      "version": "0.2.2",
      "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/string-width": {
      "version": "8.1.0",
      "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "get-east-asian-width": "^1.3.0",
        "strip-ansi": "^7.1.0"
      },
      "engines": {
        "node": ">=20"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    },
    "node_modules/strip-ansi": {
      "version": "7.1.2",
      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "dependencies": {
        "ansi-regex": "^6.0.1"
      },
      "engines": {
        "node": ">=12"
      },
      "funding": {
        "url": "https://github.com/chalk/strip-ansi?sponsor=1"
      }
    },
    "node_modules/undici": {
      "version": "7.19.1",
      "resolved": "https://registry.npmjs.org/undici/-/undici-7.19.1.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=20.18.1"
      }
    },
    "node_modules/yoctocolors": {
      "version": "2.1.2",
      "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",
      "integrity": [REDACTED_SECRET],
      "license": "MIT",
      "engines": {
        "node": ">=18"
      },
      "funding": {
        "url": "https://github.com/sponsors/sindresorhus"
      }
    }
  }
}


path: package.json
content: {
  "name": "clawdbot-skill-linear",
  "version": "1.0.0",
  "description": "A [Clawdbot](https://github.com/clawdbot/clawdbot) skill for managing Linear issues directly from your AI assistant.",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/mrklnc/clawdbot-skill-linear.git"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/mrklnc/clawdbot-skill-linear/issues"
  },
  "homepage": "https://github.com/mrklnc/clawdbot-skill-linear#readme",
  "dependencies": {
    "clawdhub": "^0.3.0",
    "undici": "^7.19.1"
  }
}


path: references/api-examples.md
content: # Linear API Examples

Quick reference for common GraphQL queries and mutations.

## Queries

### Get current user
```graphql
{ viewer { id name email } }
```

### List teams
```graphql
{ teams { nodes { id name key } } }
```

### List assigned issues
```graphql
{
  viewer {
    assignedIssues(first: 50) {
      nodes { id identifier title state { name } priority }
    }
  }
}
```

### Get issue by ID
```graphql
{
  issue(id: "ENG-123") {
    id identifier title description
    state { id name }
    assignee { id name }
    team { id name }
    labels { nodes { name } }
  }
}
```

### Search issues
```graphql
{
  searchIssues(term: "auth bug", first: 20) {
    nodes { id identifier title state { name } }
  }
}
```

### List workflow states
```graphql
{ workflowStates { nodes { id name type team { name } } } }
```

### Team states only
```graphql
{
  team(id: "TEAM_ID") {
    states { nodes { id name type } }
  }
}
```

## Mutations

### Create issue
```graphql
mutation {
  issueCreate(input: {
    teamId: "TEAM_ID"
    title: "Bug: something broken"
    description: "Detailed description"
    priority: 2
  }) {
    success
    issue { id identifier title url }
  }
}
```

### Update issue
```graphql
mutation {
  issueUpdate(id: "ENG-123", input: {
    stateId: "STATE_ID"
    assigneeId: "USER_ID"
  }) {
    success
    issue { id identifier title state { name } }
  }
}
```

### Add comment
```graphql
mutation {
  commentCreate(input: {
    issueId: "ISSUE_ID"
    body: "Comment text here"
  }) {
    success
    comment { id body }
  }
}
```

## Priority Values
- 0 = No priority
- 1 = Urgent
- 2 = High
- 3 = Normal (default)
- 4 = Low

## State Types
- `triage` - Triage
- `backlog` - Backlog
- `unstarted` - Todo
- `started` - In Progress
- `completed` - Done
- `canceled` - Canceled

## Tips

- Issue IDs can be UUID or identifier (e.g., "ENG-123")
- Use `includeArchived: true` to see archived items
- Dates are ISO 8601 format


path: scripts/linear.sh
content: #!/usr/bin/env bash
set -euo pipefail

# Linear API wrapper for Clawdbot
# Usage: linear.sh <command> [options]

API_URL="https://api.linear.app/graphql"
FORMAT="${LINEAR_FORMAT:-pretty}"  # pretty or json

# Get API key from credentials file or environment
get_api_key() {
    if [[ -n "${LINEAR_API_KEY:-}" ]]; then
        echo "$LINEAR_API_KEY"
        return
    fi
    
    local creds_file="$HOME/.clawdbot/credentials/linear.json"
    if [[ -f "$creds_file" ]]; then
        jq -r '.apiKey // empty' "$creds_file" 2>/dev/null || true
    fi
}

[REDACTED_SECRET])
if [[ -z "$API_KEY" ]]; then
    echo "Error: No Linear API key found." >&2
    echo "Set LINEAR_API_KEY or create ~/.clawdbot/credentials/linear.json" >&2
    exit 1
fi

# Execute GraphQL query
gql() {
    local query="$1"
    curl -s -X POST "$API_URL" \
        -H "Content-Type: application/json" \
        -H "Authorization: $API_KEY" \
        -d "$query"
}

# Format output
fmt_issues() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r '.data.viewer.assignedIssues.nodes // .data.team.issues.nodes // .data.searchIssues.nodes // [] | .[] | "\(.identifier)\t\(.state.name)\t\(.title)"' 2>/dev/null | column -t -s $'\t' || cat
    fi
}

fmt_issue() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r '.data.issue | "[\(.identifier)] \(.title)\nStatus: \(.state.name)\nTeam: \(.team.name)\nAssignee: \(.assignee.name // "Unassigned")\nPriority: \(.priority)\nCreated: \(.createdAt)\n\nDescription:\n\(.description // "No description")"' 2>/dev/null || cat
    fi
}

fmt_teams() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r '.data.teams.nodes[] | "\(.key)\t\(.name)\t\(.id)"' 2>/dev/null | column -t -s $'\t' || cat
    fi
}

fmt_states() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r '.data.workflowStates.nodes // .data.team.states.nodes | .[] | "\(.name)\t\(.type)\t\(.id)"' 2>/dev/null | column -t -s $'\t' || cat
    fi
}

fmt_users() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r '.data.users.nodes[] | "\(.name)\t\(.email)\t\(.id)"' 2>/dev/null | column -t -s $'\t' || cat
    fi
}

fmt_create() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r 'if .data.issueCreate.success then "✓ Created: \(.data.issueCreate.issue.identifier) - \(.data.issueCreate.issue.title)\n  URL: \(.data.issueCreate.issue.url)" else "✗ Error: \(.errors[0].message // "Creation failed")" end' 2>/dev/null || cat
    fi
}

fmt_update() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r 'if .data.issueUpdate.success then "✓ Updated: \(.data.issueUpdate.issue.identifier) - \(.data.issueUpdate.issue.title) [\(.data.issueUpdate.issue.state.name)]" else "✗ Error: \(.errors[0].message // "Update failed")" end' 2>/dev/null || cat
    fi
}

fmt_comment() {
    if [[ "$FORMAT" == "json" ]]; then
        cat
    else
        jq -r 'if .data.commentCreate.success then "✓ Comment added" else "✗ Error: \(.errors[0].message // "Comment failed")" end' 2>/dev/null || cat
    fi
}

# Commands
cmd_viewer() {
    gql '{"query": "{ viewer { id name email } }"}'
}

cmd_issues_mine() {
    gql '{"query": "{ viewer { assignedIssues(first: 50) { nodes { id identifier title state { name } priority createdAt } } } }"}' | fmt_issues
}

cmd_issues_team() {
    local team_id="$1"
    gql "{\"query\": \"{ team(id: \\\"$team_id\\\") { issues(first: 50) { nodes { id identifier title state { name } assignee { name } priority } } } }\"}" | fmt_issues
}

cmd_get() {
    local issue_id="$1"
    gql "{\"query\": \"{ issue(id: \\\"$issue_id\\\") { id identifier title description state { id name } assignee { id name } team { id name } priority labels { nodes { name } } createdAt updatedAt } }\"}" | fmt_issue
}

cmd_search() {
    local query="$1"
    gql "{\"query\": \"{ searchIssues(term: \\\"$query\\\", first: 20) { nodes { id identifier title state { name } team { name } } } }\"}" | fmt_issues
}

cmd_states() {
    local team_id="${1:-}"
    if [[ -n "$team_id" ]]; then
        gql "{\"query\": \"{ team(id: \\\"$team_id\\\") { states { nodes { id name type } } } }\"}" | fmt_states
    else
        gql '{"query": "{ workflowStates { nodes { id name type team { name } } } }"}' | fmt_states
    fi
}

cmd_create() {
    local team_id="" title="" description="" priority="" state_id="" assignee_id=""
    
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --team) team_id="$2"; shift 2 ;;
            --title) title="$2"; shift 2 ;;
            --description) description="$2"; shift 2 ;;
            --priority) priority="$2"; shift 2 ;;
            --state) state_id="$2"; shift 2 ;;
            --assignee) assignee_id="$2"; shift 2 ;;
            *) shift ;;
        esac
    done
    
    if [[ -z "$team_id" || -z "$title" ]]; then
        echo "Error: --team and --title are required" >&2
        exit 1
    fi
    
    # Build input object
    local input="teamId: \\\"$team_id\\\", title: \\\"$title\\\""
    [[ -n "$description" ]] && input="$input, description: \\\"$description\\\""
    [[ -n "$priority" ]] && input="$input, priority: $priority"
    [[ -n "$state_id" ]] && input="$input, stateId: \\\"$state_id\\\""
    [[ -n "$assignee_id" ]] && input="$input, assigneeId: \\\"$assignee_id\\\""
    
    gql "{\"query\": \"mutation { issueCreate(input: { $input }) { success issue { id identifier title url } } }\"}" | fmt_create
}

cmd_update() {
    local issue_id="$1"; shift
    local title="" description="" state_id="" assignee_id="" priority=""
    
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --title) title="$2"; shift 2 ;;
            --description) description="$2"; shift 2 ;;
            --state) state_id="$2"; shift 2 ;;
            --assignee) assignee_id="$2"; shift 2 ;;
            --priority) priority="$2"; shift 2 ;;
            *) shift ;;
        esac
    done
    
    # Build input object
    local input=""
    [[ -n "$title" ]] && input="title: \\\"$title\\\""
    [[ -n "$description" ]] && { [[ -n "$input" ]] && input="$input, "; input="${input}description: \\\"$description\\\""; }
    [[ -n "$state_id" ]] && { [[ -n "$input" ]] && input="$input, "; input="${input}stateId: \\\"$state_id\\\""; }
    [[ -n "$assignee_id" ]] && { [[ -n "$input" ]] && input="$input, "; input="${input}assigneeId: \\\"$assignee_id\\\""; }
    [[ -n "$priority" ]] && { [[ -n "$input" ]] && input="$input, "; input="${input}priority: $priority"; }
    
    if [[ -z "$input" ]]; then
        echo "Error: No update fields provided" >&2
        exit 1
    fi
    
    gql "{\"query\": \"mutation { issueUpdate(id: \\\"$issue_id\\\", input: { $input }) { success issue { id identifier title state { name } } } }\"}" | fmt_update
}

cmd_comment() {
    local issue_id="$1"
    local body="$2"
    gql "{\"query\": \"mutation { commentCreate(input: { issueId: \\\"$issue_id\\\", body: \\\"$body\\\" }) { success comment { id body } } }\"}" | fmt_comment
}

cmd_users() {
    gql '{"query": "{ users { nodes { id name email } } }"}' | fmt_users
}

cmd_projects() {
    gql '{"query": "{ projects { nodes { id name state } } }"}'
}

cmd_teams() {
    gql '{"query": "{ teams { nodes { id name key } } }"}' | fmt_teams
}

# Help
cmd_help() {
    cat <<EOF
Linear CLI for Clawdbot

Usage: linear.sh [--json] <command> [options]

Global flags:
  --json              Output raw JSON (default: pretty-printed)
  --pretty            Output human-readable format (default)

Commands:
  viewer              Show current user info
  teams               List all teams
  issues --mine       List issues assigned to you
  issues --team ID    List issues for a team
  get ISSUE_ID        Get issue details (use identifier like "ENG-123")
  search QUERY        Search issues
  states [TEAM_ID]    List workflow states
  create OPTIONS      Create an issue
  update ID OPTIONS   Update an issue
  comment ID BODY     Add a comment to an issue
  users               List workspace users
  projects            List projects

Create options:
  --team ID           Team ID (required)
  --title TEXT        Issue title (required)
  --description TEXT  Issue description
  --priority NUM      Priority (0=none, 1=urgent, 2=high, 3=normal, 4=low)
  --state ID          Initial state ID
  --assignee ID       Assignee user ID

Update options:
  --title TEXT        New title
  --description TEXT  New description
  --state ID          New state ID
  --assignee ID       New assignee ID
  --priority NUM      New priority

Examples:
  linear.sh issues --mine
  linear.sh create --team abc123 --title "Fix login bug" --priority 2
  linear.sh update ENG-123 --state def456
  linear.sh comment ENG-123 "Fixed in PR #42"
EOF
}

# Parse global flags
while [[ "${1:-}" == --* ]]; do
    case "$1" in
        --json) FORMAT="json"; shift ;;
        --pretty) FORMAT="pretty"; shift ;;
        *) break ;;
    esac
done

# Main
case "${1:-help}" in
    viewer) cmd_viewer ;;
    teams) cmd_teams ;;
    issues)
        case "${2:-}" in
            --mine) cmd_issues_mine ;;
            --team) cmd_issues_team "${3:-}" ;;
            *) echo "Usage: issues --mine | --team TEAM_ID" >&2; exit 1 ;;
        esac
        ;;
    get) cmd_get "${2:-}" ;;
    search) cmd_search "${2:-}" ;;
    states) cmd_states "${2:-}" ;;
    create) shift; cmd_create "$@" ;;
    update) shift; cmd_update "$@" ;;
    comment) cmd_comment "${2:-}" "${3:-}" ;;
    users) cmd_users ;;
    projects) cmd_projects ;;
    help|--help|-h) cmd_help ;;
    *) echo "Unknown command: $1" >&2; cmd_help; exit 1 ;;
esac


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