@erdoai/cli 0.74.0 → 0.76.0

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.
Files changed (2) hide show
  1. package/dist/index.js +153 -17
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -482,6 +482,32 @@ var ErdoClient = class {
482
482
  getVoiceCall(callID) {
483
483
  return this.request("GET", `/v1/voice/calls/${encodeURIComponent(callID)}`);
484
484
  }
485
+ // Website concierge widget conversations — the text/voice/video sessions
486
+ // visitors held on the customer's own pages. Covers every widget in the org
487
+ // unless narrowed by the widget handle (its name or public key, vw_...).
488
+ listWidgetConversations(params) {
489
+ const q = new URLSearchParams();
490
+ if (params?.widget) q.set("widget", params.widget);
491
+ if (params?.since) q.set("since", params.since);
492
+ if (params?.until) q.set("until", params.until);
493
+ if (params?.limit !== void 0) q.set("limit", String(params.limit));
494
+ if (params?.offset !== void 0) q.set("offset", String(params.offset));
495
+ if (params?.cursor) q.set("cursor", params.cursor);
496
+ const qs = q.toString();
497
+ return this.request(
498
+ "GET",
499
+ `/v1/voice/widget-conversations${qs ? `?${qs}` : ""}`
500
+ );
501
+ }
502
+ // The session id identifies the conversation on its own and another org's
503
+ // session is never returned, so the widget handle is optional.
504
+ getWidgetConversation(sessionID, widget) {
505
+ return this.request(
506
+ "POST",
507
+ `/v1/voice/widget-conversations/get`,
508
+ { session_id: sessionID, ...widget ? { widget } : {} }
509
+ );
510
+ }
485
511
  // Run a read-only HogQL query against the org's page-analytics events. Rows are
486
512
  // positional per columns; enabled:false means page analytics is off for the org
487
513
  // (not zero traffic). A rejected query surfaces PostHog's message as the error.
@@ -2246,20 +2272,27 @@ wsCmd.command("get <slug>").description("Show a workstream \u2014 status, phases
2246
2272
  fail(e);
2247
2273
  }
2248
2274
  });
2249
- wsCmd.command("create").description("Create a workstream in a project").requiredOption("--project <slug>", "project slug").requiredOption("--slug <slug>", "workstream slug (unique per org)").requiredOption("--title <title>", "human-readable title").option("--description <text>", "free-form description").action(async (opts) => {
2250
- try {
2251
- print(
2252
- await new ErdoClient().createWorkstream({
2253
- project_slug: opts.project,
2254
- slug: opts.slug,
2255
- title: opts.title,
2256
- description: opts.description
2257
- })
2258
- );
2259
- } catch (e) {
2260
- fail(e);
2275
+ wsCmd.command("create").description("Create a workstream in a project").requiredOption("--project <slug>", "project slug").requiredOption("--slug <slug>", "workstream slug (unique per org)").requiredOption("--title <title>", "human-readable title").option("--description <text>", "free-form description").option(
2276
+ "--token-budget <millicents>",
2277
+ "spend ceiling in millicents \u2014 100,000 per dollar, so 10000000 = $100 and 25000000 = $250; omit for unlimited",
2278
+ (v) => parseInt(v, 10)
2279
+ ).action(
2280
+ async (opts) => {
2281
+ try {
2282
+ print(
2283
+ await new ErdoClient().createWorkstream({
2284
+ project_slug: opts.project,
2285
+ slug: opts.slug,
2286
+ title: opts.title,
2287
+ description: opts.description,
2288
+ token_budget_millicents: opts.tokenBudget
2289
+ })
2290
+ );
2291
+ } catch (e) {
2292
+ fail(e);
2293
+ }
2261
2294
  }
2262
- });
2295
+ );
2263
2296
  wsCmd.command("log <slug> <body...>").description("Append a line to the workstream's event log").action(async (slug, body) => {
2264
2297
  try {
2265
2298
  print(await new ErdoClient().appendWorkstreamEvent(slug, body.join(" ")));
@@ -2290,6 +2323,10 @@ wsCmd.command("phase-add <slug>").description("Append a new phase to a workstrea
2290
2323
  }
2291
2324
  );
2292
2325
  wsCmd.command("set-state <slug>").description("Update a workstream's status / title / description / phase statuses").option("--status <status>", "active, awaiting_user, blocked, completed").option("--title <title>", "retitle").option("--description <text>", "describe current state / surface a blocker").option("--summary <text>", "one-line audit-log summary of this update").option(
2326
+ "--token-budget <millicents>",
2327
+ "spend ceiling in millicents \u2014 100,000 per dollar, so 25000000 = $250; this is how you raise a ceiling a loop has spent through",
2328
+ (v) => parseInt(v, 10)
2329
+ ).option("--escalation-budget <n>", "max urgent attention items per rolling 24h", (v) => parseInt(v, 10)).option(
2293
2330
  "--phase <slug=status>",
2294
2331
  "set one phase's status (repeatable), e.g. --phase brand-brief=completed --phase landing-pages=running; statuses: pending, running, awaiting_user, blocked, completed, skipped, failed",
2295
2332
  collect,
@@ -2312,6 +2349,8 @@ wsCmd.command("set-state <slug>").description("Update a workstream's status / ti
2312
2349
  title: opts.title,
2313
2350
  description: opts.description,
2314
2351
  summary: opts.summary,
2352
+ token_budget_millicents: opts.tokenBudget,
2353
+ escalation_budget_per_day: opts.escalationBudget,
2315
2354
  phase_updates: phaseUpdates.length > 0 ? phaseUpdates : void 0
2316
2355
  })
2317
2356
  );
@@ -2320,7 +2359,11 @@ wsCmd.command("set-state <slug>").description("Update a workstream's status / ti
2320
2359
  }
2321
2360
  }
2322
2361
  );
