@graphorin/cli 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +3 -3
  3. package/dist/bin/graphorin.js +51 -13
  4. package/dist/bin/graphorin.js.map +1 -1
  5. package/dist/commands/audit.d.ts.map +1 -1
  6. package/dist/commands/audit.js +2 -1
  7. package/dist/commands/audit.js.map +1 -1
  8. package/dist/commands/consolidator.d.ts +56 -1
  9. package/dist/commands/consolidator.d.ts.map +1 -1
  10. package/dist/commands/consolidator.js +88 -2
  11. package/dist/commands/consolidator.js.map +1 -1
  12. package/dist/commands/doctor.d.ts.map +1 -1
  13. package/dist/commands/index.d.ts +4 -4
  14. package/dist/commands/index.js +4 -4
  15. package/dist/commands/init.d.ts +11 -4
  16. package/dist/commands/init.d.ts.map +1 -1
  17. package/dist/commands/init.js +15 -11
  18. package/dist/commands/init.js.map +1 -1
  19. package/dist/commands/memory.d.ts +26 -1
  20. package/dist/commands/memory.d.ts.map +1 -1
  21. package/dist/commands/memory.js +56 -3
  22. package/dist/commands/memory.js.map +1 -1
  23. package/dist/commands/pricing.d.ts +6 -0
  24. package/dist/commands/pricing.d.ts.map +1 -1
  25. package/dist/commands/pricing.js +5 -2
  26. package/dist/commands/pricing.js.map +1 -1
  27. package/dist/commands/secrets.d.ts.map +1 -1
  28. package/dist/commands/secrets.js +2 -2
  29. package/dist/commands/secrets.js.map +1 -1
  30. package/dist/commands/skills.d.ts.map +1 -1
  31. package/dist/commands/skills.js +1 -1
  32. package/dist/commands/skills.js.map +1 -1
  33. package/dist/commands/storage.d.ts +41 -1
  34. package/dist/commands/storage.d.ts.map +1 -1
  35. package/dist/commands/storage.js +75 -1
  36. package/dist/commands/storage.js.map +1 -1
  37. package/dist/commands/token.d.ts.map +1 -1
  38. package/dist/commands/token.js +2 -2
  39. package/dist/commands/token.js.map +1 -1
  40. package/dist/commands/tools-lint.js +1 -1
  41. package/dist/commands/tools-lint.js.map +1 -1
  42. package/dist/commands/traces.d.ts +14 -2
  43. package/dist/commands/traces.d.ts.map +1 -1
  44. package/dist/commands/traces.js +39 -22
  45. package/dist/commands/traces.js.map +1 -1
  46. package/dist/commands/triggers.d.ts.map +1 -1
  47. package/dist/commands/triggers.js +5 -2
  48. package/dist/commands/triggers.js.map +1 -1
  49. package/dist/index.d.ts +4 -4
  50. package/dist/index.js +4 -5
  51. package/dist/index.js.map +1 -1
  52. package/dist/internal/output.js +6 -0
  53. package/dist/internal/output.js.map +1 -1
  54. package/dist/internal/store-context.js +13 -2
  55. package/dist/internal/store-context.js.map +1 -1
  56. package/dist/package.js +1 -1
  57. package/dist/package.js.map +1 -1
  58. package/package.json +18 -14
  59. package/src/bin/graphorin.ts +1387 -0
  60. package/src/commands/audit.ts +256 -0
  61. package/src/commands/auth.ts +238 -0
  62. package/src/commands/consolidator.ts +382 -0
  63. package/src/commands/doctor.ts +253 -0
  64. package/src/commands/guard.ts +144 -0
  65. package/src/commands/index.ts +223 -0
  66. package/src/commands/init.ts +194 -0
  67. package/src/commands/memory.ts +1052 -0
  68. package/src/commands/migrate-config.ts +77 -0
  69. package/src/commands/migrate-export.ts +117 -0
  70. package/src/commands/migrate.ts +83 -0
  71. package/src/commands/pricing.ts +244 -0
  72. package/src/commands/secrets.ts +309 -0
  73. package/src/commands/skills.ts +272 -0
  74. package/src/commands/start.ts +180 -0
  75. package/src/commands/storage.ts +659 -0
  76. package/src/commands/telemetry.ts +91 -0
  77. package/src/commands/token.ts +361 -0
  78. package/src/commands/tools-lint.ts +430 -0
  79. package/src/commands/traces.ts +188 -0
  80. package/src/commands/triggers.ts +237 -0
  81. package/src/index.ts +30 -0
  82. package/src/internal/exit.ts +62 -0
  83. package/src/internal/load-config.ts +107 -0
  84. package/src/internal/offline.ts +81 -0
  85. package/src/internal/output.ts +146 -0
  86. package/src/internal/prompts.ts +58 -0
  87. package/src/internal/store-context.ts +165 -0
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Tiny wrapper around `readline` for interactive prompts. Avoids the
3
+ * `enquirer` peer dependency for the minimal Phase 14a surface.
4
+ *
5
+ * @internal
6
+ */
7
+
8
+ import { stdin as input, stdout as output } from 'node:process';
9
+ import { createInterface } from 'node:readline/promises';
10
+
11
+ /**
12
+ * @internal
13
+ */
14
+ export async function ask(question: string, defaultValue?: string): Promise<string> {
15
+ const rl = createInterface({ input, output });
16
+ try {
17
+ const suffix = defaultValue !== undefined ? ` (${defaultValue})` : '';
18
+ const answer = await rl.question(`${question}${suffix} `);
19
+ if (answer.length === 0 && defaultValue !== undefined) return defaultValue;
20
+ return answer;
21
+ } finally {
22
+ rl.close();
23
+ }
24
+ }
25
+
26
+ /**
27
+ * @internal
28
+ */
29
+ export async function confirm(question: string, defaultValue: boolean): Promise<boolean> {
30
+ const answer = await ask(
31
+ `${question} [${defaultValue ? 'Y/n' : 'y/N'}]`,
32
+ defaultValue ? 'y' : 'n',
33
+ );
34
+ const normalized = answer.trim().toLowerCase();
35
+ if (normalized.length === 0) return defaultValue;
36
+ return normalized === 'y' || normalized === 'yes';
37
+ }
38
+
39
+ /**
40
+ * @internal
41
+ */
42
+ export async function select(
43
+ question: string,
44
+ options: ReadonlyArray<string>,
45
+ defaultValue: string,
46
+ ): Promise<string> {
47
+ const numbered = options.map(
48
+ (opt, idx) => ` ${idx + 1}. ${opt}${opt === defaultValue ? ' (default)' : ''}`,
49
+ );
50
+ const answer = await ask(`${question}\n${numbered.join('\n')}\n>`, defaultValue);
51
+ const trimmed = answer.trim();
52
+ const num = Number.parseInt(trimmed, 10);
53
+ if (Number.isFinite(num) && num >= 1 && num <= options.length) {
54
+ return options[num - 1] as string;
55
+ }
56
+ if (options.includes(trimmed)) return trimmed;
57
+ return defaultValue;
58
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Shared helper that loads `graphorin.config`, parses it through
3
+ * `parseServerConfig(...)`, opens the SQLite store, optionally resolves
4
+ * the server pepper, and returns a disposable handle for any Phase 15
5
+ * subcommand that needs a live storage / auth context.
6
+ *
7
+ * Centralising the resource lifecycle here means every command (token,
8
+ * audit, secrets, memory, triggers, storage, …) acquires + releases
9
+ * the same way; tests only stub the loader once.
10
+ *
11
+ * @internal
12
+ */
13
+
14
+ import { resolveSecret, type SecretValue } from '@graphorin/security';
15
+ import { parseServerConfig, type ServerConfigSpec } from '@graphorin/server';
16
+ import {
17
+ type CreateSqliteStoreOptions,
18
+ createSqliteStore,
19
+ type GraphorinSqliteStore,
20
+ pendingMigrations,
21
+ } from '@graphorin/store-sqlite';
22
+
23
+ import { loadConfig } from './load-config.js';
24
+
25
+ /**
26
+ * @internal
27
+ */
28
+ export interface OpenStoreContextOptions {
29
+ /** Path to `graphorin.config.{ts,js,mjs,json}`. */
30
+ readonly config?: string;
31
+ /**
32
+ * When `true`, refuse to continue when `auth.pepperRef` cannot be
33
+ * resolved. Default `false` - callers that do not need the pepper
34
+ * (e.g. `graphorin audit verify`) skip resolution entirely.
35
+ */
36
+ readonly requirePepper?: boolean;
37
+ /**
38
+ * Override the store factory - tests inject a fake store so they
39
+ * can run subcommand integration without touching a real DB.
40
+ */
41
+ readonly storeFactory?: (options: CreateSqliteStoreOptions) => Promise<GraphorinSqliteStore>;
42
+ /**
43
+ * Skip migrations. Defaults to `false` so subcommands always operate
44
+ * against an initialized schema.
45
+ */
46
+ readonly skipInit?: boolean;
47
+ /**
48
+ * W-068: what to do about pending schema migrations.
49
+ *
50
+ * - `'apply'` (default, the historical behaviour): run `store.init()`,
51
+ * which applies every pending migration.
52
+ * - `'check'`: do NOT migrate. Read-only commands use this so a newer
53
+ * CLI never silently upgrades the schema of a database a running
54
+ * (older) server owns; when the schema is behind, the command fails
55
+ * with an actionable message instead.
56
+ *
57
+ * Ignored when `skipInit` is `true`.
58
+ */
59
+ readonly migrationPolicy?: 'apply' | 'check';
60
+ }
61
+
62
+ /**
63
+ * @internal
64
+ */
65
+ export interface StoreContext {
66
+ readonly configPath: string;
67
+ readonly config: ServerConfigSpec;
68
+ readonly store: GraphorinSqliteStore;
69
+ readonly pepper?: SecretValue;
70
+ /** Releases the underlying SQLite connection. */
71
+ close(): Promise<void>;
72
+ }
73
+
74
+ /**
75
+ * Open the storage context for the provided config path. Throws on
76
+ * Zod validation errors and on `requirePepper && !pepperRef`.
77
+ *
78
+ * @internal
79
+ */
80
+ export async function openStoreContext(
81
+ options: OpenStoreContextOptions = {},
82
+ ): Promise<StoreContext> {
83
+ const loaded = await loadConfig(options.config);
84
+ const config = parseServerConfig(loaded.config);
85
+ const factory = options.storeFactory ?? createSqliteStore;
86
+ const storeOpts: CreateSqliteStoreOptions = {
87
+ path: config.storage.path,
88
+ mode: config.storage.mode,
89
+ };
90
+ // IP-1: the CLI honours the same encryption config as the server, so
91
+ // `graphorin` commands can open a database produced by
92
+ // `graphorin storage encrypt`.
93
+ if (config.storage.encryption.enabled) {
94
+ if (config.storage.encryption.passphraseRef === undefined) {
95
+ throw new Error(
96
+ '[graphorin/cli] storage.encryption.enabled is true but no passphraseRef is configured.',
97
+ );
98
+ }
99
+ const { resolveSecret } = await import('@graphorin/security/secrets');
100
+ const passphrase = await resolveSecret(config.storage.encryption.passphraseRef);
101
+ (storeOpts as { encryption?: unknown }).encryption = {
102
+ enabled: true,
103
+ ...(config.storage.encryption.cipher !== undefined
104
+ ? { cipher: config.storage.encryption.cipher }
105
+ : {}),
106
+ passphraseResolver: async () => passphrase.use((v: string) => v),
107
+ };
108
+ }
109
+ const store = await factory(storeOpts);
110
+ if (options.skipInit !== true) {
111
+ if (options.migrationPolicy === 'check') {
112
+ // Read-only commands must not auto-migrate a live server's
113
+ // database (W-068). Compare the applied set against this build's
114
+ // bundle; refuse with a recipe when the schema is behind. The
115
+ // helper reads sqlite_master first, so a foreign database is not
116
+ // marked by creating schema_migrations.
117
+ let pendingCount = 0;
118
+ try {
119
+ pendingCount = pendingMigrations(store.connection).length;
120
+ } catch {
121
+ // A store without a raw connection (test fakes) has nothing to
122
+ // check - treat as up to date.
123
+ pendingCount = 0;
124
+ }
125
+ if (pendingCount > 0) {
126
+ await store.close();
127
+ throw new Error(
128
+ `[graphorin/cli] the database schema is ${pendingCount} migration(s) behind this CLI. ` +
129
+ 'This is a read-only command and will not upgrade a live database. ' +
130
+ "Run 'graphorin migrate' (with the server stopped) or use a CLI version matching the server.",
131
+ );
132
+ }
133
+ } else {
134
+ await store.init();
135
+ }
136
+ }
137
+
138
+ let pepper: SecretValue | undefined;
139
+ if (options.requirePepper === true) {
140
+ if (config.auth.kind !== 'token' || config.auth.pepperRef === undefined) {
141
+ await store.close();
142
+ throw new Error(
143
+ `[graphorin/cli] this command requires auth.kind: 'token' + auth.pepperRef in '${loaded.path}'.`,
144
+ );
145
+ }
146
+ try {
147
+ pepper = await resolveSecret(config.auth.pepperRef);
148
+ } catch (err) {
149
+ await store.close();
150
+ throw new Error(
151
+ `[graphorin/cli] failed to resolve auth.pepperRef '${config.auth.pepperRef}': ${(err as Error).message}`,
152
+ { cause: err },
153
+ );
154
+ }
155
+ }
156
+
157
+ const ctx: StoreContext = Object.freeze({
158
+ configPath: loaded.path,
159
+ config,
160
+ store,
161
+ ...(pepper !== undefined ? { pepper } : {}),
162
+ close: () => store.close(),
163
+ });
164
+ return ctx;
165
+ }