@series-a/cli 0.2.1 → 0.6.2

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.
@@ -110,6 +110,77 @@ export function registerLeads(program) {
110
110
  } catch (e) { fail(e); }
111
111
  });
112
112
 
113
+ withCommonOpts(leads.command("import-from <source>"))
114
+ .description("Import leads from any source: linkedin_search | company_search | post_reactors | paste_profiles | csv | heyreach")
115
+ .option("--query <q>", "search keywords (linkedin_search / company_search)")
116
+ .option("--post <url>", "LinkedIn post URL (post_reactors)")
117
+ .option("--file <path>", "CSV or pasted-profile text file (csv / paste_profiles / heyreach)")
118
+ .option("--audience <name>", "audience (segment) name or id to attach to")
119
+ .option("--campaign <id>", "campaign id (required for heyreach)")
120
+ .option("--limit <n>", "max prospects for LinkedIn sources", "50")
121
+ .option("--no-enrich", "skip the LinkedIn verify + enrich chain")
122
+ .action(async function (source) {
123
+ try {
124
+ const o = this.optsWithGlobals();
125
+ const params = {
126
+ source,
127
+ query: o.query,
128
+ post_url: o.post,
129
+ audience: o.audience,
130
+ campaign_id: o.campaign,
131
+ limit: Number(o.limit),
132
+ enrich: o.enrich !== false,
133
+ };
134
+ if (o.file) {
135
+ const text = await readFile(o.file, "utf8");
136
+ if (source === "paste_profiles") params.text = text;
137
+ else params.csv = text;
138
+ }
139
+ const r = await call("leads", "import", params, ctxFrom(this));
140
+ emit(this, r, (d) => process.stdout.write(
141
+ `✓ Imported ${d.imported ?? 0} leads (${d.duplicates ?? 0} duplicates, ${d.skipped ?? 0} skipped, ${d.failed ?? 0} failed)\n` +
142
+ (d.job_id ? ` job: ${d.job_id}\n` : "")));
143
+ } catch (e) { fail(e); }
144
+ });
145
+
146
+ withCommonOpts(leads.command("import-status <jobId>"))
147
+ .description("Poll a running lead import")
148
+ .action(async function (jobId) {
149
+ try {
150
+ const r = await call("leads", "import_status", { job_id: jobId }, ctxFrom(this));
151
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.job, null, 2) + "\n"));
152
+ } catch (e) { fail(e); }
153
+ });
154
+
155
+ withCommonOpts(leads.command("update <id>"))
156
+ .description("Update a single lead")
157
+ .option("--name <v>").option("--title <v>").option("--company <v>")
158
+ .option("--email <v>").option("--linkedin <v>").option("--persona <v>")
159
+ .option("--score <n>").option("--status <v>").option("--notes <v>")
160
+ .action(async function (id) {
161
+ try {
162
+ const o = this.optsWithGlobals();
163
+ const params = { id };
164
+ for (const [k, v] of Object.entries({
165
+ name: o.name, title: o.title, company: o.company, email: o.email,
166
+ linkedin_url: o.linkedin, persona_tag: o.persona,
167
+ score: o.score !== undefined ? Number(o.score) : undefined,
168
+ status: o.status, notes: o.notes,
169
+ })) if (v !== undefined) params[k] = v;
170
+ const r = await call("leads", "update", params, ctxFrom(this));
171
+ emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.lead?.name ?? id}\n`));
172
+ } catch (e) { fail(e); }
173
+ });
174
+
175
+ withCommonOpts(leads.command("delete <id>"))
176
+ .description("Delete a lead")
177
+ .action(async function (id) {
178
+ try {
179
+ const r = await call("leads", "delete", { id }, ctxFrom(this));
180
+ emit(this, r, () => process.stdout.write(`✓ Deleted ${id}\n`));
181
+ } catch (e) { fail(e); }
182
+ });
183
+
113
184
  withCommonOpts(leads.command("verify <id>"))
114
185
  .description("Run LinkedIn verification on a lead")
115
186
  .action(async function (id) {
@@ -175,6 +246,33 @@ export function registerCampaigns(program) {
175
246
  } catch (e) { fail(e); }
176
247
  });
177
248
 
249
+ withCommonOpts(c.command("update <campaignId>"))
250
+ .description("Edit an existing campaign")
251
+ .option("--name <name>", "rename campaign")
252
+ .option("--description <text>", "description")
253
+ .option("--goal <text>", "campaign goal")
254
+ .option("--sequence <id>", "attach a sequence")
255
+ .option("--senders <csv>", "comma-separated sender_account_ids")
256
+ .option("--daily-limit <n>", "daily send cap")
257
+ .option("--status <status>", "draft|active|paused|completed|archived")
258
+ .action(async function (campaignId, opts) {
259
+ try {
260
+ const payload = { id: campaignId };
261
+ if (opts.name !== undefined) payload.name = opts.name;
262
+ if (opts.description !== undefined) payload.description = opts.description;
263
+ if (opts.goal !== undefined) payload.goal = opts.goal;
264
+ if (opts.sequence !== undefined) payload.sequence_id = opts.sequence;
265
+ if (opts.senders !== undefined) {
266
+ payload.sender_accounts = opts.senders.split(",").map((s) => s.trim()).filter(Boolean);
267
+ }
268
+ if (opts.dailyLimit !== undefined) payload.daily_send_limit = Number(opts.dailyLimit);
269
+ if (opts.status !== undefined) payload.status = opts.status;
270
+ const r = await call("campaigns", "update", payload, ctxFrom(this));
271
+ emit(this, r, (d) => process.stdout.write(`✓ Updated campaign ${d.campaign?.id}\n`));
272
+ } catch (e) { fail(e); }
273
+ });
274
+
275
+
178
276
  withCommonOpts(c.command("add-leads <campaignId>"))
179
277
  .description("Attach leads to a campaign")
180
278
  .option("--lead-ids <csv>", "comma-separated lead UUIDs")
@@ -300,14 +398,19 @@ export function registerSequences(program) {
300
398
 
301
399
  withCommonOpts(s.command("add-step <id>"))
302
400
  .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)")
401
+ .requiredOption(
402
+ "--action <action>",
403
+ "viewProfile|addReaction|sendInvitation|sendMessage|loopBack|createOpportunity|enrichLead|ifAccepted|ifReplied|ifConnection|ifNetworkDistance|stopOnReply|wait|wait_3h|wait_1d|wait_2d|wait_3d|wait_5d",
404
+ )
405
+ .option("--text <message>", "message_content (required for sendInvitation/sendMessage)")
305
406
  .option("--delay <days>", "delay_days before this step", "0")
306
407
  .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)")
408
+ .option("--step-type <t>", "action|condition|delay (defaults from --action)")
409
+ .option("--channel <c>", "channel (defaults from --action)")
410
+ .option("--branch <label>", "branch_label: yes|no|first|second_or_third|follower|out_of_network (children of a condition)")
411
+ .option("--parent-step <id>", "parent_step_id (defaults to the tail of the sequence)")
412
+ .option("--root", "start a new root step instead of appending to the tail (rarely correct)")
413
+
311
414
  .option("--order <n>", "explicit step_order (defaults to max+1)")
312
415
  .action(async function (id, opts) {
313
416
  try {
@@ -321,15 +424,49 @@ export function registerSequences(program) {
321
424
  channel: opts.channel,
322
425
  branch_label: opts.branch,
323
426
  parent_step_id: opts.parentStep,
427
+ root: opts.root === true,
324
428
  step_order: opts.order ? Number(opts.order) : undefined,
325
429
  }, ctxFrom(this));
326
430
  emit(this, r, (d) => {
327
431
  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`);
432
+ process.stdout.write(`✓ Added step ${st.id}\n #${st.step_order} ${st.action} (${st.channel}) delay=${st.delay_days}d variant=${st.variant}\n parent=${st.parent_step_id || "root"}${st.branch_label ? ` branch=${st.branch_label}` : ""}\n`);
433
+ for (const w of d.graph?.warnings || []) process.stdout.write(` ⚠ ${w.message}\n`);
434
+ });
435
+ } catch (e) { fail(e); }
436
+
437
+ });
438
+
439
+ withCommonOpts(s.command("replace-steps <id>"))
440
+ .description("Replace every step of a sequence with a validated tree from a JSON file (atomic)")
441
+ .requiredOption("--file <path>", "JSON file: [{ temp_id, parent, branch_label, action, message_content, delay_days }]")
442
+ .option("--dry-run", "validate the graph without writing")
443
+ .action(async function (id, opts) {
444
+ try {
445
+ const raw = await readFile(opts.file, "utf8");
446
+ const parsed = JSON.parse(raw);
447
+ const steps = Array.isArray(parsed) ? parsed : parsed.steps;
448
+ if (!Array.isArray(steps)) throw new Error("file must contain an array of steps or { steps: [...] }");
449
+ const r = await call("sequences", "replace_steps", {
450
+ sequence_id: id,
451
+ steps,
452
+ dry_run: opts.dryRun === true,
453
+ }, ctxFrom(this));
454
+ emit(this, r, (d) => {
455
+ if (d.dry_run) {
456
+ process.stdout.write(`✓ Graph valid — ${d.planned_steps} step(s), nothing written\n`);
457
+ return;
458
+ }
459
+ process.stdout.write(`✓ Replaced steps on ${d.sequence_id} — ${d.replaced_steps} step(s)\n`);
460
+ for (const st of d.steps || []) {
461
+ process.stdout.write(` #${st.step_order} ${st.action}${st.branch_label ? ` [${st.branch_label}]` : ""} parent=${st.parent_step_id || "root"}\n`);
462
+ }
463
+ for (const w of d.graph?.warnings || []) process.stdout.write(` ⚠ ${w.message}\n`);
329
464
  });
330
465
  } catch (e) { fail(e); }
331
466
  });
