@cavelang/cli 0.1.0

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 ADDED
@@ -0,0 +1,56 @@
1
+ # @cavelang/cli
2
+
3
+ The `cave` command — the whole stack behind one binary. Runs directly from
4
+ TypeScript sources via Node's type stripping; no build step.
5
+
6
+ ```
7
+ $ echo 'auth USES jwt @ 90%' | pnpm exec cave parse
8
+ ok: 1 claim
9
+
10
+ $ pnpm exec cave add notes.cave --db knowledge.db
11
+ added 12 claim(s), 3 edge(s)
12
+
13
+ $ pnpm exec cave query '?x USES jwt' --db knowledge.db
14
+ ?x = auth/middleware
15
+ ?x = api/gateway
16
+
17
+ $ pnpm exec cave query '?cause CAUSE app/crash' 'WHERE conf >= 0.7' --db knowledge.db
18
+ $ pnpm exec cave export --db knowledge.db --current
19
+ $ pnpm exec cave demo
20
+ ```
21
+
22
+ ## Commands
23
+
24
+ | Command | Flags | Behavior |
25
+ |---|---|---|
26
+ | `parse [file]` | `--json` | Lint (stdin by default). Exit 1 when diagnostics exist; `--json` dumps the AST document. |
27
+ | `add [file…] --db p` | `--strict`, `--no-prelude` | Ingest. Lenient by default (problems on stderr, valid lines land); `--strict` rolls back on any problem; `--no-prelude` starts from an empty registry instead of the standard §5.5 pairs. |
28
+ | `import [file…] --db p` | `--strict`, `--no-prelude` | Restore/merge a database from CAVE text — same operation as `add`, because canonical text *is* the interchange format. |
29
+ | `query <pattern…> --db p` | `--json`, `--all`, `--no-prelude` | CAVE-Q. Extra positionals join as lines, so `WHERE` filters ride as separate arguments. Bindings print as `?x = value`; fully bound patterns print the matched raw line (or the pattern itself for transitive matches, which carry no row). `--no-prelude` aligns the read-time registry with a store written via `add --no-prelude`. |
30
+ | `export --db p` | `--out <file>`, `--current`, `--no-prelude` | Canonical CAVE text — all rows in tx order, or current beliefs only. Stdout by default; `--out` writes a file and reports the claim count. |
31
+ | `mcp --db p` | `--no-prelude` | Serve the engine as an MCP server on stdio (see [`@cavelang/mcp`](../mcp)) — tools for add/query/search/about/neighbors/reconstruct/export/lint, with the §22 spec card as server instructions. |
32
+ | `ingest <globs…> --db p` | see `cave ingest --help` | LLM-driven ingestion of files through any headless agent (see [`@cavelang/ingest`](../ingest)): batching, instructions markdown, hybrid knowledge context, MCP or stdout agents, incremental digests, `--plan` NDJSON for SDK drivers. |
33
+ | `demo` | | The cave-loop multi-hop recovery demo (§18). |
34
+
35
+ ## Text backup / interchange
36
+
37
+ ```
38
+ $ cave export --db knowledge.db --out backup.cave
39
+ exported 812 claim(s) to backup.cave
40
+
41
+ $ cave import backup.cave --db restored.db
42
+ added 812 claim(s), 37 edge(s)
43
+ ```
44
+
45
+ The text round trip preserves every claim with its metadata, the **full
46
+ belief-series order** (rows export in tx order and re-ingest with fresh
47
+ monotonic tx ids, so latest-tx-wins resolution is unchanged),
48
+ qualifier/grouping edges, and in-band registry declarations (`REVERSE`,
49
+ `X IS verb`) — a restored database answers queries identically, inverse
50
+ reads included. Original transaction timestamps are re-minted: canonical
51
+ CAVE text carries no transaction identity. Use `--current` for a compact
52
+ backup of current beliefs only (history intentionally dropped).
53
+
54
+ Everything is testable without spawning: each command is a pure function
55
+ `(argv) → { code, out, err }` (`@cavelang/cli` exports them), and `main.ts` is
56
+ a four-line dispatcher. Tests cover both layers.
@@ -0,0 +1,40 @@
1
+ /**
2
+ * `cave` command implementation — pure functions from parsed arguments to
3
+ * `{ code, out, err }`, so tests can call commands directly and `main.ts`
4
+ * stays a thin dispatcher.
5
+ *
6
+ * Commands:
7
+ *
8
+ * - `cave parse [file]` — lint / dump the AST (`--json`)
9
+ * - `cave add [file…] --db <path>` — ingest into a store (`--strict`)
10
+ * - `cave query '<pattern>' --db <path>` — CAVE-Q (`--json`, `--all`)
11
+ * - `cave export --db <path>` — canonical text out (`--current`)
12
+ * - `cave demo` — the cave-loop multi-hop recovery demo
13
+ *
14
+ * `file` defaults to stdin (`-`).
15
+ */
16
+ export type Output = {
17
+ readonly code: number;
18
+ readonly out: string;
19
+ readonly err: string;
20
+ };
21
+ export declare const usage = "cave \u2014 Compressed Atomic Verb Expressions\n\nUsage:\n cave parse [file] [--json] lint CAVE text (stdin when no file)\n cave add [file...] --db <path> ingest into a store [--strict] [--no-prelude]\n cave import [file...] --db <path> restore/merge a database from CAVE text (same as add)\n cave query <pattern> --db <path> run a CAVE-Q pattern [--json] [--all] [--no-prelude]\n cave export --db <path> [--out <file>] emit canonical CAVE text [--current] [--no-prelude]\n cave mcp --db <path> serve the engine as an MCP server on stdio [--no-prelude]\n cave ingest <globs...> --db <path> LLM-driven ingestion of files (cave ingest --help)\n cave demo run the cave-loop reconstruction demo\n cave help this text\n\nThe spec lives in the .claude/skills/ directory at the repository root\n(section index in README.md).";
22
+ export declare const parseCommand: (argv: readonly string[]) => Output;
23
+ export declare const addCommand: (argv: readonly string[]) => Output;
24
+ /**
25
+ * `cave import` — restore/merge a knowledge database from CAVE text files.
26
+ * Identical to `add` by design: canonical CAVE text *is* the interchange
27
+ * format (spec §2.2), so importing a file exported with `cave export`
28
+ * replays its claims append-only. What the text round trip preserves:
29
+ * every claim with metadata, full belief-series order (rows export in tx
30
+ * order and re-ingest with fresh monotonic tx ids), qualifier/grouping
31
+ * edges, and in-band registry declarations. Original tx timestamps are
32
+ * re-minted — the text format carries no transaction identity.
33
+ */
34
+ export declare const importCommand: (argv: readonly string[]) => Output;
35
+ export declare const queryCommand: (argv: readonly string[]) => Output;
36
+ export declare const exportCommand: (argv: readonly string[]) => Output;
37
+ export declare const demoCommand: () => Output;
38
+ /** Dispatches one invocation. */
39
+ export declare const cave: (argv: readonly string[]) => Output;
40
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAUH,MAAM,MAAM,MAAM,GAAG;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB,CAAA;AAQD,eAAO,MAAM,KAAK,86BAcY,CAAA;AAO9B,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,MAyBtD,CAAA;AAkCD,eAAO,MAAM,UAAU,SAhCwB,SAAS,MAAM,EAAE,KAAG,MAgCrB,CAAA;AAE9C;;;;;;;;;GASG;AACH,eAAO,MAAM,aAAa,SA5CqB,SAAS,MAAM,EAAE,KAAG,MA4Cf,CAAA;AAEpD,eAAO,MAAM,YAAY,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,MAyCtD,CAAA;AAED,eAAO,MAAM,aAAa,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,MA0BvD,CAAA;AAED,eAAO,MAAM,WAAW,QAAO,MACS,CAAA;AAExC,iCAAiC;AACjC,eAAO,MAAM,IAAI,GAAI,MAAM,SAAS,MAAM,EAAE,KAAG,MA4B9C,CAAA"}
@@ -0,0 +1,218 @@
1
+ /**
2
+ * `cave` command implementation — pure functions from parsed arguments to
3
+ * `{ code, out, err }`, so tests can call commands directly and `main.ts`
4
+ * stays a thin dispatcher.
5
+ *
6
+ * Commands:
7
+ *
8
+ * - `cave parse [file]` — lint / dump the AST (`--json`)
9
+ * - `cave add [file…] --db <path>` — ingest into a store (`--strict`)
10
+ * - `cave query '<pattern>' --db <path>` — CAVE-Q (`--json`, `--all`)
11
+ * - `cave export --db <path>` — canonical text out (`--current`)
12
+ * - `cave demo` — the cave-loop multi-hop recovery demo
13
+ *
14
+ * `file` defaults to stdin (`-`).
15
+ */
16
+ import { readFileSync, writeFileSync } from 'node:fs';
17
+ import { parseArgs } from 'node:util';
18
+ import { parseDocument } from '@cavelang/parser';
19
+ import { Registry, standardRegistry } from '@cavelang/canonical';
20
+ import { open } from '@cavelang/store';
21
+ import { query as caveQuery } from '@cavelang/query';
22
+ import { Demo } from '@cavelang/loop';
23
+ const ok = (out) => ({ code: 0, out, err: '' });
24
+ const fail = (err, code = 1) => ({ code, out: '', err });
25
+ export const usage = `cave — Compressed Atomic Verb Expressions
26
+
27
+ Usage:
28
+ cave parse [file] [--json] lint CAVE text (stdin when no file)
29
+ cave add [file...] --db <path> ingest into a store [--strict] [--no-prelude]
30
+ cave import [file...] --db <path> restore/merge a database from CAVE text (same as add)
31
+ cave query <pattern> --db <path> run a CAVE-Q pattern [--json] [--all] [--no-prelude]
32
+ cave export --db <path> [--out <file>] emit canonical CAVE text [--current] [--no-prelude]
33
+ cave mcp --db <path> serve the engine as an MCP server on stdio [--no-prelude]
34
+ cave ingest <globs...> --db <path> LLM-driven ingestion of files (cave ingest --help)
35
+ cave demo run the cave-loop reconstruction demo
36
+ cave help this text
37
+
38
+ The spec lives in the .claude/skills/ directory at the repository root
39
+ (section index in README.md).`;
40
+ const readInput = (files) => files.length === 0 || (files.length === 1 && files[0] === '-') ?
41
+ readFileSync(0, 'utf8') :
42
+ files.map(file => readFileSync(file, 'utf8')).join('\n');
43
+ export const parseCommand = (argv) => {
44
+ const { values, positionals } = parseArgs({
45
+ args: [...argv],
46
+ options: { json: { type: 'boolean' } },
47
+ allowPositionals: true
48
+ });
49
+ const input = readInput(positionals);
50
+ const document = parseDocument(input);
51
+ if (values.json === true) {
52
+ return ok(`${JSON.stringify(document, undefined, 2)}\n`);
53
+ }
54
+ const counts = new Map();
55
+ for (const line of document.lines) {
56
+ counts.set(line.kind, (counts.get(line.kind) ?? 0) + 1);
57
+ }
58
+ const summary = [...counts]
59
+ .map(([kind, count]) => `${count} ${kind}`)
60
+ .join(', ');
61
+ if (document.diagnostics.length === 0) {
62
+ return ok(`ok: ${summary}\n`);
63
+ }
64
+ const problems = document.diagnostics
65
+ .map(diagnostic => `line ${diagnostic.line}: ${diagnostic.message}`)
66
+ .join('\n');
67
+ return { code: 1, out: `${summary}\n`, err: `${problems}\n` };
68
+ };
69
+ const ingestCommand = (name) => (argv) => {
70
+ const { values, positionals } = parseArgs({
71
+ args: [...argv],
72
+ options: {
73
+ db: { type: 'string' },
74
+ strict: { type: 'boolean' },
75
+ 'no-prelude': { type: 'boolean' }
76
+ },
77
+ allowPositionals: true
78
+ });
79
+ if (values.db === undefined) {
80
+ return fail(`cave ${name}: --db <path> is required\n`);
81
+ }
82
+ const input = readInput(positionals);
83
+ const store = open(values.db, values['no-prelude'] === true ? { registry: Registry.empty } : {});
84
+ try {
85
+ const result = store.ingest(input, { strict: values.strict === true });
86
+ const problems = result.problems
87
+ .map(problem => `line ${problem.line}: ${problem.message}`)
88
+ .join('\n');
89
+ return {
90
+ code: 0,
91
+ out: `added ${result.ids.length} claim(s), ${result.edges} edge(s)\n`,
92
+ err: problems === '' ? '' : `${problems}\n`
93
+ };
94
+ }
95
+ catch (error) {
96
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
97
+ }
98
+ finally {
99
+ store.close();
100
+ }
101
+ };
102
+ export const addCommand = ingestCommand('add');
103
+ /**
104
+ * `cave import` — restore/merge a knowledge database from CAVE text files.
105
+ * Identical to `add` by design: canonical CAVE text *is* the interchange
106
+ * format (spec §2.2), so importing a file exported with `cave export`
107
+ * replays its claims append-only. What the text round trip preserves:
108
+ * every claim with metadata, full belief-series order (rows export in tx
109
+ * order and re-ingest with fresh monotonic tx ids), qualifier/grouping
110
+ * edges, and in-band registry declarations. Original tx timestamps are
111
+ * re-minted — the text format carries no transaction identity.
112
+ */
113
+ export const importCommand = ingestCommand('import');
114
+ export const queryCommand = (argv) => {
115
+ const { values, positionals } = parseArgs({
116
+ args: [...argv],
117
+ options: {
118
+ db: { type: 'string' },
119
+ json: { type: 'boolean' },
120
+ all: { type: 'boolean' },
121
+ 'no-prelude': { type: 'boolean' }
122
+ },
123
+ allowPositionals: true
124
+ });
125
+ if (values.db === undefined) {
126
+ return fail('cave query: --db <path> is required\n');
127
+ }
128
+ if (positionals.length === 0) {
129
+ return fail('cave query: a pattern is required (spec §12.1)\n');
130
+ }
131
+ const pattern = positionals.join('\n');
132
+ const store = open(values.db, values['no-prelude'] === true ? { registry: Registry.empty } : {});
133
+ try {
134
+ const matches = caveQuery(store, pattern, { all: values.all === true });
135
+ if (values.json === true) {
136
+ return ok(`${JSON.stringify(matches, undefined, 2)}\n`);
137
+ }
138
+ if (matches.length === 0) {
139
+ return ok('no matches\n');
140
+ }
141
+ const lines = matches.map(match => {
142
+ const bindings = Object.entries(match.bindings)
143
+ .map(([name, value]) => `?${name} = ${value}`)
144
+ .join(' ');
145
+ // A fully bound pattern has no bindings to print; transitive matches
146
+ // additionally carry no row — confirm with the pattern itself.
147
+ return bindings !== '' ? bindings : match.row?.raw_line ?? pattern.split('\n')[0];
148
+ });
149
+ return ok(`${lines.join('\n')}\n`);
150
+ }
151
+ catch (error) {
152
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
153
+ }
154
+ finally {
155
+ store.close();
156
+ }
157
+ };
158
+ export const exportCommand = (argv) => {
159
+ const { values } = parseArgs({
160
+ args: [...argv],
161
+ options: {
162
+ db: { type: 'string' },
163
+ current: { type: 'boolean' },
164
+ out: { type: 'string' },
165
+ 'no-prelude': { type: 'boolean' }
166
+ },
167
+ allowPositionals: false
168
+ });
169
+ if (values.db === undefined) {
170
+ return fail('cave export: --db <path> is required\n');
171
+ }
172
+ const store = open(values.db, values['no-prelude'] === true ? { registry: Registry.empty } : {});
173
+ try {
174
+ const text = store.exportText({ current: values.current === true });
175
+ if (values.out === undefined) {
176
+ return ok(text);
177
+ }
178
+ writeFileSync(values.out, text);
179
+ const claims = text === '' ? 0 : text.trimEnd().split('\n').length;
180
+ return ok(`exported ${claims} claim(s) to ${values.out}\n`);
181
+ }
182
+ finally {
183
+ store.close();
184
+ }
185
+ };
186
+ export const demoCommand = () => ok(`${Demo.run().lines.join('\n')}\n`);
187
+ /** Dispatches one invocation. */
188
+ export const cave = (argv) => {
189
+ const [command, ...rest] = argv;
190
+ try {
191
+ switch (command) {
192
+ case 'parse':
193
+ return parseCommand(rest);
194
+ case 'add':
195
+ return addCommand(rest);
196
+ case 'import':
197
+ return importCommand(rest);
198
+ case 'query':
199
+ case 'q':
200
+ return queryCommand(rest);
201
+ case 'export':
202
+ return exportCommand(rest);
203
+ case 'demo':
204
+ return demoCommand();
205
+ case undefined:
206
+ case 'help':
207
+ case '--help':
208
+ case '-h':
209
+ return ok(`${usage}\n`);
210
+ default:
211
+ return fail(`cave: unknown command ${JSON.stringify(command)}\n\n${usage}\n`, 2);
212
+ }
213
+ }
214
+ catch (error) {
215
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`);
216
+ }
217
+ };
218
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@cavelang/cli` — the `cave` command (parse / add / query / export / demo).
3
+ * Programmatic access to the same implementations lives here.
4
+ */
5
+ export { addCommand, cave, demoCommand, exportCommand, importCommand, parseCommand, queryCommand, usage } from './cli.ts';
6
+ export type { Output } from './cli.ts';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA;AACzH,YAAY,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * `@cavelang/cli` — the `cave` command (parse / add / query / export / demo).
3
+ * Programmatic access to the same implementations lives here.
4
+ */
5
+ export { addCommand, cave, demoCommand, exportCommand, importCommand, parseCommand, queryCommand, usage } from "./cli.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA"}
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ /** `cave` binary entry point — see `cli.ts` for the command implementations. */
3
+ export {};
4
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AACA,gFAAgF"}
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env node
2
+ /** `cave` binary entry point — see `cli.ts` for the command implementations. */
3
+ import { cave } from "./cli.js";
4
+ const argv = process.argv.slice(2);
5
+ if (argv[0] === 'mcp') {
6
+ // Long-running: serves MCP on stdio until the client disconnects.
7
+ const { runMcp } = await import('@cavelang/mcp');
8
+ process.exitCode = await runMcp(argv.slice(1));
9
+ }
10
+ else if (argv[0] === 'ingest') {
11
+ // Long-running: drives an LLM agent over batches of files.
12
+ const { runIngest } = await import('@cavelang/ingest');
13
+ process.exitCode = await runIngest(argv.slice(1));
14
+ }
15
+ else {
16
+ const { code, out, err } = cave(argv);
17
+ if (out !== '') {
18
+ process.stdout.write(out);
19
+ }
20
+ if (err !== '') {
21
+ process.stderr.write(err);
22
+ }
23
+ process.exitCode = code;
24
+ }
25
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../../src/main.ts"],"names":[],"mappings":";AACA,gFAAgF;AAEhF,OAAO,EAAE,IAAI,EAAE,MAAM,UAAU,CAAA;AAE/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;AAClC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,EAAE,CAAC;IACtB,kEAAkE;IAClE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAA;IAChD,OAAO,CAAC,QAAQ,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AAChD,CAAC;KAAM,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;IAChC,2DAA2D;IAC3D,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,kBAAkB,CAAC,CAAA;IACtD,OAAO,CAAC,QAAQ,GAAG,MAAM,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;AACnD,CAAC;KAAM,CAAC;IACN,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,IAAI,CAAC,CAAA;IACrC,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IACD,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;IACD,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAA;AACzB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@cavelang/cli",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "cave — CLI for parsing, storing, querying and exporting CAVE knowledge.",
6
+ "license": "CC0-1.0",
7
+ "author": "Mirek Rusin (https://github.com/mirek)",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/mirek/cave.git",
11
+ "directory": "packages/cli"
12
+ },
13
+ "engines": {
14
+ "node": ">=22.18"
15
+ },
16
+ "bin": {
17
+ "cave": "./dist/src/main.js"
18
+ },
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/src/index.d.ts",
22
+ "default": "./dist/src/index.js"
23
+ },
24
+ "./main": {
25
+ "types": "./dist/src/main.d.ts",
26
+ "default": "./dist/src/main.js"
27
+ }
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "!dist/test",
32
+ "!dist/tsconfig.tsbuildinfo",
33
+ "src",
34
+ "!src/**/*.test.ts",
35
+ "README.md",
36
+ "License.md"
37
+ ],
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "@cavelang/canonical": "0.1.0",
43
+ "@cavelang/loop": "0.1.0",
44
+ "@cavelang/core": "0.1.0",
45
+ "@cavelang/query": "0.1.0",
46
+ "@cavelang/parser": "0.1.0",
47
+ "@cavelang/store": "0.1.0",
48
+ "@cavelang/mcp": "0.1.0",
49
+ "@cavelang/ingest": "0.1.0"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc -b",
53
+ "test": "node --disable-warning=ExperimentalWarning --test 'test/*.test.ts'",
54
+ "typecheck": "tsc -b"
55
+ }
56
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,234 @@
1
+ /**
2
+ * `cave` command implementation — pure functions from parsed arguments to
3
+ * `{ code, out, err }`, so tests can call commands directly and `main.ts`
4
+ * stays a thin dispatcher.
5
+ *
6
+ * Commands:
7
+ *
8
+ * - `cave parse [file]` — lint / dump the AST (`--json`)
9
+ * - `cave add [file…] --db <path>` — ingest into a store (`--strict`)
10
+ * - `cave query '<pattern>' --db <path>` — CAVE-Q (`--json`, `--all`)
11
+ * - `cave export --db <path>` — canonical text out (`--current`)
12
+ * - `cave demo` — the cave-loop multi-hop recovery demo
13
+ *
14
+ * `file` defaults to stdin (`-`).
15
+ */
16
+
17
+ import { readFileSync, writeFileSync } from 'node:fs'
18
+ import { parseArgs } from 'node:util'
19
+ import { parseDocument } from '@cavelang/parser'
20
+ import { Registry, standardRegistry } from '@cavelang/canonical'
21
+ import { open } from '@cavelang/store'
22
+ import { query as caveQuery } from '@cavelang/query'
23
+ import { Demo } from '@cavelang/loop'
24
+
25
+ export type Output = {
26
+ readonly code: number
27
+ readonly out: string
28
+ readonly err: string
29
+ }
30
+
31
+ const ok = (out: string): Output =>
32
+ ({ code: 0, out, err: '' })
33
+
34
+ const fail = (err: string, code = 1): Output =>
35
+ ({ code, out: '', err })
36
+
37
+ export const usage = `cave — Compressed Atomic Verb Expressions
38
+
39
+ Usage:
40
+ cave parse [file] [--json] lint CAVE text (stdin when no file)
41
+ cave add [file...] --db <path> ingest into a store [--strict] [--no-prelude]
42
+ cave import [file...] --db <path> restore/merge a database from CAVE text (same as add)
43
+ cave query <pattern> --db <path> run a CAVE-Q pattern [--json] [--all] [--no-prelude]
44
+ cave export --db <path> [--out <file>] emit canonical CAVE text [--current] [--no-prelude]
45
+ cave mcp --db <path> serve the engine as an MCP server on stdio [--no-prelude]
46
+ cave ingest <globs...> --db <path> LLM-driven ingestion of files (cave ingest --help)
47
+ cave demo run the cave-loop reconstruction demo
48
+ cave help this text
49
+
50
+ The spec lives in the .claude/skills/ directory at the repository root
51
+ (section index in README.md).`
52
+
53
+ const readInput = (files: readonly string[]): string =>
54
+ files.length === 0 || (files.length === 1 && files[0] === '-') ?
55
+ readFileSync(0, 'utf8') :
56
+ files.map(file => readFileSync(file, 'utf8')).join('\n')
57
+
58
+ export const parseCommand = (argv: readonly string[]): Output => {
59
+ const { values, positionals } = parseArgs({
60
+ args: [...argv],
61
+ options: { json: { type: 'boolean' } },
62
+ allowPositionals: true
63
+ })
64
+ const input = readInput(positionals)
65
+ const document = parseDocument(input)
66
+ if (values.json === true) {
67
+ return ok(`${JSON.stringify(document, undefined, 2)}\n`)
68
+ }
69
+ const counts = new Map<string, number>()
70
+ for (const line of document.lines) {
71
+ counts.set(line.kind, (counts.get(line.kind) ?? 0) + 1)
72
+ }
73
+ const summary = [...counts]
74
+ .map(([kind, count]) => `${count} ${kind}`)
75
+ .join(', ')
76
+ if (document.diagnostics.length === 0) {
77
+ return ok(`ok: ${summary}\n`)
78
+ }
79
+ const problems = document.diagnostics
80
+ .map(diagnostic => `line ${diagnostic.line}: ${diagnostic.message}`)
81
+ .join('\n')
82
+ return { code: 1, out: `${summary}\n`, err: `${problems}\n` }
83
+ }
84
+
85
+ const ingestCommand = (name: string) => (argv: readonly string[]): Output => {
86
+ const { values, positionals } = parseArgs({
87
+ args: [...argv],
88
+ options: {
89
+ db: { type: 'string' },
90
+ strict: { type: 'boolean' },
91
+ 'no-prelude': { type: 'boolean' }
92
+ },
93
+ allowPositionals: true
94
+ })
95
+ if (values.db === undefined) {
96
+ return fail(`cave ${name}: --db <path> is required\n`)
97
+ }
98
+ const input = readInput(positionals)
99
+ const store = open(values.db, values['no-prelude'] === true ? { registry: Registry.empty } : {})
100
+ try {
101
+ const result = store.ingest(input, { strict: values.strict === true })
102
+ const problems = result.problems
103
+ .map(problem => `line ${problem.line}: ${problem.message}`)
104
+ .join('\n')
105
+ return {
106
+ code: 0,
107
+ out: `added ${result.ids.length} claim(s), ${result.edges} edge(s)\n`,
108
+ err: problems === '' ? '' : `${problems}\n`
109
+ }
110
+ } catch (error) {
111
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`)
112
+ } finally {
113
+ store.close()
114
+ }
115
+ }
116
+
117
+ export const addCommand = ingestCommand('add')
118
+
119
+ /**
120
+ * `cave import` — restore/merge a knowledge database from CAVE text files.
121
+ * Identical to `add` by design: canonical CAVE text *is* the interchange
122
+ * format (spec §2.2), so importing a file exported with `cave export`
123
+ * replays its claims append-only. What the text round trip preserves:
124
+ * every claim with metadata, full belief-series order (rows export in tx
125
+ * order and re-ingest with fresh monotonic tx ids), qualifier/grouping
126
+ * edges, and in-band registry declarations. Original tx timestamps are
127
+ * re-minted — the text format carries no transaction identity.
128
+ */
129
+ export const importCommand = ingestCommand('import')
130
+
131
+ export const queryCommand = (argv: readonly string[]): Output => {
132
+ const { values, positionals } = parseArgs({
133
+ args: [...argv],
134
+ options: {
135
+ db: { type: 'string' },
136
+ json: { type: 'boolean' },
137
+ all: { type: 'boolean' },
138
+ 'no-prelude': { type: 'boolean' }
139
+ },
140
+ allowPositionals: true
141
+ })
142
+ if (values.db === undefined) {
143
+ return fail('cave query: --db <path> is required\n')
144
+ }
145
+ if (positionals.length === 0) {
146
+ return fail('cave query: a pattern is required (spec §12.1)\n')
147
+ }
148
+ const pattern = positionals.join('\n')
149
+ const store = open(values.db, values['no-prelude'] === true ? { registry: Registry.empty } : {})
150
+ try {
151
+ const matches = caveQuery(store, pattern, { all: values.all === true })
152
+ if (values.json === true) {
153
+ return ok(`${JSON.stringify(matches, undefined, 2)}\n`)
154
+ }
155
+ if (matches.length === 0) {
156
+ return ok('no matches\n')
157
+ }
158
+ const lines = matches.map(match => {
159
+ const bindings = Object.entries(match.bindings)
160
+ .map(([name, value]) => `?${name} = ${value}`)
161
+ .join(' ')
162
+ // A fully bound pattern has no bindings to print; transitive matches
163
+ // additionally carry no row — confirm with the pattern itself.
164
+ return bindings !== '' ? bindings : match.row?.raw_line ?? pattern.split('\n')[0]!
165
+ })
166
+ return ok(`${lines.join('\n')}\n`)
167
+ } catch (error) {
168
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`)
169
+ } finally {
170
+ store.close()
171
+ }
172
+ }
173
+
174
+ export const exportCommand = (argv: readonly string[]): Output => {
175
+ const { values } = parseArgs({
176
+ args: [...argv],
177
+ options: {
178
+ db: { type: 'string' },
179
+ current: { type: 'boolean' },
180
+ out: { type: 'string' },
181
+ 'no-prelude': { type: 'boolean' }
182
+ },
183
+ allowPositionals: false
184
+ })
185
+ if (values.db === undefined) {
186
+ return fail('cave export: --db <path> is required\n')
187
+ }
188
+ const store = open(values.db, values['no-prelude'] === true ? { registry: Registry.empty } : {})
189
+ try {
190
+ const text = store.exportText({ current: values.current === true })
191
+ if (values.out === undefined) {
192
+ return ok(text)
193
+ }
194
+ writeFileSync(values.out, text)
195
+ const claims = text === '' ? 0 : text.trimEnd().split('\n').length
196
+ return ok(`exported ${claims} claim(s) to ${values.out}\n`)
197
+ } finally {
198
+ store.close()
199
+ }
200
+ }
201
+
202
+ export const demoCommand = (): Output =>
203
+ ok(`${Demo.run().lines.join('\n')}\n`)
204
+
205
+ /** Dispatches one invocation. */
206
+ export const cave = (argv: readonly string[]): Output => {
207
+ const [command, ...rest] = argv
208
+ try {
209
+ switch (command) {
210
+ case 'parse':
211
+ return parseCommand(rest)
212
+ case 'add':
213
+ return addCommand(rest)
214
+ case 'import':
215
+ return importCommand(rest)
216
+ case 'query':
217
+ case 'q':
218
+ return queryCommand(rest)
219
+ case 'export':
220
+ return exportCommand(rest)
221
+ case 'demo':
222
+ return demoCommand()
223
+ case undefined:
224
+ case 'help':
225
+ case '--help':
226
+ case '-h':
227
+ return ok(`${usage}\n`)
228
+ default:
229
+ return fail(`cave: unknown command ${JSON.stringify(command)}\n\n${usage}\n`, 2)
230
+ }
231
+ } catch (error) {
232
+ return fail(`${error instanceof Error ? error.message : String(error)}\n`)
233
+ }
234
+ }
package/src/index.ts ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `@cavelang/cli` — the `cave` command (parse / add / query / export / demo).
3
+ * Programmatic access to the same implementations lives here.
4
+ */
5
+
6
+ export { addCommand, cave, demoCommand, exportCommand, importCommand, parseCommand, queryCommand, usage } from './cli.ts'
7
+ export type { Output } from './cli.ts'
package/src/main.ts ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /** `cave` binary entry point — see `cli.ts` for the command implementations. */
3
+
4
+ import { cave } from './cli.ts'
5
+
6
+ const argv = process.argv.slice(2)
7
+ if (argv[0] === 'mcp') {
8
+ // Long-running: serves MCP on stdio until the client disconnects.
9
+ const { runMcp } = await import('@cavelang/mcp')
10
+ process.exitCode = await runMcp(argv.slice(1))
11
+ } else if (argv[0] === 'ingest') {
12
+ // Long-running: drives an LLM agent over batches of files.
13
+ const { runIngest } = await import('@cavelang/ingest')
14
+ process.exitCode = await runIngest(argv.slice(1))
15
+ } else {
16
+ const { code, out, err } = cave(argv)
17
+ if (out !== '') {
18
+ process.stdout.write(out)
19
+ }
20
+ if (err !== '') {
21
+ process.stderr.write(err)
22
+ }
23
+ process.exitCode = code
24
+ }