mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
feat(skills/linear): add Documents support + Python helper script (#20752)
* feat(skills/linear): add Documents support + Python helper script The bundled Linear skill (PR #1230) covered issues, projects, teams, and workflow states via curl. It had no coverage for Linear's Documents API, so fetching an RFC/doc from a linear.app URL required hand-writing GraphQL against an underdocumented schema. Adds: - Documents section in SKILL.md explaining slugId extraction from URLs, the contentState (markdown) vs contentState (ProseMirror) split, and four canonical curl examples (fetch by slugId, fetch by UUID, list recent, title-search). - scripts/linear_api.py — stdlib-only Python CLI wrapping the most common operations (whoami, list-teams, list/get/search/create/update issues, add-comment, update-status, list/get/search documents, raw GraphQL passthrough). Zero deps, reads LINEAR_API_KEY from env. Auth header quirk (personal key takes bare $LINEAR_API_KEY, no Bearer prefix) is already documented in the skill. Found during RFC review: the existing skill's lack of document support forced falling back to the browser (which hit Linear's login wall). Also fixes a schema gotcha — the Document field is `contentState`, not `contentData` (which returns 400). Tested end-to-end against the production API: python3 linear_api.py whoami python3 linear_api.py get-document 38359beef67c Both return expected payloads. * fix(skills/linear): point LINEAR_API_KEY setup to the correct page The org-level Settings > API page (/settings/api) only shows OAuth apps and workspace-member keys. Personal API keys live under Account, Security, access (/settings/account/security). Update both the setup link in config.py (shown during hermes setup) and the setup step in SKILL.md so users land on the page that can create a personal key.
This commit is contained in:
parent
b62a82e0c3
commit
ad7aad251c
4 changed files with 612 additions and 3 deletions
|
|
@ -33,7 +33,7 @@ Manage Linear issues, projects, and teams directly via the GraphQL API using `cu
|
|||
|
||||
## Setup
|
||||
|
||||
1. Get a personal API key from **Linear Settings > API > Personal API keys**
|
||||
1. Get a personal API key from **Linear Settings > Account > Security & access > Personal API keys** (URL: https://linear.app/settings/account/security). Note: the org-level *Settings > API* page only shows OAuth apps and workspace-member keys, not personal keys.
|
||||
2. Set `LINEAR_API_KEY` in your environment (via `hermes setup` or your env config)
|
||||
|
||||
## API Basics
|
||||
|
|
@ -51,6 +51,24 @@ curl -s -X POST https://api.linear.app/graphql \
|
|||
-d '{"query": "{ viewer { id name } }"}' | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Python helper script (ergonomic alternative)
|
||||
|
||||
For faster one-liners that don't need hand-written GraphQL, this skill ships a stdlib Python CLI at `scripts/linear_api.py`. Zero dependencies. Same auth (reads `LINEAR_API_KEY`).
|
||||
|
||||
```bash
|
||||
SCRIPT=$(dirname "$(find ~/.hermes -path '*skills/productivity/linear/scripts/linear_api.py' 2>/dev/null | head -1)")/linear_api.py
|
||||
|
||||
python3 "$SCRIPT" whoami
|
||||
python3 "$SCRIPT" list-teams
|
||||
python3 "$SCRIPT" get-issue ENG-42
|
||||
python3 "$SCRIPT" get-document 38359beef67c # fetch a doc by slugId from the URL
|
||||
python3 "$SCRIPT" raw 'query { viewer { name } }'
|
||||
```
|
||||
|
||||
All subcommands: `whoami`, `list-teams`, `list-projects`, `list-states`, `list-issues`, `get-issue`, `search-issues`, `create-issue`, `update-issue`, `update-status`, `add-comment`, `list-documents`, `get-document`, `search-documents`, `raw`. Run with `--help` for flags.
|
||||
|
||||
Use the script when: you want a quick answer without crafting GraphQL. Use curl when: you need a query the script doesn't wrap, or you want to compose filters inline.
|
||||
|
||||
## Workflow States
|
||||
|
||||
Linear uses `WorkflowState` objects with a `type` field. **6 state types:**
|
||||
|
|
@ -260,6 +278,70 @@ curl -s -X POST https://api.linear.app/graphql \
|
|||
}' | python3 -m json.tool
|
||||
```
|
||||
|
||||
## Documents
|
||||
|
||||
Linear **Documents** are prose docs (RFCs, specs, notes) stored alongside issues. They have their own `documents` root query and `document(id:)` single-fetch.
|
||||
|
||||
### Document URLs and `slugId`
|
||||
|
||||
Document URLs look like:
|
||||
```
|
||||
https://linear.app/<workspace>/document/<slug>-<hexSlugId>
|
||||
```
|
||||
|
||||
The trailing hex segment is the `slugId`. Example: `https://linear.app/nousresearch/document/rfc-hermes-permission-gateway-discord-38359beef67c` → `slugId` is `38359beef67c`.
|
||||
|
||||
**Important schema detail:** the Markdown body is in the `content` field. The ProseMirror JSON is in `contentState` (not `contentData` — that field does not exist and the API returns 400).
|
||||
|
||||
### Fetch a document by slugId
|
||||
|
||||
`document(id:)` only accepts UUIDs. To fetch by the URL's hex slug, filter the collection:
|
||||
|
||||
```bash
|
||||
curl -s -X POST https://api.linear.app/graphql \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "query($s: String!) { documents(filter: { slugId: { eq: $s } }, first: 1) { nodes { id title content contentState slugId url creator { name } project { name } updatedAt } } }", "variables": {"s": "38359beef67c"}}' \
|
||||
| python3 -m json.tool
|
||||
```
|
||||
|
||||
Or via the Python helper:
|
||||
```bash
|
||||
python3 scripts/linear_api.py get-document 38359beef67c
|
||||
```
|
||||
|
||||
### Fetch a document by UUID
|
||||
|
||||
```bash
|
||||
curl -s -X POST https://api.linear.app/graphql \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ document(id: \"11700cff-b514-4db3-afcc-3ed1afacba1c\") { title content url } }"}' \
|
||||
| python3 -m json.tool
|
||||
```
|
||||
|
||||
### List recent documents
|
||||
|
||||
```bash
|
||||
curl -s -X POST https://api.linear.app/graphql \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ documents(first: 25, orderBy: updatedAt) { nodes { id title slugId url updatedAt project { name } } } }"}' \
|
||||
| python3 -m json.tool
|
||||
```
|
||||
|
||||
### Search documents by title
|
||||
|
||||
Linear's schema has no `searchDocuments` root. Use a title-substring filter instead:
|
||||
|
||||
```bash
|
||||
curl -s -X POST https://api.linear.app/graphql \
|
||||
-H "Authorization: $LINEAR_API_KEY" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"query": "{ documents(filter: { title: { containsIgnoreCase: \"RFC\" } }, first: 25) { nodes { title slugId url } } }"}' \
|
||||
| python3 -m json.tool
|
||||
```
|
||||
|
||||
## Pagination
|
||||
|
||||
Linear uses Relay-style cursor pagination:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue