---
name: graph
description: Work with the user's Graph knowledge index at https://graphindex.org — a graph of notes that point to materials stored elsewhere (websites, Google Drive, Notion, local disks). Use when the user mentions their graph or knowledge base, asks where something is or what they saved about a topic, wants to save a link, note or reference, when a task needs the user's own materials, or when a project's CLAUDE.md or GRAPH.md points to Graph.
---

# 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 <machine>, user <osUser>")
     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

```bash
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; `key`s 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:
```bash
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:
```bash
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):
```bash
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):

```markdown
# 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

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