2323
- wsCmd.command("arm <slug>").description("Arm the recurring reconciliation loop (allocator pass) for a workstream").option("--interval <minutes>", "how often the loop runs", (v) => parseInt(v, 10)).option("--model <id>", "model the loop runs on").option("--timezone <tz>", "timezone for scheduling").option("--token-budget <millicents>", "total spend ceiling in millicents (tenths of a cent)", (v) => parseInt(v, 10)).option("--escalation-budget <n>", "max escalations to a human per day", (v) => parseInt(v, 10)).action(
2362
+ wsCmd.command("arm <slug>").description("Arm the recurring reconciliation loop (allocator pass) for a workstream").option("--interval <minutes>", "how often the loop runs", (v) => parseInt(v, 10)).option("--model <id>", "model the loop runs on").option("--timezone <tz>", "timezone for scheduling").option(
2363
+ "--token-budget <millicents>",
2364
+ "total spend ceiling in millicents \u2014 100,000 per dollar, so 25000000 = $250; also how you raise a ceiling a loop has spent through",
2365
+ (v) => parseInt(v, 10)
2366
+ ).option("--escalation-budget <n>", "max escalations to a human per day", (v) => parseInt(v, 10)).action(
2324
2367
  async (slug, opts) => {
2325
2368
  try {
2326
2369
  const client = new ErdoClient();
@@ -4736,7 +4779,17 @@ sentEmailsCmd.command("get <emailID>").description("Read one sent email, includi
4736
4779
  fail(e);
4737
4780
  }
4738
4781
  });
4739
- var voiceCmd = program.command("voice").description("Read voice agent phone conversations");
4782
+ var voiceCmd = program.command("voice").description("Read voice agent phone calls and website widget conversations");
4783
+ function contactLabel(contact) {
4784
+ if (!contact) return "-";
4785
+ const name = [contact.first_name, contact.last_name].filter(Boolean).join(" ");
4786
+ const reach = contact.email || contact.phone;
4787
+ if (!name && !reach) return "none";
4788
+ return [name, reach].filter(Boolean).join(" \xB7 ");
4789
+ }
4790
+ function leadStatusLabel(outcome) {
4791
+ return outcome ? outcome : "pending";
4792
+ }
4740
4793
  var voiceCallsCmd = voiceCmd.command("calls").description("Inbound and outbound phone call records");
4741
4794
  voiceCallsCmd.command("list").description("List the organization's phone calls, newest first").option("--agent <slug>", "only calls held by this voice agent, by slug").option("--direction <direction>", "only 'inbound' (calls to the agent's number) or 'outbound'").option("--limit <n>", "page size (default 25, max 100)").option("--offset <n>", "rows to skip").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
4742
4795
  async (opts) => {
@@ -4758,7 +4811,19 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
4758
4811
  return;
4759
4812
  }
4760
4813
  printAlignedTable(
4761
- ["call id", "direction", "from", "to", "status", "secs", "transcript", "started", "summary"],
4814
+ [
4815
+ "call id",
4816
+ "direction",
4817
+ "from",
4818
+ "to",
4819
+ "status",
4820
+ "secs",
4821
+ "transcript",
4822
+ "contact",
4823
+ "lead",
4824
+ "started",
4825
+ "summary"
4826
+ ],
4762
4827
  calls.map((call) => [
4763
4828
  call.call_id,
4764
4829
  call.direction,
@@ -4767,6 +4832,8 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
4767
4832
  call.status,
4768
4833
  call.duration_seconds,
4769
4834
  call.has_transcript ? "yes" : "no",
4835
+ contactLabel(call.contact),
4836
+ leadStatusLabel(call.lead_status),
4770
4837
  call.created_at,
4771
4838
  call.transcript_summary
4772
4839
  ])
@@ -4782,13 +4849,82 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
4782
4849
  }
4783
4850
  }
4784
4851
  );
