@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 +11 -0
- package/package.json +1 -1
- package/src/commands/resources.mjs +1059 -23
- package/src/index.mjs +9 -1
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
|
@@ -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("
|
|
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) => [
|
|
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
|
-
.
|
|
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
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
-
|
|
306
|
-
|
|
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
|
-
`✓
|
|
309
|
-
`
|
|
310
|
-
`
|
|
311
|
-
`
|
|
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,
|
|
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
|
|
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", "
|
|
373
|
-
(d.agents || []).map((a) => [
|
|
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
|
|