@graphorin/cli 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +8 -8
  3. package/dist/bin/graphorin.js +15 -7
  4. package/dist/bin/graphorin.js.map +1 -1
  5. package/dist/commands/audit.d.ts +2 -2
  6. package/dist/commands/audit.js +6 -6
  7. package/dist/commands/audit.js.map +1 -1
  8. package/dist/commands/auth.d.ts +1 -1
  9. package/dist/commands/auth.js +3 -3
  10. package/dist/commands/auth.js.map +1 -1
  11. package/dist/commands/consolidator.js +2 -2
  12. package/dist/commands/consolidator.js.map +1 -1
  13. package/dist/commands/doctor.d.ts +1 -1
  14. package/dist/commands/doctor.js +5 -5
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/commands/guard.js +3 -3
  17. package/dist/commands/guard.js.map +1 -1
  18. package/dist/commands/index.d.ts +2 -2
  19. package/dist/commands/index.js +2 -2
  20. package/dist/commands/init.d.ts +2 -2
  21. package/dist/commands/init.js +3 -3
  22. package/dist/commands/init.js.map +1 -1
  23. package/dist/commands/memory.d.ts +5 -5
  24. package/dist/commands/memory.d.ts.map +1 -1
  25. package/dist/commands/memory.js +11 -11
  26. package/dist/commands/memory.js.map +1 -1
  27. package/dist/commands/migrate-config.js +2 -2
  28. package/dist/commands/migrate-config.js.map +1 -1
  29. package/dist/commands/migrate-export.d.ts +1 -1
  30. package/dist/commands/migrate-export.js +2 -2
  31. package/dist/commands/migrate-export.js.map +1 -1
  32. package/dist/commands/migrate.d.ts +1 -1
  33. package/dist/commands/migrate.js +1 -1
  34. package/dist/commands/migrate.js.map +1 -1
  35. package/dist/commands/pricing.d.ts +1 -1
  36. package/dist/commands/pricing.js +6 -6
  37. package/dist/commands/pricing.js.map +1 -1
  38. package/dist/commands/secrets.d.ts +1 -1
  39. package/dist/commands/secrets.js +5 -5
  40. package/dist/commands/secrets.js.map +1 -1
  41. package/dist/commands/skills.js +5 -5
  42. package/dist/commands/skills.js.map +1 -1
  43. package/dist/commands/start.d.ts +1 -1
  44. package/dist/commands/start.js +1 -1
  45. package/dist/commands/start.js.map +1 -1
  46. package/dist/commands/storage.d.ts +26 -2
  47. package/dist/commands/storage.d.ts.map +1 -1
  48. package/dist/commands/storage.js +51 -11
  49. package/dist/commands/storage.js.map +1 -1
  50. package/dist/commands/telemetry.js +6 -6
  51. package/dist/commands/telemetry.js.map +1 -1
  52. package/dist/commands/token.d.ts +2 -2
  53. package/dist/commands/token.js +4 -4
  54. package/dist/commands/token.js.map +1 -1
  55. package/dist/commands/tools-lint.d.ts +1 -1
  56. package/dist/commands/tools-lint.js +6 -6
  57. package/dist/commands/tools-lint.js.map +1 -1
  58. package/dist/commands/traces.js +5 -5
  59. package/dist/commands/traces.js.map +1 -1
  60. package/dist/commands/triggers.d.ts +1 -1
  61. package/dist/commands/triggers.js +1 -1
  62. package/dist/commands/triggers.js.map +1 -1
  63. package/dist/index.d.ts +4 -4
  64. package/dist/index.js +4 -4
  65. package/dist/index.js.map +1 -1
  66. package/dist/internal/exit.js.map +1 -1
  67. package/dist/internal/load-config.js +5 -5
  68. package/dist/internal/load-config.js.map +1 -1
  69. package/dist/internal/offline.d.ts +1 -1
  70. package/dist/internal/offline.js +2 -2
  71. package/dist/internal/offline.js.map +1 -1
  72. package/dist/internal/output.d.ts +6 -6
  73. package/dist/internal/output.js +6 -6
  74. package/dist/internal/output.js.map +1 -1
  75. package/dist/internal/store-context.js.map +1 -1
  76. package/package.json +12 -12
