@productbrain/cli 0.1.0-beta.29 → 0.1.0-beta.32

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