@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,182 @@
1
+ import { brand, defaultPrintSink, emitReport, statusMarker } from "../internal/output.js";
2
+ import { EXIT_CODES } from "../internal/exit.js";
3
+ import { stdin } from "node:process";
4
+ import { SecretValue, createSecretsStore, getActiveSecretsStore, parseSecretRef, resolveSecret } from "@graphorin/security";
5
+
6
+ //#region src/commands/secrets.ts
7
+ /**
8
+ * `graphorin secrets` — manage the operator's secrets store.
9
+ *
10
+ * Surface (per Phase 15 § Secrets):
11
+ *
12
+ * - `graphorin secrets list`
13
+ * - `graphorin secrets get <key>`
14
+ * - `graphorin secrets set <key> [--value <v>] [--from-stdin] [--scope <ref>]`
15
+ * - `graphorin secrets delete <key>`
16
+ * - `graphorin secrets ref <uri>` — test resolution of a `SecretRef` URI.
17
+ * - `graphorin secrets rotate <key> --new-value <v>`
18
+ *
19
+ * Honours `--secrets-source <kind>` and `--strict-secrets` per
20
+ * DEC-136 — both flags are forwarded to `createSecretsStore(...)` so
21
+ * the CLI activates the same store the running server would.
22
+ *
23
+ * The CLI never logs raw secret bytes. `get` prints the value through
24
+ * `value.use((s) => print(s))` so the bytes are released as soon as
25
+ * the print callback returns; `--json` mode emits only the metadata
26
+ * fields, never the value, unless `--reveal` is explicitly supplied.
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ /** @stable */
31
+ async function runSecretsList(options = {}) {
32
+ const list = await (await openStore(options)).list();
33
+ emitReport(options, list, () => {
34
+ const print = options.print ?? defaultPrintSink;
35
+ if (list.length === 0) {
36
+ print(brand("no secrets recorded."));
37
+ return;
38
+ }
39
+ print(brand(`${list.length} secret(s):`));
40
+ for (const m of list) print(` ${statusMarker("ok")} ${m.key} (updated=${m.updatedAt ?? "-"})`);
41
+ });
42
+ return list;
43
+ }
44
+ /** @stable */
45
+ async function runSecretsGet(options) {
46
+ const value = await (await openStore(options)).get(options.key);
47
+ if (value === null) {
48
+ const out$1 = Object.freeze({
49
+ key: options.key,
50
+ found: false
51
+ });
52
+ emitReport(options, out$1, () => {
53
+ (options.print ?? defaultPrintSink)(brand(`secret '${options.key}' not found.`));
54
+ process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
55
+ });
56
+ return out$1;
57
+ }
58
+ if (options.reveal === true) {
59
+ const raw = await value.use((s) => String(s));
60
+ const out$1 = Object.freeze({
61
+ key: options.key,
62
+ found: true,
63
+ value: raw
64
+ });
65
+ emitReport(options, out$1, () => {
66
+ const print = options.print ?? defaultPrintSink;
67
+ print(brand(`secret '${options.key}' resolved (length=${raw.length})`));
68
+ print(` ${raw}`);
69
+ });
70
+ return out$1;
71
+ }
72
+ const out = Object.freeze({
73
+ key: options.key,
74
+ found: true
75
+ });
76
+ emitReport(options, out, () => {
77
+ (options.print ?? defaultPrintSink)(brand(`secret '${options.key}' found (use --reveal to print the bytes)`));
78
+ });
79
+ return out;
80
+ }
81
+ /** @stable */
82
+ async function runSecretsSet(options) {
83
+ const store = await openStore(options);
84
+ let raw = options.value;
85
+ if (raw === void 0 && options.fromStdin === true) raw = await readStdin();
86
+ if (raw === void 0) throw new Error("[graphorin/cli] secrets set requires either --value <v> or --from-stdin (no plaintext on the command line).");
87
+ await store.set(options.key, SecretValue.fromString(raw, { source: { resolver: "graphorin secrets set" } }));
88
+ emitReport(options, { ok: true }, () => {
89
+ (options.print ?? defaultPrintSink)(brand(`secret '${options.key}' written.`));
90
+ });
91
+ return { ok: true };
92
+ }
93
+ /** @stable */
94
+ async function runSecretsDelete(options) {
95
+ await (await openStore(options)).delete(options.key);
96
+ emitReport(options, { ok: true }, () => {
97
+ (options.print ?? defaultPrintSink)(brand(`secret '${options.key}' deleted.`));
98
+ });
99
+ return { ok: true };
100
+ }
101
+ /**
102
+ * Test resolution of a `SecretRef` URI. The CLI parses the URI first
103
+ * (sanity check + scheme echo), then resolves through the registered
104
+ * resolver chain.
105
+ *
106
+ * @stable
107
+ */
108
+ async function runSecretsRef(options) {
109
+ const parsed = parseSecretRef(options.uri);
110
+ try {
111
+ const value = await resolveSecret(options.uri);
112
+ const length = await value.use((s) => s.length);
113
+ const raw = options.reveal === true ? await value.use((s) => String(s)) : void 0;
114
+ const out = Object.freeze({
115
+ uri: options.uri,
116
+ scheme: parsed.scheme,
117
+ resolved: true,
118
+ length,
119
+ ...raw !== void 0 ? { value: raw } : {}
120
+ });
121
+ emitReport(options, out, () => {
122
+ (options.print ?? defaultPrintSink)(brand(`${statusMarker("ok")} resolved '${parsed.scheme}' ref (length=${length}${raw !== void 0 ? `, value='${raw}'` : ""})`));
123
+ });
124
+ return out;
125
+ } catch (err) {
126
+ const out = Object.freeze({
127
+ uri: options.uri,
128
+ scheme: parsed.scheme,
129
+ resolved: false
130
+ });
131
+ emitReport(options, out, () => {
132
+ (options.print ?? defaultPrintSink)(brand(`${statusMarker("fail")} ${parsed.scheme} ref failed: ${err.message}`));
133
+ process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
134
+ });
135
+ return out;
136
+ }
137
+ }
138
+ /**
139
+ * `graphorin secrets rotate <key>` — overwrite the existing value
140
+ * with a fresh one. Functionally identical to `set` but the CLI
141
+ * surfaces the operation explicitly so audit logs can distinguish
142
+ * a rotation from an initial write.
143
+ *
144
+ * @stable
145
+ */
146
+ async function runSecretsRotate(options) {
147
+ const store = await openStore(options);
148
+ if (await store.get(options.key) === null) throw new Error(`[graphorin/cli] cannot rotate '${options.key}' — secret does not exist (use 'graphorin secrets set' to create it).`);
149
+ let raw = options.newValue;
150
+ if (raw === void 0 && options.fromStdin === true) raw = await readStdin();
151
+ if (raw === void 0) throw new Error("[graphorin/cli] secrets rotate requires either --new-value <v> or --from-stdin.");
152
+ await store.set(options.key, SecretValue.fromString(raw, { source: { resolver: "graphorin secrets rotate" } }));
153
+ emitReport(options, { ok: true }, () => {
154
+ (options.print ?? defaultPrintSink)(brand(`secret '${options.key}' rotated.`));
155
+ });
156
+ return { ok: true };
157
+ }
158
+ async function openStore(options) {
159
+ if (options.secretsSource === void 0) {
160
+ const active = getActiveSecretsStore();
161
+ if (active !== void 0) return active;
162
+ }
163
+ return await createSecretsStore({
164
+ ...options.secretsSource !== void 0 ? { kind: options.secretsSource } : {},
165
+ ...options.strictSecrets !== void 0 ? { strict: options.strictSecrets } : {}
166
+ });
167
+ }
168
+ async function readStdin() {
169
+ return await new Promise((resolve, reject) => {
170
+ let buf = "";
171
+ stdin.setEncoding("utf8");
172
+ stdin.on("data", (chunk) => {
173
+ buf += chunk;
174
+ });
175
+ stdin.on("end", () => resolve(buf.replace(/\r?\n$/, "")));
176
+ stdin.on("error", reject);
177
+ });
178
+ }
179
+
180
+ //#endregion
181
+ export { runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet };
182
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,58 @@
1
+ import { CommonOutputOptions } from "../internal/output.js";
2
+ import { SkillInstallationStatus } from "@graphorin/security";
3
+ import { MigrationResult } from "@graphorin/skills";
4
+
5
+ //#region src/commands/skills.d.ts
6
+
7
+ /** @stable */
8
+ interface SkillsCommonOptions extends CommonOutputOptions {}
9
+ /** @stable */
10
+ type SkillTrustLevelInput = 'trusted' | 'trusted-with-scripts' | 'untrusted';
11
+ /** @stable */
12
+ interface SkillsInstallOptions extends SkillsCommonOptions {
13
+ /** `npm:<name>[@version]` or `git:<url>` source. */
14
+ readonly source: string;
15
+ /** Optional explicit version pin (npm sources only). */
16
+ readonly version?: string;
17
+ /** Optional git ref (git sources only). */
18
+ readonly ref?: string;
19
+ /** Trust level for the operator's project. Defaults to the helper's own default. */
20
+ readonly trustLevel?: SkillTrustLevelInput;
21
+ /** Working directory for npm installs. */
22
+ readonly cwd?: string;
23
+ readonly dryRun?: boolean;
24
+ }
25
+ /** @stable */
26
+ declare function runSkillsInstall(options: SkillsInstallOptions): Promise<SkillInstallationStatus>;
27
+ /** @stable */
28
+ interface SkillsInspectOptions extends SkillsCommonOptions {
29
+ readonly name: string;
30
+ }
31
+ /** @stable */
32
+ declare function runSkillsInspect(options: SkillsInspectOptions): Promise<SkillInstallationStatus | null>;
33
+ /** @stable */
34
+ interface SkillsAuditOptions extends SkillsCommonOptions {}
35
+ /** @stable */
36
+ declare function runSkillsAudit(options?: SkillsAuditOptions): readonly SkillInstallationStatus[];
37
+ /** @stable */
38
+ interface SkillsMigrateFrontmatterOptions extends SkillsCommonOptions {
39
+ /** Directory to walk. Defaults to `process.cwd()`. */
40
+ readonly path?: string;
41
+ readonly recursive?: boolean;
42
+ readonly apply?: boolean;
43
+ }
44
+ /** @stable */
45
+ interface SkillsMigrateFrontmatterResult {
46
+ readonly directory: string;
47
+ readonly visited: number;
48
+ readonly migrated: ReadonlyArray<{
49
+ readonly file: string;
50
+ readonly result: MigrationResult;
51
+ }>;
52
+ readonly applied: boolean;
53
+ }
54
+ /** @stable */
55
+ declare function runSkillsMigrateFrontmatter(options?: SkillsMigrateFrontmatterOptions): Promise<SkillsMigrateFrontmatterResult>;
56
+ //#endregion
57
+ export { SkillTrustLevelInput, SkillsAuditOptions, SkillsCommonOptions, SkillsInspectOptions, SkillsInstallOptions, SkillsMigrateFrontmatterOptions, SkillsMigrateFrontmatterResult, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter };
58
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","names":[],"sources":["../../src/commands/skills.ts"],"sourcesContent":[],"mappings":";;;;;;AA4KA;AAWsB,UA1IL,mBAAA,SAA4B,mBA0II,CAAA;;AAE9C,KAzIS,oBAAA,GAyIT,SAAA,GAAA,sBAAA,GAAA,WAAA;;UAtIc,oBAAA,SAA6B;;;;;;;;wBAQtB;;;;;;iBAOF,gBAAA,UACX,uBACR,QAAQ;;UA2CM,oBAAA,SAA6B;;;;iBAKxB,gBAAA,UACX,uBACR,QAAQ;;UAuBM,kBAAA,SAA2B;;iBAG5B,cAAA,WAAwB,8BAAuB;;UAoB9C,+BAAA,SAAwC;;;;;;;UAQxC,8BAAA;;;qBAGI;;qBAEA;;;;;iBAMC,2BAAA,WACX,kCACR,QAAQ"}
@@ -0,0 +1,181 @@
1
+ import { brand, defaultPrintSink, emitReport, statusMarker } from "../internal/output.js";
2
+ import { EXIT_CODES } from "../internal/exit.js";
3
+ import { checkOfflineModeBlocked } from "../internal/offline.js";
4
+ import { readFile, readdir, stat, writeFile } from "node:fs/promises";
5
+ import { join, resolve } from "node:path";
6
+ import process from "node:process";
7
+ import { auditInstalledSkills, installSkillFromGit, installSkillFromNpm } from "@graphorin/security";
8
+ import { migrateFrontmatter } from "@graphorin/skills";
9
+
10
+ //#region src/commands/skills.ts
11
+ /**
12
+ * `graphorin skills` — install + audit + migrate operator-managed
13
+ * skill packages.
14
+ *
15
+ * Surface (per Phase 15 § Skills):
16
+ *
17
+ * - `graphorin skills install <source>` — `npm:` or `git:` source.
18
+ * Honours allowlist / denylist + signature verification (DEC-140 /
19
+ * ADR-034). Refuses when `GRAPHORIN_OFFLINE=1` is set.
20
+ * - `graphorin skills inspect <name>` — frontmatter + signature +
21
+ * supply-chain status of an installed skill.
22
+ * - `graphorin skills audit` — full audit of every recorded
23
+ * installation.
24
+ * - `graphorin skills migrate-frontmatter [--apply] [--recursive]`
25
+ * — DEC-156: rewrites legacy `graphorin-*` frontmatter fields onto
26
+ * their upstream equivalents.
27
+ *
28
+ * @packageDocumentation
29
+ */
30
+ /** @stable */
31
+ async function runSkillsInstall(options) {
32
+ if (!checkOfflineModeBlocked("skills install", { ...options.print !== void 0 ? { print: options.print } : {} })) process.exit(EXIT_CODES.RECOVERABLE_FAILURE);
33
+ const print = options.print ?? defaultPrintSink;
34
+ const parsed = parseSource(options.source);
35
+ let result;
36
+ if (parsed.kind === "npm") result = await installSkillFromNpm({
37
+ packageName: parsed.value,
38
+ ...options.version !== void 0 ? { version: options.version } : {},
39
+ ...options.trustLevel !== void 0 ? { trustLevel: options.trustLevel } : {},
40
+ ...options.cwd !== void 0 ? { cwd: options.cwd } : {},
41
+ ...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {}
42
+ });
43
+ else result = await installSkillFromGit({
44
+ repoUrl: parsed.value,
45
+ ...options.ref !== void 0 ? { ref: options.ref } : {},
46
+ ...options.trustLevel !== void 0 ? { trustLevel: options.trustLevel } : {},
47
+ ...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {}
48
+ });
49
+ emitReport(options, result, () => {
50
+ print(brand(`installed skill '${result.id}' (${result.source.kind}, signatureVerified=${result.signatureVerified})`));
51
+ if (result.signature !== void 0) print(` signature valid: ${result.signature.valid}`);
52
+ });
53
+ return result;
54
+ }
55
+ /** @stable */
56
+ async function runSkillsInspect(options) {
57
+ const match = auditInstalledSkills().find((s) => s.id === options.name) ?? null;
58
+ emitReport(options, match, () => {
59
+ const print = options.print ?? defaultPrintSink;
60
+ if (match === null) {
61
+ print(brand(`skill '${options.name}' not found in this process registry.`));
62
+ process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;
63
+ return;
64
+ }
65
+ print(brand(`skill ${match.id}`));
66
+ print(` source: ${match.source.kind}`);
67
+ print(` trust level: ${match.trustLevel}`);
68
+ print(` ignoreScripts: ${match.ignoreScripts}`);
69
+ print(` signatureVerified: ${match.signatureVerified}`);
70
+ print(` installedAt: ${new Date(match.installedAt).toISOString()}`);
71
+ if (match.installPath !== void 0) print(` installPath: ${match.installPath}`);
72
+ if (match.publisher !== void 0) print(` publisher: ${match.publisher}`);
73
+ });
74
+ return match;
75
+ }
76
+ /** @stable */
77
+ function runSkillsAudit(options = {}) {
78
+ const all = auditInstalledSkills();
79
+ emitReport(options, all, () => {
80
+ const print = options.print ?? defaultPrintSink;
81
+ if (all.length === 0) {
82
+ print(brand("no skills installed in this process registry."));
83
+ return;
84
+ }
85
+ print(brand(`${all.length} installed skill(s):`));
86
+ for (const s of all) print(` ${s.signatureVerified ? statusMarker("ok") : statusMarker("warn")} ${s.id} (${s.source.kind}, trust=${s.trustLevel}, signatureVerified=${s.signatureVerified})`);
87
+ });
88
+ return all;
89
+ }
90
+ /** @stable */
91
+ async function runSkillsMigrateFrontmatter(options = {}) {
92
+ const root = resolve(options.path ?? process.cwd());
93
+ const recursive = options.recursive === true;
94
+ const apply = options.apply === true;
95
+ const files = await collectSkillMd(root, recursive);
96
+ const migrated = [];
97
+ for (const file of files) {
98
+ const raw = await readFile(file, "utf8");
99
+ let result;
100
+ try {
101
+ result = migrateFrontmatter(raw, {
102
+ apply,
103
+ skillId: file
104
+ });
105
+ } catch {
106
+ continue;
107
+ }
108
+ if (result.changed) {
109
+ if (apply) await writeFile(file, result.migratedSkillMd, { mode: 384 });
110
+ migrated.push({
111
+ file,
112
+ result
113
+ });
114
+ }
115
+ }
116
+ const out = Object.freeze({
117
+ directory: root,
118
+ visited: files.length,
119
+ migrated: Object.freeze(migrated),
120
+ applied: apply
121
+ });
122
+ emitReport(options, out, () => {
123
+ const print = options.print ?? defaultPrintSink;
124
+ print(brand(`migrate-frontmatter visited ${out.visited} SKILL.md file(s) under ${out.directory}.`));
125
+ if (out.migrated.length === 0) {
126
+ print(brand("no rewrites required."));
127
+ return;
128
+ }
129
+ print(brand(`${out.migrated.length} file(s) ${apply ? "rewritten" : "would be rewritten (dry-run)"}:`));
130
+ for (const m of out.migrated) print(` - ${m.file}`);
131
+ });
132
+ return out;
133
+ }
134
+ function parseSource(input) {
135
+ if (input.startsWith("npm:")) return {
136
+ kind: "npm",
137
+ value: input.slice(4)
138
+ };
139
+ if (input.startsWith("git:")) return {
140
+ kind: "git",
141
+ value: input.slice(4)
142
+ };
143
+ if (input.includes("://")) return {
144
+ kind: "git",
145
+ value: input
146
+ };
147
+ return {
148
+ kind: "npm",
149
+ value: input
150
+ };
151
+ }
152
+ async function collectSkillMd(root, recursive) {
153
+ const out = [];
154
+ await walk(root, recursive, out);
155
+ return out;
156
+ }
157
+ async function walk(dir, recursive, acc) {
158
+ let entries;
159
+ try {
160
+ entries = await readdir(dir);
161
+ } catch {
162
+ return;
163
+ }
164
+ for (const name of entries) {
165
+ if (name === "node_modules" || name === ".git" || name === "dist") continue;
166
+ const full = join(dir, name);
167
+ let s;
168
+ try {
169
+ s = await stat(full);
170
+ } catch {
171
+ continue;
172
+ }
173
+ if (s.isDirectory()) {
174
+ if (recursive) await walk(full, recursive, acc);
175
+ } else if (name === "SKILL.md") acc.push(full);
176
+ }
177
+ }
178
+
179
+ //#endregion
180
+ export { runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter };
181
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +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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"}
@@ -0,0 +1,51 @@
1
+ //#region src/commands/start.d.ts
2
+ /**
3
+ * `graphorin start [--config <path>]`. Boots the standalone server.
4
+ *
5
+ * Exits 1 on every recoverable failure (missing pepper, unresolvable
6
+ * SecretRef, missing encryption peer, migration error). The CLI never
7
+ * prints raw secret values; failure messages reference the offending
8
+ * config path + the suggested `graphorin doctor` follow-up (Phase 15).
9
+ *
10
+ * @packageDocumentation
11
+ */
12
+ /**
13
+ * Selector for which `SecretsStore` flavour the server activates at
14
+ * startup. Mirrors `--secrets-source` from DEC-136.
15
+ *
16
+ * @stable
17
+ */
18
+ type SecretsSourceFlag = 'auto' | 'keyring' | 'encrypted-file' | 'env';
19
+ /**
20
+ * @stable
21
+ */
22
+ interface StartCommandOptions {
23
+ readonly config?: string;
24
+ readonly host?: string;
25
+ readonly port?: number;
26
+ readonly logResolved?: boolean;
27
+ /**
28
+ * Override the `secrets.source` field of the loaded config. Mirrors
29
+ * the `--secrets-source <kind>` flag from DEC-136.
30
+ */
31
+ readonly secretsSource?: SecretsSourceFlag;
32
+ /**
33
+ * Refuse to fall back when the requested primary store is
34
+ * unavailable. Mirrors `--strict-secrets` from DEC-136.
35
+ */
36
+ readonly strictSecrets?: boolean;
37
+ }
38
+ /**
39
+ * Programmatic entry — used both by the CLI binary and by tests so
40
+ * the spawn cost of running the binary is paid only when an operator
41
+ * actually invokes `graphorin start` from a shell.
42
+ *
43
+ * @stable
44
+ */
45
+ declare function runStart(options?: StartCommandOptions): Promise<{
46
+ readonly host: string;
47
+ readonly port: number;
48
+ }>;
49
+ //#endregion
50
+ export { SecretsSourceFlag, StartCommandOptions, runStart };
51
+ //# sourceMappingURL=start.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"start.d.ts","names":[],"sources":["../../src/commands/start.ts"],"sourcesContent":[],"mappings":";;AAwBA;AAKA;AAwBA;;;;;;;;;;;;;KA7BY,iBAAA;;;;UAKK,mBAAA;;;;;;;;;2BASU;;;;;;;;;;;;;;iBAeL,QAAA,WACX,sBACR"}
@@ -0,0 +1,122 @@
1
+ import { loadConfig } from "../internal/load-config.js";
2
+ import process from "node:process";
3
+ import { RefuseToRunAsRootError, applyProcessHardening } from "@graphorin/security";
4
+ import { ConfigInvalidError, GraphorinServerError, createServer } from "@graphorin/server";
5
+
6
+ //#region src/commands/start.ts
7
+ /**
8
+ * `graphorin start [--config <path>]`. Boots the standalone server.
9
+ *
10
+ * Exits 1 on every recoverable failure (missing pepper, unresolvable
11
+ * SecretRef, missing encryption peer, migration error). The CLI never
12
+ * prints raw secret values; failure messages reference the offending
13
+ * config path + the suggested `graphorin doctor` follow-up (Phase 15).
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+ /**
18
+ * Programmatic entry — used both by the CLI binary and by tests so
19
+ * the spawn cost of running the binary is paid only when an operator
20
+ * actually invokes `graphorin start` from a shell.
21
+ *
22
+ * @stable
23
+ */
24
+ async function runStart(options = {}) {
25
+ applyHardeningEarly();
26
+ const loaded = await loadConfig(options.config);
27
+ if (options.logResolved !== false) process.stderr.write(`[graphorin/cli] resolved config: ${loaded.path}\n`);
28
+ const overrides = applyCliOverrides(loaded.config, options);
29
+ let server;
30
+ try {
31
+ server = await createServer({ config: overrides });
32
+ } catch (err) {
33
+ fatal(err);
34
+ }
35
+ if (server === void 0) process.exit(1);
36
+ const listening = await safeStart(server);
37
+ process.stderr.write(`[graphorin/cli] @graphorin/server v${server.version} listening on http://${listening.host}:${listening.port}${server.config.server.basePath}\n`);
38
+ installSignalHandlers(server);
39
+ return listening;
40
+ }
41
+ /**
42
+ * Merge CLI flag overrides into the loaded `graphorin.config` payload.
43
+ * Exported for unit tests so callers can assert the precedence rules
44
+ * without spinning up a real server.
45
+ *
46
+ * @internal
47
+ */
48
+ function applyCliOverrides(input, options) {
49
+ const next = { ...input ?? {} };
50
+ const serverInput = next.server ?? {};
51
+ if (options.host !== void 0 || options.port !== void 0) next.server = {
52
+ ...serverInput,
53
+ ...options.host !== void 0 ? { host: options.host } : {},
54
+ ...options.port !== void 0 ? { port: options.port } : {}
55
+ };
56
+ const secretsInput = next.secrets ?? {};
57
+ if (options.secretsSource !== void 0 || options.strictSecrets !== void 0) next.secrets = {
58
+ ...secretsInput,
59
+ ...options.secretsSource !== void 0 ? { source: options.secretsSource } : {},
60
+ ...options.strictSecrets !== void 0 ? { strict: options.strictSecrets } : {}
61
+ };
62
+ return next;
63
+ }
64
+ async function safeStart(server) {
65
+ try {
66
+ return await server.start();
67
+ } catch (err) {
68
+ fatal(err);
69
+ }
70
+ process.exit(1);
71
+ }
72
+ function fatal(err) {
73
+ if (err instanceof ConfigInvalidError) {
74
+ process.stderr.write("[graphorin/cli] graphorin.config invalid:\n");
75
+ for (const issue of err.issues) process.stderr.write(` - ${issue.path.join(".") || "<root>"}: ${issue.message}\n`);
76
+ } else if (err instanceof GraphorinServerError) {
77
+ process.stderr.write(`[graphorin/cli] ${err.kind}: ${err.message}\n`);
78
+ if (err.hint !== void 0) process.stderr.write(` hint: ${err.hint}\n`);
79
+ } else if (err instanceof Error) process.stderr.write(`[graphorin/cli] ${err.message}\n`);
80
+ else process.stderr.write(`[graphorin/cli] unknown error: ${String(err)}\n`);
81
+ process.exit(1);
82
+ }
83
+ /**
84
+ * Apply the standard hardening hooks before the server bootstraps.
85
+ * Failures (e.g. running as root on Linux/macOS) abort the start with
86
+ * a documented exit code.
87
+ *
88
+ * @internal
89
+ */
90
+ function applyHardeningEarly() {
91
+ try {
92
+ applyProcessHardening({});
93
+ } catch (err) {
94
+ if (err instanceof RefuseToRunAsRootError) {
95
+ process.stderr.write(`[graphorin/cli] ${err.message}\n`);
96
+ process.stderr.write("[graphorin/cli] hint: drop privileges (systemd User=, k8s runAsNonRoot, docker --user) and retry.\n");
97
+ process.exit(1);
98
+ }
99
+ throw err;
100
+ }
101
+ }
102
+ function installSignalHandlers(server) {
103
+ let shuttingDown = false;
104
+ const onSignal = (signal) => {
105
+ if (shuttingDown) return;
106
+ shuttingDown = true;
107
+ process.stderr.write(`[graphorin/cli] received ${signal}; draining...\n`);
108
+ server.stop().then(() => {
109
+ process.stderr.write("[graphorin/cli] graceful shutdown complete\n");
110
+ process.exit(0);
111
+ }).catch((err) => {
112
+ process.stderr.write(`[graphorin/cli] shutdown error: ${err.message}\n`);
113
+ process.exit(1);
114
+ });
115
+ };
116
+ process.once("SIGTERM", onSignal);
117
+ process.once("SIGINT", onSignal);
118
+ }
119
+
120
+ //#endregion
121
+ export { applyHardeningEarly, runStart };
122
+ //# sourceMappingURL=start.js.map