@graphorin/cli 0.5.0 → 0.6.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 +39 -0
- package/README.md +8 -8
- package/dist/bin/graphorin.js +15 -7
- package/dist/bin/graphorin.js.map +1 -1
- package/dist/commands/audit.d.ts +2 -2
- package/dist/commands/audit.js +6 -6
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/auth.d.ts +1 -1
- package/dist/commands/auth.js +3 -3
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/consolidator.js +2 -2
- package/dist/commands/consolidator.js.map +1 -1
- package/dist/commands/doctor.d.ts +1 -1
- package/dist/commands/doctor.js +5 -5
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/guard.js +3 -3
- package/dist/commands/guard.js.map +1 -1
- package/dist/commands/index.d.ts +2 -2
- package/dist/commands/index.js +2 -2
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +3 -3
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/memory.d.ts +5 -5
- package/dist/commands/memory.d.ts.map +1 -1
- package/dist/commands/memory.js +11 -11
- package/dist/commands/memory.js.map +1 -1
- package/dist/commands/migrate-config.js +2 -2
- package/dist/commands/migrate-config.js.map +1 -1
- package/dist/commands/migrate-export.d.ts +1 -1
- package/dist/commands/migrate-export.js +2 -2
- package/dist/commands/migrate-export.js.map +1 -1
- package/dist/commands/migrate.d.ts +1 -1
- package/dist/commands/migrate.js +1 -1
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/pricing.d.ts +1 -1
- package/dist/commands/pricing.js +6 -6
- package/dist/commands/pricing.js.map +1 -1
- package/dist/commands/secrets.d.ts +1 -1
- package/dist/commands/secrets.js +5 -5
- package/dist/commands/secrets.js.map +1 -1
- package/dist/commands/skills.js +5 -5
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/start.d.ts +1 -1
- package/dist/commands/start.js +1 -1
- package/dist/commands/start.js.map +1 -1
- package/dist/commands/storage.d.ts +26 -2
- package/dist/commands/storage.d.ts.map +1 -1
- package/dist/commands/storage.js +51 -11
- package/dist/commands/storage.js.map +1 -1
- package/dist/commands/telemetry.js +6 -6
- package/dist/commands/telemetry.js.map +1 -1
- package/dist/commands/token.d.ts +2 -2
- package/dist/commands/token.js +4 -4
- package/dist/commands/token.js.map +1 -1
- package/dist/commands/tools-lint.d.ts +1 -1
- package/dist/commands/tools-lint.js +6 -6
- package/dist/commands/tools-lint.js.map +1 -1
- package/dist/commands/traces.js +5 -5
- package/dist/commands/traces.js.map +1 -1
- package/dist/commands/triggers.d.ts +1 -1
- package/dist/commands/triggers.js +1 -1
- package/dist/commands/triggers.js.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/internal/exit.js.map +1 -1
- package/dist/internal/load-config.js +5 -5
- package/dist/internal/load-config.js.map +1 -1
- package/dist/internal/offline.d.ts +1 -1
- package/dist/internal/offline.js +2 -2
- package/dist/internal/offline.js.map +1 -1
- package/dist/internal/output.d.ts +6 -6
- package/dist/internal/output.js +6 -6
- package/dist/internal/output.js.map +1 -1
- package/dist/internal/store-context.js.map +1 -1
- package/package.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"secrets.js","names":["out: SecretsGetResult","out","out: SecretsRefResult"],"sources":["../../src/commands/secrets.ts"],"sourcesContent":["/**\n * `graphorin secrets` — manage the operator's secrets store.\n *\n * Surface (per Phase 15 § Secrets):\n *\n * - `graphorin secrets list`\n * - `graphorin secrets get <key>`\n * - `graphorin secrets set <key> [--value <v>] [--from-stdin] [--scope <ref>]`\n * - `graphorin secrets delete <key>`\n * - `graphorin secrets ref <uri>` — test resolution of a `SecretRef` URI.\n * - `graphorin secrets rotate <key> --new-value <v>`\n *\n * Honours `--secrets-source <kind>` and `--strict-secrets` per\n * DEC-136 — both flags are forwarded to `createSecretsStore(...)` so\n * the CLI activates the same store the running server would.\n *\n * The CLI never logs raw secret bytes. `get` prints the value through\n * `value.use((s) => print(s))` so the bytes are released as soon as\n * the print callback returns; `--json` mode emits only the metadata\n * fields, never the value, unless `--reveal` is explicitly supplied.\n *\n * @packageDocumentation\n */\n\nimport { stdin } from 'node:process';\n\nimport type { SecretMetadata } from '@graphorin/core/contracts';\nimport {\n createSecretsStore,\n getActiveSecretsStore,\n parseSecretRef,\n resolveSecret,\n type SecretsStoreKind,\n SecretValue,\n} from '@graphorin/security';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface SecretsCommonOptions extends CommonOutputOptions {\n /** Mirrors `--secrets-source` per DEC-136. */\n readonly secretsSource?: SecretsStoreKind;\n /** Mirrors `--strict-secrets` per DEC-136. */\n readonly strictSecrets?: boolean;\n}\n\n/** @stable */\nexport interface SecretsListOptions extends SecretsCommonOptions {}\n\n/** @stable */\nexport async function runSecretsList(\n options: SecretsListOptions = {},\n): Promise<ReadonlyArray<SecretMetadata>> {\n const store = await openStore(options);\n const list = await store.list();\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no secrets recorded.'));\n return;\n }\n print(brand(`${list.length} secret(s):`));\n for (const m of list) {\n print(` ${statusMarker('ok')} ${m.key} (updated=${m.updatedAt ?? '-'})`);\n }\n });\n return list;\n}\n\n/** @stable */\nexport interface SecretsGetOptions extends SecretsCommonOptions {\n readonly key: string;\n /**\n * When `true`, print the raw value through the human report. The\n * default is to print only structured metadata so the bytes do not\n * surface in shell history.\n */\n readonly reveal?: boolean;\n}\n\n/** @stable */\nexport interface SecretsGetResult {\n readonly key: string;\n readonly found: boolean;\n /** Only present when `reveal: true` AND the secret exists. */\n readonly value?: string;\n}\n\n/** @stable */\nexport async function runSecretsGet(options: SecretsGetOptions): Promise<SecretsGetResult> {\n const store = await openStore(options);\n const value = await store.get(options.key);\n if (value === null) {\n const out: SecretsGetResult = Object.freeze({ key: options.key, found: false });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n }\n if (options.reveal === true) {\n const raw = await value.use((s) => String(s));\n const out: SecretsGetResult = Object.freeze({ key: options.key, found: true, value: raw });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' resolved (length=${raw.length})`));\n print(` ${raw}`);\n });\n return out;\n }\n const out: SecretsGetResult = Object.freeze({ key: options.key, found: true });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' found (use --reveal to print the bytes)`));\n });\n return out;\n}\n\n/** @stable */\nexport interface SecretsSetOptions extends SecretsCommonOptions {\n readonly key: string;\n readonly value?: string;\n /** When `true`, read the value from stdin. */\n readonly fromStdin?: boolean;\n}\n\n/** @stable */\nexport async function runSecretsSet(options: SecretsSetOptions): Promise<{ readonly ok: true }> {\n const store = await openStore(options);\n let raw = options.value;\n if (raw === undefined && options.fromStdin === true) {\n raw = await readStdin();\n }\n if (raw === undefined) {\n throw new Error(\n '[graphorin/cli] secrets set requires either --value <v> or --from-stdin (no plaintext on the command line).',\n );\n }\n await store.set(\n options.key,\n SecretValue.fromString(raw, { source: { resolver: 'graphorin secrets set' } }),\n );\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' written.`));\n });\n return { ok: true };\n}\n\n/** @stable */\nexport interface SecretsDeleteOptions extends SecretsCommonOptions {\n readonly key: string;\n}\n\n/** @stable */\nexport async function runSecretsDelete(\n options: SecretsDeleteOptions,\n): Promise<{ readonly ok: true }> {\n const store = await openStore(options);\n await store.delete(options.key);\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' deleted.`));\n });\n return { ok: true };\n}\n\n/** @stable */\nexport interface SecretsRefOptions extends CommonOutputOptions {\n readonly uri: string;\n readonly reveal?: boolean;\n}\n\n/** @stable */\nexport interface SecretsRefResult {\n readonly uri: string;\n readonly scheme: string;\n readonly resolved: boolean;\n readonly length?: number;\n readonly value?: string;\n}\n\n/**\n * Test resolution of a `SecretRef` URI. The CLI parses the URI first\n * (sanity check + scheme echo), then resolves through the registered\n * resolver chain.\n *\n * @stable\n */\nexport async function runSecretsRef(options: SecretsRefOptions): Promise<SecretsRefResult> {\n const parsed = parseSecretRef(options.uri);\n try {\n const value = await resolveSecret(options.uri);\n const length = await value.use((s) => s.length);\n const raw = options.reveal === true ? await value.use((s) => String(s)) : undefined;\n const out: SecretsRefResult = Object.freeze({\n uri: options.uri,\n scheme: parsed.scheme,\n resolved: true,\n length,\n ...(raw !== undefined ? { value: raw } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `${statusMarker('ok')} resolved '${parsed.scheme}' ref (length=${length}${raw !== undefined ? `, value='${raw}'` : ''})`,\n ),\n );\n });\n return out;\n } catch (err) {\n const out: SecretsRefResult = Object.freeze({\n uri: options.uri,\n scheme: parsed.scheme,\n resolved: false,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(`${statusMarker('fail')} ${parsed.scheme} ref failed: ${(err as Error).message}`),\n );\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n }\n}\n\n/** @stable */\nexport interface SecretsRotateOptions extends SecretsCommonOptions {\n readonly key: string;\n readonly newValue?: string;\n readonly fromStdin?: boolean;\n}\n\n/**\n * `graphorin secrets rotate <key>` — overwrite the existing value\n * with a fresh one. Functionally identical to `set` but the CLI\n * surfaces the operation explicitly so audit logs can distinguish\n * a rotation from an initial write.\n *\n * @stable\n */\nexport async function runSecretsRotate(\n options: SecretsRotateOptions,\n): Promise<{ readonly ok: true }> {\n const store = await openStore(options);\n const existing = await store.get(options.key);\n if (existing === null) {\n throw new Error(\n `[graphorin/cli] cannot rotate '${options.key}' — secret does not exist (use 'graphorin secrets set' to create it).`,\n );\n }\n let raw = options.newValue;\n if (raw === undefined && options.fromStdin === true) raw = await readStdin();\n if (raw === undefined) {\n throw new Error(\n '[graphorin/cli] secrets rotate requires either --new-value <v> or --from-stdin.',\n );\n }\n await store.set(\n options.key,\n SecretValue.fromString(raw, { source: { resolver: 'graphorin secrets rotate' } }),\n );\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' rotated.`));\n });\n return { ok: true };\n}\n\nasync function openStore(options: SecretsCommonOptions) {\n // Reuse the per-process active store when the caller did not request\n // a different kind. The singleton matches the running server / lib\n // process so consecutive CLI invocations within the same process\n // address the same backing store (this matters for the in-memory\n // store + for subprocess CLI flows that share a parent's resolver\n // chain via env vars).\n if (options.secretsSource === undefined) {\n const active = getActiveSecretsStore();\n if (active !== undefined) return active;\n }\n return await createSecretsStore({\n ...(options.secretsSource !== undefined ? { kind: options.secretsSource } : {}),\n ...(options.strictSecrets !== undefined ? { strict: options.strictSecrets } : {}),\n });\n}\n\nasync function readStdin(): Promise<string> {\n return await new Promise((resolve, reject) => {\n let buf = '';\n stdin.setEncoding('utf8');\n stdin.on('data', (chunk) => {\n buf += chunk;\n });\n stdin.on('end', () => resolve(buf.replace(/\\r?\\n$/, '')));\n stdin.on('error', reject);\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"secrets.js","names":["out: SecretsGetResult","out","out: SecretsRefResult"],"sources":["../../src/commands/secrets.ts"],"sourcesContent":["/**\n * `graphorin secrets` - manage the operator's secrets store.\n *\n * Surface (per Phase 15 § Secrets):\n *\n * - `graphorin secrets list`\n * - `graphorin secrets get <key>`\n * - `graphorin secrets set <key> [--value <v>] [--from-stdin] [--scope <ref>]`\n * - `graphorin secrets delete <key>`\n * - `graphorin secrets ref <uri>` - test resolution of a `SecretRef` URI.\n * - `graphorin secrets rotate <key> --new-value <v>`\n *\n * Honours `--secrets-source <kind>` and `--strict-secrets` per\n * DEC-136 - both flags are forwarded to `createSecretsStore(...)` so\n * the CLI activates the same store the running server would.\n *\n * The CLI never logs raw secret bytes. `get` prints the value through\n * `value.use((s) => print(s))` so the bytes are released as soon as\n * the print callback returns; `--json` mode emits only the metadata\n * fields, never the value, unless `--reveal` is explicitly supplied.\n *\n * @packageDocumentation\n */\n\nimport { stdin } from 'node:process';\n\nimport type { SecretMetadata } from '@graphorin/core/contracts';\nimport {\n createSecretsStore,\n getActiveSecretsStore,\n parseSecretRef,\n resolveSecret,\n type SecretsStoreKind,\n SecretValue,\n} from '@graphorin/security';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface SecretsCommonOptions extends CommonOutputOptions {\n /** Mirrors `--secrets-source` per DEC-136. */\n readonly secretsSource?: SecretsStoreKind;\n /** Mirrors `--strict-secrets` per DEC-136. */\n readonly strictSecrets?: boolean;\n}\n\n/** @stable */\nexport interface SecretsListOptions extends SecretsCommonOptions {}\n\n/** @stable */\nexport async function runSecretsList(\n options: SecretsListOptions = {},\n): Promise<ReadonlyArray<SecretMetadata>> {\n const store = await openStore(options);\n const list = await store.list();\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no secrets recorded.'));\n return;\n }\n print(brand(`${list.length} secret(s):`));\n for (const m of list) {\n print(` ${statusMarker('ok')} ${m.key} (updated=${m.updatedAt ?? '-'})`);\n }\n });\n return list;\n}\n\n/** @stable */\nexport interface SecretsGetOptions extends SecretsCommonOptions {\n readonly key: string;\n /**\n * When `true`, print the raw value through the human report. The\n * default is to print only structured metadata so the bytes do not\n * surface in shell history.\n */\n readonly reveal?: boolean;\n}\n\n/** @stable */\nexport interface SecretsGetResult {\n readonly key: string;\n readonly found: boolean;\n /** Only present when `reveal: true` AND the secret exists. */\n readonly value?: string;\n}\n\n/** @stable */\nexport async function runSecretsGet(options: SecretsGetOptions): Promise<SecretsGetResult> {\n const store = await openStore(options);\n const value = await store.get(options.key);\n if (value === null) {\n const out: SecretsGetResult = Object.freeze({ key: options.key, found: false });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n }\n if (options.reveal === true) {\n const raw = await value.use((s) => String(s));\n const out: SecretsGetResult = Object.freeze({ key: options.key, found: true, value: raw });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' resolved (length=${raw.length})`));\n print(` ${raw}`);\n });\n return out;\n }\n const out: SecretsGetResult = Object.freeze({ key: options.key, found: true });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' found (use --reveal to print the bytes)`));\n });\n return out;\n}\n\n/** @stable */\nexport interface SecretsSetOptions extends SecretsCommonOptions {\n readonly key: string;\n readonly value?: string;\n /** When `true`, read the value from stdin. */\n readonly fromStdin?: boolean;\n}\n\n/** @stable */\nexport async function runSecretsSet(options: SecretsSetOptions): Promise<{ readonly ok: true }> {\n const store = await openStore(options);\n let raw = options.value;\n if (raw === undefined && options.fromStdin === true) {\n raw = await readStdin();\n }\n if (raw === undefined) {\n throw new Error(\n '[graphorin/cli] secrets set requires either --value <v> or --from-stdin (no plaintext on the command line).',\n );\n }\n await store.set(\n options.key,\n SecretValue.fromString(raw, { source: { resolver: 'graphorin secrets set' } }),\n );\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' written.`));\n });\n return { ok: true };\n}\n\n/** @stable */\nexport interface SecretsDeleteOptions extends SecretsCommonOptions {\n readonly key: string;\n}\n\n/** @stable */\nexport async function runSecretsDelete(\n options: SecretsDeleteOptions,\n): Promise<{ readonly ok: true }> {\n const store = await openStore(options);\n await store.delete(options.key);\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' deleted.`));\n });\n return { ok: true };\n}\n\n/** @stable */\nexport interface SecretsRefOptions extends CommonOutputOptions {\n readonly uri: string;\n readonly reveal?: boolean;\n}\n\n/** @stable */\nexport interface SecretsRefResult {\n readonly uri: string;\n readonly scheme: string;\n readonly resolved: boolean;\n readonly length?: number;\n readonly value?: string;\n}\n\n/**\n * Test resolution of a `SecretRef` URI. The CLI parses the URI first\n * (sanity check + scheme echo), then resolves through the registered\n * resolver chain.\n *\n * @stable\n */\nexport async function runSecretsRef(options: SecretsRefOptions): Promise<SecretsRefResult> {\n const parsed = parseSecretRef(options.uri);\n try {\n const value = await resolveSecret(options.uri);\n const length = await value.use((s) => s.length);\n const raw = options.reveal === true ? await value.use((s) => String(s)) : undefined;\n const out: SecretsRefResult = Object.freeze({\n uri: options.uri,\n scheme: parsed.scheme,\n resolved: true,\n length,\n ...(raw !== undefined ? { value: raw } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `${statusMarker('ok')} resolved '${parsed.scheme}' ref (length=${length}${raw !== undefined ? `, value='${raw}'` : ''})`,\n ),\n );\n });\n return out;\n } catch (err) {\n const out: SecretsRefResult = Object.freeze({\n uri: options.uri,\n scheme: parsed.scheme,\n resolved: false,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(`${statusMarker('fail')} ${parsed.scheme} ref failed: ${(err as Error).message}`),\n );\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n }\n}\n\n/** @stable */\nexport interface SecretsRotateOptions extends SecretsCommonOptions {\n readonly key: string;\n readonly newValue?: string;\n readonly fromStdin?: boolean;\n}\n\n/**\n * `graphorin secrets rotate <key>` - overwrite the existing value\n * with a fresh one. Functionally identical to `set` but the CLI\n * surfaces the operation explicitly so audit logs can distinguish\n * a rotation from an initial write.\n *\n * @stable\n */\nexport async function runSecretsRotate(\n options: SecretsRotateOptions,\n): Promise<{ readonly ok: true }> {\n const store = await openStore(options);\n const existing = await store.get(options.key);\n if (existing === null) {\n throw new Error(\n `[graphorin/cli] cannot rotate '${options.key}' - secret does not exist (use 'graphorin secrets set' to create it).`,\n );\n }\n let raw = options.newValue;\n if (raw === undefined && options.fromStdin === true) raw = await readStdin();\n if (raw === undefined) {\n throw new Error(\n '[graphorin/cli] secrets rotate requires either --new-value <v> or --from-stdin.',\n );\n }\n await store.set(\n options.key,\n SecretValue.fromString(raw, { source: { resolver: 'graphorin secrets rotate' } }),\n );\n emitReport(options, { ok: true } as const, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`secret '${options.key}' rotated.`));\n });\n return { ok: true };\n}\n\nasync function openStore(options: SecretsCommonOptions) {\n // Reuse the per-process active store when the caller did not request\n // a different kind. The singleton matches the running server / lib\n // process so consecutive CLI invocations within the same process\n // address the same backing store (this matters for the in-memory\n // store + for subprocess CLI flows that share a parent's resolver\n // chain via env vars).\n if (options.secretsSource === undefined) {\n const active = getActiveSecretsStore();\n if (active !== undefined) return active;\n }\n return await createSecretsStore({\n ...(options.secretsSource !== undefined ? { kind: options.secretsSource } : {}),\n ...(options.strictSecrets !== undefined ? { strict: options.strictSecrets } : {}),\n });\n}\n\nasync function readStdin(): Promise<string> {\n return await new Promise((resolve, reject) => {\n let buf = '';\n stdin.setEncoding('utf8');\n stdin.on('data', (chunk) => {\n buf += chunk;\n });\n stdin.on('end', () => resolve(buf.replace(/\\r?\\n$/, '')));\n stdin.on('error', reject);\n });\n}\n"],"mappings":"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"}
|
package/dist/commands/skills.js
CHANGED
|
@@ -9,20 +9,20 @@ import { migrateFrontmatter } from "@graphorin/skills";
|
|
|
9
9
|
|
|
10
10
|
//#region src/commands/skills.ts
|
|
11
11
|
/**
|
|
12
|
-
* `graphorin skills`
|
|
12
|
+
* `graphorin skills` - install + audit + migrate operator-managed
|
|
13
13
|
* skill packages.
|
|
14
14
|
*
|
|
15
15
|
* Surface (per Phase 15 § Skills):
|
|
16
16
|
*
|
|
17
|
-
* - `graphorin skills install <source>`
|
|
17
|
+
* - `graphorin skills install <source>` - `npm:` or `git:` source.
|
|
18
18
|
* Honours allowlist / denylist + signature verification (DEC-140 /
|
|
19
19
|
* ADR-034). Refuses when `GRAPHORIN_OFFLINE=1` is set.
|
|
20
|
-
* - `graphorin skills inspect <name>`
|
|
20
|
+
* - `graphorin skills inspect <name>` - frontmatter + signature +
|
|
21
21
|
* supply-chain status of an installed skill.
|
|
22
|
-
* - `graphorin skills audit`
|
|
22
|
+
* - `graphorin skills audit` - full audit of every recorded
|
|
23
23
|
* installation.
|
|
24
24
|
* - `graphorin skills migrate-frontmatter [--apply] [--recursive]`
|
|
25
|
-
*
|
|
25
|
+
* - DEC-156: rewrites legacy `graphorin-*` frontmatter fields onto
|
|
26
26
|
* their upstream equivalents.
|
|
27
27
|
*
|
|
28
28
|
* @packageDocumentation
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills.js","names":["result: SkillInstallationStatus","migrated: Array<{ file: string; result: MigrationResult }>","result: MigrationResult","out: SkillsMigrateFrontmatterResult","out: string[]","entries: string[]","s: import('node:fs').Stats"],"sources":["../../src/commands/skills.ts"],"sourcesContent":["/**\n * `graphorin skills` — install + audit + migrate operator-managed\n * skill packages.\n *\n * Surface (per Phase 15 § Skills):\n *\n * - `graphorin skills install <source>` — `npm:` or `git:` source.\n * Honours allowlist / denylist + signature verification (DEC-140 /\n * ADR-034). Refuses when `GRAPHORIN_OFFLINE=1` is set.\n * - `graphorin skills inspect <name>` — frontmatter + signature +\n * supply-chain status of an installed skill.\n * - `graphorin skills audit` — full audit of every recorded\n * installation.\n * - `graphorin skills migrate-frontmatter [--apply] [--recursive]`\n * — DEC-156: rewrites legacy `graphorin-*` frontmatter fields onto\n * their upstream equivalents.\n *\n * @packageDocumentation\n */\n\nimport { readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport {\n auditInstalledSkills,\n type InstallSkillFromGitOptions,\n type InstallSkillFromNpmOptions,\n installSkillFromGit,\n installSkillFromNpm,\n type SkillInstallationStatus,\n} from '@graphorin/security';\nimport { loadSkills, type MigrationResult, migrateFrontmatter } from '@graphorin/skills';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { checkOfflineModeBlocked } from '../internal/offline.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface SkillsCommonOptions extends CommonOutputOptions {}\n\n/** @stable */\nexport type SkillTrustLevelInput = 'trusted' | 'trusted-with-scripts' | 'untrusted';\n\n/** @stable */\nexport interface SkillsInstallOptions extends SkillsCommonOptions {\n /** `npm:<name>[@version]` or `git:<url>` source. */\n readonly source: string;\n /** Optional explicit version pin (npm sources only). */\n readonly version?: string;\n /** Optional git ref (git sources only). */\n readonly ref?: string;\n /** Trust level for the operator's project. Defaults to the helper's own default. */\n readonly trustLevel?: SkillTrustLevelInput;\n /** Working directory for npm installs. */\n readonly cwd?: string;\n readonly dryRun?: boolean;\n}\n\n/** @stable */\nexport async function runSkillsInstall(\n options: SkillsInstallOptions,\n): Promise<SkillInstallationStatus> {\n if (\n !checkOfflineModeBlocked('skills install', {\n ...(options.print !== undefined ? { print: options.print } : {}),\n })\n ) {\n process.exit(EXIT_CODES.RECOVERABLE_FAILURE);\n }\n const print = options.print ?? defaultPrintSink;\n const parsed = parseSource(options.source);\n let result: SkillInstallationStatus;\n if (parsed.kind === 'npm') {\n const installOpts: InstallSkillFromNpmOptions = {\n packageName: parsed.value,\n ...(options.version !== undefined ? { version: options.version } : {}),\n ...(options.trustLevel !== undefined ? { trustLevel: options.trustLevel } : {}),\n ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n ...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),\n };\n result = await installSkillFromNpm(installOpts);\n } else {\n const installOpts: InstallSkillFromGitOptions = {\n repoUrl: parsed.value,\n ...(options.ref !== undefined ? { ref: options.ref } : {}),\n ...(options.trustLevel !== undefined ? { trustLevel: options.trustLevel } : {}),\n ...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),\n };\n result = await installSkillFromGit(installOpts);\n }\n emitReport(options, result, () => {\n print(\n brand(\n `installed skill '${result.id}' (${result.source.kind}, signatureVerified=${result.signatureVerified})`,\n ),\n );\n if (result.signature !== undefined) {\n print(` signature valid: ${result.signature.valid}`);\n }\n });\n return result;\n}\n\n/** @stable */\nexport interface SkillsInspectOptions extends SkillsCommonOptions {\n readonly name: string;\n}\n\n/** @stable */\nexport async function runSkillsInspect(\n options: SkillsInspectOptions,\n): Promise<SkillInstallationStatus | null> {\n const all = auditInstalledSkills();\n const match = all.find((s) => s.id === options.name) ?? null;\n emitReport(options, match, () => {\n const print = options.print ?? defaultPrintSink;\n if (match === null) {\n print(brand(`skill '${options.name}' not found in this process registry.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`skill ${match.id}`));\n print(` source: ${match.source.kind}`);\n print(` trust level: ${match.trustLevel}`);\n print(` ignoreScripts: ${match.ignoreScripts}`);\n print(` signatureVerified: ${match.signatureVerified}`);\n print(` installedAt: ${new Date(match.installedAt).toISOString()}`);\n if (match.installPath !== undefined) print(` installPath: ${match.installPath}`);\n if (match.publisher !== undefined) print(` publisher: ${match.publisher}`);\n });\n return match;\n}\n\n/** @stable */\nexport interface SkillsAuditOptions extends SkillsCommonOptions {}\n\n/** @stable */\nexport function runSkillsAudit(options: SkillsAuditOptions = {}) {\n const all = auditInstalledSkills();\n emitReport(options, all, () => {\n const print = options.print ?? defaultPrintSink;\n if (all.length === 0) {\n print(brand('no skills installed in this process registry.'));\n return;\n }\n print(brand(`${all.length} installed skill(s):`));\n for (const s of all) {\n const mark = s.signatureVerified ? statusMarker('ok') : statusMarker('warn');\n print(\n ` ${mark} ${s.id} (${s.source.kind}, trust=${s.trustLevel}, signatureVerified=${s.signatureVerified})`,\n );\n }\n });\n return all;\n}\n\n/** @stable */\nexport interface SkillsMigrateFrontmatterOptions extends SkillsCommonOptions {\n /** Directory to walk. Defaults to `process.cwd()`. */\n readonly path?: string;\n readonly recursive?: boolean;\n readonly apply?: boolean;\n}\n\n/** @stable */\nexport interface SkillsMigrateFrontmatterResult {\n readonly directory: string;\n readonly visited: number;\n readonly migrated: ReadonlyArray<{\n readonly file: string;\n readonly result: MigrationResult;\n }>;\n readonly applied: boolean;\n}\n\n/** @stable */\nexport async function runSkillsMigrateFrontmatter(\n options: SkillsMigrateFrontmatterOptions = {},\n): Promise<SkillsMigrateFrontmatterResult> {\n const root = resolve(options.path ?? process.cwd());\n const recursive = options.recursive === true;\n const apply = options.apply === true;\n const files = await collectSkillMd(root, recursive);\n const migrated: Array<{ file: string; result: MigrationResult }> = [];\n for (const file of files) {\n const raw = await readFile(file, 'utf8');\n let result: MigrationResult;\n try {\n result = migrateFrontmatter(raw, { apply, skillId: file });\n } catch {\n continue;\n }\n if (result.changed) {\n if (apply) {\n await writeFile(file, result.migratedSkillMd, { mode: 0o600 });\n }\n migrated.push({ file, result });\n }\n }\n const out: SkillsMigrateFrontmatterResult = Object.freeze({\n directory: root,\n visited: files.length,\n migrated: Object.freeze(migrated),\n applied: apply,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(`migrate-frontmatter visited ${out.visited} SKILL.md file(s) under ${out.directory}.`),\n );\n if (out.migrated.length === 0) {\n print(brand('no rewrites required.'));\n return;\n }\n print(\n brand(\n `${out.migrated.length} file(s) ${apply ? 'rewritten' : 'would be rewritten (dry-run)'}:`,\n ),\n );\n for (const m of out.migrated) print(` - ${m.file}`);\n });\n return out;\n}\n\nfunction parseSource(input: string): { readonly kind: 'npm' | 'git'; readonly value: string } {\n if (input.startsWith('npm:')) return { kind: 'npm', value: input.slice(4) };\n if (input.startsWith('git:')) return { kind: 'git', value: input.slice(4) };\n if (input.includes('://')) return { kind: 'git', value: input };\n return { kind: 'npm', value: input };\n}\n\nasync function collectSkillMd(root: string, recursive: boolean): Promise<string[]> {\n const out: string[] = [];\n await walk(root, recursive, out);\n return out;\n}\n\nasync function walk(dir: string, recursive: boolean, acc: string[]): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name === 'node_modules' || name === '.git' || name === 'dist') continue;\n const full = join(dir, name);\n let s: import('node:fs').Stats;\n try {\n s = await stat(full);\n } catch {\n continue;\n }\n if (s.isDirectory()) {\n if (recursive) await walk(full, recursive, acc);\n } else if (name === 'SKILL.md') {\n acc.push(full);\n }\n }\n}\n\n// Touch the loader once so the import is preserved (downstream callers\n// that want to re-load the migrated skill consume `loadSkills(...)`).\nvoid loadSkills;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"skills.js","names":["result: SkillInstallationStatus","migrated: Array<{ file: string; result: MigrationResult }>","result: MigrationResult","out: SkillsMigrateFrontmatterResult","out: string[]","entries: string[]","s: import('node:fs').Stats"],"sources":["../../src/commands/skills.ts"],"sourcesContent":["/**\n * `graphorin skills` - install + audit + migrate operator-managed\n * skill packages.\n *\n * Surface (per Phase 15 § Skills):\n *\n * - `graphorin skills install <source>` - `npm:` or `git:` source.\n * Honours allowlist / denylist + signature verification (DEC-140 /\n * ADR-034). Refuses when `GRAPHORIN_OFFLINE=1` is set.\n * - `graphorin skills inspect <name>` - frontmatter + signature +\n * supply-chain status of an installed skill.\n * - `graphorin skills audit` - full audit of every recorded\n * installation.\n * - `graphorin skills migrate-frontmatter [--apply] [--recursive]`\n * - DEC-156: rewrites legacy `graphorin-*` frontmatter fields onto\n * their upstream equivalents.\n *\n * @packageDocumentation\n */\n\nimport { readdir, readFile, stat, writeFile } from 'node:fs/promises';\nimport { join, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport {\n auditInstalledSkills,\n type InstallSkillFromGitOptions,\n type InstallSkillFromNpmOptions,\n installSkillFromGit,\n installSkillFromNpm,\n type SkillInstallationStatus,\n} from '@graphorin/security';\nimport { loadSkills, type MigrationResult, migrateFrontmatter } from '@graphorin/skills';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { checkOfflineModeBlocked } from '../internal/offline.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface SkillsCommonOptions extends CommonOutputOptions {}\n\n/** @stable */\nexport type SkillTrustLevelInput = 'trusted' | 'trusted-with-scripts' | 'untrusted';\n\n/** @stable */\nexport interface SkillsInstallOptions extends SkillsCommonOptions {\n /** `npm:<name>[@version]` or `git:<url>` source. */\n readonly source: string;\n /** Optional explicit version pin (npm sources only). */\n readonly version?: string;\n /** Optional git ref (git sources only). */\n readonly ref?: string;\n /** Trust level for the operator's project. Defaults to the helper's own default. */\n readonly trustLevel?: SkillTrustLevelInput;\n /** Working directory for npm installs. */\n readonly cwd?: string;\n readonly dryRun?: boolean;\n}\n\n/** @stable */\nexport async function runSkillsInstall(\n options: SkillsInstallOptions,\n): Promise<SkillInstallationStatus> {\n if (\n !checkOfflineModeBlocked('skills install', {\n ...(options.print !== undefined ? { print: options.print } : {}),\n })\n ) {\n process.exit(EXIT_CODES.RECOVERABLE_FAILURE);\n }\n const print = options.print ?? defaultPrintSink;\n const parsed = parseSource(options.source);\n let result: SkillInstallationStatus;\n if (parsed.kind === 'npm') {\n const installOpts: InstallSkillFromNpmOptions = {\n packageName: parsed.value,\n ...(options.version !== undefined ? { version: options.version } : {}),\n ...(options.trustLevel !== undefined ? { trustLevel: options.trustLevel } : {}),\n ...(options.cwd !== undefined ? { cwd: options.cwd } : {}),\n ...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),\n };\n result = await installSkillFromNpm(installOpts);\n } else {\n const installOpts: InstallSkillFromGitOptions = {\n repoUrl: parsed.value,\n ...(options.ref !== undefined ? { ref: options.ref } : {}),\n ...(options.trustLevel !== undefined ? { trustLevel: options.trustLevel } : {}),\n ...(options.dryRun !== undefined ? { dryRun: options.dryRun } : {}),\n };\n result = await installSkillFromGit(installOpts);\n }\n emitReport(options, result, () => {\n print(\n brand(\n `installed skill '${result.id}' (${result.source.kind}, signatureVerified=${result.signatureVerified})`,\n ),\n );\n if (result.signature !== undefined) {\n print(` signature valid: ${result.signature.valid}`);\n }\n });\n return result;\n}\n\n/** @stable */\nexport interface SkillsInspectOptions extends SkillsCommonOptions {\n readonly name: string;\n}\n\n/** @stable */\nexport async function runSkillsInspect(\n options: SkillsInspectOptions,\n): Promise<SkillInstallationStatus | null> {\n const all = auditInstalledSkills();\n const match = all.find((s) => s.id === options.name) ?? null;\n emitReport(options, match, () => {\n const print = options.print ?? defaultPrintSink;\n if (match === null) {\n print(brand(`skill '${options.name}' not found in this process registry.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`skill ${match.id}`));\n print(` source: ${match.source.kind}`);\n print(` trust level: ${match.trustLevel}`);\n print(` ignoreScripts: ${match.ignoreScripts}`);\n print(` signatureVerified: ${match.signatureVerified}`);\n print(` installedAt: ${new Date(match.installedAt).toISOString()}`);\n if (match.installPath !== undefined) print(` installPath: ${match.installPath}`);\n if (match.publisher !== undefined) print(` publisher: ${match.publisher}`);\n });\n return match;\n}\n\n/** @stable */\nexport interface SkillsAuditOptions extends SkillsCommonOptions {}\n\n/** @stable */\nexport function runSkillsAudit(options: SkillsAuditOptions = {}) {\n const all = auditInstalledSkills();\n emitReport(options, all, () => {\n const print = options.print ?? defaultPrintSink;\n if (all.length === 0) {\n print(brand('no skills installed in this process registry.'));\n return;\n }\n print(brand(`${all.length} installed skill(s):`));\n for (const s of all) {\n const mark = s.signatureVerified ? statusMarker('ok') : statusMarker('warn');\n print(\n ` ${mark} ${s.id} (${s.source.kind}, trust=${s.trustLevel}, signatureVerified=${s.signatureVerified})`,\n );\n }\n });\n return all;\n}\n\n/** @stable */\nexport interface SkillsMigrateFrontmatterOptions extends SkillsCommonOptions {\n /** Directory to walk. Defaults to `process.cwd()`. */\n readonly path?: string;\n readonly recursive?: boolean;\n readonly apply?: boolean;\n}\n\n/** @stable */\nexport interface SkillsMigrateFrontmatterResult {\n readonly directory: string;\n readonly visited: number;\n readonly migrated: ReadonlyArray<{\n readonly file: string;\n readonly result: MigrationResult;\n }>;\n readonly applied: boolean;\n}\n\n/** @stable */\nexport async function runSkillsMigrateFrontmatter(\n options: SkillsMigrateFrontmatterOptions = {},\n): Promise<SkillsMigrateFrontmatterResult> {\n const root = resolve(options.path ?? process.cwd());\n const recursive = options.recursive === true;\n const apply = options.apply === true;\n const files = await collectSkillMd(root, recursive);\n const migrated: Array<{ file: string; result: MigrationResult }> = [];\n for (const file of files) {\n const raw = await readFile(file, 'utf8');\n let result: MigrationResult;\n try {\n result = migrateFrontmatter(raw, { apply, skillId: file });\n } catch {\n continue;\n }\n if (result.changed) {\n if (apply) {\n await writeFile(file, result.migratedSkillMd, { mode: 0o600 });\n }\n migrated.push({ file, result });\n }\n }\n const out: SkillsMigrateFrontmatterResult = Object.freeze({\n directory: root,\n visited: files.length,\n migrated: Object.freeze(migrated),\n applied: apply,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(`migrate-frontmatter visited ${out.visited} SKILL.md file(s) under ${out.directory}.`),\n );\n if (out.migrated.length === 0) {\n print(brand('no rewrites required.'));\n return;\n }\n print(\n brand(\n `${out.migrated.length} file(s) ${apply ? 'rewritten' : 'would be rewritten (dry-run)'}:`,\n ),\n );\n for (const m of out.migrated) print(` - ${m.file}`);\n });\n return out;\n}\n\nfunction parseSource(input: string): { readonly kind: 'npm' | 'git'; readonly value: string } {\n if (input.startsWith('npm:')) return { kind: 'npm', value: input.slice(4) };\n if (input.startsWith('git:')) return { kind: 'git', value: input.slice(4) };\n if (input.includes('://')) return { kind: 'git', value: input };\n return { kind: 'npm', value: input };\n}\n\nasync function collectSkillMd(root: string, recursive: boolean): Promise<string[]> {\n const out: string[] = [];\n await walk(root, recursive, out);\n return out;\n}\n\nasync function walk(dir: string, recursive: boolean, acc: string[]): Promise<void> {\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch {\n return;\n }\n for (const name of entries) {\n if (name === 'node_modules' || name === '.git' || name === 'dist') continue;\n const full = join(dir, name);\n let s: import('node:fs').Stats;\n try {\n s = await stat(full);\n } catch {\n continue;\n }\n if (s.isDirectory()) {\n if (recursive) await walk(full, recursive, acc);\n } else if (name === 'SKILL.md') {\n acc.push(full);\n }\n }\n}\n\n// Touch the loader once so the import is preserved (downstream callers\n// that want to re-load the migrated skill consume `loadSkills(...)`).\nvoid loadSkills;\n"],"mappings":"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"}
|
package/dist/commands/start.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ interface StartCommandOptions {
|
|
|
36
36
|
readonly strictSecrets?: boolean;
|
|
37
37
|
}
|
|
38
38
|
/**
|
|
39
|
-
* Programmatic entry
|
|
39
|
+
* Programmatic entry - used both by the CLI binary and by tests so
|
|
40
40
|
* the spawn cost of running the binary is paid only when an operator
|
|
41
41
|
* actually invokes `graphorin start` from a shell.
|
|
42
42
|
*
|
package/dist/commands/start.js
CHANGED
|
@@ -15,7 +15,7 @@ import { ConfigInvalidError, GraphorinServerError, createServer } from "@graphor
|
|
|
15
15
|
* @packageDocumentation
|
|
16
16
|
*/
|
|
17
17
|
/**
|
|
18
|
-
* Programmatic entry
|
|
18
|
+
* Programmatic entry - used both by the CLI binary and by tests so
|
|
19
19
|
* the spawn cost of running the binary is paid only when an operator
|
|
20
20
|
* actually invokes `graphorin start` from a shell.
|
|
21
21
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.js","names":["server: Awaited<ReturnType<typeof createServer>> | undefined"],"sources":["../../src/commands/start.ts"],"sourcesContent":["/**\n * `graphorin start [--config <path>]`. Boots the standalone server.\n *\n * Exits 1 on every recoverable failure (missing pepper, unresolvable\n * SecretRef, missing encryption peer, migration error). The CLI never\n * prints raw secret values; failure messages reference the offending\n * config path + the suggested `graphorin doctor` follow-up (Phase 15).\n *\n * @packageDocumentation\n */\n\nimport process from 'node:process';\n\nimport { applyProcessHardening, RefuseToRunAsRootError } from '@graphorin/security';\nimport { ConfigInvalidError, createServer, GraphorinServerError } from '@graphorin/server';\n\nimport { loadConfig } from '../internal/load-config.js';\n\n/**\n * Selector for which `SecretsStore` flavour the server activates at\n * startup. Mirrors `--secrets-source` from DEC-136.\n *\n * @stable\n */\nexport type SecretsSourceFlag = 'auto' | 'keyring' | 'encrypted-file' | 'env';\n\n/**\n * @stable\n */\nexport interface StartCommandOptions {\n readonly config?: string;\n readonly host?: string;\n readonly port?: number;\n readonly logResolved?: boolean;\n /**\n * Override the `secrets.source` field of the loaded config. Mirrors\n * the `--secrets-source <kind>` flag from DEC-136.\n */\n readonly secretsSource?: SecretsSourceFlag;\n /**\n * Refuse to fall back when the requested primary store is\n * unavailable. Mirrors `--strict-secrets` from DEC-136.\n */\n readonly strictSecrets?: boolean;\n}\n\n/**\n * Programmatic entry
|
|
1
|
+
{"version":3,"file":"start.js","names":["server: Awaited<ReturnType<typeof createServer>> | undefined"],"sources":["../../src/commands/start.ts"],"sourcesContent":["/**\n * `graphorin start [--config <path>]`. Boots the standalone server.\n *\n * Exits 1 on every recoverable failure (missing pepper, unresolvable\n * SecretRef, missing encryption peer, migration error). The CLI never\n * prints raw secret values; failure messages reference the offending\n * config path + the suggested `graphorin doctor` follow-up (Phase 15).\n *\n * @packageDocumentation\n */\n\nimport process from 'node:process';\n\nimport { applyProcessHardening, RefuseToRunAsRootError } from '@graphorin/security';\nimport { ConfigInvalidError, createServer, GraphorinServerError } from '@graphorin/server';\n\nimport { loadConfig } from '../internal/load-config.js';\n\n/**\n * Selector for which `SecretsStore` flavour the server activates at\n * startup. Mirrors `--secrets-source` from DEC-136.\n *\n * @stable\n */\nexport type SecretsSourceFlag = 'auto' | 'keyring' | 'encrypted-file' | 'env';\n\n/**\n * @stable\n */\nexport interface StartCommandOptions {\n readonly config?: string;\n readonly host?: string;\n readonly port?: number;\n readonly logResolved?: boolean;\n /**\n * Override the `secrets.source` field of the loaded config. Mirrors\n * the `--secrets-source <kind>` flag from DEC-136.\n */\n readonly secretsSource?: SecretsSourceFlag;\n /**\n * Refuse to fall back when the requested primary store is\n * unavailable. Mirrors `--strict-secrets` from DEC-136.\n */\n readonly strictSecrets?: boolean;\n}\n\n/**\n * Programmatic entry - used both by the CLI binary and by tests so\n * the spawn cost of running the binary is paid only when an operator\n * actually invokes `graphorin start` from a shell.\n *\n * @stable\n */\nexport async function runStart(\n options: StartCommandOptions = {},\n): Promise<{ readonly host: string; readonly port: number }> {\n applyHardeningEarly();\n const loaded = await loadConfig(options.config);\n if (options.logResolved !== false) {\n process.stderr.write(`[graphorin/cli] resolved config: ${loaded.path}\\n`);\n }\n const overrides = applyCliOverrides(loaded.config, options);\n\n let server: Awaited<ReturnType<typeof createServer>> | undefined;\n try {\n server = await createServer({ config: overrides });\n } catch (err) {\n fatal(err);\n }\n if (server === undefined) process.exit(1);\n\n const listening = await safeStart(server);\n process.stderr.write(\n `[graphorin/cli] @graphorin/server v${server.version} listening on http://${listening.host}:${listening.port}${server.config.server.basePath}\\n`,\n );\n installSignalHandlers(server);\n return listening;\n}\n\n/**\n * Merge CLI flag overrides into the loaded `graphorin.config` payload.\n * Exported for unit tests so callers can assert the precedence rules\n * without spinning up a real server.\n *\n * @internal\n */\nexport function applyCliOverrides(\n input: { readonly server?: unknown; readonly secrets?: unknown } | undefined,\n options: StartCommandOptions,\n): Record<string, unknown> {\n const next = { ...(input ?? {}) } as Record<string, unknown>;\n const serverInput = (next.server as Record<string, unknown> | undefined) ?? {};\n if (options.host !== undefined || options.port !== undefined) {\n next.server = {\n ...serverInput,\n ...(options.host !== undefined ? { host: options.host } : {}),\n ...(options.port !== undefined ? { port: options.port } : {}),\n };\n }\n const secretsInput = (next.secrets as Record<string, unknown> | undefined) ?? {};\n if (options.secretsSource !== undefined || options.strictSecrets !== undefined) {\n next.secrets = {\n ...secretsInput,\n ...(options.secretsSource !== undefined ? { source: options.secretsSource } : {}),\n ...(options.strictSecrets !== undefined ? { strict: options.strictSecrets } : {}),\n };\n }\n return next;\n}\n\nasync function safeStart(server: Awaited<ReturnType<typeof createServer>>) {\n try {\n return await server.start();\n } catch (err) {\n fatal(err);\n }\n process.exit(1);\n}\n\nfunction fatal(err: unknown): never {\n if (err instanceof ConfigInvalidError) {\n process.stderr.write('[graphorin/cli] graphorin.config invalid:\\n');\n for (const issue of err.issues) {\n process.stderr.write(` - ${issue.path.join('.') || '<root>'}: ${issue.message}\\n`);\n }\n } else if (err instanceof GraphorinServerError) {\n process.stderr.write(`[graphorin/cli] ${err.kind}: ${err.message}\\n`);\n if (err.hint !== undefined) {\n process.stderr.write(` hint: ${err.hint}\\n`);\n }\n } else if (err instanceof Error) {\n process.stderr.write(`[graphorin/cli] ${err.message}\\n`);\n } else {\n process.stderr.write(`[graphorin/cli] unknown error: ${String(err)}\\n`);\n }\n process.exit(1);\n}\n\n/**\n * Apply the standard hardening hooks before the server bootstraps.\n * Failures (e.g. running as root on Linux/macOS) abort the start with\n * a documented exit code.\n *\n * @internal\n */\nexport function applyHardeningEarly(): void {\n try {\n applyProcessHardening({});\n } catch (err) {\n if (err instanceof RefuseToRunAsRootError) {\n process.stderr.write(`[graphorin/cli] ${err.message}\\n`);\n process.stderr.write(\n '[graphorin/cli] hint: drop privileges (systemd User=, k8s runAsNonRoot, docker --user) and retry.\\n',\n );\n process.exit(1);\n }\n throw err;\n }\n}\n\nfunction installSignalHandlers(server: Awaited<ReturnType<typeof createServer>>): void {\n let shuttingDown = false;\n const onSignal = (signal: NodeJS.Signals) => {\n if (shuttingDown) return;\n shuttingDown = true;\n process.stderr.write(`[graphorin/cli] received ${signal}; draining...\\n`);\n server\n .stop()\n .then(() => {\n process.stderr.write('[graphorin/cli] graceful shutdown complete\\n');\n process.exit(0);\n })\n .catch((err) => {\n process.stderr.write(`[graphorin/cli] shutdown error: ${(err as Error).message}\\n`);\n process.exit(1);\n });\n };\n process.once('SIGTERM', onSignal);\n process.once('SIGINT', onSignal);\n}\n"],"mappings":"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"}
|
|
@@ -35,6 +35,30 @@ interface StorageStatusResult {
|
|
|
35
35
|
/** @stable */
|
|
36
36
|
declare function runStorageStatus(options?: StorageCommonOptions): Promise<StorageStatusResult>;
|
|
37
37
|
/** @stable */
|
|
38
|
+
interface StorageBackupOptions extends StorageCommonOptions {
|
|
39
|
+
/** Destination file path for the backup copy. */
|
|
40
|
+
readonly dest: string;
|
|
41
|
+
/** Replace an existing destination file. Default: refuse. */
|
|
42
|
+
readonly overwrite?: boolean;
|
|
43
|
+
}
|
|
44
|
+
/** @stable */
|
|
45
|
+
interface StorageBackupResult {
|
|
46
|
+
readonly source: string;
|
|
47
|
+
readonly dest: string;
|
|
48
|
+
readonly sizeBytes?: number;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* store-02: online backup via the driver's page-level `backup()` API -
|
|
52
|
+
* consistent under a live writer (the daemon can keep running),
|
|
53
|
+
* preserves rowids so FTS5 external-content mappings survive, and for
|
|
54
|
+
* an encrypted store produces an equally-encrypted copy (same key).
|
|
55
|
+
* This is the ONLY supported SQL-level backup: `VACUUM INTO`
|
|
56
|
+
* renumbers rowids and corrupts the FTS mapping on restore.
|
|
57
|
+
*
|
|
58
|
+
* @stable
|
|
59
|
+
*/
|
|
60
|
+
declare function runStorageBackup(options: StorageBackupOptions): Promise<StorageBackupResult>;
|
|
61
|
+
/** @stable */
|
|
38
62
|
interface StorageEncryptOptions extends StorageCommonOptions {
|
|
39
63
|
/** SecretRef URI for the new passphrase. */
|
|
40
64
|
readonly passphraseFrom: string;
|
|
@@ -61,7 +85,7 @@ interface StorageEncryptResult {
|
|
|
61
85
|
};
|
|
62
86
|
}
|
|
63
87
|
/**
|
|
64
|
-
* `graphorin storage encrypt --passphrase-from <ref>`
|
|
88
|
+
* `graphorin storage encrypt --passphrase-from <ref>` - encrypt a
|
|
65
89
|
* previously unencrypted SQLite store. Delegates to the optional Phase
|
|
66
90
|
* 16 sub-pack `@graphorin/store-sqlite-encrypted` once installed; when
|
|
67
91
|
* the sub-pack is missing the CLI exits `2` (`UNSUPPORTED`) with an
|
|
@@ -106,5 +130,5 @@ interface StorageCleanupBackupsResult {
|
|
|
106
130
|
*/
|
|
107
131
|
declare function runStorageCleanupBackups(options?: StorageCleanupBackupsOptions): Promise<StorageCleanupBackupsResult>;
|
|
108
132
|
//#endregion
|
|
109
|
-
export { StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus };
|
|
133
|
+
export { StorageBackupOptions, StorageBackupResult, StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus };
|
|
110
134
|
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.d.ts","names":[],"sources":["../../src/commands/storage.ts"],"sourcesContent":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"storage.d.ts","names":[],"sources":["../../src/commands/storage.ts"],"sourcesContent":[],"mappings":";;;;;AAkS4E,UAnP3D,oBAAA,SAA6B,mBAmP8B,CAAA;EA0C3D,SAAA,MAAA,CAAA,EAAA,MAAA;AASjB;AAaA;AACW,UA/SM,mBAAA,CA+SN;EACA,SAAA,IAAA,EAAA,MAAA;EAAR,SAAA,IAAA,EAAA,KAAA,GAAA,QAAA;EAAO,SAAA,UAAA,EAAA;;;;;;;;;;;;;;;;;;;;;;;iBAjSY,gBAAA,WACX,uBACR,QAAQ;;UA+DM,oBAAA,SAA6B;;;;;;;UAQ7B,mBAAA;;;;;;;;;;;;;;;iBAgBK,gBAAA,UACX,uBACR,QAAQ;;UAqCM,qBAAA,SAA8B;;;;;;;;;;;;;;;;UAiB9B,oBAAA;;;;;;;;;;;;;;;;;;iBAiBK,iBAAA,UACX,wBACR,QAAQ;;UA8CM,mBAAA,SAA4B;;;;;UAM5B,kBAAA;;;;;;iBAOK,eAAA,UAAyB,sBAAsB,QAAQ;;UA0C5D,4BAAA,SAAqC;;;;;;;;UASrC,2BAAA;;oBAEG;;;;;;;;;;iBAWE,wBAAA,WACX,+BACR,QAAQ"}
|
package/dist/commands/storage.js
CHANGED
|
@@ -2,27 +2,31 @@ import { brand, defaultPrintSink, emitReport, statusMarker } from "../internal/o
|
|
|
2
2
|
import { EXIT_CODES } from "../internal/exit.js";
|
|
3
3
|
import { loadConfig } from "../internal/load-config.js";
|
|
4
4
|
import { readdir, stat, unlink } from "node:fs/promises";
|
|
5
|
-
import { dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { resolveSecret } from "@graphorin/security";
|
|
8
8
|
import { parseServerConfig } from "@graphorin/server";
|
|
9
9
|
|
|
10
10
|
//#region src/commands/storage.ts
|
|
11
11
|
/**
|
|
12
|
-
* `graphorin storage`
|
|
12
|
+
* `graphorin storage` - manage the SQLite store and its encryption-
|
|
13
13
|
* at-rest opt-in.
|
|
14
14
|
*
|
|
15
15
|
* Surface (per Phase 15 § Storage):
|
|
16
16
|
*
|
|
17
|
-
* - `graphorin storage status`
|
|
17
|
+
* - `graphorin storage status` - reports cipher peer + WAL + size +
|
|
18
18
|
* encryption mode.
|
|
19
|
-
* - `graphorin storage encrypt --passphrase-from <ref>`
|
|
19
|
+
* - `graphorin storage encrypt --passphrase-from <ref>` - opt-in
|
|
20
20
|
* encryption migration. Requires the `@graphorin/store-sqlite-
|
|
21
21
|
* encrypted` sub-pack from Phase 16.
|
|
22
|
-
* - `graphorin storage rekey --new-passphrase-from <ref>`
|
|
22
|
+
* - `graphorin storage rekey --new-passphrase-from <ref>` - re-key
|
|
23
23
|
* an already-encrypted DB.
|
|
24
|
-
* - `graphorin storage cleanup-backups`
|
|
24
|
+
* - `graphorin storage cleanup-backups` - drop stale `.bak` /
|
|
25
25
|
* `.bak.<ts>` files left by previous encrypt / rekey runs.
|
|
26
|
+
* - `graphorin storage backup <dest>` - online, consistent copy via
|
|
27
|
+
* the driver's page-level backup API (store-02). Safe under a live
|
|
28
|
+
* writer, preserves rowids (FTS5 mappings survive). Never use
|
|
29
|
+
* `VACUUM INTO` - rowid renumbering corrupts FTS mappings.
|
|
26
30
|
*
|
|
27
31
|
* `encrypt`, `rekey`, and `cleanup-backups` need the cipher peer
|
|
28
32
|
* (`better-sqlite3-multiple-ciphers`) which ships in the optional
|
|
@@ -83,7 +87,43 @@ async function runStorageStatus(options = {}) {
|
|
|
83
87
|
return out;
|
|
84
88
|
}
|
|
85
89
|
/**
|
|
86
|
-
*
|
|
90
|
+
* store-02: online backup via the driver's page-level `backup()` API -
|
|
91
|
+
* consistent under a live writer (the daemon can keep running),
|
|
92
|
+
* preserves rowids so FTS5 external-content mappings survive, and for
|
|
93
|
+
* an encrypted store produces an equally-encrypted copy (same key).
|
|
94
|
+
* This is the ONLY supported SQL-level backup: `VACUUM INTO`
|
|
95
|
+
* renumbers rowids and corrupts the FTS mapping on restore.
|
|
96
|
+
*
|
|
97
|
+
* @stable
|
|
98
|
+
*/
|
|
99
|
+
async function runStorageBackup(options) {
|
|
100
|
+
const { openStoreContext } = await import("../internal/store-context.js");
|
|
101
|
+
const dest = isAbsolute(options.dest) ? options.dest : resolve(process.cwd(), options.dest);
|
|
102
|
+
if ((await statSafely(dest)).exists && options.overwrite !== true) throw new Error(`[graphorin/cli] backup destination already exists: ${dest}. Pass --overwrite to replace it.`);
|
|
103
|
+
const ctx = await openStoreContext({ ...options.config !== void 0 ? { config: options.config } : {} });
|
|
104
|
+
let source;
|
|
105
|
+
try {
|
|
106
|
+
source = ctx.store.connection.path;
|
|
107
|
+
if (source === dest) throw new Error("[graphorin/cli] backup destination must differ from the store path.");
|
|
108
|
+
await ctx.store.connection.raw().backup(dest);
|
|
109
|
+
} finally {
|
|
110
|
+
await ctx.close();
|
|
111
|
+
}
|
|
112
|
+
const written = await statSafely(dest);
|
|
113
|
+
const out = Object.freeze({
|
|
114
|
+
source,
|
|
115
|
+
dest,
|
|
116
|
+
...written.size !== void 0 ? { sizeBytes: written.size } : {}
|
|
117
|
+
});
|
|
118
|
+
emitReport(options, out, () => {
|
|
119
|
+
const print = options.print ?? defaultPrintSink;
|
|
120
|
+
print(brand("storage backup"));
|
|
121
|
+
print(` ${statusMarker("ok")} ${out.source} -> ${out.dest} (${formatSize(out.sizeBytes)})`);
|
|
122
|
+
});
|
|
123
|
+
return out;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* `graphorin storage encrypt --passphrase-from <ref>` - encrypt a
|
|
87
127
|
* previously unencrypted SQLite store. Delegates to the optional Phase
|
|
88
128
|
* 16 sub-pack `@graphorin/store-sqlite-encrypted` once installed; when
|
|
89
129
|
* the sub-pack is missing the CLI exits `2` (`UNSUPPORTED`) with an
|
|
@@ -162,17 +202,17 @@ async function runStorageRekey(options) {
|
|
|
162
202
|
async function runStorageCleanupBackups(options = {}) {
|
|
163
203
|
const dbPath = resolveStoragePath(parseServerConfig((await loadConfig(options.config)).config).storage.path);
|
|
164
204
|
const dir = dirname(dbPath);
|
|
165
|
-
const basename = dbPath
|
|
205
|
+
const basename$1 = basename(dbPath);
|
|
166
206
|
let entries;
|
|
167
207
|
try {
|
|
168
208
|
entries = await readdir(dir);
|
|
169
209
|
} catch (err) {
|
|
170
210
|
throw new Error(`[graphorin/cli] cannot read storage directory '${dir}': ${err.message}`);
|
|
171
211
|
}
|
|
172
|
-
const candidates = entries.filter((name) => isStaleBackup(basename, name));
|
|
212
|
+
const candidates = entries.filter((name) => isStaleBackup(basename$1, name));
|
|
173
213
|
const removed = [];
|
|
174
214
|
for (const name of candidates) {
|
|
175
|
-
const full =
|
|
215
|
+
const full = join(dir, name);
|
|
176
216
|
if (options.dryRun !== true) try {
|
|
177
217
|
await unlink(full);
|
|
178
218
|
removed.push(full);
|
|
@@ -270,5 +310,5 @@ function failUnsupported(options, message, hint) {
|
|
|
270
310
|
}
|
|
271
311
|
|
|
272
312
|
//#endregion
|
|
273
|
-
export { runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus };
|
|
313
|
+
export { runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus };
|
|
274
314
|
//# sourceMappingURL=storage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"storage.js","names":["auditPath: string | undefined","auditExists: boolean | undefined","out: StorageStatusResult","out: StorageEncryptResult","out: StorageRekeyResult","entries: string[]","removed: string[]","out: StorageCleanupBackupsResult"],"sources":["../../src/commands/storage.ts"],"sourcesContent":["/**\n * `graphorin storage` — manage the SQLite store and its encryption-\n * at-rest opt-in.\n *\n * Surface (per Phase 15 § Storage):\n *\n * - `graphorin storage status` — reports cipher peer + WAL + size +\n * encryption mode.\n * - `graphorin storage encrypt --passphrase-from <ref>` — opt-in\n * encryption migration. Requires the `@graphorin/store-sqlite-\n * encrypted` sub-pack from Phase 16.\n * - `graphorin storage rekey --new-passphrase-from <ref>` — re-key\n * an already-encrypted DB.\n * - `graphorin storage cleanup-backups` — drop stale `.bak` /\n * `.bak.<ts>` files left by previous encrypt / rekey runs.\n *\n * `encrypt`, `rekey`, and `cleanup-backups` need the cipher peer\n * (`better-sqlite3-multiple-ciphers`) which ships in the optional\n * Phase 16 sub-pack. When the peer is missing the CLI exits `2`\n * (`UNSUPPORTED`) with an actionable hint instead of silently\n * doing nothing.\n *\n * @packageDocumentation\n */\n\nimport { readdir, stat, unlink } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport { resolveSecret, type SecretValue } from '@graphorin/security';\nimport { parseServerConfig } from '@graphorin/server';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { loadConfig } from '../internal/load-config.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface StorageCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface StorageStatusResult {\n readonly path: string;\n readonly mode: 'lib' | 'server';\n readonly encryption: { readonly enabled: boolean; readonly cipher?: string };\n readonly cipherPeer: { readonly installed: boolean; readonly hint?: string };\n readonly mainDb: { readonly exists: boolean; readonly sizeBytes?: number };\n readonly walFile: { readonly exists: boolean; readonly sizeBytes?: number };\n readonly auditDb: {\n readonly enabled: boolean;\n readonly path?: string;\n readonly exists?: boolean;\n };\n}\n\n/** @stable */\nexport async function runStorageStatus(\n options: StorageCommonOptions = {},\n): Promise<StorageStatusResult> {\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const cipherPeer = await probeCipherPeer();\n const mainDb = await statSafely(resolveStoragePath(config.storage.path));\n const walFile = await statSafely(`${resolveStoragePath(config.storage.path)}-wal`);\n let auditPath: string | undefined;\n let auditExists: boolean | undefined;\n if (config.audit.enabled) {\n auditPath = resolveStoragePath(config.audit.path ?? deriveAuditPath(config.storage.path));\n auditExists = (await statSafely(auditPath)).exists;\n }\n const out: StorageStatusResult = Object.freeze({\n path: resolveStoragePath(config.storage.path),\n mode: config.storage.mode,\n encryption: Object.freeze({\n enabled: config.storage.encryption.enabled,\n ...(config.storage.encryption.cipher !== undefined\n ? { cipher: config.storage.encryption.cipher }\n : {}),\n }),\n cipherPeer: Object.freeze({\n installed: cipherPeer.installed,\n ...(cipherPeer.hint !== undefined ? { hint: cipherPeer.hint } : {}),\n }),\n mainDb: Object.freeze({\n exists: mainDb.exists,\n ...(mainDb.size !== undefined ? { sizeBytes: mainDb.size } : {}),\n }),\n walFile: Object.freeze({\n exists: walFile.exists,\n ...(walFile.size !== undefined ? { sizeBytes: walFile.size } : {}),\n }),\n auditDb: Object.freeze({\n enabled: config.audit.enabled,\n ...(auditPath !== undefined ? { path: auditPath } : {}),\n ...(auditExists !== undefined ? { exists: auditExists } : {}),\n }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`storage status (${loaded.path})`));\n print(\n ` ${marker(out.mainDb.exists)} main: ${out.path} (${formatSize(out.mainDb.sizeBytes)})`,\n );\n print(\n ` ${marker(out.walFile.exists)} wal: ${out.path}-wal (${formatSize(out.walFile.sizeBytes)})`,\n );\n print(\n ` ${marker(out.encryption.enabled)} encryption: ${out.encryption.enabled ? `enabled (cipher=${out.encryption.cipher ?? 'sqlcipher'})` : 'disabled'}`,\n );\n print(\n ` ${marker(out.cipherPeer.installed)} cipher peer (better-sqlite3-multiple-ciphers): ${out.cipherPeer.installed ? 'installed' : 'missing'}`,\n );\n if (out.cipherPeer.hint !== undefined) print(` -> ${out.cipherPeer.hint}`);\n if (out.auditDb.enabled) {\n print(` ${marker(out.auditDb.exists === true)} audit.db: ${out.auditDb.path}`);\n }\n });\n return out;\n}\n\n/** @stable */\nexport interface StorageEncryptOptions extends StorageCommonOptions {\n /** SecretRef URI for the new passphrase. */\n readonly passphraseFrom: string;\n /**\n * Optional explicit target path for the encrypted output. Default:\n * `<storage.path>.encrypted`.\n */\n readonly targetPath?: string;\n /**\n * If `true`, atomically swap the encrypted target into the\n * `storage.path` location after the integrity check, leaving the\n * original under `<storage.path>.bak.<timestamp>`. Default `false`.\n */\n readonly swap?: boolean;\n}\n\n/** @stable */\nexport interface StorageEncryptResult {\n readonly sourcePath: string;\n readonly targetPath: string;\n readonly cipher: string;\n readonly integrityOk: boolean;\n readonly swap?: { readonly originalRenamedTo: string };\n}\n\n/**\n * `graphorin storage encrypt --passphrase-from <ref>` — encrypt a\n * previously unencrypted SQLite store. Delegates to the optional Phase\n * 16 sub-pack `@graphorin/store-sqlite-encrypted` once installed; when\n * the sub-pack is missing the CLI exits `2` (`UNSUPPORTED`) with an\n * actionable hint.\n *\n * @stable\n */\nexport async function runStorageEncrypt(\n options: StorageEncryptOptions,\n): Promise<StorageEncryptResult> {\n const subpack = await loadEncryptedSubpack();\n if (subpack === null) {\n return failUnsupported(\n options,\n `'graphorin storage encrypt' requires the optional sub-pack '@graphorin/store-sqlite-encrypted' (Phase 16) and the cipher peer 'better-sqlite3-multiple-ciphers'.`,\n 'Install @graphorin/store-sqlite-encrypted (which pulls the cipher peer transitively) before running this command.',\n );\n }\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const sourcePath = resolveStoragePath(config.storage.path);\n const targetPath = options.targetPath ?? `${sourcePath}.encrypted`;\n const passphrase = await resolvePassphraseRef(options.passphraseFrom);\n try {\n const result = await passphrase.use((raw) =>\n subpack.encryptDatabase({\n sourcePath,\n targetPath,\n passphrase: raw,\n ...(options.swap === true ? { swap: true } : {}),\n overwriteTarget: false,\n }),\n );\n const out: StorageEncryptResult = Object.freeze({\n sourcePath,\n targetPath: result.targetPath,\n cipher: result.cipher,\n integrityOk: result.integrityCheck.ok,\n ...(result.swap !== undefined ? { swap: result.swap } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`encrypt: ${out.sourcePath} -> ${out.targetPath} (cipher=${out.cipher})`));\n print(` ${marker(out.integrityOk)} cipher_integrity_check`);\n if (out.swap !== undefined) {\n print(` ${statusMarker('ok')} swapped; original kept at ${out.swap.originalRenamedTo}`);\n }\n });\n return out;\n } finally {\n passphrase.dispose();\n }\n}\n\n/** @stable */\nexport interface StorageRekeyOptions extends StorageCommonOptions {\n readonly oldPassphraseFrom: string;\n readonly newPassphraseFrom: string;\n}\n\n/** @stable */\nexport interface StorageRekeyResult {\n readonly path: string;\n readonly cipher: string;\n readonly integrityOk: boolean;\n}\n\n/** @stable */\nexport async function runStorageRekey(options: StorageRekeyOptions): Promise<StorageRekeyResult> {\n const subpack = await loadEncryptedSubpack();\n if (subpack === null) {\n return failUnsupported(\n options,\n `'graphorin storage rekey' requires the optional sub-pack '@graphorin/store-sqlite-encrypted' (Phase 16).`,\n 'Install @graphorin/store-sqlite-encrypted before running this command.',\n );\n }\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const path = resolveStoragePath(config.storage.path);\n const oldPassphrase = await resolvePassphraseRef(options.oldPassphraseFrom);\n const newPassphrase = await resolvePassphraseRef(options.newPassphraseFrom);\n try {\n const result = await oldPassphrase.use((oldRaw) =>\n newPassphrase.use((newRaw) =>\n subpack.rekeyDatabase({\n path,\n oldPassphrase: oldRaw,\n newPassphrase: newRaw,\n }),\n ),\n );\n const out: StorageRekeyResult = Object.freeze({\n path: result.path,\n cipher: result.cipher,\n integrityOk: result.integrityCheck.ok,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`rekey: ${out.path} (cipher=${out.cipher})`));\n print(` ${marker(out.integrityOk)} cipher_integrity_check`);\n });\n return out;\n } finally {\n oldPassphrase.dispose();\n newPassphrase.dispose();\n }\n}\n\n/** @stable */\nexport interface StorageCleanupBackupsOptions extends StorageCommonOptions {\n /**\n * Skip the actual delete; print what would be removed. Default `false`.\n * Tests pass `true` to assert the discovery without touching files.\n */\n readonly dryRun?: boolean;\n}\n\n/** @stable */\nexport interface StorageCleanupBackupsResult {\n readonly directory: string;\n readonly removed: ReadonlyArray<string>;\n readonly dryRun: boolean;\n}\n\n/**\n * Drop stale `.bak`, `.bak.<ts>`, and `.tmp.<ts>` siblings of the\n * configured storage path. Useful after `encrypt` / `rekey` runs that\n * leave intermediate copies around.\n *\n * @stable\n */\nexport async function runStorageCleanupBackups(\n options: StorageCleanupBackupsOptions = {},\n): Promise<StorageCleanupBackupsResult> {\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const dbPath = resolveStoragePath(config.storage.path);\n const dir = dirname(dbPath);\n const basename = dbPath.split('/').pop() ?? '';\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch (err) {\n throw new Error(\n `[graphorin/cli] cannot read storage directory '${dir}': ${(err as Error).message}`,\n );\n }\n const candidates = entries.filter((name) => isStaleBackup(basename, name));\n const removed: string[] = [];\n for (const name of candidates) {\n const full = `${dir}/${name}`;\n if (options.dryRun !== true) {\n try {\n await unlink(full);\n removed.push(full);\n } catch {\n // best-effort cleanup; surface in summary\n }\n } else {\n removed.push(full);\n }\n }\n const out: StorageCleanupBackupsResult = Object.freeze({\n directory: dir,\n removed: Object.freeze(removed),\n dryRun: options.dryRun === true,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.removed.length === 0) {\n print(brand(`no stale backups found in ${dir}.`));\n return;\n }\n print(\n brand(\n `${out.dryRun ? 'would remove' : 'removed'} ${out.removed.length} stale backup file(s) in ${dir}:`,\n ),\n );\n for (const name of out.removed) print(` - ${name}`);\n });\n return out;\n}\n\nfunction isStaleBackup(baseName: string, candidate: string): boolean {\n if (candidate === baseName) return false;\n if (!candidate.startsWith(baseName)) return false;\n const suffix = candidate.slice(baseName.length);\n if (suffix === '.bak') return true;\n if (/^\\.bak\\.\\d+$/.test(suffix)) return true;\n if (/^\\.tmp\\.\\d+$/.test(suffix)) return true;\n return false;\n}\n\nasync function probeCipherPeer(): Promise<{\n readonly installed: boolean;\n readonly hint?: string;\n}> {\n try {\n const moduleName = 'better-sqlite3-multiple-ciphers';\n await import(/* @vite-ignore */ moduleName);\n return Object.freeze({ installed: true });\n } catch {\n return Object.freeze({\n installed: false,\n hint: \"install '@graphorin/store-sqlite-encrypted' (Phase 16) which transitively installs the cipher peer.\",\n });\n }\n}\n\n/**\n * Encryption sub-pack surface consumed by the CLI runners. Declared\n * here so the dynamic-import path stays typed without forcing the CLI\n * to take a hard dependency on the optional package.\n *\n * @internal\n */\ninterface EncryptedSubpack {\n encryptDatabase(args: {\n sourcePath: string;\n targetPath: string;\n passphrase: string;\n swap?: boolean;\n overwriteTarget?: boolean;\n }): Promise<{\n sourcePath: string;\n targetPath: string;\n cipher: string;\n integrityCheck: { ok: boolean; rows: ReadonlyArray<string> };\n swap?: { originalRenamedTo: string };\n }>;\n rekeyDatabase(args: { path: string; oldPassphrase: string; newPassphrase: string }): Promise<{\n path: string;\n cipher: string;\n integrityCheck: { ok: boolean; rows: ReadonlyArray<string> };\n }>;\n}\n\n/** @internal */\nasync function loadEncryptedSubpack(): Promise<EncryptedSubpack | null> {\n try {\n // Computed module name keeps TypeScript's resolution off the\n // build-time graph so the CLI typechecks without the optional\n // package installed.\n const moduleName = '@graphorin/store-sqlite-encrypted';\n const mod = (await import(/* @vite-ignore */ moduleName)) as Partial<EncryptedSubpack>;\n if (typeof mod?.encryptDatabase === 'function' && typeof mod?.rekeyDatabase === 'function') {\n return mod as EncryptedSubpack;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/** @internal */\nasync function resolvePassphraseRef(ref: string): Promise<SecretValue> {\n try {\n return await resolveSecret(ref);\n } catch (err) {\n throw new Error(\n `[graphorin/cli] failed to resolve storage passphrase '${ref}': ${(err as Error).message}`,\n { cause: err },\n );\n }\n}\n\nasync function statSafely(\n path: string,\n): Promise<{ readonly exists: boolean; readonly size?: number }> {\n try {\n const s = await stat(path);\n return { exists: true, size: s.size };\n } catch {\n return { exists: false };\n }\n}\n\n// IP-20: resolve a relative storage path against the CWD — the SAME rule the\n// server (`createServer` → `createSqliteStore`) and `openStoreContext` use — so\n// `graphorin storage status / encrypt` from any directory reports the same\n// database the server and the other CLI commands (`memory`, …) open. Resolving\n// against the config-file dir made `storage status` the lone outlier: from a\n// foreign CWD it inspected a different `data.db` than everything else.\nfunction resolveStoragePath(target: string): string {\n return isAbsolute(target) ? target : resolve(target);\n}\n\nfunction deriveAuditPath(storagePath: string): string {\n const idx = storagePath.lastIndexOf('/');\n if (idx < 0) return 'audit.db';\n return `${storagePath.slice(0, idx)}/audit.db`;\n}\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined) return 'missing';\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;\n return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;\n}\n\nfunction marker(ok: boolean): string {\n return ok ? statusMarker('ok') : statusMarker('warn');\n}\n\nfunction failUnsupported(options: CommonOutputOptions, message: string, hint: string): never {\n const print = options.print ?? defaultPrintSink;\n print(brand(message));\n print(brand(`hint: ${hint}`));\n process.exit(EXIT_CODES.UNSUPPORTED);\n // `process.exit` returns `never` at runtime, but the type system\n // narrows the return so explicit callers do not need a cast.\n /* c8 ignore next */\n throw new Error('unreachable');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"storage.js","names":["auditPath: string | undefined","auditExists: boolean | undefined","out: StorageStatusResult","source: string","out: StorageBackupResult","out: StorageEncryptResult","out: StorageRekeyResult","basename","pathBasename","entries: string[]","removed: string[]","out: StorageCleanupBackupsResult"],"sources":["../../src/commands/storage.ts"],"sourcesContent":["/**\n * `graphorin storage` - manage the SQLite store and its encryption-\n * at-rest opt-in.\n *\n * Surface (per Phase 15 § Storage):\n *\n * - `graphorin storage status` - reports cipher peer + WAL + size +\n * encryption mode.\n * - `graphorin storage encrypt --passphrase-from <ref>` - opt-in\n * encryption migration. Requires the `@graphorin/store-sqlite-\n * encrypted` sub-pack from Phase 16.\n * - `graphorin storage rekey --new-passphrase-from <ref>` - re-key\n * an already-encrypted DB.\n * - `graphorin storage cleanup-backups` - drop stale `.bak` /\n * `.bak.<ts>` files left by previous encrypt / rekey runs.\n * - `graphorin storage backup <dest>` - online, consistent copy via\n * the driver's page-level backup API (store-02). Safe under a live\n * writer, preserves rowids (FTS5 mappings survive). Never use\n * `VACUUM INTO` - rowid renumbering corrupts FTS mappings.\n *\n * `encrypt`, `rekey`, and `cleanup-backups` need the cipher peer\n * (`better-sqlite3-multiple-ciphers`) which ships in the optional\n * Phase 16 sub-pack. When the peer is missing the CLI exits `2`\n * (`UNSUPPORTED`) with an actionable hint instead of silently\n * doing nothing.\n *\n * @packageDocumentation\n */\n\nimport { readdir, stat, unlink } from 'node:fs/promises';\nimport { dirname, isAbsolute, join, basename as pathBasename, resolve } from 'node:path';\nimport process from 'node:process';\n\nimport { resolveSecret, type SecretValue } from '@graphorin/security';\nimport { parseServerConfig } from '@graphorin/server';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport { loadConfig } from '../internal/load-config.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface StorageCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface StorageStatusResult {\n readonly path: string;\n readonly mode: 'lib' | 'server';\n readonly encryption: { readonly enabled: boolean; readonly cipher?: string };\n readonly cipherPeer: { readonly installed: boolean; readonly hint?: string };\n readonly mainDb: { readonly exists: boolean; readonly sizeBytes?: number };\n readonly walFile: { readonly exists: boolean; readonly sizeBytes?: number };\n readonly auditDb: {\n readonly enabled: boolean;\n readonly path?: string;\n readonly exists?: boolean;\n };\n}\n\n/** @stable */\nexport async function runStorageStatus(\n options: StorageCommonOptions = {},\n): Promise<StorageStatusResult> {\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const cipherPeer = await probeCipherPeer();\n const mainDb = await statSafely(resolveStoragePath(config.storage.path));\n const walFile = await statSafely(`${resolveStoragePath(config.storage.path)}-wal`);\n let auditPath: string | undefined;\n let auditExists: boolean | undefined;\n if (config.audit.enabled) {\n auditPath = resolveStoragePath(config.audit.path ?? deriveAuditPath(config.storage.path));\n auditExists = (await statSafely(auditPath)).exists;\n }\n const out: StorageStatusResult = Object.freeze({\n path: resolveStoragePath(config.storage.path),\n mode: config.storage.mode,\n encryption: Object.freeze({\n enabled: config.storage.encryption.enabled,\n ...(config.storage.encryption.cipher !== undefined\n ? { cipher: config.storage.encryption.cipher }\n : {}),\n }),\n cipherPeer: Object.freeze({\n installed: cipherPeer.installed,\n ...(cipherPeer.hint !== undefined ? { hint: cipherPeer.hint } : {}),\n }),\n mainDb: Object.freeze({\n exists: mainDb.exists,\n ...(mainDb.size !== undefined ? { sizeBytes: mainDb.size } : {}),\n }),\n walFile: Object.freeze({\n exists: walFile.exists,\n ...(walFile.size !== undefined ? { sizeBytes: walFile.size } : {}),\n }),\n auditDb: Object.freeze({\n enabled: config.audit.enabled,\n ...(auditPath !== undefined ? { path: auditPath } : {}),\n ...(auditExists !== undefined ? { exists: auditExists } : {}),\n }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`storage status (${loaded.path})`));\n print(\n ` ${marker(out.mainDb.exists)} main: ${out.path} (${formatSize(out.mainDb.sizeBytes)})`,\n );\n print(\n ` ${marker(out.walFile.exists)} wal: ${out.path}-wal (${formatSize(out.walFile.sizeBytes)})`,\n );\n print(\n ` ${marker(out.encryption.enabled)} encryption: ${out.encryption.enabled ? `enabled (cipher=${out.encryption.cipher ?? 'sqlcipher'})` : 'disabled'}`,\n );\n print(\n ` ${marker(out.cipherPeer.installed)} cipher peer (better-sqlite3-multiple-ciphers): ${out.cipherPeer.installed ? 'installed' : 'missing'}`,\n );\n if (out.cipherPeer.hint !== undefined) print(` -> ${out.cipherPeer.hint}`);\n if (out.auditDb.enabled) {\n print(` ${marker(out.auditDb.exists === true)} audit.db: ${out.auditDb.path}`);\n }\n });\n return out;\n}\n\n/** @stable */\nexport interface StorageBackupOptions extends StorageCommonOptions {\n /** Destination file path for the backup copy. */\n readonly dest: string;\n /** Replace an existing destination file. Default: refuse. */\n readonly overwrite?: boolean;\n}\n\n/** @stable */\nexport interface StorageBackupResult {\n readonly source: string;\n readonly dest: string;\n readonly sizeBytes?: number;\n}\n\n/**\n * store-02: online backup via the driver's page-level `backup()` API -\n * consistent under a live writer (the daemon can keep running),\n * preserves rowids so FTS5 external-content mappings survive, and for\n * an encrypted store produces an equally-encrypted copy (same key).\n * This is the ONLY supported SQL-level backup: `VACUUM INTO`\n * renumbers rowids and corrupts the FTS mapping on restore.\n *\n * @stable\n */\nexport async function runStorageBackup(\n options: StorageBackupOptions,\n): Promise<StorageBackupResult> {\n const { openStoreContext } = await import('../internal/store-context.js');\n const dest = isAbsolute(options.dest) ? options.dest : resolve(process.cwd(), options.dest);\n const destStat = await statSafely(dest);\n if (destStat.exists && options.overwrite !== true) {\n throw new Error(\n `[graphorin/cli] backup destination already exists: ${dest}. Pass --overwrite to replace it.`,\n );\n }\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n let source: string;\n try {\n source = ctx.store.connection.path;\n if (source === dest) {\n throw new Error('[graphorin/cli] backup destination must differ from the store path.');\n }\n await ctx.store.connection.raw().backup(dest);\n } finally {\n await ctx.close();\n }\n const written = await statSafely(dest);\n const out: StorageBackupResult = Object.freeze({\n source,\n dest,\n ...(written.size !== undefined ? { sizeBytes: written.size } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand('storage backup'));\n print(` ${statusMarker('ok')} ${out.source} -> ${out.dest} (${formatSize(out.sizeBytes)})`);\n });\n return out;\n}\n\n/** @stable */\nexport interface StorageEncryptOptions extends StorageCommonOptions {\n /** SecretRef URI for the new passphrase. */\n readonly passphraseFrom: string;\n /**\n * Optional explicit target path for the encrypted output. Default:\n * `<storage.path>.encrypted`.\n */\n readonly targetPath?: string;\n /**\n * If `true`, atomically swap the encrypted target into the\n * `storage.path` location after the integrity check, leaving the\n * original under `<storage.path>.bak.<timestamp>`. Default `false`.\n */\n readonly swap?: boolean;\n}\n\n/** @stable */\nexport interface StorageEncryptResult {\n readonly sourcePath: string;\n readonly targetPath: string;\n readonly cipher: string;\n readonly integrityOk: boolean;\n readonly swap?: { readonly originalRenamedTo: string };\n}\n\n/**\n * `graphorin storage encrypt --passphrase-from <ref>` - encrypt a\n * previously unencrypted SQLite store. Delegates to the optional Phase\n * 16 sub-pack `@graphorin/store-sqlite-encrypted` once installed; when\n * the sub-pack is missing the CLI exits `2` (`UNSUPPORTED`) with an\n * actionable hint.\n *\n * @stable\n */\nexport async function runStorageEncrypt(\n options: StorageEncryptOptions,\n): Promise<StorageEncryptResult> {\n const subpack = await loadEncryptedSubpack();\n if (subpack === null) {\n return failUnsupported(\n options,\n `'graphorin storage encrypt' requires the optional sub-pack '@graphorin/store-sqlite-encrypted' (Phase 16) and the cipher peer 'better-sqlite3-multiple-ciphers'.`,\n 'Install @graphorin/store-sqlite-encrypted (which pulls the cipher peer transitively) before running this command.',\n );\n }\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const sourcePath = resolveStoragePath(config.storage.path);\n const targetPath = options.targetPath ?? `${sourcePath}.encrypted`;\n const passphrase = await resolvePassphraseRef(options.passphraseFrom);\n try {\n const result = await passphrase.use((raw) =>\n subpack.encryptDatabase({\n sourcePath,\n targetPath,\n passphrase: raw,\n ...(options.swap === true ? { swap: true } : {}),\n overwriteTarget: false,\n }),\n );\n const out: StorageEncryptResult = Object.freeze({\n sourcePath,\n targetPath: result.targetPath,\n cipher: result.cipher,\n integrityOk: result.integrityCheck.ok,\n ...(result.swap !== undefined ? { swap: result.swap } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`encrypt: ${out.sourcePath} -> ${out.targetPath} (cipher=${out.cipher})`));\n print(` ${marker(out.integrityOk)} cipher_integrity_check`);\n if (out.swap !== undefined) {\n print(` ${statusMarker('ok')} swapped; original kept at ${out.swap.originalRenamedTo}`);\n }\n });\n return out;\n } finally {\n passphrase.dispose();\n }\n}\n\n/** @stable */\nexport interface StorageRekeyOptions extends StorageCommonOptions {\n readonly oldPassphraseFrom: string;\n readonly newPassphraseFrom: string;\n}\n\n/** @stable */\nexport interface StorageRekeyResult {\n readonly path: string;\n readonly cipher: string;\n readonly integrityOk: boolean;\n}\n\n/** @stable */\nexport async function runStorageRekey(options: StorageRekeyOptions): Promise<StorageRekeyResult> {\n const subpack = await loadEncryptedSubpack();\n if (subpack === null) {\n return failUnsupported(\n options,\n `'graphorin storage rekey' requires the optional sub-pack '@graphorin/store-sqlite-encrypted' (Phase 16).`,\n 'Install @graphorin/store-sqlite-encrypted before running this command.',\n );\n }\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const path = resolveStoragePath(config.storage.path);\n const oldPassphrase = await resolvePassphraseRef(options.oldPassphraseFrom);\n const newPassphrase = await resolvePassphraseRef(options.newPassphraseFrom);\n try {\n const result = await oldPassphrase.use((oldRaw) =>\n newPassphrase.use((newRaw) =>\n subpack.rekeyDatabase({\n path,\n oldPassphrase: oldRaw,\n newPassphrase: newRaw,\n }),\n ),\n );\n const out: StorageRekeyResult = Object.freeze({\n path: result.path,\n cipher: result.cipher,\n integrityOk: result.integrityCheck.ok,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`rekey: ${out.path} (cipher=${out.cipher})`));\n print(` ${marker(out.integrityOk)} cipher_integrity_check`);\n });\n return out;\n } finally {\n oldPassphrase.dispose();\n newPassphrase.dispose();\n }\n}\n\n/** @stable */\nexport interface StorageCleanupBackupsOptions extends StorageCommonOptions {\n /**\n * Skip the actual delete; print what would be removed. Default `false`.\n * Tests pass `true` to assert the discovery without touching files.\n */\n readonly dryRun?: boolean;\n}\n\n/** @stable */\nexport interface StorageCleanupBackupsResult {\n readonly directory: string;\n readonly removed: ReadonlyArray<string>;\n readonly dryRun: boolean;\n}\n\n/**\n * Drop stale `.bak`, `.bak.<ts>`, and `.tmp.<ts>` siblings of the\n * configured storage path. Useful after `encrypt` / `rekey` runs that\n * leave intermediate copies around.\n *\n * @stable\n */\nexport async function runStorageCleanupBackups(\n options: StorageCleanupBackupsOptions = {},\n): Promise<StorageCleanupBackupsResult> {\n const loaded = await loadConfig(options.config);\n const config = parseServerConfig(loaded.config);\n const dbPath = resolveStoragePath(config.storage.path);\n const dir = dirname(dbPath);\n // E6: node:path basename, NOT dbPath.split('/') - on Windows the path is\n // backslash-separated, so the split returned the whole path, no readdir\n // entry ever matched, and cleanup-backups was a silent no-op.\n const basename = pathBasename(dbPath);\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch (err) {\n throw new Error(\n `[graphorin/cli] cannot read storage directory '${dir}': ${(err as Error).message}`,\n );\n }\n const candidates = entries.filter((name) => isStaleBackup(basename, name));\n const removed: string[] = [];\n for (const name of candidates) {\n const full = join(dir, name);\n if (options.dryRun !== true) {\n try {\n await unlink(full);\n removed.push(full);\n } catch {\n // best-effort cleanup; surface in summary\n }\n } else {\n removed.push(full);\n }\n }\n const out: StorageCleanupBackupsResult = Object.freeze({\n directory: dir,\n removed: Object.freeze(removed),\n dryRun: options.dryRun === true,\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.removed.length === 0) {\n print(brand(`no stale backups found in ${dir}.`));\n return;\n }\n print(\n brand(\n `${out.dryRun ? 'would remove' : 'removed'} ${out.removed.length} stale backup file(s) in ${dir}:`,\n ),\n );\n for (const name of out.removed) print(` - ${name}`);\n });\n return out;\n}\n\nfunction isStaleBackup(baseName: string, candidate: string): boolean {\n if (candidate === baseName) return false;\n if (!candidate.startsWith(baseName)) return false;\n const suffix = candidate.slice(baseName.length);\n if (suffix === '.bak') return true;\n if (/^\\.bak\\.\\d+$/.test(suffix)) return true;\n if (/^\\.tmp\\.\\d+$/.test(suffix)) return true;\n return false;\n}\n\nasync function probeCipherPeer(): Promise<{\n readonly installed: boolean;\n readonly hint?: string;\n}> {\n try {\n const moduleName = 'better-sqlite3-multiple-ciphers';\n await import(/* @vite-ignore */ moduleName);\n return Object.freeze({ installed: true });\n } catch {\n return Object.freeze({\n installed: false,\n hint: \"install '@graphorin/store-sqlite-encrypted' (Phase 16) which transitively installs the cipher peer.\",\n });\n }\n}\n\n/**\n * Encryption sub-pack surface consumed by the CLI runners. Declared\n * here so the dynamic-import path stays typed without forcing the CLI\n * to take a hard dependency on the optional package.\n *\n * @internal\n */\ninterface EncryptedSubpack {\n encryptDatabase(args: {\n sourcePath: string;\n targetPath: string;\n passphrase: string;\n swap?: boolean;\n overwriteTarget?: boolean;\n }): Promise<{\n sourcePath: string;\n targetPath: string;\n cipher: string;\n integrityCheck: { ok: boolean; rows: ReadonlyArray<string> };\n swap?: { originalRenamedTo: string };\n }>;\n rekeyDatabase(args: { path: string; oldPassphrase: string; newPassphrase: string }): Promise<{\n path: string;\n cipher: string;\n integrityCheck: { ok: boolean; rows: ReadonlyArray<string> };\n }>;\n}\n\n/** @internal */\nasync function loadEncryptedSubpack(): Promise<EncryptedSubpack | null> {\n try {\n // Computed module name keeps TypeScript's resolution off the\n // build-time graph so the CLI typechecks without the optional\n // package installed.\n const moduleName = '@graphorin/store-sqlite-encrypted';\n const mod = (await import(/* @vite-ignore */ moduleName)) as Partial<EncryptedSubpack>;\n if (typeof mod?.encryptDatabase === 'function' && typeof mod?.rekeyDatabase === 'function') {\n return mod as EncryptedSubpack;\n }\n return null;\n } catch {\n return null;\n }\n}\n\n/** @internal */\nasync function resolvePassphraseRef(ref: string): Promise<SecretValue> {\n try {\n return await resolveSecret(ref);\n } catch (err) {\n throw new Error(\n `[graphorin/cli] failed to resolve storage passphrase '${ref}': ${(err as Error).message}`,\n { cause: err },\n );\n }\n}\n\nasync function statSafely(\n path: string,\n): Promise<{ readonly exists: boolean; readonly size?: number }> {\n try {\n const s = await stat(path);\n return { exists: true, size: s.size };\n } catch {\n return { exists: false };\n }\n}\n\n// IP-20: resolve a relative storage path against the CWD - the SAME rule the\n// server (`createServer` → `createSqliteStore`) and `openStoreContext` use - so\n// `graphorin storage status / encrypt` from any directory reports the same\n// database the server and the other CLI commands (`memory`, …) open. Resolving\n// against the config-file dir made `storage status` the lone outlier: from a\n// foreign CWD it inspected a different `data.db` than everything else.\nfunction resolveStoragePath(target: string): string {\n return isAbsolute(target) ? target : resolve(target);\n}\n\nfunction deriveAuditPath(storagePath: string): string {\n const idx = storagePath.lastIndexOf('/');\n if (idx < 0) return 'audit.db';\n return `${storagePath.slice(0, idx)}/audit.db`;\n}\n\nfunction formatSize(bytes: number | undefined): string {\n if (bytes === undefined) return 'missing';\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;\n return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;\n}\n\nfunction marker(ok: boolean): string {\n return ok ? statusMarker('ok') : statusMarker('warn');\n}\n\nfunction failUnsupported(options: CommonOutputOptions, message: string, hint: string): never {\n const print = options.print ?? defaultPrintSink;\n print(brand(message));\n print(brand(`hint: ${hint}`));\n process.exit(EXIT_CODES.UNSUPPORTED);\n // `process.exit` returns `never` at runtime, but the type system\n // narrows the return so explicit callers do not need a cast.\n /* c8 ignore next */\n throw new Error('unreachable');\n}\n"],"mappings":"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"}
|
|
@@ -4,7 +4,7 @@ import process from "node:process";
|
|
|
4
4
|
|
|
5
5
|
//#region src/commands/telemetry.ts
|
|
6
6
|
/**
|
|
7
|
-
* `graphorin telemetry`
|
|
7
|
+
* `graphorin telemetry` - informational stubs.
|
|
8
8
|
*
|
|
9
9
|
* The framework promises **zero default telemetry** (DEC-154 /
|
|
10
10
|
* ADR-041). The Phase 15 surface ships the four subcommands as
|
|
@@ -15,11 +15,11 @@ import process from "node:process";
|
|
|
15
15
|
*
|
|
16
16
|
* Surface (per Phase 15 § Telemetry):
|
|
17
17
|
*
|
|
18
|
-
* - `graphorin telemetry status`
|
|
19
|
-
* - `graphorin telemetry enable`
|
|
18
|
+
* - `graphorin telemetry status` - always reports `disabled`.
|
|
19
|
+
* - `graphorin telemetry enable` - refuses with a documentation
|
|
20
20
|
* pointer.
|
|
21
|
-
* - `graphorin telemetry disable`
|
|
22
|
-
* - `graphorin telemetry inspect`
|
|
21
|
+
* - `graphorin telemetry disable` - no-op success.
|
|
22
|
+
* - `graphorin telemetry inspect` - informational summary of the
|
|
23
23
|
* promise (no phone home, no version pings, no crash reports).
|
|
24
24
|
*
|
|
25
25
|
* @packageDocumentation
|
|
@@ -43,7 +43,7 @@ function runTelemetryStatus(options = {}) {
|
|
|
43
43
|
function runTelemetryEnable(options = {}) {
|
|
44
44
|
emitReport(options, ZERO_DEFAULT_RESULT, () => {
|
|
45
45
|
const print = options.print ?? defaultPrintSink;
|
|
46
|
-
print(brand(`telemetry enable refused
|
|
46
|
+
print(brand(`telemetry enable refused - the framework promises zero phone-home (DEC-154).`));
|
|
47
47
|
print(brand(`an opt-in collector is on the v0.2+ roadmap; see ${PRIVACY_REF}.`));
|
|
48
48
|
});
|
|
49
49
|
process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.js","names":["ZERO_DEFAULT_RESULT: TelemetryStatusResult"],"sources":["../../src/commands/telemetry.ts"],"sourcesContent":["/**\n * `graphorin telemetry`
|
|
1
|
+
{"version":3,"file":"telemetry.js","names":["ZERO_DEFAULT_RESULT: TelemetryStatusResult"],"sources":["../../src/commands/telemetry.ts"],"sourcesContent":["/**\n * `graphorin telemetry` - informational stubs.\n *\n * The framework promises **zero default telemetry** (DEC-154 /\n * ADR-041). The Phase 15 surface ships the four subcommands as\n * stubs that explain the policy and point operators at the\n * documentation. Phase v0.2+ will extend the surface with an opt-in\n * collector; today the stubs ensure the surface name-space is\n * reserved + the operator gets a clear answer.\n *\n * Surface (per Phase 15 § Telemetry):\n *\n * - `graphorin telemetry status` - always reports `disabled`.\n * - `graphorin telemetry enable` - refuses with a documentation\n * pointer.\n * - `graphorin telemetry disable` - no-op success.\n * - `graphorin telemetry inspect` - informational summary of the\n * promise (no phone home, no version pings, no crash reports).\n *\n * @packageDocumentation\n */\n\nimport process from 'node:process';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\n\n/** @stable */\nexport interface TelemetryStatusResult {\n readonly enabled: false;\n readonly policy: 'zero-default';\n readonly reference: string;\n}\n\nconst PRIVACY_REF = 'SECURITY.md § Privacy & telemetry (DEC-154 / ADR-041)';\n\nconst ZERO_DEFAULT_RESULT: TelemetryStatusResult = Object.freeze({\n enabled: false,\n policy: 'zero-default',\n reference: PRIVACY_REF,\n});\n\n/** @stable */\nexport function runTelemetryStatus(options: CommonOutputOptions = {}): TelemetryStatusResult {\n emitReport(options, ZERO_DEFAULT_RESULT, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`${statusMarker('info')} telemetry: disabled (policy: zero-default)`));\n print(brand(`reference: ${PRIVACY_REF}`));\n });\n return ZERO_DEFAULT_RESULT;\n}\n\n/** @stable */\nexport function runTelemetryEnable(options: CommonOutputOptions = {}): TelemetryStatusResult {\n emitReport(options, ZERO_DEFAULT_RESULT, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`telemetry enable refused - the framework promises zero phone-home (DEC-154).`));\n print(brand(`an opt-in collector is on the v0.2+ roadmap; see ${PRIVACY_REF}.`));\n });\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return ZERO_DEFAULT_RESULT;\n}\n\n/** @stable */\nexport function runTelemetryDisable(options: CommonOutputOptions = {}): TelemetryStatusResult {\n emitReport(options, ZERO_DEFAULT_RESULT, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`telemetry is already disabled (no-op; zero-default policy).`));\n });\n return ZERO_DEFAULT_RESULT;\n}\n\n/** @stable */\nexport function runTelemetryInspect(options: CommonOutputOptions = {}): TelemetryStatusResult {\n emitReport(options, ZERO_DEFAULT_RESULT, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand('graphorin telemetry inspect:'));\n print(` ${statusMarker('ok')} no phone home`);\n print(` ${statusMarker('ok')} no version pings`);\n print(` ${statusMarker('ok')} no crash reports`);\n print(` ${statusMarker('ok')} no auto-updates`);\n print(brand(`reference: ${PRIVACY_REF}`));\n });\n return ZERO_DEFAULT_RESULT;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAM,cAAc;AAEpB,MAAMA,sBAA6C,OAAO,OAAO;CAC/D,SAAS;CACT,QAAQ;CACR,WAAW;CACZ,CAAC;;AAGF,SAAgB,mBAAmB,UAA+B,EAAE,EAAyB;AAC3F,YAAW,SAAS,2BAA2B;EAC7C,MAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,MAAM,GAAG,aAAa,OAAO,CAAC,6CAA6C,CAAC;AAClF,QAAM,MAAM,cAAc,cAAc,CAAC;GACzC;AACF,QAAO;;;AAIT,SAAgB,mBAAmB,UAA+B,EAAE,EAAyB;AAC3F,YAAW,SAAS,2BAA2B;EAC7C,MAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,MAAM,+EAA+E,CAAC;AAC5F,QAAM,MAAM,oDAAoD,YAAY,GAAG,CAAC;GAChF;AACF,SAAQ,WAAW,WAAW;AAC9B,QAAO;;;AAIT,SAAgB,oBAAoB,UAA+B,EAAE,EAAyB;AAC5F,YAAW,SAAS,2BAA2B;AAE7C,GADc,QAAQ,SAAS,kBACzB,MAAM,8DAA8D,CAAC;GAC3E;AACF,QAAO;;;AAIT,SAAgB,oBAAoB,UAA+B,EAAE,EAAyB;AAC5F,YAAW,SAAS,2BAA2B;EAC7C,MAAM,QAAQ,QAAQ,SAAS;AAC/B,QAAM,MAAM,+BAA+B,CAAC;AAC5C,QAAM,KAAK,aAAa,KAAK,CAAC,gBAAgB;AAC9C,QAAM,KAAK,aAAa,KAAK,CAAC,mBAAmB;AACjD,QAAM,KAAK,aAAa,KAAK,CAAC,mBAAmB;AACjD,QAAM,KAAK,aAAa,KAAK,CAAC,kBAAkB;AAChD,QAAM,MAAM,cAAc,cAAc,CAAC;GACzC;AACF,QAAO"}
|
package/dist/commands/token.d.ts
CHANGED
|
@@ -90,7 +90,7 @@ interface TokenVerifyResult {
|
|
|
90
90
|
/**
|
|
91
91
|
* Offline checksum verification. Confirms the structural shape, the
|
|
92
92
|
* environment marker, and the CRC checksum but does NOT consult the
|
|
93
|
-
* token store
|
|
93
|
+
* token store - it only proves the token was minted by a Graphorin
|
|
94
94
|
* helper.
|
|
95
95
|
*
|
|
96
96
|
* @stable
|
|
@@ -98,7 +98,7 @@ interface TokenVerifyResult {
|
|
|
98
98
|
declare function runTokenVerify(options: TokenVerifyOptions): TokenVerifyResult;
|
|
99
99
|
/**
|
|
100
100
|
* Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns
|
|
101
|
-
* milliseconds. Throws on invalid input
|
|
101
|
+
* milliseconds. Throws on invalid input - surfaced as a fail-fast at
|
|
102
102
|
* the CLI boundary.
|
|
103
103
|
*
|
|
104
104
|
* @internal
|