@series-a/cli 0.1.0 → 0.2.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/CHANGELOG.md CHANGED
@@ -4,8 +4,19 @@ All notable changes to this package are documented here. This project follows
4
4
  [Semantic Versioning](https://semver.org/) and uses
5
5
  [Changesets](https://github.com/changesets/changesets) to manage releases.
6
6
 
7
+ ## 0.2.0 — Write coverage expansion
8
+
9
+ - `inbox` — added `send`, `reply` (dry-run by default, `--confirm` to dispatch, 7,900 char guard), `mark-read` (bulk), `snooze` (`--for`, `--until`, `--clear`).
10
+ - `sequences` — added `create`, `update`, `add-step` (per-action char limits: invite 200 / DM 8000 / comment 1250), `clone` (with branch remap), `validate`.
11
+ - `content` — full CRUD: `create`, `update`, `delete` alongside existing `list`/`show`/`publish`.
12
+ - `calendar` — new resource group: `list`, `create`, `update`, `delete` against `calendar_posts`.
13
+ - `signals` — new resource group backed by `intelligence_signals`: `list`, `create`, `update`, `delete`.
14
+ - `pipelines` — alias of `opportunities` with `list`, `create`, `update`, `delete`.
15
+ - `agents` — added `create`, `update`, `set-status`, `delete`, `recent-runs`.
16
+
7
17
  ## 0.1.0 — Initial release
8
18
 
19
+
9
20
  - `advisoros login` / `logout` / `whoami` / `profiles` with multi-workspace profile support.
10
21
  - `leads` — `list`, `get`, `set-status`, `import` (CSV/JSON/JSONL, auto-chunked), `verify`.
11
22
  - `campaigns` — `list`, `show`, `create`, `add-leads`, `launch`, `pause`, `resume`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@series-a/cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "AdvisorOS command-line interface — drive leads, campaigns, sequences and agents from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -222,7 +222,7 @@ export function registerCampaigns(program) {
222
222
  }
223
223
 
224
224
  export function registerSequences(program) {
225
- const s = program.command("sequences").description("View LinkedIn sequences");
225
+ const s = program.command("sequences").description("Manage LinkedIn sequences");
226
226
 
227
227
  withCommonOpts(s.command("list"))
228
228
  .description("List sequences")
@@ -234,7 +234,7 @@ export function registerSequences(program) {
234
234
  emit(this, r, (d) => {
235
235
  process.stdout.write(table(
236
236
  ["id", "name", "status"],
237
- (d.sequences || []).map((s) => [truncate(s.id, 8), truncate(s.name, 40), s.status]),
237
+ (d.sequences || []).map((s) => [s.id, truncate(s.name, 40), s.status]),
238
238
  ) + `\n${d.sequences?.length ?? 0} rows\n`);
239
239
  });
240
240
  } catch (e) { fail(e); }
@@ -257,10 +257,129 @@ export function registerSequences(program) {
257
257
  });
258
258
  } catch (e) { fail(e); }
259
259
  });
260
+
261
+ withCommonOpts(s.command("create"))
262
+ .description("Create a new (empty, draft) sequence")
263
+ .requiredOption("--name <name>", "sequence name")
264
+ .option("--description <text>", "description")
265
+ .option("--trigger <type>", "trigger_type", "manual")
266
+ .option("--stage <stage>", "launch_stage (liberate|architect|uncover|navigate|catalyse|harness|review|graduation)")
267
+ .action(async function (opts) {
268
+ try {
269
+ const r = await call("sequences", "create", {
270
+ name: opts.name,
271
+ description: opts.description,
272
+ trigger_type: opts.trigger,
273
+ launch_stage: opts.stage,
274
+ }, ctxFrom(this));
275
+ emit(this, r, (d) => {
276
+ process.stdout.write(`✓ Created sequence ${d.sequence?.id}\n name: ${d.sequence?.name}\n status: ${d.sequence?.status}\n`);
277
+ });
278
+ } catch (e) { fail(e); }
279
+ });
280
+
281
+ withCommonOpts(s.command("update <id>"))
282
+ .description("Update sequence metadata (name/description/status/stage)")
283
+ .option("--name <name>", "rename")
284
+ .option("--description <text>", "set description (pass empty string to clear)")
285
+ .option("--status <s>", "draft|active|paused|archived")
286
+ .option("--stage <stage>", "launch_stage (pass '' to clear)")
287
+ .action(async function (id, opts) {
288
+ try {
289
+ const params = { id };
290
+ if (opts.name != null) params.name = opts.name;
291
+ if (opts.description != null) params.description = opts.description || null;
292
+ if (opts.status != null) params.status = opts.status;
293
+ if (opts.stage != null) params.launch_stage = opts.stage || null;
294
+ const r = await call("sequences", "update", params, ctxFrom(this));
295
+ emit(this, r, (d) => {
296
+ process.stdout.write(`✓ Updated ${d.sequence?.id}\n name: ${d.sequence?.name}\n status: ${d.sequence?.status}\n stage: ${d.sequence?.launch_stage || "—"}\n`);
297
+ });
298
+ } catch (e) { fail(e); }
299
+ });
300
+
301
+ withCommonOpts(s.command("add-step <id>"))
302
+ .description("Append a step to a sequence")
303
+ .requiredOption("--action <action>", "viewProfile|addReaction|addComment|sendInvitation|sendMessage|wait")
304
+ .option("--text <message>", "message_content (required for addComment/sendInvitation/sendMessage)")
305
+ .option("--delay <days>", "delay_days before this step", "0")
306
+ .option("--variant <v>", "variant label", "A")
307
+ .option("--step-type <t>", "action|branch|wait", "action")
308
+ .option("--channel <c>", "channel", "linkedin")
309
+ .option("--branch <label>", "branch_label (for branching steps)")
310
+ .option("--parent-step <id>", "parent_step_id (for branches)")
311
+ .option("--order <n>", "explicit step_order (defaults to max+1)")
312
+ .action(async function (id, opts) {
313
+ try {
314
+ const r = await call("sequences", "add_step", {
315
+ sequence_id: id,
316
+ action: opts.action,
317
+ message_content: opts.text,
318
+ delay_days: Number(opts.delay) || 0,
319
+ variant: opts.variant,
320
+ step_type: opts.stepType,
321
+ channel: opts.channel,
322
+ branch_label: opts.branch,
323
+ parent_step_id: opts.parentStep,
324
+ step_order: opts.order ? Number(opts.order) : undefined,
325
+ }, ctxFrom(this));
326
+ emit(this, r, (d) => {
327
+ const st = d.step || {};
328
+ process.stdout.write(`✓ Added step ${st.id}\n #${st.step_order} ${st.action} (${st.channel}) delay=${st.delay_days}d variant=${st.variant}\n`);
329
+ });
330
+ } catch (e) { fail(e); }
331
+ });
332
+
333
+ withCommonOpts(s.command("clone <id>"))
334
+ .description("Duplicate a sequence (and all its steps) as a new draft")
335
+ .option("--name <name>", "name for the clone (default: '<src> (copy)')")
336
+ .action(async function (id, opts) {
337
+ try {
338
+ const r = await call("sequences", "clone", { id, name: opts.name }, ctxFrom(this));
339
+ emit(this, r, (d) => {
340
+ process.stdout.write(`✓ Cloned to ${d.sequence?.id}\n name: ${d.sequence?.name}\n copied steps: ${d.cloned_steps}\n`);
341
+ });
342
+ } catch (e) { fail(e); }
343
+ });
344
+
345
+ withCommonOpts(s.command("validate <id>"))
346
+ .description("Run safety + char-limit validation on a sequence (same checks as 'campaigns launch')")
347
+ .action(async function (id) {
348
+ try {
349
+ const r = await call("sequences", "validate", { id }, ctxFrom(this));
350
+ emit(this, r, (d) => {
351
+ const status = d.ok ? "✓ PASS" : "✖ FAIL";
352
+ process.stdout.write(`${status} — ${d.step_count} step(s), ${d.errors.length} error(s), ${d.warnings.length} warning(s)\n`);
353
+ for (const e of d.errors) {
354
+ process.stdout.write(` ERROR #${e.step_order ?? "?"} ${e.action ?? ""} ${e.kind}` +
355
+ (e.field ? ` [${e.field}]` : "") +
356
+ (e.length != null ? ` ${e.length}/${e.limit} chars (+${e.excess})` : "") + "\n");
357
+ }
358
+ for (const w of d.warnings) {
359
+ process.stdout.write(` WARN #${w.step_order} ${w.action} ${w.kind}` +
360
+ (w.field ? ` [${w.field}]` : "") +
361
+ ` ${w.length}/${w.limit} chars (+${w.excess})\n`);
362
+ }
363
+ if (!d.ok) process.exitCode = 1;
364
+ });
365
+ } catch (e) { fail(e); }
366
+ });
367
+ }
368
+
369
+ function parseSnoozeDuration(s) {
370
+ // Accepts e.g. "30m", "2h", "1d", "45" (minutes). Returns minutes (int).
371
+ if (!s) return null;
372
+ const m = String(s).trim().match(/^(\d+)\s*([mhd]?)$/i);
373
+ if (!m) return null;
374
+ const n = parseInt(m[1], 10);
375
+ if (!Number.isFinite(n) || n <= 0) return null;
376
+ const unit = (m[2] || "m").toLowerCase();
377
+ const mult = unit === "h" ? 60 : unit === "d" ? 60 * 24 : 1;
378
+ return n * mult;
260
379
  }
