@productbrain/cli 0.1.0-beta.30 → 0.1.0-beta.34

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 (214) hide show
  1. package/README.md +62 -178
  2. package/dist/__tests__/capture.test.js +2 -11
  3. package/dist/__tests__/capture.test.js.map +1 -1
  4. package/dist/__tests__/config.test.d.ts +8 -0
  5. package/dist/__tests__/config.test.d.ts.map +1 -0
  6. package/dist/__tests__/config.test.js +166 -0
  7. package/dist/__tests__/config.test.js.map +1 -0
  8. package/dist/__tests__/constants.test.js +1 -1
  9. package/dist/__tests__/constants.test.js.map +1 -1
  10. package/dist/__tests__/constellation.test.js +2 -8
  11. package/dist/__tests__/constellation.test.js.map +1 -1
  12. package/dist/__tests__/errors.test.d.ts +2 -0
  13. package/dist/__tests__/errors.test.d.ts.map +1 -0
  14. package/dist/__tests__/errors.test.js +117 -0
  15. package/dist/__tests__/errors.test.js.map +1 -0
  16. package/dist/__tests__/fields.test.js +14 -14
  17. package/dist/__tests__/fields.test.js.map +1 -1
  18. package/dist/__tests__/glossary.test.d.ts +2 -0
  19. package/dist/__tests__/glossary.test.d.ts.map +1 -0
  20. package/dist/__tests__/glossary.test.js +32 -0
  21. package/dist/__tests__/glossary.test.js.map +1 -0
  22. package/dist/__tests__/handshake.test.js +9 -17
  23. package/dist/__tests__/handshake.test.js.map +1 -1
  24. package/dist/__tests__/init.test.d.ts +7 -0
  25. package/dist/__tests__/init.test.d.ts.map +1 -0
  26. package/dist/__tests__/init.test.js +146 -0
  27. package/dist/__tests__/init.test.js.map +1 -0
  28. package/dist/__tests__/login.test.js +29 -30
  29. package/dist/__tests__/login.test.js.map +1 -1
  30. package/dist/__tests__/onboarding.test.d.ts +6 -0
  31. package/dist/__tests__/onboarding.test.d.ts.map +1 -0
  32. package/dist/__tests__/onboarding.test.js +199 -0
  33. package/dist/__tests__/onboarding.test.js.map +1 -0
  34. package/dist/__tests__/profiles.test.d.ts +2 -0
  35. package/dist/__tests__/profiles.test.d.ts.map +1 -0
  36. package/dist/__tests__/profiles.test.js +168 -0
  37. package/dist/__tests__/profiles.test.js.map +1 -0
  38. package/dist/__tests__/promote.test.js +4 -16
  39. package/dist/__tests__/promote.test.js.map +1 -1
  40. package/dist/__tests__/prompts.test.d.ts +6 -0
  41. package/dist/__tests__/prompts.test.d.ts.map +1 -0
  42. package/dist/__tests__/prompts.test.js +146 -0
  43. package/dist/__tests__/prompts.test.js.map +1 -0
  44. package/dist/__tests__/proposals.test.js +6 -29
  45. package/dist/__tests__/proposals.test.js.map +1 -1
  46. package/dist/__tests__/relate.test.js +6 -23
  47. package/dist/__tests__/relate.test.js.map +1 -1
  48. package/dist/__tests__/runner.test.js +19 -15
  49. package/dist/__tests__/runner.test.js.map +1 -1
  50. package/dist/__tests__/session.test.js +2 -8
  51. package/dist/__tests__/session.test.js.map +1 -1
  52. package/dist/__tests__/setup.test.js +39 -25
  53. package/dist/__tests__/setup.test.js.map +1 -1
  54. package/dist/__tests__/spinner-labels.test.d.ts +2 -0
  55. package/dist/__tests__/spinner-labels.test.d.ts.map +1 -0
  56. package/dist/__tests__/spinner-labels.test.js +23 -0
  57. package/dist/__tests__/spinner-labels.test.js.map +1 -0
  58. package/dist/__tests__/update.test.js +27 -61
  59. package/dist/__tests__/update.test.js.map +1 -1
  60. package/dist/__tests__/workspace.test.d.ts +2 -0
  61. package/dist/__tests__/workspace.test.d.ts.map +1 -0
  62. package/dist/__tests__/workspace.test.js +308 -0
  63. package/dist/__tests__/workspace.test.js.map +1 -0
  64. package/dist/commands/accept.d.ts.map +1 -1
  65. package/dist/commands/accept.js +6 -2
  66. package/dist/commands/accept.js.map +1 -1
  67. package/dist/commands/brief.d.ts.map +1 -1
  68. package/dist/commands/brief.js +6 -1
  69. package/dist/commands/brief.js.map +1 -1
  70. package/dist/commands/capture.d.ts.map +1 -1
  71. package/dist/commands/capture.js +20 -12
  72. package/dist/commands/capture.js.map +1 -1
  73. package/dist/commands/chain-walk.d.ts.map +1 -1
  74. package/dist/commands/chain-walk.js +6 -1
  75. package/dist/commands/chain-walk.js.map +1 -1
  76. package/dist/commands/changes.d.ts.map +1 -1
  77. package/dist/commands/changes.js +6 -1
  78. package/dist/commands/changes.js.map +1 -1
  79. package/dist/commands/codex-prep.d.ts.map +1 -1
  80. package/dist/commands/codex-prep.js +6 -2
  81. package/dist/commands/codex-prep.js.map +1 -1
  82. package/dist/commands/collections.d.ts.map +1 -1
  83. package/dist/commands/collections.js +6 -2
  84. package/dist/commands/collections.js.map +1 -1
  85. package/dist/commands/constellation.d.ts.map +1 -1
  86. package/dist/commands/constellation.js +6 -1
  87. package/dist/commands/constellation.js.map +1 -1
  88. package/dist/commands/context.d.ts.map +1 -1
  89. package/dist/commands/context.js +6 -1
  90. package/dist/commands/context.js.map +1 -1
  91. package/dist/commands/doctor.d.ts +9 -2
  92. package/dist/commands/doctor.d.ts.map +1 -1
  93. package/dist/commands/doctor.js +125 -28
  94. package/dist/commands/doctor.js.map +1 -1
  95. package/dist/commands/doctor.test.d.ts +3 -1
  96. package/dist/commands/doctor.test.d.ts.map +1 -1
  97. package/dist/commands/doctor.test.js +206 -10
  98. package/dist/commands/doctor.test.js.map +1 -1
  99. package/dist/commands/fields.d.ts.map +1 -1
  100. package/dist/commands/fields.js +6 -2
  101. package/dist/commands/fields.js.map +1 -1
  102. package/dist/commands/get.d.ts.map +1 -1
  103. package/dist/commands/get.js +11 -3
  104. package/dist/commands/get.js.map +1 -1
  105. package/dist/commands/handshake.d.ts.map +1 -1
  106. package/dist/commands/handshake.js +21 -22
  107. package/dist/commands/handshake.js.map +1 -1
  108. package/dist/commands/ingest.d.ts.map +1 -1
  109. package/dist/commands/ingest.js +11 -3
  110. package/dist/commands/ingest.js.map +1 -1
  111. package/dist/commands/init.d.ts +14 -0
  112. package/dist/commands/init.d.ts.map +1 -0
  113. package/dist/commands/init.js +117 -0
  114. package/dist/commands/init.js.map +1 -0
  115. package/dist/commands/login.d.ts.map +1 -1
  116. package/dist/commands/login.js +32 -20
  117. package/dist/commands/login.js.map +1 -1
  118. package/dist/commands/profile.d.ts +24 -0
  119. package/dist/commands/profile.d.ts.map +1 -0
  120. package/dist/commands/profile.js +82 -0
  121. package/dist/commands/profile.js.map +1 -0
  122. package/dist/commands/promote.d.ts.map +1 -1
  123. package/dist/commands/promote.js +22 -7
  124. package/dist/commands/promote.js.map +1 -1
  125. package/dist/commands/reject.d.ts.map +1 -1
  126. package/dist/commands/reject.js +11 -5
  127. package/dist/commands/reject.js.map +1 -1
  128. package/dist/commands/relate.d.ts.map +1 -1
  129. package/dist/commands/relate.js +21 -10
  130. package/dist/commands/relate.js.map +1 -1
  131. package/dist/commands/session.d.ts.map +1 -1
  132. package/dist/commands/session.js +16 -6
  133. package/dist/commands/session.js.map +1 -1
  134. package/dist/commands/setup.d.ts +1 -2
  135. package/dist/commands/setup.d.ts.map +1 -1
  136. package/dist/commands/setup.js +17 -38
  137. package/dist/commands/setup.js.map +1 -1
  138. package/dist/commands/update.d.ts.map +1 -1
  139. package/dist/commands/update.js +45 -27
  140. package/dist/commands/update.js.map +1 -1
  141. package/dist/commands/verify.d.ts.map +1 -1
  142. package/dist/commands/verify.js +11 -5
  143. package/dist/commands/verify.js.map +1 -1
  144. package/dist/commands/workspace.d.ts +41 -0
  145. package/dist/commands/workspace.d.ts.map +1 -0
  146. package/dist/commands/workspace.js +239 -0
  147. package/dist/commands/workspace.js.map +1 -0
  148. package/dist/formatters/promote.d.ts +1 -0
  149. package/dist/formatters/promote.d.ts.map +1 -1
  150. package/dist/formatters/promote.js +1 -0
  151. package/dist/formatters/promote.js.map +1 -1
  152. package/dist/index.js +328 -299
  153. package/dist/index.js.map +1 -1
  154. package/dist/lib/client.d.ts +4 -2
  155. package/dist/lib/client.d.ts.map +1 -1
  156. package/dist/lib/client.js +143 -68
  157. package/dist/lib/client.js.map +1 -1
  158. package/dist/lib/config.d.ts +63 -4
  159. package/dist/lib/config.d.ts.map +1 -1
  160. package/dist/lib/config.js +230 -36
  161. package/dist/lib/config.js.map +1 -1
  162. package/dist/lib/constants.d.ts +1 -1
  163. package/dist/lib/constants.d.ts.map +1 -1
  164. package/dist/lib/constants.js +1 -1
  165. package/dist/lib/constants.js.map +1 -1
  166. package/dist/lib/errors.d.ts +58 -0
  167. package/dist/lib/errors.d.ts.map +1 -0
  168. package/dist/lib/errors.js +67 -0
  169. package/dist/lib/errors.js.map +1 -0
  170. package/dist/lib/format.d.ts +10 -0
  171. package/dist/lib/format.d.ts.map +1 -0
  172. package/dist/lib/format.js +27 -0
  173. package/dist/lib/format.js.map +1 -0
  174. package/dist/lib/glossary.d.ts +19 -0
  175. package/dist/lib/glossary.d.ts.map +1 -0
  176. package/dist/lib/glossary.js +53 -0
  177. package/dist/lib/glossary.js.map +1 -0
  178. package/dist/lib/onboarding.d.ts +19 -0
  179. package/dist/lib/onboarding.d.ts.map +1 -0
  180. package/dist/lib/onboarding.js +373 -0
  181. package/dist/lib/onboarding.js.map +1 -0
  182. package/dist/lib/profiles.d.ts +34 -0
  183. package/dist/lib/profiles.d.ts.map +1 -0
  184. package/dist/lib/profiles.js +173 -0
  185. package/dist/lib/profiles.js.map +1 -0
  186. package/dist/lib/prompts.d.ts +38 -0
  187. package/dist/lib/prompts.d.ts.map +1 -0
  188. package/dist/lib/prompts.js +90 -0
  189. package/dist/lib/prompts.js.map +1 -0
  190. package/dist/lib/runner.d.ts +2 -0
  191. package/dist/lib/runner.d.ts.map +1 -1
  192. package/dist/lib/runner.js +21 -7
  193. package/dist/lib/runner.js.map +1 -1
  194. package/dist/lib/spinner.d.ts +27 -0
  195. package/dist/lib/spinner.d.ts.map +1 -0
  196. package/dist/lib/spinner.js +76 -0
  197. package/dist/lib/spinner.js.map +1 -0
  198. package/dist/lib/spinner.test.d.ts +2 -0
  199. package/dist/lib/spinner.test.d.ts.map +1 -0
  200. package/dist/lib/spinner.test.js +39 -0
  201. package/dist/lib/spinner.test.js.map +1 -0
  202. package/dist/lib/style.d.ts +65 -0
  203. package/dist/lib/style.d.ts.map +1 -0
  204. package/dist/lib/style.js +108 -0
  205. package/dist/lib/style.js.map +1 -0
  206. package/dist/lib/style.test.d.ts +7 -0
  207. package/dist/lib/style.test.d.ts.map +1 -0
  208. package/dist/lib/style.test.js +195 -0
  209. package/dist/lib/style.test.js.map +1 -0
  210. package/dist/lib/workspace-probe.d.ts +16 -0
  211. package/dist/lib/workspace-probe.d.ts.map +1 -0
  212. package/dist/lib/workspace-probe.js +33 -0
  213. package/dist/lib/workspace-probe.js.map +1 -0
  214. package/package.json +2 -1