4785
- voiceCallsCmd.command("get <callID>").description("Read one phone call in full: transcript, summary, and per-turn LLM metrics").action(async (callID) => {
4852
+ voiceCallsCmd.command("get <callID>").description(
4853
+ "Read one phone call in full: transcript, summary, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at)"
4854
+ ).action(async (callID) => {
4786
4855
  try {
4787
4856
  print(await new ErdoClient().getVoiceCall(callID));
4788
4857
  } catch (e) {
4789
4858
  fail(e);
4790
4859
  }
4791
4860
  });
4861
+ var widgetConversationsCmd = voiceCmd.command("widget-conversations").description("Website concierge widget conversations (text, voice and video sessions)");
4862
+ widgetConversationsCmd.command("list").description("List the organization's widget conversations, newest first").option("--widget <handle>", "only this widget's conversations \u2014 its name or public key (vw_...)").option("--since <rfc3339>", "only conversations that started at or after this time (inclusive)").option("--until <rfc3339>", "only conversations that started before this time (exclusive)").option("--limit <n>", "page size (default 25, max 100)").option("--offset <n>", "rows to skip").option("--cursor <cursor>", "next_cursor from the preceding page (stable paging)").option("--json", "print the raw JSON result instead of a table").action(
4863
+ async (opts) => {
4864
+ try {
4865
+ const res = await new ErdoClient().listWidgetConversations({
4866
+ widget: opts.widget,
4867
+ since: opts.since,
4868
+ until: opts.until,
4869
+ limit: opts.limit ? Number(opts.limit) : void 0,
4870
+ offset: opts.offset ? Number(opts.offset) : void 0,
4871
+ cursor: opts.cursor
4872
+ });
4873
+ if (opts.json) {
4874
+ print(res);
4875
+ return;
4876
+ }
4877
+ const conversations = res.conversations ?? [];
4878
+ if (conversations.length === 0) {
4879
+ console.log("No conversations match those filters.");
4880
+ return;
4881
+ }
4882
+ printAlignedTable(
4883
+ [
4884
+ "session id",
4885
+ "widget",
4886
+ "modality",
4887
+ "secs",
4888
+ "transcript",
4889
+ "contact",
4890
+ "lead",
4891
+ "started",
4892
+ "summary"
4893
+ ],
4894
+ conversations.map((c) => [
4895
+ c.session_id,
4896
+ c.widget_name || c.widget,
4897
+ c.modality,
4898
+ c.duration_seconds,
4899
+ c.has_transcript ? "yes" : "no",
4900
+ contactLabel(c.contact),
4901
+ leadStatusLabel(c.lead_status),
4902
+ c.created_at,
4903
+ c.transcript_summary
4904
+ ])
4905
+ );
4906
+ process.stderr.write(
4907
+ `showing ${conversations.length} of ${res.total} conversation(s)
4908
+ `
4909
+ );
4910
+ if (res.next_cursor) {
4911
+ process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
4912
+ `);
4913
+ }
4914
+ } catch (e) {
4915
+ fail(e);
4916
+ }
4917
+ }
4918
+ );
4919
+ widgetConversationsCmd.command("get <sessionID>").description(
4920
+ "Read one widget conversation in full: transcript, summary, topics, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at)"
4921
+ ).option("--widget <handle>", "the widget it belongs to \u2014 optional, the session id identifies it").action(async (sessionID, opts) => {
4922
+ try {
4923
+ print(await new ErdoClient().getWidgetConversation(sessionID, opts.widget));
4924
+ } catch (e) {
4925
+ fail(e);
4926
+ }
4927
+ });
4792
4928
  var datasetsCmd = program.command("datasets").description("Datasets");
4793
4929
  datasetsCmd.command("list").description("List datasets").option(
4794
4930
  "--class <class>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.74.0",
3
+ "version": "0.76.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {