@formigio/fazemos-cli 0.3.0 → 0.4.1
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.
- package/dist/index.js +896 -15
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -224,6 +224,32 @@ orgs
|
|
|
224
224
|
process.exit(1);
|
|
225
225
|
}
|
|
226
226
|
});
|
|
227
|
+
orgs
|
|
228
|
+
.command('show')
|
|
229
|
+
.description('Show organization details. Defaults to the active org if no ID is provided. Use "orgs list" to find org IDs.')
|
|
230
|
+
.argument('[id]', 'Organization ID (defaults to active org)')
|
|
231
|
+
.action(async (id) => {
|
|
232
|
+
try {
|
|
233
|
+
const orgId = id || getActiveOrgId();
|
|
234
|
+
if (!orgId) {
|
|
235
|
+
console.error(chalk.red('No active org. Run: fazemos orgs switch <slug>'));
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
const data = await api('GET', `/api/organizations/${orgId}`);
|
|
239
|
+
const org = data.organization;
|
|
240
|
+
console.log(chalk.cyan(org.name));
|
|
241
|
+
console.log(` ID: ${org.id}`);
|
|
242
|
+
console.log(` Slug: ${org.slug}`);
|
|
243
|
+
if (org.description)
|
|
244
|
+
console.log(` Desc: ${org.description}`);
|
|
245
|
+
if (org.member_count != null)
|
|
246
|
+
console.log(` Members: ${org.member_count}`);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
console.error(chalk.red(err.message));
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
227
253
|
orgs
|
|
228
254
|
.command('switch')
|
|
229
255
|
.description('Switch active organization')
|
|
@@ -264,6 +290,82 @@ orgs
|
|
|
264
290
|
process.exit(1);
|
|
265
291
|
}
|
|
266
292
|
});
|
|
293
|
+
orgs
|
|
294
|
+
.command('update')
|
|
295
|
+
.description('Update the active organization. Provide at least one of --name or --description. Requires an active org (use "orgs switch" first).')
|
|
296
|
+
.option('-n, --name <name>', 'New organization name')
|
|
297
|
+
.option('-d, --description <desc>', 'New description')
|
|
298
|
+
.action(async (opts) => {
|
|
299
|
+
try {
|
|
300
|
+
if (!opts.name && opts.description == null) {
|
|
301
|
+
console.error(chalk.red('Provide --name and/or --description'));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
const orgId = getActiveOrgId();
|
|
305
|
+
if (!orgId) {
|
|
306
|
+
console.error(chalk.red('No active org. Run: fazemos orgs switch <slug>'));
|
|
307
|
+
process.exit(1);
|
|
308
|
+
}
|
|
309
|
+
const body = {};
|
|
310
|
+
if (opts.name)
|
|
311
|
+
body.name = opts.name;
|
|
312
|
+
if (opts.description != null)
|
|
313
|
+
body.description = opts.description;
|
|
314
|
+
const data = await api('PATCH', `/api/organizations/${orgId}`, body);
|
|
315
|
+
console.log(chalk.green(`Updated: ${data.organization.name}`));
|
|
316
|
+
}
|
|
317
|
+
catch (err) {
|
|
318
|
+
console.error(chalk.red(err.message));
|
|
319
|
+
process.exit(1);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
orgs
|
|
323
|
+
.command('invite')
|
|
324
|
+
.description('Invite a human member to the organization by email. The invitee receives an email to join. Use "orgs add-agent" for AI agents instead.')
|
|
325
|
+
.requiredOption('-e, --email <email>', 'Email address to invite')
|
|
326
|
+
.option('-r, --role <role>', 'Organization role: admin (full access) or member (default)', 'member')
|
|
327
|
+
.action(async (opts) => {
|
|
328
|
+
try {
|
|
329
|
+
const orgId = getActiveOrgId();
|
|
330
|
+
if (!orgId) {
|
|
331
|
+
console.error(chalk.red('No active org. Run: fazemos orgs switch <slug>'));
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
const data = await api('POST', `/api/organizations/${orgId}/members/invite`, {
|
|
335
|
+
email: opts.email,
|
|
336
|
+
role: opts.role,
|
|
337
|
+
});
|
|
338
|
+
console.log(chalk.green(`Invited ${opts.email} as ${opts.role}`));
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
console.error(chalk.red(err.message));
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
orgs
|
|
346
|
+
.command('add-agent')
|
|
347
|
+
.description('Create an AI agent member in the organization. The agent can then be assigned work via actions, commitments, or pipeline steps. Use "agents register" for bulk registration with roles and config.')
|
|
348
|
+
.requiredOption('-n, --name <name>', 'Agent display name (e.g., "kate", "marco")')
|
|
349
|
+
.option('-r, --role <role>', 'Organization role (admin, member)', 'member')
|
|
350
|
+
.action(async (opts) => {
|
|
351
|
+
try {
|
|
352
|
+
const orgId = getActiveOrgId();
|
|
353
|
+
if (!orgId) {
|
|
354
|
+
console.error(chalk.red('No active org. Run: fazemos orgs switch <slug>'));
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
const data = await api('POST', `/api/organizations/${orgId}/members`, {
|
|
358
|
+
displayName: opts.name,
|
|
359
|
+
role: opts.role,
|
|
360
|
+
});
|
|
361
|
+
console.log(chalk.green(`Created agent: ${data.member.display_name}`));
|
|
362
|
+
console.log(` ID: ${data.member.id}`);
|
|
363
|
+
}
|
|
364
|
+
catch (err) {
|
|
365
|
+
console.error(chalk.red(err.message));
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
267
369
|
orgs
|
|
268
370
|
.command('members')
|
|
269
371
|
.description('List org members')
|
|
@@ -339,6 +441,91 @@ ws
|
|
|
339
441
|
process.exit(1);
|
|
340
442
|
}
|
|
341
443
|
});
|
|
444
|
+
ws
|
|
445
|
+
.command('update')
|
|
446
|
+
.description('Update worksheet properties. Provide at least one field. Use "ws list" to find IDs, "ws show <id>" to see current values.')
|
|
447
|
+
.argument('<id>', 'Worksheet ID')
|
|
448
|
+
.option('-n, --name <name>', 'New worksheet name')
|
|
449
|
+
.option('-p, --purpose <purpose>', 'New purpose (e.g., "From X to Y by When")')
|
|
450
|
+
.option('--cadence <cadence>', 'Check-in cadence: weekly, biweekly, or monthly')
|
|
451
|
+
.action(async (id, opts) => {
|
|
452
|
+
try {
|
|
453
|
+
if (!opts.name && !opts.purpose && !opts.cadence) {
|
|
454
|
+
console.error(chalk.red('Provide --name, --purpose, and/or --cadence'));
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const body = {};
|
|
458
|
+
if (opts.name)
|
|
459
|
+
body.name = opts.name;
|
|
460
|
+
if (opts.purpose)
|
|
461
|
+
body.purpose = opts.purpose;
|
|
462
|
+
if (opts.cadence)
|
|
463
|
+
body.checkInCadence = opts.cadence;
|
|
464
|
+
const data = await api('PATCH', `/api/worksheets/${id}`, body);
|
|
465
|
+
console.log(chalk.green(`Updated: ${data.worksheet.name}`));
|
|
466
|
+
}
|
|
467
|
+
catch (err) {
|
|
468
|
+
console.error(chalk.red(err.message));
|
|
469
|
+
process.exit(1);
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
ws
|
|
473
|
+
.command('archive')
|
|
474
|
+
.description('Archive a worksheet. Archived worksheets are hidden from "ws list" by default but can be found with "ws list -s archived". This is not reversible from the CLI.')
|
|
475
|
+
.argument('<id>', 'Worksheet ID')
|
|
476
|
+
.action(async (id) => {
|
|
477
|
+
try {
|
|
478
|
+
await api('POST', `/api/worksheets/${id}/archive`);
|
|
479
|
+
console.log(chalk.green('Worksheet archived'));
|
|
480
|
+
}
|
|
481
|
+
catch (err) {
|
|
482
|
+
console.error(chalk.red(err.message));
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
ws
|
|
487
|
+
.command('progress')
|
|
488
|
+
.description('Show the aggregated progress board for a worksheet. Combines outcomes, milestones, commitments, and actions into a single view. Use "ws show <id>" for the raw detail view instead.')
|
|
489
|
+
.argument('<id>', 'Worksheet ID')
|
|
490
|
+
.action(async (id) => {
|
|
491
|
+
try {
|
|
492
|
+
const data = await api('GET', `/api/worksheets/${id}/progress-board`);
|
|
493
|
+
const board = data.progressBoard || data;
|
|
494
|
+
console.log(chalk.cyan('Progress Board'));
|
|
495
|
+
if (board.outcomes?.length) {
|
|
496
|
+
console.log(chalk.cyan('\n Outcomes:'));
|
|
497
|
+
for (const o of board.outcomes) {
|
|
498
|
+
const progress = o.target_value ? ` ${o.current_value ?? 0}/${o.target_value}` : '';
|
|
499
|
+
console.log(` ${o.status === 'achieved' ? '✓' : '○'} ${o.name}${progress}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (board.milestones?.length) {
|
|
503
|
+
console.log(chalk.cyan('\n Milestones:'));
|
|
504
|
+
for (const m of board.milestones) {
|
|
505
|
+
const icon = m.status === 'reached' ? '✓' : m.status === 'missed' ? '✗' : '○';
|
|
506
|
+
console.log(` ${icon} ${m.name}${m.target_date ? ` (${m.target_date})` : ''}`);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
if (board.commitments?.length) {
|
|
510
|
+
console.log(chalk.cyan('\n Commitments:'));
|
|
511
|
+
for (const c of board.commitments) {
|
|
512
|
+
const icon = c.status === 'completed' ? chalk.green('✓') : c.status === 'missed' ? chalk.red('✗') : '○';
|
|
513
|
+
console.log(` ${icon} ${c.description} — ${c.status}`);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (board.actions?.length) {
|
|
517
|
+
console.log(chalk.cyan('\n Actions:'));
|
|
518
|
+
for (const a of board.actions) {
|
|
519
|
+
const progress = a.target_value ? ` ${a.current_value ?? 0}/${a.target_value}` : '';
|
|
520
|
+
console.log(` ${a.description}${progress}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
catch (err) {
|
|
525
|
+
console.error(chalk.red(err.message));
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
});
|
|
342
529
|
ws
|
|
343
530
|
.command('show')
|
|
344
531
|
.description('Show worksheet detail')
|
|
@@ -483,6 +670,41 @@ outcomes
|
|
|
483
670
|
process.exit(1);
|
|
484
671
|
}
|
|
485
672
|
});
|
|
673
|
+
outcomes
|
|
674
|
+
.command('update')
|
|
675
|
+
.description('Update an outcome. Provide at least one field to change. Use "oc update-value" to change just the current value, or "oc link/unlink" to manage parent linkage. Use "ws show <id>" to find outcome IDs.')
|
|
676
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
677
|
+
.requiredOption('-o, --outcome <id>', 'Outcome ID (from "ws show" output)')
|
|
678
|
+
.option('-n, --name <name>', 'New outcome name')
|
|
679
|
+
.option('-d, --description <desc>', 'New description')
|
|
680
|
+
.option('-m, --measurement <method>', 'How it is measured (e.g., "Count of active users")')
|
|
681
|
+
.option('-t, --target <value>', 'Target value (numeric)', parseNumber)
|
|
682
|
+
.option('-s, --status <status>', 'Status: active, achieved, or dropped')
|
|
683
|
+
.action(async (opts) => {
|
|
684
|
+
try {
|
|
685
|
+
const body = {};
|
|
686
|
+
if (opts.name)
|
|
687
|
+
body.name = opts.name;
|
|
688
|
+
if (opts.description != null)
|
|
689
|
+
body.description = opts.description;
|
|
690
|
+
if (opts.measurement)
|
|
691
|
+
body.measurement = opts.measurement;
|
|
692
|
+
if (opts.target !== undefined)
|
|
693
|
+
body.targetValue = opts.target;
|
|
694
|
+
if (opts.status)
|
|
695
|
+
body.status = opts.status;
|
|
696
|
+
if (Object.keys(body).length === 0) {
|
|
697
|
+
console.error(chalk.red('Provide at least one field to update'));
|
|
698
|
+
process.exit(1);
|
|
699
|
+
}
|
|
700
|
+
await api('PATCH', `/api/worksheets/${opts.worksheet}/outcomes/${opts.outcome}`, body);
|
|
701
|
+
console.log(chalk.green('Outcome updated'));
|
|
702
|
+
}
|
|
703
|
+
catch (err) {
|
|
704
|
+
console.error(chalk.red(err.message));
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
486
708
|
outcomes
|
|
487
709
|
.command('update-value')
|
|
488
710
|
.description('Update an outcome current value')
|
|
@@ -499,6 +721,101 @@ outcomes
|
|
|
499
721
|
process.exit(1);
|
|
500
722
|
}
|
|
501
723
|
});
|
|
724
|
+
outcomes
|
|
725
|
+
.command('remove')
|
|
726
|
+
.description('Permanently delete an outcome from a worksheet. If the outcome has linked children, those links will be broken. This cannot be undone.')
|
|
727
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
728
|
+
.requiredOption('-o, --outcome <id>', 'Outcome ID (from "ws show" output)')
|
|
729
|
+
.action(async (opts) => {
|
|
730
|
+
try {
|
|
731
|
+
await api('DELETE', `/api/worksheets/${opts.worksheet}/outcomes/${opts.outcome}`);
|
|
732
|
+
console.log(chalk.green('Outcome deleted'));
|
|
733
|
+
}
|
|
734
|
+
catch (err) {
|
|
735
|
+
console.error(chalk.red(err.message));
|
|
736
|
+
process.exit(1);
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
outcomes
|
|
740
|
+
.command('reorder')
|
|
741
|
+
.description('Reorder outcomes on a worksheet. Pass all outcome IDs in the desired display order. IDs not included will be appended at the end.')
|
|
742
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
743
|
+
.requiredOption('--ids <ids>', 'Comma-separated outcome IDs in desired order (e.g., --ids id1,id2,id3)', (v) => v.split(','))
|
|
744
|
+
.action(async (opts) => {
|
|
745
|
+
try {
|
|
746
|
+
await api('PATCH', `/api/worksheets/${opts.worksheet}/outcomes/reorder`, { outcomeIds: opts.ids });
|
|
747
|
+
console.log(chalk.green('Outcomes reordered'));
|
|
748
|
+
}
|
|
749
|
+
catch (err) {
|
|
750
|
+
console.error(chalk.red(err.message));
|
|
751
|
+
process.exit(1);
|
|
752
|
+
}
|
|
753
|
+
});
|
|
754
|
+
outcomes
|
|
755
|
+
.command('linkable')
|
|
756
|
+
.description('List outcomes from other worksheets that can be linked to as parents. Use this to discover valid --parent IDs for "oc link". Only shows outcomes that would not create a circular reference.')
|
|
757
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID whose outcomes you want to link from')
|
|
758
|
+
.action(async (opts) => {
|
|
759
|
+
try {
|
|
760
|
+
const data = await api('GET', `/api/outcomes/linkable?worksheetId=${opts.worksheet}`);
|
|
761
|
+
if (!data.outcomes?.length) {
|
|
762
|
+
console.log(chalk.yellow('No linkable outcomes'));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
for (const o of data.outcomes) {
|
|
766
|
+
const ws = o.worksheet_name ? ` on ${o.worksheet_name}` : '';
|
|
767
|
+
console.log(` ${o.name}${ws} — ${o.id}`);
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
console.error(chalk.red(err.message));
|
|
772
|
+
process.exit(1);
|
|
773
|
+
}
|
|
774
|
+
});
|
|
775
|
+
outcomes
|
|
776
|
+
.command('children')
|
|
777
|
+
.description('Show outcomes from other worksheets that are linked to this outcome as children. These are outcomes where "oc link --parent <this-id>" was used.')
|
|
778
|
+
.argument('<id>', 'Parent outcome ID')
|
|
779
|
+
.action(async (id) => {
|
|
780
|
+
try {
|
|
781
|
+
const data = await api('GET', `/api/outcomes/${id}/children`);
|
|
782
|
+
if (!data.children?.length) {
|
|
783
|
+
console.log(chalk.yellow('No child outcomes'));
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
for (const c of data.children) {
|
|
787
|
+
const progress = c.target_value ? ` ${c.current_value ?? 0}/${c.target_value}` : '';
|
|
788
|
+
const ws = c.worksheet_name ? ` on ${c.worksheet_name}` : '';
|
|
789
|
+
console.log(` ${c.status === 'achieved' ? '✓' : '○'} ${c.name}${progress}${ws} — ${c.id}`);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
catch (err) {
|
|
793
|
+
console.error(chalk.red(err.message));
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
outcomes
|
|
798
|
+
.command('tree')
|
|
799
|
+
.description('Show the full hierarchy tree for an outcome, including all descendant outcomes across worksheets. Useful for seeing how team-level outcomes roll up to org-level goals.')
|
|
800
|
+
.argument('<id>', 'Root outcome ID')
|
|
801
|
+
.action(async (id) => {
|
|
802
|
+
try {
|
|
803
|
+
const data = await api('GET', `/api/outcomes/${id}/tree`);
|
|
804
|
+
const printNode = (node, indent) => {
|
|
805
|
+
const progress = node.target_value ? ` ${node.current_value ?? 0}/${node.target_value}` : '';
|
|
806
|
+
const ws = node.worksheet_name ? ` on ${node.worksheet_name}` : '';
|
|
807
|
+
console.log(`${indent}${node.status === 'achieved' ? '✓' : '○'} ${node.name}${progress}${ws}`);
|
|
808
|
+
for (const child of node.children || []) {
|
|
809
|
+
printNode(child, indent + ' ');
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
printNode(data.tree || data, ' ');
|
|
813
|
+
}
|
|
814
|
+
catch (err) {
|
|
815
|
+
console.error(chalk.red(err.message));
|
|
816
|
+
process.exit(1);
|
|
817
|
+
}
|
|
818
|
+
});
|
|
502
819
|
// ── Milestones ──────────────────────────────────────────────
|
|
503
820
|
const milestones = program.command('milestones').alias('ms').description('Milestone commands');
|
|
504
821
|
milestones
|
|
@@ -527,8 +844,71 @@ milestones
|
|
|
527
844
|
process.exit(1);
|
|
528
845
|
}
|
|
529
846
|
});
|
|
847
|
+
milestones
|
|
848
|
+
.command('update')
|
|
849
|
+
.description('Update a milestone. Provide at least one field to change. Use "ws show <id>" to find milestone IDs and see current values.')
|
|
850
|
+
.argument('<id>', 'Milestone ID (from "ws show" output)')
|
|
851
|
+
.option('-n, --name <name>', 'New milestone name')
|
|
852
|
+
.option('-d, --description <desc>', 'New description')
|
|
853
|
+
.option('-t, --target-date <date>', 'New target date (YYYY-MM-DD)')
|
|
854
|
+
.option('-s, --status <status>', 'Status: pending (default), reached, or missed')
|
|
855
|
+
.action(async (id, opts) => {
|
|
856
|
+
try {
|
|
857
|
+
const body = {};
|
|
858
|
+
if (opts.name)
|
|
859
|
+
body.name = opts.name;
|
|
860
|
+
if (opts.description != null)
|
|
861
|
+
body.description = opts.description;
|
|
862
|
+
if (opts.targetDate)
|
|
863
|
+
body.targetDate = opts.targetDate;
|
|
864
|
+
if (opts.status)
|
|
865
|
+
body.status = opts.status;
|
|
866
|
+
if (Object.keys(body).length === 0) {
|
|
867
|
+
console.error(chalk.red('Provide at least one field to update'));
|
|
868
|
+
process.exit(1);
|
|
869
|
+
}
|
|
870
|
+
await api('PATCH', `/api/milestones/${id}`, body);
|
|
871
|
+
console.log(chalk.green('Milestone updated'));
|
|
872
|
+
}
|
|
873
|
+
catch (err) {
|
|
874
|
+
console.error(chalk.red(err.message));
|
|
875
|
+
process.exit(1);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
milestones
|
|
879
|
+
.command('remove')
|
|
880
|
+
.description('Permanently delete a milestone. This cannot be undone.')
|
|
881
|
+
.argument('<id>', 'Milestone ID (from "ws show" output)')
|
|
882
|
+
.action(async (id) => {
|
|
883
|
+
try {
|
|
884
|
+
await api('DELETE', `/api/milestones/${id}`);
|
|
885
|
+
console.log(chalk.green('Milestone deleted'));
|
|
886
|
+
}
|
|
887
|
+
catch (err) {
|
|
888
|
+
console.error(chalk.red(err.message));
|
|
889
|
+
process.exit(1);
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
milestones
|
|
893
|
+
.command('reorder')
|
|
894
|
+
.description('Reorder milestones on a worksheet. Pass all milestone IDs in the desired display order.')
|
|
895
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
896
|
+
.requiredOption('--ids <ids>', 'Comma-separated milestone IDs in desired order (e.g., --ids id1,id2,id3)', (v) => v.split(','))
|
|
897
|
+
.action(async (opts) => {
|
|
898
|
+
try {
|
|
899
|
+
await api('PATCH', `/api/worksheets/${opts.worksheet}/milestones/reorder`, { milestoneIds: opts.ids });
|
|
900
|
+
console.log(chalk.green('Milestones reordered'));
|
|
901
|
+
}
|
|
902
|
+
catch (err) {
|
|
903
|
+
console.error(chalk.red(err.message));
|
|
904
|
+
process.exit(1);
|
|
905
|
+
}
|
|
906
|
+
});
|
|
530
907
|
// ── Members ─────────────────────────────────────────────────
|
|
531
|
-
const members = program.command('members').description('
|
|
908
|
+
const members = program.command('members').description('Member commands (worksheet and org level).\n\n' +
|
|
909
|
+
' Worksheet members: add, list, update, remove — manage who works on a worksheet.\n' +
|
|
910
|
+
' Org members: show, update-org, delete — manage org-level member records.\n' +
|
|
911
|
+
' Use "orgs members" to list all org members and find member IDs.');
|
|
532
912
|
members
|
|
533
913
|
.command('add')
|
|
534
914
|
.description('Add a member to a worksheet')
|
|
@@ -549,19 +929,111 @@ members
|
|
|
549
929
|
}
|
|
550
930
|
});
|
|
551
931
|
members
|
|
552
|
-
.command('list')
|
|
553
|
-
.description('List worksheet members')
|
|
554
|
-
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
555
|
-
.action(async (opts) => {
|
|
932
|
+
.command('list')
|
|
933
|
+
.description('List worksheet members')
|
|
934
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
935
|
+
.action(async (opts) => {
|
|
936
|
+
try {
|
|
937
|
+
const data = await api('GET', `/api/worksheets/${opts.worksheet}/members`);
|
|
938
|
+
if (!data.members?.length) {
|
|
939
|
+
console.log(chalk.yellow('No members'));
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
for (const m of data.members) {
|
|
943
|
+
console.log(` ${chalk.cyan(m.display_name)} (${m.worksheet_role}) — ${m.member_id}`);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
catch (err) {
|
|
947
|
+
console.error(chalk.red(err.message));
|
|
948
|
+
process.exit(1);
|
|
949
|
+
}
|
|
950
|
+
});
|
|
951
|
+
members
|
|
952
|
+
.command('update')
|
|
953
|
+
.description('Change a worksheet member\'s role. Use "members list -w <id>" to find worksheet member IDs.')
|
|
954
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
955
|
+
.requiredOption('-m, --member <id>', 'Worksheet member ID (from "members list" output)')
|
|
956
|
+
.requiredOption('-r, --role <role>', 'New role: lead or contributor')
|
|
957
|
+
.action(async (opts) => {
|
|
958
|
+
try {
|
|
959
|
+
await api('PATCH', `/api/worksheets/${opts.worksheet}/members/${opts.member}`, { role: opts.role });
|
|
960
|
+
console.log(chalk.green(`Member role updated to ${opts.role}`));
|
|
961
|
+
}
|
|
962
|
+
catch (err) {
|
|
963
|
+
console.error(chalk.red(err.message));
|
|
964
|
+
process.exit(1);
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
members
|
|
968
|
+
.command('remove')
|
|
969
|
+
.description('Remove a member from a worksheet. The member remains in the organization but loses access to this worksheet.')
|
|
970
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
971
|
+
.requiredOption('-m, --member <id>', 'Worksheet member ID (from "members list" output)')
|
|
972
|
+
.action(async (opts) => {
|
|
973
|
+
try {
|
|
974
|
+
await api('DELETE', `/api/worksheets/${opts.worksheet}/members/${opts.member}`);
|
|
975
|
+
console.log(chalk.green('Member removed from worksheet'));
|
|
976
|
+
}
|
|
977
|
+
catch (err) {
|
|
978
|
+
console.error(chalk.red(err.message));
|
|
979
|
+
process.exit(1);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
members
|
|
983
|
+
.command('show')
|
|
984
|
+
.description('Show an organization member\'s detail, including role, type, and agent status. Use "orgs members" to find member IDs.')
|
|
985
|
+
.argument('<id>', 'Member ID (from "orgs members" output)')
|
|
986
|
+
.action(async (id) => {
|
|
987
|
+
try {
|
|
988
|
+
const data = await api('GET', `/api/members/${id}`);
|
|
989
|
+
const m = data.member;
|
|
990
|
+
console.log(chalk.cyan(m.display_name));
|
|
991
|
+
console.log(` ID: ${m.id}`);
|
|
992
|
+
console.log(` Role: ${m.role}`);
|
|
993
|
+
console.log(` Type: ${m.member_type}`);
|
|
994
|
+
if (m.email)
|
|
995
|
+
console.log(` Email: ${m.email}`);
|
|
996
|
+
if (m.agent_status)
|
|
997
|
+
console.log(` Status: ${m.agent_status}`);
|
|
998
|
+
}
|
|
999
|
+
catch (err) {
|
|
1000
|
+
console.error(chalk.red(err.message));
|
|
1001
|
+
process.exit(1);
|
|
1002
|
+
}
|
|
1003
|
+
});
|
|
1004
|
+
members
|
|
1005
|
+
.command('update-org')
|
|
1006
|
+
.description('Update an organization member\'s role or display name. This is the org-level role, not the worksheet role. Use "members update" for worksheet roles instead.')
|
|
1007
|
+
.argument('<id>', 'Member ID (from "orgs members" output)')
|
|
1008
|
+
.option('-r, --role <role>', 'New org role: admin or member')
|
|
1009
|
+
.option('-n, --name <name>', 'New display name')
|
|
1010
|
+
.action(async (id, opts) => {
|
|
1011
|
+
try {
|
|
1012
|
+
const body = {};
|
|
1013
|
+
if (opts.role)
|
|
1014
|
+
body.role = opts.role;
|
|
1015
|
+
if (opts.name)
|
|
1016
|
+
body.displayName = opts.name;
|
|
1017
|
+
if (Object.keys(body).length === 0) {
|
|
1018
|
+
console.error(chalk.red('Provide --role and/or --name'));
|
|
1019
|
+
process.exit(1);
|
|
1020
|
+
}
|
|
1021
|
+
await api('PATCH', `/api/members/${id}`, body);
|
|
1022
|
+
console.log(chalk.green('Member updated'));
|
|
1023
|
+
}
|
|
1024
|
+
catch (err) {
|
|
1025
|
+
console.error(chalk.red(err.message));
|
|
1026
|
+
process.exit(1);
|
|
1027
|
+
}
|
|
1028
|
+
});
|
|
1029
|
+
members
|
|
1030
|
+
.command('delete')
|
|
1031
|
+
.description('Permanently remove a member from the organization. This removes them from all worksheets and revokes access. Cannot be undone.')
|
|
1032
|
+
.argument('<id>', 'Member ID (from "orgs members" output)')
|
|
1033
|
+
.action(async (id) => {
|
|
556
1034
|
try {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
console.log(chalk.yellow('No members'));
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
for (const m of data.members) {
|
|
563
|
-
console.log(` ${chalk.cyan(m.display_name)} (${m.worksheet_role}) — ${m.member_id}`);
|
|
564
|
-
}
|
|
1035
|
+
await api('DELETE', `/api/members/${id}`);
|
|
1036
|
+
console.log(chalk.green('Member removed from organization'));
|
|
565
1037
|
}
|
|
566
1038
|
catch (err) {
|
|
567
1039
|
console.error(chalk.red(err.message));
|
|
@@ -637,6 +1109,55 @@ actions
|
|
|
637
1109
|
process.exit(1);
|
|
638
1110
|
}
|
|
639
1111
|
});
|
|
1112
|
+
actions
|
|
1113
|
+
.command('update')
|
|
1114
|
+
.description('Update an action. Provide at least one field to change. Use "ac update-value" to change just the current value. Use "ac list -w <id>" to find action IDs.')
|
|
1115
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1116
|
+
.requiredOption('-a, --action <id>', 'Action ID (from "ac list" or "ws show" output)')
|
|
1117
|
+
.option('-n, --name <name>', 'New action description')
|
|
1118
|
+
.option('-m, --measurement <method>', 'How it is measured (e.g., "PRs merged per week")')
|
|
1119
|
+
.option('-t, --target <value>', 'Target value (numeric)', parseNumber)
|
|
1120
|
+
.option('-o, --outcome <id>', 'Link to a different outcome ID')
|
|
1121
|
+
.action(async (opts) => {
|
|
1122
|
+
try {
|
|
1123
|
+
const body = {};
|
|
1124
|
+
if (opts.name)
|
|
1125
|
+
body.description = opts.name;
|
|
1126
|
+
if (opts.measurement)
|
|
1127
|
+
body.measurement = opts.measurement;
|
|
1128
|
+
if (opts.target !== undefined)
|
|
1129
|
+
body.targetValue = opts.target;
|
|
1130
|
+
if (opts.outcome)
|
|
1131
|
+
body.outcomeId = opts.outcome;
|
|
1132
|
+
if (Object.keys(body).length === 0) {
|
|
1133
|
+
console.error(chalk.red('Provide at least one field to update'));
|
|
1134
|
+
process.exit(1);
|
|
1135
|
+
}
|
|
1136
|
+
await api('PATCH', `/api/worksheets/${opts.worksheet}/actions/${opts.action}`, body);
|
|
1137
|
+
console.log(chalk.green('Action updated'));
|
|
1138
|
+
}
|
|
1139
|
+
catch (err) {
|
|
1140
|
+
console.error(chalk.red(err.message));
|
|
1141
|
+
process.exit(1);
|
|
1142
|
+
}
|
|
1143
|
+
});
|
|
1144
|
+
actions
|
|
1145
|
+
.command('execute')
|
|
1146
|
+
.description('Trigger an agent execution for an action. The API determines which agent to use based on the action\'s configuration. For more control over agent selection and parameters, use the top-level "execute" command instead.')
|
|
1147
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1148
|
+
.requiredOption('-a, --action <id>', 'Action ID (from "ac list" or "ws show" output)')
|
|
1149
|
+
.action(async (opts) => {
|
|
1150
|
+
try {
|
|
1151
|
+
const data = await api('POST', `/api/worksheets/${opts.worksheet}/actions/${opts.action}/execute`);
|
|
1152
|
+
console.log(chalk.green('Execution triggered'));
|
|
1153
|
+
if (data.execution?.id)
|
|
1154
|
+
console.log(` Execution ID: ${data.execution.id}`);
|
|
1155
|
+
}
|
|
1156
|
+
catch (err) {
|
|
1157
|
+
console.error(chalk.red(err.message));
|
|
1158
|
+
process.exit(1);
|
|
1159
|
+
}
|
|
1160
|
+
});
|
|
640
1161
|
// ── Commitments ─────────────────────────────────────────────
|
|
641
1162
|
const commitments = program.command('commitments').alias('cm').description('Commitment commands');
|
|
642
1163
|
commitments
|
|
@@ -697,6 +1218,243 @@ commitments
|
|
|
697
1218
|
process.exit(1);
|
|
698
1219
|
}
|
|
699
1220
|
});
|
|
1221
|
+
commitments
|
|
1222
|
+
.command('execute')
|
|
1223
|
+
.description('Trigger an agent execution for a commitment. The API determines which agent to use based on the commitment\'s configuration. For more control, use the top-level "execute" command instead.')
|
|
1224
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1225
|
+
.requiredOption('-c, --commitment <id>', 'Commitment ID (from "cm list" or "ws show" output)')
|
|
1226
|
+
.action(async (opts) => {
|
|
1227
|
+
try {
|
|
1228
|
+
const data = await api('POST', `/api/worksheets/${opts.worksheet}/commitments/${opts.commitment}/execute`);
|
|
1229
|
+
console.log(chalk.green('Execution triggered'));
|
|
1230
|
+
if (data.execution?.id)
|
|
1231
|
+
console.log(` Execution ID: ${data.execution.id}`);
|
|
1232
|
+
}
|
|
1233
|
+
catch (err) {
|
|
1234
|
+
console.error(chalk.red(err.message));
|
|
1235
|
+
process.exit(1);
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
// ── Notes ───────────────────────────────────────────────────
|
|
1239
|
+
const notes = program.command('notes').alias('nt').description('Worksheet note commands. Notes are freeform text attached to a worksheet for capturing context, decisions, or observations.');
|
|
1240
|
+
notes
|
|
1241
|
+
.command('add')
|
|
1242
|
+
.description('Add a note to a worksheet. Notes are timestamped and attributed to the current user. Good for recording decisions, context, or observations that don\'t fit into outcomes or actions.')
|
|
1243
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1244
|
+
.requiredOption('-t, --text <text>', 'Note text (e.g., "Decided to defer mobile until Q3")')
|
|
1245
|
+
.action(async (opts) => {
|
|
1246
|
+
try {
|
|
1247
|
+
const data = await api('POST', `/api/worksheets/${opts.worksheet}/notes`, { text: opts.text });
|
|
1248
|
+
const n = data.note;
|
|
1249
|
+
console.log(chalk.green('Note added'));
|
|
1250
|
+
console.log(` ID: ${n.id}`);
|
|
1251
|
+
}
|
|
1252
|
+
catch (err) {
|
|
1253
|
+
console.error(chalk.red(err.message));
|
|
1254
|
+
process.exit(1);
|
|
1255
|
+
}
|
|
1256
|
+
});
|
|
1257
|
+
notes
|
|
1258
|
+
.command('list')
|
|
1259
|
+
.description('List notes on a worksheet')
|
|
1260
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1261
|
+
.action(async (opts) => {
|
|
1262
|
+
try {
|
|
1263
|
+
const data = await api('GET', `/api/worksheets/${opts.worksheet}/notes`);
|
|
1264
|
+
if (!data.notes?.length) {
|
|
1265
|
+
console.log(chalk.yellow('No notes'));
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
for (const n of data.notes) {
|
|
1269
|
+
const time = n.created_at ? new Date(n.created_at).toLocaleString() : '';
|
|
1270
|
+
const author = n.author_name || '';
|
|
1271
|
+
console.log(` ${chalk.gray(time)} ${author ? chalk.cyan(author) + ' — ' : ''}${n.text}`);
|
|
1272
|
+
console.log(` ID: ${n.id}`);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
catch (err) {
|
|
1276
|
+
console.error(chalk.red(err.message));
|
|
1277
|
+
process.exit(1);
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
notes
|
|
1281
|
+
.command('remove')
|
|
1282
|
+
.description('Permanently delete a note. Use "nt list -w <id>" to find note IDs.')
|
|
1283
|
+
.argument('<id>', 'Note ID (from "nt list" output)')
|
|
1284
|
+
.action(async (id) => {
|
|
1285
|
+
try {
|
|
1286
|
+
await api('DELETE', `/api/notes/${id}`);
|
|
1287
|
+
console.log(chalk.green('Note deleted'));
|
|
1288
|
+
}
|
|
1289
|
+
catch (err) {
|
|
1290
|
+
console.error(chalk.red(err.message));
|
|
1291
|
+
process.exit(1);
|
|
1292
|
+
}
|
|
1293
|
+
});
|
|
1294
|
+
// ── Check-ins ───────────────────────────────────────────────
|
|
1295
|
+
const checkins = program.command('check-ins').alias('ci').description('Check-in commands.\n\n' +
|
|
1296
|
+
' Check-ins are periodic reviews of worksheet progress (weekly, biweekly, etc.).\n' +
|
|
1297
|
+
' During a check-in, members report on commitments and make new ones.\n\n' +
|
|
1298
|
+
' Typical flow:\n' +
|
|
1299
|
+
' 1. ci create -w <id> Start a new check-in\n' +
|
|
1300
|
+
' 2. ci report <id> --reports [...] Report on past commitments\n' +
|
|
1301
|
+
' 3. ci add-commitment <id> -d "..." Make new commitments\n' +
|
|
1302
|
+
' 4. ci complete <id> Close the check-in');
|
|
1303
|
+
checkins
|
|
1304
|
+
.command('create')
|
|
1305
|
+
.description('Start a new check-in for a worksheet. This creates a check-in record where members can report on past commitments and make new ones. Only one check-in is typically open at a time.')
|
|
1306
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1307
|
+
.option('-n, --name <name>', 'Check-in name (e.g., "Week of 2026-03-23")')
|
|
1308
|
+
.action(async (opts) => {
|
|
1309
|
+
try {
|
|
1310
|
+
const body = {};
|
|
1311
|
+
if (opts.name)
|
|
1312
|
+
body.name = opts.name;
|
|
1313
|
+
const data = await api('POST', `/api/worksheets/${opts.worksheet}/check-ins`, body);
|
|
1314
|
+
const ci = data.checkIn;
|
|
1315
|
+
console.log(chalk.green('Check-in created'));
|
|
1316
|
+
console.log(` ID: ${ci.id}`);
|
|
1317
|
+
if (ci.status)
|
|
1318
|
+
console.log(` Status: ${ci.status}`);
|
|
1319
|
+
}
|
|
1320
|
+
catch (err) {
|
|
1321
|
+
console.error(chalk.red(err.message));
|
|
1322
|
+
process.exit(1);
|
|
1323
|
+
}
|
|
1324
|
+
});
|
|
1325
|
+
checkins
|
|
1326
|
+
.command('list')
|
|
1327
|
+
.description('List check-ins for a worksheet')
|
|
1328
|
+
.requiredOption('-w, --worksheet <id>', 'Worksheet ID')
|
|
1329
|
+
.action(async (opts) => {
|
|
1330
|
+
try {
|
|
1331
|
+
const data = await api('GET', `/api/worksheets/${opts.worksheet}/check-ins`);
|
|
1332
|
+
if (!data.checkIns?.length) {
|
|
1333
|
+
console.log(chalk.yellow('No check-ins'));
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
for (const ci of data.checkIns) {
|
|
1337
|
+
const icon = ci.status === 'completed' ? chalk.green('✓') : '○';
|
|
1338
|
+
const date = ci.created_at ? new Date(ci.created_at).toLocaleDateString() : '';
|
|
1339
|
+
console.log(` ${icon} ${ci.name || date} (${ci.status}) — ${ci.id}`);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
catch (err) {
|
|
1343
|
+
console.error(chalk.red(err.message));
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
checkins
|
|
1348
|
+
.command('show')
|
|
1349
|
+
.description('Show check-in detail including commitment reports and new commitments made during the check-in.')
|
|
1350
|
+
.argument('<id>', 'Check-in ID (from "ci list" output)')
|
|
1351
|
+
.action(async (id) => {
|
|
1352
|
+
try {
|
|
1353
|
+
const data = await api('GET', `/api/check-ins/${id}`);
|
|
1354
|
+
const ci = data.checkIn;
|
|
1355
|
+
console.log(chalk.cyan(ci.name || `Check-in ${ci.id}`));
|
|
1356
|
+
console.log(` ID: ${ci.id}`);
|
|
1357
|
+
console.log(` Status: ${ci.status}`);
|
|
1358
|
+
if (ci.created_at)
|
|
1359
|
+
console.log(` Created: ${ci.created_at}`);
|
|
1360
|
+
if (ci.reports?.length) {
|
|
1361
|
+
console.log(chalk.cyan('\n Reports:'));
|
|
1362
|
+
for (const r of ci.reports) {
|
|
1363
|
+
const icon = r.status === 'completed' ? chalk.green('✓') : r.status === 'missed' ? chalk.red('✗') : '○';
|
|
1364
|
+
console.log(` ${icon} ${r.commitment_description || r.commitment_id} — ${r.status}`);
|
|
1365
|
+
if (r.notes)
|
|
1366
|
+
console.log(` ${r.notes}`);
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
if (ci.commitments?.length) {
|
|
1370
|
+
console.log(chalk.cyan('\n Commitments:'));
|
|
1371
|
+
for (const c of ci.commitments) {
|
|
1372
|
+
const icon = c.status === 'completed' ? chalk.green('✓') : '○';
|
|
1373
|
+
console.log(` ${icon} ${c.description} — due ${c.due_date} — ${c.id}`);
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
catch (err) {
|
|
1378
|
+
console.error(chalk.red(err.message));
|
|
1379
|
+
process.exit(1);
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
checkins
|
|
1383
|
+
.command('update')
|
|
1384
|
+
.description('Update a check-in\'s name or notes. The check-in must still be open (not completed).')
|
|
1385
|
+
.argument('<id>', 'Check-in ID (from "ci list" output)')
|
|
1386
|
+
.option('-n, --name <name>', 'New check-in name')
|
|
1387
|
+
.option('--notes <text>', 'General notes for this check-in')
|
|
1388
|
+
.action(async (id, opts) => {
|
|
1389
|
+
try {
|
|
1390
|
+
const body = {};
|
|
1391
|
+
if (opts.name)
|
|
1392
|
+
body.name = opts.name;
|
|
1393
|
+
if (opts.notes != null)
|
|
1394
|
+
body.notes = opts.notes;
|
|
1395
|
+
if (Object.keys(body).length === 0) {
|
|
1396
|
+
console.error(chalk.red('Provide at least one field to update'));
|
|
1397
|
+
process.exit(1);
|
|
1398
|
+
}
|
|
1399
|
+
await api('PATCH', `/api/check-ins/${id}`, body);
|
|
1400
|
+
console.log(chalk.green('Check-in updated'));
|
|
1401
|
+
}
|
|
1402
|
+
catch (err) {
|
|
1403
|
+
console.error(chalk.red(err.message));
|
|
1404
|
+
process.exit(1);
|
|
1405
|
+
}
|
|
1406
|
+
});
|
|
1407
|
+
checkins
|
|
1408
|
+
.command('report')
|
|
1409
|
+
.description('Submit commitment reports for a check-in. Each report records whether a past commitment was completed, missed, or is still in progress.\n\n Example:\n ci report <id> --reports \'[{"commitmentId":"abc","status":"completed","notes":"Shipped it"}]\'')
|
|
1410
|
+
.argument('<id>', 'Check-in ID (from "ci list" output)')
|
|
1411
|
+
.requiredOption('--reports <json>', 'JSON array of reports. Each: {commitmentId, status (completed|missed|in_progress), notes?}')
|
|
1412
|
+
.action(async (id, opts) => {
|
|
1413
|
+
try {
|
|
1414
|
+
const reports = JSON.parse(opts.reports);
|
|
1415
|
+
await api('POST', `/api/check-ins/${id}/reports`, { reports });
|
|
1416
|
+
console.log(chalk.green(`Submitted ${reports.length} report(s)`));
|
|
1417
|
+
}
|
|
1418
|
+
catch (err) {
|
|
1419
|
+
console.error(chalk.red(err.message));
|
|
1420
|
+
process.exit(1);
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
checkins
|
|
1424
|
+
.command('add-commitment')
|
|
1425
|
+
.description('Make a new commitment during a check-in. This is how team members pledge specific deliverables for the next period. The commitment will appear in "cm list" and be reportable in the next check-in.')
|
|
1426
|
+
.argument('<id>', 'Check-in ID (from "ci list" output)')
|
|
1427
|
+
.requiredOption('-d, --description <desc>', 'What you commit to do (e.g., "Ship the new API endpoint")')
|
|
1428
|
+
.requiredOption('--due <date>', 'Due date (YYYY-MM-DD)')
|
|
1429
|
+
.option('-a, --action <id>', 'Link to an action ID for tracking')
|
|
1430
|
+
.action(async (id, opts) => {
|
|
1431
|
+
try {
|
|
1432
|
+
const body = { description: opts.description, dueDate: opts.due };
|
|
1433
|
+
if (opts.action)
|
|
1434
|
+
body.actionId = opts.action;
|
|
1435
|
+
const data = await api('POST', `/api/check-ins/${id}/commitments`, body);
|
|
1436
|
+
console.log(chalk.green(`Commitment added: ${data.commitment.description}`));
|
|
1437
|
+
console.log(` ID: ${data.commitment.id}`);
|
|
1438
|
+
}
|
|
1439
|
+
catch (err) {
|
|
1440
|
+
console.error(chalk.red(err.message));
|
|
1441
|
+
process.exit(1);
|
|
1442
|
+
}
|
|
1443
|
+
});
|
|
1444
|
+
checkins
|
|
1445
|
+
.command('complete')
|
|
1446
|
+
.description('Close a check-in. Once completed, no more reports or commitments can be added. This finalizes the check-in record.')
|
|
1447
|
+
.argument('<id>', 'Check-in ID (from "ci list" output)')
|
|
1448
|
+
.action(async (id) => {
|
|
1449
|
+
try {
|
|
1450
|
+
await api('POST', `/api/check-ins/${id}/complete`);
|
|
1451
|
+
console.log(chalk.green('Check-in completed'));
|
|
1452
|
+
}
|
|
1453
|
+
catch (err) {
|
|
1454
|
+
console.error(chalk.red(err.message));
|
|
1455
|
+
process.exit(1);
|
|
1456
|
+
}
|
|
1457
|
+
});
|
|
700
1458
|
// ── Template I/O helpers ───────────────────────────────────
|
|
701
1459
|
function allSteps(definition) {
|
|
702
1460
|
const steps = [];
|
|
@@ -784,7 +1542,9 @@ templates
|
|
|
784
1542
|
for (const phase of t.definition.phases) {
|
|
785
1543
|
console.log(chalk.cyan(`\n Phase: ${phase.name}`));
|
|
786
1544
|
for (const step of phase.steps || []) {
|
|
787
|
-
|
|
1545
|
+
const reviewer = step.reviewer ? ` reviewer:${step.reviewer}` : '';
|
|
1546
|
+
const cycles = step.reviewer && step.max_review_cycles ? ` max-cycles:${step.max_review_cycles}` : '';
|
|
1547
|
+
console.log(` Step: ${step.name} [${step.role || 'unassigned'}] (${step.step_type || step.stepType || 'human'})${reviewer}${cycles}`);
|
|
788
1548
|
if (step.outputs?.length) {
|
|
789
1549
|
console.log(' Outputs:');
|
|
790
1550
|
for (const o of step.outputs) {
|
|
@@ -1279,7 +2039,8 @@ templates
|
|
|
1279
2039
|
return;
|
|
1280
2040
|
}
|
|
1281
2041
|
for (const s of steps) {
|
|
1282
|
-
|
|
2042
|
+
const rev = s.reviewer ? ` reviewer:${s.reviewer}` : '';
|
|
2043
|
+
console.log(` ${chalk.dim(s.id)} ${s.name} [${s.role || 'unassigned'}]${rev}`);
|
|
1283
2044
|
}
|
|
1284
2045
|
}
|
|
1285
2046
|
catch (err) {
|
|
@@ -1996,6 +2757,50 @@ step
|
|
|
1996
2757
|
process.exit(1);
|
|
1997
2758
|
}
|
|
1998
2759
|
});
|
|
2760
|
+
step
|
|
2761
|
+
.command('approve')
|
|
2762
|
+
.description('Approve a submitted step. The step must be in "submitted" status (awaiting review). Approval marks the step as completed and may trigger downstream steps to be queued. Use "pl show <id>" to see step statuses.')
|
|
2763
|
+
.argument('<instanceId>', 'Pipeline instance ID')
|
|
2764
|
+
.argument('<stepId>', 'Step instance ID')
|
|
2765
|
+
.option('--notes <text>', 'Approval notes (visible to the step assignee)')
|
|
2766
|
+
.action(async (instanceId, stepId, opts) => {
|
|
2767
|
+
try {
|
|
2768
|
+
const body = {};
|
|
2769
|
+
if (opts.notes)
|
|
2770
|
+
body.notes = opts.notes;
|
|
2771
|
+
const data = await api('POST', `/api/pipeline-instances/${instanceId}/steps/${stepId}/approve`, body);
|
|
2772
|
+
console.log(chalk.green(`Step approved: ${data.step?.step_name || stepId}`));
|
|
2773
|
+
if (data.queued_steps?.length) {
|
|
2774
|
+
console.log(chalk.cyan(`\n Next steps queued: ${data.queued_steps.length}`));
|
|
2775
|
+
for (const qs of data.queued_steps) {
|
|
2776
|
+
console.log(` ○ ${qs.step_name} [${qs.role || '-'}]`);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
catch (err) {
|
|
2781
|
+
console.error(chalk.red(err.message));
|
|
2782
|
+
process.exit(1);
|
|
2783
|
+
}
|
|
2784
|
+
});
|
|
2785
|
+
step
|
|
2786
|
+
.command('revise')
|
|
2787
|
+
.description('Request revision on a submitted step. Sends the step back to "in_progress" with feedback. The assignee can then resubmit. The step\'s review cycle counter increments; if max cycles are reached, the step auto-approves.')
|
|
2788
|
+
.argument('<instanceId>', 'Pipeline instance ID')
|
|
2789
|
+
.argument('<stepId>', 'Step instance ID')
|
|
2790
|
+
.requiredOption('--reason <text>', 'Revision feedback explaining what needs to change')
|
|
2791
|
+
.action(async (instanceId, stepId, opts) => {
|
|
2792
|
+
try {
|
|
2793
|
+
const data = await api('POST', `/api/pipeline-instances/${instanceId}/steps/${stepId}/revise`, {
|
|
2794
|
+
reason: opts.reason,
|
|
2795
|
+
});
|
|
2796
|
+
console.log(chalk.green(`Revision requested: ${data.step?.step_name || stepId}`));
|
|
2797
|
+
console.log(` Status: ${data.step?.status}`);
|
|
2798
|
+
}
|
|
2799
|
+
catch (err) {
|
|
2800
|
+
console.error(chalk.red(err.message));
|
|
2801
|
+
process.exit(1);
|
|
2802
|
+
}
|
|
2803
|
+
});
|
|
1999
2804
|
step
|
|
2000
2805
|
.command('skip')
|
|
2001
2806
|
.description('Skip a step')
|
|
@@ -2400,6 +3205,82 @@ executions
|
|
|
2400
3205
|
process.exit(1);
|
|
2401
3206
|
}
|
|
2402
3207
|
});
|
|
3208
|
+
executions
|
|
3209
|
+
.command('dispatch')
|
|
3210
|
+
.description('Dispatch a pending execution, claiming it for container launch. Moves the execution from "pending" to "dispatched". Typically called by the execution dispatcher, not manually.')
|
|
3211
|
+
.argument('<id>', 'Execution ID')
|
|
3212
|
+
.action(async (id) => {
|
|
3213
|
+
try {
|
|
3214
|
+
const data = await api('POST', `/api/executions/${id}/dispatch`);
|
|
3215
|
+
console.log(chalk.green(`Execution dispatched: ${data.execution?.id || id}`));
|
|
3216
|
+
console.log(` Status: ${data.execution?.status}`);
|
|
3217
|
+
}
|
|
3218
|
+
catch (err) {
|
|
3219
|
+
console.error(chalk.red(err.message));
|
|
3220
|
+
process.exit(1);
|
|
3221
|
+
}
|
|
3222
|
+
});
|
|
3223
|
+
executions
|
|
3224
|
+
.command('complete')
|
|
3225
|
+
.description('Mark an execution as completed with optional results. Typically called by the agent container when work is done. Use "ex update" for partial updates during execution.')
|
|
3226
|
+
.argument('<id>', 'Execution ID')
|
|
3227
|
+
.option('-o, --output <json>', 'Final output data as JSON')
|
|
3228
|
+
.option('--cost <usd>', 'Total cost in USD', parseNumber)
|
|
3229
|
+
.option('--input-tokens <n>', 'Total input token count', parseNumber)
|
|
3230
|
+
.option('--output-tokens <n>', 'Total output token count', parseNumber)
|
|
3231
|
+
.option('--duration <ms>', 'Total duration in milliseconds', parseNumber)
|
|
3232
|
+
.action(async (id, opts) => {
|
|
3233
|
+
try {
|
|
3234
|
+
const body = {};
|
|
3235
|
+
if (opts.output)
|
|
3236
|
+
body.outputData = JSON.parse(opts.output);
|
|
3237
|
+
if (opts.cost !== undefined)
|
|
3238
|
+
body.costUsd = opts.cost;
|
|
3239
|
+
if (opts.inputTokens !== undefined)
|
|
3240
|
+
body.inputTokens = opts.inputTokens;
|
|
3241
|
+
if (opts.outputTokens !== undefined)
|
|
3242
|
+
body.outputTokens = opts.outputTokens;
|
|
3243
|
+
if (opts.duration !== undefined)
|
|
3244
|
+
body.durationMs = opts.duration;
|
|
3245
|
+
await api('POST', `/api/executions/${id}/complete`, body);
|
|
3246
|
+
console.log(chalk.green('Execution completed'));
|
|
3247
|
+
}
|
|
3248
|
+
catch (err) {
|
|
3249
|
+
console.error(chalk.red(err.message));
|
|
3250
|
+
process.exit(1);
|
|
3251
|
+
}
|
|
3252
|
+
});
|
|
3253
|
+
executions
|
|
3254
|
+
.command('fail')
|
|
3255
|
+
.description('Mark an execution as failed with an error message. Typically called by the agent container on unrecoverable errors. Partial output and cost data can still be recorded.')
|
|
3256
|
+
.argument('<id>', 'Execution ID')
|
|
3257
|
+
.requiredOption('--error <text>', 'Error message describing the failure')
|
|
3258
|
+
.option('-o, --output <json>', 'Partial output data as JSON (any work completed before failure)')
|
|
3259
|
+
.option('--cost <usd>', 'Cost incurred in USD', parseNumber)
|
|
3260
|
+
.option('--input-tokens <n>', 'Input token count', parseNumber)
|
|
3261
|
+
.option('--output-tokens <n>', 'Output token count', parseNumber)
|
|
3262
|
+
.option('--duration <ms>', 'Duration in milliseconds before failure', parseNumber)
|
|
3263
|
+
.action(async (id, opts) => {
|
|
3264
|
+
try {
|
|
3265
|
+
const body = { error: opts.error };
|
|
3266
|
+
if (opts.output)
|
|
3267
|
+
body.outputData = JSON.parse(opts.output);
|
|
3268
|
+
if (opts.cost !== undefined)
|
|
3269
|
+
body.costUsd = opts.cost;
|
|
3270
|
+
if (opts.inputTokens !== undefined)
|
|
3271
|
+
body.inputTokens = opts.inputTokens;
|
|
3272
|
+
if (opts.outputTokens !== undefined)
|
|
3273
|
+
body.outputTokens = opts.outputTokens;
|
|
3274
|
+
if (opts.duration !== undefined)
|
|
3275
|
+
body.durationMs = opts.duration;
|
|
3276
|
+
await api('POST', `/api/executions/${id}/fail`, body);
|
|
3277
|
+
console.log(chalk.green('Execution marked as failed'));
|
|
3278
|
+
}
|
|
3279
|
+
catch (err) {
|
|
3280
|
+
console.error(chalk.red(err.message));
|
|
3281
|
+
process.exit(1);
|
|
3282
|
+
}
|
|
3283
|
+
});
|
|
2403
3284
|
executions
|
|
2404
3285
|
.command('cloudwatch-logs')
|
|
2405
3286
|
.alias('cw')
|