@cavelang/cli 0.33.0 → 0.35.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 +15 -3
- package/dist/internal/automate/main.d.ts.map +1 -1
- package/dist/internal/automate/main.js +9 -2
- package/dist/internal/automate/main.js.map +1 -1
- package/dist/internal/connect/declared.d.ts +225 -0
- package/dist/internal/connect/declared.d.ts.map +1 -0
- package/dist/internal/connect/declared.js +521 -0
- package/dist/internal/connect/declared.js.map +1 -0
- package/dist/internal/connect/index.d.ts +4 -2
- package/dist/internal/connect/index.d.ts.map +1 -1
- package/dist/internal/connect/index.js +3 -1
- package/dist/internal/connect/index.js.map +1 -1
- package/dist/internal/connect/main.d.ts.map +1 -1
- package/dist/internal/connect/main.js +356 -10
- package/dist/internal/connect/main.js.map +1 -1
- package/dist/internal/connect/run.d.ts +47 -1
- package/dist/internal/connect/run.d.ts.map +1 -1
- package/dist/internal/connect/run.js +81 -33
- package/dist/internal/connect/run.js.map +1 -1
- package/dist/internal/connect/source.d.ts +33 -2
- package/dist/internal/connect/source.d.ts.map +1 -1
- package/dist/internal/connect/source.js +168 -8
- package/dist/internal/connect/source.js.map +1 -1
- package/dist/internal/connect/template.d.ts +18 -0
- package/dist/internal/connect/template.d.ts.map +1 -1
- package/dist/internal/connect/template.js +28 -0
- 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 +19 -2
- package/dist/internal/ingest/main.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 +15 -3
- package/dist/internal/mcp/main.js.map +1 -1
- package/dist/internal/rules/rule.d.ts.map +1 -1
- package/dist/internal/rules/rule.js +1 -22
- package/dist/internal/rules/rule.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.d.ts.map +1 -1
- package/dist/internal/view/main.js +4 -3
- package/dist/internal/view/main.js.map +1 -1
- package/dist/src/cli.d.ts +15 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +227 -54
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands.d.ts +2 -2
- package/dist/src/commands.js +2 -2
- package/dist/src/commands.js.map +1 -1
- package/dist/src/dispatch.d.ts.map +1 -1
- package/dist/src/dispatch.js +7 -1
- package/dist/src/dispatch.js.map +1 -1
- package/dist/src/doctor.d.ts +1 -1
- package/dist/src/doctor.d.ts.map +1 -1
- package/dist/src/doctor.js +27 -2
- package/dist/src/doctor.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/package.json +19 -19
- package/src/cli.ts +258 -53
- package/src/commands.ts +2 -2
- package/src/dispatch.ts +7 -1
- package/src/doctor.ts +29 -3
- package/src/index.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cavelang/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "cave — CLI for parsing, storing, querying and exporting CAVE knowledge.",
|
|
6
6
|
"keywords": [
|
|
@@ -97,26 +97,26 @@
|
|
|
97
97
|
"@modelcontextprotocol/server": "2.0.0",
|
|
98
98
|
"@mozilla/readability": "^0.6.0",
|
|
99
99
|
"linkedom": "^0.18.13",
|
|
100
|
-
"@cavelang/
|
|
101
|
-
"@cavelang/
|
|
102
|
-
"@cavelang/core": "0.
|
|
103
|
-
"@cavelang/
|
|
104
|
-
"@cavelang/
|
|
105
|
-
"@cavelang/query": "0.
|
|
106
|
-
"@cavelang/
|
|
100
|
+
"@cavelang/canonical": "0.35.0",
|
|
101
|
+
"@cavelang/fusion": "0.35.0",
|
|
102
|
+
"@cavelang/core": "0.35.0",
|
|
103
|
+
"@cavelang/highlight": "0.35.0",
|
|
104
|
+
"@cavelang/parser": "0.35.0",
|
|
105
|
+
"@cavelang/query": "0.35.0",
|
|
106
|
+
"@cavelang/store": "0.35.0"
|
|
107
107
|
},
|
|
108
108
|
"devDependencies": {
|
|
109
|
-
"@cavelang/act": "0.
|
|
110
|
-
"@cavelang/automate": "0.
|
|
111
|
-
"@cavelang/
|
|
112
|
-
"@cavelang/
|
|
113
|
-
"@cavelang/ingest": "0.
|
|
114
|
-
"@cavelang/loop": "0.
|
|
115
|
-
"@cavelang/
|
|
116
|
-
"@cavelang/
|
|
117
|
-
"@cavelang/
|
|
118
|
-
"@cavelang/
|
|
119
|
-
"@cavelang/
|
|
109
|
+
"@cavelang/act": "0.35.0",
|
|
110
|
+
"@cavelang/automate": "0.35.0",
|
|
111
|
+
"@cavelang/eval": "0.35.0",
|
|
112
|
+
"@cavelang/connect": "0.35.0",
|
|
113
|
+
"@cavelang/ingest": "0.35.0",
|
|
114
|
+
"@cavelang/loop": "0.35.0",
|
|
115
|
+
"@cavelang/rules": "0.35.0",
|
|
116
|
+
"@cavelang/shape": "0.35.0",
|
|
117
|
+
"@cavelang/mcp": "0.35.0",
|
|
118
|
+
"@cavelang/sync": "0.35.0",
|
|
119
|
+
"@cavelang/view": "0.35.0"
|
|
120
120
|
},
|
|
121
121
|
"scripts": {
|
|
122
122
|
"build": "tsc -b && node scripts/consolidate.mjs",
|
package/src/cli.ts
CHANGED
|
@@ -41,8 +41,9 @@ import { parseArgs } from 'node:util'
|
|
|
41
41
|
import { Version } from '@cavelang/core'
|
|
42
42
|
import { parseDocument, Token } from '@cavelang/parser'
|
|
43
43
|
import { Registry, standardRegistry } from '@cavelang/canonical'
|
|
44
|
-
import {
|
|
45
|
-
import type { Store } from '@cavelang/store'
|
|
44
|
+
import { backup as backupStore, defaultDbPath, kindOf, LocateError, openAt, restoreBackup, Sensitivity, verifyBackup } from '@cavelang/store'
|
|
45
|
+
import type { OpenIntent, Store } from '@cavelang/store'
|
|
46
|
+
import { Declared, assemble as assembleSources } from '@cavelang/connect'
|
|
46
47
|
import { defaultLimit as defaultQueryLimit, maxLimit as maxQueryLimit, page as caveQueryPage } from '@cavelang/query'
|
|
47
48
|
import {
|
|
48
49
|
check as caveCheck, defaultMinScore, defaultStaleDays, gatedIngest, judgePrompt, parseJudgeReply,
|
|
@@ -142,10 +143,12 @@ Usage:
|
|
|
142
143
|
cave help [command] this text, or one command's options and examples
|
|
143
144
|
|
|
144
145
|
Every command answers --help. --db defaults to $CAVE_DB, or cave.db in
|
|
145
|
-
the current directory
|
|
146
|
+
the current directory, and names a SQLite store or a CAVE text file: text
|
|
147
|
+
replays into an in-memory store for commands that only read (writes refuse
|
|
148
|
+
it), and a read never creates a missing database. The spec lives in the .claude/skills/ directory at
|
|
146
149
|
the repository root (section index in README.md).`
|
|
147
150
|
|
|
148
|
-
const dbHelp = `--db <path>
|
|
151
|
+
const dbHelp = `--db <path> store: a SQLite file, or CAVE text read into memory (default: $CAVE_DB, or cave.db)`
|
|
149
152
|
|
|
150
153
|
/** Per-command help, printed for \`cave <command> --help\` and \`cave help <command>\`. */
|
|
151
154
|
export const commandHelp: Record<string, string> = {
|
|
@@ -250,7 +253,7 @@ Examples:
|
|
|
250
253
|
query: `cave query — run a CAVE-Q pattern against a store
|
|
251
254
|
|
|
252
255
|
Usage:
|
|
253
|
-
cave query [--db <path>] <pattern> [WHERE <filter>] [--limit <n>] [--cursor <token>] [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--no-prelude]
|
|
256
|
+
cave query [--db <path>] <pattern> [WHERE <filter>] [--limit <n>] [--cursor <token>] [--json] [--all] [--aliases] [--as-of <t>] [--at <t>] [--resolve] [--sources] [--no-prelude]
|
|
254
257
|
|
|
255
258
|
Options:
|
|
256
259
|
${dbHelp}
|
|
@@ -276,6 +279,12 @@ Options:
|
|
|
276
279
|
collapse to the row the resolution policy picks
|
|
277
280
|
(precedence class, reliability-weighted confidence, tx);
|
|
278
281
|
incompatible with --all
|
|
282
|
+
--sources overlay the store's declared sources (spec §23.4) inside a
|
|
283
|
+
rolled-back transaction — federation-lite: external data is
|
|
284
|
+
consulted at query time, nothing persists. The answer is
|
|
285
|
+
whole (no --cursor, --limit is not a page size); a text
|
|
286
|
+
store followed its local sources on open, so only URL
|
|
287
|
+
sources are loaded here
|
|
279
288
|
--no-prelude open the store without the standard verb registry
|
|
280
289
|
|
|
281
290
|
Patterns are claim triples with ?variables and optional metadata filters
|
|
@@ -286,6 +295,7 @@ with the answer; fully bound patterns print the raw line, comment included.
|
|
|
286
295
|
|
|
287
296
|
Examples:
|
|
288
297
|
cave query '?x USES jwt'
|
|
298
|
+
cave query --db notes.cave '?x USES jwt'
|
|
289
299
|
cave query --db k.db '?x HAS bug: ?bug #security'
|
|
290
300
|
cave query --db k.db '?cause CAUSE app/crash' 'WHERE conf >= 0.5'
|
|
291
301
|
cave query --db k.db 'terrier EXTENDS+ animal'
|
|
@@ -294,6 +304,7 @@ Examples:
|
|
|
294
304
|
cave query --db k.db 'revenue IS' --at 2026-07
|
|
295
305
|
cave query --db k.db 'alice WORKS-AT ?org' --at 2021
|
|
296
306
|
cave query --db k.db 'service HAS owner: ?who' --resolve
|
|
307
|
+
cave query --db k.db '?who WORKS-AT acme' --sources
|
|
297
308
|
cave query --db k.db '?x ?verb ?y @production' --json`,
|
|
298
309
|
|
|
299
310
|
search: `cave search — full-text search over claims and comments (spec §13.2)
|
|
@@ -782,6 +793,57 @@ const readStdin = (): string => {
|
|
|
782
793
|
}
|
|
783
794
|
}
|
|
784
795
|
|
|
796
|
+
/**
|
|
797
|
+
* Opens the store `--db` names (spec §13.7): a SQLite file, or a CAVE text
|
|
798
|
+
* file replayed into memory. `read` never creates, migrates, or changes a
|
|
799
|
+
* file — a missing path is an error, not an empty database; `scratch` is a
|
|
800
|
+
* dry run that appends inside a rolled-back transaction, so it may write
|
|
801
|
+
* but creates and migrates nothing; `write` creates a missing SQLite store,
|
|
802
|
+
* migrates an older one, and refuses text files with the materialization
|
|
803
|
+
* hint.
|
|
804
|
+
*/
|
|
805
|
+
const openDb = (values: { db?: string, 'no-prelude'?: boolean }, intent: OpenIntent): Store =>
|
|
806
|
+
openAt(values.db ?? defaultDbPath(), {
|
|
807
|
+
intent,
|
|
808
|
+
// A text store follows its declared sources on open (spec §23.4).
|
|
809
|
+
assemble: assembleSources,
|
|
810
|
+
...values['no-prelude'] === true ? { registry: Registry.empty } : {}
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
const sourcesRollback = Symbol('cave query --sources rollback')
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* Runs `body` with the store's declared sources overlaid (spec §23.4): the
|
|
817
|
+
* sources, loaded beforehand (URLs included), append inside a transaction
|
|
818
|
+
* that always rolls back, so external data is consulted at query time and
|
|
819
|
+
* nothing persists, digests included.
|
|
820
|
+
*/
|
|
821
|
+
const withSources = <T>(store: Store, discovered: Declared.Discovery, body: () => T): undefined | { value: T } => {
|
|
822
|
+
let result: undefined | { value: T }
|
|
823
|
+
try {
|
|
824
|
+
store.transaction(() => {
|
|
825
|
+
// Discovered on a snapshot: if a writer changed the declarations
|
|
826
|
+
// since, the sequence is stale and the caller rediscovers.
|
|
827
|
+
if (!Declared.sameDeclarations(discovered.baseline, Declared.declarationState(store))) {
|
|
828
|
+
throw sourcesRollback
|
|
829
|
+
}
|
|
830
|
+
for (const source of discovered.sequence) {
|
|
831
|
+
const report = Declared.run(store, source, { force: true })
|
|
832
|
+
if (report.failures.length > 0) {
|
|
833
|
+
throw new LocateError(`source/${source.declared.name}: ${report.failures.map(failure => `${failure.record}: ${failure.problems.join('; ')}`).join('; ')}`)
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
result = { value: body() }
|
|
837
|
+
throw sourcesRollback
|
|
838
|
+
})
|
|
839
|
+
} catch (error) {
|
|
840
|
+
if (error !== sourcesRollback) {
|
|
841
|
+
throw error
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
return result
|
|
845
|
+
}
|
|
846
|
+
|
|
785
847
|
const readInput = (files: readonly string[]): string =>
|
|
786
848
|
files.length === 0 || (files.length === 1 && files[0] === '-') ?
|
|
787
849
|
readStdin() :
|
|
@@ -865,7 +927,7 @@ const ingestCommand = (name: 'add' | 'import') => (argv: readonly string[]): Out
|
|
|
865
927
|
allowPositionals: true
|
|
866
928
|
})
|
|
867
929
|
const input = readInput(positionals)
|
|
868
|
-
const store =
|
|
930
|
+
const store = openDb(values, 'write')
|
|
869
931
|
try {
|
|
870
932
|
const options = {
|
|
871
933
|
strict: values.strict === true,
|
|
@@ -911,39 +973,150 @@ export const addCommand = ingestCommand('add')
|
|
|
911
973
|
*/
|
|
912
974
|
export const importCommand = ingestCommand('import')
|
|
913
975
|
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
976
|
+
const queryOptions = {
|
|
977
|
+
db: { type: 'string' },
|
|
978
|
+
json: { type: 'boolean' },
|
|
979
|
+
all: { type: 'boolean' },
|
|
980
|
+
aliases: { type: 'boolean' },
|
|
981
|
+
'as-of': { type: 'string' },
|
|
982
|
+
at: { type: 'string' },
|
|
983
|
+
resolve: { type: 'boolean' },
|
|
984
|
+
sources: { type: 'boolean' },
|
|
985
|
+
limit: { type: 'string' },
|
|
986
|
+
cursor: { type: 'string' },
|
|
987
|
+
'no-prelude': { type: 'boolean' }
|
|
988
|
+
} as const
|
|
989
|
+
|
|
990
|
+
type QueryValues = {
|
|
991
|
+
db?: string
|
|
992
|
+
json?: boolean
|
|
993
|
+
all?: boolean
|
|
994
|
+
aliases?: boolean
|
|
995
|
+
'as-of'?: string
|
|
996
|
+
at?: string
|
|
997
|
+
resolve?: boolean
|
|
998
|
+
sources?: boolean
|
|
999
|
+
limit?: string
|
|
1000
|
+
cursor?: string
|
|
1001
|
+
'no-prelude'?: boolean
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
const parseQuery = (argv: readonly string[]): { values: QueryValues, pattern: string } | { failure: Output } => {
|
|
1005
|
+
const { values, positionals } = parseArgs({ args: [...argv], options: queryOptions, allowPositionals: true })
|
|
931
1006
|
if (positionals.length === 0) {
|
|
932
|
-
return fail('cave query: a pattern is required (spec §12.1)\n')
|
|
1007
|
+
return { failure: fail('cave query: a pattern is required (spec §12.1)\n') }
|
|
1008
|
+
}
|
|
1009
|
+
if (values.sources === true && values.cursor !== undefined) {
|
|
1010
|
+
return { failure: fail('cave query: --cursor does not apply to --sources — the overlay exists for one invocation and is answered whole\n') }
|
|
933
1011
|
}
|
|
934
|
-
|
|
935
|
-
|
|
1012
|
+
return { values, pattern: positionals.join('\n') }
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* `cave query --sources` — the store with its declared sources overlaid
|
|
1017
|
+
* (spec §23.4). Asynchronous because the sources load first, URLs
|
|
1018
|
+
* included, before the overlay appends inside a transaction that rolls
|
|
1019
|
+
* back; the dispatcher routes `--sources` here. A text store followed its
|
|
1020
|
+
* local sources on open, so only what assembly could not follow — URL
|
|
1021
|
+
* sources and what they declare — is loaded. The overlay exists for this
|
|
1022
|
+
* invocation alone, so the answer is whole: pages are followed inside the
|
|
1023
|
+
* transaction and no cursor is handed out.
|
|
1024
|
+
*/
|
|
1025
|
+
export const querySourcesCommand = async (
|
|
1026
|
+
argv: readonly string[],
|
|
1027
|
+
runtime: { readonly fetchImpl?: Declared.DiscoverOptions['fetchImpl'] } = {}
|
|
1028
|
+
): Promise<Output> => {
|
|
1029
|
+
const parsed = parseQuery(argv)
|
|
1030
|
+
if ('failure' in parsed) {
|
|
1031
|
+
return parsed.failure
|
|
1032
|
+
}
|
|
1033
|
+
const { values, pattern } = parsed
|
|
1034
|
+
if (values.sources !== true) {
|
|
1035
|
+
return queryCommand(argv)
|
|
1036
|
+
}
|
|
1037
|
+
const root = values.db ?? defaultDbPath()
|
|
1038
|
+
// The overlay appends inside a transaction it rolls back: writable, but
|
|
1039
|
+
// never creating or migrating (spec §13.7).
|
|
1040
|
+
const store = openDb(values, 'scratch')
|
|
936
1041
|
try {
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1042
|
+
for (let attempt = 0; attempt < Declared.overlayAttempts; attempt += 1) {
|
|
1043
|
+
const discovered = await Declared.discovery(store, root, {
|
|
1044
|
+
// The overlay applies every source it loads (force), so discovery
|
|
1045
|
+
// simulates that; a text store skips what assembly already followed.
|
|
1046
|
+
force: true,
|
|
1047
|
+
...kindOf(root) === 'text' ? { skipFollowed: true } : {},
|
|
1048
|
+
...runtime.fetchImpl === undefined ? {} : { fetchImpl: runtime.fetchImpl }
|
|
1049
|
+
})
|
|
1050
|
+
const page = withSources(store, discovered, () => wholeQuery(store, values, pattern))
|
|
1051
|
+
if (page !== undefined) {
|
|
1052
|
+
return renderQueryPage(store, values, pattern, () => page.value)
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
throw Declared.staleOverlay()
|
|
1056
|
+
} catch (error) {
|
|
1057
|
+
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1058
|
+
} finally {
|
|
1059
|
+
store.close()
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
export const queryCommand = (argv: readonly string[]): Output => {
|
|
1064
|
+
const parsed = parseQuery(argv)
|
|
1065
|
+
if ('failure' in parsed) {
|
|
1066
|
+
return parsed.failure
|
|
1067
|
+
}
|
|
1068
|
+
const { values, pattern } = parsed
|
|
1069
|
+
if (values.sources === true) {
|
|
1070
|
+
// Loading sources cannot be synchronous: the in-process entry for the
|
|
1071
|
+
// overlay is `querySourcesCommand`, which the dispatcher uses.
|
|
1072
|
+
return fail('cave query: --sources loads the declared sources first and runs asynchronously — use querySourcesCommand\n')
|
|
1073
|
+
}
|
|
1074
|
+
const store = openDb(values, 'read')
|
|
1075
|
+
try {
|
|
1076
|
+
return renderQueryPage(store, values, pattern, () => queryPage(store, values, pattern))
|
|
1077
|
+
} catch (error) {
|
|
1078
|
+
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
1079
|
+
} finally {
|
|
1080
|
+
store.close()
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
/** Every match of the overlay: the pages are followed here, inside the transaction that holds it. */
|
|
1085
|
+
const wholeQuery = (store: Store, values: QueryValues, pattern: string): ReturnType<typeof caveQueryPage> => {
|
|
1086
|
+
const matches: ReturnType<typeof caveQueryPage>['matches'][number][] = []
|
|
1087
|
+
let first: undefined | ReturnType<typeof caveQueryPage>
|
|
1088
|
+
let cursor: undefined | string
|
|
1089
|
+
for (;;) {
|
|
1090
|
+
const page = queryPage(store, { ...values, limit: '1000', ...cursor === undefined ? {} : { cursor } }, pattern)
|
|
1091
|
+
first ??= page
|
|
1092
|
+
matches.push(...page.matches)
|
|
1093
|
+
if (page.next === undefined) {
|
|
1094
|
+
const { next: _next, ...rest } = first
|
|
1095
|
+
return { ...rest, matches }
|
|
1096
|
+
}
|
|
1097
|
+
cursor = page.next
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
const queryPage = (store: Store, values: QueryValues, pattern: string): ReturnType<typeof caveQueryPage> =>
|
|
1102
|
+
caveQueryPage(store, pattern, {
|
|
1103
|
+
all: values.all === true,
|
|
1104
|
+
aliases: values.aliases === true,
|
|
1105
|
+
resolve: values.resolve === true,
|
|
1106
|
+
limit: values.limit === undefined ? defaultQueryLimit : Number(values.limit),
|
|
1107
|
+
...values.cursor === undefined ? {} : { cursor: values.cursor },
|
|
1108
|
+
...values['as-of'] === undefined ? {} : { asOf: values['as-of'] },
|
|
1109
|
+
...values.at === undefined ? {} : { at: values.at }
|
|
1110
|
+
})
|
|
1111
|
+
|
|
1112
|
+
const renderQueryPage = (
|
|
1113
|
+
store: Store,
|
|
1114
|
+
values: QueryValues,
|
|
1115
|
+
pattern: string,
|
|
1116
|
+
page: () => ReturnType<typeof caveQueryPage>
|
|
1117
|
+
): Output => {
|
|
1118
|
+
{
|
|
1119
|
+
const result = page()
|
|
947
1120
|
if (values.json === true) {
|
|
948
1121
|
return ok(`${JSON.stringify(result, undefined, 2)}\n`)
|
|
949
1122
|
}
|
|
@@ -972,10 +1145,6 @@ export const queryCommand = (argv: readonly string[]): Output => {
|
|
|
972
1145
|
line
|
|
973
1146
|
})
|
|
974
1147
|
return ok(`${lines.join('\n')}\n${result.next === undefined ? '' : `next: ${result.next}\n`}`)
|
|
975
|
-
} catch (error) {
|
|
976
|
-
return fail(`${error instanceof Error ? error.message : String(error)}\n`)
|
|
977
|
-
} finally {
|
|
978
|
-
store.close()
|
|
979
1148
|
}
|
|
980
1149
|
}
|
|
981
1150
|
|
|
@@ -1007,7 +1176,7 @@ export const searchCommand = (argv: readonly string[]): Output => {
|
|
|
1007
1176
|
if (!Number.isInteger(limit) || limit < 1 || limit > maxQueryLimit) {
|
|
1008
1177
|
return fail(`cave search: --limit must be an integer from 1 to ${maxQueryLimit}\n`)
|
|
1009
1178
|
}
|
|
1010
|
-
const store =
|
|
1179
|
+
const store = openDb(values, 'read')
|
|
1011
1180
|
try {
|
|
1012
1181
|
const raw = values.raw === true
|
|
1013
1182
|
const found = store.search(query, { raw, limit: limit + 1 })
|
|
@@ -1057,7 +1226,7 @@ export const resolveCommand = (argv: readonly string[]): Output => {
|
|
|
1057
1226
|
allowPositionals: false
|
|
1058
1227
|
})
|
|
1059
1228
|
const percent = (value: number): string => `${Math.round(value * 1000) / 10}%`
|
|
1060
|
-
const store =
|
|
1229
|
+
const store = openDb(values, 'read')
|
|
1061
1230
|
try {
|
|
1062
1231
|
if (values.policy === true) {
|
|
1063
1232
|
const policy = store.resolutionPolicy()
|
|
@@ -1120,7 +1289,12 @@ export const deriveCommand = (argv: readonly string[]): Output => {
|
|
|
1120
1289
|
if (maxPasses !== undefined && (!Number.isInteger(maxPasses) || maxPasses < 1)) {
|
|
1121
1290
|
return fail(`cave derive: --max-passes expects a positive integer, got ${JSON.stringify(values['max-passes'])}\n`)
|
|
1122
1291
|
}
|
|
1123
|
-
|
|
1292
|
+
// The intent follows the branch order below: listing reads, retracting
|
|
1293
|
+
// writes, a dry run appends inside a rolled-back transaction.
|
|
1294
|
+
const store = openDb(values,
|
|
1295
|
+
values.list === true ? 'read' :
|
|
1296
|
+
values.retract !== undefined ? 'write' :
|
|
1297
|
+
values['dry-run'] === true ? 'scratch' : 'write')
|
|
1124
1298
|
try {
|
|
1125
1299
|
if (values.list === true) {
|
|
1126
1300
|
const rules = listRules(store)
|
|
@@ -1274,7 +1448,14 @@ export const actCommand = (argv: readonly string[]): Output => {
|
|
|
1274
1448
|
},
|
|
1275
1449
|
allowPositionals: true
|
|
1276
1450
|
})
|
|
1277
|
-
|
|
1451
|
+
// The intent follows the branch order below: declaring and retracting
|
|
1452
|
+
// write, listing reads, a dry-run execution appends inside a rolled-back
|
|
1453
|
+
// transaction.
|
|
1454
|
+
const store = openDb(values,
|
|
1455
|
+
values.declare === true ? 'write' :
|
|
1456
|
+
values.list === true ? 'read' :
|
|
1457
|
+
values.retract !== undefined ? 'write' :
|
|
1458
|
+
values['dry-run'] === true ? 'scratch' : 'write')
|
|
1278
1459
|
try {
|
|
1279
1460
|
if (values.declare === true) {
|
|
1280
1461
|
const declaration = declareActions(store, readInput(positionals))
|
|
@@ -1395,7 +1576,7 @@ export const checkCommand = (argv: readonly string[]): Output => {
|
|
|
1395
1576
|
if (!Number.isFinite(staleDays) || staleDays < 0) {
|
|
1396
1577
|
return fail(`cave check: --stale expects a non-negative number of days, got ${JSON.stringify(values.stale)}\n`)
|
|
1397
1578
|
}
|
|
1398
|
-
const store =
|
|
1579
|
+
const store = openDb(values, 'read')
|
|
1399
1580
|
try {
|
|
1400
1581
|
const report = caveCheck(store, { staleDays })
|
|
1401
1582
|
const out = values.json === true ?
|
|
@@ -1444,7 +1625,15 @@ export const backupCommand = (argv: readonly string[]): Output => {
|
|
|
1444
1625
|
if (values.out === undefined) {
|
|
1445
1626
|
return fail('cave backup: --out <file> is required\n')
|
|
1446
1627
|
}
|
|
1447
|
-
const
|
|
1628
|
+
const dbPath = values.db ?? defaultDbPath()
|
|
1629
|
+
// A text store is replayed into memory, so the backup API cannot see that
|
|
1630
|
+
// the destination aliases the source: refuse before the file could be
|
|
1631
|
+
// replaced by a SQLite snapshot of itself (a SQLite source is covered by
|
|
1632
|
+
// the API's own check).
|
|
1633
|
+
if (sameFile(dbPath, values.out)) {
|
|
1634
|
+
return fail(`cave backup: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1635
|
+
}
|
|
1636
|
+
const store = openAt(dbPath, { intent: 'read', assemble: assembleSources })
|
|
1448
1637
|
try {
|
|
1449
1638
|
return ok(snapshotLine('created', backupStore(store, values.out, { force: values.force === true })))
|
|
1450
1639
|
} finally {
|
|
@@ -1527,7 +1716,7 @@ export const suggestAliasCommand = async (
|
|
|
1527
1716
|
if (timeoutSeconds !== undefined && (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)) {
|
|
1528
1717
|
return fail(`cave suggest-alias: --timeout must be a positive number of seconds, got '${values.timeout}'\n`)
|
|
1529
1718
|
}
|
|
1530
|
-
const store =
|
|
1719
|
+
const store = openDb(values, values.write === true ? 'write' : 'read')
|
|
1531
1720
|
try {
|
|
1532
1721
|
let suggestions = suggestAliases(store, { minScore, ...limit === undefined ? {} : { limit } })
|
|
1533
1722
|
if (values.agent !== undefined && suggestions.length > 0) {
|
|
@@ -1584,7 +1773,7 @@ export const syncCommand = (argv: readonly string[]): Output => {
|
|
|
1584
1773
|
return fail('cave sync: exactly one source is required — a CAVE store file, ;@-annotated text, or - for stdin (spec §28.5)\n')
|
|
1585
1774
|
}
|
|
1586
1775
|
const dbPath = values.db ?? defaultDbPath()
|
|
1587
|
-
const store =
|
|
1776
|
+
const store = openDb(values, values['dry-run'] === true ? 'scratch' : 'write')
|
|
1588
1777
|
try {
|
|
1589
1778
|
const options = {
|
|
1590
1779
|
into: values.into === undefined ? labelOf(dbPath) : sanitizeLabel(values.into),
|
|
@@ -1645,7 +1834,10 @@ export const reportCommand = (argv: readonly string[]): Output => {
|
|
|
1645
1834
|
if (maximum === undefined) {
|
|
1646
1835
|
return fail(`cave report: --max-sensitivity expects ${Sensitivity.levels.join(', ')}, got ${JSON.stringify(values['max-sensitivity'])}\n`)
|
|
1647
1836
|
}
|
|
1648
|
-
|
|
1837
|
+
if (values.out !== undefined && sameFile(values.db ?? defaultDbPath(), values.out)) {
|
|
1838
|
+
return fail(`cave report: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1839
|
+
}
|
|
1840
|
+
const store = openDb(values, 'read')
|
|
1649
1841
|
try {
|
|
1650
1842
|
const rendered = caveReport(store, template, {
|
|
1651
1843
|
aliases: values.aliases === true,
|
|
@@ -1713,7 +1905,7 @@ export const exportCommand = (argv: readonly string[]): Output => {
|
|
|
1713
1905
|
if (values.out !== undefined && sameFile(db, values.out)) {
|
|
1714
1906
|
return fail(`cave export: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1715
1907
|
}
|
|
1716
|
-
const store =
|
|
1908
|
+
const store = openDb(values, 'read')
|
|
1717
1909
|
try {
|
|
1718
1910
|
const text = store.exportText({ current: values.current === true, tx: values.tx === true, maxSensitivity: maximum })
|
|
1719
1911
|
if (values.out === undefined) {
|
|
@@ -1752,7 +1944,7 @@ export const generateCommand = (argv: readonly string[]): Output => {
|
|
|
1752
1944
|
if (values.out !== undefined && sameFile(db, values.out)) {
|
|
1753
1945
|
return fail(`cave generate: --out '${values.out}' is the source database — refusing to overwrite it\n`)
|
|
1754
1946
|
}
|
|
1755
|
-
const store =
|
|
1947
|
+
const store = openDb(values, 'read')
|
|
1756
1948
|
try {
|
|
1757
1949
|
const generated = generateClient(store, version === undefined ? {} : { version })
|
|
1758
1950
|
if (!generated.ok) {
|
|
@@ -1817,7 +2009,7 @@ export const reconstructCommand = async (
|
|
|
1817
2009
|
if (timeoutSeconds !== undefined && (!Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)) {
|
|
1818
2010
|
return fail(`cave reconstruct: --timeout must be a positive number of seconds, got '${values.timeout}'\n`)
|
|
1819
2011
|
}
|
|
1820
|
-
const store =
|
|
2012
|
+
const store = openDb(values, 'read')
|
|
1821
2013
|
try {
|
|
1822
2014
|
const graph = sqliteStore(store)
|
|
1823
2015
|
const result = values.agent === undefined ?
|
|
@@ -1902,6 +2094,19 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
1902
2094
|
(rest.includes('--help') || rest.includes('-h'))) {
|
|
1903
2095
|
return ok(`${commandHelp[canonical]}\n`)
|
|
1904
2096
|
}
|
|
2097
|
+
try {
|
|
2098
|
+
return run(canonical, rest)
|
|
2099
|
+
} catch (error) {
|
|
2100
|
+
// A --db path that is not what the command needs (spec §13.7) is a
|
|
2101
|
+
// usage failure, reported the way dispatch reports thrown errors.
|
|
2102
|
+
if (error instanceof LocateError) {
|
|
2103
|
+
return fail(`cave ${canonical}: ${error.message}\n`)
|
|
2104
|
+
}
|
|
2105
|
+
throw error
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
const run = (canonical: undefined | string, rest: readonly string[]): Output => {
|
|
1905
2110
|
switch (canonical) {
|
|
1906
2111
|
case 'parse':
|
|
1907
2112
|
return parseCommand(rest)
|
|
@@ -1948,6 +2153,6 @@ export const cave = (argv: readonly string[]): Output => {
|
|
|
1948
2153
|
case '-h':
|
|
1949
2154
|
return ok(`${usage}\n`)
|
|
1950
2155
|
default:
|
|
1951
|
-
return fail(`cave: unknown command ${JSON.stringify(
|
|
2156
|
+
return fail(`cave: unknown command ${JSON.stringify(canonical)}\n\n${usage}\n`, 2)
|
|
1952
2157
|
}
|
|
1953
2158
|
}
|
package/src/commands.ts
CHANGED
|
@@ -18,7 +18,7 @@ export const commandRegistry = [
|
|
|
18
18
|
{ name: 'highlight', importantOptions: [] },
|
|
19
19
|
{ name: 'add', importantOptions: ['--strict', '--check', '--no-prelude', '--no-src'] },
|
|
20
20
|
{ name: 'import', importantOptions: ['--strict', '--no-prelude'] },
|
|
21
|
-
{ name: 'query', importantOptions: ['--json', '--limit', '--cursor', '--all', '--aliases', '--as-of', '--at', '--resolve', '--no-prelude'] },
|
|
21
|
+
{ name: 'query', importantOptions: ['--json', '--limit', '--cursor', '--all', '--aliases', '--as-of', '--at', '--resolve', '--sources', '--no-prelude'] },
|
|
22
22
|
{ name: 'search', importantOptions: ['--raw', '--limit', '--json', '--no-prelude'] },
|
|
23
23
|
{ name: 'resolve', importantOptions: ['--aliases', '--policy', '--json', '--no-prelude'] },
|
|
24
24
|
{ name: 'derive', importantOptions: ['--dry-run', '--full', '--aliases', '--min-conf', '--max-passes', '--list', '--retract', '--json', '--no-prelude'] },
|
|
@@ -36,7 +36,7 @@ export const commandRegistry = [
|
|
|
36
36
|
{ name: 'mcp', importantOptions: ['--read-only', '--permissions', '--tools', '--hooks', '--no-prelude', '--src', '--no-src'], delegated: true },
|
|
37
37
|
{ name: 'ingest', importantOptions: ['--agent', '--stdout', '--lenient', '--plan', '--dry-run', '--json', '--no-prelude'], delegated: true },
|
|
38
38
|
{ name: 'eval', importantOptions: ['--agent', '--judge', '--runs', '--stdout', '--min', '--json', '--no-prelude'], delegated: true },
|
|
39
|
-
{ name: 'connect', importantOptions: ['--map', '--key', '--watch', '--prune', '--query', '--dry-run', '--no-prelude'], delegated: true },
|
|
39
|
+
{ name: 'connect', importantOptions: ['--map', '--key', '--watch', '--prune', '--query', '--dry-run', '--list', '--no-prelude'], delegated: true },
|
|
40
40
|
{ name: 'reconstruct', importantOptions: ['--query', '--agent', '--steps', '--claims', '--timeout', '--trace', '--no-prelude'] },
|
|
41
41
|
{ name: 'doctor', importantOptions: ['--hooks', '--json'] },
|
|
42
42
|
{ name: 'demo', importantOptions: [] },
|
package/src/dispatch.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** One promise-based command dispatcher and process lifecycle for every command. */
|
|
2
2
|
|
|
3
|
-
import { cave, highlightCommand, reconstructCommand, suggestAliasCommand } from './cli.ts'
|
|
3
|
+
import { cave, highlightCommand, querySourcesCommand, reconstructCommand, suggestAliasCommand } from './cli.ts'
|
|
4
4
|
import { delegatedCommandNames } from './commands.ts'
|
|
5
5
|
import type { Output } from './cli.ts'
|
|
6
6
|
|
|
@@ -78,6 +78,12 @@ const execute = async (raw: readonly string[], runtime: CommandRuntime): Promise
|
|
|
78
78
|
const { runServe } = await import('@cavelang/view')
|
|
79
79
|
return await runServe(rest, context)
|
|
80
80
|
}
|
|
81
|
+
case 'q':
|
|
82
|
+
case 'query':
|
|
83
|
+
if (rest.includes('--sources')) {
|
|
84
|
+
return await runOutput(runtime, argv, querySourcesCommand)
|
|
85
|
+
}
|
|
86
|
+
break
|
|
81
87
|
case 'reconstruct':
|
|
82
88
|
return await runOutput(runtime, argv,
|
|
83
89
|
args => reconstructCommand(args, { ...runtime.signal === undefined ? {} : { signal: runtime.signal } }))
|
package/src/doctor.ts
CHANGED
|
@@ -6,7 +6,8 @@ import { dirname, join, parse } from 'node:path'
|
|
|
6
6
|
import { parseArgs } from 'node:util'
|
|
7
7
|
import { Version } from '@cavelang/core'
|
|
8
8
|
import { directCommand, runProcessSync } from '@cavelang/loop'
|
|
9
|
-
import { Schema, defaultDbPath } from '@cavelang/store'
|
|
9
|
+
import { LocateError, Schema, defaultDbPath, kindOf, openText } from '@cavelang/store'
|
|
10
|
+
import { assemble } from '@cavelang/connect'
|
|
10
11
|
import { nodeSqliteAdapter } from '@cavelang/store/adapter/node'
|
|
11
12
|
import type { SqliteDatabase } from '@cavelang/store/adapter'
|
|
12
13
|
|
|
@@ -32,7 +33,7 @@ export type DoctorReport = {
|
|
|
32
33
|
readonly configuration: {
|
|
33
34
|
readonly database: {
|
|
34
35
|
readonly source: 'flag' | 'environment' | 'default'
|
|
35
|
-
readonly kind: 'memory' | 'file'
|
|
36
|
+
readonly kind: 'memory' | 'file' | 'text'
|
|
36
37
|
readonly exists: boolean
|
|
37
38
|
readonly schemaVersion?: number
|
|
38
39
|
readonly claims?: number
|
|
@@ -216,6 +217,31 @@ const inspectDatabase = (path: string, source: DatabaseConfiguration['source']):
|
|
|
216
217
|
'Run cave add to create it, or pass --db for an existing store.')]
|
|
217
218
|
}
|
|
218
219
|
}
|
|
220
|
+
if (!memory && kindOf(path) === 'text') {
|
|
221
|
+
// A CAVE text file is a read-only store assembled in memory (spec
|
|
222
|
+
// §13.7, §23.4): replay it, sources included, and count what it holds.
|
|
223
|
+
const configuration: DatabaseConfiguration = { ...base, kind: 'text' }
|
|
224
|
+
try {
|
|
225
|
+
const store = openText(path, { assemble })
|
|
226
|
+
try {
|
|
227
|
+
const claims = store.currentBeliefs().filter(row => row.conf > 0).length
|
|
228
|
+
return {
|
|
229
|
+
configuration: { ...configuration, claims },
|
|
230
|
+
checks: [check('store.database', 'pass',
|
|
231
|
+
`CAVE text store assembled in memory (${claims} current claim(s)); text stores are read-only`)]
|
|
232
|
+
}
|
|
233
|
+
} finally {
|
|
234
|
+
store.close()
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return {
|
|
238
|
+
configuration,
|
|
239
|
+
checks: [check('store.database', 'fail',
|
|
240
|
+
`The CAVE text store does not load: ${error instanceof LocateError ? error.message.split('\n')[0] : error instanceof Error ? error.message : String(error)}`,
|
|
241
|
+
'Fix the file (cave parse) or the source it declares; a text store is read-only.')]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
219
245
|
|
|
220
246
|
let db: SqliteDatabase | undefined
|
|
221
247
|
let observed = base
|
|
@@ -249,7 +275,7 @@ const inspectDatabase = (path: string, source: DatabaseConfiguration['source']):
|
|
|
249
275
|
configuration,
|
|
250
276
|
checks: [check('store.database', 'warn',
|
|
251
277
|
`Database schema ${version} needs migration to schema ${Schema.currentVersion}`,
|
|
252
|
-
'
|
|
278
|
+
'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
279
|
}
|
|
254
280
|
}
|
|
255
281
|
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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, searchCommand, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
|
|
7
|
+
export { actCommand, addCommand, backupCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, generateCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, querySourcesCommand, 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'
|