@cavelang/cli 0.32.3 → 0.34.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 +16 -3
- package/dist/internal/automate/main.d.ts.map +1 -1
- package/dist/internal/automate/main.js +7 -2
- package/dist/internal/automate/main.js.map +1 -1
- package/dist/internal/connect/main.d.ts.map +1 -1
- package/dist/internal/connect/main.js +16 -10
- package/dist/internal/connect/main.js.map +1 -1
- package/dist/internal/connect/template.d.ts +9 -2
- package/dist/internal/connect/template.d.ts.map +1 -1
- package/dist/internal/connect/template.js +36 -5
- package/dist/internal/connect/template.js.map +1 -1
- package/dist/internal/ingest/main.d.ts.map +1 -1
- package/dist/internal/ingest/main.js +18 -2
- package/dist/internal/ingest/main.js.map +1 -1
- package/dist/internal/loop/demo.d.ts.map +1 -1
- package/dist/internal/loop/demo.js +2 -1
- package/dist/internal/loop/demo.js.map +1 -1
- package/dist/internal/mcp/guide.js +3 -3
- package/dist/internal/mcp/guide.js.map +1 -1
- package/dist/internal/mcp/main.d.ts +1 -1
- package/dist/internal/mcp/main.d.ts.map +1 -1
- package/dist/internal/mcp/main.js +13 -3
- package/dist/internal/mcp/main.js.map +1 -1
- package/dist/internal/mcp/tools.d.ts.map +1 -1
- package/dist/internal/mcp/tools.js +39 -10
- package/dist/internal/mcp/tools.js.map +1 -1
- package/dist/internal/sync/sync.d.ts +2 -3
- package/dist/internal/sync/sync.d.ts.map +1 -1
- package/dist/internal/sync/sync.js +3 -15
- package/dist/internal/sync/sync.js.map +1 -1
- package/dist/internal/view/main.js +3 -3
- package/dist/internal/view/main.js.map +1 -1
- package/dist/internal/view/report.d.ts.map +1 -1
- package/dist/internal/view/report.js +6 -1
- package/dist/internal/view/report.js.map +1 -1
- package/dist/src/cli.d.ts +14 -5
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +224 -34
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands.d.ts +3 -0
- package/dist/src/commands.d.ts.map +1 -1
- package/dist/src/commands.js +1 -0
- package/dist/src/commands.js.map +1 -1
- package/dist/src/dispatch.d.ts.map +1 -1
- package/dist/src/dispatch.js +3 -1
- package/dist/src/dispatch.js.map +1 -1
- package/dist/src/doctor.js +1 -1
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/package.json +19 -19
- package/src/cli.ts +232 -37
- package/src/commands.ts +1 -0
- package/src/dispatch.ts +3 -1
- package/src/doctor.ts +1 -1
- package/src/index.ts +2 -2
package/src/cli.ts
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
* - `cave highlight [file]` — ANSI syntax colors (async)
|
|
10
10
|
* - `cave add [--db <path>] [file…]` — ingest into a store (`--strict`, `--check`)
|
|
11
11
|
* - `cave query [--db <path>] '<pattern>'` — CAVE-Q (`--json`, `--all`, `--aliases`, `--as-of`, `--resolve`)
|
|
12
|
+
* - `cave search [--db <path>] <terms..>` — FTS5 full-text search over claims and comments (`--raw`, `--limit`, `--json`)
|
|
12
13
|
* - `cave resolve [--db <path>]` — contested facts under the §26 policy (`--aliases`, `--policy`, `--json`)
|
|
13
14
|
* - `cave derive [--db <path>] [rules.cave…]` — fire rules (`--dry-run`, `--full`, `--list`, `--retract`)
|
|
14
15
|
* - `cave act [--db <path>] <name> [param=value…]` — execute an action (spec §25; `--declare`, `--list`, `--retract`)
|
|
15
16
|
* - `cave automate [--db <path>]` — the event-driven loop (spec §29; async)
|
|
16
17
|
* - `cave check [--db <path>]` — knowledge health report (`--stale`, `--json`)
|
|
17
|
-
* - `cave backup [--db <path>] --out <path>` — exact verified
|
|
18
|
+
* - `cave backup [--db <path>] --out <path>` — exact verified store snapshot
|
|
18
19
|
* - `cave restore <snapshot> --db <path>` — verify and atomically restore a snapshot
|
|
19
20
|
* - `cave generate [--db <path>]` — versioned TypeScript client from EXPECTS declarations
|
|
20
21
|
* - `cave suggest-alias [--db <path>]` — same-entity candidates (spec §27; async)
|
|
@@ -38,11 +39,11 @@ import { readFileSync, readSync, statSync, writeFileSync } from 'node:fs'
|
|
|
38
39
|
import { resolve } from 'node:path'
|
|
39
40
|
import { parseArgs } from 'node:util'
|
|
40
41
|
import { Version } from '@cavelang/core'
|
|
41
|
-
import { parseDocument } from '@cavelang/parser'
|
|
42
|
+
import { parseDocument, Token } from '@cavelang/parser'
|
|
42
43
|
import { Registry, standardRegistry } from '@cavelang/canonical'
|
|
43
|
-
import { Sensitivity, backup as backupStore, defaultDbPath,
|
|
44
|
-
import type { Store } from '@cavelang/store'
|
|
45
|
-
import { defaultLimit as defaultQueryLimit, page as caveQueryPage } from '@cavelang/query'
|
|
44
|
+
import { LocateError, Sensitivity, backup as backupStore, defaultDbPath, openAt, restoreBackup, verifyBackup } from '@cavelang/store'
|
|
45
|
+
import type { OpenIntent, Store } from '@cavelang/store'
|
|
46
|
+
import { defaultLimit as defaultQueryLimit, maxLimit as maxQueryLimit, page as caveQueryPage } from '@cavelang/query'
|
|
46
47
|
import {
|
|
47
48
|
check as caveCheck, defaultMinScore, defaultStaleDays, gatedIngest, judgePrompt, parseJudgeReply,
|
|
48
49
|
generateClient, suggestAliases, writeSuggestions
|
|
@@ -115,13 +116,14 @@ Usage:
|
|
|
115
116
|
cave add [--db <path>] [file...] ingest into a store [--strict] [--check] [--no-prelude] [--no-src]
|
|
116
117
|
cave import [--db <path>] [file...] restore/merge from CAVE text (add without @src: stamping)
|
|
117
118
|
cave query [--db <path>] <pattern> run a bounded CAVE-Q page [--limit <n>] [--cursor <token>] [--json]
|
|
119
|
+
cave search [--db <path>] <terms..> full-text search over claims and comments (FTS5) [--raw] [--limit <n>] [--json]
|
|
118
120
|
cave resolve [--db <path>] contested facts + winners (spec §26) [--aliases] [--policy] [--json]
|
|
119
121
|
cave derive [--db <path>] [rules.cave..] declare + fire rules (spec §24) [--dry-run] [--full] [--list] [--retract <rule>]
|
|
120
122
|
cave act [--db <path>] <name> [p=v...] execute an action (spec §25) [--dry-run] [--no-check] [--hooks <file>]
|
|
121
123
|
cave act --declare [file...] declare actions from a CAVE document; --list / --retract <name> manage them
|
|
122
124
|
cave automate [--db <path>] event-driven loop (spec §29): new claims fire rules/actions/hooks/agent prompts [--once]
|
|
123
125
|
cave check [--db <path>] knowledge health report (spec §20) [--stale <days>] [--json]
|
|
124
|
-
cave backup [--db <path>] --out <file> exact verified
|
|
126
|
+
cave backup [--db <path>] --out <file> exact verified store snapshot [--force]
|
|
125
127
|
cave restore <snapshot> --db <path> verify and atomically restore an exact snapshot [--force]
|
|
126
128
|
cave generate [--db <path>] generate a typed client from EXPECTS [--out <file>] [--version <n>]
|
|
127
129
|
cave suggest-alias [--db <path>] propose same-entity ALIAS candidates (spec §27) [--min <s>] [--agent] [--write]
|
|
@@ -140,10 +142,12 @@ Usage:
|
|
|
140
142
|
cave help [command] this text, or one command's options and examples
|
|
141
143
|
|
|
142
144
|
Every command answers --help. --db defaults to $CAVE_DB, or cave.db in
|
|
143
|
-
the current directory
|
|
145
|
+
the current directory, and names a SQLite store or a CAVE text file: text
|
|
146
|
+
replays into an in-memory store for commands that only read (writes refuse
|
|
147
|
+
it), and a read never creates a missing database. The spec lives in the .claude/skills/ directory at
|
|
144
148
|
the repository root (section index in README.md).`
|
|
145
149
|
|
|
146
|
-
const dbHelp = `--db <path>
|
|
150
|
+
const dbHelp = `--db <path> store: a SQLite file, or CAVE text read into memory (default: $CAVE_DB, or cave.db)`
|
|
147
151
|
|
|
148
152
|
/** Per-command help, printed for \`cave <command> --help\` and \`cave help <command>\`. */
|
|
149
153
|
export const commandHelp: Record<string, string> = {
|
|
@@ -278,10 +282,13 @@ Options:
|
|
|
278
282
|
|
|
279
283
|
Patterns are claim triples with ?variables and optional metadata filters
|
|
280
284
|
(spec §12). A second positional starting with WHERE filters on conf,
|
|
281
|
-
value or tx.
|
|
285
|
+
value or tx. A binding line carries the matched claim's comment
|
|
286
|
+
(?x = value ; comment), so evidence written next to a claim travels
|
|
287
|
+
with the answer; fully bound patterns print the raw line, comment included.
|
|
282
288
|
|
|
283
289
|
Examples:
|
|
284
290
|
cave query '?x USES jwt'
|
|
291
|
+
cave query --db notes.cave '?x USES jwt'
|
|
285
292
|
cave query --db k.db '?x HAS bug: ?bug #security'
|
|
286
293
|
cave query --db k.db '?cause CAUSE app/crash' 'WHERE conf >= 0.5'
|
|
287
294
|
cave query --db k.db 'terrier EXTENDS+ animal'
|
|
@@ -292,6 +299,39 @@ Examples:
|
|
|
292
299
|
cave query --db k.db 'service HAS owner: ?who' --resolve
|
|
293
300
|
cave query --db k.db '?x ?verb ?y @production' --json`,
|
|
294
301
|
|
|
302
|
+
search: `cave search — full-text search over claims and comments (spec §13.2)
|
|
303
|
+
|
|
304
|
+
Usage:
|
|
305
|
+
cave search [--db <path>] <terms...> [--raw] [--limit <n>] [--json] [--no-prelude]
|
|
306
|
+
|
|
307
|
+
Options:
|
|
308
|
+
${dbHelp}
|
|
309
|
+
--raw treat the terms as FTS5 MATCH syntax: AND / OR / NOT,
|
|
310
|
+
NEAR(a b), prefix* and column filters such as
|
|
311
|
+
comment:heap or subject:auth
|
|
312
|
+
--limit <n> matches to print (default ${defaultQueryLimit}, maximum ${maxQueryLimit})
|
|
313
|
+
--json emit a versioned cave.search/v1 JSON object whose matches
|
|
314
|
+
are cave.claim/v1 records
|
|
315
|
+
--no-prelude open the store without the standard verb registry
|
|
316
|
+
|
|
317
|
+
Searches the full-text index the store maintains over every claim:
|
|
318
|
+
subject, verb, object, attribute name, value text, comment, and the raw
|
|
319
|
+
line as written (so tags, contexts, and inverse spellings match too).
|
|
320
|
+
Terms are one literal phrase by default — safe for hyphenated names such
|
|
321
|
+
as token-expiry, which raw FTS5 syntax would read as a column filter.
|
|
322
|
+
Tokens split on punctuation, so heap-dump also matches "heap dump" and
|
|
323
|
+
auth/middleware matches auth middleware. Results are newest first, one
|
|
324
|
+
raw line each, comments included; retracted and superseded rows stay
|
|
325
|
+
searchable (spec §9.6), so pair a hit with cave query or cave export
|
|
326
|
+
--current when currency matters.
|
|
327
|
+
|
|
328
|
+
Examples:
|
|
329
|
+
cave search 'heap dump'
|
|
330
|
+
cave search --db k.db token-expiry
|
|
331
|
+
cave search --db k.db --raw 'comment:heap AND subject:auth'
|
|
332
|
+
cave search --db k.db --raw 'jwt OR oauth*' --limit 20
|
|
333
|
+
cave search --db k.db 'rotated weekly' --json`,
|
|
334
|
+
|
|
295
335
|
resolve: `cave resolve — contested facts and their winners (spec §26)
|
|
296
336
|
|
|
297
337
|
Usage:
|
|
@@ -434,7 +474,7 @@ Examples:
|
|
|
434
474
|
cave check --db k.db --stale 30
|
|
435
475
|
cave check --db k.db --json | jq '.violations'`,
|
|
436
476
|
|
|
437
|
-
backup: `cave backup — exact, verified
|
|
477
|
+
backup: `cave backup — exact, verified store snapshot (spec §13.2.2)
|
|
438
478
|
|
|
439
479
|
Usage:
|
|
440
480
|
cave backup [--db <path>] --out <file> [--force]
|
|
@@ -456,7 +496,7 @@ Examples:
|
|
|
456
496
|
cave backup --db k.db --out backups/k.db
|
|
457
497
|
cave backup --verify backups/k.db --sha256 <hex>`,
|
|
458
498
|
|
|
459
|
-
restore: `cave restore — verify and atomically restore an exact
|
|
499
|
+
restore: `cave restore — verify and atomically restore an exact store snapshot (spec §13.2.2)
|
|
460
500
|
|
|
461
501
|
Usage:
|
|
462
502
|
cave restore <snapshot> --db <path> [--force] [--sha256 <hex>]
|
|
@@ -712,7 +752,19 @@ topic → fix over a small in-memory store.`,
|
|
|
712
752
|
version: `cave version — print the cave version
|
|
713
753
|
|
|
714
754
|
Usage:
|
|
715
|
-
cave version
|
|
755
|
+
cave version`,
|
|
756
|
+
|
|
757
|
+
help: `cave help — the overview, or one command's options and examples
|
|
758
|
+
|
|
759
|
+
Usage:
|
|
760
|
+
cave help
|
|
761
|
+
cave help <command>
|
|
762
|
+
|
|
763
|
+
Every command answers --help with the same text as cave help <command>.
|
|
764
|
+
|
|
765
|
+
Examples:
|
|
766
|
+
cave help query
|
|
767
|
+
cave help serve`
|
|
716
768
|
}
|
|
717
769
|
|
|
718
770
|
const readStdin = (): string => {
|
|
@@ -733,6 +785,21 @@ const readStdin = (): string => {
|
|
|
733
785
|
}
|
|
734
786
|
}
|
|
735
787
|
|
|
788
|
+
/**
|
|
789
|
+
* Opens the store `--db` names (spec §13.7): a SQLite file, or a CAVE text
|
|
790
|
+
* file replayed into memory. `read` never creates, migrates, or changes a
|
|
791
|
+
* file — a missing path is an error, not an empty database; `scratch` is a
|
|
792
|
+
* dry run that appends inside a rolled-back transaction, so it may write
|
|
793
|
+
* but creates and migrates nothing; `write` creates a missing SQLite store,
|
|
794
|
+
* migrates an older one, and refuses text files with the materialization
|
|
795
|
+
* hint.
|
|
796
|
+
*/
|
|
797
|
+
const openDb = (values: { db?: string, 'no-prelude'?: boolean }, intent: OpenIntent): Store =>
|
|
798
|
+
openAt(values.db ?? defaultDbPath(), {
|
|
799
|
+
intent,
|
|
800
|
+
...values['no-prelude'] === true ? { registry: Registry.empty } : {}
|
|
801
|
+
})
|
|
802
|
+
|
|
736
803
|
const readInput = (files: readonly string[]): string =>
|
|
737
804
|
files.length === 0 || (files.length === 1 && files[0] === '-') ?
|
|
738
805
|
readStdin() :
|
|
@@ -816,7 +883,7 @@ const ingestCommand = (name: 'add' | 'import') => (argv: readonly string[]): Out
|
|
|
816
883
|
allowPositionals: true
|
|
817
884
|
})
|
|
818
885
|
const input = readInput(positionals)
|
|
819
|
-
const store =
|
|
886
|
+
const store = openDb(values, 'write')
|
|
820
887
|
try {
|
|
821
888
|
const options = {
|
|
822
889
|
strict: values.strict === true,
|
|
@@ -883,7 +950,7 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
883
950
|
return fail('cave query: a pattern is required (spec §12.1)\n')
|
|
884
951
|
}
|
|
885
952
|
const pattern = positionals.join('\n')
|
|
886
|
-
const store =
|
|
953
|
+
const store = openDb(values, 'read')
|
|
887
954
|
try {
|
|
888
955
|
const limit = values.limit === undefined ? defaultQueryLimit : Number(values.limit)
|
|
889
956
|
const result = caveQueryPage(store, pattern, {
|
|
@@ -908,7 +975,14 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
908
975
|
.join(' ')
|
|
909
976
|
// A fully bound pattern has no bindings to print; transitive matches
|
|
910
977
|
// additionally carry no row — confirm with the pattern itself.
|
|
911
|
-
|
|
978
|
+
// Bindings carry the matched claim's comment so the evidence written
|
|
979
|
+
// next to a claim travels with the answer; raw lines already do. A
|
|
980
|
+
// multi-line comment (§6.4) opens above the binding line, as it does
|
|
981
|
+
// above a claim.
|
|
982
|
+
const comment = match.claim?.claim.comment
|
|
983
|
+
const line = bindings !== '' ?
|
|
984
|
+
comment === undefined ? bindings : Token.joinComment(`${bindings} `, comment) :
|
|
985
|
+
match.claim?.claim.raw ?? pattern.split('\n')[0]!
|
|
912
986
|
// An interpolated trajectory shows its value at the anchor
|
|
913
987
|
// (spec §32.4) — bindings already carry it in the value slot.
|
|
914
988
|
return match.at !== undefined && bindings === '' ?
|
|
@@ -923,6 +997,66 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
923
997
|
}
|
|
924
998
|
}
|
|
925
999
|
|
|
1000
|
+
export const searchFormat = 'cave.search' as const
|
|
1001
|
+
export const searchVersion = 1 as const
|
|
1002
|
+
|
|
1003
|
+
/**
|
|
1004
|
+
* `cave search` — the store's FTS5 index (spec §13.2) from the shell:
|
|
1005
|
+
* one literal phrase by default, raw MATCH syntax on request, newest
|
|
1006
|
+
* matches first. Lines print as written, so comments come along.
|
|
1007
|
+
*/
|
|
1008
|
+
export const searchCommand = (argv: readonly string[]): Output => {
|
|
1009
|
+
const { values, positionals } = parseArgs({
|
|
1010
|
+
args: [...argv],
|
|
1011
|
+
options: {
|
|
1012
|
+
db: { type: 'string' },
|
|
1013
|
+
raw: { type: 'boolean' },
|
|
1014
|
+
limit: { type: 'string' },
|
|
1015
|
+
json: { type: 'boolean' },
|
|
1016
|
+
'no-prelude': { type: 'boolean' }
|
|
1017
|
+
},
|
|
1018
|
+
allowPositionals: true
|
|
1019
|
+
})
|
|
1020
|
+
const query = positionals.join(' ').trim()
|
|
1021
|
+
if (query === '') {
|
|
1022
|
+
return fail('cave search: search terms are required\n')
|
|
1023
|
+
}
|
|
1024
|
+
const limit = values.limit === undefined ? defaultQueryLimit : Number(values.limit)
|
|
1025
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > maxQueryLimit) {
|
|
1026
|
+
return fail(`cave search: --limit must be an integer from 1 to ${maxQueryLimit}\n`)
|
|
1027
|
+
}
|
|
1028
|
+
const store = openDb(values, 'read')
|
|
1029
|
+
try {
|
|
1030
|
+
const raw = values.raw === true
|
|
1031
|
+
const found = store.search(query, { raw, limit: limit + 1 })
|
|
1032
|
+
const matches = found.slice(0, limit)
|
|
1033
|
+
const truncated = found.length > limit
|
|
1034
|
+
if (values.json === true) {
|
|
1035
|
+
return ok(`${JSON.stringify({
|
|
1036
|
+
format: searchFormat,
|
|
1037
|
+
version: searchVersion,
|
|
1038
|
+
query,
|
|
1039
|
+
raw,
|
|
1040
|
+
limit,
|
|
1041
|
+
truncated,
|
|
1042
|
+
matches: matches.map(row => store.recordOf(row))
|
|
1043
|
+
}, undefined, 2)}\n`)
|
|
1044
|
+
}
|
|
1045
|
+
if (matches.length === 0) {
|
|
1046
|
+
return ok('no matches\n')
|
|
1047
|
+
}
|
|
1048
|
+
const lines = matches.map(row => row.raw_line)
|
|
1049
|
+
if (truncated) {
|
|
1050
|
+
lines.push(`more matches beyond ${limit}; raise --limit`)
|
|
1051
|
+
}
|
|
1052
|
+
return ok(`${lines.join('\n')}\n`)
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1055
|
+
} finally {
|
|
1056
|
+
store.close()
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
|
|
926
1060
|
/**
|
|
927
1061
|
* `cave resolve` — the human view of contradiction resolution (spec §26.4):
|
|
928
1062
|
* contested facts with their candidates ranked as the policy sees them,
|
|
@@ -941,7 +1075,7 @@ export const resolveCommand = (argv: readonly string[]): Output => {
|
|
|
941
1075
|
allowPositionals: false
|
|
942
1076
|
})
|
|
943
1077
|
const percent = (value: number): string => `${Math.round(value * 1000) / 10}%`
|
|
944
|
-
const store =
|
|
1078
|
+
const store = openDb(values, 'read')
|
|
945
1079
|
try {
|
|
946
1080
|
if (values.policy === true) {
|
|
947
1081
|
const policy = store.resolutionPolicy()
|
|
@@ -1004,7 +1138,12 @@ export const deriveCommand = (argv: readonly string[]): Output => {
|
|
|
1004
1138
|
if (maxPasses !== undefined && (!Number.isInteger(maxPasses) || maxPasses < 1)) {
|
|
1005
1139
|
return fail(`cave derive: --max-passes expects a positive integer, got ${JSON.stringify(values['max-passes'])}\n`)
|
|
1006
1140
|
}
|
|
1007
|
-
|
|
1141
|
+
// The intent follows the branch order below: listing reads, retracting
|
|
1142
|
+
// writes, a dry run appends inside a rolled-back transaction.
|
|
1143
|
+
const store = openDb(values,
|
|
1144
|
+
values.list === true ? 'read' :
|
|
1145
|
+
values.retract !== undefined ? 'write' :
|
|
1146
|
+
values['dry-run'] === true ? 'scratch' : 'write')
|
|
1008
1147
|
try {
|
|
1009
1148
|
if (values.list === true) {
|
|
1010
1149
|
const rules = listRules(store)
|
|
@@ -1158,7 +1297,14 @@ export const actCommand = (argv: readonly string[]): Output => {
|
|
|
1158
1297
|
},
|
|
1159
1298
|
allowPositionals: true
|
|
1160
1299
|
})
|
|
1161
|
-
|
|
1300
|
+
// The intent follows the branch order below: declaring and retracting
|
|
1301
|
+
// write, listing reads, a dry-run execution appends inside a rolled-back
|
|
1302
|
+
// transaction.
|
|
1303
|
+
const store = openDb(values,
|
|
1304
|
+
values.declare === true ? 'write' :
|
|
1305
|
+
values.list === true ? 'read' :
|
|
1306
|
+
values.retract !== undefined ? 'write' :
|
|
1307
|
+
values['dry-run'] === true ? 'scratch' : 'write')
|
|
1162
1308
|
try {
|
|
1163
1309
|
if (values.declare === true) {
|
|
1164
1310
|
const declaration = declareActions(store, readInput(positionals))
|
|
@@ -1279,7 +1425,7 @@ export const checkCommand = (argv: readonly string[]): Output => {
|
|
|
1279
1425
|
if (!Number.isFinite(staleDays) || staleDays < 0) {
|
|
1280
1426
|
return fail(`cave check: --stale expects a non-negative number of days, got ${JSON.stringify(values.stale)}\n`)
|
|
1281
1427
|
}
|
|
1282
|
-
const store =
|
|
1428
|
+
const store = openDb(values, 'read')
|
|
1283
1429
|
try {
|
|
1284
1430
|
const report = caveCheck(store, { staleDays })
|
|
1285
1431
|
const out = values.json === true ?
|
|
@@ -1299,7 +1445,7 @@ const snapshotLine = (
|
|
|
1299
1445
|
`${action} exact backup (${snapshot.rows} row(s), schema v${snapshot.schemaVersion}, ` +
|
|
1300
1446
|
`${snapshot.bytes} bytes, sha256:${snapshot.sha256}) at ${snapshot.path}\n`
|
|
1301
1447
|
|
|
1302
|
-
/** `cave backup` — create or verify an exact
|
|
1448
|
+
/** `cave backup` — create or verify an exact store snapshot (§13.2.2). */
|
|
1303
1449
|
export const backupCommand = (argv: readonly string[]): Output => {
|
|
1304
1450
|
const { values } = parseArgs({
|
|
1305
1451
|
args: [...argv],
|
|
@@ -1328,7 +1474,15 @@ export const backupCommand = (argv: readonly string[]): Output => {
|
|
|
1328
1474
|
if (values.out === undefined) {
|
|
1329
1475
|
return fail('cave backup: --out <file> is required\n')
|
|
1330
1476
|
}
|
|
1331
|
-
const
|
|
1477
|
+
const dbPath = values.db ?? defaultDbPath()
|
|
1478
|
+
// A text store is replayed into memory, so the backup API cannot see that
|
|
1479
|
+
// the destination aliases the source: refuse before the file could be
|
|
1480
|
+
// replaced by a SQLite snapshot of itself (a SQLite source is covered by
|
|
1481
|
+
// the API's own check).
|
|
1482
|
+
if (sameFile(dbPath, values.out)) {
|
|
1483
|
+
return fail(`cave backup: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1484
|
+
}
|
|
1485
|
+
const store = openAt(dbPath, { intent: 'read' })
|
|
1332
1486
|
try {
|
|
1333
1487
|
return ok(snapshotLine('created', backupStore(store, values.out, { force: values.force === true })))
|
|
1334
1488
|
} finally {
|
|
@@ -1411,7 +1565,7 @@ export const suggestAliasCommand = async (
|
|
|
1411
1565
|
if (timeoutSeconds !== undefined && (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)) {
|
|
1412
1566
|
return fail(`cave suggest-alias: --timeout must be a positive number of seconds, got '${values.timeout}'\n`)
|
|
1413
1567
|
}
|
|
1414
|
-
const store =
|
|
1568
|
+
const store = openDb(values, values.write === true ? 'write' : 'read')
|
|
1415
1569
|
try {
|
|
1416
1570
|
let suggestions = suggestAliases(store, { minScore, ...limit === undefined ? {} : { limit } })
|
|
1417
1571
|
if (values.agent !== undefined && suggestions.length > 0) {
|
|
@@ -1468,7 +1622,7 @@ export const syncCommand = (argv: readonly string[]): Output => {
|
|
|
1468
1622
|
return fail('cave sync: exactly one source is required — a CAVE store file, ;@-annotated text, or - for stdin (spec §28.5)\n')
|
|
1469
1623
|
}
|
|
1470
1624
|
const dbPath = values.db ?? defaultDbPath()
|
|
1471
|
-
const store =
|
|
1625
|
+
const store = openDb(values, values['dry-run'] === true ? 'scratch' : 'write')
|
|
1472
1626
|
try {
|
|
1473
1627
|
const options = {
|
|
1474
1628
|
into: values.into === undefined ? labelOf(dbPath) : sanitizeLabel(values.into),
|
|
@@ -1529,7 +1683,10 @@ export const reportCommand = (argv: readonly string[]): Output => {
|
|
|
1529
1683
|
if (maximum === undefined) {
|
|
1530
1684
|
return fail(`cave report: --max-sensitivity expects ${Sensitivity.levels.join(', ')}, got ${JSON.stringify(values['max-sensitivity'])}\n`)
|
|
1531
1685
|
}
|
|
1532
|
-
|
|
1686
|
+
if (values.out !== undefined && sameFile(values.db ?? defaultDbPath(), values.out)) {
|
|
1687
|
+
return fail(`cave report: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1688
|
+
}
|
|
1689
|
+
const store = openDb(values, 'read')
|
|
1533
1690
|
try {
|
|
1534
1691
|
const rendered = caveReport(store, template, {
|
|
1535
1692
|
aliases: values.aliases === true,
|
|
@@ -1597,7 +1754,7 @@ export const exportCommand = (argv: readonly string[]): Output => {
|
|
|
1597
1754
|
if (values.out !== undefined && sameFile(db, values.out)) {
|
|
1598
1755
|
return fail(`cave export: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1599
1756
|
}
|
|
1600
|
-
const store =
|
|
1757
|
+
const store = openDb(values, 'read')
|
|
1601
1758
|
try {
|
|
1602
1759
|
const text = store.exportText({ current: values.current === true, tx: values.tx === true, maxSensitivity: maximum })
|
|
1603
1760
|
if (values.out === undefined) {
|
|
@@ -1636,7 +1793,7 @@ export const generateCommand = (argv: readonly string[]): Output => {
|
|
|
1636
1793
|
if (values.out !== undefined && sameFile(db, values.out)) {
|
|
1637
1794
|
return fail(`cave generate: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1638
1795
|
}
|
|
1639
|
-
const store =
|
|
1796
|
+
const store = openDb(values, 'read')
|
|
1640
1797
|
try {
|
|
1641
1798
|
const generated = generateClient(store, version === undefined ? {} : { version })
|
|
1642
1799
|
if (!generated.ok) {
|
|
@@ -1701,7 +1858,7 @@ export const reconstructCommand = async (
|
|
|
1701
1858
|
if (timeoutSeconds !== undefined && (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)) {
|
|
1702
1859
|
return fail(`cave reconstruct: --timeout must be a positive number of seconds, got '${values.timeout}'\n`)
|
|
1703
1860
|
}
|
|
1704
|
-
const store =
|
|
1861
|
+
const store = openDb(values, 'read')
|
|
1705
1862
|
try {
|
|
1706
1863
|
const graph = sqliteStore(store)
|
|
1707
1864
|
const result = values.agent === undefined ?
|
|
@@ -1717,9 +1874,11 @@ export const reconstructCommand = async (
|
|
|
1717
1874
|
),
|
|
1718
1875
|
positionals
|
|
1719
1876
|
)
|
|
1877
|
+
// The trace is a documentary comment block: the blank line after it
|
|
1878
|
+
// keeps it off the first claim when the output is added back (§6.4).
|
|
1720
1879
|
const lines = [
|
|
1721
|
-
...values.trace === true ?
|
|
1722
|
-
result.trace.map(step => `; ${step.step}. ${step.cue.entity} @ ${step.cue.score.toFixed(2)} +${step.collected} claim(s)`) :
|
|
1880
|
+
...values.trace === true && result.trace.length > 0 ?
|
|
1881
|
+
[...result.trace.map(step => `; ${step.step}. ${step.cue.entity} @ ${step.cue.score.toFixed(2)} +${step.collected} claim(s)`), ''] :
|
|
1723
1882
|
[],
|
|
1724
1883
|
...result.claims.map(claim => emitClaim(claim))
|
|
1725
1884
|
]
|
|
@@ -1732,15 +1891,36 @@ export const reconstructCommand = async (
|
|
|
1732
1891
|
}
|
|
1733
1892
|
}
|
|
1734
1893
|
|
|
1735
|
-
|
|
1736
|
-
|
|
1894
|
+
/**
|
|
1895
|
+
* Positional arguments of a command that declares no options: an unknown
|
|
1896
|
+
* option throws, which the dispatcher reports as an ordinary problem
|
|
1897
|
+
* (status 1).
|
|
1898
|
+
*/
|
|
1899
|
+
const positionalsOf = (argv: readonly string[]): readonly string[] =>
|
|
1900
|
+
parseArgs({ args: [...argv], options: {}, allowPositionals: true }).positionals
|
|
1901
|
+
|
|
1902
|
+
/** The usage error (status 2) for more positionals than a command takes. */
|
|
1903
|
+
const surplusPositionals = (command: string, positionals: readonly string[], max: number): Output | undefined =>
|
|
1904
|
+
positionals.length > max ? fail(`cave ${command}: unexpected positional arguments\n`, 2) : undefined
|
|
1737
1905
|
|
|
1738
|
-
export const
|
|
1739
|
-
|
|
1906
|
+
export const demoCommand = (argv: readonly string[] = []): Output => {
|
|
1907
|
+
const problem = surplusPositionals('demo', positionalsOf(argv), 0)
|
|
1908
|
+
if (problem !== undefined) return problem
|
|
1909
|
+
return ok(`${Demo.run().lines.join('\n')}\n`)
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
export const versionCommand = (argv: readonly string[] = []): Output => {
|
|
1913
|
+
const problem = surplusPositionals('version', positionalsOf(argv), 0)
|
|
1914
|
+
if (problem !== undefined) return problem
|
|
1915
|
+
return ok(`${Version.current()}\n`)
|
|
1916
|
+
}
|
|
1740
1917
|
|
|
1741
1918
|
/** `cave help [command]` — the overview, or one command's help text. */
|
|
1742
1919
|
export const helpCommand = (argv: readonly string[]): Output => {
|
|
1743
|
-
const
|
|
1920
|
+
const positionals = positionalsOf(argv)
|
|
1921
|
+
const problem = surplusPositionals('help', positionals, 1)
|
|
1922
|
+
if (problem !== undefined) return problem
|
|
1923
|
+
const [topic] = positionals
|
|
1744
1924
|
if (topic === undefined) {
|
|
1745
1925
|
return ok(`${usage}\n`)
|
|
1746
1926
|
}
|
|
@@ -1763,6 +1943,19 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
1763
1943
|
(rest.includes('--help') || rest.includes('-h'))) {
|
|
1764
1944
|
return ok(`${commandHelp[canonical]}\n`)
|
|
1765
1945
|
}
|
|
1946
|
+
try {
|
|
1947
|
+
return run(canonical, rest)
|
|
1948
|
+
} catch (error) {
|
|
1949
|
+
// A --db path that is not what the command needs (spec §13.7) is a
|
|
1950
|
+
// usage failure, reported the way dispatch reports thrown errors.
|
|
1951
|
+
if (error instanceof LocateError) {
|
|
1952
|
+
return fail(`cave ${canonical}: ${error.message}\n`)
|
|
1953
|
+
}
|
|
1954
|
+
throw error
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
|
|
1958
|
+
const run = (canonical: undefined | string, rest: readonly string[]): Output => {
|
|
1766
1959
|
switch (canonical) {
|
|
1767
1960
|
case 'parse':
|
|
1768
1961
|
return parseCommand(rest)
|
|
@@ -1772,6 +1965,8 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
1772
1965
|
return importCommand(rest)
|
|
1773
1966
|
case 'query':
|
|
1774
1967
|
return queryCommand(rest)
|
|
1968
|
+
case 'search':
|
|
1969
|
+
return searchCommand(rest)
|
|
1775
1970
|
case 'resolve':
|
|
1776
1971
|
return resolveCommand(rest)
|
|
1777
1972
|
case 'derive':
|
|
@@ -1795,11 +1990,11 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
1795
1990
|
case 'doctor':
|
|
1796
1991
|
return doctorCommand(rest)
|
|
1797
1992
|
case 'demo':
|
|
1798
|
-
return demoCommand()
|
|
1993
|
+
return demoCommand(rest)
|
|
1799
1994
|
case 'version':
|
|
1800
1995
|
case '--version':
|
|
1801
1996
|
case '-v':
|
|
1802
|
-
return versionCommand()
|
|
1997
|
+
return versionCommand(rest)
|
|
1803
1998
|
case 'help':
|
|
1804
1999
|
return helpCommand(rest)
|
|
1805
2000
|
case undefined:
|
|
@@ -1807,6 +2002,6 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
1807
2002
|
case '-h':
|
|
1808
2003
|
return ok(`${usage}\n`)
|
|
1809
2004
|
default:
|
|
1810
|
-
return fail(`cave: unknown command ${JSON.stringify(
|
|
2005
|
+
return fail(`cave: unknown command ${JSON.stringify(canonical)}\n\n${usage}\n`, 2)
|
|
1811
2006
|
}
|
|
1812
2007
|
}
|
package/src/commands.ts
CHANGED
|
@@ -19,6 +19,7 @@ export const commandRegistry = [
|
|
|
19
19
|
{ name: 'add', importantOptions: ['--strict', '--check', '--no-prelude', '--no-src'] },
|
|
20
20
|
{ name: 'import', importantOptions: ['--strict', '--no-prelude'] },
|
|
21
21
|
{ name: 'query', importantOptions: ['--json', '--limit', '--cursor', '--all', '--aliases', '--as-of', '--at', '--resolve', '--no-prelude'] },
|
|
22
|
+
{ name: 'search', importantOptions: ['--raw', '--limit', '--json', '--no-prelude'] },
|
|
22
23
|
{ name: 'resolve', importantOptions: ['--aliases', '--policy', '--json', '--no-prelude'] },
|
|
23
24
|
{ name: 'derive', importantOptions: ['--dry-run', '--full', '--aliases', '--min-conf', '--max-passes', '--list', '--retract', '--json', '--no-prelude'] },
|
|
24
25
|
{ name: 'act', importantOptions: ['--declare', '--list', '--retract', '--dry-run', '--no-check', '--aliases', '--hooks', '--json', '--no-prelude'] },
|
package/src/dispatch.ts
CHANGED
|
@@ -22,8 +22,10 @@ const processRuntime = (): CommandRuntime => ({
|
|
|
22
22
|
|
|
23
23
|
const delegatedHelp = new Set<string>(delegatedCommandNames)
|
|
24
24
|
|
|
25
|
+
// Exactly `help <delegated command>` becomes `<command> --help`; anything
|
|
26
|
+
// longer stays with `cave help`, which rejects the surplus as a usage error.
|
|
25
27
|
const normalizedArgs = (raw: readonly string[]): readonly string[] =>
|
|
26
|
-
raw
|
|
28
|
+
raw.length === 2 && raw[0] === 'help' && delegatedHelp.has(raw[1]!) ? [raw[1]!, '--help'] : raw
|
|
27
29
|
|
|
28
30
|
const write = (runtime: CommandRuntime, output: Output): number => {
|
|
29
31
|
if (output.out !== '') runtime.stdout.write(output.out)
|
package/src/doctor.ts
CHANGED
|
@@ -249,7 +249,7 @@ const inspectDatabase = (path: string, source: DatabaseConfiguration['source']):
|
|
|
249
249
|
configuration,
|
|
250
250
|
checks: [check('store.database', 'warn',
|
|
251
251
|
`Database schema ${version} needs migration to schema ${Schema.currentVersion}`,
|
|
252
|
-
'
|
|
252
|
+
'Close every user and copy the database file as a rollback point, then open it with a writing command such as cave add to migrate it; reads never migrate.')]
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
255
|
|
package/src/index.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@cavelang/cli` — the `cave` command (parse / add / query / resolve / derive / act / check / suggest-alias / sync / export / report / reconstruct / demo).
|
|
2
|
+
* `@cavelang/cli` — the `cave` command (parse / add / query / search / resolve / derive / act / check / suggest-alias / sync / export / report / reconstruct / demo).
|
|
3
3
|
* Programmatic access to the same implementations lives here; `dispatch`
|
|
4
4
|
* and `runCli` expose the shared awaited command lifecycle.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
export { actCommand, addCommand, backupCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, generateCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, reconstructCommand, reportCommand, resolveCommand, restoreCommand, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
|
|
7
|
+
export { actCommand, addCommand, backupCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, generateCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, reconstructCommand, reportCommand, resolveCommand, restoreCommand, searchCommand, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
|
|
8
8
|
export { dispatch, runCli } from './dispatch.ts'
|
|
9
9
|
export type { CommandRuntime } from './dispatch.ts'
|
|
10
10
|
export { commandRegistry } from './commands.ts'
|