package/dist/index.js CHANGED
@@ -7,10 +7,11 @@
7
7
  import { readFileSync } from 'node:fs';
8
8
  import { dirname, join } from 'node:path';
9
9
  import { fileURLToPath } from 'node:url';
10
- import { Command } from 'commander';
10
+ import { Command, CommanderError } from 'commander';
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
12
  const cliPackageVersion = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
13
- import { setOutputMode } from './lib/runner.js';
13
+ import { setOutputMode, setQuietMode, isJsonMode } from './lib/runner.js';
14
+ import { CLIError, ErrorCode } from './lib/errors.js';
14
15
  import { runContext } from './commands/context.js';
15
16
  import { runGet, runGetMany } from './commands/get.js';
16
17
  import { runLogin } from './commands/login.js';
@@ -38,15 +39,73 @@ import { runBrief, runCompoundBrief, isCompoundType } from './commands/brief.js'
38
39
  import { runVerify } from './commands/verify.js';
39
40
  import { runCodexPrep } from './commands/codex-prep.js';
40
41
  import { runCollectionsList, runCollectionsGet, runCollectionsAudit, runCollectionsExport } from './commands/collections.js';
42
+ import { runWorkspaceVerify, runWorkspaceRepair, runDefinitionsDiff } from './commands/workspace.js';
41
43
  import { runDoctor } from './commands/doctor.js';
