@cairnkeep/cli 1.1.1 → 1.1.3
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/CHANGELOG.md +16 -0
- package/README.md +1 -0
- package/docs/building-an-overlay.md +9 -0
- package/docs/overlay-distributions.md +97 -0
- package/examples/overlay-distribution/cairnkeep.overlay.json +25 -0
- package/package.json +3 -1
- package/schemas/cairnkeep-overlay.schema.json +80 -0
- package/scripts/test-overlay-schema.sh +44 -0
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,22 @@
|
|
|
3
3
|
All notable user-facing changes are documented here. This project follows
|
|
4
4
|
[Semantic Versioning](https://semver.org/).
|
|
5
5
|
|
|
6
|
+
## [1.1.3] - 2026-07-14
|
|
7
|
+
|
|
8
|
+
### Fixed
|
|
9
|
+
|
|
10
|
+
- Allow the standard `$schema` self-reference in strict overlay manifests and
|
|
11
|
+
verify that the shipped example uses only declared top-level properties.
|
|
12
|
+
|
|
13
|
+
## [1.1.2] - 2026-07-14
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Define a provider-neutral managed-distribution contract for private overlays.
|
|
18
|
+
- Ship a versioned overlay manifest schema and local-first example.
|
|
19
|
+
- Document wrapper commands, profile locks, data-routing diagnostics, private
|
|
20
|
+
registry delivery, package hygiene, fleet migration, and rollback practices.
|
|
21
|
+
|
|
6
22
|
## [1.1.1] - 2026-07-14
|
|
7
23
|
|
|
8
24
|
### Fixed
|
package/README.md
CHANGED
|
@@ -186,6 +186,7 @@ enabling them.
|
|
|
186
186
|
|
|
187
187
|
- **Optional document RAG** (`domain_knowledge_*` via AnythingLLM) — [docs/domain-knowledge.md](docs/domain-knowledge.md)
|
|
188
188
|
- **Building a private overlay** (wrap cairnkeep for your org/provider) — [docs/building-an-overlay.md](docs/building-an-overlay.md)
|
|
189
|
+
- **Managed overlay distributions** (wrapper CLI, policy lock, private registry, rollback) — [docs/overlay-distributions.md](docs/overlay-distributions.md)
|
|
189
190
|
- **Full operating guide** — [docs/operating.md](docs/operating.md)
|
|
190
191
|
- **Memory storage and deployment** — [docs/storage.md](docs/storage.md)
|
|
191
192
|
- **Privacy and data flow** — [docs/privacy-and-data-flow.md](docs/privacy-and-data-flow.md)
|
|
@@ -6,6 +6,11 @@ credentials, and no organization-specific launchers. Everything specific to
|
|
|
6
6
|
launchers, policy — belongs in a small private **overlay** that wraps cairnkeep.
|
|
7
7
|
This guide shows the pattern.
|
|
8
8
|
|
|
9
|
+
For multi-machine or organization-wide use, package the overlay as a managed
|
|
10
|
+
distribution that owns the enrolled machine's `cairn` command. See
|
|
11
|
+
[Managed Cairnkeep distributions](overlay-distributions.md) for the command,
|
|
12
|
+
manifest, profile-lock, private-registry, and rollback contract.
|
|
13
|
+
|
|
9
14
|
## Principles
|
|
10
15
|
|
|
11
16
|
1. **Consume, don't fork.** Treat cairnkeep as an upstream dependency: install it
|
|
@@ -122,3 +127,7 @@ secret storage, not generated project files.
|
|
|
122
127
|
- [ ] Operating layer scoped as intended (global vs `--live-root`)
|
|
123
128
|
- [ ] Optional integrations wired only where wanted
|
|
124
129
|
- [ ] A single documented command takes a fresh machine to a working setup
|
|
130
|
+
- [ ] Managed machines resolve `cairn` to the overlay distribution, not raw core
|
|
131
|
+
- [ ] `cairn config explain` reports memory and RAG destinations without secrets
|
|
132
|
+
- [ ] A project profile lock prevents silent cross-overlay reconfiguration
|
|
133
|
+
- [ ] Private packages reject public-registry publication and exclude runtime data
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# Managed Cairnkeep distributions
|
|
2
|
+
|
|
3
|
+
A private overlay becomes significantly easier to operate when it is treated as
|
|
4
|
+
a **distribution** rather than a directory of configuration fragments. The
|
|
5
|
+
distribution owns the `cairn` command on enrolled machines while consuming the
|
|
6
|
+
public `@cairnkeep/cli` package as an exact dependency.
|
|
7
|
+
|
|
8
|
+
This keeps Cairnkeep neutral and upgradeable. The distribution owns environment
|
|
9
|
+
policy, data routing, launchers, private integrations, and delivery through an
|
|
10
|
+
approved package registry. It should not patch or fork the core implementation.
|
|
11
|
+
|
|
12
|
+
## Command contract
|
|
13
|
+
|
|
14
|
+
An overlay package can expose its own `cairn` binary and delegate to its pinned
|
|
15
|
+
core dependency:
|
|
16
|
+
|
|
17
|
+
- `cairn bootstrap <project>` runs core bootstrap and then applies the overlay.
|
|
18
|
+
- `cairn doctor` runs core diagnostics plus overlay policy checks.
|
|
19
|
+
- `cairn config explain` reports effective storage and service destinations
|
|
20
|
+
without printing credentials.
|
|
21
|
+
- `cairn overlay info` reports distribution and core versions separately.
|
|
22
|
+
- `cairn core <command>` explicitly bypasses overlay behavior for diagnosis.
|
|
23
|
+
- Other commands, especially `memory-server`, delegate to the pinned core with
|
|
24
|
+
the validated machine and project environment loaded.
|
|
25
|
+
|
|
26
|
+
Do not replace core source files. A small wrapper preserves the upstream command
|
|
27
|
+
surface while making managed bootstrap the default users reasonably expect.
|
|
28
|
+
|
|
29
|
+
## Distribution manifest
|
|
30
|
+
|
|
31
|
+
Store non-secret capabilities and data-routing policy in
|
|
32
|
+
`cairnkeep.overlay.json`. The reference schema is
|
|
33
|
+
[`schemas/cairnkeep-overlay.schema.json`](../schemas/cairnkeep-overlay.schema.json),
|
|
34
|
+
with a complete example in
|
|
35
|
+
[`examples/overlay-distribution`](../examples/overlay-distribution/cairnkeep.overlay.json).
|
|
36
|
+
|
|
37
|
+
The distribution version and core version are independent. Pin the core exactly
|
|
38
|
+
and test every distribution release against it.
|
|
39
|
+
|
|
40
|
+
After a successful bootstrap, write an untracked project profile lock containing
|
|
41
|
+
the distribution identity, versions, and non-secret policy. Refuse a different
|
|
42
|
+
overlay identity before making changes. A stale lock should make `doctor` fail
|
|
43
|
+
with an instruction to rerun managed bootstrap.
|
|
44
|
+
|
|
45
|
+
## Configuration and secrets
|
|
46
|
+
|
|
47
|
+
Use two explicit configuration layers:
|
|
48
|
+
|
|
49
|
+
1. A mode-0600 machine profile in the user's configuration directory.
|
|
50
|
+
2. An optional untracked, mode-0600 project override in `.ai/.env`.
|
|
51
|
+
|
|
52
|
+
Validate both files before sourcing them: reject symlinks, permissive modes, and
|
|
53
|
+
tracked project files. The manifest and project lock must never contain tokens,
|
|
54
|
+
cookies, private keys, or credential-bearing URLs.
|
|
55
|
+
|
|
56
|
+
`cairn config explain` should answer these questions directly:
|
|
57
|
+
|
|
58
|
+
- Is memory local or remote?
|
|
59
|
+
- Which transport is used?
|
|
60
|
+
- Which directory or endpoint receives memory?
|
|
61
|
+
- Is document RAG disabled, local, or remote?
|
|
62
|
+
- Which configuration files selected those values?
|
|
63
|
+
|
|
64
|
+
## Private package delivery
|
|
65
|
+
|
|
66
|
+
A distribution can be an ordinary scoped npm package with:
|
|
67
|
+
|
|
68
|
+
- `@cairnkeep/cli` as an exact dependency;
|
|
69
|
+
- a `bin.cairn` entry for the wrapper;
|
|
70
|
+
- `publishConfig.access` set to `restricted`;
|
|
71
|
+
- an explicit guard that rejects the public npm registry;
|
|
72
|
+
- registry credentials supplied by a user credential helper or protected CI
|
|
73
|
+
variables, never by repository files.
|
|
74
|
+
|
|
75
|
+
Support installation from a private registry, a reviewed tarball, and a local
|
|
76
|
+
checkout. Avoid `postinstall` enrollment: installing a package must not silently
|
|
77
|
+
replace machine policy. Provide an explicit enrollment command with a retained
|
|
78
|
+
rollback target.
|
|
79
|
+
|
|
80
|
+
## Release and fleet safety
|
|
81
|
+
|
|
82
|
+
Every release should verify:
|
|
83
|
+
|
|
84
|
+
- fresh managed bootstrap and idempotent reruns;
|
|
85
|
+
- core delegation and the explicit core escape hatch;
|
|
86
|
+
- profile mismatch rejection before mutation;
|
|
87
|
+
- storage and RAG policy enforcement;
|
|
88
|
+
- secret and runtime-artifact exclusion from the package;
|
|
89
|
+
- package installation from the produced tarball;
|
|
90
|
+
- checksums, an SBOM, and an immutable version;
|
|
91
|
+
- pre-upgrade and post-upgrade `doctor` results;
|
|
92
|
+
- rollback to the previous command or package version.
|
|
93
|
+
|
|
94
|
+
For a fleet migration, update the machine operating layer once, reapply managed
|
|
95
|
+
bootstrap to projects without altering tracked Git status, and validate every
|
|
96
|
+
profile lock. Storage migrations require their own backup, integrity, canary,
|
|
97
|
+
and rollback procedure; changing an overlay version alone must not move data.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "../../schemas/cairnkeep-overlay.schema.json",
|
|
3
|
+
"schemaVersion": 1,
|
|
4
|
+
"id": "team-managed",
|
|
5
|
+
"name": "Example managed Cairnkeep distribution",
|
|
6
|
+
"version": "1.0.0",
|
|
7
|
+
"core": {
|
|
8
|
+
"package": "@cairnkeep/cli",
|
|
9
|
+
"version": "1.1.3"
|
|
10
|
+
},
|
|
11
|
+
"policy": {
|
|
12
|
+
"memory": {
|
|
13
|
+
"transport": "stdio",
|
|
14
|
+
"storage": "local",
|
|
15
|
+
"baseDir": "${HOME}/.cairnkeep"
|
|
16
|
+
},
|
|
17
|
+
"rag": {"mode": "disabled"},
|
|
18
|
+
"gitProvider": "none"
|
|
19
|
+
},
|
|
20
|
+
"bootstrap": {
|
|
21
|
+
"defaultMode": "untracked",
|
|
22
|
+
"launcher": ".ai/start-managed-agent.sh",
|
|
23
|
+
"profileLock": ".ai/cairnkeep-profile.json"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cairnkeep/cli",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.3",
|
|
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",
|
|
@@ -17,6 +17,8 @@
|
|
|
17
17
|
"claude/",
|
|
18
18
|
"opencode/",
|
|
19
19
|
"examples/anythingllm/",
|
|
20
|
+
"examples/overlay-distribution/",
|
|
21
|
+
"schemas/",
|
|
20
22
|
"docs/",
|
|
21
23
|
"CHANGELOG.md",
|
|
22
24
|
"CONTRIBUTING.md",
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/cairnkeep/cairnkeep/main/schemas/cairnkeep-overlay.schema.json",
|
|
4
|
+
"title": "Cairnkeep distribution profile",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": ["schemaVersion", "id", "name", "version", "core", "policy", "bootstrap"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"$schema": {"type": "string", "format": "uri-reference"},
|
|
10
|
+
"schemaVersion": {"const": 1},
|
|
11
|
+
"id": {"type": "string", "pattern": "^[a-z][a-z0-9-]{1,63}$"},
|
|
12
|
+
"name": {"type": "string", "minLength": 1, "maxLength": 120},
|
|
13
|
+
"version": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$"
|
|
16
|
+
},
|
|
17
|
+
"core": {
|
|
18
|
+
"type": "object",
|
|
19
|
+
"additionalProperties": false,
|
|
20
|
+
"required": ["package", "version"],
|
|
21
|
+
"properties": {
|
|
22
|
+
"package": {"const": "@cairnkeep/cli"},
|
|
23
|
+
"version": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+(?:[-+][0-9A-Za-z.-]+)?$"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"policy": {
|
|
30
|
+
"type": "object",
|
|
31
|
+
"additionalProperties": false,
|
|
32
|
+
"required": ["memory", "rag", "gitProvider"],
|
|
33
|
+
"properties": {
|
|
34
|
+
"memory": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"additionalProperties": false,
|
|
37
|
+
"required": ["transport", "storage"],
|
|
38
|
+
"properties": {
|
|
39
|
+
"transport": {"enum": ["stdio", "http"]},
|
|
40
|
+
"storage": {"enum": ["local", "remote"]},
|
|
41
|
+
"baseDir": {"type": "string", "minLength": 1},
|
|
42
|
+
"endpoint": {"type": "string", "format": "uri"}
|
|
43
|
+
},
|
|
44
|
+
"allOf": [
|
|
45
|
+
{
|
|
46
|
+
"if": {"properties": {"storage": {"const": "local"}}},
|
|
47
|
+
"then": {"required": ["baseDir"]}
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"if": {"properties": {"storage": {"const": "remote"}}},
|
|
51
|
+
"then": {"required": ["endpoint"]}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"rag": {
|
|
56
|
+
"type": "object",
|
|
57
|
+
"additionalProperties": false,
|
|
58
|
+
"required": ["mode"],
|
|
59
|
+
"properties": {
|
|
60
|
+
"mode": {"enum": ["disabled", "local", "remote"]},
|
|
61
|
+
"baseUrl": {"type": "string", "format": "uri"}
|
|
62
|
+
},
|
|
63
|
+
"if": {"properties": {"mode": {"enum": ["local", "remote"]}}},
|
|
64
|
+
"then": {"required": ["baseUrl"]}
|
|
65
|
+
},
|
|
66
|
+
"gitProvider": {"type": "string", "minLength": 1}
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
"bootstrap": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"additionalProperties": false,
|
|
72
|
+
"required": ["defaultMode", "launcher", "profileLock"],
|
|
73
|
+
"properties": {
|
|
74
|
+
"defaultMode": {"enum": ["tracked", "untracked"]},
|
|
75
|
+
"launcher": {"type": "string", "minLength": 1},
|
|
76
|
+
"profileLock": {"type": "string", "minLength": 1}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
5
|
+
|
|
6
|
+
node --input-type=module - "$ROOT" <<'NODE'
|
|
7
|
+
import fs from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
|
|
10
|
+
const root = process.argv[2];
|
|
11
|
+
const schema = JSON.parse(fs.readFileSync(path.join(root, "schemas/cairnkeep-overlay.schema.json"), "utf8"));
|
|
12
|
+
const example = JSON.parse(fs.readFileSync(path.join(root, "examples/overlay-distribution/cairnkeep.overlay.json"), "utf8"));
|
|
13
|
+
|
|
14
|
+
if (schema.$schema !== "https://json-schema.org/draft/2020-12/schema") throw new Error("unexpected schema draft");
|
|
15
|
+
if (schema.properties.schemaVersion.const !== 1) throw new Error("unexpected schema version");
|
|
16
|
+
for (const field of schema.required) {
|
|
17
|
+
if (!(field in example)) throw new Error(`example missing required field: ${field}`);
|
|
18
|
+
}
|
|
19
|
+
for (const field of Object.keys(example)) {
|
|
20
|
+
if (!(field in schema.properties)) throw new Error(`example uses undeclared field: ${field}`);
|
|
21
|
+
}
|
|
22
|
+
if (example.core.package !== "@cairnkeep/cli") throw new Error("example must consume the public core");
|
|
23
|
+
if (example.policy.memory.storage !== "local") throw new Error("example must remain local-first");
|
|
24
|
+
if (example.policy.memory.transport !== "stdio") throw new Error("example must remain local-first");
|
|
25
|
+
NODE
|
|
26
|
+
|
|
27
|
+
pack_manifest=$(mktemp)
|
|
28
|
+
trap 'rm -f "$pack_manifest"' EXIT
|
|
29
|
+
npm --prefix "$ROOT" pack --dry-run --json --ignore-scripts > "$pack_manifest"
|
|
30
|
+
node --input-type=module - "$pack_manifest" <<'NODE'
|
|
31
|
+
import fs from "node:fs";
|
|
32
|
+
|
|
33
|
+
const manifest = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
|
|
34
|
+
const files = new Set(manifest[0].files.map((item) => item.path));
|
|
35
|
+
for (const required of [
|
|
36
|
+
"schemas/cairnkeep-overlay.schema.json",
|
|
37
|
+
"examples/overlay-distribution/cairnkeep.overlay.json",
|
|
38
|
+
"docs/overlay-distributions.md",
|
|
39
|
+
]) {
|
|
40
|
+
if (!files.has(required)) throw new Error(`npm package missing ${required}`);
|
|
41
|
+
}
|
|
42
|
+
NODE
|
|
43
|
+
|
|
44
|
+
echo "overlay schema checks passed"
|