@graphorin/cli 0.6.1 → 0.7.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/CHANGELOG.md +63 -0
- package/README.md +3 -3
- package/dist/bin/graphorin.js +51 -13
- package/dist/bin/graphorin.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +2 -1
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/consolidator.d.ts +56 -1
- package/dist/commands/consolidator.d.ts.map +1 -1
- package/dist/commands/consolidator.js +88 -2
- package/dist/commands/consolidator.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/index.d.ts +4 -4
- package/dist/commands/index.js +4 -4
- package/dist/commands/init.d.ts +11 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +15 -11
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/memory.d.ts +26 -1
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +56 -3
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/pricing.d.ts +6 -0
- package/dist/commands/pricing.d.ts.map +1 -1
- package/dist/commands/pricing.js +5 -2
- package/dist/commands/pricing.js.map +1 -1
- package/dist/commands/secrets.d.ts.map +1 -1
- package/dist/commands/secrets.js +2 -2
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/skills.d.ts.map +1 -1
- package/dist/commands/skills.js +1 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/storage.d.ts +41 -1
- package/dist/commands/storage.d.ts.map +1 -1
- package/dist/commands/storage.js +75 -1
- package/dist/commands/storage.js.map +1 -1
- package/dist/commands/token.d.ts.map +1 -1
- package/dist/commands/token.js +2 -2
- package/dist/commands/token.js.map +1 -1
- package/dist/commands/tools-lint.js +1 -1
- package/dist/commands/tools-lint.js.map +1 -1
- package/dist/commands/traces.d.ts +14 -2
- package/dist/commands/traces.d.ts.map +1 -1
- package/dist/commands/traces.js +39 -22
- package/dist/commands/traces.js.map +1 -1
- package/dist/commands/triggers.d.ts.map +1 -1
- package/dist/commands/triggers.js +5 -2
- package/dist/commands/triggers.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -5
- package/dist/index.js.map +1 -1
- package/dist/internal/output.js +6 -0
- package/dist/internal/output.js.map +1 -1
- package/dist/internal/store-context.js +13 -2
- package/dist/internal/store-context.js.map +1 -1
- package/dist/package.js +1 -1
- package/dist/package.js.map +1 -1
- package/package.json +18 -14
- package/src/bin/graphorin.ts +1387 -0
- package/src/commands/audit.ts +256 -0
- package/src/commands/auth.ts +238 -0
- package/src/commands/consolidator.ts +382 -0
- package/src/commands/doctor.ts +253 -0
- package/src/commands/guard.ts +144 -0
- package/src/commands/index.ts +223 -0
- package/src/commands/init.ts +194 -0
- package/src/commands/memory.ts +1052 -0
- package/src/commands/migrate-config.ts +77 -0
- package/src/commands/migrate-export.ts +117 -0
- package/src/commands/migrate.ts +83 -0
- package/src/commands/pricing.ts +244 -0
- package/src/commands/secrets.ts +309 -0
- package/src/commands/skills.ts +272 -0
- package/src/commands/start.ts +180 -0
- package/src/commands/storage.ts +659 -0
- package/src/commands/telemetry.ts +91 -0
- package/src/commands/token.ts +361 -0
- package/src/commands/tools-lint.ts +430 -0
- package/src/commands/traces.ts +188 -0
- package/src/commands/triggers.ts +237 -0
- package/src/index.ts +30 -0
- package/src/internal/exit.ts +62 -0
- package/src/internal/load-config.ts +107 -0
- package/src/internal/offline.ts +81 -0
- package/src/internal/output.ts +146 -0
- package/src/internal/prompts.ts +58 -0
- package/src/internal/store-context.ts +165 -0
|
@@ -0,0 +1,1387 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `graphorin` CLI entry point. Built into `dist/bin/graphorin.js`
|
|
3
|
+
* with a shebang prepended by `tsdown`; the `package.json` `bin`
|
|
4
|
+
* field points npm at the compiled file.
|
|
5
|
+
*
|
|
6
|
+
* Phase 14a installs three subcommands grouped by lifecycle phase:
|
|
7
|
+
*
|
|
8
|
+
* Bootstrap:
|
|
9
|
+
* - graphorin init
|
|
10
|
+
*
|
|
11
|
+
* Maintenance:
|
|
12
|
+
* - graphorin migrate
|
|
13
|
+
*
|
|
14
|
+
* Runtime:
|
|
15
|
+
* - graphorin start
|
|
16
|
+
*
|
|
17
|
+
* Phase 15 extends this binary with the full operator surface
|
|
18
|
+
* (`doctor`, `token`, `secrets`, `audit`, `storage`, `memory`,
|
|
19
|
+
* `consolidator`, `triggers`, `auth`, `pricing`, `skills`, `traces`,
|
|
20
|
+
* `migrate-export`, `migrate-config`, `guard`, `telemetry`,
|
|
21
|
+
* `tools lint`).
|
|
22
|
+
*
|
|
23
|
+
* `GRAPHORIN_OFFLINE=1` is honoured by every subcommand - the v0.1
|
|
24
|
+
* surface never makes implicit network calls. Phase 15 commands that
|
|
25
|
+
* do touch the network (e.g. `graphorin pricing refresh`) consult
|
|
26
|
+
* the same flag through the helper in `../internal/offline.ts`.
|
|
27
|
+
*
|
|
28
|
+
* @packageDocumentation
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import process from 'node:process';
|
|
32
|
+
|
|
33
|
+
import { Command } from 'commander';
|
|
34
|
+
|
|
35
|
+
import {
|
|
36
|
+
runAuditExport,
|
|
37
|
+
runAuditPrune,
|
|
38
|
+
runAuditVerify,
|
|
39
|
+
runAuthList,
|
|
40
|
+
runAuthLogin,
|
|
41
|
+
runAuthRefresh,
|
|
42
|
+
runAuthRevoke,
|
|
43
|
+
runAuthStatus,
|
|
44
|
+
runConsolidatorDlqClear,
|
|
45
|
+
runConsolidatorDlqList,
|
|
46
|
+
runConsolidatorSetTier,
|
|
47
|
+
runConsolidatorStatus,
|
|
48
|
+
runConsolidatorStop,
|
|
49
|
+
runDoctor,
|
|
50
|
+
runGuardExplain,
|
|
51
|
+
runGuardStatus,
|
|
52
|
+
runInit,
|
|
53
|
+
runMemoryActivity,
|
|
54
|
+
runMemoryInspect,
|
|
55
|
+
runMemoryMigrate,
|
|
56
|
+
runMemoryPruneHistory,
|
|
57
|
+
runMemoryReview,
|
|
58
|
+
runMemoryStatus,
|
|
59
|
+
runMemoryWhy,
|
|
60
|
+
runMigrate,
|
|
61
|
+
runMigrateConfig,
|
|
62
|
+
runMigrateExport,
|
|
63
|
+
runPricingDiff,
|
|
64
|
+
runPricingLookup,
|
|
65
|
+
runPricingMissing,
|
|
66
|
+
runPricingRefresh,
|
|
67
|
+
runPricingStatus,
|
|
68
|
+
runSecretsDelete,
|
|
69
|
+
runSecretsGet,
|
|
70
|
+
runSecretsList,
|
|
71
|
+
runSecretsRef,
|
|
72
|
+
runSecretsRotate,
|
|
73
|
+
runSecretsSet,
|
|
74
|
+
runSkillsAudit,
|
|
75
|
+
runSkillsInspect,
|
|
76
|
+
runSkillsInstall,
|
|
77
|
+
runSkillsMigrateFrontmatter,
|
|
78
|
+
runStart,
|
|
79
|
+
runStorageBackup,
|
|
80
|
+
runStorageCleanupBackups,
|
|
81
|
+
runStorageCompact,
|
|
82
|
+
runStorageEncrypt,
|
|
83
|
+
runStorageRekey,
|
|
84
|
+
runStorageStatus,
|
|
85
|
+
runTelemetryDisable,
|
|
86
|
+
runTelemetryEnable,
|
|
87
|
+
runTelemetryInspect,
|
|
88
|
+
runTelemetryStatus,
|
|
89
|
+
runTokenCreate,
|
|
90
|
+
runTokenList,
|
|
91
|
+
runTokenRekey,
|
|
92
|
+
runTokenRevoke,
|
|
93
|
+
runTokenRotate,
|
|
94
|
+
runTokenVerify,
|
|
95
|
+
runToolsLint,
|
|
96
|
+
runTracesPrune,
|
|
97
|
+
runTracesStatus,
|
|
98
|
+
runTriggersDisable,
|
|
99
|
+
runTriggersFire,
|
|
100
|
+
runTriggersList,
|
|
101
|
+
runTriggersPrune,
|
|
102
|
+
runTriggersStatus,
|
|
103
|
+
VERSION,
|
|
104
|
+
} from '../index.js';
|
|
105
|
+
import { isOfflineMode } from '../internal/offline.js';
|
|
106
|
+
|
|
107
|
+
async function main(): Promise<void> {
|
|
108
|
+
const program = new Command();
|
|
109
|
+
program
|
|
110
|
+
.name('graphorin')
|
|
111
|
+
.description(
|
|
112
|
+
[
|
|
113
|
+
'Operator CLI for the Graphorin framework.',
|
|
114
|
+
'',
|
|
115
|
+
'Commands grouped by purpose:',
|
|
116
|
+
' Bootstrap: init, migrate, migrate-config',
|
|
117
|
+
' Runtime: start',
|
|
118
|
+
' Diagnostics: doctor, telemetry, traces, guard',
|
|
119
|
+
' Auth: token, secrets, auth',
|
|
120
|
+
' Storage: storage, audit, memory, consolidator, triggers, migrate-export',
|
|
121
|
+
' Catalogue: pricing, skills, tools',
|
|
122
|
+
'',
|
|
123
|
+
'Honours GRAPHORIN_OFFLINE=1 - the v0.1 surface never makes implicit network calls.',
|
|
124
|
+
].join('\n'),
|
|
125
|
+
)
|
|
126
|
+
.version(VERSION);
|
|
127
|
+
|
|
128
|
+
registerLifecycleCommands(program);
|
|
129
|
+
registerDoctorCommand(program);
|
|
130
|
+
registerTokenCommands(program);
|
|
131
|
+
registerSecretsCommands(program);
|
|
132
|
+
registerStorageCommands(program);
|
|
133
|
+
registerAuditCommands(program);
|
|
134
|
+
registerMemoryCommands(program);
|
|
135
|
+
registerConsolidatorCommands(program);
|
|
136
|
+
registerTriggersCommands(program);
|
|
137
|
+
registerAuthCommands(program);
|
|
138
|
+
registerPricingCommands(program);
|
|
139
|
+
registerSkillsCommands(program);
|
|
140
|
+
registerTracesCommands(program);
|
|
141
|
+
registerMigrateExportCommand(program);
|
|
142
|
+
registerMigrateConfigCommand(program);
|
|
143
|
+
registerGuardCommands(program);
|
|
144
|
+
registerTelemetryCommands(program);
|
|
145
|
+
registerToolsCommands(program);
|
|
146
|
+
|
|
147
|
+
await program.parseAsync(process.argv);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function registerLifecycleCommands(program: Command): void {
|
|
151
|
+
program
|
|
152
|
+
.command('start')
|
|
153
|
+
.description('[Runtime] Start the @graphorin/server daemon.')
|
|
154
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file (TS / JS / JSON).')
|
|
155
|
+
.option('-h, --host <host>', 'Override the configured listen host.')
|
|
156
|
+
.option('-p, --port <port>', 'Override the configured listen port.', (value) =>
|
|
157
|
+
Number.parseInt(value, 10),
|
|
158
|
+
)
|
|
159
|
+
.option(
|
|
160
|
+
'--secrets-source <kind>',
|
|
161
|
+
'Override secrets.source: auto | keyring | encrypted-file | env (DEC-136).',
|
|
162
|
+
)
|
|
163
|
+
.option('--strict-secrets', 'Refuse to fall back to a different secrets store (DEC-136).')
|
|
164
|
+
.action(
|
|
165
|
+
async (opts: {
|
|
166
|
+
config?: string;
|
|
167
|
+
host?: string;
|
|
168
|
+
port?: number;
|
|
169
|
+
secretsSource?: 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
170
|
+
strictSecrets?: boolean;
|
|
171
|
+
}) => {
|
|
172
|
+
if (isOfflineMode()) {
|
|
173
|
+
process.stderr.write(
|
|
174
|
+
'[graphorin/cli] GRAPHORIN_OFFLINE=1 - running with no implicit network calls.\n',
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
await runStart({
|
|
178
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
179
|
+
...(opts.host !== undefined ? { host: opts.host } : {}),
|
|
180
|
+
...(opts.port !== undefined ? { port: opts.port } : {}),
|
|
181
|
+
...(opts.secretsSource !== undefined ? { secretsSource: opts.secretsSource } : {}),
|
|
182
|
+
...(opts.strictSecrets !== undefined ? { strictSecrets: opts.strictSecrets } : {}),
|
|
183
|
+
});
|
|
184
|
+
},
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
program
|
|
188
|
+
.command('init')
|
|
189
|
+
.description(
|
|
190
|
+
'[Bootstrap] Generate a fresh graphorin.config.ts + the server pepper (mint tokens afterwards with token create).',
|
|
191
|
+
)
|
|
192
|
+
.option('-o, --out <path>', 'Output path; defaults to ./graphorin.config.ts.')
|
|
193
|
+
.option('--non-interactive', 'Accept defaults / env vars without prompting.', false)
|
|
194
|
+
.option(
|
|
195
|
+
'--cloud-consent <tier>',
|
|
196
|
+
'Cloud-upload consent tier: public-only | public-and-internal | all-with-warnings.',
|
|
197
|
+
)
|
|
198
|
+
.option('--encrypted', 'Enable storage encryption opt-in.')
|
|
199
|
+
.option('--no-encrypted', 'Disable storage encryption opt-in.')
|
|
200
|
+
.action(
|
|
201
|
+
async (opts: {
|
|
202
|
+
out?: string;
|
|
203
|
+
nonInteractive?: boolean;
|
|
204
|
+
cloudConsent?: 'public-only' | 'public-and-internal' | 'all-with-warnings';
|
|
205
|
+
encrypted?: boolean;
|
|
206
|
+
}) => {
|
|
207
|
+
if (isOfflineMode()) {
|
|
208
|
+
process.stderr.write(
|
|
209
|
+
'[graphorin/cli] GRAPHORIN_OFFLINE=1 - init never reaches the network.\n',
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
await runInit({
|
|
213
|
+
...(opts.out !== undefined ? { out: opts.out } : {}),
|
|
214
|
+
...(opts.nonInteractive !== undefined ? { nonInteractive: opts.nonInteractive } : {}),
|
|
215
|
+
...(opts.cloudConsent !== undefined ? { cloudConsent: opts.cloudConsent } : {}),
|
|
216
|
+
...(opts.encrypted !== undefined ? { encrypted: opts.encrypted } : {}),
|
|
217
|
+
});
|
|
218
|
+
},
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
program
|
|
222
|
+
.command('migrate')
|
|
223
|
+
.description(
|
|
224
|
+
'[Maintenance] Apply pending storage migrations against the configured SQLite store.',
|
|
225
|
+
)
|
|
226
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
227
|
+
.option('--target <version>', 'Reserved for the Phase 15 partial-migration runner.')
|
|
228
|
+
.action(async (opts: { config?: string; target?: string }) => {
|
|
229
|
+
if (isOfflineMode()) {
|
|
230
|
+
process.stderr.write(
|
|
231
|
+
'[graphorin/cli] GRAPHORIN_OFFLINE=1 - migrate is a local-only operation.\n',
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
await runMigrate({
|
|
235
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
236
|
+
...(opts.target !== undefined ? { target: opts.target } : {}),
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function registerDoctorCommand(program: Command): void {
|
|
242
|
+
program
|
|
243
|
+
.command('doctor')
|
|
244
|
+
.description('[Diagnostics] Health check (perms + secrets + encryption + systemd).')
|
|
245
|
+
.option('--fix-perms', 'Repair drifted POSIX file modes.')
|
|
246
|
+
.option('--check-perms', 'Run the file-perms check.')
|
|
247
|
+
.option('--check-secrets', 'Run the secrets-store check.')
|
|
248
|
+
.option('--check-encryption', 'Run the audit-encryption check.')
|
|
249
|
+
.option('--check-systemd', 'Run the systemd-hardening check (Linux only).')
|
|
250
|
+
.option('--all', 'Run every check.')
|
|
251
|
+
.option('--json', 'Emit a structured JSON report on stdout.')
|
|
252
|
+
.action(
|
|
253
|
+
async (opts: {
|
|
254
|
+
fixPerms?: boolean;
|
|
255
|
+
checkPerms?: boolean;
|
|
256
|
+
checkSecrets?: boolean;
|
|
257
|
+
checkEncryption?: boolean;
|
|
258
|
+
checkSystemd?: boolean;
|
|
259
|
+
all?: boolean;
|
|
260
|
+
json?: boolean;
|
|
261
|
+
}) => {
|
|
262
|
+
await runDoctor({
|
|
263
|
+
...(opts.fixPerms !== undefined ? { fixPerms: opts.fixPerms } : {}),
|
|
264
|
+
...(opts.checkPerms !== undefined ? { checkPerms: opts.checkPerms } : {}),
|
|
265
|
+
...(opts.checkSecrets !== undefined ? { checkSecrets: opts.checkSecrets } : {}),
|
|
266
|
+
...(opts.checkEncryption !== undefined ? { checkEncryption: opts.checkEncryption } : {}),
|
|
267
|
+
...(opts.checkSystemd !== undefined ? { checkSystemd: opts.checkSystemd } : {}),
|
|
268
|
+
...(opts.all !== undefined ? { all: opts.all } : {}),
|
|
269
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function registerTokenCommands(program: Command): void {
|
|
276
|
+
const token = program.command('token').description('[Auth] Manage server auth tokens.');
|
|
277
|
+
token
|
|
278
|
+
.command('create')
|
|
279
|
+
.description('Mint a new token. Prints the raw value once.')
|
|
280
|
+
.requiredOption('--scopes <list>', 'Comma-separated scope list.')
|
|
281
|
+
.option('--label <name>', 'Optional label.')
|
|
282
|
+
.option('--expires-in <duration>', 'Duration string: 30d, 12h, 90m, 45s.')
|
|
283
|
+
.option('--env <env>', 'Token environment: live | test.', 'live')
|
|
284
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
285
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
286
|
+
.action(
|
|
287
|
+
async (opts: {
|
|
288
|
+
scopes: string;
|
|
289
|
+
label?: string;
|
|
290
|
+
expiresIn?: string;
|
|
291
|
+
env?: 'live' | 'test';
|
|
292
|
+
config?: string;
|
|
293
|
+
json?: boolean;
|
|
294
|
+
}) => {
|
|
295
|
+
await runTokenCreate({
|
|
296
|
+
scopes: opts.scopes
|
|
297
|
+
.split(',')
|
|
298
|
+
.map((s) => s.trim())
|
|
299
|
+
.filter(Boolean),
|
|
300
|
+
...(opts.label !== undefined ? { label: opts.label } : {}),
|
|
301
|
+
...(opts.expiresIn !== undefined ? { expiresIn: opts.expiresIn } : {}),
|
|
302
|
+
...(opts.env !== undefined ? { env: opts.env } : {}),
|
|
303
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
304
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
305
|
+
});
|
|
306
|
+
},
|
|
307
|
+
);
|
|
308
|
+
token
|
|
309
|
+
.command('list')
|
|
310
|
+
.description('List token metadata.')
|
|
311
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
312
|
+
.option('--include-revoked', 'Include revoked tokens.')
|
|
313
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
314
|
+
.action(async (opts: { config?: string; includeRevoked?: boolean; json?: boolean }) => {
|
|
315
|
+
await runTokenList({
|
|
316
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
317
|
+
...(opts.includeRevoked !== undefined ? { includeRevoked: opts.includeRevoked } : {}),
|
|
318
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
token
|
|
322
|
+
.command('revoke <id>')
|
|
323
|
+
.description('Revoke a single token.')
|
|
324
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
325
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
326
|
+
.action(async (id: string, opts: { config?: string; json?: boolean }) => {
|
|
327
|
+
await runTokenRevoke({
|
|
328
|
+
id,
|
|
329
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
330
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
token
|
|
334
|
+
.command('rotate <id>')
|
|
335
|
+
.description('Revoke + reissue a token with the same scopes.')
|
|
336
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
337
|
+
.option('--env <env>', 'Token environment: live | test.', 'live')
|
|
338
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
339
|
+
.action(
|
|
340
|
+
async (id: string, opts: { config?: string; env?: 'live' | 'test'; json?: boolean }) => {
|
|
341
|
+
await runTokenRotate({
|
|
342
|
+
id,
|
|
343
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
344
|
+
...(opts.env !== undefined ? { env: opts.env } : {}),
|
|
345
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
346
|
+
});
|
|
347
|
+
},
|
|
348
|
+
);
|
|
349
|
+
token
|
|
350
|
+
.command('rekey')
|
|
351
|
+
.description('Re-issue every active token (use after a known compromise).')
|
|
352
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
353
|
+
.option('--env <env>', 'Token environment: live | test.', 'live')
|
|
354
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
355
|
+
.action(async (opts: { config?: string; env?: 'live' | 'test'; json?: boolean }) => {
|
|
356
|
+
await runTokenRekey({
|
|
357
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
358
|
+
...(opts.env !== undefined ? { env: opts.env } : {}),
|
|
359
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
token
|
|
363
|
+
.command('verify <token>')
|
|
364
|
+
.description('Offline checksum verification - never consults the store.')
|
|
365
|
+
.option('--prefix <prefix>', 'Override the expected token prefix.')
|
|
366
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
367
|
+
.action((tok: string, opts: { prefix?: string; json?: boolean }) => {
|
|
368
|
+
runTokenVerify({
|
|
369
|
+
token: tok,
|
|
370
|
+
...(opts.prefix !== undefined ? { prefix: opts.prefix } : {}),
|
|
371
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function registerSecretsCommands(program: Command): void {
|
|
377
|
+
const secrets = program
|
|
378
|
+
.command('secrets')
|
|
379
|
+
.description('[Auth] Manage secrets in the configured store.');
|
|
380
|
+
const commonOpts = (cmd: Command): Command =>
|
|
381
|
+
cmd
|
|
382
|
+
.option('--secrets-source <kind>', 'auto | keyring | encrypted-file | env (DEC-136).')
|
|
383
|
+
.option('--strict-secrets', 'Refuse to fall back to a different secrets store.')
|
|
384
|
+
.option('--json', 'Emit a structured JSON document on stdout.');
|
|
385
|
+
commonOpts(secrets.command('list').description('List secret metadata.')).action(
|
|
386
|
+
async (opts: {
|
|
387
|
+
secretsSource?: 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
388
|
+
strictSecrets?: boolean;
|
|
389
|
+
json?: boolean;
|
|
390
|
+
}) => {
|
|
391
|
+
await runSecretsList({
|
|
392
|
+
...(opts.secretsSource !== undefined ? { secretsSource: opts.secretsSource } : {}),
|
|
393
|
+
...(opts.strictSecrets !== undefined ? { strictSecrets: opts.strictSecrets } : {}),
|
|
394
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
395
|
+
});
|
|
396
|
+
},
|
|
397
|
+
);
|
|
398
|
+
commonOpts(
|
|
399
|
+
secrets
|
|
400
|
+
.command('get <key>')
|
|
401
|
+
.description('Read a secret. Use --reveal to print the bytes.')
|
|
402
|
+
.option('--reveal', 'Print the raw value.'),
|
|
403
|
+
).action(
|
|
404
|
+
async (
|
|
405
|
+
key: string,
|
|
406
|
+
opts: {
|
|
407
|
+
secretsSource?: 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
408
|
+
strictSecrets?: boolean;
|
|
409
|
+
json?: boolean;
|
|
410
|
+
reveal?: boolean;
|
|
411
|
+
},
|
|
412
|
+
) => {
|
|
413
|
+
await runSecretsGet({
|
|
414
|
+
key,
|
|
415
|
+
...(opts.secretsSource !== undefined ? { secretsSource: opts.secretsSource } : {}),
|
|
416
|
+
...(opts.strictSecrets !== undefined ? { strictSecrets: opts.strictSecrets } : {}),
|
|
417
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
418
|
+
...(opts.reveal !== undefined ? { reveal: opts.reveal } : {}),
|
|
419
|
+
});
|
|
420
|
+
},
|
|
421
|
+
);
|
|
422
|
+
commonOpts(
|
|
423
|
+
secrets
|
|
424
|
+
.command('set <key>')
|
|
425
|
+
.description('Persist a secret. Pass --value <v> or --from-stdin.')
|
|
426
|
+
.option('--value <v>', 'Inline value.')
|
|
427
|
+
.option('--from-stdin', 'Read the value from stdin.'),
|
|
428
|
+
).action(
|
|
429
|
+
async (
|
|
430
|
+
key: string,
|
|
431
|
+
opts: {
|
|
432
|
+
value?: string;
|
|
433
|
+
fromStdin?: boolean;
|
|
434
|
+
secretsSource?: 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
435
|
+
strictSecrets?: boolean;
|
|
436
|
+
json?: boolean;
|
|
437
|
+
},
|
|
438
|
+
) => {
|
|
439
|
+
await runSecretsSet({
|
|
440
|
+
key,
|
|
441
|
+
...(opts.value !== undefined ? { value: opts.value } : {}),
|
|
442
|
+
...(opts.fromStdin !== undefined ? { fromStdin: opts.fromStdin } : {}),
|
|
443
|
+
...(opts.secretsSource !== undefined ? { secretsSource: opts.secretsSource } : {}),
|
|
444
|
+
...(opts.strictSecrets !== undefined ? { strictSecrets: opts.strictSecrets } : {}),
|
|
445
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
446
|
+
});
|
|
447
|
+
},
|
|
448
|
+
);
|
|
449
|
+
commonOpts(secrets.command('delete <key>').description('Delete a secret.')).action(
|
|
450
|
+
async (
|
|
451
|
+
key: string,
|
|
452
|
+
opts: {
|
|
453
|
+
secretsSource?: 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
454
|
+
strictSecrets?: boolean;
|
|
455
|
+
json?: boolean;
|
|
456
|
+
},
|
|
457
|
+
) => {
|
|
458
|
+
await runSecretsDelete({
|
|
459
|
+
key,
|
|
460
|
+
...(opts.secretsSource !== undefined ? { secretsSource: opts.secretsSource } : {}),
|
|
461
|
+
...(opts.strictSecrets !== undefined ? { strictSecrets: opts.strictSecrets } : {}),
|
|
462
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
);
|
|
466
|
+
commonOpts(
|
|
467
|
+
secrets
|
|
468
|
+
.command('ref <uri>')
|
|
469
|
+
.description('Test resolution of a SecretRef URI.')
|
|
470
|
+
.option('--reveal', 'Include the resolved value in the report.'),
|
|
471
|
+
).action(async (uri: string, opts: { reveal?: boolean; json?: boolean }) => {
|
|
472
|
+
await runSecretsRef({
|
|
473
|
+
uri,
|
|
474
|
+
...(opts.reveal !== undefined ? { reveal: opts.reveal } : {}),
|
|
475
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
commonOpts(
|
|
479
|
+
secrets
|
|
480
|
+
.command('rotate <key>')
|
|
481
|
+
.description('Replace an existing secret with a fresh value.')
|
|
482
|
+
.option('--new-value <v>', 'Inline new value.')
|
|
483
|
+
.option('--from-stdin', 'Read the new value from stdin.'),
|
|
484
|
+
).action(
|
|
485
|
+
async (
|
|
486
|
+
key: string,
|
|
487
|
+
opts: {
|
|
488
|
+
newValue?: string;
|
|
489
|
+
fromStdin?: boolean;
|
|
490
|
+
secretsSource?: 'auto' | 'keyring' | 'encrypted-file' | 'env';
|
|
491
|
+
strictSecrets?: boolean;
|
|
492
|
+
json?: boolean;
|
|
493
|
+
},
|
|
494
|
+
) => {
|
|
495
|
+
await runSecretsRotate({
|
|
496
|
+
key,
|
|
497
|
+
...(opts.newValue !== undefined ? { newValue: opts.newValue } : {}),
|
|
498
|
+
...(opts.fromStdin !== undefined ? { fromStdin: opts.fromStdin } : {}),
|
|
499
|
+
...(opts.secretsSource !== undefined ? { secretsSource: opts.secretsSource } : {}),
|
|
500
|
+
...(opts.strictSecrets !== undefined ? { strictSecrets: opts.strictSecrets } : {}),
|
|
501
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
502
|
+
});
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function registerStorageCommands(program: Command): void {
|
|
508
|
+
const storage = program
|
|
509
|
+
.command('storage')
|
|
510
|
+
.description('[Storage] SQLite + encryption-at-rest commands.');
|
|
511
|
+
storage
|
|
512
|
+
.command('status')
|
|
513
|
+
.description('Report current store state + cipher peer availability.')
|
|
514
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
515
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
516
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
517
|
+
await runStorageStatus({
|
|
518
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
519
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
520
|
+
});
|
|
521
|
+
});
|
|
522
|
+
storage
|
|
523
|
+
.command('encrypt')
|
|
524
|
+
.description(
|
|
525
|
+
'Encrypt the store. Requires the @graphorin/store-sqlite-encrypted sub-pack (Phase 16).',
|
|
526
|
+
)
|
|
527
|
+
.requiredOption('--passphrase-from <ref>', 'SecretRef URI for the new passphrase.')
|
|
528
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
529
|
+
.option(
|
|
530
|
+
'--target-path <path>',
|
|
531
|
+
'Override the encrypted output path. Default <storage>.encrypted.',
|
|
532
|
+
)
|
|
533
|
+
.option(
|
|
534
|
+
'--swap',
|
|
535
|
+
'Atomically swap the encrypted output into the storage path (REQUIRES a stopped server - refused when another process holds the DB); original kept under .bak.<ts>.',
|
|
536
|
+
)
|
|
537
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
538
|
+
.action(
|
|
539
|
+
async (opts: {
|
|
540
|
+
passphraseFrom: string;
|
|
541
|
+
config?: string;
|
|
542
|
+
targetPath?: string;
|
|
543
|
+
swap?: boolean;
|
|
544
|
+
json?: boolean;
|
|
545
|
+
}) => {
|
|
546
|
+
await runStorageEncrypt({
|
|
547
|
+
passphraseFrom: opts.passphraseFrom,
|
|
548
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
549
|
+
...(opts.targetPath !== undefined ? { targetPath: opts.targetPath } : {}),
|
|
550
|
+
...(opts.swap === true ? { swap: true } : {}),
|
|
551
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
552
|
+
});
|
|
553
|
+
},
|
|
554
|
+
);
|
|
555
|
+
storage
|
|
556
|
+
.command('rekey')
|
|
557
|
+
.description('Rekey an already-encrypted store.')
|
|
558
|
+
.requiredOption('--old-passphrase-from <ref>', 'SecretRef URI for the existing passphrase.')
|
|
559
|
+
.requiredOption('--new-passphrase-from <ref>', 'SecretRef URI for the new passphrase.')
|
|
560
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
561
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
562
|
+
.action(
|
|
563
|
+
async (opts: {
|
|
564
|
+
oldPassphraseFrom: string;
|
|
565
|
+
newPassphraseFrom: string;
|
|
566
|
+
config?: string;
|
|
567
|
+
json?: boolean;
|
|
568
|
+
}) => {
|
|
569
|
+
await runStorageRekey({
|
|
570
|
+
oldPassphraseFrom: opts.oldPassphraseFrom,
|
|
571
|
+
newPassphraseFrom: opts.newPassphraseFrom,
|
|
572
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
573
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
574
|
+
});
|
|
575
|
+
},
|
|
576
|
+
);
|
|
577
|
+
storage
|
|
578
|
+
.command('backup')
|
|
579
|
+
.description(
|
|
580
|
+
'Online, consistent copy of the store via the page-level backup API (safe under a live writer; never use VACUUM INTO).',
|
|
581
|
+
)
|
|
582
|
+
.argument('<dest>', 'Destination file path for the backup copy.')
|
|
583
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
584
|
+
.option('--overwrite', 'Replace an existing destination file.')
|
|
585
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
586
|
+
.action(
|
|
587
|
+
async (dest: string, opts: { config?: string; overwrite?: boolean; json?: boolean }) => {
|
|
588
|
+
await runStorageBackup({
|
|
589
|
+
dest,
|
|
590
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
591
|
+
...(opts.overwrite === true ? { overwrite: true } : {}),
|
|
592
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
593
|
+
});
|
|
594
|
+
},
|
|
595
|
+
);
|
|
596
|
+
storage
|
|
597
|
+
.command('compact')
|
|
598
|
+
.description(
|
|
599
|
+
'Return pruned pages to the OS via batched incremental_vacuum (rowid-safe, unlike the forbidden VACUUM). Requires a database created with auto_vacuum=2; reports the high-water-mark limitation honestly on older files.',
|
|
600
|
+
)
|
|
601
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
602
|
+
.option(
|
|
603
|
+
'--batch-pages <n>',
|
|
604
|
+
'Free pages released per incremental_vacuum batch (keeps writer locks short). Default 1000.',
|
|
605
|
+
)
|
|
606
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
607
|
+
.action(async (opts: { config?: string; batchPages?: string; json?: boolean }) => {
|
|
608
|
+
await runStorageCompact({
|
|
609
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
610
|
+
...(opts.batchPages !== undefined ? { batchPages: Number(opts.batchPages) } : {}),
|
|
611
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
storage
|
|
615
|
+
.command('cleanup-backups')
|
|
616
|
+
.description('Drop stale .bak / .tmp.<ts> files left by previous encrypt / rekey runs.')
|
|
617
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
618
|
+
.option('--dry-run', 'Print what would be removed without touching the filesystem.')
|
|
619
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
620
|
+
.action(async (opts: { config?: string; dryRun?: boolean; json?: boolean }) => {
|
|
621
|
+
await runStorageCleanupBackups({
|
|
622
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
623
|
+
...(opts.dryRun !== undefined ? { dryRun: opts.dryRun } : {}),
|
|
624
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function registerAuditCommands(program: Command): void {
|
|
630
|
+
const audit = program
|
|
631
|
+
.command('audit')
|
|
632
|
+
.description('[Storage] Operate on the encrypted audit log.');
|
|
633
|
+
audit
|
|
634
|
+
.command('verify')
|
|
635
|
+
.description('Replay the chain and report the first broken link (if any).')
|
|
636
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
637
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
638
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
639
|
+
await runAuditVerify({
|
|
640
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
641
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
audit
|
|
645
|
+
.command('prune')
|
|
646
|
+
.description('Drop entries older than --before (cutoff is required).')
|
|
647
|
+
.requiredOption('--before <date>', 'ISO date / epoch ms cutoff.')
|
|
648
|
+
.option('--retain <n>', 'Minimum number of entries that must survive.', (v) =>
|
|
649
|
+
Number.parseInt(v, 10),
|
|
650
|
+
)
|
|
651
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
652
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
653
|
+
.action(async (opts: { before: string; retain?: number; config?: string; json?: boolean }) => {
|
|
654
|
+
await runAuditPrune({
|
|
655
|
+
before: opts.before,
|
|
656
|
+
...(opts.retain !== undefined ? { retain: opts.retain } : {}),
|
|
657
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
658
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
audit
|
|
662
|
+
.command('export')
|
|
663
|
+
.description('Stream every audit entry as JSONL into --to.')
|
|
664
|
+
.requiredOption('--to <file>', 'Output path.')
|
|
665
|
+
.option('--from-seq <n>', 'Lower bound (inclusive).', (v) => Number.parseInt(v, 10))
|
|
666
|
+
.option('--to-seq <n>', 'Upper bound (inclusive).', (v) => Number.parseInt(v, 10))
|
|
667
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
668
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
669
|
+
.action(
|
|
670
|
+
async (opts: {
|
|
671
|
+
to: string;
|
|
672
|
+
fromSeq?: number;
|
|
673
|
+
toSeq?: number;
|
|
674
|
+
config?: string;
|
|
675
|
+
json?: boolean;
|
|
676
|
+
}) => {
|
|
677
|
+
await runAuditExport({
|
|
678
|
+
to: opts.to,
|
|
679
|
+
...(opts.fromSeq !== undefined ? { fromSeq: opts.fromSeq } : {}),
|
|
680
|
+
...(opts.toSeq !== undefined ? { toSeq: opts.toSeq } : {}),
|
|
681
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
682
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
683
|
+
});
|
|
684
|
+
},
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function registerMemoryCommands(program: Command): void {
|
|
689
|
+
const memory = program
|
|
690
|
+
.command('memory')
|
|
691
|
+
.description('[Storage] Long-term memory operator commands.');
|
|
692
|
+
memory
|
|
693
|
+
.command('status')
|
|
694
|
+
.description('Report counts + active embedder + migration state.')
|
|
695
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
696
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
697
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
698
|
+
await runMemoryStatus({
|
|
699
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
700
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
memory
|
|
704
|
+
.command('migrate')
|
|
705
|
+
.description('Embedder swap. Requires --embedders module that exports the factories.')
|
|
706
|
+
.requiredOption('--from <id>', 'Source embedder id.')
|
|
707
|
+
.requiredOption('--to <id>', 'Target embedder id.')
|
|
708
|
+
.requiredOption('--strategy <s>', 'lock-on-first | auto-migrate | multi-active.')
|
|
709
|
+
.option('--embedders <path>', 'Path to a module exporting embedder factories.')
|
|
710
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
711
|
+
.action(
|
|
712
|
+
async (opts: {
|
|
713
|
+
from: string;
|
|
714
|
+
to: string;
|
|
715
|
+
strategy: string;
|
|
716
|
+
embedders?: string;
|
|
717
|
+
config?: string;
|
|
718
|
+
}) => {
|
|
719
|
+
await runMemoryMigrate({
|
|
720
|
+
from: opts.from,
|
|
721
|
+
to: opts.to,
|
|
722
|
+
strategy: opts.strategy as 'lock-on-first' | 'auto-migrate' | 'multi-active',
|
|
723
|
+
...(opts.embedders !== undefined ? { embeddersModule: opts.embedders } : {}),
|
|
724
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
725
|
+
});
|
|
726
|
+
},
|
|
727
|
+
);
|
|
728
|
+
memory
|
|
729
|
+
.command('inspect')
|
|
730
|
+
.description('Inspect one fact: supersede chain, quarantine, conflicts, citing insights.')
|
|
731
|
+
.argument('<factId>', 'Id of the fact to inspect.')
|
|
732
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
733
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
734
|
+
.action(async (factId: string, opts: { config?: string; json?: boolean }) => {
|
|
735
|
+
await runMemoryInspect({
|
|
736
|
+
factId,
|
|
737
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
738
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
739
|
+
});
|
|
740
|
+
});
|
|
741
|
+
memory
|
|
742
|
+
.command('activity')
|
|
743
|
+
.description('Store-wide consolidator / reflection activity: quarantine, history, conflicts.')
|
|
744
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
745
|
+
.option('--limit <n>', 'Cap on recent history / conflict rows (default 20).')
|
|
746
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
747
|
+
.action(async (opts: { config?: string; limit?: string; json?: boolean }) => {
|
|
748
|
+
const limit = opts.limit !== undefined ? Number.parseInt(opts.limit, 10) : undefined;
|
|
749
|
+
await runMemoryActivity({
|
|
750
|
+
...(limit !== undefined && Number.isFinite(limit) ? { limit } : {}),
|
|
751
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
752
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
memory
|
|
756
|
+
.command('prune-history')
|
|
757
|
+
.description(
|
|
758
|
+
'Delete memory_history rows older than the threshold (storage-cost hygiene; purge() already scrubs sensitive text).',
|
|
759
|
+
)
|
|
760
|
+
.requiredOption(
|
|
761
|
+
'--older-than <duration|date>',
|
|
762
|
+
"Age threshold: a duration ('30d', '12h') or a past ISO date. Mandatory - destructive.",
|
|
763
|
+
)
|
|
764
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
765
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
766
|
+
.action(async (opts: { olderThan: string; config?: string; json?: boolean }) => {
|
|
767
|
+
await runMemoryPruneHistory({
|
|
768
|
+
olderThan: opts.olderThan,
|
|
769
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
770
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
771
|
+
});
|
|
772
|
+
});
|
|
773
|
+
memory
|
|
774
|
+
.command('why')
|
|
775
|
+
.description(
|
|
776
|
+
'Explain why facts were recalled (ranking signals) from the persisted recall spans.',
|
|
777
|
+
)
|
|
778
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
779
|
+
.option('--session <id>', "Restrict to one session's recall spans.")
|
|
780
|
+
.option('--limit <n>', 'Cap on the most-recent recall spans (default 5).')
|
|
781
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
782
|
+
.action(async (opts: { config?: string; session?: string; limit?: string; json?: boolean }) => {
|
|
783
|
+
const limit = opts.limit !== undefined ? Number.parseInt(opts.limit, 10) : undefined;
|
|
784
|
+
await runMemoryWhy({
|
|
785
|
+
...(opts.session !== undefined ? { sessionId: opts.session } : {}),
|
|
786
|
+
...(limit !== undefined && Number.isFinite(limit) ? { limit } : {}),
|
|
787
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
788
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
789
|
+
});
|
|
790
|
+
});
|
|
791
|
+
memory
|
|
792
|
+
.command('review')
|
|
793
|
+
.description('List quarantined memory by tier, or promote a reviewed item with --promote.')
|
|
794
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
795
|
+
.option('--limit <n>', 'Cap on rows listed per type (default 20).')
|
|
796
|
+
.option('--promote <id>', 'Promote this quarantined memory id out of quarantine.')
|
|
797
|
+
.option('--reason <text>', 'Audit reason recorded with the promotion.')
|
|
798
|
+
.option('--force', 'Override the injection-refusal gate (operator action, after review).')
|
|
799
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
800
|
+
.action(
|
|
801
|
+
async (opts: {
|
|
802
|
+
config?: string;
|
|
803
|
+
limit?: string;
|
|
804
|
+
promote?: string;
|
|
805
|
+
reason?: string;
|
|
806
|
+
force?: boolean;
|
|
807
|
+
json?: boolean;
|
|
808
|
+
}) => {
|
|
809
|
+
const limit = opts.limit !== undefined ? Number.parseInt(opts.limit, 10) : undefined;
|
|
810
|
+
await runMemoryReview({
|
|
811
|
+
...(limit !== undefined && Number.isFinite(limit) ? { limit } : {}),
|
|
812
|
+
...(opts.promote !== undefined ? { promote: opts.promote } : {}),
|
|
813
|
+
...(opts.reason !== undefined ? { reason: opts.reason } : {}),
|
|
814
|
+
...(opts.force !== undefined ? { force: opts.force } : {}),
|
|
815
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
816
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
function registerConsolidatorCommands(program: Command): void {
|
|
823
|
+
const c = program
|
|
824
|
+
.command('consolidator')
|
|
825
|
+
.description('[Storage] Inspect and steer the memory consolidator.');
|
|
826
|
+
c.command('status')
|
|
827
|
+
.description('Current tier hint + DLQ size + recent run history.')
|
|
828
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
829
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
830
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
831
|
+
await runConsolidatorStatus({
|
|
832
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
833
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
834
|
+
});
|
|
835
|
+
});
|
|
836
|
+
c.command('set-tier <tier>')
|
|
837
|
+
.description(
|
|
838
|
+
'Runtime tier switching is not wired yet: reports UNSUPPORTED (exit 2) and points at consolidator.tier in the config (IP-4).',
|
|
839
|
+
)
|
|
840
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
841
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
842
|
+
.action(async (tier: string, opts: { config?: string; json?: boolean }) => {
|
|
843
|
+
await runConsolidatorSetTier({
|
|
844
|
+
tier: tier as 'free' | 'cheap' | 'standard' | 'full' | 'custom',
|
|
845
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
846
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
847
|
+
});
|
|
848
|
+
});
|
|
849
|
+
c.command('stop')
|
|
850
|
+
.description(
|
|
851
|
+
'Runtime pause is not wired yet: reports UNSUPPORTED (exit 2); stop the server process to stop consolidation (IP-4).',
|
|
852
|
+
)
|
|
853
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
854
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
855
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
856
|
+
await runConsolidatorStop({
|
|
857
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
858
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
859
|
+
});
|
|
860
|
+
});
|
|
861
|
+
c.command('dlq-list')
|
|
862
|
+
.description('List dead-letter batches (all users; narrow with --user).')
|
|
863
|
+
.option('--user <id>', 'Only batches scoped to this user id.')
|
|
864
|
+
.option('--limit <n>', 'Maximum rows to list.', '100')
|
|
865
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
866
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
867
|
+
.action(async (opts: { user?: string; limit?: string; config?: string; json?: boolean }) => {
|
|
868
|
+
await runConsolidatorDlqList({
|
|
869
|
+
...(opts.user !== undefined ? { user: opts.user } : {}),
|
|
870
|
+
...(opts.limit !== undefined ? { limit: Number(opts.limit) } : {}),
|
|
871
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
872
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
873
|
+
});
|
|
874
|
+
});
|
|
875
|
+
c.command('dlq-clear')
|
|
876
|
+
.description(
|
|
877
|
+
'Delete dead-letter batches. Default: only EXHAUSTED batches (retries used up); batches awaiting retry need --exhausted-only=false.',
|
|
878
|
+
)
|
|
879
|
+
.option('--exhausted-only [bool]', 'Only exhausted batches (default true).', 'true')
|
|
880
|
+
.option('--before <date>', 'Only batches that failed before this ISO date / epoch ms.')
|
|
881
|
+
.option('--id <id>', 'Clear one batch by id.')
|
|
882
|
+
.option('--user <id>', 'Only batches scoped to this user id.')
|
|
883
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
884
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
885
|
+
.action(
|
|
886
|
+
async (opts: {
|
|
887
|
+
exhaustedOnly?: string | boolean;
|
|
888
|
+
before?: string;
|
|
889
|
+
id?: string;
|
|
890
|
+
user?: string;
|
|
891
|
+
config?: string;
|
|
892
|
+
json?: boolean;
|
|
893
|
+
}) => {
|
|
894
|
+
await runConsolidatorDlqClear({
|
|
895
|
+
exhaustedOnly: String(opts.exhaustedOnly ?? 'true') !== 'false',
|
|
896
|
+
...(opts.before !== undefined ? { before: opts.before } : {}),
|
|
897
|
+
...(opts.id !== undefined ? { id: opts.id } : {}),
|
|
898
|
+
...(opts.user !== undefined ? { user: opts.user } : {}),
|
|
899
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
900
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
901
|
+
});
|
|
902
|
+
},
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
function registerTriggersCommands(program: Command): void {
|
|
907
|
+
const t = program
|
|
908
|
+
.command('triggers')
|
|
909
|
+
.description('[Storage] Operate on the durable trigger registry.');
|
|
910
|
+
t.command('list')
|
|
911
|
+
.description('Every persisted trigger.')
|
|
912
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
913
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
914
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
915
|
+
await runTriggersList({
|
|
916
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
917
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
918
|
+
});
|
|
919
|
+
});
|
|
920
|
+
t.command('status <id>')
|
|
921
|
+
.description('Single-trigger detail.')
|
|
922
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
923
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
924
|
+
.action(async (id: string, opts: { config?: string; json?: boolean }) => {
|
|
925
|
+
await runTriggersStatus({
|
|
926
|
+
id,
|
|
927
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
928
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
929
|
+
});
|
|
930
|
+
});
|
|
931
|
+
t.command('fire <id>')
|
|
932
|
+
.description('Operator-fired side-effect (admin only).')
|
|
933
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
934
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
935
|
+
.action(async (id: string, opts: { config?: string; json?: boolean }) => {
|
|
936
|
+
await runTriggersFire({
|
|
937
|
+
id,
|
|
938
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
939
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
940
|
+
});
|
|
941
|
+
});
|
|
942
|
+
t.command('disable <id>')
|
|
943
|
+
.description('Flip the disabled column on the registry row.')
|
|
944
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
945
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
946
|
+
.action(async (id: string, opts: { config?: string; json?: boolean }) => {
|
|
947
|
+
await runTriggersDisable({
|
|
948
|
+
id,
|
|
949
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
950
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
t.command('prune')
|
|
954
|
+
.description('Drop disabled triggers older than --before.')
|
|
955
|
+
.option(
|
|
956
|
+
'--before <date>',
|
|
957
|
+
'ISO date / epoch ms cutoff. Defaults to 0 (drop every disabled row).',
|
|
958
|
+
)
|
|
959
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
960
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
961
|
+
.action(async (opts: { before?: string; config?: string; json?: boolean }) => {
|
|
962
|
+
await runTriggersPrune({
|
|
963
|
+
...(opts.before !== undefined ? { before: opts.before } : {}),
|
|
964
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
965
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
966
|
+
});
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
function registerAuthCommands(program: Command): void {
|
|
971
|
+
const a = program.command('auth').description('[Auth] Outbound OAuth (e.g. for MCP servers).');
|
|
972
|
+
a.command('login')
|
|
973
|
+
.description('Drive an interactive login flow.')
|
|
974
|
+
.requiredOption('--server <url>', 'Authorization server URL.')
|
|
975
|
+
.option('--server-id <id>', 'Override the persisted server identifier.')
|
|
976
|
+
.option('--scope <scope>', 'OAuth scope.')
|
|
977
|
+
.option('--device-flow', 'Use the Device Authorization Grant.')
|
|
978
|
+
.option('--client-id <id>', 'Pre-existing client identifier (skip DCR).')
|
|
979
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
980
|
+
.action(
|
|
981
|
+
async (opts: {
|
|
982
|
+
server: string;
|
|
983
|
+
serverId?: string;
|
|
984
|
+
scope?: string;
|
|
985
|
+
deviceFlow?: boolean;
|
|
986
|
+
clientId?: string;
|
|
987
|
+
config?: string;
|
|
988
|
+
}) => {
|
|
989
|
+
await runAuthLogin({
|
|
990
|
+
serverUrl: opts.server,
|
|
991
|
+
...(opts.serverId !== undefined ? { serverId: opts.serverId } : {}),
|
|
992
|
+
...(opts.scope !== undefined ? { scope: opts.scope } : {}),
|
|
993
|
+
...(opts.deviceFlow !== undefined ? { deviceFlow: opts.deviceFlow } : {}),
|
|
994
|
+
...(opts.clientId !== undefined ? { clientId: opts.clientId } : {}),
|
|
995
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
996
|
+
});
|
|
997
|
+
},
|
|
998
|
+
);
|
|
999
|
+
a.command('list')
|
|
1000
|
+
.description('List persisted OAuth sessions.')
|
|
1001
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
1002
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1003
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
1004
|
+
await runAuthList({
|
|
1005
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1006
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1007
|
+
});
|
|
1008
|
+
});
|
|
1009
|
+
a.command('refresh <id>')
|
|
1010
|
+
.description('Refresh a session.')
|
|
1011
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
1012
|
+
.action(async (id: string, opts: { config?: string }) => {
|
|
1013
|
+
await runAuthRefresh({
|
|
1014
|
+
id,
|
|
1015
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1016
|
+
});
|
|
1017
|
+
});
|
|
1018
|
+
a.command('revoke <id>')
|
|
1019
|
+
.description('Revoke a session.')
|
|
1020
|
+
.option('--reason <text>', 'Optional reason persisted in the audit log.')
|
|
1021
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
1022
|
+
.action(async (id: string, opts: { reason?: string; config?: string }) => {
|
|
1023
|
+
await runAuthRevoke({
|
|
1024
|
+
id,
|
|
1025
|
+
...(opts.reason !== undefined ? { reason: opts.reason } : {}),
|
|
1026
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1027
|
+
});
|
|
1028
|
+
});
|
|
1029
|
+
a.command('status')
|
|
1030
|
+
.description('OAuth subsystem snapshot.')
|
|
1031
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
1032
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1033
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
1034
|
+
await runAuthStatus({
|
|
1035
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1036
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function registerPricingCommands(program: Command): void {
|
|
1042
|
+
const p = program.command('pricing').description('[Catalogue] Bundled LLM pricing snapshot.');
|
|
1043
|
+
p.command('status')
|
|
1044
|
+
.description('Show bundled snapshot version + entry count + digest.')
|
|
1045
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1046
|
+
.action((opts: { json?: boolean }) => {
|
|
1047
|
+
runPricingStatus({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1048
|
+
});
|
|
1049
|
+
p.command('refresh')
|
|
1050
|
+
.description('Pull a fresh snapshot from --url (network).')
|
|
1051
|
+
.requiredOption('--url <url>', 'Snapshot URL.')
|
|
1052
|
+
.option('--out <file>', 'Optional path to write the refreshed snapshot to.')
|
|
1053
|
+
.option(
|
|
1054
|
+
'--format <format>',
|
|
1055
|
+
"Body format: 'auto' (default), 'graphorin', or 'genai-prices'.",
|
|
1056
|
+
'auto',
|
|
1057
|
+
)
|
|
1058
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1059
|
+
.action(async (opts: { url: string; out?: string; format?: string; json?: boolean }) => {
|
|
1060
|
+
const format = opts.format ?? 'auto';
|
|
1061
|
+
if (format !== 'auto' && format !== 'graphorin' && format !== 'genai-prices') {
|
|
1062
|
+
process.stderr.write(
|
|
1063
|
+
`graphorin pricing refresh: invalid --format '${format}' (expected auto | graphorin | genai-prices)\n`,
|
|
1064
|
+
);
|
|
1065
|
+
process.exit(2);
|
|
1066
|
+
}
|
|
1067
|
+
await runPricingRefresh({
|
|
1068
|
+
url: opts.url,
|
|
1069
|
+
format,
|
|
1070
|
+
...(opts.out !== undefined ? { out: opts.out } : {}),
|
|
1071
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
p.command('diff')
|
|
1075
|
+
.description('Diff a supplied snapshot against the bundled one.')
|
|
1076
|
+
.requiredOption('--snapshot <file>', 'JSON file containing a PricingSnapshot.')
|
|
1077
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1078
|
+
.action(async (opts: { snapshot: string; json?: boolean }) => {
|
|
1079
|
+
await runPricingDiff({
|
|
1080
|
+
snapshot: opts.snapshot,
|
|
1081
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1082
|
+
});
|
|
1083
|
+
});
|
|
1084
|
+
p.command('lookup')
|
|
1085
|
+
.description('Print the per-token price for a single (provider, model) pair.')
|
|
1086
|
+
.requiredOption('--provider <name>', 'Provider id.')
|
|
1087
|
+
.requiredOption('--model <id>', 'Model id.')
|
|
1088
|
+
.option('--region <region>', 'Optional region.')
|
|
1089
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1090
|
+
.action((opts: { provider: string; model: string; region?: string; json?: boolean }) => {
|
|
1091
|
+
runPricingLookup({
|
|
1092
|
+
provider: opts.provider,
|
|
1093
|
+
model: opts.model,
|
|
1094
|
+
...(opts.region !== undefined ? { region: opts.region } : {}),
|
|
1095
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1096
|
+
});
|
|
1097
|
+
});
|
|
1098
|
+
p.command('missing')
|
|
1099
|
+
.description('Report (provider, model) pairs absent from the bundled snapshot.')
|
|
1100
|
+
.requiredOption('--spans <file>', 'JSON file containing trace spans (array of { attributes }).')
|
|
1101
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1102
|
+
.action(async (opts: { spans: string; json?: boolean }) => {
|
|
1103
|
+
await runPricingMissing({
|
|
1104
|
+
spans: opts.spans,
|
|
1105
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1106
|
+
});
|
|
1107
|
+
});
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function registerSkillsCommands(program: Command): void {
|
|
1111
|
+
const s = program
|
|
1112
|
+
.command('skills')
|
|
1113
|
+
.description('[Catalogue] Manage operator-managed skill packages.');
|
|
1114
|
+
s.command('install <source>')
|
|
1115
|
+
.description('Install a skill from npm:<name>[@version] or git:<url>.')
|
|
1116
|
+
.option('--version <version>', 'npm version pin.')
|
|
1117
|
+
.option('--ref <ref>', 'git ref (branch / tag / sha).')
|
|
1118
|
+
.option('--trust-level <level>', 'trusted | trusted-with-scripts | untrusted.')
|
|
1119
|
+
.option('--cwd <dir>', 'Working directory for npm installs.')
|
|
1120
|
+
.option('--dry-run', 'Skip the install; run the policy + audit pipeline only.')
|
|
1121
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1122
|
+
.action(
|
|
1123
|
+
async (
|
|
1124
|
+
source: string,
|
|
1125
|
+
opts: {
|
|
1126
|
+
version?: string;
|
|
1127
|
+
ref?: string;
|
|
1128
|
+
trustLevel?: 'trusted' | 'trusted-with-scripts' | 'untrusted';
|
|
1129
|
+
cwd?: string;
|
|
1130
|
+
dryRun?: boolean;
|
|
1131
|
+
json?: boolean;
|
|
1132
|
+
},
|
|
1133
|
+
) => {
|
|
1134
|
+
await runSkillsInstall({
|
|
1135
|
+
source,
|
|
1136
|
+
...(opts.version !== undefined ? { version: opts.version } : {}),
|
|
1137
|
+
...(opts.ref !== undefined ? { ref: opts.ref } : {}),
|
|
1138
|
+
...(opts.trustLevel !== undefined ? { trustLevel: opts.trustLevel } : {}),
|
|
1139
|
+
...(opts.cwd !== undefined ? { cwd: opts.cwd } : {}),
|
|
1140
|
+
...(opts.dryRun !== undefined ? { dryRun: opts.dryRun } : {}),
|
|
1141
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1142
|
+
});
|
|
1143
|
+
},
|
|
1144
|
+
);
|
|
1145
|
+
s.command('inspect <name>')
|
|
1146
|
+
.description('Print a single recorded installation.')
|
|
1147
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1148
|
+
.action(async (name: string, opts: { json?: boolean }) => {
|
|
1149
|
+
await runSkillsInspect({
|
|
1150
|
+
name,
|
|
1151
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1152
|
+
});
|
|
1153
|
+
});
|
|
1154
|
+
s.command('audit')
|
|
1155
|
+
.description('Audit every recorded installation in this process.')
|
|
1156
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1157
|
+
.action((opts: { json?: boolean }) => {
|
|
1158
|
+
runSkillsAudit({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1159
|
+
});
|
|
1160
|
+
s.command('migrate-frontmatter')
|
|
1161
|
+
.description(
|
|
1162
|
+
'Migrate legacy graphorin-* frontmatter fields onto upstream equivalents (DEC-156).',
|
|
1163
|
+
)
|
|
1164
|
+
.option('--path <dir>', 'Directory to walk. Defaults to cwd.')
|
|
1165
|
+
.option('--recursive', 'Walk subdirectories.')
|
|
1166
|
+
.option('--apply', 'Actually rewrite files (default: dry-run).')
|
|
1167
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1168
|
+
.action(
|
|
1169
|
+
async (opts: { path?: string; recursive?: boolean; apply?: boolean; json?: boolean }) => {
|
|
1170
|
+
await runSkillsMigrateFrontmatter({
|
|
1171
|
+
...(opts.path !== undefined ? { path: opts.path } : {}),
|
|
1172
|
+
...(opts.recursive !== undefined ? { recursive: opts.recursive } : {}),
|
|
1173
|
+
...(opts.apply !== undefined ? { apply: opts.apply } : {}),
|
|
1174
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1175
|
+
});
|
|
1176
|
+
},
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
function registerTracesCommands(program: Command): void {
|
|
1181
|
+
const t = program.command('traces').description('[Diagnostics] Operate on persisted spans.');
|
|
1182
|
+
t.command('status')
|
|
1183
|
+
.description('Count persisted spans + report their time range.')
|
|
1184
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
1185
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1186
|
+
.action(async (opts: { config?: string; json?: boolean }) => {
|
|
1187
|
+
await runTracesStatus({
|
|
1188
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1189
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1190
|
+
});
|
|
1191
|
+
});
|
|
1192
|
+
t.command('prune')
|
|
1193
|
+
.description('Delete spans that FINISHED before the cutoff (cutoff is required).')
|
|
1194
|
+
.requiredOption(
|
|
1195
|
+
'--before <date>',
|
|
1196
|
+
'ISO date / epoch ms cutoff (spans ending strictly before it are deleted).',
|
|
1197
|
+
)
|
|
1198
|
+
.option('-c, --config <path>', 'Path to the graphorin.config file.')
|
|
1199
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1200
|
+
.action(async (opts: { before: string; config?: string; json?: boolean }) => {
|
|
1201
|
+
await runTracesPrune({
|
|
1202
|
+
before: opts.before,
|
|
1203
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1204
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1205
|
+
});
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
function registerMigrateExportCommand(program: Command): void {
|
|
1210
|
+
program
|
|
1211
|
+
.command('migrate-export <input>')
|
|
1212
|
+
.description('[Storage] Migrate a session export file to the current schema (DEC-155).')
|
|
1213
|
+
.requiredOption('--to <file>', 'Output path.')
|
|
1214
|
+
.option('--to-schema <version>', 'Target schema version.', '1.0')
|
|
1215
|
+
.option('--writer <id>', 'Writer identifier surfaced on the meta header.')
|
|
1216
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1217
|
+
.action(
|
|
1218
|
+
async (
|
|
1219
|
+
input: string,
|
|
1220
|
+
opts: { to: string; toSchema?: string; writer?: string; json?: boolean },
|
|
1221
|
+
) => {
|
|
1222
|
+
await runMigrateExport({
|
|
1223
|
+
input,
|
|
1224
|
+
to: opts.to,
|
|
1225
|
+
...(opts.toSchema !== undefined ? { toSchema: opts.toSchema } : {}),
|
|
1226
|
+
...(opts.writer !== undefined ? { writer: opts.writer } : {}),
|
|
1227
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1228
|
+
});
|
|
1229
|
+
},
|
|
1230
|
+
);
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
function registerMigrateConfigCommand(program: Command): void {
|
|
1234
|
+
program
|
|
1235
|
+
.command('migrate-config <input>')
|
|
1236
|
+
.description('[Bootstrap] Migrate an old config file to the current schema.')
|
|
1237
|
+
.option('--out <file>', 'Output path. Defaults to <input>.migrated.json next to the source.')
|
|
1238
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1239
|
+
.action(async (input: string, opts: { out?: string; json?: boolean }) => {
|
|
1240
|
+
await runMigrateConfig({
|
|
1241
|
+
input,
|
|
1242
|
+
...(opts.out !== undefined ? { out: opts.out } : {}),
|
|
1243
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
function registerGuardCommands(program: Command): void {
|
|
1249
|
+
const g = program
|
|
1250
|
+
.command('guard')
|
|
1251
|
+
.description('[Diagnostics] Inspect the memory-modification guard.');
|
|
1252
|
+
g.command('status')
|
|
1253
|
+
.description('Print the four guard tiers and their variants.')
|
|
1254
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1255
|
+
.action((opts: { json?: boolean }) => {
|
|
1256
|
+
runGuardStatus({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1257
|
+
});
|
|
1258
|
+
g.command('explain <toolName>')
|
|
1259
|
+
.description(
|
|
1260
|
+
'Derive the tier the classifier would assign to a tool with the supplied metadata.',
|
|
1261
|
+
)
|
|
1262
|
+
.option('--tags <list>', 'Comma-separated tag list.')
|
|
1263
|
+
.option('--secrets-allowed <list>', 'Comma-separated secret allowlist.')
|
|
1264
|
+
.option('--trust-level <level>', 'built-in | user-defined | trusted | untrusted.')
|
|
1265
|
+
.option(
|
|
1266
|
+
'--explicit-tier <tier>',
|
|
1267
|
+
'pure | side-effecting-no-memory | memory-aware | unknown | untrusted.',
|
|
1268
|
+
)
|
|
1269
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1270
|
+
.action(
|
|
1271
|
+
(
|
|
1272
|
+
toolName: string,
|
|
1273
|
+
opts: {
|
|
1274
|
+
tags?: string;
|
|
1275
|
+
secretsAllowed?: string;
|
|
1276
|
+
trustLevel?: 'built-in' | 'user-defined' | 'trusted' | 'untrusted';
|
|
1277
|
+
explicitTier?:
|
|
1278
|
+
| 'pure'
|
|
1279
|
+
| 'side-effecting-no-memory'
|
|
1280
|
+
| 'memory-aware'
|
|
1281
|
+
| 'unknown'
|
|
1282
|
+
| 'untrusted';
|
|
1283
|
+
json?: boolean;
|
|
1284
|
+
},
|
|
1285
|
+
) => {
|
|
1286
|
+
runGuardExplain({
|
|
1287
|
+
toolName,
|
|
1288
|
+
...(opts.tags !== undefined
|
|
1289
|
+
? {
|
|
1290
|
+
tags: opts.tags
|
|
1291
|
+
.split(',')
|
|
1292
|
+
.map((s) => s.trim())
|
|
1293
|
+
.filter(Boolean),
|
|
1294
|
+
}
|
|
1295
|
+
: {}),
|
|
1296
|
+
...(opts.secretsAllowed !== undefined
|
|
1297
|
+
? {
|
|
1298
|
+
secretsAllowed: opts.secretsAllowed
|
|
1299
|
+
.split(',')
|
|
1300
|
+
.map((s) => s.trim())
|
|
1301
|
+
.filter(Boolean),
|
|
1302
|
+
}
|
|
1303
|
+
: {}),
|
|
1304
|
+
...(opts.trustLevel !== undefined ? { trustLevel: opts.trustLevel } : {}),
|
|
1305
|
+
...(opts.explicitTier !== undefined ? { explicitTier: opts.explicitTier } : {}),
|
|
1306
|
+
...(opts.json !== undefined ? { json: opts.json } : {}),
|
|
1307
|
+
});
|
|
1308
|
+
},
|
|
1309
|
+
);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
function registerTelemetryCommands(program: Command): void {
|
|
1313
|
+
const t = program
|
|
1314
|
+
.command('telemetry')
|
|
1315
|
+
.description('[Diagnostics] Telemetry policy (zero-default per ADR-041).');
|
|
1316
|
+
t.command('status')
|
|
1317
|
+
.description('Always reports disabled.')
|
|
1318
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1319
|
+
.action((opts: { json?: boolean }) => {
|
|
1320
|
+
runTelemetryStatus({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1321
|
+
});
|
|
1322
|
+
t.command('enable')
|
|
1323
|
+
.description('Refuses; opt-in collector is on the v0.2+ roadmap.')
|
|
1324
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1325
|
+
.action((opts: { json?: boolean }) => {
|
|
1326
|
+
runTelemetryEnable({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1327
|
+
});
|
|
1328
|
+
t.command('disable')
|
|
1329
|
+
.description('No-op; telemetry is always disabled.')
|
|
1330
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1331
|
+
.action((opts: { json?: boolean }) => {
|
|
1332
|
+
runTelemetryDisable({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1333
|
+
});
|
|
1334
|
+
t.command('inspect')
|
|
1335
|
+
.description('Informational summary of the zero-default promise.')
|
|
1336
|
+
.option('--json', 'Emit a structured JSON document on stdout.')
|
|
1337
|
+
.action((opts: { json?: boolean }) => {
|
|
1338
|
+
runTelemetryInspect({ ...(opts.json !== undefined ? { json: opts.json } : {}) });
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
function registerToolsCommands(program: Command): void {
|
|
1343
|
+
const tools = program.command('tools').description('[Catalogue] Operate on the tool registry.');
|
|
1344
|
+
tools
|
|
1345
|
+
.command('lint')
|
|
1346
|
+
.description(
|
|
1347
|
+
[
|
|
1348
|
+
'Text-based static scan of every tool({...}) registration; per-tool grader + threshold gate (RB-49).',
|
|
1349
|
+
'',
|
|
1350
|
+
'Grader rubric (40 + 30 + 30 = 100 points):',
|
|
1351
|
+
' description axis (0..40): 0 if missing/placeholder/<20 chars; 16/24/32/40 by length; degenerate text (under 4 unique words or one word over half the text) caps at 16 (W-044).',
|
|
1352
|
+
' examples axis (0..30): 0 if none or >5; 12 base + 6 per additional, cap 30; -6 per PII.',
|
|
1353
|
+
' parameter naming (0..30): 30 base; -30/N per ambiguous name; -10/N per numeric suffix.',
|
|
1354
|
+
'',
|
|
1355
|
+
'Exit codes (CI-friendly):',
|
|
1356
|
+
' 0 every tool meets or exceeds --threshold.',
|
|
1357
|
+
' 1 at least one tool falls below --threshold.',
|
|
1358
|
+
' 2 invocation could not start (config missing, walker failed).',
|
|
1359
|
+
].join('\n'),
|
|
1360
|
+
)
|
|
1361
|
+
.option('-c, --config <path>', 'Optional tsconfig.json whose include overrides the file glob.')
|
|
1362
|
+
.option('--threshold <n>', 'Minimum acceptable per-tool score (default 60).', (v) =>
|
|
1363
|
+
Number.parseInt(v, 10),
|
|
1364
|
+
)
|
|
1365
|
+
.option('--format <fmt>', 'text | json (default text).')
|
|
1366
|
+
.option('--source <pattern>', 'File glob override (e.g. src/skills/**/tools/*.ts).')
|
|
1367
|
+
.action(
|
|
1368
|
+
async (opts: {
|
|
1369
|
+
config?: string;
|
|
1370
|
+
threshold?: number;
|
|
1371
|
+
format?: 'text' | 'json';
|
|
1372
|
+
source?: string;
|
|
1373
|
+
}) => {
|
|
1374
|
+
await runToolsLint({
|
|
1375
|
+
...(opts.config !== undefined ? { config: opts.config } : {}),
|
|
1376
|
+
...(opts.threshold !== undefined ? { threshold: opts.threshold } : {}),
|
|
1377
|
+
...(opts.format !== undefined ? { format: opts.format } : {}),
|
|
1378
|
+
...(opts.source !== undefined ? { source: opts.source } : {}),
|
|
1379
|
+
});
|
|
1380
|
+
},
|
|
1381
|
+
);
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
main().catch((err) => {
|
|
1385
|
+
process.stderr.write(`[graphorin/cli] ${(err as Error).message}\n`);
|
|
1386
|
+
process.exit(1);
|
|
1387
|
+
});
|