44
+ import { runProfileList, runProfileCreate, runProfileUse, runProfileDelete } from './commands/profile.js';
45
+ import { runInit } from './commands/init.js';
46
+ import { GLOSSARY, formatGlossary } from './lib/glossary.js';
47
+ import { hint, heading, bold, dim, green, icons } from './lib/style.js';
48
+ import { readSession } from './lib/session.js';
49
+ import { formatElapsed } from './lib/format.js';
50
+ import { probeWorkspace } from './lib/workspace-probe.js';
42
51
  const program = new Command();
52
+ /**
53
+ * Global error handler — single exit point for all CLI errors.
54
+ * CLIError: format with code + category + guidance.
55
+ * CommanderError: re-throw for --help / --version (exitCode 0), format others.
56
+ * Regular Error: wrap as INTERNAL.
57
+ */
58
+ function handleError(err) {
59
+ // Commander help/version exits with code 0 — let them through
60
+ if (err instanceof CommanderError && err.exitCode === 0) {
61
+ process.exit(0);
62
+ }
63
+ const json = isJsonMode();
64
+ if (err instanceof CLIError) {
65
+ if (json) {
66
+ process.stderr.write(JSON.stringify(err.toJSON()) + '\n');
67
+ }
68
+ else {
69
+ process.stderr.write(err.message + '\n');
70
+ if (err.guidance) {
71
+ process.stderr.write(err.guidance + '\n');
72
+ }
73
+ }
74
+ process.exit(1);
75
+ }
76
+ // CommanderError (bad usage, missing args, etc.)
77
+ if (err instanceof CommanderError) {
78
+ const cliErr = new CLIError(err.message, {
79
+ code: ErrorCode.VALIDATION_FAILED,
80
+ category: 'validation',
81
+ });
82
+ if (json) {
83
+ process.stderr.write(JSON.stringify(cliErr.toJSON()) + '\n');
84
+ }
85
+ else {
86
+ process.stderr.write(err.message + '\n');
87
+ }
88
+ process.exit(err.exitCode);
89
+ }
90
+ // Unknown / unstructured errors
91
+ const message = err instanceof Error ? err.message : String(err);
92
+ if (json) {
93
+ process.stderr.write(JSON.stringify({ error: message, code: 'INTERNAL', category: 'internal' }) + '\n');
94
+ }
95
+ else {
96
+ process.stderr.write(message + '\n');
97
+ }
98
+ process.exit(1);
99
+ }
43
100
  program
44
101
  .name('pb')
45
102
  .description('Product Brain — Chain knowledge + write-back CLI')
46
103
  .version(cliPackageVersion.version)
47
104
  // Global output mode flags (DEC-299, BET-181)
48
105
  .option('--json', 'Output machine-readable JSON (overrides TTY auto-detection)')
49
- .option('--pretty', 'Force human-readable output even when piped');
106
+ .option('--pretty', 'Force human-readable output even when piped')
107
+ .option('-q, --quiet', 'Suppress non-essential output (banners, hints, progress)')
108
+ .exitOverride(); // Throw CommanderError instead of calling process.exit directly
50
109
  // Apply global output mode BEFORE subcommand actions run
