@tricknowtech/context
v0.7.0
Published
Carry a project's LLM context between machines — Claude Code, Codex, Cursor, Copilot, Gemini, Windsurf, Cline, Aider. Local-first, commit it to your repo.
Maintainers
Readme
@tricknowtech/context
Carry a project's LLM context — memory, skills, instructions, and where you left off — between machines.
Local-first: the store is a plain directory you commit to your repo, so context travels with the code. No account, no server, works offline. A hosted Tricknowtech remote is optional and comes later.
npx @tricknowtech/context initThe problem
Open the same project on a second machine and your assistant knows nothing. Instruction files travel with the repo, but the parts that actually accumulate — per-project memory, custom skills and prompts, and the thread of what you were doing — live in your home directory and never leave the machine that built them.
Works with the tool you actually use
Assistants are detected automatically, from what's installed and what the repo already contains:
| Tool | Project files | User-global |
|---|---|---|
| Claude Code | CLAUDE.md, .claude/** | memory, skills, agents, plans |
| OpenAI Codex | AGENTS.md, .codex/** | AGENTS.md, config.toml, prompts |
| Cursor | .cursorrules, .cursor/rules/** | global rules |
| GitHub Copilot | .github/copilot-instructions.md, instructions, prompts | — |
| Gemini CLI | GEMINI.md, .gemini/** | GEMINI.md, settings, commands |
| Windsurf | .windsurfrules, .windsurf/** | — |
| Cline | .clinerules | — |
| Aider | CONVENTIONS.md, .aider.conf.yml | — |
| Continue | .continue/** | — |
Each tool's home-directory config is namespaced in the store (assistants/codex/…) and restored to the right place on the other machine — ~/.codex, ~/.gemini, ~/.claude — even when that machine's home and project paths are completely different.
This also works across a team on different tools: a Cursor user's rules sync for a Claude user, because detection keys off what's in the repo, not just what's installed locally. Pin it explicitly if you'd rather:
{ "assistants": ["claude", "codex"] }What it does
ctx init creates .contextsync/ and installs a /context slash command. From then on:
ctx push # collect context into the store
ctx pull # restore it, and print where you left off
ctx status # what changed since the last push
ctx handoff # show or write the session handoff
ctx doctor # check the setup is actually wired correctlySearch everything you've synced (file-based RAG, no database)
Carrying 300 MB of context is only useful if you can find things in it.
ctx index # build the index + knowledge graph
ctx search "why does nginx 502 after a deploy"
ctx ask "what did we decide about transcripts" # context pack for a model
ctx graph startServer # what touches this symbol 18.59 assistants/claude/memory/nginx_upstream_ip_cache.md [core]
Recreating the `app` container without also restarting `nginx` causes a
live 502 outage — nginx caches app's old IP and never re-resolves…No database, no embeddings, no dependencies. Retrieval is BM25 over an inverted index stored as plain files. The postings are sharded by term hash, so a four-word query reads four small files instead of parsing one huge one — the access pattern a key-value store would give you, using only the filesystem. Scoring needs nothing but term frequencies and document lengths, so it is deterministic, offline, and reproducible.
The tradeoff versus embeddings is honest: there's no synonym matching, so "auth" won't find "login" unless the word appears. In exchange you get zero install weight, no model download, no network call per query, and results you can explain. On technical corpora the rare exact terms — identifiers, file names, error strings — are what you actually search for, and those are exactly what BM25's IDF rewards.
The graph half
ctx index also builds a knowledge graph by extracting definitions and imports across TypeScript, Python, Go, Rust, PHP, Java and others, then linking your context documents to the code they mention.
That closes a gap pure keyword search can't: a memory note explaining why something breaks often never contains the word you searched for. Documents one hop from a matched symbol are pulled in, discounted so they can never outrank a direct textual match.
It uses graphify's exact schema — same node/edge fields, same relation vocabulary. If graphify-out/graph.json exists it is merged in and wins, since it was built with an LLM and carries semantic relations regex extraction cannot infer. Think of this as the AST-only equivalent: cheaper, offline, instant.
Transcripts are opt-in
ctx index --transcriptsThey're ~99% of the corpus by volume. Indexing them by default turned a 5-second command into 4 minutes and produced an index larger than the data itself, so you ask for them explicitly.
Cut what a session costs in tokens
The expensive part of an LLM session is rarely the conversation — it's the context re-sent on every request. ctx budget prices it:
per turn 4.9k tokens × 100 turns = 492.7k
per session 8.9k tokens × 1 = 8.9k
on demand 28.8k tokens (only when pulled in)
session total ≈ 501.6k tokens before any code or conversation
Biggest lever:
CLAUDE.md is 4.8k tokens on *every* request.
Halving it saves ~237.5k tokens over 100 turns.That split is the whole point. An 18k-token skill loaded on demand is cheap; a 4.8k-token instruction file is not, because you pay it every turn. It also flags content duplicated across files, which you pay for twice.
Then load only what a task needs, instead of everything:
ctx pack "fixing an nginx 502 after redeploying containers"## assistants/claude/memory/nginx_upstream_ip_cache.md
nginx's `fastcgi_pass app:9000` resolves the hostname once and caches it…
---
Pack ≈ 590 tokens vs ≈ 42.6k for all context — 99% smaller.The practical recipe: move situational detail out of instruction files into memory or skills, then retrieve it on demand. Instruction files should hold what's true every turn; everything else is better paid for only when it's relevant.
Token counts are estimates (±15%) — enough to decide what to cut, not a billing oracle.
Continue a session on a VPS
Move the whole context — including the current session's transcripts — to a server, so you can ssh in and carry on exactly where you stopped:
ctx remote add vps [email protected]:/srv/myproject
ctx push --to vps --restore--restore is what makes it a continuation rather than a file copy: it runs the restore on the far end, so memory, skills, plans and transcripts land where the assistant actually reads them. Then just:
ssh [email protected]
cd /srv/myproject && claude --resumeBring work back the other way with ctx pull --from vps.
Transport is your own ssh and rsync, so ~/.ssh/config aliases, jump hosts, agent forwarding and custom ports all work untouched. rsync means the second push only sends what changed. Without rsync it falls back to streaming a tar over ssh, which always sends everything.
Measured on a 327 MB context: 92 MB transferred, ~48s, and the restored transcript was a byte-exact prefix of the live local one.
Start with ctx doctor if anything seems off — it verifies the pieces that
fail silently, including whether your store is accidentally gitignored (in
which case it never reaches the other machine) and whether restored memory
lands where Claude Code actually reads it.
Commit .contextsync/ and your teammates — and your other laptop — get the same context on clone.
It skips what git already carries
Your CLAUDE.md is already in the repo, so copying it into the store would just create a second copy that drifts. The collector checks git ls-files and leaves tracked files alone. What it does capture is the part that never travels:
| Store path | Comes from |
|---|---|
| memory/ | ~/.claude/projects/<project-key>/memory/ |
| skills/ | ~/.claude/skills/ |
| agents/ | ~/.claude/agents/ |
| user/ | ~/.claude/CLAUDE.md, settings.json |
| project/ | untracked instruction files in the repo |
| artifacts/ | derived indexes, opt-in (--artifacts) |
| handoff.json | written by /context push |
Paths are rewritten on restore
Claude Code names its per-project directories after the absolute project path — /root/my-app becomes -root-my-app. Clone the repo somewhere else and that key is wrong. The manifest stores paths as templates ({userClaude}/projects/{cwdKey}/memory/…) and ctx pull recomputes them for wherever the repo actually lives.
The handoff
/context push asks the model to write .contextsync/handoff.json first — goal, decisions made, open threads, files touched, next step. Only the model has the conversation; only the CLI has the disk, so the slash command is the one place both are available.
On the other machine, /context pull restores everything and reads the handoff back, so the new session starts oriented instead of blank:
Where you left off (2h ago)
Goal Build the context-sync tool and publish it
Next step Start the cloud remote, leading with the token-scoping fix
Decided:
· Local-first: store is committed to the repo, cloud is an optional remote
Still open:
· mcp:* token abilities are granted but never checkedThe handoff is validated on write and on read — a model-authored file that's
malformed is reported rather than silently ignored, since a handoff that looks
present but says nothing is worse than an obviously absent one. ctx push
tells you when no handoff was written, so you never discover it only after
arriving on the other machine.
Safety
The store gets committed, and a committed credential is permanent — so ctx push scans everything first and refuses if it finds anything that looks like a secret:
Refusing to push — 2 possible secrets found:
user/settings.json:14 [assigned-secret] wJal********MPLEK
memory/deploy.md:8 [aws-access-key-id] AKIA********MPLE
These would be committed to the repository and be very hard to remove..env files, ~/.claude/.credentials.json, .claude.json, and private keys are never collected at all, at any tier.
Session transcripts are excluded from local mode entirely. They run to hundreds of megabytes, are append-only, and carry the largest leak surface (full tool output). They will be supported through the hosted remote, where content-addressed chunking makes them practical.
Options
| Flag | Effect |
|---|---|
| --artifacts | include derived indexes (graphify-out/, etc.) |
| --dry-run | show what would sync, write nothing |
| --force | init: overwrite config · pull: overwrite differing files |
| --allow-secrets | push despite scan hits — think first |
ctx pull never overwrites a local file whose contents differ; it lists them and leaves them alone until you pass --force.
Configuration
.contextsync/config.json:
{
"projectId": "…",
"name": "my-app",
"rootHint": "/root/my-app",
"tiers": ["core", "handoff"],
"artifactPaths": ["graphify-out"],
"exclude": ["**/*.log"],
"remotes": {}
}Programmatic use
import { collect, findProjectRoot, loadConfig, LocalStore } from '@tricknowtech/context'
const root = findProjectRoot()!
const cfg = loadConfig(root)!
const { files, skippedTracked } = collect(root, cfg, ['core'])
new LocalStore(root).write(files, root)License
ISC © Tricknowtech
