@graphorin/cli 0.5.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.
Files changed (109) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/LICENSE +21 -0
  3. package/README.md +130 -0
  4. package/dist/bin/graphorin.d.ts +1 -0
  5. package/dist/bin/graphorin.js +606 -0
  6. package/dist/bin/graphorin.js.map +1 -0
  7. package/dist/commands/audit.d.ts +59 -0
  8. package/dist/commands/audit.d.ts.map +1 -0
  9. package/dist/commands/audit.js +156 -0
  10. package/dist/commands/audit.js.map +1 -0
  11. package/dist/commands/auth.d.ts +48 -0
  12. package/dist/commands/auth.d.ts.map +1 -0
  13. package/dist/commands/auth.js +148 -0
  14. package/dist/commands/auth.js.map +1 -0
  15. package/dist/commands/consolidator.d.ts +49 -0
  16. package/dist/commands/consolidator.d.ts.map +1 -0
  17. package/dist/commands/consolidator.js +97 -0
  18. package/dist/commands/consolidator.js.map +1 -0
  19. package/dist/commands/doctor.d.ts +64 -0
  20. package/dist/commands/doctor.d.ts.map +1 -0
  21. package/dist/commands/doctor.js +150 -0
  22. package/dist/commands/doctor.js.map +1 -0
  23. package/dist/commands/guard.d.ts +35 -0
  24. package/dist/commands/guard.d.ts.map +1 -0
  25. package/dist/commands/guard.js +86 -0
  26. package/dist/commands/guard.js.map +1 -0
  27. package/dist/commands/index.d.ts +21 -0
  28. package/dist/commands/index.js +22 -0
  29. package/dist/commands/init.d.ts +46 -0
  30. package/dist/commands/init.d.ts.map +1 -0
  31. package/dist/commands/init.js +131 -0
  32. package/dist/commands/init.js.map +1 -0
  33. package/dist/commands/memory.d.ts +243 -0
  34. package/dist/commands/memory.d.ts.map +1 -0
  35. package/dist/commands/memory.js +471 -0
  36. package/dist/commands/memory.js.map +1 -0
  37. package/dist/commands/migrate-config.d.ts +20 -0
  38. package/dist/commands/migrate-config.d.ts.map +1 -0
  39. package/dist/commands/migrate-config.js +48 -0
  40. package/dist/commands/migrate-config.js.map +1 -0
  41. package/dist/commands/migrate-export.d.ts +26 -0
  42. package/dist/commands/migrate-export.d.ts.map +1 -0
  43. package/dist/commands/migrate-export.js +67 -0
  44. package/dist/commands/migrate-export.js.map +1 -0
  45. package/dist/commands/migrate.d.ts +31 -0
  46. package/dist/commands/migrate.d.ts.map +1 -0
  47. package/dist/commands/migrate.js +59 -0
  48. package/dist/commands/migrate.js.map +1 -0
  49. package/dist/commands/pricing.d.ts +66 -0
  50. package/dist/commands/pricing.d.ts.map +1 -0
  51. package/dist/commands/pricing.js +128 -0
  52. package/dist/commands/pricing.js.map +1 -0
  53. package/dist/commands/secrets.d.ts +96 -0
  54. package/dist/commands/secrets.d.ts.map +1 -0
  55. package/dist/commands/secrets.js +182 -0
  56. package/dist/commands/secrets.js.map +1 -0
  57. package/dist/commands/skills.d.ts +58 -0
  58. package/dist/commands/skills.d.ts.map +1 -0
  59. package/dist/commands/skills.js +181 -0
  60. package/dist/commands/skills.js.map +1 -0
  61. package/dist/commands/start.d.ts +51 -0
  62. package/dist/commands/start.d.ts.map +1 -0
  63. package/dist/commands/start.js +122 -0
  64. package/dist/commands/start.js.map +1 -0
  65. package/dist/commands/storage.d.ts +110 -0
  66. package/dist/commands/storage.d.ts.map +1 -0
  67. package/dist/commands/storage.js +274 -0
  68. package/dist/commands/storage.js.map +1 -0
  69. package/dist/commands/telemetry.d.ts +21 -0
  70. package/dist/commands/telemetry.d.ts.map +1 -0
  71. package/dist/commands/telemetry.js +75 -0
  72. package/dist/commands/telemetry.js.map +1 -0
  73. package/dist/commands/token.d.ts +109 -0
  74. package/dist/commands/token.d.ts.map +1 -0
  75. package/dist/commands/token.js +246 -0
  76. package/dist/commands/token.js.map +1 -0
  77. package/dist/commands/tools-lint.d.ts +103 -0
  78. package/dist/commands/tools-lint.d.ts.map +1 -0
  79. package/dist/commands/tools-lint.js +240 -0
  80. package/dist/commands/tools-lint.js.map +1 -0
  81. package/dist/commands/traces.d.ts +33 -0
  82. package/dist/commands/traces.d.ts.map +1 -0
  83. package/dist/commands/traces.js +103 -0
  84. package/dist/commands/traces.js.map +1 -0
  85. package/dist/commands/triggers.d.ts +49 -0
  86. package/dist/commands/triggers.d.ts.map +1 -0
  87. package/dist/commands/triggers.js +129 -0
  88. package/dist/commands/triggers.js.map +1 -0
  89. package/dist/index.d.ts +46 -0
  90. package/dist/index.d.ts.map +1 -0
  91. package/dist/index.js +47 -0
  92. package/dist/index.js.map +1 -0
  93. package/dist/internal/exit.js +17 -0
  94. package/dist/internal/exit.js.map +1 -0
  95. package/dist/internal/load-config.js +84 -0
  96. package/dist/internal/load-config.js.map +1 -0
  97. package/dist/internal/offline.d.ts +58 -0
  98. package/dist/internal/offline.d.ts.map +1 -0
  99. package/dist/internal/offline.js +72 -0
  100. package/dist/internal/offline.js.map +1 -0
  101. package/dist/internal/output.d.ts +56 -0
  102. package/dist/internal/output.d.ts.map +1 -0
  103. package/dist/internal/output.js +86 -0
  104. package/dist/internal/output.js.map +1 -0
  105. package/dist/internal/prompts.js +49 -0
  106. package/dist/internal/prompts.js.map +1 -0
  107. package/dist/internal/store-context.js +69 -0
  108. package/dist/internal/store-context.js.map +1 -0
  109. package/package.json +79 -0