51
110
  program.hook('preAction', (thisCommand) => {
52
111
  const globalOpts = program.opts();
@@ -57,8 +116,94 @@ program.hook('preAction', (thisCommand) => {
57
116
  setOutputMode('pretty');
58
117
  }
59
118
  // else: 'auto' (default) — TTY detection handles it
119
+ if (globalOpts.quiet) {
120
+ setQuietMode(true);
121
+ }
60
122
  void thisCommand;
61
123
  });
124
+ // Default action — status dashboard when `pb` is invoked with no command.
125
+ // `pb --help` still works because Commander intercepts --help before the action runs.
126
+ program.action(async () => {
127
+ // Check if we have a valid API key (sync, no network)
128
+ let hasKey = false;
129
+ let config = null;
130
+ try {
131
+ const { getConfig: gc } = await import('./lib/config.js');
132
+ config = gc();
133
+ hasKey = true;
134
+ }
135
+ catch {
136
+ // No valid key
137
+ }
138
+ // JSON mode: structured status
139
+ if (isJsonMode()) {
140
+ if (!hasKey) {
141
+ process.stdout.write(JSON.stringify({
142
+ configured: false,
143
+ hint: 'Run pb login to configure your API key.',
144
+ }) + '\n');
145
+ return;
146
+ }
147
+ const session = readSession();
148
+ const wsInfo = await probeWorkspace();
149
+ process.stdout.write(JSON.stringify({
150
+ configured: true,
151
+ workspace: wsInfo?.name ?? null,
152
+ session: session ? { id: session.sessionId, startedAt: session.startedAt } : null,
153
+ entryCount: wsInfo?.entryCount ?? null,
154
+ hint: 'Run pb --help for available commands.',
155
+ }) + '\n');
156
+ return;
157
+ }
158
+ // Human mode — not configured
159
+ if (!hasKey) {
160
+ process.stdout.write('\n');
161
+ process.stdout.write(`${bold('Product Brain')} ${dim('— not connected')}\n\n`);
162
+ process.stdout.write(`${heading('Get started')}\n\n`);
163
+ hint('1. pb login Save your API key');
164
+ hint('2. pb setup Connect a workspace');
165
+ hint('3. pb orient -b See what\'s happening');
166
+ process.stdout.write('\n');
167
+ hint('pb --help Show all commands');
168
+ process.stdout.write('\n');
169
+ return;
170
+ }
171
+ // Human mode — configured, try workspace probe
172
+ const session = readSession();
173
+ const wsInfo = await probeWorkspace();
174
+ const workspaceName = wsInfo?.name ?? null;
175
+ const entryCount = wsInfo?.entryCount ?? null;
176
+ const offline = !wsInfo;
177
+ process.stdout.write('\n');
178
+ if (offline) {
179
+ process.stdout.write(`${bold('Product Brain')} ${dim('— connected (offline)')}\n\n`);
180
+ process.stdout.write(` ${green(icons.pass)} Key: configured\n`);
181
+ process.stdout.write(` ${dim('Server: unreachable — check your connection')}\n\n`);
182
+ hint('pb doctor Diagnose connection issues');
183
+ hint('pb --help Show all commands');
184
+ process.stdout.write('\n');
185
+ return;
186
+ }
187
+ // Online with workspace
188
+ const title = workspaceName
189
+ ? `${bold('Product Brain')} ${dim('—')} ${bold(workspaceName)}`
190
+ : `${bold('Product Brain')}`;
191
+ process.stdout.write(`${title}\n\n`);
192
+ if (session) {
193
+ const elapsed = formatElapsed(session.startedAt);
194
+ process.stdout.write(` Session: active (started ${elapsed})\n`);
195
+ }
196
+ if (entryCount !== null && entryCount > 0) {
197
+ process.stdout.write(` Entries: ${entryCount}\n`);
198
+ }
199
+ process.stdout.write('\n');
200
+ if (!session) {
201
+ hint('pb session start Start a write session');
202
+ }
203
+ hint('pb orient -b Workspace overview');
204
+ hint('pb --help Show all commands');
205
+ process.stdout.write('\n');
206
+ });
62
207
  program
63
208
  .command('get <entry-id>')
64
209
  .description('Display full entry by ID (data, relations, last 10 history events)')
@@ -67,25 +212,13 @@ program
67
212
  program.commands.find((c) => c.name() === 'get')?.help();
68
213
  return;
69
214
  }
70
- try {
71
- await runGet({ entryId: entryId.trim() });
72
- }
73
- catch (err) {
74
- console.error(err instanceof Error ? err.message : String(err));
75
- process.exit(1);
76
- }
215
+ await runGet({ entryId: entryId.trim() });
77
216
  });
78
217
  program
79
218
  .command('get-many <entry-ids...>')
80
219
  .description('Fetch multiple entries in parallel (space-separated IDs)')
81
220
  .action(async (entryIds) => {
82
- try {
83
- await runGetMany({ entryIds: entryIds.map((id) => id.trim()).filter(Boolean) });
84
- }
85
- catch (err) {
86
- console.error(err instanceof Error ? err.message : String(err));
87
- process.exit(1);
88
- }
221
+ await runGetMany({ entryIds: entryIds.map((id) => id.trim()).filter(Boolean) });
89
222
  });
90
223
  program
91
224
  .command('context <entry-id>')
@@ -95,26 +228,14 @@ program
95
228
  program.commands.find((c) => c.name() === 'context')?.help();
96
229
  return;
97
230
  }
98
- try {
99
- await runContext({ entryId: entryId.trim() });
100
- }
101
- catch (err) {
102
- console.error(err instanceof Error ? err.message : String(err));
103
- process.exit(1);
104
- }
231
+ await runContext({ entryId: entryId.trim() });
105
232
  });
106
233
  program
107
234
  .command('changes')
108
235
  .description('Detect entries modified and relations created since a given time (BET-239)')
109
236
  .requiredOption('--since <duration>', 'Time window: 1h, 6h, 1d, 7d, 30d')
110
237
  .action(async (opts) => {
111
- try {
112
- await runChanges({ since: opts.since });
113
- }
114
- catch (err) {
115
- console.error(err instanceof Error ? err.message : String(err));
116
- process.exit(1);
117
- }
238
+ await runChanges({ since: opts.since });
118
239
  });
119
240
  program
120
241
  .command('walk <entry-id>')
@@ -123,36 +244,26 @@ program
123
244
  .option('--direction <dir>', 'Traversal direction: outgoing or incoming (default outgoing)', 'outgoing')
124
245
  .option('-t, --type <relation-type>', 'Filter by relation type (e.g. depends_on, informs, governs)')