332
467
 
468
+
469
+
333
470
  withCommonOpts(s.command("clone <id>"))
334
471
  .description("Duplicate a sequence (and all its steps) as a new draft")
335
472
  .option("--name <name>", "name for the clone (default: '<src> (copy)')")
@@ -552,6 +689,8 @@ export function registerKnowledge(program) {
552
689
  .option("--visibility <v>", "workspace | private (default: workspace)")
553
690
  .option("--no-summarize", "skip AI auto-tag/classify (raw insert only)")
554
691
  .option("--no-scrape", "skip Firecrawl scrape for --url (URL stored as bookmark)")
692
+ .option("--style-reference", "flag as a Tone-of-Voice style reference (for known author documents)")
693
+ .option("--auto-style", "score as a style reference on arrival and auto-flag if it meets the threshold (default 70)")
555
694
  .action(async function (opts) {
556
695
  try {
557
696
  const sources = [opts.url, opts.text, opts.file].filter(Boolean);
@@ -575,6 +714,8 @@ export function registerKnowledge(program) {
575
714
  visibility: opts.visibility,
576
715
  skip_summarize: opts.summarize === false ? true : undefined,
577
716
  skip_scrape: opts.scrape === false ? true : undefined,
717
+ is_style_reference: opts.styleReference === true ? true : undefined,
718
+ auto_style_reference: opts.autoStyle === true ? true : undefined,
578
719
  };
579
720
  const r = await call("knowledge", "add", params, ctxFrom(this));
580
721
  emit(this, r, (d) => {
@@ -586,7 +727,11 @@ export function registerKnowledge(program) {
586
727
  ` category: ${it.category}\n` +
587
728
  ` tags: ${(it.tags || []).join(", ") || "(none)"}\n` +
588
729
  (it.summary ? ` summary: ${truncate(it.summary, 240)}\n` : "") +
589
- (d.ai?.success === false ? ` ⚠ AI classify: ${d.ai.error}\n` : ""),
730
+ (it.is_style_reference ? ` ★ style reference\n` : "") +
731
+ (d.ai?.success === false ? ` ⚠ AI classify: ${d.ai.error}\n` : "") +
732
+ (d.style_reference_triage?.data?.results?.[0] && !it.is_style_reference
733
+ ? ` style triage: ${d.style_reference_triage.data.results[0].score}/100 — ${d.style_reference_triage.data.results[0].reason}\n`
734
+ : ""),
590
735
  );
591
736
  });
592
737
  } catch (e) { fail(e); }
@@ -599,6 +744,7 @@ export function registerKnowledge(program) {
599
744
  .option("--category <c>", "set category")
600
745
  .option("--tags <csv>", "replace tags (comma-separated)")
601
746
  .option("--visibility <v>", "workspace | private")
747
+ .option("--style-reference <on|off>", "flag/unflag as a Tone-of-Voice style reference")
602
748
  .action(async function (id, opts) {
603
749
  try {
604
750
  const patch = {
@@ -608,6 +754,7 @@ export function registerKnowledge(program) {
608
754
  category: opts.category,
609
755
  tags: opts.tags ? opts.tags.split(",").map((s) => s.trim()).filter(Boolean) : undefined,
610
756
  visibility: opts.visibility,
757
+ is_style_reference: opts.styleReference === "on" ? true : opts.styleReference === "off" ? false : undefined,
611
758
  };
612
759
  const r = await call("knowledge", "update", patch, ctxFrom(this));
613
760
  emit(this, r, (d) => process.stdout.write(`✓ Updated ${d.item?.id}\n`));
@@ -759,8 +906,51 @@ export function registerContent(program) {
759
906
  emit(this, r, (d) => process.stdout.write(`✓ Deleted ${d.deleted}\n`));
760
907
  } catch (e) { fail(e); }
761
908
  });
909
+
910
+ withCommonOpts(c.command("generate"))
911
+ .description("Generate a draft via agent-content-creator (KB + reference library grounded, URL-sanitised, citations recorded)")
912
+ .requiredOption("--angle <text>", "topic / angle for the draft")
913
+ .option("--format <f>", "blog | linkedin_post | video | explainer_video", "blog")
914
+ .option("--signal <id...>", "intelligence_signals ids to ground on (repeatable)")
915
+ .option("--reference <id...>", "knowledge_items ids to ground on (repeatable)")
916
+ .option("--grounding-text <text>", "extra freeform grounding to append")
917
+ .option("--wait", "block until the job completes (up to 4 min)")
918
+ .action(async function (opts) {
919
+ try {
920
+ const r = await call("content", "generate", {
921
+ angle: opts.angle,
922
+ format: opts.format,
923
+ signal_ids: opts.signal,
924
+ reference_item_ids: opts.reference,
925
+ grounding_text: opts.groundingText,
926
+ async: !opts.wait,
927
+ }, ctxFrom(this));
928
+ emit(this, r, (d) => {
929
+ if (d.content) {
930
+ process.stdout.write(`✓ Draft ${d.content.id} (${d.content.type}, ${d.citations?.length ?? 0} citations)\n`);
931
+ } else {
932
+ process.stdout.write(`⏳ Queued job ${d.job_id} — poll with: advisoros agents job-status ${d.job_id}\n`);
933
+ }
934
+ });
935
+ } catch (e) { fail(e); }
936
+ });
937
+
938
+ withCommonOpts(c.command("citations <id>"))
939
+ .description("List every source (signal, reference, transcript, published) recorded for a content item")
940
+ .action(async function (id) {
941
+ try {
942
+ const r = await call("content", "citations", { id }, ctxFrom(this));
943
+ emit(this, r, (d) => {
944
+ process.stdout.write(table(
945
+ ["kind", "id", "title", "url"],
946
+ (d.citations || []).map((c) => [c.source_kind, truncate(c.source_id ?? "", 8), truncate(c.title ?? "", 40), truncate(c.source_url ?? "", 60)]),
947
+ ) + `\n${d.citations?.length ?? 0} citations\n`);
948
+ });
949
+ } catch (e) { fail(e); }
950
+ });
762
951
  }
763
952
 
953
+
764
954
  export function registerCalendar(program) {
765
955
  const cal = program.command("calendar").description("Manage the content calendar");
766
956
 
@@ -1104,6 +1294,42 @@ export function registerPipelines(program) {
1104
1294
  export function registerAgents(program) {
1105
1295
  const a = program.command("agents").description("List, create, configure and invoke agents");
1106
1296
 
1297
+ withCommonOpts(a.command("trigger <name>"))
1298
+ .description("Trigger an agent by name with optional JSON input")
1299
+ .option("--input <json>", "JSON input payload", "{}")
1300
+ .action(async function (name) {
1301
+ try {
1302
+ const o = this.optsWithGlobals();
1303
+ let input = {};
1304
+ try { input = JSON.parse(o.input); } catch { throw new Error("--input must be valid JSON"); }
1305
+ const r = await call("agents", "trigger", { agent_name: name, input }, ctxFrom(this));
1306
+ emit(this, r, (d) => process.stdout.write(JSON.stringify(d.run, null, 2) + "\n"));
1307
+ } catch (e) { fail(e); }
1308
+ });
1309
+
1310
+ withCommonOpts(a.command("history"))
1311
+ .description("Recent agent runs and jobs")
1312
+ .option("--agent <name>", "filter by agent name")
1313
+ .option("--days <n>", "look-back window", "30")
1314
+ .option("--limit <n>", "max rows", "50")
1315
+ .action(async function () {
1316
+ try {
1317
+ const o = this.optsWithGlobals();
1318
+ const r = await call("agents", "history", {
1319
+ agent_name: o.agent, days: Number(o.days), limit: Number(o.limit),
1320
+ }, ctxFrom(this));
1321
+ emit(this, r, (d) => {
1322
+ process.stdout.write(table(
1323
+ ["when", "agent", "status", "results"],
1324
+ (d.runs || []).map((x) => [
1325
+ String(x.created_at ?? "").slice(0, 16), truncate(x.agent_name, 30),
1326
+ x.status ?? "", String(x.results_count ?? ""),
1327
+ ]),
1328
+ ) + `\n${d.runs?.length ?? 0} runs, ${d.jobs?.length ?? 0} jobs\n`);
1329
+ });
1330
+ } catch (e) { fail(e); }
1331
+ });
1332
+
1107
1333
  withCommonOpts(a.command("list"))
1108
1334
  .description("List available agents")
1109
1335
  .option("--scope <s>", "workspace | global | both (default: both)")
package/src/index.mjs CHANGED
@@ -6,14 +6,21 @@ import {
6
6
  registerCalendar, registerSignals, registerPipelines, registerAuthority,
7
7
  registerBriefs,
8
8
  } from "./commands/resources.mjs";
9
+ import {
10
+ registerCampaignAnalytics, registerAnalytics, registerSafety, registerCtas,
11
+ registerNotifications, registerAudiences, registerNewsletter, registerVideo,
12
+ registerWatchlist, registerMeetings, registerActivity, registerWorkspacesExtras,
13
+ registerExport, registerIntegrations,
14
+ } from "./commands/insights.mjs";
9
15
  import { registerFlow } from "./commands/flow.mjs";
16
+ import { registerVaultSync } from "./commands/knowledge-sync.mjs";
10
17
 
11
18
  const program = new Command();
12
19
 
13
20
  program
14
21
  .name("advisoros")
15
- .description("AdvisorOS CLI — drive leads, campaigns, sequences and agents from your terminal.")
16
- .version("0.1.0")
22
+ .description("AdvisorPro CLI — drive leads, campaigns, sequences and agents from your terminal.")
23
+ .version("0.5.0")
17
24
  .option("--json", "machine-readable JSON output (applies to all subcommands)");
18
25
 
19
26
  registerAuth(program);
@@ -29,9 +36,29 @@ registerPipelines(program);
29
36
  registerAuthority(program);
30
37
  registerBriefs(program);
31
38
  registerAgents(program);
39
+ registerIntegrations(program);
40
+
41
+ // Analytics, safety guardrails and the remaining resource surface.
42
+ registerCampaignAnalytics(program);
43
+ registerAnalytics(program);
44
+ registerSafety(program);
45
+ registerCtas(program);
46
+ registerNotifications(program);
47
+ registerAudiences(program);
48
+ registerNewsletter(program);
49
+ registerVideo(program);
50
+ registerWatchlist(program);
51
+ registerMeetings(program);
52
+ registerActivity(program);
53
+ registerWorkspacesExtras(program);
54
+ registerExport(program);
55
+
56
+ registerVaultSync(program);
57
+
32
58
  registerFlow(program);
33
59
 
34
60
  program.parseAsync(process.argv).catch((e) => {
35
61
  process.stderr.write(`✖ ${e?.message ?? e}\n`);
36
62
  process.exit(1);
37
63
  });
64
+