@cavelang/cli 0.27.3 → 0.29.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/Authors.md +5 -0
- package/License.md +6 -0
- package/README.md +122 -24
- package/dist/internal/act/action.d.ts +69 -0
- package/dist/internal/act/action.d.ts.map +1 -0
- package/dist/internal/act/action.js +201 -0
- package/dist/internal/act/action.js.map +1 -0
- package/dist/internal/act/declare.d.ts +102 -0
- package/dist/internal/act/declare.d.ts.map +1 -0
- package/dist/internal/act/declare.js +270 -0
- package/dist/internal/act/declare.js.map +1 -0
- package/dist/internal/act/engine.d.ts +103 -0
- package/dist/internal/act/engine.d.ts.map +1 -0
- package/dist/internal/act/engine.js +298 -0
- package/dist/internal/act/engine.js.map +1 -0
- package/dist/internal/act/index.d.ts +27 -0
- package/dist/internal/act/index.d.ts.map +1 -0
- package/dist/internal/act/index.js +25 -0
- package/dist/internal/act/index.js.map +1 -0
- package/dist/internal/automate/automation.d.ts +78 -0
- package/dist/internal/automate/automation.d.ts.map +1 -0
- package/dist/internal/automate/automation.js +135 -0
- package/dist/internal/automate/automation.js.map +1 -0
- package/dist/internal/automate/declare.d.ts +84 -0
- package/dist/internal/automate/declare.d.ts.map +1 -0
- package/dist/internal/automate/declare.js +233 -0
- package/dist/internal/automate/declare.js.map +1 -0
- package/dist/internal/automate/engine.d.ts +125 -0
- package/dist/internal/automate/engine.d.ts.map +1 -0
- package/dist/internal/automate/engine.js +388 -0
- package/dist/internal/automate/engine.js.map +1 -0
- package/dist/internal/automate/index.d.ts +28 -0
- package/dist/internal/automate/index.d.ts.map +1 -0
- package/dist/internal/automate/index.js +26 -0
- package/dist/internal/automate/index.js.map +1 -0
- package/dist/internal/automate/main.d.ts +26 -0
- package/dist/internal/automate/main.d.ts.map +1 -0
- package/dist/internal/automate/main.js +298 -0
- package/dist/internal/automate/main.js.map +1 -0
- package/dist/internal/connect/index.d.ts +26 -0
- package/dist/internal/connect/index.d.ts.map +1 -0
- package/dist/internal/connect/index.js +24 -0
- package/dist/internal/connect/index.js.map +1 -0
- package/dist/internal/connect/main.d.ts +23 -0
- package/dist/internal/connect/main.d.ts.map +1 -0
- package/dist/internal/connect/main.js +330 -0
- package/dist/internal/connect/main.js.map +1 -0
- package/dist/internal/connect/run.d.ts +85 -0
- package/dist/internal/connect/run.d.ts.map +1 -0
- package/dist/internal/connect/run.js +242 -0
- package/dist/internal/connect/run.js.map +1 -0
- package/dist/internal/connect/source.d.ts +44 -0
- package/dist/internal/connect/source.d.ts.map +1 -0
- package/dist/internal/connect/source.js +215 -0
- package/dist/internal/connect/source.js.map +1 -0
- package/dist/internal/connect/template.d.ts +69 -0
- package/dist/internal/connect/template.d.ts.map +1 -0
- package/dist/internal/connect/template.js +221 -0
- package/dist/internal/connect/template.js.map +1 -0
- package/dist/internal/eval/index.d.ts +28 -0
- package/dist/internal/eval/index.d.ts.map +1 -0
- package/dist/internal/eval/index.js +27 -0
- package/dist/internal/eval/index.js.map +1 -0
- package/dist/internal/eval/judge.d.ts +30 -0
- package/dist/internal/eval/judge.d.ts.map +1 -0
- package/dist/internal/eval/judge.js +101 -0
- package/dist/internal/eval/judge.js.map +1 -0
- package/dist/internal/eval/loop.d.ts +64 -0
- package/dist/internal/eval/loop.d.ts.map +1 -0
- package/dist/internal/eval/loop.js +123 -0
- package/dist/internal/eval/loop.js.map +1 -0
- package/dist/internal/eval/main.d.ts +17 -0
- package/dist/internal/eval/main.d.ts.map +1 -0
- package/dist/internal/eval/main.js +236 -0
- package/dist/internal/eval/main.js.map +1 -0
- package/dist/internal/eval/queries.d.ts +74 -0
- package/dist/internal/eval/queries.d.ts.map +1 -0
- package/dist/internal/eval/queries.js +161 -0
- package/dist/internal/eval/queries.js.map +1 -0
- package/dist/internal/eval/run.d.ts +143 -0
- package/dist/internal/eval/run.d.ts.map +1 -0
- package/dist/internal/eval/run.js +376 -0
- package/dist/internal/eval/run.js.map +1 -0
- package/dist/internal/eval/score.d.ts +81 -0
- package/dist/internal/eval/score.d.ts.map +1 -0
- package/dist/internal/eval/score.js +126 -0
- package/dist/internal/eval/score.js.map +1 -0
- package/dist/internal/eval/suite.d.ts +66 -0
- package/dist/internal/eval/suite.d.ts.map +1 -0
- package/dist/internal/eval/suite.js +130 -0
- package/dist/internal/eval/suite.js.map +1 -0
- package/dist/internal/ingest/context.d.ts +20 -0
- package/dist/internal/ingest/context.d.ts.map +1 -0
- package/dist/internal/ingest/context.js +59 -0
- package/dist/internal/ingest/context.js.map +1 -0
- package/dist/internal/ingest/files.d.ts +46 -0
- package/dist/internal/ingest/files.d.ts.map +1 -0
- package/dist/internal/ingest/files.js +115 -0
- package/dist/internal/ingest/files.js.map +1 -0
- package/dist/internal/ingest/index.d.ts +31 -0
- package/dist/internal/ingest/index.d.ts.map +1 -0
- package/dist/internal/ingest/index.js +29 -0
- package/dist/internal/ingest/index.js.map +1 -0
- package/dist/internal/ingest/main.d.ts +12 -0
- package/dist/internal/ingest/main.d.ts.map +1 -0
- package/dist/internal/ingest/main.js +180 -0
- package/dist/internal/ingest/main.js.map +1 -0
- package/dist/internal/ingest/prompt.d.ts +32 -0
- package/dist/internal/ingest/prompt.d.ts.map +1 -0
- package/dist/internal/ingest/prompt.js +75 -0
- package/dist/internal/ingest/prompt.js.map +1 -0
- package/dist/internal/ingest/run.d.ts +142 -0
- package/dist/internal/ingest/run.d.ts.map +1 -0
- package/dist/internal/ingest/run.js +350 -0
- package/dist/internal/ingest/run.js.map +1 -0
- package/dist/internal/ingest/web.d.ts +48 -0
- package/dist/internal/ingest/web.d.ts.map +1 -0
- package/dist/internal/ingest/web.js +140 -0
- package/dist/internal/ingest/web.js.map +1 -0
- package/dist/internal/loop/demo.d.ts +22 -0
- package/dist/internal/loop/demo.d.ts.map +1 -0
- package/dist/internal/loop/demo.js +67 -0
- package/dist/internal/loop/demo.js.map +1 -0
- package/dist/internal/loop/index.d.ts +20 -0
- package/dist/internal/loop/index.d.ts.map +1 -0
- package/dist/internal/loop/index.js +17 -0
- package/dist/internal/loop/index.js.map +1 -0
- package/dist/internal/loop/llm.d.ts +99 -0
- package/dist/internal/loop/llm.d.ts.map +1 -0
- package/dist/internal/loop/llm.js +231 -0
- package/dist/internal/loop/llm.js.map +1 -0
- package/dist/internal/loop/reconstruct.d.ts +87 -0
- package/dist/internal/loop/reconstruct.d.ts.map +1 -0
- package/dist/internal/loop/reconstruct.js +157 -0
- package/dist/internal/loop/reconstruct.js.map +1 -0
- package/dist/internal/loop/sqlite.d.ts +13 -0
- package/dist/internal/loop/sqlite.d.ts.map +1 -0
- package/dist/internal/loop/sqlite.js +33 -0
- package/dist/internal/loop/sqlite.js.map +1 -0
- package/dist/internal/loop/store.d.ts +50 -0
- package/dist/internal/loop/store.d.ts.map +1 -0
- package/dist/internal/loop/store.js +100 -0
- package/dist/internal/loop/store.js.map +1 -0
- package/dist/internal/mcp/bin.d.ts +7 -0
- package/dist/internal/mcp/bin.d.ts.map +1 -0
- package/dist/internal/mcp/bin.js +8 -0
- package/dist/internal/mcp/bin.js.map +1 -0
- package/dist/internal/mcp/index.d.ts +25 -0
- package/dist/internal/mcp/index.d.ts.map +1 -0
- package/dist/internal/mcp/index.js +23 -0
- package/dist/internal/mcp/index.js.map +1 -0
- package/dist/internal/mcp/main.d.ts +23 -0
- package/dist/internal/mcp/main.d.ts.map +1 -0
- package/dist/internal/mcp/main.js +156 -0
- package/dist/internal/mcp/main.js.map +1 -0
- package/dist/internal/mcp/server.d.ts +92 -0
- package/dist/internal/mcp/server.d.ts.map +1 -0
- package/dist/internal/mcp/server.js +251 -0
- package/dist/internal/mcp/server.js.map +1 -0
- package/dist/internal/mcp/tools.d.ts +91 -0
- package/dist/internal/mcp/tools.d.ts.map +1 -0
- package/dist/internal/mcp/tools.js +584 -0
- package/dist/internal/mcp/tools.js.map +1 -0
- package/dist/internal/rules/declare.d.ts +58 -0
- package/dist/internal/rules/declare.d.ts.map +1 -0
- package/dist/internal/rules/declare.js +152 -0
- package/dist/internal/rules/declare.js.map +1 -0
- package/dist/internal/rules/engine.d.ts +125 -0
- package/dist/internal/rules/engine.d.ts.map +1 -0
- package/dist/internal/rules/engine.js +575 -0
- package/dist/internal/rules/engine.js.map +1 -0
- package/dist/internal/rules/index.d.ts +23 -0
- package/dist/internal/rules/index.d.ts.map +1 -0
- package/dist/internal/rules/index.js +21 -0
- package/dist/internal/rules/index.js.map +1 -0
- package/dist/internal/rules/rule.d.ts +88 -0
- package/dist/internal/rules/rule.d.ts.map +1 -0
- package/dist/internal/rules/rule.js +221 -0
- package/dist/internal/rules/rule.js.map +1 -0
- package/dist/internal/shape/check.d.ts +118 -0
- package/dist/internal/shape/check.d.ts.map +1 -0
- package/dist/internal/shape/check.js +331 -0
- package/dist/internal/shape/check.js.map +1 -0
- package/dist/internal/shape/client.d.ts +31 -0
- package/dist/internal/shape/client.d.ts.map +1 -0
- package/dist/internal/shape/client.js +170 -0
- package/dist/internal/shape/client.js.map +1 -0
- package/dist/internal/shape/gate.d.ts +33 -0
- package/dist/internal/shape/gate.d.ts.map +1 -0
- package/dist/internal/shape/gate.js +0 -0
- package/dist/internal/shape/gate.js.map +1 -0
- package/dist/internal/shape/index.d.ts +27 -0
- package/dist/internal/shape/index.d.ts.map +1 -0
- package/dist/internal/shape/index.js +23 -0
- package/dist/internal/shape/index.js.map +1 -0
- package/dist/internal/shape/suggest.d.ts +82 -0
- package/dist/internal/shape/suggest.d.ts.map +1 -0
- package/dist/internal/shape/suggest.js +374 -0
- package/dist/internal/shape/suggest.js.map +1 -0
- package/dist/internal/sync/index.d.ts +22 -0
- package/dist/internal/sync/index.d.ts.map +1 -0
- package/dist/internal/sync/index.js +21 -0
- package/dist/internal/sync/index.js.map +1 -0
- package/dist/internal/sync/sync.d.ts +95 -0
- package/dist/internal/sync/sync.d.ts.map +1 -0
- package/dist/internal/sync/sync.js +342 -0
- package/dist/internal/sync/sync.js.map +1 -0
- package/dist/internal/view/api.d.ts +196 -0
- package/dist/internal/view/api.d.ts.map +1 -0
- package/dist/internal/view/api.js +189 -0
- package/dist/internal/view/api.js.map +1 -0
- package/dist/internal/view/index.d.ts +33 -0
- package/dist/internal/view/index.d.ts.map +1 -0
- package/dist/internal/view/index.js +30 -0
- package/dist/internal/view/index.js.map +1 -0
- package/dist/internal/view/main.d.ts +11 -0
- package/dist/internal/view/main.d.ts.map +1 -0
- package/dist/internal/view/main.js +98 -0
- package/dist/internal/view/main.js.map +1 -0
- package/dist/internal/view/page.d.ts +14 -0
- package/dist/internal/view/page.d.ts.map +1 -0
- package/dist/internal/view/page.js +370 -0
- package/dist/internal/view/page.js.map +1 -0
- package/dist/internal/view/report.d.ts +52 -0
- package/dist/internal/view/report.d.ts.map +1 -0
- package/dist/internal/view/report.js +367 -0
- package/dist/internal/view/report.js.map +1 -0
- package/dist/internal/view/scope.d.ts +11 -0
- package/dist/internal/view/scope.d.ts.map +1 -0
- package/dist/internal/view/scope.js +41 -0
- package/dist/internal/view/scope.js.map +1 -0
- package/dist/internal/view/server.d.ts +35 -0
- package/dist/internal/view/server.d.ts.map +1 -0
- package/dist/internal/view/server.js +141 -0
- package/dist/internal/view/server.js.map +1 -0
- package/dist/src/act.d.ts +3 -0
- package/dist/src/act.d.ts.map +1 -0
- package/dist/src/act.js +3 -0
- package/dist/src/act.js.map +1 -0
- package/dist/src/automate.d.ts +3 -0
- package/dist/src/automate.d.ts.map +1 -0
- package/dist/src/automate.js +3 -0
- package/dist/src/automate.js.map +1 -0
- package/dist/src/cli.d.ts +24 -17
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +416 -109
- package/dist/src/cli.js.map +1 -1
- package/dist/src/commands.d.ts +104 -0
- package/dist/src/commands.d.ts.map +1 -0
- package/dist/src/commands.js +33 -0
- package/dist/src/commands.js.map +1 -0
- package/dist/src/connect.d.ts +3 -0
- package/dist/src/connect.d.ts.map +1 -0
- package/dist/src/connect.js +3 -0
- package/dist/src/connect.js.map +1 -0
- package/dist/src/dispatch.d.ts +14 -0
- package/dist/src/dispatch.d.ts.map +1 -0
- package/dist/src/dispatch.js +116 -0
- package/dist/src/dispatch.js.map +1 -0
- package/dist/src/doctor.d.ts +45 -0
- package/dist/src/doctor.d.ts.map +1 -0
- package/dist/src/doctor.js +284 -0
- package/dist/src/doctor.js.map +1 -0
- package/dist/src/eval.d.ts +3 -0
- package/dist/src/eval.d.ts.map +1 -0
- package/dist/src/eval.js +3 -0
- package/dist/src/eval.js.map +1 -0
- package/dist/src/index.d.ts +9 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -2
- package/dist/src/index.js.map +1 -1
- package/dist/src/ingest.d.ts +3 -0
- package/dist/src/ingest.d.ts.map +1 -0
- package/dist/src/ingest.js +3 -0
- package/dist/src/ingest.js.map +1 -0
- package/dist/src/loop.d.ts +3 -0
- package/dist/src/loop.d.ts.map +1 -0
- package/dist/src/loop.js +3 -0
- package/dist/src/loop.js.map +1 -0
- package/dist/src/main.d.ts +1 -1
- package/dist/src/main.d.ts.map +1 -1
- package/dist/src/main.js +3 -75
- package/dist/src/main.js.map +1 -1
- package/dist/src/mcp.d.ts +3 -0
- package/dist/src/mcp.d.ts.map +1 -0
- package/dist/src/mcp.js +3 -0
- package/dist/src/mcp.js.map +1 -0
- package/dist/src/rules.d.ts +3 -0
- package/dist/src/rules.d.ts.map +1 -0
- package/dist/src/rules.js +3 -0
- package/dist/src/rules.js.map +1 -0
- package/dist/src/shape.d.ts +3 -0
- package/dist/src/shape.d.ts.map +1 -0
- package/dist/src/shape.js +3 -0
- package/dist/src/shape.js.map +1 -0
- package/dist/src/sync.d.ts +3 -0
- package/dist/src/sync.d.ts.map +1 -0
- package/dist/src/sync.js +3 -0
- package/dist/src/sync.js.map +1 -0
- package/dist/src/view.d.ts +3 -0
- package/dist/src/view.d.ts.map +1 -0
- package/dist/src/view.js +3 -0
- package/dist/src/view.js.map +1 -0
- package/package.json +81 -21
- package/src/act.ts +2 -0
- package/src/automate.ts +2 -0
- package/src/cli.ts +423 -104
- package/src/commands.ts +48 -0
- package/src/connect.ts +2 -0
- package/src/dispatch.ts +141 -0
- package/src/doctor.ts +370 -0
- package/src/eval.ts +2 -0
- package/src/index.ts +9 -2
- package/src/ingest.ts +2 -0
- package/src/loop.ts +2 -0
- package/src/main.ts +3 -66
- package/src/mcp.ts +2 -0
- package/src/rules.ts +2 -0
- package/src/shape.ts +2 -0
- package/src/sync.ts +2 -0
- package/src/view.ts +2 -0
package/src/commands.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authoritative inventory of the commands shipped by the `cave` binary.
|
|
3
|
+
*
|
|
4
|
+
* `importantOptions` is deliberately smaller than each command's complete
|
|
5
|
+
* `--help`: it is the review contract for the package README's reference
|
|
6
|
+
* table. Add a command or an option here when it becomes part of the public
|
|
7
|
+
* surface; the documentation test then identifies every stale copy.
|
|
8
|
+
*/
|
|
9
|
+
export type CommandReference = {
|
|
10
|
+
readonly name: string
|
|
11
|
+
readonly importantOptions: readonly string[]
|
|
12
|
+
/** The command implementation and detailed help live in another package. */
|
|
13
|
+
readonly delegated?: true
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const commandRegistry = [
|
|
17
|
+
{ name: 'parse', importantOptions: ['--json'] },
|
|
18
|
+
{ name: 'highlight', importantOptions: [] },
|
|
19
|
+
{ name: 'add', importantOptions: ['--strict', '--check', '--no-prelude', '--no-src'] },
|
|
20
|
+
{ name: 'import', importantOptions: ['--strict', '--no-prelude'] },
|
|
21
|
+
{ name: 'query', importantOptions: ['--json', '--limit', '--cursor', '--all', '--aliases', '--as-of', '--at', '--resolve', '--no-prelude'] },
|
|
22
|
+
{ name: 'resolve', importantOptions: ['--aliases', '--policy', '--json', '--no-prelude'] },
|
|
23
|
+
{ name: 'derive', importantOptions: ['--dry-run', '--full', '--aliases', '--min-conf', '--max-passes', '--list', '--retract', '--json', '--no-prelude'] },
|
|
24
|
+
{ name: 'act', importantOptions: ['--declare', '--list', '--retract', '--dry-run', '--no-check', '--aliases', '--hooks', '--json', '--no-prelude'] },
|
|
25
|
+
{ name: 'automate', importantOptions: ['--once', '--declare', '--list', '--retract', '--hooks', '--agent', '--json', '--no-prelude'], delegated: true },
|
|
26
|
+
{ name: 'check', importantOptions: ['--stale', '--json', '--no-prelude'] },
|
|
27
|
+
{ name: 'backup', importantOptions: ['--out', '--force', '--verify', '--sha256'] },
|
|
28
|
+
{ name: 'restore', importantOptions: ['--db', '--force', '--sha256'] },
|
|
29
|
+
{ name: 'generate', importantOptions: ['--out', '--version', '--no-prelude'] },
|
|
30
|
+
{ name: 'suggest-alias', importantOptions: ['--min', '--limit', '--agent', '--timeout', '--write', '--json', '--no-prelude'] },
|
|
31
|
+
{ name: 'sync', importantOptions: ['--as', '--into', '--dry-run', '--no-record', '--json', '--no-prelude'] },
|
|
32
|
+
{ name: 'export', importantOptions: ['--out', '--current', '--tx', '--max-sensitivity', '--no-prelude'] },
|
|
33
|
+
{ name: 'report', importantOptions: ['--out', '--aliases', '--resolve', '--as-of', '--at', '--max-sensitivity', '--no-prelude'] },
|
|
34
|
+
{ name: 'serve', importantOptions: ['--port', '--host', '--max-sensitivity', '--no-prelude'], delegated: true },
|
|
35
|
+
{ name: 'mcp', importantOptions: ['--read-only', '--permissions', '--tools', '--hooks', '--no-prelude', '--src', '--no-src'], delegated: true },
|
|
36
|
+
{ name: 'ingest', importantOptions: ['--agent', '--stdout', '--lenient', '--plan', '--dry-run', '--json', '--no-prelude'], delegated: true },
|
|
37
|
+
{ name: 'eval', importantOptions: ['--agent', '--judge', '--runs', '--stdout', '--min', '--json', '--no-prelude'], delegated: true },
|
|
38
|
+
{ name: 'connect', importantOptions: ['--map', '--key', '--watch', '--prune', '--query', '--dry-run', '--no-prelude'], delegated: true },
|
|
39
|
+
{ name: 'reconstruct', importantOptions: ['--query', '--agent', '--steps', '--claims', '--timeout', '--trace', '--no-prelude'] },
|
|
40
|
+
{ name: 'doctor', importantOptions: ['--hooks', '--json'] },
|
|
41
|
+
{ name: 'demo', importantOptions: [] },
|
|
42
|
+
{ name: 'version', importantOptions: [] },
|
|
43
|
+
{ name: 'help', importantOptions: [] }
|
|
44
|
+
] as const satisfies readonly CommandReference[]
|
|
45
|
+
|
|
46
|
+
export const delegatedCommandNames: readonly string[] = commandRegistry
|
|
47
|
+
.filter(command => 'delegated' in command && command.delegated === true)
|
|
48
|
+
.map(command => command.name)
|
package/src/connect.ts
ADDED
package/src/dispatch.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/** One promise-based command dispatcher and process lifecycle for every command. */
|
|
2
|
+
|
|
3
|
+
import { cave, highlightCommand, reconstructCommand, suggestAliasCommand } from './cli.ts'
|
|
4
|
+
import { delegatedCommandNames } from './commands.ts'
|
|
5
|
+
import type { Output } from './cli.ts'
|
|
6
|
+
|
|
7
|
+
export type CommandRuntime = {
|
|
8
|
+
readonly stdin: NodeJS.ReadableStream
|
|
9
|
+
readonly stdout: NodeJS.WritableStream
|
|
10
|
+
readonly stderr: NodeJS.WritableStream
|
|
11
|
+
readonly signal?: AbortSignal
|
|
12
|
+
/** Include stack traces for unexpected exceptions. */
|
|
13
|
+
readonly debug?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const processRuntime = (): CommandRuntime => ({
|
|
17
|
+
stdin: process.stdin,
|
|
18
|
+
stdout: process.stdout,
|
|
19
|
+
stderr: process.stderr,
|
|
20
|
+
debug: process.env['CAVE_DEBUG'] === '1'
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
const delegatedHelp = new Set<string>(delegatedCommandNames)
|
|
24
|
+
|
|
25
|
+
const normalizedArgs = (raw: readonly string[]): readonly string[] =>
|
|
26
|
+
raw[0] === 'help' && raw[1] !== undefined && delegatedHelp.has(raw[1]) ? [raw[1], '--help'] : raw
|
|
27
|
+
|
|
28
|
+
const write = (runtime: CommandRuntime, output: Output): number => {
|
|
29
|
+
if (output.out !== '') runtime.stdout.write(output.out)
|
|
30
|
+
if (output.err !== '') runtime.stderr.write(output.err)
|
|
31
|
+
return output.code
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const runOutput = async (
|
|
35
|
+
runtime: CommandRuntime,
|
|
36
|
+
argv: readonly string[],
|
|
37
|
+
handler: (args: readonly string[]) => Output | Promise<Output>
|
|
38
|
+
): Promise<number> => {
|
|
39
|
+
runtime.signal?.throwIfAborted()
|
|
40
|
+
const output = await handler(argv.slice(1))
|
|
41
|
+
runtime.signal?.throwIfAborted()
|
|
42
|
+
return write(runtime, output)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const execute = async (raw: readonly string[], runtime: CommandRuntime): Promise<number> => {
|
|
46
|
+
const argv = normalizedArgs(raw)
|
|
47
|
+
const [command, ...rest] = argv
|
|
48
|
+
const context = {
|
|
49
|
+
stdin: runtime.stdin,
|
|
50
|
+
stdout: runtime.stdout,
|
|
51
|
+
stderr: runtime.stderr,
|
|
52
|
+
...runtime.signal === undefined ? {} : { signal: runtime.signal }
|
|
53
|
+
}
|
|
54
|
+
switch (command) {
|
|
55
|
+
case 'mcp': {
|
|
56
|
+
const { runMcp } = await import('@cavelang/mcp')
|
|
57
|
+
return await runMcp(rest, context)
|
|
58
|
+
}
|
|
59
|
+
case 'ingest': {
|
|
60
|
+
const { runIngest } = await import('@cavelang/ingest')
|
|
61
|
+
return await runIngest(rest, context)
|
|
62
|
+
}
|
|
63
|
+
case 'eval': {
|
|
64
|
+
const { runEval } = await import('@cavelang/eval')
|
|
65
|
+
return await runEval(rest, context)
|
|
66
|
+
}
|
|
67
|
+
case 'connect': {
|
|
68
|
+
const { runConnect } = await import('@cavelang/connect')
|
|
69
|
+
return await runConnect(rest, context)
|
|
70
|
+
}
|
|
71
|
+
case 'automate': {
|
|
72
|
+
const { runAutomate } = await import('@cavelang/automate')
|
|
73
|
+
return await runAutomate(rest, context)
|
|
74
|
+
}
|
|
75
|
+
case 'serve': {
|
|
76
|
+
const { runServe } = await import('@cavelang/view')
|
|
77
|
+
return await runServe(rest, context)
|
|
78
|
+
}
|
|
79
|
+
case 'reconstruct':
|
|
80
|
+
return await runOutput(runtime, argv, reconstructCommand)
|
|
81
|
+
case 'suggest-alias':
|
|
82
|
+
return await runOutput(runtime, argv, suggestAliasCommand)
|
|
83
|
+
case 'highlight':
|
|
84
|
+
if (!rest.includes('--help') && !rest.includes('-h')) {
|
|
85
|
+
return await runOutput(runtime, argv, highlightCommand)
|
|
86
|
+
}
|
|
87
|
+
break
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
runtime.signal?.throwIfAborted()
|
|
91
|
+
const output = await Promise.resolve(cave(argv))
|
|
92
|
+
runtime.signal?.throwIfAborted()
|
|
93
|
+
const colorize = output.code === 0 && command === 'export' && !rest.includes('--out') &&
|
|
94
|
+
output.out !== '' && runtime.stdout === process.stdout && process.stdout.isTTY === true &&
|
|
95
|
+
process.env['NO_COLOR'] === undefined
|
|
96
|
+
if (!colorize) return write(runtime, output)
|
|
97
|
+
const { highlighter } = await import('@cavelang/highlight')
|
|
98
|
+
return write(runtime, { ...output, out: (await highlighter()).ansi(output.out) })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Dispatch one invocation through the shared argument, I/O, error, and exit path. */
|
|
102
|
+
export const dispatch = async (
|
|
103
|
+
argv: readonly string[],
|
|
104
|
+
runtime: CommandRuntime = processRuntime()
|
|
105
|
+
): Promise<number> => {
|
|
106
|
+
try {
|
|
107
|
+
return await execute(argv, runtime)
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (runtime.signal?.aborted === true) return 0
|
|
110
|
+
const command = argv[0] === 'q' ? 'query' : argv[0]
|
|
111
|
+
const message = runtime.debug === true && error instanceof Error && error.stack !== undefined ?
|
|
112
|
+
error.stack : error instanceof Error ? error.message : String(error)
|
|
113
|
+
runtime.stderr.write(`cave${command === undefined || command.startsWith('-') ? '' : ` ${command}`}: ${message}\n`)
|
|
114
|
+
return 1
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const signalExitCode = { SIGINT: 130, SIGTERM: 143 } as const
|
|
119
|
+
|
|
120
|
+
/** Run the process lifecycle, aborting handlers and awaiting their cleanup on signals. */
|
|
121
|
+
export const runCli = async (
|
|
122
|
+
argv: readonly string[],
|
|
123
|
+
runtime: CommandRuntime = processRuntime()
|
|
124
|
+
): Promise<number> => {
|
|
125
|
+
const controller = new AbortController()
|
|
126
|
+
let received: keyof typeof signalExitCode | undefined
|
|
127
|
+
const handlers = {
|
|
128
|
+
SIGINT: (): void => { received = 'SIGINT'; controller.abort() },
|
|
129
|
+
SIGTERM: (): void => { received = 'SIGTERM'; controller.abort() }
|
|
130
|
+
}
|
|
131
|
+
process.once('SIGINT', handlers.SIGINT)
|
|
132
|
+
process.once('SIGTERM', handlers.SIGTERM)
|
|
133
|
+
const signal = runtime.signal === undefined ? controller.signal : AbortSignal.any([runtime.signal, controller.signal])
|
|
134
|
+
try {
|
|
135
|
+
const code = await dispatch(argv, { ...runtime, signal })
|
|
136
|
+
return received === undefined ? code : signalExitCode[received]
|
|
137
|
+
} finally {
|
|
138
|
+
process.removeListener('SIGINT', handlers.SIGINT)
|
|
139
|
+
process.removeListener('SIGTERM', handlers.SIGTERM)
|
|
140
|
+
}
|
|
141
|
+
}
|
package/src/doctor.ts
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/** Read-only installation, runtime, and store diagnostics for `cave doctor`. */
|
|
2
|
+
|
|
3
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
4
|
+
import { createRequire } from 'node:module'
|
|
5
|
+
import { dirname, join, parse } from 'node:path'
|
|
6
|
+
import { spawnSync } from 'node:child_process'
|
|
7
|
+
import { parseArgs } from 'node:util'
|
|
8
|
+
import { Version } from '@cavelang/core'
|
|
9
|
+
import { Schema, defaultDbPath } from '@cavelang/store'
|
|
10
|
+
import { nodeSqliteAdapter } from '@cavelang/store/adapter/node'
|
|
11
|
+
import type { SqliteDatabase } from '@cavelang/store/adapter'
|
|
12
|
+
|
|
13
|
+
export type DoctorStatus = 'pass' | 'warn' | 'fail'
|
|
14
|
+
|
|
15
|
+
export type DoctorCheck = {
|
|
16
|
+
readonly id: string
|
|
17
|
+
readonly status: DoctorStatus
|
|
18
|
+
readonly summary: string
|
|
19
|
+
readonly remediation?: string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DoctorReport = {
|
|
23
|
+
readonly format: 'cave.doctor'
|
|
24
|
+
readonly version: 1
|
|
25
|
+
readonly ok: boolean
|
|
26
|
+
readonly caveVersion: string
|
|
27
|
+
readonly runtime: {
|
|
28
|
+
readonly node: string
|
|
29
|
+
readonly platform: NodeJS.Platform
|
|
30
|
+
readonly arch: string
|
|
31
|
+
}
|
|
32
|
+
readonly configuration: {
|
|
33
|
+
readonly database: {
|
|
34
|
+
readonly source: 'flag' | 'environment' | 'default'
|
|
35
|
+
readonly kind: 'memory' | 'file'
|
|
36
|
+
readonly exists: boolean
|
|
37
|
+
readonly schemaVersion?: number
|
|
38
|
+
readonly claims?: number
|
|
39
|
+
}
|
|
40
|
+
readonly hooks: {
|
|
41
|
+
readonly source: 'flag' | 'none'
|
|
42
|
+
readonly exists: boolean
|
|
43
|
+
readonly entries?: number
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
readonly checks: readonly DoctorCheck[]
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type DoctorOutput = {
|
|
50
|
+
readonly code: number
|
|
51
|
+
readonly out: string
|
|
52
|
+
readonly err: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const requiredNode = '22.18.0'
|
|
56
|
+
|
|
57
|
+
const versionParts = (value: string): readonly [number, number, number] | undefined => {
|
|
58
|
+
const match = /^(\d+)\.(\d+)\.(\d+)/.exec(value)
|
|
59
|
+
return match === null ? undefined : [Number(match[1]), Number(match[2]), Number(match[3])]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const numericVersion = (value: string): string | undefined => {
|
|
63
|
+
const parts = versionParts(value)
|
|
64
|
+
return parts === undefined ? undefined : parts.join('.')
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const atLeast = (actual: string, required: string): boolean => {
|
|
68
|
+
const a = versionParts(actual)
|
|
69
|
+
const b = versionParts(required)
|
|
70
|
+
if (a === undefined || b === undefined) return false
|
|
71
|
+
for (let index = 0; index < 3; index += 1) {
|
|
72
|
+
if (a[index]! !== b[index]!) return a[index]! > b[index]!
|
|
73
|
+
}
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const check = (
|
|
78
|
+
id: string,
|
|
79
|
+
status: DoctorStatus,
|
|
80
|
+
summary: string,
|
|
81
|
+
remediation?: string
|
|
82
|
+
): DoctorCheck => ({ id, status, summary, ...remediation === undefined ? {} : { remediation } })
|
|
83
|
+
|
|
84
|
+
const nodeCheck = (): DoctorCheck =>
|
|
85
|
+
atLeast(process.versions.node, requiredNode) ?
|
|
86
|
+
check('runtime.node', 'pass', `Node ${process.versions.node} satisfies >=${requiredNode}`) :
|
|
87
|
+
check('runtime.node', 'fail', `Node ${process.versions.node} is unsupported`,
|
|
88
|
+
`Install Node ${requiredNode} or newer and rerun cave doctor.`)
|
|
89
|
+
|
|
90
|
+
const sqliteCheck = (): DoctorCheck => {
|
|
91
|
+
let db: SqliteDatabase | undefined
|
|
92
|
+
try {
|
|
93
|
+
db = nodeSqliteAdapter.open(':memory:')
|
|
94
|
+
db.exec('PRAGMA foreign_keys = ON')
|
|
95
|
+
const sqlite = db.prepare('SELECT sqlite_version() AS version').get()?.['version']
|
|
96
|
+
const json = db.prepare(`SELECT json_valid('{}') AS available`).get()?.['available']
|
|
97
|
+
const foreignKeys = db.prepare('PRAGMA foreign_keys').get()?.['foreign_keys']
|
|
98
|
+
db.exec('CREATE VIRTUAL TABLE doctor_fts USING fts5(value)')
|
|
99
|
+
db.exec('DROP TABLE doctor_fts')
|
|
100
|
+
if (typeof sqlite !== 'string' || json !== 1 || foreignKeys !== 1 ||
|
|
101
|
+
nodeSqliteAdapter.capabilities.loadExtension === undefined) {
|
|
102
|
+
throw new Error('capability unavailable')
|
|
103
|
+
}
|
|
104
|
+
return check('runtime.sqlite', 'pass',
|
|
105
|
+
`SQLite ${sqlite} supports FTS5, JSON functions, foreign keys, and extension loading`)
|
|
106
|
+
} catch {
|
|
107
|
+
return check('runtime.sqlite', 'fail', 'The Node SQLite runtime lacks a required capability',
|
|
108
|
+
`Use an official Node ${requiredNode}+ build with SQLite FTS5 and JSON support.`)
|
|
109
|
+
} finally {
|
|
110
|
+
db?.close()
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const grammarCheck = (): DoctorCheck => {
|
|
115
|
+
try {
|
|
116
|
+
const fromHighlight = createRequire(import.meta.resolve('@cavelang/highlight'))
|
|
117
|
+
const assets = [
|
|
118
|
+
fromHighlight.resolve('@cavelang/tree-sitter-cave/wasm'),
|
|
119
|
+
fromHighlight.resolve('@cavelang/tree-sitter-cave/highlights')
|
|
120
|
+
]
|
|
121
|
+
if (assets.some(asset => !statSync(asset).isFile() || statSync(asset).size === 0)) {
|
|
122
|
+
throw new Error('asset unavailable')
|
|
123
|
+
}
|
|
124
|
+
return check('package.grammar', 'pass', 'Grammar WASM and highlight query are installed')
|
|
125
|
+
} catch {
|
|
126
|
+
return check('package.grammar', 'fail', 'Grammar assets are missing from the installed package layout',
|
|
127
|
+
'Reinstall @cavelang/cli without omitting its production dependencies.')
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
type Workspace = { readonly packageManager: string }
|
|
132
|
+
|
|
133
|
+
const workspaceFrom = (start: string): Workspace | undefined => {
|
|
134
|
+
let directory = start
|
|
135
|
+
while (true) {
|
|
136
|
+
if (existsSync(join(directory, 'pnpm-workspace.yaml'))) {
|
|
137
|
+
try {
|
|
138
|
+
const manifest = JSON.parse(readFileSync(join(directory, 'package.json'), 'utf8')) as {
|
|
139
|
+
packageManager?: unknown
|
|
140
|
+
}
|
|
141
|
+
if (typeof manifest.packageManager === 'string' && manifest.packageManager.startsWith('pnpm@')) {
|
|
142
|
+
return { packageManager: manifest.packageManager }
|
|
143
|
+
}
|
|
144
|
+
} catch {
|
|
145
|
+
return { packageManager: 'pnpm' }
|
|
146
|
+
}
|
|
147
|
+
return { packageManager: 'pnpm' }
|
|
148
|
+
}
|
|
149
|
+
const parent = dirname(directory)
|
|
150
|
+
if (parent === directory || directory === parse(directory).root) return undefined
|
|
151
|
+
directory = parent
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const pnpmCheck = (): DoctorCheck => {
|
|
156
|
+
const workspace = workspaceFrom(process.cwd())
|
|
157
|
+
if (workspace === undefined) {
|
|
158
|
+
return check('workspace.pnpm', 'pass', 'pnpm is not required for this installed CLI')
|
|
159
|
+
}
|
|
160
|
+
const result = spawnSync('pnpm', ['--version'], {
|
|
161
|
+
encoding: 'utf8',
|
|
162
|
+
timeout: 3_000,
|
|
163
|
+
stdio: ['ignore', 'pipe', 'ignore']
|
|
164
|
+
})
|
|
165
|
+
const actual = result.status === 0 ? numericVersion(result.stdout.trim()) : undefined
|
|
166
|
+
const pinned = workspace.packageManager.startsWith('pnpm@')
|
|
167
|
+
const required = pinned ? numericVersion(workspace.packageManager.slice('pnpm@'.length)) : undefined
|
|
168
|
+
if (actual === undefined) {
|
|
169
|
+
return check('workspace.pnpm', 'fail', 'This workspace requires pnpm, but pnpm is unavailable',
|
|
170
|
+
'Enable Corepack or install the pnpm version declared by the workspace.')
|
|
171
|
+
}
|
|
172
|
+
if (required !== undefined && !atLeast(actual, required)) {
|
|
173
|
+
return check('workspace.pnpm', 'fail', `pnpm ${actual} is older than the workspace requirement`,
|
|
174
|
+
`Enable Corepack or install pnpm ${required}.`)
|
|
175
|
+
}
|
|
176
|
+
return check('workspace.pnpm', 'pass', `pnpm ${actual} satisfies the workspace requirement`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
type DatabaseConfiguration = DoctorReport['configuration']['database']
|
|
180
|
+
|
|
181
|
+
const inspectDatabase = (path: string, source: DatabaseConfiguration['source']): {
|
|
182
|
+
readonly configuration: DatabaseConfiguration
|
|
183
|
+
readonly checks: readonly DoctorCheck[]
|
|
184
|
+
} => {
|
|
185
|
+
const memory = path === ':memory:'
|
|
186
|
+
const exists = memory || existsSync(path)
|
|
187
|
+
const base: DatabaseConfiguration = { source, kind: memory ? 'memory' : 'file', exists }
|
|
188
|
+
if (!exists) {
|
|
189
|
+
return {
|
|
190
|
+
configuration: base,
|
|
191
|
+
checks: [check('store.database', 'warn', 'The configured database does not exist yet',
|
|
192
|
+
'Run cave add to create it, or pass --db for an existing store.')]
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
let db: SqliteDatabase | undefined
|
|
197
|
+
let observed = base
|
|
198
|
+
try {
|
|
199
|
+
db = nodeSqliteAdapter.open(path, { readOnly: true })
|
|
200
|
+
const version = db.prepare('PRAGMA user_version').get()?.['user_version']
|
|
201
|
+
if (typeof version !== 'number') throw new Error('invalid version')
|
|
202
|
+
const caveObjects = db.prepare(
|
|
203
|
+
`SELECT count(*) AS count FROM sqlite_schema WHERE name LIKE 'cave_%' OR name LIKE 'idx_cave_%'`
|
|
204
|
+
).get()?.['count']
|
|
205
|
+
const configuration = { ...base, schemaVersion: version }
|
|
206
|
+
observed = configuration
|
|
207
|
+
|
|
208
|
+
if (version > Schema.currentVersion) {
|
|
209
|
+
return {
|
|
210
|
+
configuration,
|
|
211
|
+
checks: [check('store.database', 'fail',
|
|
212
|
+
`Database schema ${version} is newer than supported schema ${Schema.currentVersion}`,
|
|
213
|
+
'Upgrade CAVE before opening this database.')]
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (version === 0 && caveObjects === 0) {
|
|
217
|
+
return {
|
|
218
|
+
configuration,
|
|
219
|
+
checks: [check('store.database', 'warn', 'The database is readable but is not initialized as a CAVE store',
|
|
220
|
+
'Run cave add to initialize it; cave doctor made no changes.')]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (version < Schema.currentVersion) {
|
|
224
|
+
return {
|
|
225
|
+
configuration,
|
|
226
|
+
checks: [check('store.database', 'warn',
|
|
227
|
+
`Database schema ${version} needs migration to schema ${Schema.currentVersion}`,
|
|
228
|
+
'Back up the database, then open it with a normal cave command to migrate it.')]
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
Schema.validate(db, version)
|
|
233
|
+
const integrity = db.prepare('PRAGMA integrity_check').all()
|
|
234
|
+
if (integrity.length !== 1 || integrity[0]?.['integrity_check'] !== 'ok') {
|
|
235
|
+
return {
|
|
236
|
+
configuration,
|
|
237
|
+
checks: [check('store.integrity', 'fail', 'SQLite integrity checking failed',
|
|
238
|
+
'Restore a verified backup or recover the database with SQLite tooling.')]
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const foreignKeys = db.prepare('PRAGMA foreign_key_check').all()
|
|
242
|
+
if (foreignKeys.length > 0) {
|
|
243
|
+
return {
|
|
244
|
+
configuration,
|
|
245
|
+
checks: [check('store.integrity', 'fail', 'The store contains broken foreign-key references',
|
|
246
|
+
'Restore a verified backup or repair the affected database rows.')]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
const claims = db.prepare('SELECT count(*) AS count FROM cave_claim').get()?.['count']
|
|
250
|
+
if (typeof claims !== 'number') throw new Error('invalid count')
|
|
251
|
+
return {
|
|
252
|
+
configuration: { ...configuration, claims },
|
|
253
|
+
checks: [
|
|
254
|
+
check('store.database', 'pass', `CAVE schema ${version} is compatible (${claims} claim(s))`),
|
|
255
|
+
check('store.integrity', 'pass', 'SQLite integrity and foreign-key checks passed')
|
|
256
|
+
]
|
|
257
|
+
}
|
|
258
|
+
} catch {
|
|
259
|
+
return {
|
|
260
|
+
configuration: observed,
|
|
261
|
+
checks: [check('store.database', 'fail', 'The configured database is unreadable or has an incompatible schema',
|
|
262
|
+
'Check file permissions, restore a verified backup, or pass --db for another store.')]
|
|
263
|
+
}
|
|
264
|
+
} finally {
|
|
265
|
+
db?.close()
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
type HooksConfiguration = DoctorReport['configuration']['hooks']
|
|
270
|
+
|
|
271
|
+
const inspectHooks = (path: string | undefined): {
|
|
272
|
+
readonly configuration: HooksConfiguration
|
|
273
|
+
readonly check: DoctorCheck
|
|
274
|
+
} => {
|
|
275
|
+
if (path === undefined) {
|
|
276
|
+
return {
|
|
277
|
+
configuration: { source: 'none', exists: false },
|
|
278
|
+
check: check('config.hooks', 'pass', 'No optional hooks file was configured')
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const configuration = { source: 'flag' as const, exists: existsSync(path) }
|
|
282
|
+
if (!configuration.exists) {
|
|
283
|
+
return {
|
|
284
|
+
configuration,
|
|
285
|
+
check: check('config.hooks', 'fail', 'The configured hooks file does not exist',
|
|
286
|
+
'Create a JSON object of hook names to shell command strings, or omit --hooks.')
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
try {
|
|
290
|
+
const parsed: unknown = JSON.parse(readFileSync(path, 'utf8'))
|
|
291
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) throw new Error('invalid')
|
|
292
|
+
const entries = Object.entries(parsed)
|
|
293
|
+
if (entries.some(([name, command]) => name.trim() === '' || typeof command !== 'string')) {
|
|
294
|
+
throw new Error('invalid')
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
configuration: { ...configuration, entries: entries.length },
|
|
298
|
+
check: check('config.hooks', 'pass', `Hooks configuration is valid (${entries.length} entr${entries.length === 1 ? 'y' : 'ies'})`)
|
|
299
|
+
}
|
|
300
|
+
} catch {
|
|
301
|
+
return {
|
|
302
|
+
configuration,
|
|
303
|
+
check: check('config.hooks', 'fail', 'The configured hooks file is unreadable or malformed',
|
|
304
|
+
'Use a JSON object whose values are shell command strings.')
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
export const diagnose = (options: { readonly db?: string, readonly hooks?: string } = {}): DoctorReport => {
|
|
310
|
+
const databasePath = options.db ?? defaultDbPath()
|
|
311
|
+
const databaseSource: DatabaseConfiguration['source'] = options.db !== undefined ? 'flag' :
|
|
312
|
+
process.env['CAVE_DB'] !== undefined ? 'environment' : 'default'
|
|
313
|
+
const database = inspectDatabase(databasePath, databaseSource)
|
|
314
|
+
const hooks = inspectHooks(options.hooks)
|
|
315
|
+
const checks = [
|
|
316
|
+
nodeCheck(),
|
|
317
|
+
sqliteCheck(),
|
|
318
|
+
grammarCheck(),
|
|
319
|
+
pnpmCheck(),
|
|
320
|
+
...database.checks,
|
|
321
|
+
hooks.check
|
|
322
|
+
]
|
|
323
|
+
return {
|
|
324
|
+
format: 'cave.doctor',
|
|
325
|
+
version: 1,
|
|
326
|
+
ok: checks.every(entry => entry.status !== 'fail'),
|
|
327
|
+
caveVersion: Version.current(),
|
|
328
|
+
runtime: { node: process.versions.node, platform: process.platform, arch: process.arch },
|
|
329
|
+
configuration: { database: database.configuration, hooks: hooks.configuration },
|
|
330
|
+
checks
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const render = (report: DoctorReport): string => {
|
|
335
|
+
const lines = [`cave doctor ${report.caveVersion}`]
|
|
336
|
+
for (const entry of report.checks) {
|
|
337
|
+
lines.push(`${entry.status.toUpperCase().padEnd(4)} ${entry.summary}`)
|
|
338
|
+
if (entry.remediation !== undefined) lines.push(` ${entry.remediation}`)
|
|
339
|
+
}
|
|
340
|
+
const failures = report.checks.filter(entry => entry.status === 'fail').length
|
|
341
|
+
const warnings = report.checks.filter(entry => entry.status === 'warn').length
|
|
342
|
+
lines.push(report.ok ?
|
|
343
|
+
`result: ready${warnings === 0 ? '' : ` with ${warnings} warning(s)`}` :
|
|
344
|
+
`result: ${failures} problem(s), ${warnings} warning(s)`)
|
|
345
|
+
return `${lines.join('\n')}\n`
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export const doctorCommand = (argv: readonly string[]): DoctorOutput => {
|
|
349
|
+
const { values, positionals } = parseArgs({
|
|
350
|
+
args: [...argv],
|
|
351
|
+
options: {
|
|
352
|
+
db: { type: 'string' },
|
|
353
|
+
hooks: { type: 'string' },
|
|
354
|
+
json: { type: 'boolean' }
|
|
355
|
+
},
|
|
356
|
+
allowPositionals: true
|
|
357
|
+
})
|
|
358
|
+
if (positionals.length > 0) {
|
|
359
|
+
return { code: 2, out: '', err: 'cave doctor: unexpected positional arguments\n' }
|
|
360
|
+
}
|
|
361
|
+
const report = diagnose({
|
|
362
|
+
...values.db === undefined ? {} : { db: values.db },
|
|
363
|
+
...values.hooks === undefined ? {} : { hooks: values.hooks }
|
|
364
|
+
})
|
|
365
|
+
return {
|
|
366
|
+
code: report.ok ? 0 : 1,
|
|
367
|
+
out: values.json === true ? `${JSON.stringify(report, undefined, 2)}\n` : render(report),
|
|
368
|
+
err: ''
|
|
369
|
+
}
|
|
370
|
+
}
|
package/src/eval.ts
ADDED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@cavelang/cli` — the `cave` command (parse / add / query / resolve / derive / act / check / suggest-alias / sync / export / report / reconstruct / demo).
|
|
3
|
-
* Programmatic access to the same implementations lives here
|
|
3
|
+
* Programmatic access to the same implementations lives here; `dispatch`
|
|
4
|
+
* and `runCli` expose the shared awaited command lifecycle.
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
export { actCommand, addCommand, cave, checkCommand, commandHelp, demoCommand, deriveCommand, exportCommand, helpCommand, highlightCommand, importCommand, parseCommand, queryCommand, reconstructCommand, reportCommand, resolveCommand, 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, suggestAliasCommand, syncCommand, usage, versionCommand } from './cli.ts'
|
|
8
|
+
export { dispatch, runCli } from './dispatch.ts'
|
|
9
|
+
export type { CommandRuntime } from './dispatch.ts'
|
|
10
|
+
export { commandRegistry } from './commands.ts'
|
|
11
|
+
export type { CommandReference } from './commands.ts'
|
|
7
12
|
export type { Output } from './cli.ts'
|
|
13
|
+
export { diagnose, doctorCommand } from './doctor.ts'
|
|
14
|
+
export type { DoctorCheck, DoctorOutput, DoctorReport, DoctorStatus } from './doctor.ts'
|
package/src/ingest.ts
ADDED
package/src/loop.ts
ADDED
package/src/main.ts
CHANGED
|
@@ -1,69 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/** `cave` binary entry point —
|
|
2
|
+
/** `cave` binary entry point — one lifecycle for every command. */
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { runCli } from './dispatch.ts'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
if (out !== '') {
|
|
8
|
-
process.stdout.write(out)
|
|
9
|
-
}
|
|
10
|
-
if (err !== '') {
|
|
11
|
-
process.stderr.write(err)
|
|
12
|
-
}
|
|
13
|
-
process.exitCode = code
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
// mcp, ingest, connect, eval, automate and serve own their help text, so `cave help X` becomes `cave X --help`.
|
|
17
|
-
const raw = process.argv.slice(2)
|
|
18
|
-
const argv = raw[0] === 'help' && (raw[1] === 'mcp' || raw[1] === 'ingest' || raw[1] === 'connect' || raw[1] === 'eval' || raw[1] === 'automate' || raw[1] === 'serve') ?
|
|
19
|
-
[raw[1], '--help'] :
|
|
20
|
-
raw
|
|
21
|
-
if (argv[0] === 'mcp') {
|
|
22
|
-
// Long-running: serves MCP on stdio until the client disconnects.
|
|
23
|
-
const { runMcp } = await import('@cavelang/mcp')
|
|
24
|
-
process.exitCode = await runMcp(argv.slice(1))
|
|
25
|
-
} else if (argv[0] === 'ingest') {
|
|
26
|
-
// Long-running: drives an LLM agent over batches of files.
|
|
27
|
-
const { runIngest } = await import('@cavelang/ingest')
|
|
28
|
-
process.exitCode = await runIngest(argv.slice(1))
|
|
29
|
-
} else if (argv[0] === 'eval') {
|
|
30
|
-
// Long-running: drives an agent (and optionally a judge) over eval fixtures.
|
|
31
|
-
const { runEval } = await import('@cavelang/eval')
|
|
32
|
-
process.exitCode = await runEval(argv.slice(1))
|
|
33
|
-
} else if (argv[0] === 'connect') {
|
|
34
|
-
// Async (URL sources fetch) and potentially long-running (--watch).
|
|
35
|
-
const { runConnect } = await import('@cavelang/connect')
|
|
36
|
-
process.exitCode = await runConnect(argv.slice(1))
|
|
37
|
-
} else if (argv[0] === 'automate') {
|
|
38
|
-
// Async (prompt steps run a shell agent) and potentially long-running (the loop).
|
|
39
|
-
const { runAutomate } = await import('@cavelang/automate')
|
|
40
|
-
process.exitCode = await runAutomate(argv.slice(1))
|
|
41
|
-
} else if (argv[0] === 'serve') {
|
|
42
|
-
// Long-running: the human read surface serves HTTP until interrupted.
|
|
43
|
-
const { runServe } = await import('@cavelang/view')
|
|
44
|
-
process.exitCode = await runServe(argv.slice(1))
|
|
45
|
-
} else if (argv[0] === 'reconstruct') {
|
|
46
|
-
// Async: the LLM policy runs a shell agent once per step.
|
|
47
|
-
const { code, out, err } = await reconstructCommand(argv.slice(1))
|
|
48
|
-
write(code, out, err)
|
|
49
|
-
} else if (argv[0] === 'suggest-alias') {
|
|
50
|
-
// Async: the optional judge runs a shell agent.
|
|
51
|
-
const { code, out, err } = await suggestAliasCommand(argv.slice(1))
|
|
52
|
-
write(code, out, err)
|
|
53
|
-
} else if (argv[0] === 'highlight' && !argv.includes('--help') && !argv.includes('-h')) {
|
|
54
|
-
// Async (grammar WASM loads on first use); --help stays on the sync path.
|
|
55
|
-
const { code, out, err } = await highlightCommand(argv.slice(1))
|
|
56
|
-
write(code, out, err)
|
|
57
|
-
} else {
|
|
58
|
-
const { code, out, err } = cave(argv)
|
|
59
|
-
// Canonical CAVE text going to a terminal gets syntax colors from the same
|
|
60
|
-
// grammar query `cave highlight` uses; piping or NO_COLOR keeps bytes clean.
|
|
61
|
-
const colorize = code === 0 && argv[0] === 'export' && !argv.includes('--out') &&
|
|
62
|
-
out !== '' && process.stdout.isTTY === true && process.env['NO_COLOR'] === undefined
|
|
63
|
-
if (colorize) {
|
|
64
|
-
const { highlighter } = await import('@cavelang/highlight')
|
|
65
|
-
write(code, (await highlighter()).ansi(out), err)
|
|
66
|
-
} else {
|
|
67
|
-
write(code, out, err)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
6
|
+
process.exitCode = await runCli(process.argv.slice(2))
|
package/src/mcp.ts
ADDED
package/src/rules.ts
ADDED