125
246
  .action(async (entryId, opts) => {
126
- try {
127
- const depth = parseInt(opts.depth, 10);
128
- if (isNaN(depth) || depth < 1 || depth > 4) {
129
- console.error('--depth must be between 1 and 4.');
130
- process.exit(1);
131
- }
132
- await runChainWalk({
133
- entryId: entryId.trim(),
134
- depth,
135
- direction: opts.direction,
136
- type: opts.type,
247
+ const depth = parseInt(opts.depth, 10);
248
+ if (isNaN(depth) || depth < 1 || depth > 4) {
249
+ throw new CLIError('--depth must be between 1 and 4.', {
250
+ code: ErrorCode.VALIDATION_FAILED,
251
+ category: 'validation',
137
252
  });
138
253
  }
139
- catch (err) {
140
- console.error(err instanceof Error ? err.message : String(err));
141
- process.exit(1);
142
- }
254
+ await runChainWalk({
255
+ entryId: entryId.trim(),
256
+ depth,
257
+ direction: opts.direction,
258
+ type: opts.type,
259
+ });
143
260
  });
144
261
  program
145
262
  .command('cross-cut')
146
263
  .description('Structural aggregation — all relations of a given type grouped by source collection (BET-239)')
147
264
  .requiredOption('--type <relation-type>', 'Relation type to scan (e.g. part_of, informs, governs)')
148
265
  .action(async (opts) => {
149
- try {
150
- await runCrossCut({ type: opts.type });
151
- }
152
- catch (err) {
153
- console.error(err instanceof Error ? err.message : String(err));
154
- process.exit(1);
155
- }
266
+ await runCrossCut({ type: opts.type });
156
267
  });
157
268
  program
158
269
  .command('brief [type]')
@@ -165,27 +276,20 @@ program
165
276
  .option('--since-last', 'Compare against last brief run (incremental mode)')
166
277
  .option('--since <timestamp>', 'ISO 8601 timestamp for delta type (e.g. 2026-03-24T00:00:00Z)')
167
278
  .action(async (type, opts) => {
168
- try {
169
- // If positional arg is a compound type, route to compound brief
170
- if (type && isCompoundType(type)) {
171
- await runCompoundBrief({ type, since: opts.since });
172
- return;
173
- }
174
- // Otherwise, require --skill for incremental brief
175
- if (!opts.skill) {
176
- console.error('Usage:\n' +
177
- ' pb brief --skill <name> Incremental delta\n' +
178
- ' pb brief steering Compound steering brief\n' +
179
- ' pb brief confidence Compound confidence pass\n' +
180
- ' pb brief delta Compound delta sync');
181
- process.exit(1);
182
- }
183
- await runBrief({ skill: opts.skill, sinceLast: opts.sinceLast });
279
+ // If positional arg is a compound type, route to compound brief
280
+ if (type && isCompoundType(type)) {
281
+ await runCompoundBrief({ type, since: opts.since });
282
+ return;
184
283
  }
185
- catch (err) {
186
- console.error(err instanceof Error ? err.message : String(err));
187
- process.exit(1);
284
+ // Otherwise, require --skill for incremental brief
285
+ if (!opts.skill) {
286
+ throw new CLIError('Usage:\n' +
287
+ ' pb brief --skill <name> Incremental delta\n' +
288
+ ' pb brief steering Compound steering brief\n' +
289
+ ' pb brief confidence Compound confidence pass\n' +
290
+ ' pb brief delta Compound delta sync', { code: ErrorCode.VALIDATION_FAILED, category: 'validation' });
188
291
  }
292
+ await runBrief({ skill: opts.skill, sinceLast: opts.sinceLast });
189
293
  });
190
294
  program
191
295
  .command('search [query...]')
@@ -196,13 +300,7 @@ program
196
300
  program.commands.find((c) => c.name() === 'search')?.help();
197
301
  return;
198
302
  }
199
- try {
200
- await runSearch({ query });
201
- }
202
- catch (err) {
203
- console.error(err instanceof Error ? err.message : String(err));
204
- process.exit(1);
205
- }
303
+ await runSearch({ query });
206
304
  });
207
305
  program
208
306
  .command('orient')
@@ -212,13 +310,7 @@ program
212
310
  // SYNC: domain list must match TaskDomain in convex/mcpKnowledge/startupResolver.ts
213
311
  .option('-s, --scope <domain>', 'Domain scope for governance filtering. Valid values: auth, governance, architecture, product, data-foundation, chainwork, capture-pipeline, ingestion, intelligence-and-operations, review-and-learning, general')
214
312
  .action(async (opts) => {
215
- try {
216
- await runOrient({ brief: opts.brief, task: opts.task?.trim() || undefined, scope: opts.scope?.trim() || undefined });
217
- }
218
- catch (err) {
219
- console.error(err instanceof Error ? err.message : String(err));
220
- process.exit(1);
221
- }
313
+ await runOrient({ brief: opts.brief, task: opts.task?.trim() || undefined, scope: opts.scope?.trim() || undefined });
222
314
  });
223
315
  program
224
316
  .command('handshake')
@@ -229,17 +321,11 @@ program
229
321
  .option('--level <level>', 'With --init: trust level (guide|work|silent|full-trust). Without --init: content tier (beginner|intermediate|expert)')
230
322
  .option('--generate', 'Fetch governance entries from the Chain and merge generated rules (BET-286)')
231
323
  .action(async (opts) => {
232
- try {
233
- if (opts.init) {
234
- await runHandshakeInit({ level: opts.level, dryRun: opts.dryRun });
235
- return;
236
- }
237
- await runHandshake({ force: opts.force, dryRun: opts.dryRun, level: opts.level, generate: opts.generate });
238
- }
239
- catch (err) {
240
- console.error(err instanceof Error ? err.message : String(err));
241
- process.exit(1);
324
+ if (opts.init) {
325
+ await runHandshakeInit({ level: opts.level, dryRun: opts.dryRun });
326
+ return;
242
327
  }
328
+ await runHandshake({ force: opts.force, dryRun: opts.dryRun, level: opts.level, generate: opts.generate });
243
329
  });
244
330
  program
245
331
  .command('codex-prep <task...>')
@@ -251,49 +337,33 @@ program
251
337
  program.commands.find((c) => c.name() === 'codex-prep')?.help();
252
338
  return;
253
339
  }
254
- try {
255
- await runCodexPrep({ task, dryRun: opts.dryRun });
256
- }
257
- catch (err) {
258
- console.error(err instanceof Error ? err.message : String(err));
259
- process.exit(1);
260
- }
340
+ await runCodexPrep({ task, dryRun: opts.dryRun });
341
+ });
342
+ program
343
+ .command('init')
344
+ .description('Detect setup state and guide you through configuration (WP-303)')
345
+ .action(async () => {
346
+ await runInit();
261
347
  });
