@cairnkeep/cli 1.0.4 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/bin/cairn +44 -35
- package/docs/operating.md +29 -11
- package/docs/storage.md +134 -0
- package/mcp-memory-server/dist/index.js +1 -1
- package/package.json +5 -3
- package/scripts/bootstrap.sh +8 -4
- package/scripts/doctor.sh +22 -7
- package/scripts/memory-store.sh +10 -9
- package/scripts/probe-memory-server.mjs +69 -0
- package/scripts/test-bootstrap-untracked.sh +4 -0
- package/scripts/test-cli-dispatch.sh +3 -0
- package/scripts/test-package-install.sh +32 -0
- package/scripts/test-shipped-perms.sh +7 -11
- package/scripts/test-uninstall.sh +3 -1
- package/scripts/uninstall.sh +16 -6
- package/templates/agentfs-gitignore.template +3 -0
- package/templates/env.example.template +7 -3
package/README.md
CHANGED
|
@@ -27,7 +27,8 @@ cairnkeep-org sibling project.
|
|
|
27
27
|
embedding-ranked search against any OpenAI-compatible endpoint.
|
|
28
28
|
- **`bin/cairn`** — the CLI. `cairn bootstrap [path]` scaffolds a project's
|
|
29
29
|
`.ai/` launchers + env; `cairn doctor` health-checks the configured pieces;
|
|
30
|
-
`cairn memory export|import` relocates the durable store between machines
|
|
30
|
+
`cairn memory export|import` relocates the durable store between machines
|
|
31
|
+
(`export` requires the optional `sqlite3` CLI);
|
|
31
32
|
`cairn audit-timer` installs the scheduled memory+wiki audit; `cairn uninstall`
|
|
32
33
|
reverses the install (backup-first, revertible).
|
|
33
34
|
- **`templates/`** — project scaffolding (generic launchers, env) plus the
|
|
@@ -60,6 +61,11 @@ operating layer (commands, agents, hooks) installed into your harness, and a
|
|
|
60
61
|
bootstrapped project. `cairn bootstrap` does only the last of these — the full
|
|
61
62
|
ordered walkthrough is in **[docs/operating.md](docs/operating.md)**.
|
|
62
63
|
|
|
64
|
+
This setup is local by default: the registered stdio server stores memory on
|
|
65
|
+
the user's computer. Cairnkeep never discovers or selects a remote host.
|
|
66
|
+
Remote HTTP storage requires an explicit server deployment and client URL; see
|
|
67
|
+
**[Memory storage and deployment](docs/storage.md)** before enabling it.
|
|
68
|
+
|
|
63
69
|
The short version for Claude Code:
|
|
64
70
|
|
|
65
71
|
```bash
|
|
@@ -106,7 +112,7 @@ unless a wrapper opts in:
|
|
|
106
112
|
|
|
107
113
|
**Contributor mode.** Working on a repo you don't own? `cairn bootstrap
|
|
108
114
|
--untracked /path/to/project` additionally writes the scaffolded paths
|
|
109
|
-
(`.ai/`, `.planning/`) into the repo's `.git/info/exclude`, so the workflow
|
|
115
|
+
(`.ai/`, `.planning/`, `.agentfs/`) into the repo's `.git/info/exclude`, so the workflow
|
|
110
116
|
files stay purely local: nothing to commit or push, invisible to every other
|
|
111
117
|
contributor, and no edit to the shared `.gitignore`. The trade-off is that
|
|
112
118
|
untracked planning state lives only on that clone — deleting the clone
|
|
@@ -141,6 +147,7 @@ Without an API key, search degrades gracefully to substring matching.
|
|
|
141
147
|
- **Optional document RAG** (`domain_knowledge_*` via AnythingLLM) — [docs/domain-knowledge.md](docs/domain-knowledge.md)
|
|
142
148
|
- **Building a private overlay** (wrap cairnkeep for your org/provider) — [docs/building-an-overlay.md](docs/building-an-overlay.md)
|
|
143
149
|
- **Full operating guide** — [docs/operating.md](docs/operating.md)
|
|
150
|
+
- **Memory storage and deployment** — [docs/storage.md](docs/storage.md)
|
|
144
151
|
- **Git providers** — [docs/git-providers.md](docs/git-providers.md)
|
|
145
152
|
- **Contributing** — [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
146
153
|
- **Security policy** — [SECURITY.md](SECURITY.md)
|
package/bin/cairn
CHANGED
|
@@ -15,6 +15,43 @@ CAIRN_ROOT=$(cd -P "$(dirname "$SOURCE")/.." && pwd)
|
|
|
15
15
|
cmd="${1:-help}"
|
|
16
16
|
[[ $# -gt 0 ]] && shift
|
|
17
17
|
|
|
18
|
+
usage() {
|
|
19
|
+
cat <<'USAGE'
|
|
20
|
+
cairn — Cairnkeep CLI
|
|
21
|
+
|
|
22
|
+
Usage:
|
|
23
|
+
cairn bootstrap [--untracked] [path]
|
|
24
|
+
Scaffold launchers, planning files, and the private
|
|
25
|
+
project-memory ignore guard (default path: .).
|
|
26
|
+
--untracked keeps the scaffold out of git via the repo's
|
|
27
|
+
.git/info/exclude (contributor mode: nothing to commit,
|
|
28
|
+
invisible to other contributors, local-only).
|
|
29
|
+
cairn memory-server Run the memory MCP server (stdio). Register it with your
|
|
30
|
+
harness, e.g. claude mcp add cairn-memory -s user -- cairn memory-server.
|
|
31
|
+
cairn sync [--apply] [--live-root DIR]
|
|
32
|
+
Install/update the Claude Code operating layer (commands,
|
|
33
|
+
agents, hooks) into ~/.claude, or into DIR for a single
|
|
34
|
+
project (--live-root <project>/.claude).
|
|
35
|
+
cairn doctor Health-check the memory server and configured LLM /
|
|
36
|
+
embedding endpoints, git provider, and store.
|
|
37
|
+
cairn memory <path|export|import>
|
|
38
|
+
Relocate global-scope memory. `path` prints its location;
|
|
39
|
+
`export <file.tgz>` takes a WAL-safe snapshot (requires
|
|
40
|
+
sqlite3); `import <file.tgz>` restores it.
|
|
41
|
+
cairn audit-timer [--on-calendar SPEC] [--para-root PATH] [--render-only DIR]
|
|
42
|
+
Install (opt-in) a systemd user timer that runs the
|
|
43
|
+
memory+wiki audit on a schedule; prints a cron alternative.
|
|
44
|
+
cairn uninstall [--dry-run] [--yes] [--purge-memory] [--live-root DIR] [PROJECT ...]
|
|
45
|
+
Reverse what cairnkeep installed/modified (operating
|
|
46
|
+
layer, hook registrations, MCP registration, audit
|
|
47
|
+
timer, and any given projects' bootstrap scaffold).
|
|
48
|
+
Backs everything up first with a revert.sh; keeps the
|
|
49
|
+
durable memory store unless --purge-memory is given.
|
|
50
|
+
cairn version Print version
|
|
51
|
+
cairn help Show this help
|
|
52
|
+
USAGE
|
|
53
|
+
}
|
|
54
|
+
|
|
18
55
|
case "$cmd" in
|
|
19
56
|
bootstrap)
|
|
20
57
|
exec "$CAIRN_ROOT/scripts/bootstrap.sh" "$@"
|
|
@@ -43,40 +80,12 @@ case "$cmd" in
|
|
|
43
80
|
v=$(sed -n 's/.*"version"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$CAIRN_ROOT/package.json" 2>/dev/null | head -1)
|
|
44
81
|
echo "cairnkeep ${v:-unknown}"
|
|
45
82
|
;;
|
|
46
|
-
help | --help | -h
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
--untracked keeps the scaffold out of git via the repo's
|
|
54
|
-
.git/info/exclude (contributor mode: nothing to commit,
|
|
55
|
-
invisible to other contributors, local-only).
|
|
56
|
-
cairn memory-server Run the memory MCP server (stdio). Register it with your
|
|
57
|
-
harness, e.g. claude mcp add cairn-memory -s user -- cairn memory-server.
|
|
58
|
-
cairn sync [--apply] [--live-root DIR]
|
|
59
|
-
Install/update the Claude Code operating layer (commands,
|
|
60
|
-
agents, hooks) into ~/.claude, or into DIR for a single
|
|
61
|
-
project (--live-root <project>/.claude).
|
|
62
|
-
cairn doctor Health-check the configured pieces (memory server, LLM /
|
|
63
|
-
embedding endpoints, git provider, store); skips what is
|
|
64
|
-
unconfigured, fails only on a configured-but-unreachable dep.
|
|
65
|
-
cairn memory <path|export|import>
|
|
66
|
-
Relocate the durable memory store. `path` prints its
|
|
67
|
-
location; `export <file.tgz>` takes a WAL-safe snapshot;
|
|
68
|
-
`import <file.tgz>` restores it on another machine.
|
|
69
|
-
cairn audit-timer [--on-calendar SPEC] [--para-root PATH] [--render-only DIR]
|
|
70
|
-
Install (opt-in) a systemd user timer that runs the
|
|
71
|
-
memory+wiki audit on a schedule; prints a cron alternative.
|
|
72
|
-
cairn uninstall [--dry-run] [--yes] [--purge-memory] [--live-root DIR] [PROJECT ...]
|
|
73
|
-
Reverse what cairnkeep installed/modified (operating
|
|
74
|
-
layer, hook registrations, MCP registration, audit
|
|
75
|
-
timer, and any given projects' bootstrap scaffold).
|
|
76
|
-
Backs everything up first with a revert.sh; keeps the
|
|
77
|
-
durable memory store unless --purge-memory is given.
|
|
78
|
-
cairn version Print version
|
|
79
|
-
cairn help Show this help
|
|
80
|
-
USAGE
|
|
83
|
+
help | --help | -h)
|
|
84
|
+
usage
|
|
85
|
+
;;
|
|
86
|
+
*)
|
|
87
|
+
echo "Unknown command: $cmd" >&2
|
|
88
|
+
usage >&2
|
|
89
|
+
exit 2
|
|
81
90
|
;;
|
|
82
91
|
esac
|
package/docs/operating.md
CHANGED
|
@@ -18,12 +18,19 @@ installs. This guide covers all three in order.
|
|
|
18
18
|
|
|
19
19
|
- Node.js (for the memory server) and a supported harness: Claude Code or
|
|
20
20
|
OpenCode.
|
|
21
|
+
- Optional: the `sqlite3` CLI for `cairn memory export`. Runtime memory and
|
|
22
|
+
`cairn memory import` do not require it.
|
|
21
23
|
- Optional: an OpenAI-compatible LLM endpoint for memory extraction and
|
|
22
24
|
embedding-ranked search. Without it, memory search degrades to substring
|
|
23
25
|
matching — everything else still works.
|
|
24
26
|
|
|
25
27
|
## Setup order (Claude Code)
|
|
26
28
|
|
|
29
|
+
The commands below install the default local topology: `cairn-memory` runs as a
|
|
30
|
+
stdio child process and writes SQLite databases on this computer. Nothing in
|
|
31
|
+
the installer discovers or selects a remote host. Read
|
|
32
|
+
[Memory storage and deployment](storage.md) before choosing remote HTTP mode.
|
|
33
|
+
|
|
27
34
|
**Via npm** (simplest — everything is on `PATH` as `cairn`):
|
|
28
35
|
|
|
29
36
|
```bash
|
|
@@ -52,7 +59,7 @@ claude mcp add cairn-memory -s user -- node "$PWD/mcp-memory-server/dist/index.j
|
|
|
52
59
|
scripts/sync-claude-assets.sh --apply
|
|
53
60
|
|
|
54
61
|
# 4. Scaffold a target project
|
|
55
|
-
cairn bootstrap /path/to/project
|
|
62
|
+
bin/cairn bootstrap /path/to/project
|
|
56
63
|
cp /path/to/project/.ai/env.example /path/to/project/.ai/.env
|
|
57
64
|
$EDITOR /path/to/project/.ai/.env # see "Configuration" below
|
|
58
65
|
# Not the repo's owner? Add --untracked to keep the scaffold out of git
|
|
@@ -139,7 +146,7 @@ vendor or host.
|
|
|
139
146
|
| `CAIRN_LLM_EXTRACTION_MODEL` | Chat model for `memory-capture` extraction |
|
|
140
147
|
| `CAIRN_MEMORY_EMBEDDING_URL` | Embeddings endpoint (falls back to `CAIRN_LLM_API_URL`) |
|
|
141
148
|
| `CAIRN_MEMORY_EMBEDDING_MODEL` | Embedding model name (required for semantic search) |
|
|
142
|
-
| `CAIRN_AGENTFS_BASE_DIR` |
|
|
149
|
+
| `CAIRN_AGENTFS_BASE_DIR` | Server-side base dir for named/global memory scopes (default `~/.cairnkeep`); it does not affect `project` scope |
|
|
143
150
|
| `CAIRN_GIT_PROVIDER` | Git host for collaboration commands: `github`\|`gitlab`\|`codeberg`\|`forgejo`\|`none`. See [git-providers.md](git-providers.md) |
|
|
144
151
|
| `CAIRN_ROUTE_ENDPOINT` | Base URL of an already-running token-miser routing/tiering proxy (unset → the `route_check` tool is inert) |
|
|
145
152
|
| `CAIRN_EXPLORE_BINARY` | Absolute path to the `token_miser` binary used by `context_explore` (unset → the tool throws at call time) |
|
|
@@ -243,10 +250,11 @@ Claude-Code-only path this milestone — there is no OpenCode parity plugin.
|
|
|
243
250
|
|
|
244
251
|
### HTTP transport (opt-in, network-facing)
|
|
245
252
|
|
|
246
|
-
The server runs over stdio by default. Setting `MCP_HTTP_PORT` switches
|
|
247
|
-
streamable HTTP transport so one long-lived process can serve many
|
|
248
|
-
|
|
249
|
-
|
|
253
|
+
The server runs locally over stdio by default. Setting `MCP_HTTP_PORT` switches
|
|
254
|
+
it to a streamable HTTP transport so one long-lived process can serve many
|
|
255
|
+
clients within one trusted storage domain. The databases then live on the HTTP
|
|
256
|
+
server host. Because that exposes every memory tool over the network, HTTP mode
|
|
257
|
+
is guarded and **fails closed**:
|
|
250
258
|
|
|
251
259
|
| Variable | Purpose |
|
|
252
260
|
|---|---|
|
|
@@ -258,7 +266,10 @@ because that exposes every memory tool over the network, HTTP mode is guarded an
|
|
|
258
266
|
|
|
259
267
|
Requests without a valid bearer token get `401`; requests with an unexpected
|
|
260
268
|
`Host` header get `403`. Keep HTTP mode bound to `127.0.0.1` unless you have a
|
|
261
|
-
specific reason to expose it, and use a long random token.
|
|
269
|
+
specific reason to expose it, and use a long random token. HTTP mode has no
|
|
270
|
+
per-user ACL or tenant isolation, and every client shares the server process's
|
|
271
|
+
`project` database. See [Memory storage and deployment](storage.md) for the
|
|
272
|
+
placement rules, client registration, TLS requirements, and backup boundaries.
|
|
262
273
|
|
|
263
274
|
## The workflow
|
|
264
275
|
|
|
@@ -335,9 +346,11 @@ its settings file and exports `CAIRN_EXTRA_SETTINGS` — no change to the launch
|
|
|
335
346
|
|
|
336
347
|
### `cairn doctor`
|
|
337
348
|
|
|
338
|
-
Health-checks the
|
|
339
|
-
env
|
|
340
|
-
|
|
349
|
+
Health-checks the bundled local memory server with a real MCP stdio handshake,
|
|
350
|
+
then checks `./.ai/.env` (or the current environment). It does not inspect a
|
|
351
|
+
harness's remote HTTP registration. Unconfigured optional dependencies are
|
|
352
|
+
skipped; it exits non-zero when the local server probe fails or a configured
|
|
353
|
+
dependency (LLM/embedding endpoint, writable store) is unreachable.
|
|
341
354
|
|
|
342
355
|
```bash
|
|
343
356
|
cd /path/to/project && cairn doctor
|
|
@@ -345,7 +358,7 @@ cd /path/to/project && cairn doctor
|
|
|
345
358
|
|
|
346
359
|
### `cairn memory export|import|path`
|
|
347
360
|
|
|
348
|
-
Relocate
|
|
361
|
+
Relocate named/global memory (one SQLite `.db` per scope under
|
|
349
362
|
`CAIRN_AGENTFS_BASE_DIR`) between machines or backends:
|
|
350
363
|
|
|
351
364
|
```bash
|
|
@@ -354,6 +367,11 @@ cairn memory export store.tgz # WAL-safe snapshot of every scope db
|
|
|
354
367
|
cairn memory import store.tgz # restore on another machine (backs up existing)
|
|
355
368
|
```
|
|
356
369
|
|
|
370
|
+
`cairn memory export` requires the `sqlite3` CLI so it can use SQLite's online
|
|
371
|
+
backup operation and produce a consistent snapshot while WAL mode is active.
|
|
372
|
+
It does not include project memory at `<project>/.agentfs/project.db`; see
|
|
373
|
+
[Memory storage and deployment](storage.md) for project backup instructions.
|
|
374
|
+
|
|
357
375
|
### `cairn audit-timer`
|
|
358
376
|
|
|
359
377
|
`memory-wiki-audit.sh` is the deterministic invalidation backstop meant to run on
|
package/docs/storage.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Memory storage and deployment
|
|
2
|
+
|
|
3
|
+
## The placement rule
|
|
4
|
+
|
|
5
|
+
Cairnkeep stores memory on the filesystem of the machine running the
|
|
6
|
+
`cairn-memory` server process. It does not discover a cloud service, VPS, or
|
|
7
|
+
shared host, and installing the npm package does not contact one.
|
|
8
|
+
|
|
9
|
+
The standard registration is local:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
claude mcp add cairn-memory -s user -- cairn memory-server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This starts `cairn memory-server` as a local stdio child process. With that
|
|
16
|
+
registration, memory remains on that computer.
|
|
17
|
+
|
|
18
|
+
| Scope | Database path |
|
|
19
|
+
|---|---|
|
|
20
|
+
| `project` | `<server working directory>/.agentfs/project.db` |
|
|
21
|
+
| Any named/global scope, such as `identity` or `work` | `${CAIRN_AGENTFS_BASE_DIR:-~/.cairnkeep}/<scope>.db` |
|
|
22
|
+
|
|
23
|
+
For the local launchers, the server working directory is normally the project
|
|
24
|
+
root. Bootstrap installs `.agentfs/.gitignore` so private project memory is not
|
|
25
|
+
accidentally committed. SQLite may also create `-wal` and `-shm` sidecar files.
|
|
26
|
+
|
|
27
|
+
`CAIRN_LLM_API_URL` and `CAIRN_MEMORY_EMBEDDING_URL` select optional model
|
|
28
|
+
services used to process/search memory. They do not change where the SQLite
|
|
29
|
+
databases are stored. Git-provider and routing configuration do not change
|
|
30
|
+
storage either.
|
|
31
|
+
|
|
32
|
+
## Remote HTTP mode
|
|
33
|
+
|
|
34
|
+
Remote storage is explicit. An operator starts Cairnkeep in HTTP mode on a
|
|
35
|
+
server, configures storage in that server process's environment, and registers
|
|
36
|
+
the resulting URL in each client harness. For example:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# On the server host. Put TLS in front of this listener before remote use.
|
|
40
|
+
CAIRN_AGENTFS_BASE_DIR=/var/lib/cairnkeep \
|
|
41
|
+
CAIRN_MEMORY_HTTP_TOKEN="$(openssl rand -hex 32)" \
|
|
42
|
+
CAIRN_MEMORY_HTTP_ALLOWED_HOSTS=memory.example.com \
|
|
43
|
+
MCP_HTTP_HOST=127.0.0.1 MCP_HTTP_PORT=7801 \
|
|
44
|
+
cairn memory-server
|
|
45
|
+
|
|
46
|
+
# On a client. CAIRN_MEMORY_HTTP_TOKEN must contain the server's token.
|
|
47
|
+
claude mcp add --transport http -s user \
|
|
48
|
+
--header "Authorization: Bearer $CAIRN_MEMORY_HTTP_TOKEN" \
|
|
49
|
+
cairn-memory https://memory.example.com/mcp
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
With this topology, the databases are on the server host, not the client PC.
|
|
53
|
+
Setting `CAIRN_AGENTFS_BASE_DIR` on the client does not redirect a remote
|
|
54
|
+
server; set it in the server service environment.
|
|
55
|
+
|
|
56
|
+
HTTP mode is one trusted storage domain:
|
|
57
|
+
|
|
58
|
+
- One bearer token grants access to every exposed memory tool.
|
|
59
|
+
- There is no per-user ACL, tenant isolation, or client-specific filesystem.
|
|
60
|
+
- `project` scope resolves from the server process's working directory, so all
|
|
61
|
+
HTTP clients of that process share the same `project.db`.
|
|
62
|
+
- Use one server instance per isolation boundary. Do not offer one instance to
|
|
63
|
+
mutually untrusted users.
|
|
64
|
+
- Keep the Cairnkeep listener on loopback behind a TLS reverse proxy, or use an
|
|
65
|
+
encrypted private network. Do not expose its raw HTTP listener publicly.
|
|
66
|
+
|
|
67
|
+
## Inspecting and moving memory
|
|
68
|
+
|
|
69
|
+
For a local server, these commands report and move named/global scopes only:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
cairn memory path
|
|
73
|
+
cairn memory export global-memory.tgz
|
|
74
|
+
cairn memory import global-memory.tgz
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`cairn memory path` reports the local process's global-scope directory; it
|
|
78
|
+
cannot inspect a harness's remote HTTP registration. Export uses SQLite's
|
|
79
|
+
online backup operation and requires the `sqlite3` CLI.
|
|
80
|
+
|
|
81
|
+
Project memory is separate at `<project>/.agentfs/project.db` and is not
|
|
82
|
+
included in `cairn memory export`. Back it up while the server is stopped, or
|
|
83
|
+
take an online snapshot with SQLite:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
sqlite3 /path/to/project/.agentfs/project.db \
|
|
87
|
+
".backup '/safe/path/project-memory.db'"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Treat every database and export archive as sensitive. They may contain source
|
|
91
|
+
paths, decisions, incident details, and other project context.
|
|
92
|
+
|
|
93
|
+
## Automating trusted personal clients
|
|
94
|
+
|
|
95
|
+
Keep fleet-specific values in a private dotfiles repository or secret manager,
|
|
96
|
+
not in Cairnkeep. A personal bootstrap can install the public package and then
|
|
97
|
+
perform the explicit remote registration:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Populate these from a private secret manager on each trusted PC.
|
|
101
|
+
export CAIRN_MEMORY_REMOTE_URL=https://memory.example.com/mcp
|
|
102
|
+
export CAIRN_MEMORY_HTTP_TOKEN=replace-from-secret-manager
|
|
103
|
+
|
|
104
|
+
npm install -g @cairnkeep/cli
|
|
105
|
+
|
|
106
|
+
claude mcp remove cairn-memory -s user 2>/dev/null || true
|
|
107
|
+
claude mcp add --transport http -s user \
|
|
108
|
+
cairn-memory "$CAIRN_MEMORY_REMOTE_URL" \
|
|
109
|
+
--header "Authorization: Bearer $CAIRN_MEMORY_HTTP_TOKEN"
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
OpenCode is configured separately in its per-user `opencode.json`. It supports
|
|
113
|
+
environment references in remote MCP headers, so the token does not need to be
|
|
114
|
+
written literally into the JSON:
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{
|
|
118
|
+
"mcp": {
|
|
119
|
+
"cairn-memory": {
|
|
120
|
+
"type": "remote",
|
|
121
|
+
"url": "https://memory.example.com/mcp",
|
|
122
|
+
"oauth": false,
|
|
123
|
+
"headers": {
|
|
124
|
+
"Authorization": "Bearer {env:CAIRN_MEMORY_HTTP_TOKEN}"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Ensure the secret-manager bootstrap exports `CAIRN_MEMORY_HTTP_TOKEN` before
|
|
132
|
+
OpenCode starts. Preserve any other keys already present in `opencode.json`
|
|
133
|
+
rather than replacing the whole file. The URL, token, private host names, and
|
|
134
|
+
device-specific configuration must never be committed to this public project.
|
|
@@ -669,7 +669,7 @@ function gitToplevel(cwd) {
|
|
|
669
669
|
// Factory: each MCP client/session needs its own McpServer instance (the SDK
|
|
670
670
|
// only allows one connected transport per server). All instances share the
|
|
671
671
|
// module-level helpers + AgentFS below. Enables a single long-lived process to
|
|
672
|
-
// serve many concurrent clients
|
|
672
|
+
// serve many concurrent clients within one trusted server-side storage domain.
|
|
673
673
|
function createMemoryServer() {
|
|
674
674
|
const server = new McpServer({ name: "cairn-memory", version: "0.1.0" });
|
|
675
675
|
server.registerTool("memory_read", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnkeep/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A durable, harness-agnostic memory + context layer for coding agents (Claude Code, OpenCode).",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -32,10 +32,12 @@
|
|
|
32
32
|
"zod": "^3.25.76"
|
|
33
33
|
},
|
|
34
34
|
"scripts": {
|
|
35
|
-
"
|
|
35
|
+
"build:server": "npm --prefix mcp-memory-server ci --include=dev --global=false --dry-run=false && npm --prefix mcp-memory-server run build --global=false --dry-run=false",
|
|
36
|
+
"fix:perms": "git ls-files -s | awk '$1==\"100755\"{print $4}' | xargs chmod 755",
|
|
37
|
+
"prepack": "npm run build:server && npm run fix:perms",
|
|
36
38
|
"test": "for test in scripts/test-*.sh; do \"$test\" || exit 1; done",
|
|
37
39
|
"check:public": "scripts/verify-no-private-references.sh && scripts/verify-docs-parity.sh && npm test",
|
|
38
|
-
"prepublishOnly": "npm run check:public && npm
|
|
40
|
+
"prepublishOnly": "npm run check:public && npm run build:server && npm --prefix mcp-memory-server test --global=false --dry-run=false"
|
|
39
41
|
},
|
|
40
42
|
"repository": {
|
|
41
43
|
"type": "git",
|
package/scripts/bootstrap.sh
CHANGED
|
@@ -57,6 +57,10 @@ install_file "$TPL/start-claude.sh.template" "$target/.ai/start-claude.sh" 0
|
|
|
57
57
|
install_file "$TPL/start-opencode.sh.template" "$target/.ai/start-opencode.sh" 0755
|
|
58
58
|
install_file "$TPL/env.example.template" "$target/.ai/env.example" 0644
|
|
59
59
|
|
|
60
|
+
# Project-scope memory is private runtime data. Keep it out of version control
|
|
61
|
+
# even before the first .agentfs/project.db is created.
|
|
62
|
+
install_file "$TPL/agentfs-gitignore.template" "$target/.agentfs/.gitignore" 0644
|
|
63
|
+
|
|
60
64
|
# .planning/ derived-knowledge layer (template : destination)
|
|
61
65
|
planning_pairs=(
|
|
62
66
|
"planning-config.json.template:.planning/config.json"
|
|
@@ -77,7 +81,7 @@ done
|
|
|
77
81
|
|
|
78
82
|
if [[ $untracked -eq 1 ]]; then
|
|
79
83
|
mkdir -p "$(dirname "$exclude_file")"
|
|
80
|
-
for entry in "/${exclude_prefix}.ai/" "/${exclude_prefix}.planning/"; do
|
|
84
|
+
for entry in "/${exclude_prefix}.ai/" "/${exclude_prefix}.planning/" "/${exclude_prefix}.agentfs/"; do
|
|
81
85
|
if grep -qxF "$entry" "$exclude_file" 2>/dev/null; then
|
|
82
86
|
echo "skip (already excluded): $entry"
|
|
83
87
|
else
|
|
@@ -93,7 +97,7 @@ echo
|
|
|
93
97
|
echo "Cairnkeep bootstrapped into $target"
|
|
94
98
|
echo "Next steps (full guide: docs/operating.md):"
|
|
95
99
|
echo " 1. cp .ai/env.example .ai/.env and edit"
|
|
96
|
-
echo " 2.
|
|
97
|
-
echo "
|
|
98
|
-
echo "
|
|
100
|
+
echo " 2. Register the memory server: claude mcp add cairn-memory -s user -- cairn memory-server"
|
|
101
|
+
echo " 3. Install the operating layer (commands/agents/hooks): cairn sync --apply"
|
|
102
|
+
echo " From a source clone, use bin/cairn in place of cairn."
|
|
99
103
|
echo " 4. Launch: $target/.ai/start-claude.sh"
|
package/scripts/doctor.sh
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Reports pass/warn/skip/fail for the pieces cairnkeep needs, reading only what
|
|
5
5
|
# is already configured in ./.ai/.env (or the current environment). Unconfigured
|
|
6
|
-
# optional dependencies are SKIPPED, never failed.
|
|
7
|
-
#
|
|
8
|
-
#
|
|
6
|
+
# optional dependencies are SKIPPED, never failed. The required memory server
|
|
7
|
+
# is checked with a real MCP initialize exchange. Exits non-zero if that probe
|
|
8
|
+
# fails or a configured dependency is unreachable.
|
|
9
9
|
set -uo pipefail
|
|
10
10
|
|
|
11
11
|
CAIRN_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
@@ -30,11 +30,18 @@ reachable() { curl -sS -m 5 -o /dev/null "$1" >/dev/null 2>&1; }
|
|
|
30
30
|
|
|
31
31
|
echo "cairn doctor"
|
|
32
32
|
|
|
33
|
-
# 1.
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
# 1. Required memory server: prove that the shipped build and dependencies can
|
|
34
|
+
# complete an MCP stdio handshake, rather than only checking for one file.
|
|
35
|
+
server="$CAIRN_ROOT/mcp-memory-server/dist/index.js"
|
|
36
|
+
probe="$CAIRN_ROOT/scripts/probe-memory-server.mjs"
|
|
37
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
38
|
+
fail "Node.js not found (Node.js 18 or newer is required)"
|
|
39
|
+
elif [[ ! -f "$server" ]]; then
|
|
40
|
+
fail "memory server not built — run: (cd \"$CAIRN_ROOT/mcp-memory-server\" && npm install && npm run build)"
|
|
41
|
+
elif node "$probe" "$server" >/dev/null 2>&1; then
|
|
42
|
+
pass "bundled local memory server responds over MCP stdio"
|
|
36
43
|
else
|
|
37
|
-
|
|
44
|
+
fail "memory server failed its MCP stdio probe — reinstall cairnkeep or rebuild mcp-memory-server"
|
|
38
45
|
fi
|
|
39
46
|
|
|
40
47
|
# 2. LLM extraction endpoint (optional; unset → substring-only memory search).
|
|
@@ -83,6 +90,14 @@ else
|
|
|
83
90
|
else fail "memory store parent not writable: $parent"; fi
|
|
84
91
|
fi
|
|
85
92
|
|
|
93
|
+
# 6. Optional backup dependency. Runtime memory and imports do not need it,
|
|
94
|
+
# but exports use sqlite3's .backup command for a consistent WAL snapshot.
|
|
95
|
+
if command -v sqlite3 >/dev/null 2>&1; then
|
|
96
|
+
pass "sqlite3 available (memory export enabled)"
|
|
97
|
+
else
|
|
98
|
+
warn "sqlite3 not found — memory export unavailable (runtime and import unaffected)"
|
|
99
|
+
fi
|
|
100
|
+
|
|
86
101
|
echo
|
|
87
102
|
if [[ "$fails" -gt 0 ]]; then
|
|
88
103
|
echo "cairn doctor: $fails configured dependency check(s) failed."
|
package/scripts/memory-store.sh
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# cairn memory <path|export|import> — relocate
|
|
3
|
-
# machines or backends.
|
|
2
|
+
# cairn memory <path|export|import> — relocate global-scope memory between
|
|
3
|
+
# machines or backends. Project-scope memory lives in <project>/.agentfs/ and
|
|
4
|
+
# is intentionally outside this command's base directory.
|
|
4
5
|
#
|
|
5
|
-
# The store is one SQLite .db per scope under
|
|
6
|
-
# (default ~/.cairnkeep). `export` takes a WAL-safe
|
|
7
|
-
# into a portable archive; `import` restores
|
|
8
|
-
#
|
|
6
|
+
# The global store is one SQLite .db per named scope under
|
|
7
|
+
# CAIRN_AGENTFS_BASE_DIR (default ~/.cairnkeep). `export` takes a WAL-safe
|
|
8
|
+
# snapshot of every global-scope db into a portable archive; `import` restores
|
|
9
|
+
# them on another machine; `path` prints the global store location.
|
|
9
10
|
set -euo pipefail
|
|
10
11
|
|
|
11
12
|
base="${CAIRN_AGENTFS_BASE_DIR:-$HOME/.cairnkeep}"
|
|
@@ -14,9 +15,9 @@ base="${base/#\~/$HOME}"
|
|
|
14
15
|
usage() {
|
|
15
16
|
cat <<'EOF'
|
|
16
17
|
Usage: cairn memory <path|export|import>
|
|
17
|
-
path Print the memory
|
|
18
|
-
export <file.tgz> WAL-safe snapshot of every scope .db
|
|
19
|
-
import <file.tgz> Restore scope .db files
|
|
18
|
+
path Print the global-scope memory base directory.
|
|
19
|
+
export <file.tgz> WAL-safe snapshot of every global-scope .db.
|
|
20
|
+
import <file.tgz> Restore global-scope .db files (backs up existing).
|
|
20
21
|
EOF
|
|
21
22
|
}
|
|
22
23
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
|
|
4
|
+
const serverPath = process.argv[2];
|
|
5
|
+
if (!serverPath) {
|
|
6
|
+
console.error("usage: probe-memory-server.mjs PATH_TO_SERVER");
|
|
7
|
+
process.exit(2);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const child = spawn(process.execPath, [serverPath], {
|
|
11
|
+
env: process.env,
|
|
12
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
let stderr = "";
|
|
16
|
+
child.stderr.setEncoding("utf8");
|
|
17
|
+
child.stderr.on("data", (chunk) => {
|
|
18
|
+
stderr = (stderr + chunk).slice(-2000);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const response = new Promise((resolve, reject) => {
|
|
22
|
+
let buffered = "";
|
|
23
|
+
child.stdout.setEncoding("utf8");
|
|
24
|
+
child.stdout.on("data", (chunk) => {
|
|
25
|
+
buffered += chunk;
|
|
26
|
+
const lines = buffered.split("\n");
|
|
27
|
+
buffered = lines.pop() ?? "";
|
|
28
|
+
for (const line of lines) {
|
|
29
|
+
if (!line.trim()) continue;
|
|
30
|
+
try {
|
|
31
|
+
const message = JSON.parse(line);
|
|
32
|
+
if (message.id === 1 && message.result?.serverInfo?.name === "cairn-memory") {
|
|
33
|
+
resolve();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Ignore non-protocol output while waiting for the initialize response.
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
child.once("error", reject);
|
|
42
|
+
child.once("exit", (code) => reject(new Error(`server exited with status ${code}`)));
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const timeout = new Promise((_, reject) => {
|
|
46
|
+
setTimeout(() => reject(new Error("MCP initialize timed out")), 5000).unref();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const initialize = {
|
|
50
|
+
jsonrpc: "2.0",
|
|
51
|
+
id: 1,
|
|
52
|
+
method: "initialize",
|
|
53
|
+
params: {
|
|
54
|
+
protocolVersion: "2024-11-05",
|
|
55
|
+
capabilities: {},
|
|
56
|
+
clientInfo: { name: "cairn-doctor", version: "1.0" },
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
child.stdin.end(`${JSON.stringify(initialize)}\n`);
|
|
62
|
+
await Promise.race([response, timeout]);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
const detail = stderr.trim() || error.message;
|
|
65
|
+
console.error(`memory server probe failed: ${detail}`);
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
} finally {
|
|
68
|
+
child.kill();
|
|
69
|
+
}
|
|
@@ -24,18 +24,22 @@ git -C "$tmp/repo" init -q
|
|
|
24
24
|
"$ROOT/scripts/bootstrap.sh" --untracked "$tmp/repo" >/dev/null
|
|
25
25
|
[[ -f "$tmp/repo/.ai/start-claude.sh" ]] || fail "scaffold missing"
|
|
26
26
|
[[ -f "$tmp/repo/.planning/config.json" ]] || fail "planning layer missing"
|
|
27
|
+
[[ -f "$tmp/repo/.agentfs/.gitignore" ]] || fail "project-memory ignore guard missing"
|
|
27
28
|
[[ -z "$(git -C "$tmp/repo" status --porcelain)" ]] || fail "scaffold visible to git"
|
|
28
29
|
grep -qxF "/.ai/" "$tmp/repo/.git/info/exclude" || fail "missing /.ai/ exclude entry"
|
|
29
30
|
grep -qxF "/.planning/" "$tmp/repo/.git/info/exclude" || fail "missing /.planning/ exclude entry"
|
|
31
|
+
grep -qxF "/.agentfs/" "$tmp/repo/.git/info/exclude" || fail "missing /.agentfs/ exclude entry"
|
|
30
32
|
|
|
31
33
|
# Re-run is idempotent: no duplicate exclude entries
|
|
32
34
|
"$ROOT/scripts/bootstrap.sh" --untracked "$tmp/repo" >/dev/null
|
|
33
35
|
[[ $(grep -cxF "/.ai/" "$tmp/repo/.git/info/exclude") -eq 1 ]] || fail "duplicate exclude entries"
|
|
36
|
+
[[ $(grep -cxF "/.agentfs/" "$tmp/repo/.git/info/exclude") -eq 1 ]] || fail "duplicate .agentfs exclude entry"
|
|
34
37
|
|
|
35
38
|
# Default mode is unchanged: scaffold stays visible to git
|
|
36
39
|
mkdir "$tmp/repo2"
|
|
37
40
|
git -C "$tmp/repo2" init -q
|
|
38
41
|
"$ROOT/scripts/bootstrap.sh" "$tmp/repo2" >/dev/null
|
|
39
42
|
git -C "$tmp/repo2" status --porcelain | grep -q "\.ai/" || fail "default mode should leave the scaffold tracked"
|
|
43
|
+
git -C "$tmp/repo2" status --porcelain | grep -q "\.agentfs/" || fail "default mode should track the memory ignore guard"
|
|
40
44
|
|
|
41
45
|
echo "PASS: bootstrap --untracked contributor mode"
|
|
@@ -11,6 +11,9 @@ cairn="$ROOT/bin/cairn"
|
|
|
11
11
|
"$cairn" help | grep -q "cairn sync" || fail "help missing sync"
|
|
12
12
|
"$cairn" help | grep -q "cairn memory-server" || fail "help missing memory-server"
|
|
13
13
|
"$cairn" sync --help >/dev/null 2>&1 || fail "cairn sync dispatch"
|
|
14
|
+
if "$cairn" not-a-command >/dev/null 2>&1; then
|
|
15
|
+
fail "unknown command should exit non-zero"
|
|
16
|
+
fi
|
|
14
17
|
|
|
15
18
|
tmp=$(mktemp -d); trap 'rm -rf "$tmp"' EXIT
|
|
16
19
|
CAIRN_AGENTFS_BASE_DIR="$tmp/store" "$cairn" memory path | grep -qx "$tmp/store" || fail "cairn memory path dispatch"
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Prove that the actual npm tarball installs and runs without relying on files
|
|
3
|
+
# or dependencies from the source checkout.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
7
|
+
tmp=$(mktemp -d)
|
|
8
|
+
trap 'rm -rf "$tmp"' EXIT
|
|
9
|
+
fail() { echo "FAIL: $1" >&2; exit 1; }
|
|
10
|
+
|
|
11
|
+
cd "$ROOT"
|
|
12
|
+
npm pack --silent --pack-destination "$tmp" --dry-run=false >/dev/null
|
|
13
|
+
tarballs=("$tmp"/*.tgz)
|
|
14
|
+
[[ ${#tarballs[@]} -eq 1 && -f "${tarballs[0]}" ]] || fail "npm pack did not produce exactly one tarball"
|
|
15
|
+
|
|
16
|
+
npm install -g --silent --prefix "$tmp/prefix" --dry-run=false "${tarballs[0]}"
|
|
17
|
+
export HOME="$tmp/home"
|
|
18
|
+
export PATH="$tmp/prefix/bin:$PATH"
|
|
19
|
+
mkdir -p "$HOME" "$tmp/project"
|
|
20
|
+
|
|
21
|
+
expected=$(node -p 'require(process.argv[1]).version' "$ROOT/package.json")
|
|
22
|
+
[[ "$(cairn version)" == "cairnkeep $expected" ]] || fail "installed CLI reports the wrong version"
|
|
23
|
+
|
|
24
|
+
bootstrap_output=$(cairn bootstrap "$tmp/project")
|
|
25
|
+
[[ "$bootstrap_output" == *"cairn memory-server"* ]] || fail "bootstrap printed invalid memory-server setup instructions"
|
|
26
|
+
[[ "$bootstrap_output" == *"cairn sync --apply"* ]] || fail "bootstrap printed invalid operating-layer setup instructions"
|
|
27
|
+
[[ -x "$tmp/project/.ai/start-claude.sh" ]] || fail "bootstrap did not install an executable Claude launcher"
|
|
28
|
+
[[ -f "$tmp/project/.planning/config.json" ]] || fail "bootstrap did not install the planning scaffold"
|
|
29
|
+
|
|
30
|
+
(cd "$tmp/project" && cairn doctor) >/dev/null || fail "installed package failed cairn doctor"
|
|
31
|
+
|
|
32
|
+
echo "PASS: npm tarball installs a self-contained CLI and MCP server"
|
|
@@ -2,24 +2,20 @@
|
|
|
2
2
|
# Regression guard for the 744-perms packaging bug. A root-owned `sudo npm i -g`
|
|
3
3
|
# makes root the file owner, so a non-root user can only run cairn if "other"
|
|
4
4
|
# has the execute bit. A publisher umask can strip it at pack time; the
|
|
5
|
-
# `
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Deliberately does NOT run `npm pack`: this test runs inside `npm test`, which
|
|
9
|
-
# runs inside `npm publish`'s prepublishOnly, and nesting npm that deep is not
|
|
10
|
-
# portable. Running the prepack command directly tests the same guarantee.
|
|
5
|
+
# `fix:perms` script re-applies git's tracked executable bit. This verifies it
|
|
6
|
+
# heals a umask-mangled entrypoint back to 755 (world-executable).
|
|
11
7
|
set -euo pipefail
|
|
12
8
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
13
9
|
cd "$ROOT"
|
|
14
10
|
fail() { echo "FAIL: $1" >&2; exit 1; }
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
[[ -n "$
|
|
12
|
+
fix_perms=$(node -e 'process.stdout.write(require("./package.json").scripts["fix:perms"]||"")')
|
|
13
|
+
[[ -n "$fix_perms" ]] || fail "package.json has no fix:perms script"
|
|
18
14
|
|
|
19
15
|
entry=bin/cairn
|
|
20
16
|
trap 'chmod 755 "$entry" 2>/dev/null || true' EXIT
|
|
21
17
|
chmod 744 "$entry" # simulate a stripping umask
|
|
22
|
-
sh -c "$
|
|
18
|
+
sh -c "$fix_perms" # run the real packaging command
|
|
23
19
|
mode=$(stat -c '%a' "$entry" 2>/dev/null || stat -f '%Lp' "$entry") # GNU || BSD/macOS
|
|
24
|
-
[[ "$mode" == "755" ]] || fail "
|
|
25
|
-
echo "OK:
|
|
20
|
+
[[ "$mode" == "755" ]] || fail "fix:perms did not restore $entry to 755 (got $mode) -- 744 packaging bug"
|
|
21
|
+
echo "OK: fix:perms restores shipped executables to 755"
|
|
@@ -58,11 +58,13 @@ EXCL="$PROJ/.git/info/exclude"
|
|
|
58
58
|
mkdir -p "$SB/home/.cairnkeep"; echo data >"$SB/home/.cairnkeep/db"
|
|
59
59
|
"$ROOT_DIR/scripts/uninstall.sh" --yes --purge-memory --live-root "$SB/home/.claude" "$PROJ" >/dev/null 2>&1
|
|
60
60
|
check "project .ai removed" "$([[ -e "$PROJ/.ai" ]] && echo no || echo yes)" "yes"
|
|
61
|
-
check "
|
|
61
|
+
check "project .agentfs removed" "$([[ -e "$PROJ/.agentfs" ]] && echo no || echo yes)" "yes"
|
|
62
|
+
check "exclude lines stripped" "$(grep -cE '\.ai/|\.planning/|\.agentfs/' "$EXCL" 2>/dev/null || true)" "0"
|
|
62
63
|
check "memory store purged" "$([[ -e "$SB/home/.cairnkeep" ]] && echo no || echo yes)" "yes"
|
|
63
64
|
BK2=$(ls -dt "$SB/home/.cairnkeep-uninstall-"* 2>/dev/null | head -1)
|
|
64
65
|
bash "$BK2/revert.sh" >/dev/null 2>&1
|
|
65
66
|
check "project scaffold restored" "$([[ -d "$PROJ/.ai" ]] && echo yes || echo no)" "yes"
|
|
67
|
+
check "project ignore restored" "$([[ -f "$PROJ/.agentfs/.gitignore" ]] && echo yes || echo no)" "yes"
|
|
66
68
|
check "memory store restored" "$(cat "$SB/home/.cairnkeep/db" 2>/dev/null)" "data"
|
|
67
69
|
|
|
68
70
|
echo
|
package/scripts/uninstall.sh
CHANGED
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
# Undoes: the operating layer (`cairn sync --apply` — commands, agents,
|
|
11
11
|
# templates, hooks + their settings.json registrations), the MCP registration
|
|
12
12
|
# (`claude mcp add cairn-memory`), the opt-in audit timer, and — for any project
|
|
13
|
-
# paths you pass — the `cairn bootstrap` scaffold.
|
|
13
|
+
# paths you pass — the `cairn bootstrap` scaffold. Project `.agentfs/` memory is
|
|
14
|
+
# retained unless `--purge-memory` is given. The npm package itself is
|
|
14
15
|
# npm's to remove; the final step is printed, not run (it would delete this
|
|
15
16
|
# script mid-execution).
|
|
16
17
|
set -uo pipefail
|
|
@@ -39,9 +40,8 @@ $HOME/.cairnkeep-uninstall-<ts>/ with a revert.sh before any change.
|
|
|
39
40
|
|
|
40
41
|
--dry-run Print what would happen; touch nothing.
|
|
41
42
|
--yes Do not prompt for confirmation.
|
|
42
|
-
--purge-memory Also remove the
|
|
43
|
-
|
|
44
|
-
accumulated memory — off by default.
|
|
43
|
+
--purge-memory Also remove the global memory store and .agentfs/ memory in
|
|
44
|
+
each PROJECT. Backed up first; off by default.
|
|
45
45
|
--live-root PATH Claude root to clean (default: $CLAUDE_CONFIG_DIR or ~/.claude).
|
|
46
46
|
PROJECT ... Also revert `cairn bootstrap` in these project dirs
|
|
47
47
|
(.ai/, .planning/, and any .git/info/exclude entries).
|
|
@@ -209,16 +209,26 @@ for proj in "${PROJECTS[@]:-}"; do
|
|
|
209
209
|
echo "Project scaffold: $proj"
|
|
210
210
|
remove_path "$proj/.ai"
|
|
211
211
|
remove_path "$proj/.planning"
|
|
212
|
+
if [[ $PURGE_MEMORY -eq 1 ]]; then
|
|
213
|
+
remove_path "$proj/.agentfs"
|
|
214
|
+
elif [[ -d "$proj/.agentfs" ]]; then
|
|
215
|
+
echo " kept project memory: $proj/.agentfs"
|
|
216
|
+
fi
|
|
212
217
|
# Strip the .git/info/exclude lines contributor-mode bootstrap added.
|
|
213
218
|
if excl=$(cd "$proj" && git rev-parse --git-path info/exclude 2>/dev/null); then
|
|
214
219
|
[[ "$excl" == /* ]] || excl="$proj/$excl"
|
|
215
220
|
prefix=$(cd "$proj" && git rev-parse --show-prefix 2>/dev/null)
|
|
216
221
|
if [[ -f "$excl" ]] && grep -qxF "/${prefix}.ai/" "$excl" 2>/dev/null; then
|
|
217
222
|
if [[ $DRY_RUN -eq 1 ]]; then
|
|
218
|
-
echo " would strip
|
|
223
|
+
echo " would strip scaffold entries from ${excl}"
|
|
219
224
|
else
|
|
220
225
|
back_up "$excl"
|
|
221
|
-
|
|
226
|
+
if [[ $PURGE_MEMORY -eq 1 ]]; then
|
|
227
|
+
grep -vxF -e "/${prefix}.ai/" -e "/${prefix}.planning/" -e "/${prefix}.agentfs/" "$excl" >"$excl.tmp" || true
|
|
228
|
+
else
|
|
229
|
+
grep -vxF -e "/${prefix}.ai/" -e "/${prefix}.planning/" "$excl" >"$excl.tmp" || true
|
|
230
|
+
fi
|
|
231
|
+
mv "$excl.tmp" "$excl"
|
|
222
232
|
echo " stripped exclude entries from ${excl}"
|
|
223
233
|
fi
|
|
224
234
|
fi
|
|
@@ -12,9 +12,13 @@
|
|
|
12
12
|
# One of: github | gitlab | codeberg | forgejo | none. See docs/git-providers.md.
|
|
13
13
|
# CAIRN_GIT_PROVIDER=github
|
|
14
14
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
15
|
+
# Storage belongs to the host running the memory-server process. Named/global
|
|
16
|
+
# scopes default to ~/.cairnkeep; project scope uses <server-cwd>/.agentfs/.
|
|
17
|
+
# CAIRN_AGENTFS_BASE_DIR=~/.cairnkeep
|
|
18
|
+
#
|
|
19
|
+
# The memory server runs locally over stdio by default. These HTTP settings are
|
|
20
|
+
# for the SERVER host of an explicit remote deployment, not a normal local
|
|
21
|
+
# client. Setting MCP_HTTP_PORT REQUIRES a bearer token, or startup fails.
|
|
18
22
|
# CAIRN_MEMORY_HTTP_TOKEN=change-me-to-a-long-random-secret
|
|
19
23
|
# MCP_HTTP_PORT=7801
|
|
20
24
|
# MCP_HTTP_HOST=127.0.0.1
|