261
380
 
262
381
  export function registerInbox(program) {
263
- const inbox = program.command("inbox").description("Inspect unified inbox");
382
+ const inbox = program.command("inbox").description("Inspect and act on the unified inbox");
264
383
 
265
384
  withCommonOpts(inbox.command("threads"))
266
385
  .description("List recent inbox messages")
@@ -290,46 +409,254 @@ export function registerInbox(program) {
290
409
  } catch (e) { fail(e); }
291
410
  });
292
411
 
412
+ const sendAction = async function (leadId, opts) {
413
+ try {
414
+ const text = String(opts.text || "").trim();
415
+ if (!text) { fail(new Error("--text cannot be empty")); return; }
416
+ if (text.length > 7900) {
417
+ fail(new Error(`--text is ${text.length} chars; hard cap is 7900`));
418
+ return;
419
+ }
420
+ if (!opts.confirm && !opts.yes) {
421
+ process.stderr.write(
422
+ `Refusing to send — re-run with --confirm to actually dispatch.\n` +
423
+ ` lead: ${leadId}\n` +
424
+ ` sender: ${opts.sender || "(auto)"}\n` +
425
+ ` chars: ${text.length}\n` +
426
+ ` text: ${truncate(text.replace(/\s+/g, " "), 120)}\n`,
427
+ );
428
+ process.exit(2);
429
+ }
430
+ const r = await call("inbox", "send_dm", {
431
+ lead_id: leadId,
432
+ text,
433
+ sender_account_id: opts.sender,
434
+ }, ctxFrom(this));
435
+ emit(this, r, (d) => {
436
+ const s = d.sent || {};
437
+ const b = s.budget || {};
438
+ process.stdout.write(
439
+ `✓ ${s.channel_kind === "linkedin_invite" ? "Invite note" : "DM"} sent to ${s.lead_name || leadId}\n` +
440
+ ` sender: ${s.sender_account_id}\n` +
441
+ ` chars: ${s.chars}\n` +
442
+ ` budget: invites ${b.invites_sent}/${b.invites_cap}, messages ${b.messages_sent}/${b.messages_cap}\n` +
443
+ (s.external_id ? ` msg id: ${s.external_id}\n` : ""),
444
+ );
445
+ });
446
+ } catch (e) { fail(e); }
447
+ };
448
+
293
449
  withCommonOpts(inbox.command("send <leadId>"))
294
450
  .description("Send a one-off LinkedIn DM (or first-touch invite note) to a lead")
295
451
  .requiredOption("--text <message>", "message body (auto-truncated to channel limit)")
296
452
  .option("--sender <accountId>", "sender_account_id (defaults to most-recently-updated linkedin_tool sender)")
297
- .action(async function (leadId, opts) {
453
+ .option("--confirm", "actually dispatch (otherwise prints a dry-run preview and exits)")
454
+ .action(sendAction);
455
+
456
+ // `reply` is a semantic alias for `send` — the server auto-resolves the most
457
+ // recent LinkedIn thread for (lead, sender) and routes via sendMessage.
458
+ withCommonOpts(inbox.command("reply <leadId>"))
459
+ .description("Reply on the most recent LinkedIn thread for a lead (alias of `send`; falls back to invite note if no thread)")
460
+ .requiredOption("--text <message>", "reply body (auto-truncated to channel limit)")
461
+ .option("--sender <accountId>", "sender_account_id (defaults to most-recently-updated linkedin_tool sender)")
462
+ .option("--confirm", "actually dispatch (otherwise prints a dry-run preview and exits)")
463
+ .action(sendAction);
464
+
465
+ withCommonOpts(inbox.command("mark-read <messageIds...>"))
466
+ .description("Mark one or more inbox messages as read")
467
+ .action(async function (messageIds) {
298
468
  try {
299
- const r = await call("inbox", "send_dm", {
300
- lead_id: leadId,
301
- text: opts.text,
302
- sender_account_id: opts.sender,
469
+ if (!messageIds?.length) { fail(new Error("At least one messageId required")); return; }
470
+ const r = await call("inbox", "mark_read", { message_ids: messageIds }, ctxFrom(this));
471
+ emit(this, r, (d) => {
472
+ process.stdout.write(`✓ Marked ${d.marked_read}/${d.requested} message(s) read\n`);
473
+ });
474
+ } catch (e) { fail(e); }
475
+ });
476
+
477
+ withCommonOpts(inbox.command("snooze <messageId>"))
478
+ .description("Snooze a message until a future time")
479
+ .option("--for <duration>", "duration like 30m, 2h, 1d (default unit: minutes)")
480
+ .option("--until <iso>", "explicit ISO datetime (e.g. 2026-05-27T09:00:00Z)")
481
+ .option("--clear", "clear an existing snooze on this message")
482
+ .action(async function (messageId, opts) {
483
+ try {
484
+ const params = { message_id: messageId };
485
+ if (opts.clear) {
486
+ params.clear = true;
487
+ } else if (opts.until) {
488
+ params.until = opts.until;
489
+ } else if (opts.for) {
490
+ const mins = parseSnoozeDuration(opts.for);
491
+ if (mins == null) { fail(new Error("Invalid --for value; use e.g. 30m, 2h, 1d")); return; }
492
+ params.minutes = mins;
493
+ } else {
494
+ fail(new Error("Provide --for <duration>, --until <iso>, or --clear"));
495
+ return;
496
+ }
497
+ const r = await call("inbox", "snooze", params, ctxFrom(this));
498
+ emit(this, r, (d) => {
499
+ if (opts.clear) process.stdout.write(`✓ Snooze cleared on ${d.snoozed?.id}\n`);
500
+ else process.stdout.write(`✓ Snoozed ${d.snoozed?.id} until ${d.snoozed?.snoozed_until}\n`);
501
+ });
502
+ } catch (e) { fail(e); }
503
+ });
504
+ }
505
+
506
+ export function registerKnowledge(program) {
507
+ const k = program.command("knowledge").description("Manage the workspace knowledge base");
508
+
509
+ withCommonOpts(k.command("list"))
510
+ .description("List active knowledge items")
511
+ .option("--category <c>", "filter by category (positioning, frameworks, case-studies, …)")
512
+ .option("--search <q>", "search name or summary")
513
+ .option("--limit <n>", "max rows", "50")
514
+ .action(async function () {
515
+ try {
516
+ const o = this.optsWithGlobals();
517
+ const r = await call("knowledge", "list", {
518
+ category: o.category, search: o.search, limit: Number(o.limit),
303
519
  }, ctxFrom(this));
304
520
  emit(this, r, (d) => {
305
- const s = d.sent || {};
306
- const b = s.budget || {};
521
+ process.stdout.write(table(
522
+ ["id", "type", "category", "name", "tags"],
523
+ (d.items || []).map((i) => [
524
+ truncate(i.id, 8), i.type, i.category,
525
+ truncate(i.name, 40),
526
+ truncate((i.tags || []).join(","), 30),
527
+ ]),
528
+ ) + `\n${d.items?.length ?? 0} rows\n`);
529
+ });
530
+ } catch (e) { fail(e); }
531
+ });
532
+
533
+ withCommonOpts(k.command("get <id>"))
534
+ .description("Show a knowledge item (full JSON, including extracted_text)")
535
+ .action(async function (id) {
536
+ try {
537
+ const r = await call("knowledge", "get", { id }, ctxFrom(this));
538
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.item, null, 2) + "\n"));
539
+ } catch (e) { fail(e); }
540
+ });
541
+
542
+ // `add` mirrors the web UI flow: insert → (URL: firecrawl-scrape) → AI auto-tag.
543
+ // Choose exactly one source: --url, --text, or --file.
544
+ withCommonOpts(k.command("add"))
545
+ .description("Add a knowledge item (URL is auto-scraped + AI-classified, same as the web UI)")
546
+ .option("--url <url>", "scrape this URL via Firecrawl")
547
+ .option("--text <body>", "inline text body")
548
+ .option("--file <path>", "read body from a local text/markdown file")
549
+ .option("--name <title>", "display title (defaults to URL hostname or filename)")
550
+ .option("--category <c>", "seed category (AI may re-classify)")
551
+ .option("--tags <csv>", "seed tags, comma-separated (AI merges with classifier tags)")
552
+ .option("--visibility <v>", "workspace | private (default: workspace)")
553
+ .option("--no-summarize", "skip AI auto-tag/classify (raw insert only)")
554
+ .option("--no-scrape", "skip Firecrawl scrape for --url (URL stored as bookmark)")
555
+ .action(async function (opts) {
556
+ try {
557
+ const sources = [opts.url, opts.text, opts.file].filter(Boolean);
558
+ if (sources.length !== 1) {
559
+ return fail(new Error("Provide exactly one of --url, --text, or --file"));
560
+ }
561
+ let extracted_text;
562
+ let derivedName = opts.name;
563
+ if (opts.file) {
564
+ extracted_text = await readFile(opts.file, "utf8");
565
+ if (!derivedName) derivedName = opts.file.split(/[\\/]/).pop();
566
+ } else if (opts.text) {
567
+ extracted_text = opts.text;
568
+ }
569
+ const params = {
570
+ content_url: opts.url || undefined,
571
+ extracted_text,
572
+ name: derivedName,
573
+ category: opts.category,
574
+ tags: opts.tags ? opts.tags.split(",").map((s) => s.trim()).filter(Boolean) : undefined,
575
+ visibility: opts.visibility,
576
+ skip_summarize: opts.summarize === false ? true : undefined,
577
+ skip_scrape: opts.scrape === false ? true : undefined,
578
+ };
579
+ const r = await call("knowledge", "add", params, ctxFrom(this));
580
+ emit(this, r, (d) => {
581
+ const it = d.item || {};
582
+ process.stdout.write(
583
+ `✓ Added knowledge item ${it.id}\n` +
584
+ ` name: ${it.name}\n` +
585
+ ` type: ${it.type}\n` +
586
+ ` category: ${it.category}\n` +
587
+ ` tags: ${(it.tags || []).join(", ") || "(none)"}\n` +
588
+ (it.summary ? ` summary: ${truncate(it.summary, 240)}\n` : "") +
589
+ (d.ai?.success === false ? ` ⚠ AI classify: ${d.ai.error}\n` : ""),
590
+ );
591
+ });
592
+ } catch (e) { fail(e); }
593
+ });
594
+
595
+ withCommonOpts(k.command("update <id>"))
596
+ .description("Patch a knowledge item")
597
+ .option("--name <s>", "rename")
598
+ .option("--summary <s>", "set summary")
599
+ .option("--category <c>", "set category")
600
+ .option("--tags <csv>", "replace tags (comma-separated)")
601
+ .option("--visibility <v>", "workspace | private")
602
+ .action(async function (id, opts) {
603
+ try {
604
+ const patch = {
605
+ id,
606
+ name: opts.name,
607
+ summary: opts.summary,
608
+ category: opts.category,
609
+ tags: opts.tags ? opts.tags.split(",").map((s) => s.trim()).filter(Boolean) : undefined,
610
+ visibility: opts.visibility,
611
+ };
612
+ const r = await call("knowledge", "update", patch, ctxFrom(this));
613
+ emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.item?.id}\n`));
614
+ } catch (e) { fail(e); }
615
+ });
616
+
617
+ withCommonOpts(k.command("reclassify <id>"))
618
+ .description("Re-run AI auto-tag/classify (same as the UI 'sparkles' button)")
619
+ .action(async function (id) {
620
+ try {
621
+ const r = await call("knowledge", "reclassify", { id }, ctxFrom(this));
622
+ emit(this, r, (d) => {
623
+ const it = d.item || {};
307
624
  process.stdout.write(
308
- `✓ ${s.channel_kind === "linkedin_invite" ? "Invite note" : "DM"} sent to ${s.lead_name || leadId}\n` +
309
- ` sender: ${s.sender_account_id}\n` +
310
- ` chars: ${s.chars}\n` +
311
- ` budget: invites ${b.invites_sent}/${b.invites_cap}, messages ${b.messages_sent}/${b.messages_cap}\n` +
312
- (s.external_id ? ` msg id: ${s.external_id}\n` : ""),
625
+ `✓ Reclassified ${it.id}\n` +
626
+ ` category: ${it.category}\n` +
627
+ ` tags: ${(it.tags || []).join(", ") || "(none)"}\n` +
628
+ (it.summary ? ` summary: ${truncate(it.summary, 240)}\n` : ""),
313
629
  );
314
630
  });
315
631
  } catch (e) { fail(e); }
316
632
  });
633
+
634
+ withCommonOpts(k.command("deactivate <id>"))
635
+ .description("Soft-delete (sets is_active = false)")
636
+ .action(async function (id) {
637
+ try {
638
+ const r = await call("knowledge", "deactivate", { id }, ctxFrom(this));
639
+ emit(this, r, (d) => process.stdout.write(`✓ Deactivated ${d.item?.id}\n`));
640
+ } catch (e) { fail(e); }
641
+ });
317
642
  }
318
643
 
319
644
  export function registerContent(program) {
320
- const c = program.command("content").description("Browse and publish content");
645
+ const c = program.command("content").description("Browse, create, schedule and publish content");
321
646
 
322
647
  withCommonOpts(c.command("list"))
323
648
  .description("List content items")
324
- .option("--status <s>", "filter by status")
325
- .option("--type <t>", "filter by type")
649
+ .option("--status <s>", "filter by status (draft|scheduled|published|…)")
650
+ .option("--type <t>", "filter by type (article|linkedin_post|…)")
651
+ .option("--approval <s>", "filter by approval_status")
326
652
  .option("--search <q>", "search title or body")
327
653
  .option("--limit <n>", "max rows", "50")
328
654
  .action(async function () {
329
655
  try {
330
656
  const o = this.optsWithGlobals();
331
657
  const r = await call("content", "list", {
332
- status: o.status, type: o.type, search: o.search, limit: Number(o.limit),
658
+ status: o.status, type: o.type, approval_status: o.approval,
659
+ search: o.search, limit: Number(o.limit),
333
660
  }, ctxFrom(this));
334
661
  emit(this, r, (d) => {
335
662
  process.stdout.write(table(
@@ -349,6 +676,72 @@ export function registerContent(program) {
349
676
  } catch (e) { fail(e); }
350
677
  });
351
678
 
679
+ withCommonOpts(c.command("create"))
680
+ .description("Create a new content item")
681
+ .requiredOption("--title <t>", "title")
682
+ .option("--type <t>", "article | linkedin_post | newsletter | …", "linkedin_post")
683
+ .option("--body <text>", "inline body")
684
+ .option("--body-file <path>", "read body from a local file")
685
+ .option("--platforms <csv>", "target platforms (linkedin,blog,…)")
686
+ .option("--tags <csv>", "tags")
687
+ .option("--scheduled <ts>", "ISO timestamp (sets status=scheduled)")
688
+ .option("--campaign <id>", "campaign_id")
689
+ .action(async function (opts) {
690
+ try {
691
+ let body = opts.body;
692
+ if (opts.bodyFile) body = await readFile(opts.bodyFile, "utf8");
693
+ const r = await call("content", "create", {
694
+ title: opts.title, type: opts.type, body: body ?? "",
695
+ target_platforms: opts.platforms ? opts.platforms.split(",").map((s) => s.trim()) : undefined,
696
+ tags: opts.tags ? opts.tags.split(",").map((s) => s.trim()) : undefined,
697
+ scheduled_date: opts.scheduled, status: opts.scheduled ? "scheduled" : undefined,
698
+ campaign_id: opts.campaign,
699
+ }, ctxFrom(this));
700
+ emit(this, r, (d) => process.stdout.write(`✓ Created content ${d.content?.id}\n`));
701
+ } catch (e) { fail(e); }
702
+ });
703
+
704
+ withCommonOpts(c.command("update <id>"))
705
+ .description("Patch a content item")
706
+ .option("--title <t>")
707
+ .option("--body <text>")
708
+ .option("--body-file <path>")
709
+ .option("--status <s>")
710
+ .option("--platforms <csv>")
711
+ .option("--tags <csv>")
712
+ .option("--scheduled <ts>")
713
+ .action(async function (id, opts) {
714
+ try {
715
+ let body = opts.body;
716
+ if (opts.bodyFile) body = await readFile(opts.bodyFile, "utf8");
717
+ const r = await call("content", "update", {
718
+ id, title: opts.title, body, status: opts.status,
719
+ target_platforms: opts.platforms ? opts.platforms.split(",").map((s) => s.trim()) : undefined,
720
+ tags: opts.tags ? opts.tags.split(",").map((s) => s.trim()) : undefined,
721
+ scheduled_date: opts.scheduled,
722
+ }, ctxFrom(this));
723
+ emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.content?.id}\n`));
724
+ } catch (e) { fail(e); }
725
+ });
726
+
727
+ withCommonOpts(c.command("schedule <id> <isoTimestamp>"))
728
+ .description("Schedule a content item")
729
+ .action(async function (id, ts) {
730
+ try {
731
+ const r = await call("content", "schedule", { id, scheduled_date: ts }, ctxFrom(this));
732
+ emit(this, r, (d) => process.stdout.write(`✓ Scheduled ${d.content?.id} → ${d.content?.scheduled_date}\n`));
733
+ } catch (e) { fail(e); }
734
+ });
735
+
736
+ withCommonOpts(c.command("approve <id>"))
737
+ .description("Approve a content item (approval_status=approved)")
738
+ .action(async function (id) {
739
+ try {
740
+ const r = await call("content", "approve", { id }, ctxFrom(this));
741
+ emit(this, r, (d) => process.stdout.write(`✓ Approved ${d.content?.id}\n`));
742
+ } catch (e) { fail(e); }
743
+ });
744
+
352
745
  withCommonOpts(c.command("publish <id>"))
353
746
  .description("Dispatch publication via agent-content-posting")
354
747
  .action(async function (id) {
@@ -357,25 +750,456 @@ export function registerContent(program) {
357
750
  emit(this, r, (d) => process.stdout.write(JSON.stringify(d.publish, null, 2) + "\n"));
358
751
  } catch (e) { fail(e); }
359
752
  });
753
+
754
+ withCommonOpts(c.command("delete <id>"))
755
+ .description("Delete a content item")
756
+ .action(async function (id) {
757
+ try {
758
+ const r = await call("content", "delete", { id }, ctxFrom(this));
759
+ emit(this, r, (d) => process.stdout.write(`✓ Deleted ${d.deleted}\n`));
760
+ } catch (e) { fail(e); }
761
+ });
762
+ }
763
+
764
+ export function registerCalendar(program) {
765
+ const cal = program.command("calendar").description("Manage the content calendar");
766
+
767
+ withCommonOpts(cal.command("list"))
768
+ .description("List scheduled calendar posts")
769
+ .option("--from <date>", "YYYY-MM-DD (inclusive)")
770
+ .option("--to <date>", "YYYY-MM-DD (inclusive)")
771
+ .option("--platform <p>", "filter by platform")
772
+ .option("--status <s>", "filter by status")
773
+ .option("--campaign <id>", "filter by campaign_id")
774
+ .option("--limit <n>", "max rows", "100")
775
+ .action(async function () {
776
+ try {
777
+ const o = this.optsWithGlobals();
778
+ const r = await call("calendar", "list", {
779
+ from: o.from, to: o.to, platform: o.platform, status: o.status,
780
+ campaign_id: o.campaign, limit: Number(o.limit),
781
+ }, ctxFrom(this));
782
+ emit(this, r, (d) => {
783
+ process.stdout.write(table(
784
+ ["id", "date", "time", "platform", "status", "title"],
785
+ (d.posts || []).map((p) => [
786
+ truncate(p.id, 8), p.scheduled_date, p.scheduled_time ?? "—",
787
+ p.platform, p.status, truncate(p.title, 50),
788
+ ]),
789
+ ) + `\n${d.posts?.length ?? 0} rows\n`);
790
+ });
791
+ } catch (e) { fail(e); }
792
+ });
793
+
794
+ withCommonOpts(cal.command("show <id>"))
795
+ .description("Show a calendar post")
796
+ .action(async function (id) {
797
+ try {
798
+ const r = await call("calendar", "get", { id }, ctxFrom(this));
799
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.post, null, 2) + "\n"));
800
+ } catch (e) { fail(e); }
801
+ });
802
+
803
+ withCommonOpts(cal.command("create"))
804
+ .description("Create a calendar post")
805
+ .requiredOption("--title <t>", "title")
806
+ .requiredOption("--date <YYYY-MM-DD>", "scheduled date")
807
+ .option("--time <HH:MM>", "scheduled time")
808
+ .option("--platform <p>", "linkedin | blog | twitter | …", "linkedin")
809
+ .option("--content <text>", "post body")
810
+ .option("--content-file <path>", "read body from a local file")
811
+ .option("--hashtags <csv>", "hashtags (no #)")
812
+ .option("--cta <text>", "call to action")
813
+ .option("--campaign <id>", "campaign_id")
814
+ .option("--status <s>", "draft | scheduled | published", "draft")
815
+ .action(async function (opts) {
816
+ try {
817
+ let content = opts.content;
818
+ if (opts.contentFile) content = await readFile(opts.contentFile, "utf8");
819
+ const r = await call("calendar", "create", {
820
+ title: opts.title, scheduled_date: opts.date, scheduled_time: opts.time,
821
+ platform: opts.platform, content: content ?? "",
822
+ hashtags: opts.hashtags ? opts.hashtags.split(",").map((s) => s.trim()) : undefined,
823
+ cta: opts.cta, campaign_id: opts.campaign, status: opts.status,
824
+ }, ctxFrom(this));
825
+ emit(this, r, (d) => process.stdout.write(`✓ Created calendar post ${d.post?.id}\n`));
826
+ } catch (e) { fail(e); }
827
+ });
828
+
829
+ withCommonOpts(cal.command("update <id>"))
830
+ .description("Patch a calendar post")
831
+ .option("--title <t>")
832
+ .option("--date <YYYY-MM-DD>")
833
+ .option("--time <HH:MM>")
834
+ .option("--content <text>")
835
+ .option("--content-file <path>")
836
+ .option("--status <s>")
837
+ .option("--platform <p>")
838
+ .option("--hashtags <csv>")
839
+ .option("--cta <text>")
840
+ .action(async function (id, opts) {
841
+ try {
842
+ let content = opts.content;
843
+ if (opts.contentFile) content = await readFile(opts.contentFile, "utf8");
844
+ const r = await call("calendar", "update", {
845
+ id, title: opts.title, scheduled_date: opts.date, scheduled_time: opts.time,
846
+ content, status: opts.status, platform: opts.platform,
847
+ hashtags: opts.hashtags ? opts.hashtags.split(",").map((s) => s.trim()) : undefined,
848
+ cta: opts.cta,
849
+ }, ctxFrom(this));
850
+ emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.post?.id}\n`));
851
+ } catch (e) { fail(e); }
852
+ });
853
+
854
+ withCommonOpts(cal.command("delete <id>"))
855
+ .description("Delete a calendar post")
856
+ .action(async function (id) {
857
+ try {
858
+ const r = await call("calendar", "delete", { id }, ctxFrom(this));
859
+ emit(this, r, (d) => process.stdout.write(`✓ Deleted ${d.deleted}\n`));
860
+ } catch (e) { fail(e); }
861
+ });
862
+ }
863
+
864
+ export function registerSignals(program) {
865
+ const s = program.command("signals").description("Read and manage market intelligence signals");
866
+
867
+ withCommonOpts(s.command("list"))
868
+ .description("List intelligence signals")
869
+ .option("--type <t>", "signal_type (news|funding|hiring|…)")
870
+ .option("--category <c>", "filter by category")
871
+ .option("--agent <name>", "filter by agent_name")
872
+ .option("--unread", "only unread")
873
+ .option("--min-score <n>", "min relevance_score")
874
+ .option("--search <q>", "search title/summary")
875
+ .option("--limit <n>", "max rows", "50")
876
+ .action(async function () {
877
+ try {
878
+ const o = this.optsWithGlobals();
879
+ const r = await call("signals", "list", {
880
+ signal_type: o.type, category: o.category, agent_name: o.agent,
881
+ is_read: o.unread ? false : undefined, min_score: o.minScore,
882
+ search: o.search, limit: Number(o.limit),
883
+ }, ctxFrom(this));
884
+ emit(this, r, (d) => {
885
+ process.stdout.write(table(
886
+ ["id", "score", "type", "agent", "title"],
887
+ (d.signals || []).map((g) => [
888
+ truncate(g.id, 8), g.relevance_score ?? "", g.signal_type,
889
+ truncate(g.agent_name, 22), truncate(g.title, 60),
890
+ ]),
891
+ ) + `\n${d.signals?.length ?? 0} rows\n`);
892
+ });
893
+ } catch (e) { fail(e); }
894
+ });
895
+
896
+ withCommonOpts(s.command("show <id>"))
897
+ .description("Show a signal")
898
+ .action(async function (id) {
899
+ try {
900
+ const r = await call("signals", "get", { id }, ctxFrom(this));
901
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.signal, null, 2) + "\n"));
902
+ } catch (e) { fail(e); }
903
+ });
904
+
905
+ withCommonOpts(s.command("create"))
906
+ .description("Create a manual signal")
907
+ .requiredOption("--title <t>", "title")
908
+ .requiredOption("--agent <name>", "agent_name (source)")
909
+ .option("--type <t>", "signal_type", "news")
910
+ .option("--category <c>", "category", "general")
911
+ .option("--url <url>", "source url")
912
+ .option("--summary <text>", "short summary")
913
+ .option("--why <text>", "why_it_matters")
914
+ .option("--action <text>", "recommended_action")
915
+ .option("--score <n>", "relevance_score 0-100", "50")
916
+ .option("--flags <csv>", "trigger_flags (funding,hiring,…)")
917
+ .action(async function (opts) {
918
+ try {
919
+ const r = await call("signals", "create", {
920
+ title: opts.title, agent_name: opts.agent, signal_type: opts.type,
921
+ category: opts.category, url: opts.url, summary: opts.summary,
922
+ why_it_matters: opts.why, recommended_action: opts.action,
923
+ relevance_score: Number(opts.score),
924
+ trigger_flags: opts.flags ? opts.flags.split(",").map((s) => s.trim()) : undefined,
925
+ }, ctxFrom(this));
926
+ emit(this, r, (d) => process.stdout.write(`✓ Created signal ${d.signal?.id}\n`));
927
+ } catch (e) { fail(e); }
928
+ });
929
+
930
+ withCommonOpts(s.command("update <id>"))
931
+ .description("Patch a signal")
932
+ .option("--title <t>")
933
+ .option("--summary <text>")
934
+ .option("--why <text>")
935
+ .option("--action <text>")
936
+ .option("--score <n>")
937
+ .option("--category <c>")
938
+ .option("--sentiment <s>")
939
+ .option("--flags <csv>")
940
+ .action(async function (id, opts) {
941
+ try {
942
+ const r = await call("signals", "update", {
943
+ id, title: opts.title, summary: opts.summary,
944
+ why_it_matters: opts.why, recommended_action: opts.action,
945
+ relevance_score: opts.score ? Number(opts.score) : undefined,
946
+ category: opts.category, sentiment: opts.sentiment,
947
+ trigger_flags: opts.flags ? opts.flags.split(",").map((s) => s.trim()) : undefined,
948
+ }, ctxFrom(this));
949
+ emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.signal?.id}\n`));
950
+ } catch (e) { fail(e); }
951
+ });
952
+
953
+ withCommonOpts(s.command("read <id>"))
954
+ .description("Mark a signal as read (use --unread to flip back)")
955
+ .option("--unread", "mark as unread instead")
956
+ .action(async function (id, opts) {
957
+ try {
958
+ const r = await call("signals", "mark_read", { id, is_read: !opts.unread }, ctxFrom(this));
959
+ emit(this, r, (d) => process.stdout.write(`✓ ${d.signal?.id} → is_read=${d.signal?.is_read}\n`));
960
+ } catch (e) { fail(e); }
961
+ });
962
+
963
+ withCommonOpts(s.command("delete <id>"))
964
+ .description("Delete a signal")
965
+ .action(async function (id) {
966
+ try {
967
+ const r = await call("signals", "delete", { id }, ctxFrom(this));
968
+ emit(this, r, (d) => process.stdout.write(`✓ Deleted ${d.deleted}\n`));
969
+ } catch (e) { fail(e); }
970
+ });
971
+ }
972
+
973
+ export function registerPipelines(program) {
974
+ const p = program.command("pipelines").description("Manage the opportunities / deal pipeline");
975
+
976
+ withCommonOpts(p.command("list"))
977
+ .description("List opportunities (pipeline)")
978
+ .option("--status <s>", "filter by status")
979
+ .option("--stage <s>", "filter by pipeline_stage")
980
+ .option("--outcome <s>", "qualification_outcome (pending|qualified_in|qualified_out)")
981
+ .option("--urgency <s>", "low | medium | high")
982
+ .option("--search <q>", "search title/account")
983
+ .option("--limit <n>", "max rows", "50")
984
+ .action(async function () {
985
+ try {
986
+ const o = this.optsWithGlobals();
987
+ const r = await call("pipelines", "list", {
988
+ status: o.status, pipeline_stage: o.stage,
989
+ qualification_outcome: o.outcome, urgency: o.urgency,
990
+ search: o.search, limit: Number(o.limit),
991
+ }, ctxFrom(this));
992
+ emit(this, r, (d) => {
993
+ process.stdout.write(table(
994
+ ["id", "stage", "score", "value", "account", "title"],
995
+ (d.opportunities || []).map((op) => [
996
+ truncate(op.id, 8), op.pipeline_stage, op.score ?? "",
997
+ `${op.deal_currency ?? ""}${op.deal_value ?? 0}`,
998
+ truncate(op.account, 22), truncate(op.title, 40),
999
+ ]),
1000
+ ) + `\n${d.opportunities?.length ?? 0} rows\n`);
1001
+ });
1002
+ } catch (e) { fail(e); }
1003
+ });
1004
+
1005
+ withCommonOpts(p.command("show <id>"))
1006
+ .description("Show an opportunity")
1007
+ .action(async function (id) {
1008
+ try {
1009
+ const r = await call("pipelines", "get", { id }, ctxFrom(this));
1010
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.opportunity, null, 2) + "\n"));
1011
+ } catch (e) { fail(e); }
1012
+ });
1013
+
1014
+ withCommonOpts(p.command("create"))
1015
+ .description("Create an opportunity")
1016
+ .requiredOption("--title <t>", "title")
1017
+ .option("--account <a>", "account / company")
1018
+ .option("--type <t>", "revenue | risk | …", "revenue")
1019
+ .option("--urgency <u>", "low | medium | high", "medium")
1020
+ .option("--stage <s>", "pipeline_stage", "engaged")
1021
+ .option("--value <n>", "deal_value", "0")
1022
+ .option("--currency <c>", "deal_currency", "USD")
1023
+ .option("--why <text>", "why_it_matters")
1024
+ .option("--action <text>", "recommended_action")
1025
+ .option("--contact <name>", "contact_name")
1026
+ .option("--email <e>", "contact_email")
1027
+ .option("--close <YYYY-MM-DD>", "close_date")
1028
+ .option("--notes <text>", "notes")
1029
+ .action(async function (opts) {
1030
+ try {
1031
+ const r = await call("pipelines", "create", {
1032
+ title: opts.title, account: opts.account, type: opts.type,
1033
+ urgency: opts.urgency, pipeline_stage: opts.stage,
1034
+ deal_value: Number(opts.value), deal_currency: opts.currency,
1035
+ why_it_matters: opts.why, recommended_action: opts.action,
1036
+ contact_name: opts.contact, contact_email: opts.email,
1037
+ close_date: opts.close, notes: opts.notes,
1038
+ }, ctxFrom(this));
1039
+ emit(this, r, (d) => process.stdout.write(`✓ Created opportunity ${d.opportunity?.id}\n`));
1040
+ } catch (e) { fail(e); }
1041
+ });
1042
+
1043
+ withCommonOpts(p.command("update <id>"))
1044
+ .description("Patch an opportunity")
1045
+ .option("--title <t>")
1046
+ .option("--account <a>")
1047
+ .option("--stage <s>", "pipeline_stage")
1048
+ .option("--status <s>")
1049
+ .option("--value <n>", "deal_value")
1050
+ .option("--currency <c>")
1051
+ .option("--urgency <u>")
1052
+ .option("--score <n>")
1053
+ .option("--why <text>")
1054
+ .option("--action <text>")
1055
+ .option("--contact <name>")
1056
+ .option("--email <e>")
1057
+ .option("--close <YYYY-MM-DD>")
1058
+ .option("--notes <text>")
1059
+ .option("--lost-reason <text>")
1060
+ .action(async function (id, opts) {
1061
+ try {
1062
+ const r = await call("pipelines", "update", {
1063
+ id, title: opts.title, account: opts.account,
1064
+ pipeline_stage: opts.stage, status: opts.status,
1065
+ deal_value: opts.value ? Number(opts.value) : undefined,
1066
+ deal_currency: opts.currency, urgency: opts.urgency,
1067
+ score: opts.score ? Number(opts.score) : undefined,
1068
+ why_it_matters: opts.why, recommended_action: opts.action,
1069
+ contact_name: opts.contact, contact_email: opts.email,
1070
+ close_date: opts.close, notes: opts.notes, lost_reason: opts.lostReason,
1071
+ }, ctxFrom(this));
1072
+ emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.opportunity?.id}\n`));
1073
+ } catch (e) { fail(e); }
1074
+ });
1075
+
1076
+ withCommonOpts(p.command("stage <id> <pipelineStage>"))
1077
+ .description("Move an opportunity to a new pipeline_stage")
1078
+ .action(async function (id, stage) {
1079
+ try {
1080
+ const r = await call("pipelines", "update_stage", { id, pipeline_stage: stage }, ctxFrom(this));
1081
+ emit(this, r, (d) => process.stdout.write(`✓ ${d.opportunity?.id} → ${d.opportunity?.pipeline_stage}\n`));
1082
+ } catch (e) { fail(e); }
1083
+ });
1084
+
1085
+ withCommonOpts(p.command("qualify <id> <outcome>"))
1086
+ .description("Set qualification_outcome (pending|qualified_in|qualified_out)")
1087
+ .action(async function (id, outcome) {
1088
+ try {
1089
+ const r = await call("pipelines", "qualify", { id, qualification_outcome: outcome }, ctxFrom(this));
1090
+ emit(this, r, (d) => process.stdout.write(`✓ ${d.opportunity?.id} → ${d.opportunity?.qualification_outcome}\n`));
1091
+ } catch (e) { fail(e); }
1092
+ });
1093
+
1094
+ withCommonOpts(p.command("delete <id>"))
1095
+ .description("Delete an opportunity")
1096
+ .action(async function (id) {
1097
+ try {
1098
+ const r = await call("pipelines", "delete", { id }, ctxFrom(this));
1099
+ emit(this, r, (d) => process.stdout.write(`✓ Deleted ${d.deleted}\n`));
1100
+ } catch (e) { fail(e); }
1101
+ });
360
1102
  }
361
1103
 
362
1104
  export function registerAgents(program) {
363
- const a = program.command("agents").description("List and invoke agents");
1105
+ const a = program.command("agents").description("List, create, configure and invoke agents");
364
1106
 
365
1107
  withCommonOpts(a.command("list"))
366
1108
  .description("List available agents")
1109
+ .option("--scope <s>", "workspace | global | both (default: both)")
1110
+ .option("--status <s>", "filter by status")
1111
+ .option("--layer <l>", "filter by layer")
367
1112
  .action(async function () {
368
1113
  try {
369
- const r = await call("agents", "list", {}, ctxFrom(this));
1114
+ const o = this.optsWithGlobals();
1115
+ const r = await call("agents", "list", {
1116
+ scope: o.scope, status: o.status, layer: o.layer,
1117
+ }, ctxFrom(this));
370
1118
  emit(this, r, (d) => {
371
1119
  process.stdout.write(table(
372
- ["name", "status", "model"],
373
- (d.agents || []).map((a) => [truncate(a.name, 32), a.status ?? "", truncate(a.model, 30)]),
1120
+ ["name", "status", "layer", "schedule"],
1121
+ (d.agents || []).map((a) => [
1122
+ truncate(a.name, 32), a.status ?? "", a.layer ?? "", truncate(a.schedule, 20),
1123
+ ]),
374
1124
  ) + `\n${d.agents?.length ?? 0} rows\n`);
375
1125
  });
376
1126
  } catch (e) { fail(e); }
377
1127
  });
378
1128
 
1129
+ withCommonOpts(a.command("show <nameOrId>"))
1130
+ .description("Show an agent (by id or name)")
1131
+ .action(async function (key) {
1132
+ try {
1133
+ const isUuid = /^[0-9a-f-]{36}$/i.test(key);
1134
+ const r = await call("agents", "get", isUuid ? { id: key } : { name: key }, ctxFrom(this));
1135
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.agent, null, 2) + "\n"));
1136
+ } catch (e) { fail(e); }
1137
+ });
1138
+
1139
+ withCommonOpts(a.command("create"))
1140
+ .description("Create a custom agent")
1141
+ .requiredOption("--name <name>", "agent name (unique per workspace)")
1142
+ .requiredOption("--layer <l>", "layer (intelligence | content | sales | …)")
1143
+ .option("--description <text>")
1144
+ .option("--status <s>", "planned | active | paused", "active")
1145
+ .option("--schedule <cron>", "cron schedule")
1146
+ .option("--type <t>", "agent_type")
1147
+ .option("--icon <icon>", "icon name", "bot")
1148
+ .option("--tools <text>", "tool descriptor")
1149
+ .option("--outputs <csv>", "expected outputs")
1150
+ .option("--scope <s>", "workspace | global (default: workspace)")
1151
+ .option("--pipeline <json>", "pipeline JSON")
1152
+ .action(async function (opts) {
1153
+ try {
1154
+ let pipeline; if (opts.pipeline) try { pipeline = JSON.parse(opts.pipeline); }
1155
+ catch { return fail(new Error("--pipeline must be valid JSON")); }
1156
+ const r = await call("agents", "create", {
1157
+ name: opts.name, layer: opts.layer, description: opts.description,
1158
+ status: opts.status, schedule: opts.schedule, agent_type: opts.type,
1159
+ icon: opts.icon, tools: opts.tools, scope: opts.scope,
1160
+ outputs: opts.outputs ? opts.outputs.split(",").map((s) => s.trim()) : undefined,
1161
+ pipeline,
1162
+ }, ctxFrom(this));
1163
+ emit(this, r, (d) => process.stdout.write(`✓ Created agent ${d.agent?.name} (${d.agent?.id})\n`));
1164
+ } catch (e) { fail(e); }
1165
+ });
1166
+
1167
+ withCommonOpts(a.command("update <id>"))
1168
+ .description("Patch an agent")
1169
+ .option("--name <name>")
1170
+ .option("--description <text>")
1171
+ .option("--status <s>")
1172
+ .option("--schedule <cron>")
1173
+ .option("--layer <l>")
1174
+ .option("--type <t>")
1175
+ .option("--icon <icon>")
1176
+ .option("--tools <text>")
1177
+ .option("--outputs <csv>")
1178
+ .option("--pipeline <json>")
1179
+ .action(async function (id, opts) {
1180
+ try {
1181
+ let pipeline; if (opts.pipeline) try { pipeline = JSON.parse(opts.pipeline); }
1182
+ catch { return fail(new Error("--pipeline must be valid JSON")); }
1183
+ const r = await call("agents", "update", {
1184
+ id, name: opts.name, description: opts.description, status: opts.status,
1185
+ schedule: opts.schedule, layer: opts.layer, agent_type: opts.type,
1186
+ icon: opts.icon, tools: opts.tools,
1187
+ outputs: opts.outputs ? opts.outputs.split(",").map((s) => s.trim()) : undefined,
1188
+ pipeline,
1189
+ }, ctxFrom(this));
1190
+ emit(this, r, (d) => process.stdout.write(`✓ Updated agent ${d.agent?.id}\n`));
1191
+ } catch (e) { fail(e); }
1192
+ });
1193
+
1194
+ withCommonOpts(a.command("set-status <id> <status>"))
1195
+ .description("Change an agent's status (active|paused|planned|…)")
1196
+ .action(async function (id, status) {
1197
+ try {
1198
+ const r = await call("agents", "set_status", { id, status }, ctxFrom(this));
1199
+ emit(this, r, (d) => process.stdout.write(`✓ ${d.agent?.name ?? id} → ${d.agent?.status}\n`));
1200
+ } catch (e) { fail(e); }
1201
+ });
1202
+
379
1203
  withCommonOpts(a.command("run <name>"))
380
1204
  .description("Invoke an agent")
381
1205
  .option("--input <json>", "JSON input payload", "{}")
@@ -397,4 +1221,216 @@ export function registerAgents(program) {
397
1221
  emit(this, r, (d) => process.stdout.write(JSON.stringify(d.job, null, 2) + "\n"));
398
1222
  } catch (e) { fail(e); }
399
1223
  });
1224
+
1225
+ withCommonOpts(a.command("runs"))
1226
+ .description("List recent agent runs in this workspace")
1227
+ .option("--agent <name>", "filter by agent_name")
1228
+ .option("--limit <n>", "max rows", "25")
1229
+ .action(async function () {
1230
+ try {
1231
+ const o = this.optsWithGlobals();
1232
+ const r = await call("agents", "recent_runs", {
1233
+ agent_name: o.agent, limit: Number(o.limit),
1234
+ }, ctxFrom(this));
1235
+ emit(this, r, (d) => {
1236
+ process.stdout.write(table(
1237
+ ["created", "agent", "status", "results", "enriched"],
1238
+ (d.runs || []).map((rr) => [
1239
+ (rr.created_at || "").slice(0, 16).replace("T", " "),
1240
+ truncate(rr.agent_name, 28), rr.status, rr.results_count ?? 0, rr.enriched_count ?? 0,
1241
+ ]),
1242
+ ) + `\n${d.runs?.length ?? 0} rows\n`);
1243
+ });
1244
+ } catch (e) { fail(e); }
1245
+ });
1246
+
1247
+ withCommonOpts(a.command("delete <id>"))
1248
+ .description("Delete an agent")
1249
+ .action(async function (id) {
1250
+ try {
1251
+ const r = await call("agents", "delete", { id }, ctxFrom(this));
1252
+ emit(this, r, (d) => process.stdout.write(`✓ Deleted ${d.deleted}\n`));
1253
+ } catch (e) { fail(e); }
1254
+ });
1255
+ }
1256
+
1257
+ export function registerAuthority(program) {
1258
+ const a = program.command("authority").description("Authority Loop — queue, approve, dispatch, settings");
1259
+
1260
+ withCommonOpts(a.command("list"))
1261
+ .description("List authority touches (queued/approved/sent/failed/cancelled)")
1262
+ .option("--status <s>", "filter by status (queued|approved|sent|failed|cancelled|skipped)")
1263
+ .option("--channel <c>", "filter by channel (linkedin_dm|linkedin_invite)")
1264
+ .option("--lead <id>", "filter by lead_id")
1265
+ .option("--min-score <n>", "minimum score")
1266
+ .option("--limit <n>", "max rows", "50")
1267
+ .action(async function () {
1268
+ try {
1269
+ const o = this.optsWithGlobals();
1270
+ const r = await call("authority", "list", {
1271
+ status: o.status, channel: o.channel, lead_id: o.lead,
1272
+ min_score: o.minScore ? Number(o.minScore) : undefined,
1273
+ limit: Number(o.limit),
1274
+ }, ctxFrom(this));
1275
+ emit(this, r, (d) => {
1276
+ process.stdout.write(table(
1277
+ ["id", "status", "channel", "score", "asset", "scheduled"],
1278
+ (d.touches || []).map((t) => [
1279
+ t.id.slice(0, 8), t.status, t.channel, t.score ?? 0,
1280
+ truncate(t.asset_title || t.asset_url || "", 36),
1281
+ (t.scheduled_for || "").slice(0, 16).replace("T", " "),
1282
+ ]),
1283
+ ) + `\n${d.touches?.length ?? 0} rows\n`);
1284
+ });
1285
+ } catch (e) { fail(e); }
1286
+ });
1287
+
1288
+ withCommonOpts(a.command("show <id>"))
1289
+ .description("Full JSON for one authority touch")
1290
+ .action(async function (id) {
1291
+ try {
1292
+ const r = await call("authority", "get", { id }, ctxFrom(this));
1293
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.touch, null, 2) + "\n"));
1294
+ } catch (e) { fail(e); }
1295
+ });
1296
+
1297
+ withCommonOpts(a.command("approve <id>"))
1298
+ .description("Approve a queued touch (sets status=approved)")
1299
+ .action(async function (id) {
1300
+ try {
1301
+ const r = await call("authority", "approve", { id }, ctxFrom(this));
1302
+ emit(this, r, (d) => process.stdout.write(`✓ Approved ${d.touch?.id} → ${d.touch?.status}\n`));
1303
+ } catch (e) { fail(e); }
1304
+ });
1305
+
1306
+ withCommonOpts(a.command("cancel <id>"))
1307
+ .description("Cancel a queued/approved touch")
1308
+ .option("--reason <text>", "skipped_reason note")
1309
+ .action(async function (id, opts) {
1310
+ try {
1311
+ const r = await call("authority", "cancel", { id, reason: opts.reason }, ctxFrom(this));
1312
+ emit(this, r, (d) => process.stdout.write(`✓ Cancelled ${d.touch?.id}\n`));
1313
+ } catch (e) { fail(e); }
1314
+ });
1315
+
1316
+ withCommonOpts(a.command("dispatch <id>"))
1317
+ .description("Dispatch a queued/approved touch via LinkedIn (calls authority-touch-dispatch)")
1318
+ .action(async function (id) {
1319
+ try {
1320
+ const r = await call("authority", "dispatch", { id }, ctxFrom(this));
1321
+ emit(this, r, (d) => {
1322
+ const ok = d.dispatch?.success;
1323
+ process.stdout.write(`${ok ? "✓" : "✖"} dispatch status=${d.status} ${JSON.stringify(d.dispatch)}\n`);
1324
+ });
1325
+ } catch (e) { fail(e); }
1326
+ });
1327
+
1328
+ const settings = a.command("settings").description("Authority Loop workspace settings");
1329
+
1330
+ withCommonOpts(settings.command("get"))
1331
+ .description("Read authority_loop_settings for this workspace")
1332
+ .action(async function () {
1333
+ try {
1334
+ const r = await call("authority", "settings_get", {}, ctxFrom(this));
1335
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.settings, null, 2) + "\n"));
1336
+ } catch (e) { fail(e); }
1337
+ });
1338
+
1339
+ withCommonOpts(settings.command("set"))
1340
+ .description("Patch authority_loop_settings for this workspace (upsert)")
1341
+ .option("--enabled <bool>", "is_enabled (true|false)")
1342
+ .option("--auto-approve <bool>", "auto_approve (true|false)")
1343
+ .option("--daily-cap <n>", "max dispatches per day")
1344
+ .option("--min-relevance <n>", "min signal relevance_score (0-100)")
1345
+ .option("--min-match <n>", "min KB match score (0-100)")
1346
+ .option("--channels <csv>", "allowed_channels e.g. linkedin_dm,linkedin_invite")
1347
+ .option("--kb-categories <csv>", "allowed_kb_categories CSV")
1348
+ .option("--trigger-flags <csv>", "allowed_trigger_flags CSV")
1349
+ .option("--quiet-start <hhmm>", "quiet_hours_start (e.g. 22:00)")
1350
+ .option("--quiet-end <hhmm>", "quiet_hours_end (e.g. 07:00)")
1351
+ .action(async function (opts) {
1352
+ try {
1353
+ const asBool = (v) => v === undefined ? undefined : /^(1|true|yes|on)$/i.test(String(v));
1354
+ const asCsv = (v) => v === undefined ? undefined : String(v).split(",").map((s) => s.trim()).filter(Boolean);
1355
+ const params = {
1356
+ is_enabled: asBool(opts.enabled),
1357
+ auto_approve: asBool(opts.autoApprove),
1358
+ daily_cap: opts.dailyCap !== undefined ? Number(opts.dailyCap) : undefined,
1359
+ min_relevance_score: opts.minRelevance !== undefined ? Number(opts.minRelevance) : undefined,
1360
+ min_match_score: opts.minMatch !== undefined ? Number(opts.minMatch) : undefined,
1361
+ allowed_channels: asCsv(opts.channels),
1362
+ allowed_kb_categories: asCsv(opts.kbCategories),
1363
+ allowed_trigger_flags: asCsv(opts.triggerFlags),
1364
+ quiet_hours_start: opts.quietStart,
1365
+ quiet_hours_end: opts.quietEnd,
1366
+ };
1367
+ const r = await call("authority", "settings_set", params, ctxFrom(this));
1368
+ emit(this, r, (d) => process.stdout.write(`✓ Settings updated\n${JSON.stringify(d.settings, null, 2)}\n`));
1369
+ } catch (e) { fail(e); }
1370
+ });
1371
+ }
1372
+
1373
+ export function registerBriefs(program) {
1374
+ const b = program.command("briefs").description("Chief of Staff morning briefs — list, show, generate");
1375
+
1376
+ withCommonOpts(b.command("list"))
1377
+ .description("Recent intelligence_briefs for this workspace")
1378
+ .option("--user <id>", "filter by user_id")
1379
+ .option("--limit <n>", "max rows", "25")
1380
+ .action(async function () {
1381
+ try {
1382
+ const o = this.optsWithGlobals();
1383
+ const r = await call("briefs", "list", {
1384
+ user_id: o.user,
1385
+ limit: Number(o.limit),
1386
+ }, ctxFrom(this));
1387
+ emit(this, r, (d) => {
1388
+ process.stdout.write(table(
1389
+ ["id", "created", "signals", "actions", "sentiment", "headline"],
1390
+ (d.briefs || []).map((x) => [
1391
+ x.id.slice(0, 8),
1392
+ (x.created_at || "").slice(0, 16).replace("T", " "),
1393
+ x.signal_count ?? 0,
1394
+ x.top_actions_count ?? 0,
1395
+ x.sentiment ?? "—",
1396
+ truncate(x.headline || "", 48),
1397
+ ]),
1398
+ ) + `\n${d.briefs?.length ?? 0} rows\n`);
1399
+ });
1400
+ } catch (e) { fail(e); }
1401
+ });
1402
+
1403
+ withCommonOpts(b.command("show <id>"))
1404
+ .description("Full JSON for one brief (brief_data, talking points, top actions, risks)")
1405
+ .action(async function (id) {
1406
+ try {
1407
+ const r = await call("briefs", "get", { id }, ctxFrom(this));
1408
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.brief, null, 2) + "\n"));
1409
+ } catch (e) { fail(e); }
1410
+ });
1411
+
1412
+ withCommonOpts(b.command("generate"))
1413
+ .description("Generate a fresh brief now — invokes agent-chief-of-staff for this workspace")
1414
+ .option("--user <id>", "attribute brief to a specific user_id (defaults to workspace owner)")
1415
+ .option("--job <id>", "existing agent_jobs.id to report progress against")
1416
+ .action(async function () {
1417
+ try {
1418
+ const o = this.optsWithGlobals();
1419
+ const r = await call("briefs", "generate", {
1420
+ user_id: o.user,
1421
+ job_id: o.job,
1422
+ }, ctxFrom(this));
1423
+ emit(this, r, (d) => {
1424
+ const g = d.generate || {};
1425
+ const ok = g.success !== false && d.status < 400;
1426
+ process.stdout.write(
1427
+ `${ok ? "✓" : "✖"} chief-of-staff status=${d.status}` +
1428
+ (g.brief_id ? ` brief_id=${g.brief_id}` : "") +
1429
+ (g.headline ? `\n ${g.headline}` : "") +
1430
+ (g.signal_count !== undefined ? `\n signals=${g.signal_count} actions=${g.actions_generated ?? "?"}` : "") +
1431
+ "\n",
1432
+ );
1433
+ });
1434
+ } catch (e) { fail(e); }
1435
+ });
400
1436
  }
package/src/index.mjs CHANGED
@@ -2,7 +2,9 @@ import { Command } from "commander";
2
2
  import { registerAuth } from "./commands/auth.mjs";
3
3
  import {
4
4
  registerLeads, registerCampaigns, registerSequences,
5
- registerInbox, registerContent, registerAgents,
5
+ registerInbox, registerContent, registerAgents, registerKnowledge,
6
+ registerCalendar, registerSignals, registerPipelines, registerAuthority,
7
+ registerBriefs,
6
8
  } from "./commands/resources.mjs";
7
9
  import { registerFlow } from "./commands/flow.mjs";
8
10
 
@@ -20,6 +22,12 @@ registerCampaigns(program);
20
22
  registerSequences(program);
21
23
  registerInbox(program);
22
24
  registerContent(program);
25
+ registerCalendar(program);
26
+ registerKnowledge(program);
27
+ registerSignals(program);
28
+ registerPipelines(program);
29
+ registerAuthority(program);
30
+ registerBriefs(program);
23
31
  registerAgents(program);
24
32
  registerFlow(program);
25
33