262
348
  program
263
349
  .command('login')
264
350
  .description('Save your API key to ~/.config/productbrain/.env (works from any directory)')
265
351
  .action(async () => {
266
- try {
267
- await runLogin();
268
- }
269
- catch (err) {
270
- console.error(err instanceof Error ? err.message : String(err));
271
- process.exit(1);
272
- }
352
+ await runLogin();
273
353
  });
274
354
  program
275
355
  .command('doctor')
276
356
  .description('Check CLI configuration and connectivity')
277
- .action(async () => {
278
- try {
279
- await runDoctor();
280
- }
281
- catch (err) {
282
- console.error(err instanceof Error ? err.message : String(err));
283
- process.exit(1);
284
- }
357
+ .option('--fix', 'Auto-repair common configuration issues')
358
+ .option('--dry-run', 'Preview what --fix would do without changing anything')
359
+ .action(async (opts) => {
360
+ await runDoctor({ fix: opts.fix, dryRun: opts.dryRun });
285
361
  });
286
362
  program
287
363
  .command('setup')
288
364
  .description('Guided first-time setup: account, login, workspace, first capture')
289
365
  .action(async () => {
290
- try {
291
- await runSetup();
292
- }
293
- catch (err) {
294
- console.error(err instanceof Error ? err.message : String(err));
295
- process.exit(1);
296
- }
366
+ await runSetup();
297
367
  });
298
368
  // --- Write commands (require active session) ---
299
369
  const sessionCmd = program
@@ -304,38 +374,20 @@ sessionCmd
304
374
  .description('Start a tracked write session (opens session, refreshes context)')
305
375
  .option('--json', 'Output machine-readable JSON (deprecated: use global --json flag)')
306
376
  .action(async (opts) => {
307
- try {
308
- await runSessionStart({ json: opts.json });
309
- }
310
- catch (err) {
311
- console.error(err instanceof Error ? err.message : String(err));
312
- process.exit(1);
313
- }
377
+ await runSessionStart({ json: opts.json });
314
378
  });
315
379
  sessionCmd
316
380
  .command('id')
317
381
  .description('Print current session ID to stdout (machine-readable, TEN-707)')
318
382
  .action(() => {
319
- try {
320
- runSessionId();
321
- }
322
- catch (err) {
323
- console.error(err instanceof Error ? err.message : String(err));
324
- process.exit(1);
325
- }
383
+ runSessionId();
326
384
  });
327
385
  sessionCmd
328
386
  .command('close')
329
387
  .description('Close the active session (wrapup, refresh context)')
330
388
  .option('--force', 'Clear local session state even if server close fails')
331
389
  .action(async (opts) => {
332
- try {
333
- await runSessionClose({ force: opts.force });
334
- }
335
- catch (err) {
336
- console.error(err instanceof Error ? err.message : String(err));
337
- process.exit(1);
338
- }
390
+ await runSessionClose({ force: opts.force });
339
391
  });
340
392
  program
341
393
  .command('capture [text...]')
@@ -354,23 +406,17 @@ program
354
406
  program.commands.find((c) => c.name() === 'capture')?.help();
355
407
  return;
356
408
  }
357
- try {
358
- await runCapture({
359
- text: text || opts.name || '',
360
- name: opts.name,
361
- description: opts.description,
362
- collection: opts.collection,
363
- link: opts.link,
364
- type: opts.type,
365
- sourceRef: opts.sourceRef,
366
- sourceExcerpt: opts.sourceExcerpt,
367
- json: opts.json,
368
- });
369
- }
370
- catch (err) {
371
- console.error(err instanceof Error ? err.message : String(err));
372
- process.exit(1);
373
- }
409
+ await runCapture({
410
+ text: text || opts.name || '',
411
+ name: opts.name,
412
+ description: opts.description,
413
+ collection: opts.collection,
414
+ link: opts.link,
415
+ type: opts.type,
416
+ sourceRef: opts.sourceRef,
417
+ sourceExcerpt: opts.sourceExcerpt,
418
+ json: opts.json,
419
+ });
374
420
  });
375
421
  // --- Update command (TEN-703) ---
376
422
  program
@@ -382,20 +428,14 @@ program
382
428
  .option('--workflow-status <status>', 'Set workflow status (server-validated)')
383
429
  .option('--note <text>', 'Change note for history')
384
430
  .action(async (entryId, opts) => {
385
- try {
386
- await runUpdate({
387
- entryId,
388
- field: opts.field,
389
- name: opts.name,
390
- status: opts.status,
391
- workflowStatus: opts.workflowStatus,
392
- note: opts.note,
393
- });
394
- }
395
- catch (err) {
396
- console.error(err instanceof Error ? err.message : String(err));
397
- process.exit(1);
398
- }
431
+ await runUpdate({
432
+ entryId,
433
+ field: opts.field,
434
+ name: opts.name,
435
+ status: opts.status,
436
+ workflowStatus: opts.workflowStatus,
437
+ note: opts.note,
438
+ });
399
439
  });
400
440
  // --- Promote command (Wave 2 CLI Polish) ---
401
441
  program
@@ -406,26 +446,14 @@ program
406
446
  program.commands.find((c) => c.name() === 'verify')?.help();
407
447
  return;
408
448
  }
409
- try {
410
- await runVerify({ entryId: entryId.trim() });
411
- }
412
- catch (err) {
413
- console.error(err instanceof Error ? err.message : String(err));
414
- process.exit(1);
415
- }
449
+ await runVerify({ entryId: entryId.trim() });
416
450
  });
417
451
  program
