@series-a/cli 0.6.0 → 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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,10 @@ 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.6.1 — Campaign editing
8
+
9
+ - `campaigns` — added `update`/`edit` (name, description, goal, sequence, sender accounts, daily send limit, launch stage, status). Mirrors the new `update_campaign` MCP tool.
10
+
7
11
  ## 0.2.0 — Write coverage expansion
8
12
 
9
13
  - `inbox` — added `send`, `reply` (dry-run by default, `--confirm` to dispatch, 7,900 char guard), `mark-read` (bulk), `snooze` (`--for`, `--until`, `--clear`).
package/README.md CHANGED
@@ -73,11 +73,28 @@ advisoros leads list [--status new] [--persona founder] [--search acme] [--limit
73
73
  advisoros leads get <id>
74
74
  advisoros leads set-status <id> <new|engaged|won|lost>
75
75
  advisoros leads import --file leads.csv [--persona founder]
76
+ advisoros leads import-from <source> [--query ...] [--post <url>] [--file <path>] [--audience <name>] [--campaign <id>] [--limit 50] [--no-enrich]
77
+ advisoros leads import-status <jobId>
78
+ advisoros leads update <id> [--name ...] [--title ...] [--company ...] [--email ...] [--linkedin ...] [--persona ...] [--score ...] [--status ...] [--notes ...]
79
+ advisoros leads delete <id>
76
80
  advisoros leads verify <id>
77
81
  ```
78
82
 
79
83
  `leads import` accepts CSV (header row), JSON array, or JSONL. Upserts on `(workspace_id, linkedin_url)`. Bulk requests are auto-chunked at 200/call (server cap: 500/call).
80
84
 
85
+ `leads import-from` runs any of the six import sources in **Get Leads → Import Leads**:
86
+
87
+ | source | required params | what it does |
88
+ |---|---|---|
89
+ | `linkedin_search` | `--query` | keyword / title / company people search via Unipile |
90
+ | `company_search` | `--query` | LinkedIn company search |
91
+ | `post_reactors` | `--post <url>` | everyone who reacted to a LinkedIn post |
92
+ | `paste_profiles` | `--file <path>` | name + LinkedIn URL lines; rest is enriched |
93
+ | `csv` | `--file <path>` | same CSV parser the UI uses |
94
+ | `heyreach` | `--file <path>` `--campaign <id>` | HeyReach campaign CSV, statuses preserved |
95
+
96
+ All sources accept `--audience` to attach leads to a segment (created if missing). LinkedIn-backed sources run at bulk read priority so live campaigns are not starved. Long-running imports return a `job_id`; poll with `advisoros leads import-status <jobId>`.
97
+
81
98
  ### Campaigns
82
99
 
83
100
  ```bash
@@ -96,7 +113,7 @@ advisoros campaigns resume <id>
96
113
 
97
114
  `launch` flips status to `active` — the `enforce_campaign_char_limits` trigger validates every step's copy against LinkedIn limits before allowing the transition.
98
115
 
99
- ### Sequences / Inbox / Content / Agents
116
+ ### Sequences / Inbox / Content / Agents / Meetings / Newsletter / Integrations
100
117
 
101
118
  ```bash
102
119
  advisoros sequences list
@@ -110,11 +127,29 @@ advisoros content publish <id>
110
127
 
111
128
  advisoros agents list
112
129
  advisoros agents run <name> --input '{"foo":"bar"}'
130
+ advisoros agents trigger <name> --input '{"foo":"bar"}'
131
+ advisoros agents history [--agent <name>] [--days 30] [--limit 50]
113
132
  advisoros agents job <jobId>
133
+
134
+ advisoros meetings list [--limit 50]
135
+ advisoros meetings transcripts [--provider fireflies|roam] [--since 2026-01-01] [--limit 50]
136
+ advisoros meetings transcript <transcriptId>
137
+
138
+ advisoros newsletter list
139
+ advisoros newsletter create --subject "Weekly roundup" --body "<html>..." [--preview-text "..."]
140
+ advisoros newsletter edit <id> [--subject ...] [--body ...] [--preview-text ...] [--status draft|scheduled]
141
+ advisoros newsletter delete <id>
142
+
143
+ advisoros integrations status
144
+ advisoros integrations check <platform>
114
145
  ```
115
146
 
116
147
  > **Heads-up:** `inbox send`/`reply` are intentionally **not** in v1. Outbound goes through `campaigns launch` (sender resolution + char limits + per-sender daily budget). For one-off DMs, use the web inbox.
117
148
 
149
+ `meetings transcript` only returns full text once the transcript has been anonymised; otherwise metadata is returned with the body redacted.
150
+
151
+ `integrations status` is read-only and never returns credentials. `integrations check <platform>` runs a live health probe for the requested platform (e.g. `linkedin_tool`).
152
+
118
153
  ### Obsidian vault mirror (`kb`) — one-way
119
154
 
120
155
  ```bash
@@ -183,7 +218,7 @@ const { campaigns } = await call("campaigns", "list", { status: "active" }, {
183
218
  ```bash
184
219
  cd tools/cli
185
220
  npx changeset # describe the change
186
- npm run version # bump package.json + CHANGELOG.md
221
+ npm run version-packages # bump package.json + CHANGELOG.md
187
222
  git commit -am "chore(cli): release v$(node -p "require('./package.json').version")"
188
223
  git tag "cli-v$(node -p "require('./package.json').version")"
189
224
  git push --follow-tags
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@series-a/cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "description": "AdvisorPro command-line interface — drive leads, campaigns, sequences and agents from your terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,7 +21,7 @@
21
21
  "smoke": "node bin/advisoros.mjs --help >/dev/null && node bin/advisoros.mjs --version >/dev/null && node -e \"import('node:fs').then(({readFileSync})=>{const s=readFileSync('bin/advisoros.mjs','utf8');if(!s.startsWith('#!/usr/bin/env node')){console.error('Missing shebang in bin/advisoros.mjs');process.exit(1)}})\"",
22
22
  "prepublishOnly": "npm run smoke",
23
23
  "changeset": "changeset",
24
- "version": "changeset version",
24
+ "version-packages": "changeset version",
25
25
  "release": "npm run smoke && changeset publish --access public"
26
26
  },