@@ -5,7 +5,7 @@ import { createToken, listTokens, rekeyTokens, revokeToken, rotateToken, verifyO
5
5
 
6
6
  //#region src/commands/token.ts
7
7
  /**
8
- * `graphorin token` manage server auth tokens.
8
+ * `graphorin token` - manage server auth tokens.
9
9
  *
10
10
  * The subcommand thin-wraps the library functions in
11
11
  * `@graphorin/security/auth` (`createToken`, `listTokens`,
@@ -19,7 +19,7 @@ import { createToken, listTokens, rekeyTokens, revokeToken, rotateToken, verifyO
19
19
  * - `graphorin token rekey`
20
20
  * - `graphorin token verify <token>`
21
21
  *
22
- * The raw token bytes are shown to the operator at most once at the
22
+ * The raw token bytes are shown to the operator at most once - at the
23
23
  * call site of `create` / `rotate` / `rekey`. They are wrapped in a
24
24
  * {@link SecretValue} on the way back to keep accidental logging out
25
25
  * of the codepath; the CLI prints them via `value.use((s) => ...)` so
@@ -198,7 +198,7 @@ async function runTokenRekey(options = {}) {
198
198
  /**
199
199
  * Offline checksum verification. Confirms the structural shape, the
200
200
  * environment marker, and the CRC checksum but does NOT consult the
201
- * token store it only proves the token was minted by a Graphorin
201
+ * token store - it only proves the token was minted by a Graphorin
202
202
  * helper.
203
203
  *
204
204
  * @stable
@@ -222,7 +222,7 @@ function runTokenVerify(options) {
222
222
  }
223
223
  /**
224
224
  * Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns
225
- * milliseconds. Throws on invalid input surfaced as a fail-fast at
225
+ * milliseconds. Throws on invalid input - surfaced as a fail-fast at
226
226
  * the CLI boundary.
227
227
  *
228
228
  * @internal
@@ -1 +1 @@
1
- {"version":3,"file":"token.js","names":["out: TokenCreateResult","out: Array<{ readonly oldId: string; readonly newId: string; readonly raw: string }>","out: TokenVerifyResult"],"sources":["../../src/commands/token.ts"],"sourcesContent":["/**\n * `graphorin token` — manage server auth tokens.\n *\n * The subcommand thin-wraps the library functions in\n * `@graphorin/security/auth` (`createToken`, `listTokens`,\n * `revokeToken`, `rotateToken`, `rekeyTokens`, `verifyOffline`). Per\n * Phase 15 § Tokens the surface ships:\n *\n * - `graphorin token create --label <name> --scopes <list> [--expires-in <duration>]`\n * - `graphorin token list`\n * - `graphorin token revoke <id>`\n * - `graphorin token rotate <id>`\n * - `graphorin token rekey`\n * - `graphorin token verify <token>`\n *\n * The raw token bytes are shown to the operator at most once — at the\n * call site of `create` / `rotate` / `rekey`. They are wrapped in a\n * {@link SecretValue} on the way back to keep accidental logging out\n * of the codepath; the CLI prints them via `value.use((s) => ...)` so\n * the bytes never live as a plain string variable longer than needed.\n *\n * @packageDocumentation\n */\n\nimport {\n createToken,\n listTokens,\n rekeyTokens,\n revokeToken,\n rotateToken,\n type TokenMetadata,\n verifyOffline,\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';\nimport { openStoreContext } from '../internal/store-context.js';\n\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\n/** @stable */\nexport interface TokenCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface TokenCreateOptions extends TokenCommonOptions {\n readonly label?: string;\n /** Comma-separated scope list. */\n readonly scopes: ReadonlyArray<string>;\n /** Duration string: `30d`, `12h`, `90m`, `45s`. */\n readonly expiresIn?: string;\n readonly env?: 'live' | 'test';\n}\n\n/** @stable */\nexport interface TokenCreateResult {\n readonly id: string;\n readonly label?: string;\n readonly scopes: ReadonlyArray<string>;\n readonly raw: string;\n readonly expiresAt?: string;\n}\n\n/**\n * Create a token. Returns the raw value once + the persisted record.\n *\n * @stable\n */\nexport async function runTokenCreate(options: TokenCreateOptions): Promise<TokenCreateResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error(\n '[graphorin/cli] internal: pepper resolution should have populated ctx.pepper',\n );\n }\n const env = options.env ?? 'live';\n const expiresInMs =\n options.expiresIn !== undefined ? parseDuration(options.expiresIn) : undefined;\n const created = await createToken({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n env,\n scopes: options.scopes,\n ...(options.label !== undefined ? { label: options.label } : {}),\n ...(expiresInMs !== undefined ? { expiresInMs } : {}),\n });\n const raw = await created.raw.use((s) => String(s));\n const out: TokenCreateResult = Object.freeze({\n id: created.record.id,\n ...(created.record.label !== undefined ? { label: created.record.label } : {}),\n scopes: Object.freeze([...created.record.scopes]),\n raw,\n ...(created.record.expiresAt !== undefined ? { expiresAt: created.record.expiresAt } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`token created (id=${out.id}, scopes=${out.scopes.join(',')})`));\n print(brand(`raw token (shown ONCE):`));\n print(` ${out.raw}`);\n if (out.expiresAt !== undefined) {\n print(brand(`expires at: ${out.expiresAt}`));\n }\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenListOptions extends TokenCommonOptions {\n readonly includeRevoked?: boolean;\n}\n\n/**\n * List token metadata.\n *\n * @stable\n */\nexport async function runTokenList(\n options: TokenListOptions = {},\n): Promise<ReadonlyArray<TokenMetadata>> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const list = await listTokens(ctx.store.authTokens, {\n includeRevoked: options.includeRevoked === true,\n });\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no tokens registered.'));\n return;\n }\n print(brand(`${list.length} token(s):`));\n for (const t of list) {\n const status = t.revokedAt !== undefined ? statusMarker('warn') : statusMarker('ok');\n print(` ${status} ${t.id} (label=${t.label ?? '-'}, scopes=${t.scopes.join(',')})`);\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRevokeOptions extends TokenCommonOptions {\n readonly id: string;\n}\n\n/**\n * Revoke a single token.\n *\n * @stable\n */\nexport async function runTokenRevoke(\n options: TokenRevokeOptions,\n): Promise<TokenMetadata | undefined> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const result = await revokeToken(ctx.store.authTokens, options.id);\n emitReport(options, result ?? null, () => {\n const print = options.print ?? defaultPrintSink;\n if (result === undefined) {\n print(brand(`token '${options.id}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`token '${result.id}' revoked at ${result.revokedAt ?? '<now>'}`));\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRotateOptions extends TokenCommonOptions {\n readonly id: string;\n readonly env?: 'live' | 'test';\n}\n\n/**\n * Revoke an existing token and immediately mint a fresh one with the\n * same scopes. Returns the new raw token bytes once.\n *\n * @stable\n */\nexport async function runTokenRotate(options: TokenRotateOptions): Promise<TokenCreateResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error('[graphorin/cli] internal: pepper missing.');\n }\n const env = options.env ?? 'live';\n const result = await rotateToken({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n id: options.id,\n env,\n });\n const raw = await result.next.raw.use((s) => String(s));\n const out: TokenCreateResult = Object.freeze({\n id: result.next.record.id,\n ...(result.next.record.label !== undefined ? { label: result.next.record.label } : {}),\n scopes: Object.freeze([...result.next.record.scopes]),\n raw,\n ...(result.next.record.expiresAt !== undefined\n ? { expiresAt: result.next.record.expiresAt }\n : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`token '${result.old.id}' revoked + replaced by '${out.id}'`));\n print(brand('raw token (shown ONCE):'));\n print(` ${raw}`);\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRekeyOptions extends TokenCommonOptions {\n readonly env?: 'live' | 'test';\n}\n\n/**\n * Re-issue every active token. Used after a known compromise.\n *\n * @stable\n */\nexport async function runTokenRekey(\n options: TokenRekeyOptions = {},\n): Promise<\n ReadonlyArray<{ readonly oldId: string; readonly newId: string; readonly raw: string }>\n> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error('[graphorin/cli] internal: pepper missing.');\n }\n const result = await rekeyTokens({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n env: options.env ?? 'live',\n });\n const out: Array<{ readonly oldId: string; readonly newId: string; readonly raw: string }> = [];\n for (const [oldId, created] of result) {\n const raw = await created.raw.use((s) => String(s));\n out.push(Object.freeze({ oldId, newId: created.record.id, raw }));\n }\n const frozen = Object.freeze(out);\n emitReport(options, frozen, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`rekeyed ${frozen.length} token(s):`));\n for (const row of frozen) {\n print(` - old=${row.oldId} new=${row.newId}`);\n print(` raw: ${row.raw}`);\n }\n });\n return frozen;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenVerifyOptions extends CommonOutputOptions {\n readonly token: string;\n /** Optional override of the expected token prefix. */\n readonly prefix?: string;\n}\n\n/** @stable */\nexport interface TokenVerifyResult {\n readonly ok: boolean;\n readonly reason?: string;\n}\n\n/**\n * Offline checksum verification. Confirms the structural shape, the\n * environment marker, and the CRC checksum but does NOT consult the\n * token store — it only proves the token was minted by a Graphorin\n * helper.\n *\n * @stable\n */\nexport function runTokenVerify(options: TokenVerifyOptions): TokenVerifyResult {\n const result = verifyOffline(options.token, {\n ...(options.prefix !== undefined ? { acceptPrefix: options.prefix } : {}),\n });\n const out: TokenVerifyResult = Object.freeze({\n ok: result.ok,\n ...(result.ok ? {} : { reason: result.reason }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.ok) {\n print(brand(`token format ${statusMarker('ok')} (offline checksum verified)`));\n return;\n }\n print(brand(`token format ${statusMarker('fail')} (${out.reason ?? 'unknown reason'})`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n}\n\n/**\n * Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns\n * milliseconds. Throws on invalid input — surfaced as a fail-fast at\n * the CLI boundary.\n *\n * @internal\n */\nexport function parseDuration(input: string): number {\n const match = /^(\\d+)\\s*([smhd])$/i.exec(input.trim());\n if (!match) {\n throw new Error(\n `[graphorin/cli] invalid --expires-in '${input}'. Use the form '<number><s|m|h|d>' (e.g. 30d).`,\n );\n }\n const value = Number.parseInt(match[1] as string, 10);\n const unit = (match[2] as string).toLowerCase();\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60_000;\n case 'h':\n return value * 60 * 60_000;\n case 'd':\n return value * ONE_DAY_MS;\n default:\n throw new Error(`[graphorin/cli] invalid --expires-in unit '${unit}'.`);\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"token.js","names":["out: TokenCreateResult","out: Array<{ readonly oldId: string; readonly newId: string; readonly raw: string }>","out: TokenVerifyResult"],"sources":["../../src/commands/token.ts"],"sourcesContent":["/**\n * `graphorin token` - manage server auth tokens.\n *\n * The subcommand thin-wraps the library functions in\n * `@graphorin/security/auth` (`createToken`, `listTokens`,\n * `revokeToken`, `rotateToken`, `rekeyTokens`, `verifyOffline`). Per\n * Phase 15 § Tokens the surface ships:\n *\n * - `graphorin token create --label <name> --scopes <list> [--expires-in <duration>]`\n * - `graphorin token list`\n * - `graphorin token revoke <id>`\n * - `graphorin token rotate <id>`\n * - `graphorin token rekey`\n * - `graphorin token verify <token>`\n *\n * The raw token bytes are shown to the operator at most once - at the\n * call site of `create` / `rotate` / `rekey`. They are wrapped in a\n * {@link SecretValue} on the way back to keep accidental logging out\n * of the codepath; the CLI prints them via `value.use((s) => ...)` so\n * the bytes never live as a plain string variable longer than needed.\n *\n * @packageDocumentation\n */\n\nimport {\n createToken,\n listTokens,\n rekeyTokens,\n revokeToken,\n rotateToken,\n type TokenMetadata,\n verifyOffline,\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';\nimport { openStoreContext } from '../internal/store-context.js';\n\nconst ONE_DAY_MS = 24 * 60 * 60 * 1000;\n\n/** @stable */\nexport interface TokenCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface TokenCreateOptions extends TokenCommonOptions {\n readonly label?: string;\n /** Comma-separated scope list. */\n readonly scopes: ReadonlyArray<string>;\n /** Duration string: `30d`, `12h`, `90m`, `45s`. */\n readonly expiresIn?: string;\n readonly env?: 'live' | 'test';\n}\n\n/** @stable */\nexport interface TokenCreateResult {\n readonly id: string;\n readonly label?: string;\n readonly scopes: ReadonlyArray<string>;\n readonly raw: string;\n readonly expiresAt?: string;\n}\n\n/**\n * Create a token. Returns the raw value once + the persisted record.\n *\n * @stable\n */\nexport async function runTokenCreate(options: TokenCreateOptions): Promise<TokenCreateResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error(\n '[graphorin/cli] internal: pepper resolution should have populated ctx.pepper',\n );\n }\n const env = options.env ?? 'live';\n const expiresInMs =\n options.expiresIn !== undefined ? parseDuration(options.expiresIn) : undefined;\n const created = await createToken({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n env,\n scopes: options.scopes,\n ...(options.label !== undefined ? { label: options.label } : {}),\n ...(expiresInMs !== undefined ? { expiresInMs } : {}),\n });\n const raw = await created.raw.use((s) => String(s));\n const out: TokenCreateResult = Object.freeze({\n id: created.record.id,\n ...(created.record.label !== undefined ? { label: created.record.label } : {}),\n scopes: Object.freeze([...created.record.scopes]),\n raw,\n ...(created.record.expiresAt !== undefined ? { expiresAt: created.record.expiresAt } : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`token created (id=${out.id}, scopes=${out.scopes.join(',')})`));\n print(brand(`raw token (shown ONCE):`));\n print(` ${out.raw}`);\n if (out.expiresAt !== undefined) {\n print(brand(`expires at: ${out.expiresAt}`));\n }\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenListOptions extends TokenCommonOptions {\n readonly includeRevoked?: boolean;\n}\n\n/**\n * List token metadata.\n *\n * @stable\n */\nexport async function runTokenList(\n options: TokenListOptions = {},\n): Promise<ReadonlyArray<TokenMetadata>> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const list = await listTokens(ctx.store.authTokens, {\n includeRevoked: options.includeRevoked === true,\n });\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no tokens registered.'));\n return;\n }\n print(brand(`${list.length} token(s):`));\n for (const t of list) {\n const status = t.revokedAt !== undefined ? statusMarker('warn') : statusMarker('ok');\n print(` ${status} ${t.id} (label=${t.label ?? '-'}, scopes=${t.scopes.join(',')})`);\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRevokeOptions extends TokenCommonOptions {\n readonly id: string;\n}\n\n/**\n * Revoke a single token.\n *\n * @stable\n */\nexport async function runTokenRevoke(\n options: TokenRevokeOptions,\n): Promise<TokenMetadata | undefined> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const result = await revokeToken(ctx.store.authTokens, options.id);\n emitReport(options, result ?? null, () => {\n const print = options.print ?? defaultPrintSink;\n if (result === undefined) {\n print(brand(`token '${options.id}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`token '${result.id}' revoked at ${result.revokedAt ?? '<now>'}`));\n });\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRotateOptions extends TokenCommonOptions {\n readonly id: string;\n readonly env?: 'live' | 'test';\n}\n\n/**\n * Revoke an existing token and immediately mint a fresh one with the\n * same scopes. Returns the new raw token bytes once.\n *\n * @stable\n */\nexport async function runTokenRotate(options: TokenRotateOptions): Promise<TokenCreateResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error('[graphorin/cli] internal: pepper missing.');\n }\n const env = options.env ?? 'live';\n const result = await rotateToken({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n id: options.id,\n env,\n });\n const raw = await result.next.raw.use((s) => String(s));\n const out: TokenCreateResult = Object.freeze({\n id: result.next.record.id,\n ...(result.next.record.label !== undefined ? { label: result.next.record.label } : {}),\n scopes: Object.freeze([...result.next.record.scopes]),\n raw,\n ...(result.next.record.expiresAt !== undefined\n ? { expiresAt: result.next.record.expiresAt }\n : {}),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`token '${result.old.id}' revoked + replaced by '${out.id}'`));\n print(brand('raw token (shown ONCE):'));\n print(` ${raw}`);\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenRekeyOptions extends TokenCommonOptions {\n readonly env?: 'live' | 'test';\n}\n\n/**\n * Re-issue every active token. Used after a known compromise.\n *\n * @stable\n */\nexport async function runTokenRekey(\n options: TokenRekeyOptions = {},\n): Promise<\n ReadonlyArray<{ readonly oldId: string; readonly newId: string; readonly raw: string }>\n> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n requirePepper: true,\n });\n try {\n if (ctx.pepper === undefined) {\n throw new Error('[graphorin/cli] internal: pepper missing.');\n }\n const result = await rekeyTokens({\n tokenStore: ctx.store.authTokens,\n pepper: ctx.pepper,\n env: options.env ?? 'live',\n });\n const out: Array<{ readonly oldId: string; readonly newId: string; readonly raw: string }> = [];\n for (const [oldId, created] of result) {\n const raw = await created.raw.use((s) => String(s));\n out.push(Object.freeze({ oldId, newId: created.record.id, raw }));\n }\n const frozen = Object.freeze(out);\n emitReport(options, frozen, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`rekeyed ${frozen.length} token(s):`));\n for (const row of frozen) {\n print(` - old=${row.oldId} new=${row.newId}`);\n print(` raw: ${row.raw}`);\n }\n });\n return frozen;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TokenVerifyOptions extends CommonOutputOptions {\n readonly token: string;\n /** Optional override of the expected token prefix. */\n readonly prefix?: string;\n}\n\n/** @stable */\nexport interface TokenVerifyResult {\n readonly ok: boolean;\n readonly reason?: string;\n}\n\n/**\n * Offline checksum verification. Confirms the structural shape, the\n * environment marker, and the CRC checksum but does NOT consult the\n * token store - it only proves the token was minted by a Graphorin\n * helper.\n *\n * @stable\n */\nexport function runTokenVerify(options: TokenVerifyOptions): TokenVerifyResult {\n const result = verifyOffline(options.token, {\n ...(options.prefix !== undefined ? { acceptPrefix: options.prefix } : {}),\n });\n const out: TokenVerifyResult = Object.freeze({\n ok: result.ok,\n ...(result.ok ? {} : { reason: result.reason }),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.ok) {\n print(brand(`token format ${statusMarker('ok')} (offline checksum verified)`));\n return;\n }\n print(brand(`token format ${statusMarker('fail')} (${out.reason ?? 'unknown reason'})`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n });\n return out;\n}\n\n/**\n * Tiny duration parser. Accepts `Ns`, `Nm`, `Nh`, `Nd`. Returns\n * milliseconds. Throws on invalid input - surfaced as a fail-fast at\n * the CLI boundary.\n *\n * @internal\n */\nexport function parseDuration(input: string): number {\n const match = /^(\\d+)\\s*([smhd])$/i.exec(input.trim());\n if (!match) {\n throw new Error(\n `[graphorin/cli] invalid --expires-in '${input}'. Use the form '<number><s|m|h|d>' (e.g. 30d).`,\n );\n }\n const value = Number.parseInt(match[1] as string, 10);\n const unit = (match[2] as string).toLowerCase();\n switch (unit) {\n case 's':\n return value * 1000;\n case 'm':\n return value * 60_000;\n case 'h':\n return value * 60 * 60_000;\n case 'd':\n return value * ONE_DAY_MS;\n default:\n throw new Error(`[graphorin/cli] invalid --expires-in unit '${unit}'.`);\n }\n}\n"],"mappings":"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"}
@@ -34,7 +34,7 @@ interface ToolsLintOptions extends CommonOutputOptions {
34
34
  /** Override `cwd`. Default `process.cwd()`. */
35
35
  readonly cwd?: string;
36
36
  /**
37
- * Test seam supply a list of `(file, source)` pairs directly so
37
+ * Test seam - supply a list of `(file, source)` pairs directly so
38
38
  * the test does not need to fish around the filesystem.
39
39
  */
40
40
  readonly inlineSources?: ReadonlyArray<{
@@ -7,7 +7,7 @@ import { discoverToolCallsInSource, gradeTool, runToolRules } from "@graphorin/e
7
7
 
8
8
  //#region src/commands/tools-lint.ts
9
9
  /**
10
- * `graphorin tools lint` RB-49 / suggested DEC-165.
10
+ * `graphorin tools lint` - RB-49 / suggested DEC-165.
11
11
  *
12
12
  * Discovers every `tool({...})` registration in the operator's project
13
13
  * via static AST analysis (no runtime probe; no `tsc` invocation), runs
@@ -21,7 +21,7 @@ import { discoverToolCallsInSource, gradeTool, runToolRules } from "@graphorin/e
21
21
  * graphorin tools lint [--config <path>] [--threshold <n>]
22
22
  * [--format <text|json>] [--source <pattern>]
23
23
  *
24
- * ## Grader rubric (RB-49 calibration 40 + 30 + 30 = 100 points)
24
+ * ## Grader rubric (RB-49 calibration - 40 + 30 + 30 = 100 points)
25
25
  *
26
26
  * - **description axis (0..40):** 0 if missing / placeholder / shorter
27
27
  * than 20 chars; 16 if length >= 20; 24 if length >= 30; 32 if
@@ -34,12 +34,12 @@ import { discoverToolCallsInSource, gradeTool, runToolRules } from "@graphorin/e
34
34
  *
35
35
  * ## Exit codes
36
36
  *
37
- * - `0` every tool meets or exceeds the threshold.
38
- * - `1` at least one tool falls below the threshold.
39
- * - `2` invocation could not start (config missing, walker failed).
37
+ * - `0` - every tool meets or exceeds the threshold.
38
+ * - `1` - at least one tool falls below the threshold.
39
+ * - `2` - invocation could not start (config missing, walker failed).
40
40
  *
41
41
  * The CLI re-uses the rule modules from `@graphorin/eslint-plugin` so
42
- * the rule logic has a single source of truth the per-tool grader,
42
+ * the rule logic has a single source of truth - the per-tool grader,
43
43
  * threshold gate, and report formatting are the only logic that lives
44
44
  * in the CLI.
45
45
  *
@@ -1 +1 @@
1
- {"version":3,"file":"tools-lint.js","names":["tools: DiscoveredTool[]","scored: ToolGraderScore[]","violations: ToolsLintThresholdViolation[]","report: ToolsLintReport","out: Array<{ file: string; source: string }>","raw: string","parsed: { readonly include?: ReadonlyArray<string> }","out: string[]","s: import('node:fs').Stats","queue: string[]","entries: import('node:fs').Dirent[]"],"sources":["../../src/commands/tools-lint.ts"],"sourcesContent":["/**\n * `graphorin tools lint` — RB-49 / suggested DEC-165.\n *\n * Discovers every `tool({...})` registration in the operator's project\n * via static AST analysis (no runtime probe; no `tsc` invocation), runs\n * the three RB-49 rules from `@graphorin/eslint-plugin`, computes the\n * per-tool grader score (40 + 30 + 30 = 100 points), aggregates a\n * structured report, and exits `1` when any tool falls below the\n * `--threshold` (default `60`).\n *\n * ## Surface\n *\n * graphorin tools lint [--config <path>] [--threshold <n>]\n * [--format <text|json>] [--source <pattern>]\n *\n * ## Grader rubric (RB-49 calibration — 40 + 30 + 30 = 100 points)\n *\n * - **description axis (0..40):** 0 if missing / placeholder / shorter\n * than 20 chars; 16 if length >= 20; 24 if length >= 30; 32 if\n * length >= 50; 40 if length >= 80.\n * - **examples axis (0..30):** 0 if no examples or > 5; 12 base for\n * the first example, +6 per additional, cap at 30. Subtract 6 per\n * PII finding (cap at 0).\n * - **parameter naming axis (0..30):** 30 base, deducted per finding.\n * -30/N for ambiguous names; -10/N for numeric suffixes.\n *\n * ## Exit codes\n *\n * - `0` — every tool meets or exceeds the threshold.\n * - `1` — at least one tool falls below the threshold.\n * - `2` — invocation could not start (config missing, walker failed).\n *\n * The CLI re-uses the rule modules from `@graphorin/eslint-plugin` so\n * the rule logic has a single source of truth — the per-tool grader,\n * threshold gate, and report formatting are the only logic that lives\n * in the CLI.\n *\n * @packageDocumentation\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport process from 'node:process';\n\nimport {\n type DiscoveredTool,\n discoverToolCallsInSource,\n gradeTool,\n type LintFinding,\n type LintFindingKind,\n runToolRules,\n type ToolGraderScore,\n} from '@graphorin/eslint-plugin';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultJsonSink,\n defaultPrintSink,\n statusMarker,\n} from '../internal/output.js';\n\n/**\n * Counter event emitted per below-threshold tool per invocation. The\n * CLI exposes a configurable sink so observability pipelines (Phase\n * 04) can wire the counter into Prometheus / OTLP without touching\n * the CLI runtime. Default: no-op.\n *\n * Mirrors the `tool.lint.threshold.violations.total{toolName,score,\n * threshold}` contract documented in RB-49 § Counter emission.\n *\n * @stable\n */\nexport interface ToolsLintThresholdViolation {\n readonly toolName: string;\n readonly score: number;\n readonly threshold: number;\n}\n\n/** @stable */\nexport type ToolsLintCounterSink = (event: ToolsLintThresholdViolation) => void;\n\n/** @stable */\nexport interface ToolsLintOptions extends CommonOutputOptions {\n /** Optional path to a `tsconfig.json` whose `include` overrides the file glob. */\n readonly config?: string;\n /** Minimum acceptable per-tool score. Default `60`. */\n readonly threshold?: number;\n /** Output format. Default `'text'`. */\n readonly format?: 'text' | 'json';\n /** Optional override of the file glob pattern. */\n readonly source?: string;\n /** Override `cwd`. Default `process.cwd()`. */\n readonly cwd?: string;\n /**\n * Test seam — supply a list of `(file, source)` pairs directly so\n * the test does not need to fish around the filesystem.\n */\n readonly inlineSources?: ReadonlyArray<{ readonly file: string; readonly source: string }>;\n /**\n * Optional sink for the `tool.lint.threshold.violations.total`\n * counter (RB-49). The CLI calls this once per below-threshold tool\n * per invocation. Default: no-op.\n */\n readonly counterSink?: ToolsLintCounterSink;\n}\n\n/**\n * Documented JSON shape emitted on `--format json`. The shape is\n * stable + forward-compatible with the `@eslint/mcp` industry\n * direction (per-finding `rule` / `severity` / `message` / `location`\n * mirror the ESLint LSP convention).\n *\n * @stable\n */\nexport interface ToolsLintReport {\n readonly summary: {\n readonly totalTools: number;\n readonly totalFindings: number;\n readonly threshold: number;\n readonly passed: number;\n readonly failed: number;\n };\n readonly tools: ReadonlyArray<ToolsLintReportTool>;\n}\n\n/** @stable */\nexport interface ToolsLintReportTool {\n readonly name: string;\n readonly source: string;\n readonly score: number;\n readonly axes: {\n readonly description: number;\n readonly examples: number;\n readonly parameterNaming: number;\n };\n readonly findings: ReadonlyArray<ToolsLintReportFinding>;\n}\n\n/** @stable */\nexport interface ToolsLintReportFinding {\n readonly rule: LintFinding['rule'];\n readonly kind: LintFindingKind;\n readonly severity: LintFinding['severity'];\n readonly message: string;\n readonly location: { readonly file: string; readonly line: number };\n readonly hint?: string;\n readonly matchedPattern?: string;\n}\n\n/** Default file glob honoured when no `--source` / `--config` is supplied. */\nconst DEFAULT_GLOB = 'src/**/*.{ts,tsx}';\n\n/**\n * Run the discovery + grader pipeline. Returns the structured report\n * the CLI emits to stdout.\n *\n * @stable\n */\nexport async function runToolsLint(options: ToolsLintOptions = {}): Promise<ToolsLintReport> {\n const cwd = options.cwd ?? process.cwd();\n const threshold = options.threshold ?? 60;\n const format = options.format ?? 'text';\n\n const sources = await collectSources(cwd, options);\n const tools: DiscoveredTool[] = [];\n for (const { file, source } of sources) {\n tools.push(...discoverToolCallsInSource(file, source));\n }\n\n const scored: ToolGraderScore[] = [];\n let failed = 0;\n let passed = 0;\n let totalFindings = 0;\n const violations: ToolsLintThresholdViolation[] = [];\n for (const tool of tools) {\n const findings = runToolRules(tool);\n const score = gradeTool(tool, findings);\n scored.push(score);\n totalFindings += findings.length;\n if (score.score < threshold) {\n failed += 1;\n violations.push(\n Object.freeze({\n toolName: score.toolName,\n score: score.score,\n threshold,\n }),\n );\n } else {\n passed += 1;\n }\n }\n\n // Emit the documented counter exactly once per below-threshold tool\n // per invocation. The default sink is a no-op so non-observability\n // hosts pay nothing; the standalone server's tracer wires its own\n // counter pipeline through `--counter-sink` in v0.2.\n const counterSink = options.counterSink ?? noopCounterSink;\n for (const v of violations) counterSink(v);\n\n const report: ToolsLintReport = Object.freeze({\n summary: Object.freeze({\n totalTools: scored.length,\n totalFindings,\n threshold,\n passed,\n failed,\n }),\n tools: Object.freeze(scored.map(toReportTool)),\n });\n\n if (format === 'json') {\n const sink = options.jsonPrint ?? defaultJsonSink;\n sink(report);\n } else {\n emitTextReport(report, options);\n }\n\n if (failed > 0) {\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n }\n\n return report;\n}\n\nfunction noopCounterSink(_event: ToolsLintThresholdViolation): void {\n /* no-op */\n}\n\nfunction toReportTool(score: ToolGraderScore): ToolsLintReportTool {\n return Object.freeze({\n name: score.toolName,\n source: `${score.file}:${score.line}`,\n score: score.score,\n axes: score.axes,\n findings: Object.freeze(score.findings.map(toReportFinding)),\n });\n}\n\nfunction toReportFinding(finding: LintFinding): ToolsLintReportFinding {\n return Object.freeze({\n rule: finding.rule,\n kind: finding.kind,\n severity: finding.severity,\n message: finding.message,\n location: Object.freeze({ file: finding.file, line: finding.line }),\n ...(finding.hint !== undefined ? { hint: finding.hint } : {}),\n ...(finding.matchedPattern !== undefined ? { matchedPattern: finding.matchedPattern } : {}),\n });\n}\n\nfunction emitTextReport(report: ToolsLintReport, options: ToolsLintOptions): void {\n const print = options.print ?? defaultPrintSink;\n if (report.tools.length === 0) {\n print(brand('no tool({...}) registrations were discovered.'));\n return;\n }\n for (const tool of report.tools) {\n const ok = tool.score >= report.summary.threshold;\n const mark = ok ? statusMarker('ok') : statusMarker('fail');\n const flag = ok ? '' : ' (BELOW THRESHOLD)';\n print(brand(`${mark} ${tool.name} (score=${tool.score}/100)${flag}`));\n print(` source: ${tool.source}`);\n print(\n ` axes: description=${tool.axes.description}, examples=${tool.axes.examples}, parameterNaming=${tool.axes.parameterNaming}`,\n );\n if (tool.findings.length === 0) {\n print(` findings: none`);\n } else {\n print(` findings:`);\n for (const f of tool.findings) {\n print(\n ` [${f.severity}] ${f.rule}: ${f.message} (${f.location.file}:${f.location.line})`,\n );\n }\n }\n }\n const tag = report.summary.failed === 0 ? statusMarker('ok') : statusMarker('fail');\n print(\n brand(\n `${tag} summary: ${report.summary.totalTools} tool(s), ${report.summary.totalFindings} finding(s), threshold=${report.summary.threshold}, passed=${report.summary.passed}, failed=${report.summary.failed}`,\n ),\n );\n}\n\nasync function collectSources(\n cwd: string,\n options: ToolsLintOptions,\n): Promise<ReadonlyArray<{ readonly file: string; readonly source: string }>> {\n if (options.inlineSources !== undefined) return options.inlineSources;\n\n const glob = options.source ?? (await loadIncludeGlob(cwd, options.config)) ?? DEFAULT_GLOB;\n const files = await walkGlob(cwd, glob);\n const out: Array<{ file: string; source: string }> = [];\n for (const file of files) {\n try {\n const source = await readFile(file, 'utf8');\n out.push({ file, source });\n } catch {\n // Best-effort — skip unreadable files.\n }\n }\n return out;\n}\n\nasync function loadIncludeGlob(\n cwd: string,\n configPath: string | undefined,\n): Promise<string | undefined> {\n if (configPath === undefined) return undefined;\n const abs = isAbsolute(configPath) ? configPath : resolve(cwd, configPath);\n let raw: string;\n try {\n raw = await readFile(abs, 'utf8');\n } catch {\n return undefined;\n }\n let parsed: { readonly include?: ReadonlyArray<string> };\n try {\n parsed = JSON.parse(stripJsonComments(raw)) as { readonly include?: ReadonlyArray<string> };\n } catch {\n return undefined;\n }\n return parsed.include?.[0];\n}\n\nfunction stripJsonComments(raw: string): string {\n // Lightweight block-comment + line-comment stripper. tsconfig.json\n // commonly carries `//` comments which JSON.parse rejects.\n return raw.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/(^|[^:])\\/\\/.*$/gm, '$1');\n}\n\nasync function walkGlob(cwd: string, pattern: string): Promise<string[]> {\n const root = resolveGlobRoot(cwd, pattern);\n const matcher = compileGlob(pattern);\n const out: string[] = [];\n await walkDir(root, async (file) => {\n const rel = relative(cwd, file);\n if (!matcher(rel)) return;\n out.push(file);\n });\n return out.sort();\n}\n\nfunction resolveGlobRoot(cwd: string, pattern: string): string {\n const idx = pattern.indexOf('*');\n if (idx < 0) return resolve(cwd, pattern);\n const prefix = pattern.slice(0, idx);\n return resolve(cwd, prefix);\n}\n\nfunction compileGlob(pattern: string): (rel: string) => boolean {\n // The translation is intentionally done in three phases so the\n // brace-expansion output (which includes literal `(?:...)`) does not\n // collide with the `?` glob translation:\n //\n // 1. Tokenize the four glob constructs into ASCII placeholders\n // that are guaranteed not to appear in any host project's\n // filename.\n // 2. Escape every regex meta-character on the remaining literal\n // text.\n // 3. Substitute the placeholders for the equivalent regex\n // fragments (with `(?:...)` for brace expansion).\n if (pattern.length > 4096) {\n throw new Error(`compileGlob: pattern is too long (${pattern.length} > 4096 chars).`);\n }\n const BRACE_OPEN = '\\u0001';\n const BRACE_CLOSE = '\\u0002';\n const BRACE_SEP = '\\u0003';\n const GLOBSTAR = '\\u0004';\n const STAR = '\\u0005';\n const QUESTION = '\\u0006';\n\n // Phase 1: tokenize.\n let tokenized = pattern.replace(\n /\\{([^}]+)\\}/g,\n (_m, group: string) => `${BRACE_OPEN}${group.split(',').join(BRACE_SEP)}${BRACE_CLOSE}`,\n );\n tokenized = tokenized.replace(/\\*\\*/g, GLOBSTAR).replace(/\\*/g, STAR).replace(/\\?/g, QUESTION);\n\n // Phase 2: escape every remaining regex meta-char on the literal\n // text. The placeholders are non-printable and never collide.\n const escaped = tokenized.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n\n // Phase 3: substitute the placeholders for the regex fragments.\n const translated = escaped\n .replace(new RegExp(BRACE_OPEN, 'g'), '(?:')\n .replace(new RegExp(BRACE_CLOSE, 'g'), ')')\n .replace(new RegExp(BRACE_SEP, 'g'), '|')\n .replace(new RegExp(GLOBSTAR, 'g'), '.*')\n .replace(new RegExp(STAR, 'g'), '[^/]*')\n .replace(new RegExp(QUESTION, 'g'), '[^/]');\n\n const re = new RegExp(`^${translated}$`);\n return (rel: string) => {\n const normalized = rel.split(sep).join('/');\n return re.test(normalized);\n };\n}\n\nasync function walkDir(root: string, visit: (file: string) => Promise<void>): Promise<void> {\n let s: import('node:fs').Stats;\n try {\n s = await stat(root);\n } catch {\n return;\n }\n if (!s.isDirectory()) {\n if (s.isFile()) await visit(root);\n return;\n }\n const queue: string[] = [root];\n while (queue.length > 0) {\n const dir = queue.shift() as string;\n let entries: import('node:fs').Dirent[];\n try {\n entries = await (await import('node:fs/promises')).readdir(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') continue;\n const next = `${dir}${sep}${entry.name}`;\n if (entry.isDirectory()) queue.push(next);\n else if (entry.isFile()) await visit(next);\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"tools-lint.js","names":["tools: DiscoveredTool[]","scored: ToolGraderScore[]","violations: ToolsLintThresholdViolation[]","report: ToolsLintReport","out: Array<{ file: string; source: string }>","raw: string","parsed: { readonly include?: ReadonlyArray<string> }","out: string[]","s: import('node:fs').Stats","queue: string[]","entries: import('node:fs').Dirent[]"],"sources":["../../src/commands/tools-lint.ts"],"sourcesContent":["/**\n * `graphorin tools lint` - RB-49 / suggested DEC-165.\n *\n * Discovers every `tool({...})` registration in the operator's project\n * via static AST analysis (no runtime probe; no `tsc` invocation), runs\n * the three RB-49 rules from `@graphorin/eslint-plugin`, computes the\n * per-tool grader score (40 + 30 + 30 = 100 points), aggregates a\n * structured report, and exits `1` when any tool falls below the\n * `--threshold` (default `60`).\n *\n * ## Surface\n *\n * graphorin tools lint [--config <path>] [--threshold <n>]\n * [--format <text|json>] [--source <pattern>]\n *\n * ## Grader rubric (RB-49 calibration - 40 + 30 + 30 = 100 points)\n *\n * - **description axis (0..40):** 0 if missing / placeholder / shorter\n * than 20 chars; 16 if length >= 20; 24 if length >= 30; 32 if\n * length >= 50; 40 if length >= 80.\n * - **examples axis (0..30):** 0 if no examples or > 5; 12 base for\n * the first example, +6 per additional, cap at 30. Subtract 6 per\n * PII finding (cap at 0).\n * - **parameter naming axis (0..30):** 30 base, deducted per finding.\n * -30/N for ambiguous names; -10/N for numeric suffixes.\n *\n * ## Exit codes\n *\n * - `0` - every tool meets or exceeds the threshold.\n * - `1` - at least one tool falls below the threshold.\n * - `2` - invocation could not start (config missing, walker failed).\n *\n * The CLI re-uses the rule modules from `@graphorin/eslint-plugin` so\n * the rule logic has a single source of truth - the per-tool grader,\n * threshold gate, and report formatting are the only logic that lives\n * in the CLI.\n *\n * @packageDocumentation\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\nimport process from 'node:process';\n\nimport {\n type DiscoveredTool,\n discoverToolCallsInSource,\n gradeTool,\n type LintFinding,\n type LintFindingKind,\n runToolRules,\n type ToolGraderScore,\n} from '@graphorin/eslint-plugin';\n\nimport { EXIT_CODES } from '../internal/exit.js';\nimport {\n brand,\n type CommonOutputOptions,\n defaultJsonSink,\n defaultPrintSink,\n statusMarker,\n} from '../internal/output.js';\n\n/**\n * Counter event emitted per below-threshold tool per invocation. The\n * CLI exposes a configurable sink so observability pipelines (Phase\n * 04) can wire the counter into Prometheus / OTLP without touching\n * the CLI runtime. Default: no-op.\n *\n * Mirrors the `tool.lint.threshold.violations.total{toolName,score,\n * threshold}` contract documented in RB-49 § Counter emission.\n *\n * @stable\n */\nexport interface ToolsLintThresholdViolation {\n readonly toolName: string;\n readonly score: number;\n readonly threshold: number;\n}\n\n/** @stable */\nexport type ToolsLintCounterSink = (event: ToolsLintThresholdViolation) => void;\n\n/** @stable */\nexport interface ToolsLintOptions extends CommonOutputOptions {\n /** Optional path to a `tsconfig.json` whose `include` overrides the file glob. */\n readonly config?: string;\n /** Minimum acceptable per-tool score. Default `60`. */\n readonly threshold?: number;\n /** Output format. Default `'text'`. */\n readonly format?: 'text' | 'json';\n /** Optional override of the file glob pattern. */\n readonly source?: string;\n /** Override `cwd`. Default `process.cwd()`. */\n readonly cwd?: string;\n /**\n * Test seam - supply a list of `(file, source)` pairs directly so\n * the test does not need to fish around the filesystem.\n */\n readonly inlineSources?: ReadonlyArray<{ readonly file: string; readonly source: string }>;\n /**\n * Optional sink for the `tool.lint.threshold.violations.total`\n * counter (RB-49). The CLI calls this once per below-threshold tool\n * per invocation. Default: no-op.\n */\n readonly counterSink?: ToolsLintCounterSink;\n}\n\n/**\n * Documented JSON shape emitted on `--format json`. The shape is\n * stable + forward-compatible with the `@eslint/mcp` industry\n * direction (per-finding `rule` / `severity` / `message` / `location`\n * mirror the ESLint LSP convention).\n *\n * @stable\n */\nexport interface ToolsLintReport {\n readonly summary: {\n readonly totalTools: number;\n readonly totalFindings: number;\n readonly threshold: number;\n readonly passed: number;\n readonly failed: number;\n };\n readonly tools: ReadonlyArray<ToolsLintReportTool>;\n}\n\n/** @stable */\nexport interface ToolsLintReportTool {\n readonly name: string;\n readonly source: string;\n readonly score: number;\n readonly axes: {\n readonly description: number;\n readonly examples: number;\n readonly parameterNaming: number;\n };\n readonly findings: ReadonlyArray<ToolsLintReportFinding>;\n}\n\n/** @stable */\nexport interface ToolsLintReportFinding {\n readonly rule: LintFinding['rule'];\n readonly kind: LintFindingKind;\n readonly severity: LintFinding['severity'];\n readonly message: string;\n readonly location: { readonly file: string; readonly line: number };\n readonly hint?: string;\n readonly matchedPattern?: string;\n}\n\n/** Default file glob honoured when no `--source` / `--config` is supplied. */\nconst DEFAULT_GLOB = 'src/**/*.{ts,tsx}';\n\n/**\n * Run the discovery + grader pipeline. Returns the structured report\n * the CLI emits to stdout.\n *\n * @stable\n */\nexport async function runToolsLint(options: ToolsLintOptions = {}): Promise<ToolsLintReport> {\n const cwd = options.cwd ?? process.cwd();\n const threshold = options.threshold ?? 60;\n const format = options.format ?? 'text';\n\n const sources = await collectSources(cwd, options);\n const tools: DiscoveredTool[] = [];\n for (const { file, source } of sources) {\n tools.push(...discoverToolCallsInSource(file, source));\n }\n\n const scored: ToolGraderScore[] = [];\n let failed = 0;\n let passed = 0;\n let totalFindings = 0;\n const violations: ToolsLintThresholdViolation[] = [];\n for (const tool of tools) {\n const findings = runToolRules(tool);\n const score = gradeTool(tool, findings);\n scored.push(score);\n totalFindings += findings.length;\n if (score.score < threshold) {\n failed += 1;\n violations.push(\n Object.freeze({\n toolName: score.toolName,\n score: score.score,\n threshold,\n }),\n );\n } else {\n passed += 1;\n }\n }\n\n // Emit the documented counter exactly once per below-threshold tool\n // per invocation. The default sink is a no-op so non-observability\n // hosts pay nothing; the standalone server's tracer wires its own\n // counter pipeline through `--counter-sink` in v0.2.\n const counterSink = options.counterSink ?? noopCounterSink;\n for (const v of violations) counterSink(v);\n\n const report: ToolsLintReport = Object.freeze({\n summary: Object.freeze({\n totalTools: scored.length,\n totalFindings,\n threshold,\n passed,\n failed,\n }),\n tools: Object.freeze(scored.map(toReportTool)),\n });\n\n if (format === 'json') {\n const sink = options.jsonPrint ?? defaultJsonSink;\n sink(report);\n } else {\n emitTextReport(report, options);\n }\n\n if (failed > 0) {\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n }\n\n return report;\n}\n\nfunction noopCounterSink(_event: ToolsLintThresholdViolation): void {\n /* no-op */\n}\n\nfunction toReportTool(score: ToolGraderScore): ToolsLintReportTool {\n return Object.freeze({\n name: score.toolName,\n source: `${score.file}:${score.line}`,\n score: score.score,\n axes: score.axes,\n findings: Object.freeze(score.findings.map(toReportFinding)),\n });\n}\n\nfunction toReportFinding(finding: LintFinding): ToolsLintReportFinding {\n return Object.freeze({\n rule: finding.rule,\n kind: finding.kind,\n severity: finding.severity,\n message: finding.message,\n location: Object.freeze({ file: finding.file, line: finding.line }),\n ...(finding.hint !== undefined ? { hint: finding.hint } : {}),\n ...(finding.matchedPattern !== undefined ? { matchedPattern: finding.matchedPattern } : {}),\n });\n}\n\nfunction emitTextReport(report: ToolsLintReport, options: ToolsLintOptions): void {\n const print = options.print ?? defaultPrintSink;\n if (report.tools.length === 0) {\n print(brand('no tool({...}) registrations were discovered.'));\n return;\n }\n for (const tool of report.tools) {\n const ok = tool.score >= report.summary.threshold;\n const mark = ok ? statusMarker('ok') : statusMarker('fail');\n const flag = ok ? '' : ' (BELOW THRESHOLD)';\n print(brand(`${mark} ${tool.name} (score=${tool.score}/100)${flag}`));\n print(` source: ${tool.source}`);\n print(\n ` axes: description=${tool.axes.description}, examples=${tool.axes.examples}, parameterNaming=${tool.axes.parameterNaming}`,\n );\n if (tool.findings.length === 0) {\n print(` findings: none`);\n } else {\n print(` findings:`);\n for (const f of tool.findings) {\n print(\n ` [${f.severity}] ${f.rule}: ${f.message} (${f.location.file}:${f.location.line})`,\n );\n }\n }\n }\n const tag = report.summary.failed === 0 ? statusMarker('ok') : statusMarker('fail');\n print(\n brand(\n `${tag} summary: ${report.summary.totalTools} tool(s), ${report.summary.totalFindings} finding(s), threshold=${report.summary.threshold}, passed=${report.summary.passed}, failed=${report.summary.failed}`,\n ),\n );\n}\n\nasync function collectSources(\n cwd: string,\n options: ToolsLintOptions,\n): Promise<ReadonlyArray<{ readonly file: string; readonly source: string }>> {\n if (options.inlineSources !== undefined) return options.inlineSources;\n\n const glob = options.source ?? (await loadIncludeGlob(cwd, options.config)) ?? DEFAULT_GLOB;\n const files = await walkGlob(cwd, glob);\n const out: Array<{ file: string; source: string }> = [];\n for (const file of files) {\n try {\n const source = await readFile(file, 'utf8');\n out.push({ file, source });\n } catch {\n // Best-effort - skip unreadable files.\n }\n }\n return out;\n}\n\nasync function loadIncludeGlob(\n cwd: string,\n configPath: string | undefined,\n): Promise<string | undefined> {\n if (configPath === undefined) return undefined;\n const abs = isAbsolute(configPath) ? configPath : resolve(cwd, configPath);\n let raw: string;\n try {\n raw = await readFile(abs, 'utf8');\n } catch {\n return undefined;\n }\n let parsed: { readonly include?: ReadonlyArray<string> };\n try {\n parsed = JSON.parse(stripJsonComments(raw)) as { readonly include?: ReadonlyArray<string> };\n } catch {\n return undefined;\n }\n return parsed.include?.[0];\n}\n\nfunction stripJsonComments(raw: string): string {\n // Lightweight block-comment + line-comment stripper. tsconfig.json\n // commonly carries `//` comments which JSON.parse rejects.\n return raw.replace(/\\/\\*[\\s\\S]*?\\*\\//g, '').replace(/(^|[^:])\\/\\/.*$/gm, '$1');\n}\n\nasync function walkGlob(cwd: string, pattern: string): Promise<string[]> {\n const root = resolveGlobRoot(cwd, pattern);\n const matcher = compileGlob(pattern);\n const out: string[] = [];\n await walkDir(root, async (file) => {\n const rel = relative(cwd, file);\n if (!matcher(rel)) return;\n out.push(file);\n });\n return out.sort();\n}\n\nfunction resolveGlobRoot(cwd: string, pattern: string): string {\n const idx = pattern.indexOf('*');\n if (idx < 0) return resolve(cwd, pattern);\n const prefix = pattern.slice(0, idx);\n return resolve(cwd, prefix);\n}\n\nfunction compileGlob(pattern: string): (rel: string) => boolean {\n // The translation is intentionally done in three phases so the\n // brace-expansion output (which includes literal `(?:...)`) does not\n // collide with the `?` glob translation:\n //\n // 1. Tokenize the four glob constructs into ASCII placeholders\n // that are guaranteed not to appear in any host project's\n // filename.\n // 2. Escape every regex meta-character on the remaining literal\n // text.\n // 3. Substitute the placeholders for the equivalent regex\n // fragments (with `(?:...)` for brace expansion).\n if (pattern.length > 4096) {\n throw new Error(`compileGlob: pattern is too long (${pattern.length} > 4096 chars).`);\n }\n const BRACE_OPEN = '\\u0001';\n const BRACE_CLOSE = '\\u0002';\n const BRACE_SEP = '\\u0003';\n const GLOBSTAR = '\\u0004';\n const STAR = '\\u0005';\n const QUESTION = '\\u0006';\n\n // Phase 1: tokenize.\n let tokenized = pattern.replace(\n /\\{([^}]+)\\}/g,\n (_m, group: string) => `${BRACE_OPEN}${group.split(',').join(BRACE_SEP)}${BRACE_CLOSE}`,\n );\n tokenized = tokenized.replace(/\\*\\*/g, GLOBSTAR).replace(/\\*/g, STAR).replace(/\\?/g, QUESTION);\n\n // Phase 2: escape every remaining regex meta-char on the literal\n // text. The placeholders are non-printable and never collide.\n const escaped = tokenized.replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&');\n\n // Phase 3: substitute the placeholders for the regex fragments.\n const translated = escaped\n .replace(new RegExp(BRACE_OPEN, 'g'), '(?:')\n .replace(new RegExp(BRACE_CLOSE, 'g'), ')')\n .replace(new RegExp(BRACE_SEP, 'g'), '|')\n .replace(new RegExp(GLOBSTAR, 'g'), '.*')\n .replace(new RegExp(STAR, 'g'), '[^/]*')\n .replace(new RegExp(QUESTION, 'g'), '[^/]');\n\n const re = new RegExp(`^${translated}$`);\n return (rel: string) => {\n const normalized = rel.split(sep).join('/');\n return re.test(normalized);\n };\n}\n\nasync function walkDir(root: string, visit: (file: string) => Promise<void>): Promise<void> {\n let s: import('node:fs').Stats;\n try {\n s = await stat(root);\n } catch {\n return;\n }\n if (!s.isDirectory()) {\n if (s.isFile()) await visit(root);\n return;\n }\n const queue: string[] = [root];\n while (queue.length > 0) {\n const dir = queue.shift() as string;\n let entries: import('node:fs').Dirent[];\n try {\n entries = await (await import('node:fs/promises')).readdir(dir, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n if (entry.name === 'node_modules' || entry.name === 'dist' || entry.name === '.git') continue;\n const next = `${dir}${sep}${entry.name}`;\n if (entry.isDirectory()) queue.push(next);\n else if (entry.isFile()) await visit(next);\n }\n }\n}\n"],"mappings":"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"}
@@ -3,16 +3,16 @@ import { openStoreContext } from "../internal/store-context.js";
3
3
 
4
4
  //#region src/commands/traces.ts
5
5
  /**
6
- * `graphorin traces` operate on the local trace cache.
6
+ * `graphorin traces` - operate on the local trace cache.
7
7
  *
8
8
  * Surface (per Phase 15 § Traces):
9
9
  *
10
- * - `graphorin traces status` count rows + report TTL config.
11
- * - `graphorin traces prune [--before <date>]` manual TTL
10
+ * - `graphorin traces status` - count rows + report TTL config.
11
+ * - `graphorin traces prune [--before <date>]` - manual TTL
12
12
  * enforcement.
13
13
  *
14
14
  * The trace cache lives in the SQLite store under the `traces` table
15
- * (registered in Phase 04). This command is read + delete only the
15
+ * (registered in Phase 04). This command is read + delete only - the
16
16
  * trace producer is the running `@graphorin/observability` exporter.
17
17
  *
18
18
  * @packageDocumentation
@@ -40,7 +40,7 @@ async function runTracesStatus(options = {}) {
40
40
  emitReport(options, out, () => {
41
41
  const print = options.print ?? defaultPrintSink;
42
42
  if (!out.tableExists) {
43
- print(brand("traces table not found the observability exporter has not initialized it yet."));
43
+ print(brand("traces table not found - the observability exporter has not initialized it yet."));
44
44
  return;
45
45
  }
46
46
  print(brand(`traces: ${out.rows} row(s) (oldest=${out.oldestStartedAt ?? "-"}, newest=${out.newestStartedAt ?? "-"})`));
@@ -1 +1 @@
1
- {"version":3,"file":"traces.js","names":["out: TracesStatusResult","out: TracesPruneResult","out"],"sources":["../../src/commands/traces.ts"],"sourcesContent":["/**\n * `graphorin traces` operate on the local trace cache.\n *\n * Surface (per Phase 15 § Traces):\n *\n * - `graphorin traces status` count rows + report TTL config.\n * - `graphorin traces prune [--before <date>]` manual TTL\n * enforcement.\n *\n * The trace cache lives in the SQLite store under the `traces` table\n * (registered in Phase 04). This command is read + delete only the\n * trace producer is the running `@graphorin/observability` exporter.\n *\n * @packageDocumentation\n */\n\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\n/** @stable */\nexport interface TracesCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface TracesStatusResult {\n readonly tableExists: boolean;\n readonly rows: number;\n readonly oldestStartedAt?: string;\n readonly newestStartedAt?: string;\n}\n\n/** @stable */\nexport async function runTracesStatus(\n options: TracesCommonOptions = {},\n): Promise<TracesStatusResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const conn = ctx.store.connection;\n const exists = tableExists(conn, 'traces');\n let out: TracesStatusResult;\n if (exists) {\n const oldest = pickIso(conn, 'SELECT MIN(started_at) AS started_at FROM traces');\n const newest = pickIso(conn, 'SELECT MAX(started_at) AS started_at FROM traces');\n out = Object.freeze({\n tableExists: true,\n rows: numericCount(conn, 'SELECT COUNT(*) AS n FROM traces'),\n ...(oldest !== undefined ? { oldestStartedAt: oldest } : {}),\n ...(newest !== undefined ? { newestStartedAt: newest } : {}),\n });\n } else {\n out = Object.freeze({ tableExists: false, rows: 0 });\n }\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (!out.tableExists) {\n print(\n brand('traces table not found the observability exporter has not initialized it yet.'),\n );\n return;\n }\n print(\n brand(\n `traces: ${out.rows} row(s) (oldest=${out.oldestStartedAt ?? '-'}, newest=${out.newestStartedAt ?? '-'})`,\n ),\n );\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TracesPruneOptions extends TracesCommonOptions {\n /** ISO date / epoch ms cutoff. Required so the helper never silently\n * empties the table. */\n readonly before: string;\n}\n\n/** @stable */\nexport interface TracesPruneResult {\n readonly removed: number;\n readonly cutoff: string;\n}\n\n/** @stable */\nexport async function runTracesPrune(options: TracesPruneOptions): Promise<TracesPruneResult> {\n const cutoffMs = parseCutoff(options.before);\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n if (!tableExists(ctx.store.connection, 'traces')) {\n const out: TracesPruneResult = Object.freeze({\n removed: 0,\n cutoff: new Date(cutoffMs).toISOString(),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`traces table not found; nothing to prune.`));\n });\n return out;\n }\n const result = ctx.store.connection.run('DELETE FROM traces WHERE started_at < ?', [cutoffMs]);\n const out: TracesPruneResult = Object.freeze({\n removed: result.changes,\n cutoff: new Date(cutoffMs).toISOString(),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n const mark = out.removed > 0 ? statusMarker('ok') : statusMarker('info');\n print(brand(`${mark} pruned ${out.removed} trace row(s) (cutoff=${out.cutoff}).`));\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\nfunction tableExists(\n conn: { get: <T = unknown>(s: string, p?: ReadonlyArray<unknown>) => T | undefined },\n name: string,\n): boolean {\n const row = conn.get<{ name: string }>(\n \"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?\",\n [name],\n );\n return row !== undefined;\n}\n\nfunction numericCount(\n conn: { get: <T = unknown>(s: string) => T | undefined },\n sql: string,\n): number {\n const row = conn.get<{ n: number }>(sql);\n return typeof row?.n === 'number' ? row.n : 0;\n}\n\nfunction pickIso(\n conn: { get: <T = unknown>(s: string) => T | undefined },\n sql: string,\n): string | undefined {\n const row = conn.get<{ started_at: number | null }>(sql);\n if (row === undefined || row.started_at === null) return undefined;\n return new Date(row.started_at).toISOString();\n}\n\nfunction parseCutoff(input: string): number {\n const numeric = Number(input);\n if (Number.isFinite(numeric) && numeric > 0) return numeric;\n const ms = Date.parse(input);\n if (!Number.isFinite(ms)) {\n throw new Error(\n `[graphorin/cli] --before '${input}' is not a valid ISO date or epoch-ms value.`,\n );\n }\n return ms;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,eAAsB,gBACpB,UAA+B,EAAE,EACJ;CAC7B,MAAM,MAAM,MAAM,iBAAiB,EACjC,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE,EACnE,CAAC;AACF,KAAI;EACF,MAAM,OAAO,IAAI,MAAM;EACvB,MAAM,SAAS,YAAY,MAAM,SAAS;EAC1C,IAAIA;AACJ,MAAI,QAAQ;GACV,MAAM,SAAS,QAAQ,MAAM,mDAAmD;GAChF,MAAM,SAAS,QAAQ,MAAM,mDAAmD;AAChF,SAAM,OAAO,OAAO;IAClB,aAAa;IACb,MAAM,aAAa,MAAM,mCAAmC;IAC5D,GAAI,WAAW,SAAY,EAAE,iBAAiB,QAAQ,GAAG,EAAE;IAC3D,GAAI,WAAW,SAAY,EAAE,iBAAiB,QAAQ,GAAG,EAAE;IAC5D,CAAC;QAEF,OAAM,OAAO,OAAO;GAAE,aAAa;GAAO,MAAM;GAAG,CAAC;AAEtD,aAAW,SAAS,WAAW;GAC7B,MAAM,QAAQ,QAAQ,SAAS;AAC/B,OAAI,CAAC,IAAI,aAAa;AACpB,UACE,MAAM,kFAAkF,CACzF;AACD;;AAEF,SACE,MACE,WAAW,IAAI,KAAK,kBAAkB,IAAI,mBAAmB,IAAI,WAAW,IAAI,mBAAmB,IAAI,GACxG,CACF;IACD;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;;AAkBrB,eAAsB,eAAe,SAAyD;CAC5F,MAAM,WAAW,YAAY,QAAQ,OAAO;CAC5C,MAAM,MAAM,MAAM,iBAAiB,EACjC,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE,EACnE,CAAC;AACF,KAAI;AACF,MAAI,CAAC,YAAY,IAAI,MAAM,YAAY,SAAS,EAAE;GAChD,MAAMC,QAAyB,OAAO,OAAO;IAC3C,SAAS;IACT,QAAQ,IAAI,KAAK,SAAS,CAAC,aAAa;IACzC,CAAC;AACF,cAAW,SAASC,aAAW;AAE7B,KADc,QAAQ,SAAS,kBACzB,MAAM,4CAA4C,CAAC;KACzD;AACF,UAAOA;;EAET,MAAM,SAAS,IAAI,MAAM,WAAW,IAAI,2CAA2C,CAAC,SAAS,CAAC;EAC9F,MAAMD,MAAyB,OAAO,OAAO;GAC3C,SAAS,OAAO;GAChB,QAAQ,IAAI,KAAK,SAAS,CAAC,aAAa;GACzC,CAAC;AACF,aAAW,SAAS,WAAW;AAG7B,IAFc,QAAQ,SAAS,kBAEzB,MAAM,GADC,IAAI,UAAU,IAAI,aAAa,KAAK,GAAG,aAAa,OAAO,CACpD,UAAU,IAAI,QAAQ,wBAAwB,IAAI,OAAO,IAAI,CAAC;IAClF;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;AAIrB,SAAS,YACP,MACA,MACS;AAKT,QAJY,KAAK,IACf,oEACA,CAAC,KAAK,CACP,KACc;;AAGjB,SAAS,aACP,MACA,KACQ;CACR,MAAM,MAAM,KAAK,IAAmB,IAAI;AACxC,QAAO,OAAO,KAAK,MAAM,WAAW,IAAI,IAAI;;AAG9C,SAAS,QACP,MACA,KACoB;CACpB,MAAM,MAAM,KAAK,IAAmC,IAAI;AACxD,KAAI,QAAQ,UAAa,IAAI,eAAe,KAAM,QAAO;AACzD,QAAO,IAAI,KAAK,IAAI,WAAW,CAAC,aAAa;;AAG/C,SAAS,YAAY,OAAuB;CAC1C,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,OAAO,SAAS,QAAQ,IAAI,UAAU,EAAG,QAAO;CACpD,MAAM,KAAK,KAAK,MAAM,MAAM;AAC5B,KAAI,CAAC,OAAO,SAAS,GAAG,CACtB,OAAM,IAAI,MACR,6BAA6B,MAAM,8CACpC;AAEH,QAAO"}
1
+ {"version":3,"file":"traces.js","names":["out: TracesStatusResult","out: TracesPruneResult","out"],"sources":["../../src/commands/traces.ts"],"sourcesContent":["/**\n * `graphorin traces` - operate on the local trace cache.\n *\n * Surface (per Phase 15 § Traces):\n *\n * - `graphorin traces status` - count rows + report TTL config.\n * - `graphorin traces prune [--before <date>]` - manual TTL\n * enforcement.\n *\n * The trace cache lives in the SQLite store under the `traces` table\n * (registered in Phase 04). This command is read + delete only - the\n * trace producer is the running `@graphorin/observability` exporter.\n *\n * @packageDocumentation\n */\n\nimport {\n brand,\n type CommonOutputOptions,\n defaultPrintSink,\n emitReport,\n statusMarker,\n} from '../internal/output.js';\nimport { openStoreContext } from '../internal/store-context.js';\n\n/** @stable */\nexport interface TracesCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport interface TracesStatusResult {\n readonly tableExists: boolean;\n readonly rows: number;\n readonly oldestStartedAt?: string;\n readonly newestStartedAt?: string;\n}\n\n/** @stable */\nexport async function runTracesStatus(\n options: TracesCommonOptions = {},\n): Promise<TracesStatusResult> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const conn = ctx.store.connection;\n const exists = tableExists(conn, 'traces');\n let out: TracesStatusResult;\n if (exists) {\n const oldest = pickIso(conn, 'SELECT MIN(started_at) AS started_at FROM traces');\n const newest = pickIso(conn, 'SELECT MAX(started_at) AS started_at FROM traces');\n out = Object.freeze({\n tableExists: true,\n rows: numericCount(conn, 'SELECT COUNT(*) AS n FROM traces'),\n ...(oldest !== undefined ? { oldestStartedAt: oldest } : {}),\n ...(newest !== undefined ? { newestStartedAt: newest } : {}),\n });\n } else {\n out = Object.freeze({ tableExists: false, rows: 0 });\n }\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (!out.tableExists) {\n print(\n brand('traces table not found - the observability exporter has not initialized it yet.'),\n );\n return;\n }\n print(\n brand(\n `traces: ${out.rows} row(s) (oldest=${out.oldestStartedAt ?? '-'}, newest=${out.newestStartedAt ?? '-'})`,\n ),\n );\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TracesPruneOptions extends TracesCommonOptions {\n /** ISO date / epoch ms cutoff. Required so the helper never silently\n * empties the table. */\n readonly before: string;\n}\n\n/** @stable */\nexport interface TracesPruneResult {\n readonly removed: number;\n readonly cutoff: string;\n}\n\n/** @stable */\nexport async function runTracesPrune(options: TracesPruneOptions): Promise<TracesPruneResult> {\n const cutoffMs = parseCutoff(options.before);\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n if (!tableExists(ctx.store.connection, 'traces')) {\n const out: TracesPruneResult = Object.freeze({\n removed: 0,\n cutoff: new Date(cutoffMs).toISOString(),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`traces table not found; nothing to prune.`));\n });\n return out;\n }\n const result = ctx.store.connection.run('DELETE FROM traces WHERE started_at < ?', [cutoffMs]);\n const out: TracesPruneResult = Object.freeze({\n removed: result.changes,\n cutoff: new Date(cutoffMs).toISOString(),\n });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n const mark = out.removed > 0 ? statusMarker('ok') : statusMarker('info');\n print(brand(`${mark} pruned ${out.removed} trace row(s) (cutoff=${out.cutoff}).`));\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\nfunction tableExists(\n conn: { get: <T = unknown>(s: string, p?: ReadonlyArray<unknown>) => T | undefined },\n name: string,\n): boolean {\n const row = conn.get<{ name: string }>(\n \"SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?\",\n [name],\n );\n return row !== undefined;\n}\n\nfunction numericCount(\n conn: { get: <T = unknown>(s: string) => T | undefined },\n sql: string,\n): number {\n const row = conn.get<{ n: number }>(sql);\n return typeof row?.n === 'number' ? row.n : 0;\n}\n\nfunction pickIso(\n conn: { get: <T = unknown>(s: string) => T | undefined },\n sql: string,\n): string | undefined {\n const row = conn.get<{ started_at: number | null }>(sql);\n if (row === undefined || row.started_at === null) return undefined;\n return new Date(row.started_at).toISOString();\n}\n\nfunction parseCutoff(input: string): number {\n const numeric = Number(input);\n if (Number.isFinite(numeric) && numeric > 0) return numeric;\n const ms = Date.parse(input);\n if (!Number.isFinite(ms)) {\n throw new Error(\n `[graphorin/cli] --before '${input}' is not a valid ISO date or epoch-ms value.`,\n );\n }\n return ms;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuCA,eAAsB,gBACpB,UAA+B,EAAE,EACJ;CAC7B,MAAM,MAAM,MAAM,iBAAiB,EACjC,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE,EACnE,CAAC;AACF,KAAI;EACF,MAAM,OAAO,IAAI,MAAM;EACvB,MAAM,SAAS,YAAY,MAAM,SAAS;EAC1C,IAAIA;AACJ,MAAI,QAAQ;GACV,MAAM,SAAS,QAAQ,MAAM,mDAAmD;GAChF,MAAM,SAAS,QAAQ,MAAM,mDAAmD;AAChF,SAAM,OAAO,OAAO;IAClB,aAAa;IACb,MAAM,aAAa,MAAM,mCAAmC;IAC5D,GAAI,WAAW,SAAY,EAAE,iBAAiB,QAAQ,GAAG,EAAE;IAC3D,GAAI,WAAW,SAAY,EAAE,iBAAiB,QAAQ,GAAG,EAAE;IAC5D,CAAC;QAEF,OAAM,OAAO,OAAO;GAAE,aAAa;GAAO,MAAM;GAAG,CAAC;AAEtD,aAAW,SAAS,WAAW;GAC7B,MAAM,QAAQ,QAAQ,SAAS;AAC/B,OAAI,CAAC,IAAI,aAAa;AACpB,UACE,MAAM,kFAAkF,CACzF;AACD;;AAEF,SACE,MACE,WAAW,IAAI,KAAK,kBAAkB,IAAI,mBAAmB,IAAI,WAAW,IAAI,mBAAmB,IAAI,GACxG,CACF;IACD;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;;AAkBrB,eAAsB,eAAe,SAAyD;CAC5F,MAAM,WAAW,YAAY,QAAQ,OAAO;CAC5C,MAAM,MAAM,MAAM,iBAAiB,EACjC,GAAI,QAAQ,WAAW,SAAY,EAAE,QAAQ,QAAQ,QAAQ,GAAG,EAAE,EACnE,CAAC;AACF,KAAI;AACF,MAAI,CAAC,YAAY,IAAI,MAAM,YAAY,SAAS,EAAE;GAChD,MAAMC,QAAyB,OAAO,OAAO;IAC3C,SAAS;IACT,QAAQ,IAAI,KAAK,SAAS,CAAC,aAAa;IACzC,CAAC;AACF,cAAW,SAASC,aAAW;AAE7B,KADc,QAAQ,SAAS,kBACzB,MAAM,4CAA4C,CAAC;KACzD;AACF,UAAOA;;EAET,MAAM,SAAS,IAAI,MAAM,WAAW,IAAI,2CAA2C,CAAC,SAAS,CAAC;EAC9F,MAAMD,MAAyB,OAAO,OAAO;GAC3C,SAAS,OAAO;GAChB,QAAQ,IAAI,KAAK,SAAS,CAAC,aAAa;GACzC,CAAC;AACF,aAAW,SAAS,WAAW;AAG7B,IAFc,QAAQ,SAAS,kBAEzB,MAAM,GADC,IAAI,UAAU,IAAI,aAAa,KAAK,GAAG,aAAa,OAAO,CACpD,UAAU,IAAI,QAAQ,wBAAwB,IAAI,OAAO,IAAI,CAAC;IAClF;AACF,SAAO;WACC;AACR,QAAM,IAAI,OAAO;;;AAIrB,SAAS,YACP,MACA,MACS;AAKT,QAJY,KAAK,IACf,oEACA,CAAC,KAAK,CACP,KACc;;AAGjB,SAAS,aACP,MACA,KACQ;CACR,MAAM,MAAM,KAAK,IAAmB,IAAI;AACxC,QAAO,OAAO,KAAK,MAAM,WAAW,IAAI,IAAI;;AAG9C,SAAS,QACP,MACA,KACoB;CACpB,MAAM,MAAM,KAAK,IAAmC,IAAI;AACxD,KAAI,QAAQ,UAAa,IAAI,eAAe,KAAM,QAAO;AACzD,QAAO,IAAI,KAAK,IAAI,WAAW,CAAC,aAAa;;AAG/C,SAAS,YAAY,OAAuB;CAC1C,MAAM,UAAU,OAAO,MAAM;AAC7B,KAAI,OAAO,SAAS,QAAQ,IAAI,UAAU,EAAG,QAAO;CACpD,MAAM,KAAK,KAAK,MAAM,MAAM;AAC5B,KAAI,CAAC,OAAO,SAAS,GAAG,CACtB,OAAM,IAAI,MACR,6BAA6B,MAAM,8CACpC;AAEH,QAAO"}
@@ -33,7 +33,7 @@ declare function runTriggersDisable(options: TriggersDisableOptions): Promise<Tr
33
33
  /** @stable */
34
34
  interface TriggersPruneOptions extends TriggersCommonOptions {
35
35
  /**
36
- * ISO date / epoch ms drop disabled triggers whose `lastFiredAt`
36
+ * ISO date / epoch ms - drop disabled triggers whose `lastFiredAt`
37
37
  * (or `createdAt`, when never fired) is older than this cutoff.
38
38
  */
39
39
  readonly before?: string;
@@ -58,7 +58,7 @@ async function runTriggersFire(options) {
58
58
  unsupported: true
59
59
  };
60
60
  emitReport(options, result, () => {
61
- (options.print ?? defaultPrintSink)(brand(`direct CLI fire is not wired yet use the running server: POST /v1/triggers/${options.id}/fire (scope triggers:fire).`));
61
+ (options.print ?? defaultPrintSink)(brand(`direct CLI fire is not wired yet - use the running server: POST /v1/triggers/${options.id}/fire (scope triggers:fire).`));
62
62
  });
63
63
  process.exitCode = EXIT_CODES.UNSUPPORTED;
64
64
  return result;
@@ -1 +1 @@
1
- {"version":3,"file":"triggers.js","names":["next: TriggerState","removed: string[]","out: TriggersPruneResult"],"sources":["../../src/commands/triggers.ts"],"sourcesContent":["/**\n * `graphorin triggers` — operate on the durable trigger registry.\n *\n * Surface (per Phase 15 § Triggers):\n *\n * - `graphorin triggers list` — every persisted trigger.\n * - `graphorin triggers status <id>` — single-trigger detail.\n * - `graphorin triggers fire <id>` — operator-fired side-effect (admin\n * only). The CLI persists a fire-now signal into `trigger_admin`\n * that the running scheduler picks up; library-mode triggers\n * can also be fired by re-running the host process.\n * - `graphorin triggers disable <id>` — flip the `disabled` column.\n * - `graphorin triggers prune` — drop orphan rows whose callbackRef\n * no longer exists in the running registry. Without a running\n * server the CLI cannot tell which triggers are orphans, so the\n * helper deletes only triggers whose `disabled` column is true and\n * whose `lastFiredAt` is older than the cutoff supplied via\n * `--before <ISO>`.\n *\n * @packageDocumentation\n */\n\nimport type { TriggerState } from '@graphorin/core/contracts';\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';\nimport { openStoreContext } from '../internal/store-context.js';\n\n/** @stable */\nexport interface TriggersCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport async function runTriggersList(\n options: TriggersCommonOptions = {},\n): Promise<ReadonlyArray<TriggerState>> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const list = await ctx.store.triggers.list();\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no triggers persisted.'));\n return;\n }\n print(brand(`${list.length} trigger(s):`));\n for (const t of list) {\n const mark = t.disabled ? statusMarker('warn') : statusMarker('ok');\n print(\n ` ${mark} ${t.id} (kind=${t.kind}, spec=${t.spec}, next=${t.nextFireAt ?? '-'}, missed=${t.missedFires})`,\n );\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersStatusOptions extends TriggersCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runTriggersStatus(\n options: TriggersStatusOptions,\n): Promise<TriggerState | null> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const state = await ctx.store.triggers.get(options.id);\n emitReport(options, state, () => {\n const print = options.print ?? defaultPrintSink;\n if (state === null) {\n print(brand(`trigger '${options.id}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`trigger ${state.id}`));\n print(` kind: ${state.kind}`);\n print(` spec: ${state.spec}`);\n print(` disabled: ${state.disabled}`);\n print(` catchupPolicy: ${state.catchupPolicy}`);\n print(` nextFireAt: ${state.nextFireAt ?? '-'}`);\n print(` lastFiredAt: ${state.lastFiredAt ?? '-'}`);\n print(` missedFires: ${state.missedFires}`);\n });\n return state;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersFireOptions extends TriggersCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runTriggersFire(\n options: TriggersFireOptions,\n): Promise<{ readonly fired: false; readonly unsupported: true }> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const state = await ctx.store.triggers.get(options.id);\n if (state === null) {\n throw new Error(`[graphorin/cli] trigger '${options.id}' not found.`);\n }\n // IP-4: the old implementation queued a row into a `trigger_admin`\n // table NOTHING polled and reported success — the fire never\n // happened. Until a daemon-side poll exists, the honest answer is\n // UNSUPPORTED with the working alternative.\n const result = { fired: false, unsupported: true } as const;\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `direct CLI fire is not wired yet — use the running server: POST /v1/triggers/${options.id}/fire (scope triggers:fire).`,\n ),\n );\n });\n process.exitCode = EXIT_CODES.UNSUPPORTED;\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersDisableOptions extends TriggersCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runTriggersDisable(\n options: TriggersDisableOptions,\n): Promise<TriggerState | null> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const existing = await ctx.store.triggers.get(options.id);\n if (existing === null) {\n throw new Error(`[graphorin/cli] trigger '${options.id}' not found.`);\n }\n const next: TriggerState = {\n ...existing,\n disabled: true,\n updatedAt: new Date().toISOString(),\n };\n await ctx.store.triggers.upsert(next);\n emitReport(options, next, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`trigger '${options.id}' disabled.`));\n });\n return next;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersPruneOptions extends TriggersCommonOptions {\n /**\n * ISO date / epoch ms — drop disabled triggers whose `lastFiredAt`\n * (or `createdAt`, when never fired) is older than this cutoff.\n */\n readonly before?: string;\n}\n\n/** @stable */\nexport interface TriggersPruneResult {\n readonly removed: ReadonlyArray<string>;\n}\n\n/** @stable */\nexport async function runTriggersPrune(\n options: TriggersPruneOptions = {},\n): Promise<TriggersPruneResult> {\n const cutoff = options.before === undefined ? 0 : parseCutoff(options.before);\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const all = await ctx.store.triggers.list();\n const removed: string[] = [];\n for (const t of all) {\n if (!t.disabled) continue;\n const lastTouched = t.lastFiredAt ?? t.createdAt;\n const lastTouchedMs = Date.parse(lastTouched);\n if (Number.isFinite(lastTouchedMs) && lastTouchedMs >= cutoff) continue;\n await ctx.store.triggers.remove(t.id);\n removed.push(t.id);\n }\n const out: TriggersPruneResult = Object.freeze({ removed: Object.freeze(removed) });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.removed.length === 0) {\n print(brand('no orphan triggers found.'));\n return;\n }\n print(brand(`pruned ${out.removed.length} orphan trigger(s):`));\n for (const id of out.removed) print(` - ${id}`);\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\nfunction parseCutoff(input: string): number {\n const numeric = Number(input);\n if (Number.isFinite(numeric) && numeric > 0) return numeric;\n const ms = Date.parse(input);\n if (!Number.isFinite(ms)) {\n throw new Error(\n `[graphorin/cli] --before '${input}' is not a valid ISO date or epoch-ms value.`,\n );\n }\n return ms;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"triggers.js","names":["next: TriggerState","removed: string[]","out: TriggersPruneResult"],"sources":["../../src/commands/triggers.ts"],"sourcesContent":["/**\n * `graphorin triggers` - operate on the durable trigger registry.\n *\n * Surface (per Phase 15 § Triggers):\n *\n * - `graphorin triggers list` - every persisted trigger.\n * - `graphorin triggers status <id>` - single-trigger detail.\n * - `graphorin triggers fire <id>` - operator-fired side-effect (admin\n * only). The CLI persists a fire-now signal into `trigger_admin`\n * that the running scheduler picks up; library-mode triggers\n * can also be fired by re-running the host process.\n * - `graphorin triggers disable <id>` - flip the `disabled` column.\n * - `graphorin triggers prune` - drop orphan rows whose callbackRef\n * no longer exists in the running registry. Without a running\n * server the CLI cannot tell which triggers are orphans, so the\n * helper deletes only triggers whose `disabled` column is true and\n * whose `lastFiredAt` is older than the cutoff supplied via\n * `--before <ISO>`.\n *\n * @packageDocumentation\n */\n\nimport type { TriggerState } from '@graphorin/core/contracts';\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';\nimport { openStoreContext } from '../internal/store-context.js';\n\n/** @stable */\nexport interface TriggersCommonOptions extends CommonOutputOptions {\n readonly config?: string;\n}\n\n/** @stable */\nexport async function runTriggersList(\n options: TriggersCommonOptions = {},\n): Promise<ReadonlyArray<TriggerState>> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const list = await ctx.store.triggers.list();\n emitReport(options, list, () => {\n const print = options.print ?? defaultPrintSink;\n if (list.length === 0) {\n print(brand('no triggers persisted.'));\n return;\n }\n print(brand(`${list.length} trigger(s):`));\n for (const t of list) {\n const mark = t.disabled ? statusMarker('warn') : statusMarker('ok');\n print(\n ` ${mark} ${t.id} (kind=${t.kind}, spec=${t.spec}, next=${t.nextFireAt ?? '-'}, missed=${t.missedFires})`,\n );\n }\n });\n return list;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersStatusOptions extends TriggersCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runTriggersStatus(\n options: TriggersStatusOptions,\n): Promise<TriggerState | null> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const state = await ctx.store.triggers.get(options.id);\n emitReport(options, state, () => {\n const print = options.print ?? defaultPrintSink;\n if (state === null) {\n print(brand(`trigger '${options.id}' not found.`));\n process.exitCode = EXIT_CODES.RECOVERABLE_FAILURE;\n return;\n }\n print(brand(`trigger ${state.id}`));\n print(` kind: ${state.kind}`);\n print(` spec: ${state.spec}`);\n print(` disabled: ${state.disabled}`);\n print(` catchupPolicy: ${state.catchupPolicy}`);\n print(` nextFireAt: ${state.nextFireAt ?? '-'}`);\n print(` lastFiredAt: ${state.lastFiredAt ?? '-'}`);\n print(` missedFires: ${state.missedFires}`);\n });\n return state;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersFireOptions extends TriggersCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runTriggersFire(\n options: TriggersFireOptions,\n): Promise<{ readonly fired: false; readonly unsupported: true }> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const state = await ctx.store.triggers.get(options.id);\n if (state === null) {\n throw new Error(`[graphorin/cli] trigger '${options.id}' not found.`);\n }\n // IP-4: the old implementation queued a row into a `trigger_admin`\n // table NOTHING polled and reported success - the fire never\n // happened. Until a daemon-side poll exists, the honest answer is\n // UNSUPPORTED with the working alternative.\n const result = { fired: false, unsupported: true } as const;\n emitReport(options, result, () => {\n const print = options.print ?? defaultPrintSink;\n print(\n brand(\n `direct CLI fire is not wired yet - use the running server: POST /v1/triggers/${options.id}/fire (scope triggers:fire).`,\n ),\n );\n });\n process.exitCode = EXIT_CODES.UNSUPPORTED;\n return result;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersDisableOptions extends TriggersCommonOptions {\n readonly id: string;\n}\n\n/** @stable */\nexport async function runTriggersDisable(\n options: TriggersDisableOptions,\n): Promise<TriggerState | null> {\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const existing = await ctx.store.triggers.get(options.id);\n if (existing === null) {\n throw new Error(`[graphorin/cli] trigger '${options.id}' not found.`);\n }\n const next: TriggerState = {\n ...existing,\n disabled: true,\n updatedAt: new Date().toISOString(),\n };\n await ctx.store.triggers.upsert(next);\n emitReport(options, next, () => {\n const print = options.print ?? defaultPrintSink;\n print(brand(`trigger '${options.id}' disabled.`));\n });\n return next;\n } finally {\n await ctx.close();\n }\n}\n\n/** @stable */\nexport interface TriggersPruneOptions extends TriggersCommonOptions {\n /**\n * ISO date / epoch ms - drop disabled triggers whose `lastFiredAt`\n * (or `createdAt`, when never fired) is older than this cutoff.\n */\n readonly before?: string;\n}\n\n/** @stable */\nexport interface TriggersPruneResult {\n readonly removed: ReadonlyArray<string>;\n}\n\n/** @stable */\nexport async function runTriggersPrune(\n options: TriggersPruneOptions = {},\n): Promise<TriggersPruneResult> {\n const cutoff = options.before === undefined ? 0 : parseCutoff(options.before);\n const ctx = await openStoreContext({\n ...(options.config !== undefined ? { config: options.config } : {}),\n });\n try {\n const all = await ctx.store.triggers.list();\n const removed: string[] = [];\n for (const t of all) {\n if (!t.disabled) continue;\n const lastTouched = t.lastFiredAt ?? t.createdAt;\n const lastTouchedMs = Date.parse(lastTouched);\n if (Number.isFinite(lastTouchedMs) && lastTouchedMs >= cutoff) continue;\n await ctx.store.triggers.remove(t.id);\n removed.push(t.id);\n }\n const out: TriggersPruneResult = Object.freeze({ removed: Object.freeze(removed) });\n emitReport(options, out, () => {\n const print = options.print ?? defaultPrintSink;\n if (out.removed.length === 0) {\n print(brand('no orphan triggers found.'));\n return;\n }\n print(brand(`pruned ${out.removed.length} orphan trigger(s):`));\n for (const id of out.removed) print(` - ${id}`);\n });\n return out;\n } finally {\n await ctx.close();\n }\n}\n\nfunction parseCutoff(input: string): number {\n const numeric = Number(input);\n if (Number.isFinite(numeric) && numeric > 0) return numeric;\n const ms = Date.parse(input);\n if (!Number.isFinite(ms)) {\n throw new Error(\n `[graphorin/cli] --before '${input}' is not a valid ISO date or epoch-ms value.`,\n );\n }\n return ms;\n}\n"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -12,7 +12,7 @@ import { PricingCommonOptions, PricingDiffOptions, PricingLookupOptions, Pricing
12
12
  import { SecretsCommonOptions, SecretsDeleteOptions, SecretsGetOptions, SecretsGetResult, SecretsListOptions, SecretsRefOptions, SecretsRefResult, SecretsRotateOptions, SecretsSetOptions, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet } from "./commands/secrets.js";
13
13
  import { SkillTrustLevelInput, SkillsAuditOptions, SkillsCommonOptions, SkillsInspectOptions, SkillsInstallOptions, SkillsMigrateFrontmatterOptions, SkillsMigrateFrontmatterResult, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter } from "./commands/skills.js";
14
14
  import { SecretsSourceFlag, StartCommandOptions, runStart } from "./commands/start.js";
15
- import { StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./commands/storage.js";
15
+ import { StorageBackupOptions, StorageBackupResult, StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./commands/storage.js";
16
16
  import { TelemetryStatusResult, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus } from "./commands/telemetry.js";
17
17
  import { TokenCommonOptions, TokenCreateOptions, TokenCreateResult, TokenListOptions, TokenRekeyOptions, TokenRevokeOptions, TokenRotateOptions, TokenVerifyOptions, TokenVerifyResult, parseDuration, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify } from "./commands/token.js";
18
18
  import { ToolsLintOptions, ToolsLintReport, runToolsLint } from "./commands/tools-lint.js";
@@ -23,7 +23,7 @@ import { OfflineModeViolationError, assertNoNetworkInOfflineMode, checkOfflineMo
23
23
 
24
24
  //#region src/index.d.ts
25
25
  /**
26
- * `@graphorin/cli` operator CLI for the Graphorin framework.
26
+ * `@graphorin/cli` - operator CLI for the Graphorin framework.
27
27
  *
28
28
  * Phase 14a shipped the minimal `graphorin` binary with three
29
29
  * lifecycle commands: `start`, `init`, and `migrate`. Phase 15
@@ -40,7 +40,7 @@ import { OfflineModeViolationError, assertNoNetworkInOfflineMode, checkOfflineMo
40
40
  * @packageDocumentation
41
41
  */
42
42
  /** Canonical version constant. Mirrors the `package.json` version. */
43
- declare const VERSION = "0.5.0";
43
+ declare const VERSION = "0.6.0";
44
44
  //#endregion
45
- export { AuditCommonOptions, AuditExportOptions, AuditExportResult, AuditPruneOptions, AuditVerifyResult, AuthCommonOptions, AuthListOptions, AuthLoginOptions, AuthRefreshOptions, AuthRevokeOptions, CONSOLIDATOR_INVALID_TIER_EXIT, ConsolidatorCommonOptions, ConsolidatorSetTierOptions, ConsolidatorStatusResult, ConsolidatorStopOptions, DoctorCommandOptions, DoctorReport, GuardCommonOptions, GuardExplainOptions, GuardExplainResult, GuardStatusEntry, InitCommandOptions, InitCommandResult, MemoryActivityConflict, MemoryActivityEvent, MemoryActivityOptions, MemoryActivityResult, MemoryCitingInsight, MemoryCommonOptions, MemoryConflictEntry, MemoryHistoryEntry, MemoryInspectEntity, MemoryInspectFact, MemoryInspectOptions, MemoryInspectResult, MemoryMigrateOptions, MemoryReviewItem, MemoryReviewOptions, MemoryReviewResult, MemoryStatusEmbedder, MemoryStatusResult, MemoryWhyOptions, MemoryWhyRecall, MemoryWhyResult, MigrateCommandOptions, MigrateCommandResult, MigrateConfigOptions, MigrateConfigResult, MigrateExportOptions, MigrateExportResult, OfflineModeViolationError, PricingCommonOptions, PricingDiffOptions, PricingLookupOptions, PricingMissingOptions, PricingRefreshOptions, PricingStatusResult, SecretsCommonOptions, SecretsDeleteOptions, SecretsGetOptions, SecretsGetResult, SecretsListOptions, SecretsRefOptions, SecretsRefResult, SecretsRotateOptions, SecretsSetOptions, SecretsSourceFlag, SkillTrustLevelInput, SkillsAuditOptions, SkillsCommonOptions, SkillsInspectOptions, SkillsInstallOptions, SkillsMigrateFrontmatterOptions, SkillsMigrateFrontmatterResult, StartCommandOptions, StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, TelemetryStatusResult, TokenCommonOptions, TokenCreateOptions, TokenCreateResult, TokenListOptions, TokenRekeyOptions, TokenRevokeOptions, TokenRotateOptions, TokenVerifyOptions, TokenVerifyResult, ToolsLintOptions, ToolsLintReport, TracesCommonOptions, TracesPruneOptions, TracesPruneResult, TracesStatusResult, TriggersCommonOptions, TriggersDisableOptions, TriggersFireOptions, TriggersPruneOptions, TriggersPruneResult, TriggersStatusOptions, VERSION, assertNoNetworkInOfflineMode, checkOfflineModeBlocked, expectedFileModes, isOfflineMode, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
45
+ export { AuditCommonOptions, AuditExportOptions, AuditExportResult, AuditPruneOptions, AuditVerifyResult, AuthCommonOptions, AuthListOptions, AuthLoginOptions, AuthRefreshOptions, AuthRevokeOptions, CONSOLIDATOR_INVALID_TIER_EXIT, ConsolidatorCommonOptions, ConsolidatorSetTierOptions, ConsolidatorStatusResult, ConsolidatorStopOptions, DoctorCommandOptions, DoctorReport, GuardCommonOptions, GuardExplainOptions, GuardExplainResult, GuardStatusEntry, InitCommandOptions, InitCommandResult, MemoryActivityConflict, MemoryActivityEvent, MemoryActivityOptions, MemoryActivityResult, MemoryCitingInsight, MemoryCommonOptions, MemoryConflictEntry, MemoryHistoryEntry, MemoryInspectEntity, MemoryInspectFact, MemoryInspectOptions, MemoryInspectResult, MemoryMigrateOptions, MemoryReviewItem, MemoryReviewOptions, MemoryReviewResult, MemoryStatusEmbedder, MemoryStatusResult, MemoryWhyOptions, MemoryWhyRecall, MemoryWhyResult, MigrateCommandOptions, MigrateCommandResult, MigrateConfigOptions, MigrateConfigResult, MigrateExportOptions, MigrateExportResult, OfflineModeViolationError, PricingCommonOptions, PricingDiffOptions, PricingLookupOptions, PricingMissingOptions, PricingRefreshOptions, PricingStatusResult, SecretsCommonOptions, SecretsDeleteOptions, SecretsGetOptions, SecretsGetResult, SecretsListOptions, SecretsRefOptions, SecretsRefResult, SecretsRotateOptions, SecretsSetOptions, SecretsSourceFlag, SkillTrustLevelInput, SkillsAuditOptions, SkillsCommonOptions, SkillsInspectOptions, SkillsInstallOptions, SkillsMigrateFrontmatterOptions, SkillsMigrateFrontmatterResult, StartCommandOptions, StorageBackupOptions, StorageBackupResult, StorageCleanupBackupsOptions, StorageCleanupBackupsResult, StorageCommonOptions, StorageEncryptOptions, StorageRekeyOptions, StorageStatusResult, TelemetryStatusResult, TokenCommonOptions, TokenCreateOptions, TokenCreateResult, TokenListOptions, TokenRekeyOptions, TokenRevokeOptions, TokenRotateOptions, TokenVerifyOptions, TokenVerifyResult, ToolsLintOptions, ToolsLintReport, TracesCommonOptions, TracesPruneOptions, TracesPruneResult, TracesStatusResult, TriggersCommonOptions, TriggersDisableOptions, TriggersFireOptions, TriggersPruneOptions, TriggersPruneResult, TriggersStatusOptions, VERSION, assertNoNetworkInOfflineMode, checkOfflineModeBlocked, expectedFileModes, isOfflineMode, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
46
46
  //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import { runMigrateExport } from "./commands/migrate-export.js";
13
13
  import { runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus } from "./commands/pricing.js";
14
14
  import { runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet } from "./commands/secrets.js";
15
15
  import { runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter } from "./commands/skills.js";
16
- import { runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./commands/storage.js";
16
+ import { runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus } from "./commands/storage.js";
17
17
  import { runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus } from "./commands/telemetry.js";
18
18
  import { parseDuration, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify } from "./commands/token.js";
19
19
  import { runToolsLint } from "./commands/tools-lint.js";
@@ -23,7 +23,7 @@ import "./commands/index.js";
23
23
 
24
24
  //#region src/index.ts
25
25
  /**
26
- * `@graphorin/cli` operator CLI for the Graphorin framework.
26
+ * `@graphorin/cli` - operator CLI for the Graphorin framework.
27
27
  *
28
28
  * Phase 14a shipped the minimal `graphorin` binary with three
29
29
  * lifecycle commands: `start`, `init`, and `migrate`. Phase 15
@@ -40,8 +40,8 @@ import "./commands/index.js";
40
40
  * @packageDocumentation
41
41
  */
42
42
  /** Canonical version constant. Mirrors the `package.json` version. */
43
- const VERSION = "0.5.0";
43
+ const VERSION = "0.6.0";
44
44
 
45
45
  //#endregion
46
- export { CONSOLIDATOR_INVALID_TIER_EXIT, OfflineModeViolationError, VERSION, assertNoNetworkInOfflineMode, checkOfflineModeBlocked, expectedFileModes, isOfflineMode, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
46
+ export { CONSOLIDATOR_INVALID_TIER_EXIT, OfflineModeViolationError, VERSION, assertNoNetworkInOfflineMode, checkOfflineModeBlocked, expectedFileModes, isOfflineMode, parseDuration, runAuditExport, runAuditPrune, runAuditVerify, runAuthList, runAuthLogin, runAuthRefresh, runAuthRevoke, runAuthStatus, runConsolidatorSetTier, runConsolidatorStatus, runConsolidatorStop, runDoctor, runGuardExplain, runGuardStatus, runInit, runMemoryActivity, runMemoryInspect, runMemoryMigrate, runMemoryReview, runMemoryStatus, runMemoryWhy, runMigrate, runMigrateConfig, runMigrateExport, runPricingDiff, runPricingLookup, runPricingMissing, runPricingRefresh, runPricingStatus, runSecretsDelete, runSecretsGet, runSecretsList, runSecretsRef, runSecretsRotate, runSecretsSet, runSkillsAudit, runSkillsInspect, runSkillsInstall, runSkillsMigrateFrontmatter, runStart, runStorageBackup, runStorageCleanupBackups, runStorageEncrypt, runStorageRekey, runStorageStatus, runTelemetryDisable, runTelemetryEnable, runTelemetryInspect, runTelemetryStatus, runTokenCreate, runTokenList, runTokenRekey, runTokenRevoke, runTokenRotate, runTokenVerify, runToolsLint, runTracesPrune, runTracesStatus, runTriggersDisable, runTriggersFire, runTriggersList, runTriggersPrune, runTriggersStatus };
47
47
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphorin/cli` operator CLI for the Graphorin framework.\n *\n * Phase 14a shipped the minimal `graphorin` binary with three\n * lifecycle commands: `start`, `init`, and `migrate`. Phase 15\n * extended the same binary with the operator surface (`doctor`,\n * `token`, `secrets`, `audit`, `storage`, `memory`, `consolidator`,\n * `triggers`, `auth`, `pricing`, `skills`, `traces`,\n * `migrate-export`, `migrate-config`, `guard`, `telemetry`,\n * `tools lint`).\n *\n * Every subcommand is exported as a typed library function so\n * downstream automations can call them directly without spawning a\n * child process.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant. Mirrors the `package.json` version. */\nexport const VERSION = '0.5.0';\n\nexport * from './commands/index.js';\nexport {\n assertNoNetworkInOfflineMode,\n checkOfflineModeBlocked,\n isOfflineMode,\n OfflineModeViolationError,\n} from './internal/offline.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,UAAU"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphorin/cli` - operator CLI for the Graphorin framework.\n *\n * Phase 14a shipped the minimal `graphorin` binary with three\n * lifecycle commands: `start`, `init`, and `migrate`. Phase 15\n * extended the same binary with the operator surface (`doctor`,\n * `token`, `secrets`, `audit`, `storage`, `memory`, `consolidator`,\n * `triggers`, `auth`, `pricing`, `skills`, `traces`,\n * `migrate-export`, `migrate-config`, `guard`, `telemetry`,\n * `tools lint`).\n *\n * Every subcommand is exported as a typed library function so\n * downstream automations can call them directly without spawning a\n * child process.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant. Mirrors the `package.json` version. */\nexport const VERSION = '0.6.0';\n\nexport * from './commands/index.js';\nexport {\n assertNoNetworkInOfflineMode,\n checkOfflineModeBlocked,\n isOfflineMode,\n OfflineModeViolationError,\n} from './internal/offline.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmBA,MAAa,UAAU"}
@@ -1 +1 @@
1
- {"version":3,"file":"exit.js","names":[],"sources":["../../src/internal/exit.ts"],"sourcesContent":["/**\n * Shared exit helpers. Phase 15 subcommands fail-fast through\n * {@link fail}; the helper writes the error message via the chosen\n * sink and then calls `process.exit(code)`. Tests stub `process.exit`\n * to convert the call into a thrown sentinel they can assert against.\n *\n * Exit codes follow the small documented contract:\n *\n * - `0` success (subcommand completed without warnings or with\n * informational warnings only).\n * - `1` recoverable failure (config invalid, secret missing,\n * threshold exceeded). Operator can fix and re-run.\n * - `2` unsupported invocation (e.g. `graphorin storage encrypt`\n * when the Phase 16 sub-pack is not installed). Operator must\n * install / configure something before retrying.\n *\n * @internal\n */\n\nimport process from 'node:process';\n\nimport { brand } from './output.js';\n\n/**\n * Numeric exit codes the CLI uses uniformly.\n *\n * @internal\n */\nexport const EXIT_CODES = Object.freeze({\n OK: 0,\n RECOVERABLE_FAILURE: 1,\n UNSUPPORTED: 2,\n} as const);\n\n/**\n * Pretty-print an `Error`-shaped value without leaking secrets, then\n * exit. Re-uses `cause` chains when present.\n *\n * @internal\n */\nexport function fail(\n err: unknown,\n options: {\n readonly code?: number;\n readonly print?: (line: string) => void;\n } = {},\n): never {\n const code = options.code ?? EXIT_CODES.RECOVERABLE_FAILURE;\n const print = options.print ?? ((line: string) => process.stderr.write(`${line}\\n`));\n if (err instanceof Error) {\n const kind = (err as { kind?: string }).kind;\n const prefix = kind === undefined ? '' : `${kind}: `;\n print(brand(`${prefix}${err.message}`));\n const hint = (err as { hint?: string }).hint;\n if (typeof hint === 'string' && hint.length > 0) {\n print(brand(`hint: ${hint}`));\n }\n } else {\n print(brand(`unknown error: ${String(err)}`));\n }\n process.exit(code);\n}\n"],"mappings":";;;;;;;;AA4BA,MAAa,aAAa,OAAO,OAAO;CACtC,IAAI;CACJ,qBAAqB;CACrB,aAAa;CACd,CAAU"}
1
+ {"version":3,"file":"exit.js","names":[],"sources":["../../src/internal/exit.ts"],"sourcesContent":["/**\n * Shared exit helpers. Phase 15 subcommands fail-fast through\n * {@link fail}; the helper writes the error message via the chosen\n * sink and then calls `process.exit(code)`. Tests stub `process.exit`\n * to convert the call into a thrown sentinel they can assert against.\n *\n * Exit codes follow the small documented contract:\n *\n * - `0` - success (subcommand completed without warnings or with\n * informational warnings only).\n * - `1` - recoverable failure (config invalid, secret missing,\n * threshold exceeded). Operator can fix and re-run.\n * - `2` - unsupported invocation (e.g. `graphorin storage encrypt`\n * when the Phase 16 sub-pack is not installed). Operator must\n * install / configure something before retrying.\n *\n * @internal\n */\n\nimport process from 'node:process';\n\nimport { brand } from './output.js';\n\n/**\n * Numeric exit codes the CLI uses uniformly.\n *\n * @internal\n */\nexport const EXIT_CODES = Object.freeze({\n OK: 0,\n RECOVERABLE_FAILURE: 1,\n UNSUPPORTED: 2,\n} as const);\n\n/**\n * Pretty-print an `Error`-shaped value without leaking secrets, then\n * exit. Re-uses `cause` chains when present.\n *\n * @internal\n */\nexport function fail(\n err: unknown,\n options: {\n readonly code?: number;\n readonly print?: (line: string) => void;\n } = {},\n): never {\n const code = options.code ?? EXIT_CODES.RECOVERABLE_FAILURE;\n const print = options.print ?? ((line: string) => process.stderr.write(`${line}\\n`));\n if (err instanceof Error) {\n const kind = (err as { kind?: string }).kind;\n const prefix = kind === undefined ? '' : `${kind}: `;\n print(brand(`${prefix}${err.message}`));\n const hint = (err as { hint?: string }).hint;\n if (typeof hint === 'string' && hint.length > 0) {\n print(brand(`hint: ${hint}`));\n }\n } else {\n print(brand(`unknown error: ${String(err)}`));\n }\n process.exit(code);\n}\n"],"mappings":";;;;;;;;AA4BA,MAAa,aAAa,OAAO,OAAO;CACtC,IAAI;CACJ,qBAAqB;CACrB,aAAa;CACd,CAAU"}
@@ -6,17 +6,17 @@ import { pathToFileURL } from "node:url";
6
6
  /**
7
7
  * Tiny config loader. Resolves the path the operator handed in (or
8
8
  * the per-platform default) and returns the raw {@link ServerConfigInput}
9
- * payload strict validation happens later via
9
+ * payload - strict validation happens later via
10
10
  * `parseServerConfig(...)` from `@graphorin/server`.
11
11
  *
12
12
  * Supported file kinds (decided by extension):
13
13
  *
14
- * - `*.ts` / `*.mts` / `*.cts` re-imported through the platform
14
+ * - `*.ts` / `*.mts` / `*.cts` - re-imported through the platform
15
15
  * loader; relies on Node's native TS support (or a registered
16
- * loader) Phase 14a leaves the operator to wire `tsx`, `ts-node`,
16
+ * loader) - Phase 14a leaves the operator to wire `tsx`, `ts-node`,
17
17
  * or whatever they prefer when shipping a `.ts` config.
18
- * - `*.js` / `*.mjs` dynamic `import()`.
19
- * - `*.json` `readFile` + `JSON.parse`.
18
+ * - `*.js` / `*.mjs` - dynamic `import()`.
19
+ * - `*.json` - `readFile` + `JSON.parse`.
20
20
  *
21
21
  * The file is expected to default-export an object built via
22
22
  * `defineConfig({...})`.
@@ -1 +1 @@
1
- {"version":3,"file":"load-config.js","names":["DEFAULT_FILENAMES: ReadonlyArray<string>","absolute: string"],"sources":["../../src/internal/load-config.ts"],"sourcesContent":["/**\n * Tiny config loader. Resolves the path the operator handed in (or\n * the per-platform default) and returns the raw {@link ServerConfigInput}\n * payload strict validation happens later via\n * `parseServerConfig(...)` from `@graphorin/server`.\n *\n * Supported file kinds (decided by extension):\n *\n * - `*.ts` / `*.mts` / `*.cts` re-imported through the platform\n * loader; relies on Node's native TS support (or a registered\n * loader) Phase 14a leaves the operator to wire `tsx`, `ts-node`,\n * or whatever they prefer when shipping a `.ts` config.\n * - `*.js` / `*.mjs` dynamic `import()`.\n * - `*.json` `readFile` + `JSON.parse`.\n *\n * The file is expected to default-export an object built via\n * `defineConfig({...})`.\n *\n * @internal\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { ServerConfigInput } from '@graphorin/server';\n\n/**\n * @stable\n */\nexport interface LoadConfigOptions {\n /** Override the resolved process.cwd(). Tests inject a fixture root. */\n readonly cwd?: string;\n}\n\n/**\n * @stable\n */\nexport interface LoadedConfig {\n readonly path: string;\n readonly config: ServerConfigInput;\n}\n\nconst DEFAULT_FILENAMES: ReadonlyArray<string> = Object.freeze([\n 'graphorin.config.ts',\n 'graphorin.config.mjs',\n 'graphorin.config.js',\n 'graphorin.config.json',\n]);\n\n/**\n * Resolve the path the operator passed (or pick the first existing\n * default in `cwd`) and parse the config it exports.\n *\n * @stable\n */\nexport async function loadConfig(\n pathArg: string | undefined,\n options: LoadConfigOptions = {},\n): Promise<LoadedConfig> {\n const cwd = options.cwd ?? process.cwd();\n let absolute: string;\n if (pathArg !== undefined && pathArg.length > 0) {\n absolute = isAbsolute(pathArg) ? pathArg : resolve(cwd, pathArg);\n await assertExists(absolute);\n } else {\n absolute = await firstExistingDefault(cwd);\n }\n const config = await importConfig(absolute);\n return Object.freeze({ path: absolute, config });\n}\n\nasync function firstExistingDefault(cwd: string): Promise<string> {\n for (const name of DEFAULT_FILENAMES) {\n const candidate = resolve(cwd, name);\n try {\n await stat(candidate);\n return candidate;\n } catch {}\n }\n throw new Error(\n `[graphorin/cli] no config file found in '${cwd}'. Looked for: ${DEFAULT_FILENAMES.join(', ')}.`,\n );\n}\n\nasync function assertExists(path: string): Promise<void> {\n try {\n await stat(path);\n } catch (err) {\n throw new Error(`[graphorin/cli] config file '${path}' not found.`, { cause: err });\n }\n}\n\nasync function importConfig(path: string): Promise<ServerConfigInput> {\n if (path.endsWith('.json')) {\n const raw = await readFile(path, 'utf8');\n try {\n return JSON.parse(raw) as ServerConfigInput;\n } catch (err) {\n throw new Error(`[graphorin/cli] config '${path}' is not valid JSON.`, { cause: err });\n }\n }\n const url = pathToFileURL(path).href;\n const mod = (await import(url)) as { default?: unknown };\n if (mod.default !== undefined) return mod.default as ServerConfigInput;\n return mod as unknown as ServerConfigInput;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAMA,oBAA2C,OAAO,OAAO;CAC7D;CACA;CACA;CACA;CACD,CAAC;;;;;;;AAQF,eAAsB,WACpB,SACA,UAA6B,EAAE,EACR;CACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;CACxC,IAAIC;AACJ,KAAI,YAAY,UAAa,QAAQ,SAAS,GAAG;AAC/C,aAAW,WAAW,QAAQ,GAAG,UAAU,QAAQ,KAAK,QAAQ;AAChE,QAAM,aAAa,SAAS;OAE5B,YAAW,MAAM,qBAAqB,IAAI;CAE5C,MAAM,SAAS,MAAM,aAAa,SAAS;AAC3C,QAAO,OAAO,OAAO;EAAE,MAAM;EAAU;EAAQ,CAAC;;AAGlD,eAAe,qBAAqB,KAA8B;AAChE,MAAK,MAAM,QAAQ,mBAAmB;EACpC,MAAM,YAAY,QAAQ,KAAK,KAAK;AACpC,MAAI;AACF,SAAM,KAAK,UAAU;AACrB,UAAO;UACD;;AAEV,OAAM,IAAI,MACR,4CAA4C,IAAI,iBAAiB,kBAAkB,KAAK,KAAK,CAAC,GAC/F;;AAGH,eAAe,aAAa,MAA6B;AACvD,KAAI;AACF,QAAM,KAAK,KAAK;UACT,KAAK;AACZ,QAAM,IAAI,MAAM,gCAAgC,KAAK,eAAe,EAAE,OAAO,KAAK,CAAC;;;AAIvF,eAAe,aAAa,MAA0C;AACpE,KAAI,KAAK,SAAS,QAAQ,EAAE;EAC1B,MAAM,MAAM,MAAM,SAAS,MAAM,OAAO;AACxC,MAAI;AACF,UAAO,KAAK,MAAM,IAAI;WACf,KAAK;AACZ,SAAM,IAAI,MAAM,2BAA2B,KAAK,uBAAuB,EAAE,OAAO,KAAK,CAAC;;;CAI1F,MAAM,MAAO,MAAM,OADP,cAAc,KAAK,CAAC;AAEhC,KAAI,IAAI,YAAY,OAAW,QAAO,IAAI;AAC1C,QAAO"}
1
+ {"version":3,"file":"load-config.js","names":["DEFAULT_FILENAMES: ReadonlyArray<string>","absolute: string"],"sources":["../../src/internal/load-config.ts"],"sourcesContent":["/**\n * Tiny config loader. Resolves the path the operator handed in (or\n * the per-platform default) and returns the raw {@link ServerConfigInput}\n * payload - strict validation happens later via\n * `parseServerConfig(...)` from `@graphorin/server`.\n *\n * Supported file kinds (decided by extension):\n *\n * - `*.ts` / `*.mts` / `*.cts` - re-imported through the platform\n * loader; relies on Node's native TS support (or a registered\n * loader) - Phase 14a leaves the operator to wire `tsx`, `ts-node`,\n * or whatever they prefer when shipping a `.ts` config.\n * - `*.js` / `*.mjs` - dynamic `import()`.\n * - `*.json` - `readFile` + `JSON.parse`.\n *\n * The file is expected to default-export an object built via\n * `defineConfig({...})`.\n *\n * @internal\n */\n\nimport { readFile, stat } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\nimport type { ServerConfigInput } from '@graphorin/server';\n\n/**\n * @stable\n */\nexport interface LoadConfigOptions {\n /** Override the resolved process.cwd(). Tests inject a fixture root. */\n readonly cwd?: string;\n}\n\n/**\n * @stable\n */\nexport interface LoadedConfig {\n readonly path: string;\n readonly config: ServerConfigInput;\n}\n\nconst DEFAULT_FILENAMES: ReadonlyArray<string> = Object.freeze([\n 'graphorin.config.ts',\n 'graphorin.config.mjs',\n 'graphorin.config.js',\n 'graphorin.config.json',\n]);\n\n/**\n * Resolve the path the operator passed (or pick the first existing\n * default in `cwd`) and parse the config it exports.\n *\n * @stable\n */\nexport async function loadConfig(\n pathArg: string | undefined,\n options: LoadConfigOptions = {},\n): Promise<LoadedConfig> {\n const cwd = options.cwd ?? process.cwd();\n let absolute: string;\n if (pathArg !== undefined && pathArg.length > 0) {\n absolute = isAbsolute(pathArg) ? pathArg : resolve(cwd, pathArg);\n await assertExists(absolute);\n } else {\n absolute = await firstExistingDefault(cwd);\n }\n const config = await importConfig(absolute);\n return Object.freeze({ path: absolute, config });\n}\n\nasync function firstExistingDefault(cwd: string): Promise<string> {\n for (const name of DEFAULT_FILENAMES) {\n const candidate = resolve(cwd, name);\n try {\n await stat(candidate);\n return candidate;\n } catch {}\n }\n throw new Error(\n `[graphorin/cli] no config file found in '${cwd}'. Looked for: ${DEFAULT_FILENAMES.join(', ')}.`,\n );\n}\n\nasync function assertExists(path: string): Promise<void> {\n try {\n await stat(path);\n } catch (err) {\n throw new Error(`[graphorin/cli] config file '${path}' not found.`, { cause: err });\n }\n}\n\nasync function importConfig(path: string): Promise<ServerConfigInput> {\n if (path.endsWith('.json')) {\n const raw = await readFile(path, 'utf8');\n try {\n return JSON.parse(raw) as ServerConfigInput;\n } catch (err) {\n throw new Error(`[graphorin/cli] config '${path}' is not valid JSON.`, { cause: err });\n }\n }\n const url = pathToFileURL(path).href;\n const mod = (await import(url)) as { default?: unknown };\n if (mod.default !== undefined) return mod.default as ServerConfigInput;\n return mod as unknown as ServerConfigInput;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,MAAMA,oBAA2C,OAAO,OAAO;CAC7D;CACA;CACA;CACA;CACD,CAAC;;;;;;;AAQF,eAAsB,WACpB,SACA,UAA6B,EAAE,EACR;CACvB,MAAM,MAAM,QAAQ,OAAO,QAAQ,KAAK;CACxC,IAAIC;AACJ,KAAI,YAAY,UAAa,QAAQ,SAAS,GAAG;AAC/C,aAAW,WAAW,QAAQ,GAAG,UAAU,QAAQ,KAAK,QAAQ;AAChE,QAAM,aAAa,SAAS;OAE5B,YAAW,MAAM,qBAAqB,IAAI;CAE5C,MAAM,SAAS,MAAM,aAAa,SAAS;AAC3C,QAAO,OAAO,OAAO;EAAE,MAAM;EAAU;EAAQ,CAAC;;AAGlD,eAAe,qBAAqB,KAA8B;AAChE,MAAK,MAAM,QAAQ,mBAAmB;EACpC,MAAM,YAAY,QAAQ,KAAK,KAAK;AACpC,MAAI;AACF,SAAM,KAAK,UAAU;AACrB,UAAO;UACD;;AAEV,OAAM,IAAI,MACR,4CAA4C,IAAI,iBAAiB,kBAAkB,KAAK,KAAK,CAAC,GAC/F;;AAGH,eAAe,aAAa,MAA6B;AACvD,KAAI;AACF,QAAM,KAAK,KAAK;UACT,KAAK;AACZ,QAAM,IAAI,MAAM,gCAAgC,KAAK,eAAe,EAAE,OAAO,KAAK,CAAC;;;AAIvF,eAAe,aAAa,MAA0C;AACpE,KAAI,KAAK,SAAS,QAAQ,EAAE;EAC1B,MAAM,MAAM,MAAM,SAAS,MAAM,OAAO;AACxC,MAAI;AACF,UAAO,KAAK,MAAM,IAAI;WACf,KAAK;AACZ,SAAM,IAAI,MAAM,2BAA2B,KAAK,uBAAuB,EAAE,OAAO,KAAK,CAAC;;;CAI1F,MAAM,MAAO,MAAM,OADP,cAAc,KAAK,CAAC;AAEhC,KAAI,IAAI,YAAY,OAAW,QAAO,IAAI;AAC1C,QAAO"}
@@ -5,7 +5,7 @@
5
5
  * promise on the operator-facing side by surfacing the same flag
6
6
  * every subcommand obeys.
7
7
  *
8
- * The flag is informational in Phase 14a the three commands the
8
+ * The flag is informational in Phase 14a - the three commands the
9
9
  * binary ships do NOT make any outbound network calls. A future
10
10
  * phase that exposes a network-using subcommand (e.g. `graphorin
11
11
  * pricing refresh`) reads {@link isOfflineMode} to short-circuit.
@@ -5,7 +5,7 @@
5
5
  * promise on the operator-facing side by surfacing the same flag
6
6
  * every subcommand obeys.
7
7
  *
8
- * The flag is informational in Phase 14a the three commands the
8
+ * The flag is informational in Phase 14a - the three commands the
9
9
  * binary ships do NOT make any outbound network calls. A future
10
10
  * phase that exposes a network-using subcommand (e.g. `graphorin
11
11
  * pricing refresh`) reads {@link isOfflineMode} to short-circuit.
@@ -48,7 +48,7 @@ function assertNoNetworkInOfflineMode(operation) {
48
48
  */
49
49
  function checkOfflineModeBlocked(operation, options = {}) {
50
50
  if (!isOfflineMode(options.env ?? process.env)) return true;
51
- (options.print ?? ((line) => process.stderr.write(`${line}\n`)))(`[graphorin/cli] GRAPHORIN_OFFLINE=1 refusing to perform '${operation}'. Unset GRAPHORIN_OFFLINE to opt back in.`);
51
+ (options.print ?? ((line) => process.stderr.write(`${line}\n`)))(`[graphorin/cli] GRAPHORIN_OFFLINE=1 - refusing to perform '${operation}'. Unset GRAPHORIN_OFFLINE to opt back in.`);
52
52
  return false;
53
53
  }
54
54
  /**