418
452
  .command('promote <entry-id>')
419
453
  .description('Promote entry from draft to active (commit to SSOT)')
420
454
  .option('-m, --message <text>', 'Commit message')
421
455
  .action(async (entryId, opts) => {
422
- try {
423
- await runPromote({ entryId, message: opts.message });
424
- }
425
- catch (err) {
426
- console.error(err instanceof Error ? err.message : String(err));
427
- process.exit(1);
428
- }
456
+ await runPromote({ entryId, message: opts.message });
429
457
  });
430
458
  // --- Relate / Unrelate commands (TEN-704) ---
431
459
  program
@@ -433,25 +461,13 @@ program
433
461
  .description('Add a typed relation between two entries (requires active session)')
434
462
  .option('--if-missing', 'Only create relation if it does not already exist')
435
463
  .action(async (fromId, type, toId, opts) => {
436
- try {
437
- await runRelate({ fromId, type, toId, ifMissing: opts.ifMissing });
438
- }
439
- catch (err) {
440
- console.error(err instanceof Error ? err.message : String(err));
441
- process.exit(1);
442
- }
464
+ await runRelate({ fromId, type, toId, ifMissing: opts.ifMissing });
443
465
  });
444
466
  program
445
467
  .command('unrelate <from-id> <type> <to-id>')
446
468
  .description('Remove a typed relation between two entries (requires active session)')
447
469
  .action(async (fromId, type, toId) => {
448
- try {
449
- await runUnrelate({ fromId, type, toId });
450
- }
451
- catch (err) {
452
- console.error(err instanceof Error ? err.message : String(err));
453
- process.exit(1);
454
- }
470
+ await runUnrelate({ fromId, type, toId });
455
471
  });
456
472
  // --- Ingest command (BET-81) ---
457
473
  program
@@ -461,23 +477,19 @@ program
461
477
  .option('--resume', 'Skip files whose sourceRef is already committed in staging.')
462
478
  .option('--concurrency <n>', 'Process up to N files in parallel (default 1, max 5).', '1')
463
479
  .action(async (pattern, opts) => {
464
- try {
465
- const concurrency = parseInt(opts.concurrency ?? '1', 10);
466
- if (Number.isNaN(concurrency) || concurrency < 1) {
467
- console.error('--concurrency must be a positive integer.');
468
- process.exit(1);
469
- }
470
- await runIngest({
471
- pattern,
472
- dryRun: opts.dryRun,
473
- resume: opts.resume,
474
- concurrency,
480
+ const concurrency = parseInt(opts.concurrency ?? '1', 10);
481
+ if (Number.isNaN(concurrency) || concurrency < 1) {
482
+ throw new CLIError('--concurrency must be a positive integer.', {
483
+ code: ErrorCode.VALIDATION_FAILED,
484
+ category: 'validation',
475
485
  });
476
486
  }
477
- catch (err) {
478
- console.error(err instanceof Error ? err.message : String(err));
479
- process.exit(1);
480
- }
487
+ await runIngest({
488
+ pattern,
489
+ dryRun: opts.dryRun,
490
+ resume: opts.resume,
491
+ concurrency,
492
+ });
481
493
  });
482
494
  // --- Fields command (BET-181 Slice 2) ---
483
495
  program
@@ -488,13 +500,7 @@ program
488
500
  program.commands.find((c) => c.name() === 'fields')?.help();
489
501
  return;
490
502
  }
491
- try {
492
- await runFields({ collectionSlug: collection.trim() });
493
- }
494
- catch (err) {
495
- console.error(err instanceof Error ? err.message : String(err));
496
- process.exit(1);
497
- }
503
+ await runFields({ collectionSlug: collection.trim() });
498
504
  });
499
505
  // --- Constellation command (BET-181 Slice 3) ---
500
506
  program
@@ -505,13 +511,7 @@ program
505
511
  program.commands.find((c) => c.name() === 'constellation')?.help();
506
512
  return;
507
513
  }
508
- try {
509
- await runConstellation({ entryId: entryId.trim() });
510
- }
511
- catch (err) {
512
- console.error(err instanceof Error ? err.message : String(err));
513
- process.exit(1);
514
- }
514
+ await runConstellation({ entryId: entryId.trim() });
515
515
  });
516
516
  // --- Audit command (BET-182 Slice 2) ---
517
517
  program
@@ -522,19 +522,13 @@ program
522
522
  .option('--fix', 'Auto-execute exact fixes via pb update, rerun once')
523
523
  .option('--verbose', 'Show all gates including PASS detail')
524
524
  .action(async (entryIds, opts) => {
525
- try {
526
- await runAudit({
527
- entryIds: entryIds.map((id) => id.trim()).filter(Boolean),
528
- phase: opts.phase,
529
- gate: opts.gate,
530
- fix: opts.fix,
531
- verbose: opts.verbose,
532
- });
533
- }
534
- catch (err) {
535
- console.error(err instanceof Error ? err.message : String(err));
536
- process.exit(1);
537
- }
525
+ await runAudit({
526
+ entryIds: entryIds.map((id) => id.trim()).filter(Boolean),
527
+ phase: opts.phase,
528
+ gate: opts.gate,
529
+ fix: opts.fix,
530
+ verbose: opts.verbose,
531
+ });
538
532
  });
539
533
  program
540
534
  .command('brand-pack')
@@ -549,13 +543,7 @@ program
549
543
  .command('proposals')
550
544
  .description('List open consent proposals with expiry countdown (BET-221)')
551
545
  .action(async () => {
552
- try {
553
- await runProposals();
554
- }
555
- catch (err) {
556
- console.error(err instanceof Error ? err.message : String(err));
557
- process.exit(1);
558
- }
546
+ await runProposals();
559
547
  });
560
548
  program
561
549
  .command('accept [proposal-id]')
@@ -563,29 +551,19 @@ program
563
551
  .option('-a, --auto', 'Auto-approve all open proposals (skip contradictions)')