27
27
  "dependencies": {
@@ -58,4 +58,3 @@
58
58
  "author": "AdvisorPro",
59
59
  "license": "UNLICENSED"
60
60
  }
61
-
@@ -246,6 +246,33 @@ export function registerCampaigns(program) {
246
246
  } catch (e) { fail(e); }
247
247
  });
248
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
+
249
276
  withCommonOpts(c.command("add-leads <campaignId>"))
250
277
  .description("Attach leads to a campaign")
251
278
  .option("--lead-ids <csv>", "comma-separated lead UUIDs")
@@ -373,15 +400,17 @@ export function registerSequences(program) {
373
400
  .description("Append a step to a sequence")
374
401
  .requiredOption(
375
402
  "--action <action>",
376
- "viewProfile|addReaction|addComment|sendInvitation|sendMessage|loopBack|createOpportunity|enrichLead|ifAccepted|ifReplied|ifConnection|ifNetworkDistance|stopOnReply|wait|wait_3h|wait_1d|wait_2d|wait_3d|wait_5d",
403
+ "viewProfile|addReaction|sendInvitation|sendMessage|loopBack|createOpportunity|enrichLead|ifAccepted|ifReplied|ifConnection|ifNetworkDistance|stopOnReply|wait|wait_3h|wait_1d|wait_2d|wait_3d|wait_5d",
377
404
  )
378
- .option("--text <message>", "message_content (required for addComment/sendInvitation/sendMessage)")
405
+ .option("--text <message>", "message_content (required for sendInvitation/sendMessage)")
379
406
  .option("--delay <days>", "delay_days before this step", "0")
380
407
  .option("--variant <v>", "variant label", "A")
381
408
  .option("--step-type <t>", "action|condition|delay (defaults from --action)")
382
409
  .option("--channel <c>", "channel (defaults from --action)")
383
- .option("--branch <label>", "branch_label: yes|no (for steps under a condition)")
384
- .option("--parent-step <id>", "parent_step_id (for branches)")
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
+
385
414
  .option("--order <n>", "explicit step_order (defaults to max+1)")
386
415
  .action(async function (id, opts) {
387
416
  try {
@@ -395,15 +424,49 @@ export function registerSequences(program) {
395
424
  channel: opts.channel,
396
425
  branch_label: opts.branch,
397
426
  parent_step_id: opts.parentStep,
427
+ root: opts.root === true,
398
428
  step_order: opts.order ? Number(opts.order) : undefined,
399
429
  }, ctxFrom(this));
400
430
  emit(this, r, (d) => {
401
431
  const st = d.step || {};
402
- 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`);
403
464
  });
404
465
  } catch (e) { fail(e); }
405
466
  });
406
467
 
468
+
469
+
407
470
  withCommonOpts(s.command("clone <id>"))
408
471
  .description("Duplicate a sequence (and all its steps) as a new draft")
409
472
  .option("--name <name>", "name for the clone (default: '<src> (copy)')")