@@ -0,0 +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 — 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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"}
@@ -0,0 +1,110 @@
1
+ import { CommonOutputOptions } from "../internal/output.js";
2
+
3
+ //#region src/commands/storage.d.ts
4
+
5
+ /** @stable */
6
+ interface StorageCommonOptions extends CommonOutputOptions {
7
+ readonly config?: string;
8
+ }
9
+ /** @stable */
10
+ interface StorageStatusResult {
11
+ readonly path: string;
12
+ readonly mode: 'lib' | 'server';
13
+ readonly encryption: {
14
+ readonly enabled: boolean;
15
+ readonly cipher?: string;
16
+ };
17
+ readonly cipherPeer: {
18
+ readonly installed: boolean;
19
+ readonly hint?: string;
20
+ };
21
+ readonly mainDb: {
22
+ readonly exists: boolean;
23
+ readonly sizeBytes?: number;
24
+ };
25
+ readonly walFile: {
26
+ readonly exists: boolean;
27
+ readonly sizeBytes?: number;
28
+ };
29
+ readonly auditDb: {
30
+ readonly enabled: boolean;
31
+ readonly path?: string;
32
+ readonly exists?: boolean;
33
+ };
34
+ }
35
+ /** @stable */
36
+ declare function runStorageStatus(options?: StorageCommonOptions): Promise<StorageStatusResult>;
37
+ /** @stable */
38
+ interface StorageEncryptOptions extends StorageCommonOptions {
39
+ /** SecretRef URI for the new passphrase. */
40
+ readonly passphraseFrom: string;
41
+ /**
42
+ * Optional explicit target path for the encrypted output. Default:
43
+ * `<storage.path>.encrypted`.
44
+ */
45
+ readonly targetPath?: string;
46
+ /**
47
+ * If `true`, atomically swap the encrypted target into the
48
+ * `storage.path` location after the integrity check, leaving the
49
+ * original under `<storage.path>.bak.<timestamp>`. Default `false`.
50
+ */
51
+ readonly swap?: boolean;
52
+ }
53
+ /** @stable */
54
+ interface StorageEncryptResult {
55
+ readonly sourcePath: string;
56
+ readonly targetPath: string;
57
+ readonly cipher: string;
58
+ readonly integrityOk: boolean;
59
+ readonly swap?: {
60
+ readonly originalRenamedTo: string;
61
+ };
62
+ }
63
+ /**
64
+ * `graphorin storage encrypt --passphrase-from <ref>` — encrypt a
65
+ * previously unencrypted SQLite store. Delegates to the optional Phase
66
+ * 16 sub-pack `@graphorin/store-sqlite-encrypted` once installed; when
67
+ * the sub-pack is missing the CLI exits `2` (`UNSUPPORTED`) with an
68
+ * actionable hint.
69
+ *
70
+ * @stable
71
+ */
72
+ declare function runStorageEncrypt(options: StorageEncryptOptions): Promise<StorageEncryptResult>;
73
+ /** @stable */
74
+ interface StorageRekeyOptions extends StorageCommonOptions {
75
+ readonly oldPassphraseFrom: string;
76
+ readonly newPassphraseFrom: string;
77
+ }
78
+ /** @stable */
79
+ interface StorageRekeyResult {
80
+ readonly path: string;
81
+ readonly cipher: string;
82
+ readonly integrityOk: boolean;
83
+ }
84
+ /** @stable */
85
+ declare function runStorageRekey(options: StorageRekeyOptions): Promise<StorageRekeyResult>;
86
+ /** @stable */
87
+ interface StorageCleanupBackupsOptions extends StorageCommonOptions {
88
+ /**
89
+ * Skip the actual delete; print what would be removed. Default `false`.
90
+ * Tests pass `true` to assert the discovery without touching files.
91
+ */
92
+ readonly dryRun?: boolean;
93
+ }
94
+ /** @stable */
95
+ interface StorageCleanupBackupsResult {
96
+ readonly directory: string;
97
+ readonly removed: ReadonlyArray<string>;
98
+ readonly dryRun: boolean;
99
+ }
100
+ /**
101
+ * Drop stale `.bak`, `.bak.<ts>`, and `.tmp.<ts>` siblings of the
102
+ * configured storage path. Useful after `encrypt` / `rekey` runs that
103
+ * leave intermediate copies around.
104
+ *
105
+ * @stable
106
+ */
107
+ declare function runStorageCleanupBackups(options?: StorageCleanupBackupsOptions): Promise<StorageCleanupBackupsResult>;
108
+ //#endregion
109
+ export { StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus };
110
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","names":[],"sources":["../../src/commands/storage.ts"],"sourcesContent":[],"mappings":";;;;AAkRA;AAasB,UApPL,oBAAA,SAA6B,mBAoPA,CAAA;EACnC,SAAA,MAAA,CAAA,EAAA,MAAA;;;AACD,UAjPO,mBAAA,CAiPP;;;;;;;;;;;;;;;;;;;;;;;;;;iBAlOY,gBAAA,WACX,uBACR,QAAQ;;UA+DM,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"}
@@ -0,0 +1,274 @@
1
+ import { brand, defaultPrintSink, emitReport, statusMarker } from "../internal/output.js";
2
+ import { EXIT_CODES } from "../internal/exit.js";
3
+ import { loadConfig } from "../internal/load-config.js";
4
+ import { readdir, stat, unlink } from "node:fs/promises";
5
+ import { dirname, isAbsolute, resolve } from "node:path";
6
+ import process from "node:process";
7
+ import { resolveSecret } from "@graphorin/security";
8
+ import { parseServerConfig } from "@graphorin/server";
9
+
10
+ //#region src/commands/storage.ts
11
+ /**
12
+ * `graphorin storage` — manage the SQLite store and its encryption-
13
+ * at-rest opt-in.
14
+ *
15
+ * Surface (per Phase 15 § Storage):
16
+ *
17
+ * - `graphorin storage status` — reports cipher peer + WAL + size +
18
+ * encryption mode.
19
+ * - `graphorin storage encrypt --passphrase-from <ref>` — opt-in
20
+ * encryption migration. Requires the `@graphorin/store-sqlite-
21
+ * encrypted` sub-pack from Phase 16.
22
+ * - `graphorin storage rekey --new-passphrase-from <ref>` — re-key
23
+ * an already-encrypted DB.
24
+ * - `graphorin storage cleanup-backups` — drop stale `.bak` /
25
+ * `.bak.<ts>` files left by previous encrypt / rekey runs.
26
+ *
27
+ * `encrypt`, `rekey`, and `cleanup-backups` need the cipher peer
28
+ * (`better-sqlite3-multiple-ciphers`) which ships in the optional
29
+ * Phase 16 sub-pack. When the peer is missing the CLI exits `2`
30
+ * (`UNSUPPORTED`) with an actionable hint instead of silently
31
+ * doing nothing.
32
+ *
33
+ * @packageDocumentation
34
+ */
35
+ /** @stable */
36
+ async function runStorageStatus(options = {}) {
37
+ const loaded = await loadConfig(options.config);
38
+ const config = parseServerConfig(loaded.config);
39
+ const cipherPeer = await probeCipherPeer();
40
+ const mainDb = await statSafely(resolveStoragePath(config.storage.path));
41
+ const walFile = await statSafely(`${resolveStoragePath(config.storage.path)}-wal`);
42
+ let auditPath;
43
+ let auditExists;
44
+ if (config.audit.enabled) {
45
+ auditPath = resolveStoragePath(config.audit.path ?? deriveAuditPath(config.storage.path));
46
+ auditExists = (await statSafely(auditPath)).exists;
47
+ }
48
+ const out = Object.freeze({
49
+ path: resolveStoragePath(config.storage.path),
50
+ mode: config.storage.mode,
51
+ encryption: Object.freeze({
52
+ enabled: config.storage.encryption.enabled,
53
+ ...config.storage.encryption.cipher !== void 0 ? { cipher: config.storage.encryption.cipher } : {}
54
+ }),
55
+ cipherPeer: Object.freeze({
56
+ installed: cipherPeer.installed,
57
+ ...cipherPeer.hint !== void 0 ? { hint: cipherPeer.hint } : {}
58
+ }),
59
+ mainDb: Object.freeze({
60
+ exists: mainDb.exists,
61
+ ...mainDb.size !== void 0 ? { sizeBytes: mainDb.size } : {}
62
+ }),
63
+ walFile: Object.freeze({
64
+ exists: walFile.exists,
65
+ ...walFile.size !== void 0 ? { sizeBytes: walFile.size } : {}
66
+ }),
67
+ auditDb: Object.freeze({
68
+ enabled: config.audit.enabled,
69
+ ...auditPath !== void 0 ? { path: auditPath } : {},
70
+ ...auditExists !== void 0 ? { exists: auditExists } : {}
71
+ })
72
+ });
73
+ emitReport(options, out, () => {
74
+ const print = options.print ?? defaultPrintSink;
75
+ print(brand(`storage status (${loaded.path})`));
76
+ print(` ${marker(out.mainDb.exists)} main: ${out.path} (${formatSize(out.mainDb.sizeBytes)})`);
77
+ print(` ${marker(out.walFile.exists)} wal: ${out.path}-wal (${formatSize(out.walFile.sizeBytes)})`);
78
+ print(` ${marker(out.encryption.enabled)} encryption: ${out.encryption.enabled ? `enabled (cipher=${out.encryption.cipher ?? "sqlcipher"})` : "disabled"}`);
79
+ print(` ${marker(out.cipherPeer.installed)} cipher peer (better-sqlite3-multiple-ciphers): ${out.cipherPeer.installed ? "installed" : "missing"}`);
80
+ if (out.cipherPeer.hint !== void 0) print(` -> ${out.cipherPeer.hint}`);
81
+ if (out.auditDb.enabled) print(` ${marker(out.auditDb.exists === true)} audit.db: ${out.auditDb.path}`);
82
+ });
83
+ return out;
84
+ }
85
+ /**
86
+ * `graphorin storage encrypt --passphrase-from <ref>` — encrypt a
87
+ * previously unencrypted SQLite store. Delegates to the optional Phase
88
+ * 16 sub-pack `@graphorin/store-sqlite-encrypted` once installed; when
89
+ * the sub-pack is missing the CLI exits `2` (`UNSUPPORTED`) with an
90
+ * actionable hint.
91
+ *
92
+ * @stable
93
+ */
94
+ async function runStorageEncrypt(options) {
95
+ const subpack = await loadEncryptedSubpack();
96
+ if (subpack === null) return failUnsupported(options, `'graphorin storage encrypt' requires the optional sub-pack '@graphorin/store-sqlite-encrypted' (Phase 16) and the cipher peer 'better-sqlite3-multiple-ciphers'.`, "Install @graphorin/store-sqlite-encrypted (which pulls the cipher peer transitively) before running this command.");
97
+ const sourcePath = resolveStoragePath(parseServerConfig((await loadConfig(options.config)).config).storage.path);
98
+ const targetPath = options.targetPath ?? `${sourcePath}.encrypted`;
99
+ const passphrase = await resolvePassphraseRef(options.passphraseFrom);
100
+ try {
101
+ const result = await passphrase.use((raw) => subpack.encryptDatabase({
102
+ sourcePath,
103
+ targetPath,
104
+ passphrase: raw,
105
+ ...options.swap === true ? { swap: true } : {},
106
+ overwriteTarget: false
107
+ }));
108
+ const out = Object.freeze({
109
+ sourcePath,
110
+ targetPath: result.targetPath,
111
+ cipher: result.cipher,
112
+ integrityOk: result.integrityCheck.ok,
113
+ ...result.swap !== void 0 ? { swap: result.swap } : {}
114
+ });
115
+ emitReport(options, out, () => {
116
+ const print = options.print ?? defaultPrintSink;
117
+ print(brand(`encrypt: ${out.sourcePath} -> ${out.targetPath} (cipher=${out.cipher})`));
118
+ print(` ${marker(out.integrityOk)} cipher_integrity_check`);
119
+ if (out.swap !== void 0) print(` ${statusMarker("ok")} swapped; original kept at ${out.swap.originalRenamedTo}`);
120
+ });
121
+ return out;
122
+ } finally {
123
+ passphrase.dispose();
124
+ }
125
+ }
126
+ /** @stable */
127
+ async function runStorageRekey(options) {
128
+ const subpack = await loadEncryptedSubpack();
129
+ if (subpack === null) return failUnsupported(options, `'graphorin storage rekey' requires the optional sub-pack '@graphorin/store-sqlite-encrypted' (Phase 16).`, "Install @graphorin/store-sqlite-encrypted before running this command.");
130
+ const path = resolveStoragePath(parseServerConfig((await loadConfig(options.config)).config).storage.path);
131
+ const oldPassphrase = await resolvePassphraseRef(options.oldPassphraseFrom);
132
+ const newPassphrase = await resolvePassphraseRef(options.newPassphraseFrom);
133
+ try {
134
+ const result = await oldPassphrase.use((oldRaw) => newPassphrase.use((newRaw) => subpack.rekeyDatabase({
135
+ path,
136
+ oldPassphrase: oldRaw,
137
+ newPassphrase: newRaw
138
+ })));
139
+ const out = Object.freeze({
140
+ path: result.path,
141
+ cipher: result.cipher,
142
+ integrityOk: result.integrityCheck.ok
143
+ });
144
+ emitReport(options, out, () => {
145
+ const print = options.print ?? defaultPrintSink;
146
+ print(brand(`rekey: ${out.path} (cipher=${out.cipher})`));
147
+ print(` ${marker(out.integrityOk)} cipher_integrity_check`);
148
+ });
149
+ return out;
150
+ } finally {
151
+ oldPassphrase.dispose();
152
+ newPassphrase.dispose();
153
+ }
154
+ }
155
+ /**
156
+ * Drop stale `.bak`, `.bak.<ts>`, and `.tmp.<ts>` siblings of the
157
+ * configured storage path. Useful after `encrypt` / `rekey` runs that
158
+ * leave intermediate copies around.
159
+ *
160
+ * @stable
161
+ */
162
+ async function runStorageCleanupBackups(options = {}) {
163
+ const dbPath = resolveStoragePath(parseServerConfig((await loadConfig(options.config)).config).storage.path);
164
+ const dir = dirname(dbPath);
165
+ const basename = dbPath.split("/").pop() ?? "";
166
+ let entries;
167
+ try {
168
+ entries = await readdir(dir);
169
+ } catch (err) {
170
+ throw new Error(`[graphorin/cli] cannot read storage directory '${dir}': ${err.message}`);
171
+ }
172
+ const candidates = entries.filter((name) => isStaleBackup(basename, name));
173
+ const removed = [];
174
+ for (const name of candidates) {
175
+ const full = `${dir}/${name}`;
176
+ if (options.dryRun !== true) try {
177
+ await unlink(full);
178
+ removed.push(full);
179
+ } catch {}
180
+ else removed.push(full);
181
+ }
182
+ const out = Object.freeze({
183
+ directory: dir,
184
+ removed: Object.freeze(removed),
185
+ dryRun: options.dryRun === true
186
+ });
187
+ emitReport(options, out, () => {
188
+ const print = options.print ?? defaultPrintSink;
189
+ if (out.removed.length === 0) {
190
+ print(brand(`no stale backups found in ${dir}.`));
191
+ return;
192
+ }
193
+ print(brand(`${out.dryRun ? "would remove" : "removed"} ${out.removed.length} stale backup file(s) in ${dir}:`));
194
+ for (const name of out.removed) print(` - ${name}`);
195
+ });
196
+ return out;
197
+ }
198
+ function isStaleBackup(baseName, candidate) {
199
+ if (candidate === baseName) return false;
200
+ if (!candidate.startsWith(baseName)) return false;
201
+ const suffix = candidate.slice(baseName.length);
202
+ if (suffix === ".bak") return true;
203
+ if (/^\.bak\.\d+$/.test(suffix)) return true;
204
+ if (/^\.tmp\.\d+$/.test(suffix)) return true;
205
+ return false;
206
+ }
207
+ async function probeCipherPeer() {
208
+ try {
209
+ await import("better-sqlite3-multiple-ciphers");
210
+ return Object.freeze({ installed: true });
211
+ } catch {
212
+ return Object.freeze({
213
+ installed: false,
214
+ hint: "install '@graphorin/store-sqlite-encrypted' (Phase 16) which transitively installs the cipher peer."
215
+ });
216
+ }
217
+ }
218
+ /** @internal */
219
+ async function loadEncryptedSubpack() {
220
+ try {
221
+ const mod = await import("@graphorin/store-sqlite-encrypted");
222
+ if (typeof mod?.encryptDatabase === "function" && typeof mod?.rekeyDatabase === "function") return mod;
223
+ return null;
224
+ } catch {
225
+ return null;
226
+ }
227
+ }
228
+ /** @internal */
229
+ async function resolvePassphraseRef(ref) {
230
+ try {
231
+ return await resolveSecret(ref);
232
+ } catch (err) {
233
+ throw new Error(`[graphorin/cli] failed to resolve storage passphrase '${ref}': ${err.message}`, { cause: err });
234
+ }
235
+ }
236
+ async function statSafely(path) {
237
+ try {
238
+ return {
239
+ exists: true,
240
+ size: (await stat(path)).size
241
+ };
242
+ } catch {
243
+ return { exists: false };
244
+ }
245
+ }
246
+ function resolveStoragePath(target) {
247
+ return isAbsolute(target) ? target : resolve(target);
248
+ }
249
+ function deriveAuditPath(storagePath) {
250
+ const idx = storagePath.lastIndexOf("/");
251
+ if (idx < 0) return "audit.db";
252
+ return `${storagePath.slice(0, idx)}/audit.db`;
253
+ }
254
+ function formatSize(bytes) {
255
+ if (bytes === void 0) return "missing";
256
+ if (bytes < 1024) return `${bytes} B`;
257
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
258
+ return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;
259
+ }
260
+ function marker(ok) {
261
+ return ok ? statusMarker("ok") : statusMarker("warn");
262
+ }
263
+ function failUnsupported(options, message, hint) {
264
+ const print = options.print ?? defaultPrintSink;
265
+ print(brand(message));
266
+ print(brand(`hint: ${hint}`));
267
+ process.exit(EXIT_CODES.UNSUPPORTED);
268
+ /* c8 ignore next */
269
+ throw new Error("unreachable");
270
+ }
271
+
272
+ //#endregion
273
+ export { runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus };
274
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,21 @@
1
+ import { CommonOutputOptions } from "../internal/output.js";
2
+
3
+ //#region src/commands/telemetry.d.ts
4
+
5
+ /** @stable */
6
+ interface TelemetryStatusResult {
7
+ readonly enabled: false;
8
+ readonly policy: 'zero-default';
9
+ readonly reference: string;
10
+ }
11
+ /** @stable */
12
+ declare function runTelemetryStatus(options?: CommonOutputOptions): TelemetryStatusResult;
13
+ /** @stable */
14
+ declare function runTelemetryEnable(options?: CommonOutputOptions): TelemetryStatusResult;
15
+ /** @stable */
16
+ declare function runTelemetryDisable(options?: CommonOutputOptions): TelemetryStatusResult;
17
+ /** @stable */
18
+ declare function runTelemetryInspect(options?: CommonOutputOptions): TelemetryStatusResult;
19
+ //#endregion
20
+ export { TelemetryStatusResult, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus };
21
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telemetry.d.ts","names":[],"sources":["../../src/commands/telemetry.ts"],"sourcesContent":[],"mappings":";;;;;UAkCiB,qBAAA;;;;;;iBAeD,kBAAA,WAA4B,sBAA2B;;iBAUvD,kBAAA,WAA4B,sBAA2B;;iBAWvD,mBAAA,WAA6B,sBAA2B;;iBASxD,mBAAA,WAA6B,sBAA2B"}
@@ -0,0 +1,75 @@
1
+ import { brand, defaultPrintSink, emitReport, statusMarker } from "../internal/output.js";
2
+ import { EXIT_CODES } from "../internal/exit.js";
3
+ import process from "node:process";
4
+
5
+ //#region src/commands/telemetry.ts
6
+ /**
7
+ * `graphorin telemetry` — informational stubs.
8
+ *
9
+ * The framework promises **zero default telemetry** (DEC-154 /
10
+ * ADR-041). The Phase 15 surface ships the four subcommands as
11
+ * stubs that explain the policy and point operators at the
12
+ * documentation. Phase v0.2+ will extend the surface with an opt-in
13
+ * collector; today the stubs ensure the surface name-space is
14
+ * reserved + the operator gets a clear answer.
15
+ *
16
+ * Surface (per Phase 15 § Telemetry):
17
+ *
18
+ * - `graphorin telemetry status` — always reports `disabled`.
19
+ * - `graphorin telemetry enable` — refuses with a documentation
20
+ * pointer.
21
+ * - `graphorin telemetry disable` — no-op success.
22
+ * - `graphorin telemetry inspect` — informational summary of the
23
+ * promise (no phone home, no version pings, no crash reports).
24
+ *
25
+ * @packageDocumentation
26
+ */
27
+ const PRIVACY_REF = "SECURITY.md § Privacy & telemetry (DEC-154 / ADR-041)";
28
+ const ZERO_DEFAULT_RESULT = Object.freeze({
29
+ enabled: false,
30
+ policy: "zero-default",
31
+ reference: PRIVACY_REF
32
+ });
33
+ /** @stable */
34
+ function runTelemetryStatus(options = {}) {
35
+ emitReport(options, ZERO_DEFAULT_RESULT, () => {
36
+ const print = options.print ?? defaultPrintSink;
37
+ print(brand(`${statusMarker("info")} telemetry: disabled (policy: zero-default)`));
38
+ print(brand(`reference: ${PRIVACY_REF}`));
39
+ });
40
+ return ZERO_DEFAULT_RESULT;
41
+ }
42
+ /** @stable */
43
+ function runTelemetryEnable(options = {}) {
44
+ emitReport(options, ZERO_DEFAULT_RESULT, () => {
45
+ const print = options.print ?? defaultPrintSink;
46
+ print(brand(`telemetry enable refused — the framework promises zero phone-home (DEC-154).`));
47
+ print(brand(`an opt-in collector is on the v0.2+ roadmap; see ${PRIVACY_REF}.`));
48
+ });
49
+ process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
50
+ return ZERO_DEFAULT_RESULT;
51
+ }
52
+ /** @stable */
53
+ function runTelemetryDisable(options = {}) {
54
+ emitReport(options, ZERO_DEFAULT_RESULT, () => {
55
+ (options.print ?? defaultPrintSink)(brand(`telemetry is already disabled (no-op; zero-default policy).`));
56
+ });
57
+ return ZERO_DEFAULT_RESULT;
58
+ }
59
+ /** @stable */
60
+ function runTelemetryInspect(options = {}) {
61
+ emitReport(options, ZERO_DEFAULT_RESULT, () => {
62
+ const print = options.print ?? defaultPrintSink;
63
+ print(brand("graphorin telemetry inspect:"));
64
+ print(` ${statusMarker("ok")} no phone home`);
65
+ print(` ${statusMarker("ok")} no version pings`);
66
+ print(` ${statusMarker("ok")} no crash reports`);
67
+ print(` ${statusMarker("ok")} no auto-updates`);
68
+ print(brand(`reference: ${PRIVACY_REF}`));
69
+ });
70
+ return ZERO_DEFAULT_RESULT;
71
+ }
72
+
73
+ //#endregion
74
+ export { runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus };
75
+ //# sourceMappingURL=telemetry.js.map
@@ -0,0 +1 @@
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"}