564
552
  .action(async (proposalId, opts) => {
565
553
  if (!proposalId && !opts.auto) {
566
- process.stderr.write('Usage: pb accept <proposal-id> or pb accept --auto\n');
567
- process.exit(1);
568
- }
569
- try {
570
- await runAccept({ proposalId, auto: opts.auto });
571
- }
572
- catch (err) {
573
- console.error(err instanceof Error ? err.message : String(err));
574
- process.exit(1);
554
+ throw new CLIError('Usage: pb accept <proposal-id> or pb accept --auto', {
555
+ code: ErrorCode.VALIDATION_FAILED,
556
+ category: 'validation',
557
+ });
575
558
  }
559
+ await runAccept({ proposalId, auto: opts.auto });
576
560
  });
577
561
  program
578
562
  .command('reject <proposal-id>')
579
563
  .description('Reject a consent proposal with a required reason (BR-7)')
580
564
  .requiredOption('-r, --reason <text>', 'Reason for rejection (required)')
581
565
  .action(async (proposalId, opts) => {
582
- try {
583
- await runReject({ proposalId, reason: opts.reason });
584
- }
585
- catch (err) {
586
- console.error(err instanceof Error ? err.message : String(err));
587
- process.exit(1);
588
- }
566
+ await runReject({ proposalId, reason: opts.reason });
589
567
  });
590
568
  // --- Collections command (BET-280 Slice 1) ---
591
569
  const collectionsCmd = program
@@ -595,13 +573,7 @@ collectionsCmd
595
573
  .command('list')
596
574
  .description('List all collections — slug, name, field count, icon')
597
575
  .action(async () => {
598
- try {
599
- await runCollectionsList();
600
- }
601
- catch (err) {
602
- console.error(err instanceof Error ? err.message : String(err));
603
- process.exit(1);
604
- }
576
+ await runCollectionsList();
605
577
  });
606
578
  collectionsCmd
607
579
  .command('get <slug>')
@@ -611,37 +583,94 @@ collectionsCmd
611
583
  program.commands.find((c) => c.name() === 'collections')?.help();
612
584
  return;
613
585
  }
614
- try {
615
- await runCollectionsGet({ slug: slug.trim() });
616
- }
617
- catch (err) {
618
- console.error(err instanceof Error ? err.message : String(err));
619
- process.exit(1);
620
- }
586
+ await runCollectionsGet({ slug: slug.trim() });
621
587
  });
622
588
  collectionsCmd
623
589
  .command('audit')
624
590
  .description('Collection health report — classification, icon, displayHint coverage, schema gaps')
625
591
  .action(async () => {
626
- try {
627
- await runCollectionsAudit();
628
- }
629
- catch (err) {
630
- console.error(err instanceof Error ? err.message : String(err));
631
- process.exit(1);
632
- }
592
+ await runCollectionsAudit();
633
593
  });
634
594
  collectionsCmd
635
595
  .command('export')
636
596
  .description('Export all system_collection_definitions with full classification metadata (admin only)')
637
597
  .action(async () => {
638
- try {
639
- await runCollectionsExport();
640
- }
641
- catch (err) {
642
- console.error(err instanceof Error ? err.message : String(err));
643
- process.exit(1);
644
- }
598
+ await runCollectionsExport();
599
+ });
600
+ // --- Workspace operator commands (WP-305 Slice 4) ---
601
+ const workspaceCmd = program
602
+ .command('workspace')
603
+ .description('Operator commands for workspace health and repair (WP-305)');
604
+ workspaceCmd
605
+ .command('verify')
606
+ .description('Check workspace health — seeds, collections, glossary (admin only)')
607
+ .action(async () => {
608
+ await runWorkspaceVerify();
645
609
  });
646
- program.parse();
610
+ workspaceCmd
611
+ .command('repair')
612
+ .description('Backfill missing seeds for the active workspace (admin only)')
613
+ .action(async () => {
614
+ await runWorkspaceRepair();
615
+ });
616
+ // --- Definitions diff command (WP-305 Slice 4) ---
617
+ const definitionsCmd = program
618
+ .command('definitions')
619
+ .description('Inspect and compare workspace definitions (WP-305)');
620
+ definitionsCmd
621
+ .command('diff [file]')
622
+ .description('Compare local dev-system-collections.json against server definitions (admin only)')
623
+ .action(async (file) => {
624
+ await runDefinitionsDiff({ file });
625
+ });
626
+ // --- Profile commands (WP-302 Slice 2) ---
627
+ const profileCmd = program
628
+ .command('profile')
629
+ .description('Manage workspace profiles (WP-302: multi-workspace support)');
630
+ profileCmd
631
+ .command('list')
632
+ .description('List all profiles, mark active one')
633
+ .action(async () => {
634
+ await runProfileList();
635
+ });
636
+ profileCmd
637
+ .command('create <name>')
638
+ .description('Create a new profile with an API key')
639
+ .requiredOption('--api-key <key>', 'API key (pb_sk_...)')
640
+ .option('--url <url>', 'Convex site URL (defaults to production)')
641
+ .action(async (name, opts) => {
642
+ await runProfileCreate({ name, apiKey: opts.apiKey, url: opts.url });
643
+ });
644
+ profileCmd
645
+ .command('use <name>')
646
+ .description('Switch active profile (closes any active session)')
647
+ .action(async (name) => {
648
+ await runProfileUse({ name });
649
+ });
650
+ profileCmd
651
+ .command('delete <name>')
652
+ .description('Delete a profile (cannot delete active or last profile)')
653
+ .action(async (name) => {
654
+ await runProfileDelete({ name });
655
+ });
656
+ // --- Glossary command (WP-302 Slice 4) ---
657
+ program
658
+ .command('glossary')
659
+ .description('Show key Product Brain CLI terms and definitions')
660
+ .action(() => {
661
+ if (isJsonMode()) {
662
+ process.stdout.write(JSON.stringify(GLOSSARY) + '\n');
663
+ return;
664
+ }
665
+ const lines = [
666
+ '',
667
+ heading('Glossary'),
668
+ '',
669
+ formatGlossary(),
670
+ '',
671
+ ];
672
+ process.stdout.write(lines.join('\n') + '\n');
673
+ });
674
+ // Parse with global error handler — all uncaught errors route through handleError
675
+ program.parseAsync().catch(handleError);
647
676
  //# sourceMappingURL=index.js.map