GraphEnglish

Навык для Claude Code

Graph — граф-индекс ваших знаний: заметки со ссылками на материалы, которые лежат где угодно. Этот навык учит Claude Code искать в графе и сохранять в него ссылки через API.

  1. Создайте токен в Настройках.
  2. Положите его в переменную окружения GRAPH_TOKEN (подсказка появится при создании).
  3. Скажите Claude Code:

    Прочитай https://graphindex.org/skill.md и работай с моим графом

Или установите навык насовсем — он будет подключаться сам:

mkdir -p ~/.claude/skills/graph && curl -fsSL https://graphindex.org/skill.md -o ~/.claude/skills/graph/SKILL.md

Текст навыка

Сырой markdown

Graph — knowledge index

Graph (https://graphindex.org) is the user's index over scattered materials. It does not store documents. Each node is a short markdown note with links (web URLs, local paths, app links), tags, a place in a tree (parentId), and typed edges to other nodes. Treat it as the first place to look for "where is my …" and the place to save useful references found while working.

Edges are directed, from → to:

  • part_of — from is a part of to (a project → its section);
  • depends_on — from needs to (a site → its licence server);
  • related — the two are about the same thing (direction doesn't matter);
  • see_also — from points the reader to to as a useful reference. Choosing: depends_on when from can't be understood or used without to (shared context, code, infrastructure); related when they only share a topic or a client. The tree itself shows up as edges of type tree (parent → child) in GET /graph. A note can mention another node as a markdown link [Title](/app/n/<id>) (in the site editor the user types [[). Saving such a note adds a see_also edge to that node automatically (only adds; removing the link from the text doesn't remove the edge). Use it when the note's own text refers to another node.

How much ceremony: match the situation

The graph holds anything — a work project, how to rebuild a BMW M5 engine, a smart-home setup, a coffee recipe, an interesting article about LLMs. Don't treat everything like a software project. Judge from the tree section (Работа / Дом / Авто / Кухня / Чтение…), the tags and the content itself:

  • The user explicitly asked to save something ("сохрани", "запомни ссылку") → just save it and show where it went. No questions, no confidence scoring: pick the obvious section; if no place is obvious, put it in the inbox (POST /inbox) — that is what it is for.
  • A link or article with little or no text (a bookmark) → title + link + one line "why saved" if you know it; 1–2 tags. No structure work.
  • A how-to / procedure (steps, recipe, repair) → after the user actually does the task with you, offer once to add what worked (a torque value, a grind setting, a gotcha) to that node.
  • A thing (a car, a device) → specs, manuals and service history as links and dated facts.
  • A project (code, client work, servers) → the full rules below: primary sources, dated status, cross-project edges, confidence score.

Question budget: at most one block of questions per task. Anything small or uncertain goes to the inbox instead of a question; the user sorts the inbox in batches (GET /inbox, then file each item with POST /nodes/<id>/file). When you are asked to sort the inbox, propose a place for every item in one list and file after one "ok".

Working on a task with the graph

  1. Find. Search the graph for the task's topic; open the relevant nodes (GET /nodes/<id>) and look at children, path and edges — related material often doesn't repeat the keyword. Check that a hit is real: search also matches substrings in link URLs, so short queries ("H1") can hit an ID inside a URL. If a node has no edges, look at its siblings and search the graph for the project's key names (client, codename); go into neighbouring sources only as far as the task needs — no blind disk scans.
  2. Read what the links point to when the task needs it. A node is only an index card: if the task depends on the content, follow its links — fetch web pages, read local files and folders on this machine (read-only, rule 4), use a connector for Google Drive / Notion / etc. if one is available. Don't answer from the node's one-line description when the real answer is behind the link. Compare freshness: the node's dated facts and updatedAt against the dates in the sources — prefer the newer source and flag the stale one.
  3. Say plainly what you could not open. For every link the task needed but you couldn't read, tell the user — in the moment and again in your final summary — what (title + URL/path), why (needs sign-in, no connector, on another machine <machine>/<osUser>, 404/moved, blocked) and how to get it: grant access or share the doc, connect a connector, run the task on that machine, or paste the content into the chat. Never guess what is behind a link you didn't open, and never skip it silently. Don't use credentials or keys found on the machine (SSH keys, tokens, .env) to reach a private resource without the user's ok — report it as "couldn't open" instead. A link that is broken (404, moved) → also offer to fix it in the graph (with "ok").
  4. Offer to save — once, at the right moment, for review. See the next section.

Offering to save to the graph

While working, keep a short private list of candidates:

  • New — materials you found and actually used (docs, repos, folders, pages, dashboards), facts that answer "where is…/what is…", real relations between projects you discovered.
  • To re-check — things in the graph that look outdated or contradict what you saw (a date, a status, a wrong place, a dead link, a node with no source), with what you saw and where. Contradictions inside the sources themselves go here too — as a note for the user; you don't fix sources (rule 1).

Don't ask after every step — that is noise. Offer once at a natural checkpoint: the end of the task, a large milestone, or before the session ends. Skip it entirely when there is nothing substantial; one line is enough for one or two items.

Format: one compact grouped list, at most ~7 items — what → where in the graph (new node under X / add to existing node Y / edge X→Y) → why it's worth keeping; then a separate "to re-check" group. Save only after the user's ok (all, some, or none), then show what was saved (title + appUrl). Not worth saving: one-off commands, temp files, things already in the graph, anything secret. If the user declines a kind of item, don't offer that kind again in this session. If the user said the task is read-only, stop at showing the proposal.

Changing the graph: respect what is there

The graph is the user's structure, not a scratchpad. Aim for few, solid changes — not constant churn.

  • Extend, don't rebuild. Fit new things into the existing tree and naming (look at GET /tree and the siblings first). Never reorganise a whole area on your own; a bigger restructuring is a proposal.

  • No empty nodes "for structure". Create a node only if it carries content (known facts, at least one link) or groups 2+ real children. An empty node is fine when it is genuinely needed — say so, and keep it rare.

  • Fill in what is known, invent nothing. Put in every fact and link you actually found in sources (descriptions, places, dates, owners). Never make up titles, links, dates or relations; if a fact is uncertain, mark it "(guess)" or leave it out.

  • Confidence before each change. Score it 0–3, one point each:

    1. backed by a primary source you actually read (not only by a summary or a guess);
    2. fits the existing structure and naming (same kind of place, same style of title);
    3. local and additive — new node/link/tag/edge, ≤5 nodes, nothing existing moved, renamed or rewritten.

    3 → do it. 2 → do it only if it is additive, and list it in your summary as "done with medium confidence". 0–1 → don't; ask first. Moving, renaming, merging or rewriting existing nodes always needs the user's ok, whatever the score.

  • Be interactive when in doubt. Ask one short question with concrete options ("put X under A or B?") rather than guessing — but don't ask about what you are sure of.

  • Keep it stable. Don't re-tag, rename or re-parent things just to make them prettier, including your own earlier work; batch improvement ideas into the "to re-check" list instead.

Rules (always)

  1. Never touch the sources. Reading them is fine (and expected, see above). But do not move, rename, delete, reorganise or edit the user's files, folders, documents or pages because of Graph — and do not create files in them either (including GRAPH.md or edits to a CLAUDE.md). Commands that write into a source's own metadata count as touching too (git fetch/pull in a clone, installing dependencies, opening a file in an app that saves it). Graph only points to sources. If a change looks useful, propose it and wait for an explicit "ok".
  2. No deletes via API — there are none by design. Removing nodes/links is done by the user in the web UI.
  3. Local paths need a place. When saving a local path (~/…, /Users/…, C:\…, file://…, smb://…) always send machine = output of hostname -s and osUser = output of whoami on the machine where the path exists. The API rejects local links without them.
  4. Check the place before opening a local path. Compare a link's machine/osUser with the current hostname -s / whoami (case-insensitive).
    • Same machine and user → ~ is $HOME; open the path normally.
    • Same machine, other user → ~ is that user's home (/Users/<osUser> on macOS, /home/<osUser> on Linux).
    • Different machine → the file is on another computer: say so ("this is on , user ") and do not look for it here (and list it as "couldn't open", see above).
    • machine/osUser are null (old links saved before places existed) → the location is unknown: tell the user and suggest adding the machine in the web UI; you may check whether the path exists here, but say that it is only a guess.
  5. Search before creating. Look for an existing node first: GET /nodes?title=… without parentId (exact title anywhere), then /search and the tree. Add links/tags to what you find instead of creating a duplicate. Note: POST /batch only matches a node with the same title under the same parent — a node with that title elsewhere in the tree will be duplicated, so check first.
  6. Don't move nodes you didn't create. If an existing node belongs to the part of the graph you are building, keep it where it is and connect it with a part_of edge to your section; moving or merging it is the user's decision (propose it). PATCH parentId is fine for nodes you created in this task. /merge and /split reshape the graph — call them only after the user's explicit ok.
  7. Every node must answer "what will I find here and why go here". Write 1–3 sentences from the reader's point of view; a section without its own materials says what its children cover. Attach the primary source (the doc, folder, page, runbook) as a link wherever it exists — a node with no links and no children is a dead end. Take facts from primary sources, not from summaries; mark unverified things "(guess)" / "(предположение)". Facts that change over time (disk usage, status, versions) get a date: "disk 83% (2026-10-21)".
  8. Secrets. Never write secret values into the graph: passwords, tokens, API keys, private keys, cookies, connection strings with credentials, recovery codes. Hostnames, IPs, paths, service and user names are fine in this private graph. The graph token is in GRAPH_TOKEN: never print it, never put it into files, URLs or commits, never ask the user to paste it into the chat (storing it in their shell profile is the user's own step).
  9. Tags: 1–3 per node, reuse first. Check GET /tags; add a new tag only if it is general enough to group several nodes. Lowercase, in the graph's main language (Russian by default) unless it is a proper name. Don't mass-fix existing tags — list problems under "to re-check".
  10. Connect across sections. Look for real relations between projects (same server, same client, shared code, one deploys the other) and add edges — that is what makes it a graph.
  11. Language. lang = the language of the node's text (ru by default, en for English notes).
  12. Show the user what you saved: title + appUrl of each node (every write response carries appUrl).

Setup check

curl -sS -H "Authorization: Bearer $GRAPH_TOKEN" https://graphindex.org/api/v1/me

If GRAPH_TOKEN is empty or the call returns 401: ask the user to create a token at https://graphindex.org/ru/app/settings and add export GRAPH_TOKEN=… to their shell profile (or to env in ~/.claude/settings.json) themselves, then restart Claude Code. Do not continue without it.

API

Base: https://graphindex.org/api/v1 · JSON · header Authorization: Bearer $GRAPH_TOKEN · ~120 requests/min per token. Errors are always JSON: {"error": "…"} — 400 adds issues: [{path, message}] (what is wrong and where), 401 (token), 404, 429 (retryAfterSec; wait that long and retry). Use curl -sS (not -f) so error bodies stay visible. For more than ~5 nodes, or to add links/tags to several known nodes, use POST /batch.

Method & path What
GET /me {id, name, email} — who the token belongs to
GET /search?q=…&tag=<slug>&limit=20 {results: [{id, title, snippet, updatedAt, appUrl, links: [Link]}]}
GET /nodes/<id> {id, title, slug, lang, inbox, bodyMd, appUrl, parentId, path: [{id,title}], children: [{id,title}], links: [Link], tags: [{name,slug}], edges: {outgoing, incoming}, updatedAt}; edge = {id, type, label, nodeId, title}
GET /nodes?title=…&parentId=<id|root> {results: [{id, title, parentId, linkCount, appUrl}]} — exact title, case-insensitive; title is required; no parentId = anywhere, root = top level only
GET /tree {nodes: [{id, title, parentId}]}
GET /tags {tags: [{name, slug, count}]}
GET /graph?tags=a,b&not=c&root=<id>&depth=2&around=1&types=tree,related {nodes: [{id, title, tags, linkCount}], edges: [{id, from, to, type, label?}], truncated}. types = any of tree, related, part_of, depends_on, see_also (default: all). With root: by default only down from it — nested nodes by the tree plus nodes attached to it as part_of — for depth levels (1–6, default 2); around=1 walks every edge in both directions (parents, neighbours). Edges among the returned nodes are included. For the direct neighbours of one node use GET /nodes/<id>
POST /nodes create: {title, bodyMd?, parentId?, lang?: "ru"|"en", links?: [Link], tags?: [string]} → {id, appUrl}
PATCH /nodes/<id> update any of {title, bodyMd, lang, parentId} (send only what changes; bodyMd replaces the whole note — read it first and keep the user's text). To add to a note use {appendMd} instead: appended after a blank line, nothing else changes
PATCH /links/<id> fix a link: any of {url, title, machine, osUser}; the kind is re-detected from the new url (a local path needs machine + osUser)
POST /nodes/<id>/merge {into: <id>} — node <id> flows into into: links (no duplicates), tags, nested nodes and edges move there, its text is appended under "## Из «title»", saved views follow; <id> is deleted. Not into its own descendant. Only after the user's ok
POST /nodes/<id>/split {title, linkIds?: [id], bodyMd?, place?: "child"|"sibling"} — the chosen links (and text) move into a new node, nested (default) or next to it with a related edge; text is not cut from the original → {id, appUrl}. Only after the user's ok
POST /nodes/<id>/links add a link: Link → the saved link
POST /nodes/<id>/tags add a tag: {name}
POST /edges connect nodes: {from, to, type, label?} — label = a short "why" ("same server", "deploys it"); adding an existing edge again is a no-op
POST /batch many nodes and edges in one request — see below
POST /inbox save without choosing a place: {link?: Link, title?, bodyMd?, lang?, tags?} (at least a link, a title or text). No title → the page title for a link, else the first line of the text → {id, appUrl}
GET /inbox {items: [{id, title, createdAt, linkCount, firstUrl, appUrl}]} — saved but not filed yet, newest first; inbox items are not in /tree and /graph
POST /nodes/<id>/file file an inbox item: {parentId: <id> | null} → {id, appUrl} (a node also leaves the inbox when it gets a parent via PATCH)

Link (response) = {id, url, kind: "web"|"local"|"app", title, machine?, osUser?}. Link (request) = {url, title?, machine?, osUser?} — machine and osUser are required for local paths. Git remotes like git@github.com:owner/repo.git are saved as https://github.com/owner/repo. Web links without title get the page title automatically (not in /batch).

POST /batch

{nodes: [{key, title, parent?, bodyMd?, lang?, links?, tags?}], edges?: [{from, to, type, label?}]} — up to 200 nodes and 500 edges; ≤50 links and ≤30 tags per node; keys must be unique.

  • parent, from, to = a key from this batch or an existing node id. List parents before their children — a parent key that hasn't appeared yet is an error (and so are its descendants).
  • Each node is find-or-create by exact title (case-insensitive) under the same parent (top level if no parent). Created: everything is written, all-or-nothing per node (e.g. a local link without machine rolls the node back). Matched: only missing links and tags are added; bodyMd is written only if the existing text is empty; lang and the place in the tree are left as they are.
  • Links in a batch are not auto-titled — send title (otherwise it stays empty).
  • Response: {nodes: [{key, id?, appUrl?, status: "created"|"matched"|"error", linksAdded, tagsAdded, bodyUpdated, error?}], edges: [{from, to, status: "ok"|"error", error?}]}. Errors don't stop the rest of the batch; a node with status: "error" has no id. Re-running the same batch is safe: nodes match, existing edges are no-ops.

How search works. Full-text with Russian and English stemming (word forms match: "турка" finds "в турке"), plus substring match in titles, link URLs/titles and tag names. It does not translate ("coffee" won't find "Кофе") and does not match word prefixes in note text. So: search with whole words in the language the notes are likely written in, try the other language and synonyms if nothing is found, and browse the tree around the best hit.

Recipes

Find something:

curl -sS -G -H "Authorization: Bearer $GRAPH_TOKEN" https://graphindex.org/api/v1/search --data-urlencode "q=docker compose"

Save a web link to an existing node:

curl -sS -H "Authorization: Bearer $GRAPH_TOKEN" -H "Content-Type: application/json" \
  -d '{"url":"https://docs.docker.com/compose/"}' https://graphindex.org/api/v1/nodes/<id>/links

Save a local folder (note the place):

curl -sS -H "Authorization: Bearer $GRAPH_TOKEN" -H "Content-Type: application/json" \
  -d "{\"title\":\"Invoices 2026\",\"links\":[{\"url\":\"~/Documents/invoices/2026\",\"machine\":\"$(hostname -s)\",\"osUser\":\"$(whoami)\"}],\"tags\":[\"finance\"]}" \
  https://graphindex.org/api/v1/nodes

For anything with quotes or newlines, or for /batch, build the JSON with jq -n or a small script instead of hand-escaping.

Binding a project folder: GRAPH.md

To tie a working folder to a part of the graph, propose a GRAPH.md for it (and an @GRAPH.md line for the folder's CLAUDE.md) and create them only after the user's explicit "ok" — it is a change to their folder (rule 1):

# Graph
Knowledge for this project lives in Graph (https://graphindex.org).
Root node: <id> — "<title>". Tags: <tag>, <tag>.
Skill: https://graphindex.org/skill.md

In such a folder start from the root node: GET /graph?root=<id>&depth=2 and GET /nodes/<id>, save new findings under that root, and use its tags.

Install permanently

mkdir -p ~/.claude/skills/graph && curl -fsSL https://graphindex.org/skill.md -o ~/.claude/skills/graph/SKILL.md