@erdoai/cli 0.91.0 → 0.98.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.
- package/dist/index.js +402 -57
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -312,6 +312,19 @@ var ErdoClient = class {
|
|
|
312
312
|
createManagerKey() {
|
|
313
313
|
return this.request("POST", "/v1/manager-key");
|
|
314
314
|
}
|
|
315
|
+
// Daily paid-media and lead performance for one organization, or for every
|
|
316
|
+
// client org this account manages when organization_slug is omitted.
|
|
317
|
+
managerPerformance(params) {
|
|
318
|
+
const qs = new URLSearchParams();
|
|
319
|
+
if (params.organization_slug) qs.set("organization_slug", params.organization_slug);
|
|
320
|
+
if (params.from) qs.set("from", params.from);
|
|
321
|
+
if (params.to) qs.set("to", params.to);
|
|
322
|
+
const q = qs.toString();
|
|
323
|
+
return this.request(
|
|
324
|
+
"GET",
|
|
325
|
+
`/v1/managed-organization-performance${q ? `?${q}` : ""}`
|
|
326
|
+
);
|
|
327
|
+
}
|
|
315
328
|
// Brings an EXISTING org under your manager org. Two consents redeem here: a
|
|
316
329
|
// one-time token minted by the target org's owner (or, for an ownerless managed
|
|
317
330
|
// org, its current manager) — a manager credential on its own can never adopt an
|
|
@@ -464,6 +477,8 @@ var ErdoClient = class {
|
|
|
464
477
|
const q = new URLSearchParams();
|
|
465
478
|
if (params?.domain) q.set("domain", params.domain);
|
|
466
479
|
if (params?.limit) q.set("limit", String(params.limit));
|
|
480
|
+
if (params?.leadRef) q.set("lead_ref", params.leadRef);
|
|
481
|
+
if (params?.dataset) q.set("dataset", params.dataset);
|
|
467
482
|
const qs = q.toString();
|
|
468
483
|
return this.request(
|
|
469
484
|
"GET",
|
|
@@ -604,6 +619,16 @@ var ErdoClient = class {
|
|
|
604
619
|
{ enabled }
|
|
605
620
|
);
|
|
606
621
|
}
|
|
622
|
+
// A separate switch from the text one, on the same number: the two are
|
|
623
|
+
// separate permissions, so one endpoint per channel rather than a channel
|
|
624
|
+
// argument that could move the wrong one.
|
|
625
|
+
setVoiceAgentWhatsAppReplies(slug, enabled) {
|
|
626
|
+
return this.request(
|
|
627
|
+
"POST",
|
|
628
|
+
`/v1/voice/agents/${encodeURIComponent(slug)}/whatsapp-replies`,
|
|
629
|
+
{ enabled }
|
|
630
|
+
);
|
|
631
|
+
}
|
|
607
632
|
// Run a read-only HogQL query against the org's page-analytics events. Rows are
|
|
608
633
|
// positional per columns; enabled:false means page analytics is off for the org
|
|
609
634
|
// (not zero traffic). A rejected query surfaces PostHog's message as the error.
|
|
@@ -884,6 +909,9 @@ var ErdoClient = class {
|
|
|
884
909
|
for (const s of params?.statuses ?? []) q.append("statuses", s);
|
|
885
910
|
if (params?.engine_actions_only) q.set("engine_actions_only", "true");
|
|
886
911
|
if (params?.item_id) q.set("item_id", params.item_id);
|
|
912
|
+
if (params?.organization_slug) q.set("organization_slug", params.organization_slug);
|
|
913
|
+
if (params?.subject_kind) q.set("subject_kind", params.subject_kind);
|
|
914
|
+
if (params?.subject_id) q.set("subject_id", params.subject_id);
|
|
887
915
|
if (params?.limit) q.set("limit", String(params.limit));
|
|
888
916
|
if (params?.offset) q.set("offset", String(params.offset));
|
|
889
917
|
const qs = q.toString();
|
|
@@ -1982,6 +2010,29 @@ function timedOutMessage(threadID) {
|
|
|
1982
2010
|
function print(value) {
|
|
1983
2011
|
console.log(JSON.stringify(value, null, 2));
|
|
1984
2012
|
}
|
|
2013
|
+
function formatNumber(n) {
|
|
2014
|
+
if (!Number.isFinite(n)) return String(n);
|
|
2015
|
+
if (Number.isInteger(n)) return n.toLocaleString("en-US");
|
|
2016
|
+
if (Math.abs(n) < 1e-6) {
|
|
2017
|
+
return n.toFixed(10).replace(/0+$/, "").replace(/\.$/, "");
|
|
2018
|
+
}
|
|
2019
|
+
return n.toLocaleString("en-US", { maximumFractionDigits: 6 });
|
|
2020
|
+
}
|
|
2021
|
+
function formatDate(iso) {
|
|
2022
|
+
if (!iso) return void 0;
|
|
2023
|
+
const d = new Date(iso);
|
|
2024
|
+
if (isNaN(d.getTime())) return iso;
|
|
2025
|
+
return `${d.toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
2026
|
+
}
|
|
2027
|
+
function compactJSON(value) {
|
|
2028
|
+
if (value === void 0 || value === null) return void 0;
|
|
2029
|
+
try {
|
|
2030
|
+
const s = JSON.stringify(value);
|
|
2031
|
+
return s === "null" ? void 0 : s;
|
|
2032
|
+
} catch {
|
|
2033
|
+
return String(value);
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
1985
2036
|
async function readAllStdin() {
|
|
1986
2037
|
if (process.stdin.isTTY) {
|
|
1987
2038
|
throw new Error("nothing is piped to standard input");
|
|
@@ -2030,7 +2081,11 @@ function printPageReview(review) {
|
|
|
2030
2081
|
}
|
|
2031
2082
|
}
|
|
2032
2083
|
function printAlignedTable(columns, rows) {
|
|
2033
|
-
const cell = (v) =>
|
|
2084
|
+
const cell = (v) => {
|
|
2085
|
+
if (v === null || v === void 0) return "";
|
|
2086
|
+
if (typeof v === "object") return JSON.stringify(v);
|
|
2087
|
+
return String(v);
|
|
2088
|
+
};
|
|
2034
2089
|
const widths = columns.map((c, i) => Math.max(c.length, ...rows.map((r) => cell(r[i]).length), 0));
|
|
2035
2090
|
const line = (cells) => cells.map((c, i) => c.padEnd(widths[i])).join(" ").trimEnd();
|
|
2036
2091
|
console.log(line(columns));
|
|
@@ -2045,7 +2100,10 @@ var collect = (v, acc) => {
|
|
|
2045
2100
|
function summariseResults(results, cases = []) {
|
|
2046
2101
|
const nameByID = new Map(cases.map((c) => [c.id, c.name]));
|
|
2047
2102
|
for (const r of results) {
|
|
2048
|
-
|
|
2103
|
+
let status;
|
|
2104
|
+
if (r.agent_error) status = "ERROR";
|
|
2105
|
+
else if (r.passed) status = "PASS";
|
|
2106
|
+
else status = "FAIL";
|
|
2049
2107
|
const name = nameByID.get(r.case_id) ?? r.case_id;
|
|
2050
2108
|
console.log(` [${status}] ${r.score.toFixed(2)} ${name}`);
|
|
2051
2109
|
if (r.agent_error) console.log(` agent error: ${r.agent_error}`);
|
|
@@ -2273,6 +2331,7 @@ function printBusinessProfile(profile) {
|
|
|
2273
2331
|
["industry", profile.industry],
|
|
2274
2332
|
["privacy policy", profile.privacy_policy_url],
|
|
2275
2333
|
["terms", profile.terms_and_conditions_url],
|
|
2334
|
+
["example landing page", profile.example_landing_page_url],
|
|
2276
2335
|
["representative", `${profile.representative_first_name} ${profile.representative_last_name}`.trim()],
|
|
2277
2336
|
["title", profile.representative_title],
|
|
2278
2337
|
["job position", profile.representative_job_position],
|
|
@@ -2324,7 +2383,10 @@ businessProfileCmd.command("set").description("Save the organization's business
|
|
|
2324
2383
|
).option(
|
|
2325
2384
|
"--tax-id-stdin",
|
|
2326
2385
|
"read the tax id from standard input instead of prompting, for a script \u2014 e.g. `pass show ein | erdo org business-profile set --tax-id-stdin`"
|
|
2327
|
-
).option("--street <street>", "street address of the registered business").option("--city <city>", "city of the registered business").option("--region <region>", "state or province, e.g. FL").option("--postal-code <code>", "postal or ZIP code").option("--country <iso2>", "two-letter ISO country code, e.g. US").option("--website-url <url>", "the business's own website \u2014 carriers check that it describes it").option("--industry <industry>", "one of the industries listed by: erdo org business-profile get --json").option("--privacy-policy-url <url>", "the business's own privacy policy page (https) \u2014 a campaign without one is rejected").option("--terms-url <url>", "the business's own terms and conditions page (https) \u2014 required for the same reason").option(
|
|
2386
|
+
).option("--street <street>", "street address of the registered business").option("--city <city>", "city of the registered business").option("--region <region>", "state or province, e.g. FL").option("--postal-code <code>", "postal or ZIP code").option("--country <iso2>", "two-letter ISO country code, e.g. US").option("--website-url <url>", "the business's own website \u2014 carriers check that it describes it").option("--industry <industry>", "one of the industries listed by: erdo org business-profile get --json").option("--privacy-policy-url <url>", "the business's own privacy policy page (https) \u2014 a campaign without one is rejected").option("--terms-url <url>", "the business's own terms and conditions page (https) \u2014 required for the same reason").option(
|
|
2387
|
+
"--example-landing-page-url <url>",
|
|
2388
|
+
"a public landing page (https) where people opt in to texts through the form's SMS consent checkbox and that shows your phone number \u2014 the opt-in the campaign reviewer opens"
|
|
2389
|
+
).option("--first-name <name>", "given name of the person carriers may contact").option("--last-name <name>", "family name of that person").option("--title <title>", "their job title in the business's own words, e.g. Managing Partner").option(
|
|
2328
2390
|
"--job-position <position>",
|
|
2329
2391
|
"their role from the fixed list (ceo, cfo, director, general_counsel, gm, vp, other)"
|
|
2330
2392
|
).option("--email <email>", "their email address").option("--phone <number>", "their phone number in E.164, e.g. +13055550123").option("--json", "print the raw JSON result instead of a table").action(
|
|
@@ -2362,6 +2424,7 @@ businessProfileCmd.command("set").description("Save the organization's business
|
|
|
2362
2424
|
industry: opts.industry ?? stored.industry,
|
|
2363
2425
|
privacy_policy_url: opts.privacyPolicyUrl ?? stored.privacy_policy_url,
|
|
2364
2426
|
terms_and_conditions_url: opts.termsUrl ?? stored.terms_and_conditions_url,
|
|
2427
|
+
example_landing_page_url: opts.exampleLandingPageUrl ?? stored.example_landing_page_url ?? "",
|
|
2365
2428
|
representative_first_name: opts.firstName ?? stored.representative_first_name,
|
|
2366
2429
|
representative_last_name: opts.lastName ?? stored.representative_last_name,
|
|
2367
2430
|
representative_title: opts.title ?? stored.representative_title,
|
|
@@ -2522,9 +2585,11 @@ managed.command("ads-container <orgSlug>").description(
|
|
|
2522
2585
|
timeZone: opts.timeZone,
|
|
2523
2586
|
replaceCurrentAccount: opts.replaceCurrentAccount
|
|
2524
2587
|
});
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
)
|
|
2588
|
+
let outcome;
|
|
2589
|
+
if (c.already_provisioned) outcome = `Google Ads container already existed for ${c.org_slug}`;
|
|
2590
|
+
else if (c.adopted) outcome = `Adopted Google Ads account ${c.customer_id} as the container for ${c.org_slug}`;
|
|
2591
|
+
else outcome = `Created Google Ads container for ${c.org_slug}`;
|
|
2592
|
+
console.log(outcome);
|
|
2528
2593
|
printAdsContainer(c);
|
|
2529
2594
|
const leftBehind = c.previous_campaign_count ?? 0;
|
|
2530
2595
|
if (c.previous_customer_id && leftBehind > 0) {
|
|
@@ -2583,6 +2648,90 @@ This key operates every org you manage; target one with X-Organization-ID (or \`
|
|
|
2583
2648
|
fail(e);
|
|
2584
2649
|
}
|
|
2585
2650
|
});
|
|
2651
|
+
managed.command("performance").description(
|
|
2652
|
+
"Daily spend, traffic and captured leads for the client orgs you manage \u2014 one organization with --organization, otherwise every client. A metric whose source dataset is missing or unreadable prints as '-' and is explained under coverage; it is never reported as zero."
|
|
2653
|
+
).option("--organization <slug>", "read one organization instead of every client org").option("--from <YYYY-MM-DD>", "first day to include (inclusive); defaults to 89 days before --to").option("--to <YYYY-MM-DD>", "last day to include (inclusive); defaults to today (UTC)").option("--daily", "one row per organization per day instead of window totals").option("--json", "print the raw JSON result instead of a table").action(
|
|
2654
|
+
async (opts) => {
|
|
2655
|
+
try {
|
|
2656
|
+
const res = await new ErdoClient().managerPerformance({
|
|
2657
|
+
organization_slug: opts.organization,
|
|
2658
|
+
from: opts.from,
|
|
2659
|
+
to: opts.to
|
|
2660
|
+
});
|
|
2661
|
+
if (opts.json) {
|
|
2662
|
+
print(res);
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
if (res.organizations.length === 0) {
|
|
2666
|
+
console.log(res.message || "No organizations to read.");
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
const cell = (v, digits = 0) => v === null ? "-" : v.toFixed(digits);
|
|
2670
|
+
const total = (days, pick) => {
|
|
2671
|
+
let sum = 0;
|
|
2672
|
+
for (const day of days) {
|
|
2673
|
+
const v = pick(day);
|
|
2674
|
+
if (v === null) return null;
|
|
2675
|
+
sum += v;
|
|
2676
|
+
}
|
|
2677
|
+
return sum;
|
|
2678
|
+
};
|
|
2679
|
+
if (opts.daily) {
|
|
2680
|
+
const rows = [];
|
|
2681
|
+
for (const org2 of res.organizations) {
|
|
2682
|
+
for (const day of org2.days) {
|
|
2683
|
+
rows.push([
|
|
2684
|
+
org2.organization_slug,
|
|
2685
|
+
day.date,
|
|
2686
|
+
cell(day.spend, 2),
|
|
2687
|
+
cell(day.clicks),
|
|
2688
|
+
cell(day.visits),
|
|
2689
|
+
cell(day.form_starts),
|
|
2690
|
+
cell(day.leads_on_page),
|
|
2691
|
+
cell(day.leads_captured)
|
|
2692
|
+
]);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
printAlignedTable(
|
|
2696
|
+
["organization", "date", "spend", "clicks", "visits", "form starts", "page leads", "leads"],
|
|
2697
|
+
rows
|
|
2698
|
+
);
|
|
2699
|
+
} else {
|
|
2700
|
+
printAlignedTable(
|
|
2701
|
+
["organization", "spend", "clicks", "visits", "form starts", "page leads", "leads", "cost/lead"],
|
|
2702
|
+
res.organizations.map((org2) => {
|
|
2703
|
+
const spend = total(org2.days, (d) => d.spend);
|
|
2704
|
+
const leads = total(org2.days, (d) => d.leads_captured);
|
|
2705
|
+
const costPerLead = spend === null || leads === null || leads === 0 ? null : spend / leads;
|
|
2706
|
+
return [
|
|
2707
|
+
org2.organization_slug,
|
|
2708
|
+
cell(spend, 2),
|
|
2709
|
+
cell(total(org2.days, (d) => d.clicks)),
|
|
2710
|
+
cell(total(org2.days, (d) => d.visits)),
|
|
2711
|
+
cell(total(org2.days, (d) => d.form_starts)),
|
|
2712
|
+
cell(total(org2.days, (d) => d.leads_on_page)),
|
|
2713
|
+
cell(leads, 0),
|
|
2714
|
+
cell(costPerLead, 2)
|
|
2715
|
+
];
|
|
2716
|
+
})
|
|
2717
|
+
);
|
|
2718
|
+
}
|
|
2719
|
+
process.stderr.write(`
|
|
2720
|
+
${res.from} to ${res.to} (${res.timezone} days)
|
|
2721
|
+
`);
|
|
2722
|
+
for (const org2 of res.organizations) {
|
|
2723
|
+
for (const gap of org2.coverage) {
|
|
2724
|
+
process.stderr.write(
|
|
2725
|
+
`${org2.organization_slug}: ${gap.metrics.join(", ")} unavailable \u2014 ${gap.reason}
|
|
2726
|
+
`
|
|
2727
|
+
);
|
|
2728
|
+
}
|
|
2729
|
+
}
|
|
2730
|
+
} catch (e) {
|
|
2731
|
+
fail(e);
|
|
2732
|
+
}
|
|
2733
|
+
}
|
|
2734
|
+
);
|
|
2586
2735
|
var tokenCmd = program.command("token").description("Manage your API tokens (account-level credentials)");
|
|
2587
2736
|
tokenCmd.command("create").description("Mint a new API token; the secret is printed ONCE").requiredOption("--name <name>", "a label for the token").option("--expires-days <n>", "days until expiry (default: 30)", (v) => parseInt(v, 10)).addOption(
|
|
2588
2737
|
orgOption("the token's default org (defaults to your active org; must be one you belong to)")
|
|
@@ -2871,7 +3020,7 @@ function splitVariantSpec(spec) {
|
|
|
2871
3020
|
const key = eq > 0 ? trimmed.slice(0, eq).trim() : "";
|
|
2872
3021
|
const startsToken = trimmed === "control" || trimmed === "is_control" || key !== "" && VARIANT_SPEC_KEYS.has(key);
|
|
2873
3022
|
if (!startsToken && parts.length > 0 && COMMA_SAFE_VARIANT_KEYS.has(lastKey)) {
|
|
2874
|
-
parts[parts.length - 1] +=
|
|
3023
|
+
parts[parts.length - 1] += `,${raw}`;
|
|
2875
3024
|
continue;
|
|
2876
3025
|
}
|
|
2877
3026
|
parts.push(raw);
|
|
@@ -3381,9 +3530,13 @@ ${"Case".padEnd(34)}${res.models.map(modelCol).join("")}`);
|
|
|
3381
3530
|
console.log(`
|
|
3382
3531
|
${"Summary (avg of all cases)".padEnd(34)}${res.models.map(modelCol).join("")}`);
|
|
3383
3532
|
for (const stat of ["score", "pass", "cost", "secs"]) {
|
|
3384
|
-
let line =
|
|
3533
|
+
let line = ` ${stat}`.padEnd(34);
|
|
3385
3534
|
for (const s of res.summary) {
|
|
3386
|
-
|
|
3535
|
+
let v;
|
|
3536
|
+
if (stat === "score") v = s.avg_score.toFixed(2);
|
|
3537
|
+
else if (stat === "pass") v = `${Math.round(s.pass_rate * 100)}%`;
|
|
3538
|
+
else if (stat === "cost") v = `$${(s.avg_cost_millicents / 1e5).toFixed(3)}/case`;
|
|
3539
|
+
else v = `${(s.avg_duration_ms / 1e3).toFixed(0)}s`;
|
|
3387
3540
|
line += v.padEnd(18);
|
|
3388
3541
|
}
|
|
3389
3542
|
console.log(line);
|
|
@@ -3731,9 +3884,11 @@ function printRunDetail(detail, wantResources, wantSteps) {
|
|
|
3731
3884
|
group.set(key, entry);
|
|
3732
3885
|
byKind.set(kind, group);
|
|
3733
3886
|
}
|
|
3734
|
-
const kinds = [...byKind.keys()].sort(
|
|
3735
|
-
(a
|
|
3736
|
-
|
|
3887
|
+
const kinds = [...byKind.keys()].sort((a, b) => {
|
|
3888
|
+
if (a === "skill") return -1;
|
|
3889
|
+
if (b === "skill") return 1;
|
|
3890
|
+
return a.localeCompare(b);
|
|
3891
|
+
});
|
|
3737
3892
|
for (const kind of kinds) {
|
|
3738
3893
|
console.log(` ${kind}`);
|
|
3739
3894
|
for (const entry of byKind.get(kind).values()) {
|
|
@@ -3811,6 +3966,46 @@ function decisionLine(d) {
|
|
|
3811
3966
|
}
|
|
3812
3967
|
return parts.join(" ");
|
|
3813
3968
|
}
|
|
3969
|
+
function renderApprovalCard(approval) {
|
|
3970
|
+
console.log("");
|
|
3971
|
+
console.log("Approval:");
|
|
3972
|
+
const headline = approval.action_display || approval.action_headline;
|
|
3973
|
+
if (headline) console.log(` ${headline}`);
|
|
3974
|
+
if (approval.action_context) console.log(` ${approval.action_context}`);
|
|
3975
|
+
for (const item of approval.items ?? []) {
|
|
3976
|
+
const params = item.params ? Object.entries(item.params).map(([k, v]) => `${k}=${v}`).join(", ") : "";
|
|
3977
|
+
console.log(` - ${item.what}${params ? ` (${params})` : ""}`);
|
|
3978
|
+
if (item.why) console.log(` why: ${item.why}`);
|
|
3979
|
+
}
|
|
3980
|
+
if (approval.omitted_items) console.log(` and ${approval.omitted_items} more`);
|
|
3981
|
+
if (approval.occurrence_count && approval.occurrence_count > 1) {
|
|
3982
|
+
console.log(` proposed ${approval.occurrence_count}x`);
|
|
3983
|
+
}
|
|
3984
|
+
}
|
|
3985
|
+
function renderReportEffect(e) {
|
|
3986
|
+
if (e.measurability !== "measurable") {
|
|
3987
|
+
const reason = e.unmeasurable_reason ? `: ${e.unmeasurable_reason}` : "";
|
|
3988
|
+
console.log(` ${e.metric || e.measurability} \u2014 ${e.measurability}${reason}`);
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3991
|
+
const predicateValue = e.target_value ?? e.min_delta;
|
|
3992
|
+
const predicate = [
|
|
3993
|
+
e.metric,
|
|
3994
|
+
e.success_operator,
|
|
3995
|
+
predicateValue !== void 0 && predicateValue !== null ? formatNumber(predicateValue) : void 0
|
|
3996
|
+
].filter((p) => p !== void 0 && p !== null && p !== "").join(" ");
|
|
3997
|
+
const windows = [];
|
|
3998
|
+
if (e.baseline_window_days) windows.push(`baseline ${e.baseline_window_days}d`);
|
|
3999
|
+
if (e.outcome_window_days) windows.push(`outcome ${e.outcome_window_days}d`);
|
|
4000
|
+
const windowStr = windows.length ? ` (${windows.join(", ")})` : "";
|
|
4001
|
+
console.log(` ${predicate || e.metric || "effect"}${windowStr} \u2014 ${e.status}`);
|
|
4002
|
+
if (e.baseline_value !== void 0 && e.baseline_value !== null && e.outcome_value !== void 0 && e.outcome_value !== null) {
|
|
4003
|
+
const label = e.outcome_label ? ` (${e.outcome_label})` : "";
|
|
4004
|
+
console.log(` measured: ${formatNumber(e.baseline_value)} \u2192 ${formatNumber(e.outcome_value)}${label}`);
|
|
4005
|
+
} else if (e.outcome_label) {
|
|
4006
|
+
console.log(` outcome: ${e.outcome_label}`);
|
|
4007
|
+
}
|
|
4008
|
+
}
|
|
3814
4009
|
decisionsCmd.command("list").description("List decisions, newest first").option("--workstream <slug>", "only decisions filed under this workstream/Strategy").option(
|
|
3815
4010
|
"--source <source>",
|
|
3816
4011
|
"producer family: approval | escalation | engine_gate | allocator | experiment | workstream_commitment"
|
|
@@ -3870,6 +4065,7 @@ function renderDecision(detail) {
|
|
|
3870
4065
|
console.log("");
|
|
3871
4066
|
console.log(`Why: ${d.why}`);
|
|
3872
4067
|
}
|
|
4068
|
+
if (d.alternatives_considered) console.log(`Alternatives considered: ${d.alternatives_considered}`);
|
|
3873
4069
|
if (d.decider_rationale) console.log(`Decider said: ${d.decider_rationale}`);
|
|
3874
4070
|
if (detail.actions.length) {
|
|
3875
4071
|
console.log("");
|
|
@@ -3879,6 +4075,12 @@ function renderDecision(detail) {
|
|
|
3879
4075
|
if (a.subject_label) console.log(` subject: ${a.subject_label}`);
|
|
3880
4076
|
if (a.effective_at) console.log(` effective: ${a.effective_at}`);
|
|
3881
4077
|
if (a.result_summary) console.log(` result: ${a.result_summary}`);
|
|
4078
|
+
if (a.annotations && Object.keys(a.annotations).length) {
|
|
4079
|
+
console.log(" before:");
|
|
4080
|
+
for (const [k, v] of Object.entries(a.annotations)) {
|
|
4081
|
+
console.log(` ${k}: ${compactJSON(v) ?? "null"}`);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
3882
4084
|
});
|
|
3883
4085
|
}
|
|
3884
4086
|
if (detail.effects.length) {
|
|
@@ -3896,13 +4098,28 @@ function renderDecision(detail) {
|
|
|
3896
4098
|
if (e.unmeasurable_reason) console.log(` reason: ${e.unmeasurable_reason}`);
|
|
3897
4099
|
});
|
|
3898
4100
|
}
|
|
3899
|
-
|
|
4101
|
+
const lineage = detail.lineage;
|
|
4102
|
+
if (lineage.supersedes_slug || lineage.superseded_by_slugs?.length || lineage.workstream_slug || lineage.attention_item_slug) {
|
|
3900
4103
|
console.log("");
|
|
3901
4104
|
console.log("Lineage:");
|
|
3902
|
-
if (
|
|
3903
|
-
for (const slug of
|
|
4105
|
+
if (lineage.supersedes_slug) console.log(` replaced ${lineage.supersedes_slug}`);
|
|
4106
|
+
for (const slug of lineage.superseded_by_slugs ?? []) {
|
|
3904
4107
|
console.log(` replaced by ${slug}`);
|
|
3905
4108
|
}
|
|
4109
|
+
if (lineage.workstream_slug) {
|
|
4110
|
+
console.log(` workstream: ${lineage.workstream_title || lineage.workstream_slug} (${lineage.workstream_slug})`);
|
|
4111
|
+
}
|
|
4112
|
+
if (lineage.attention_item_slug) {
|
|
4113
|
+
console.log(
|
|
4114
|
+
` raised by: ${lineage.attention_item_title || lineage.attention_item_slug} (${lineage.attention_item_slug})`
|
|
4115
|
+
);
|
|
4116
|
+
}
|
|
4117
|
+
}
|
|
4118
|
+
if (lineage.approval) renderApprovalCard(lineage.approval);
|
|
4119
|
+
else if (lineage.approval_unreadable) {
|
|
4120
|
+
console.log("");
|
|
4121
|
+
console.log("Approval:");
|
|
4122
|
+
console.log(" The approval card could not be loaded.");
|
|
3906
4123
|
}
|
|
3907
4124
|
}
|
|
3908
4125
|
decisionsCmd.command("show <slug>").description(
|
|
@@ -4176,9 +4393,7 @@ approvalsCmd.command("settings [mode]").description(
|
|
|
4176
4393
|
try {
|
|
4177
4394
|
const client = new ErdoClient();
|
|
4178
4395
|
let gates;
|
|
4179
|
-
if (opts.gates
|
|
4180
|
-
gates = opts.gates.split(",").map((g) => g.trim()).filter(Boolean);
|
|
4181
|
-
} else {
|
|
4396
|
+
if (opts.gates === void 0) {
|
|
4182
4397
|
switch (mode) {
|
|
4183
4398
|
case void 0:
|
|
4184
4399
|
break;
|
|
@@ -4195,6 +4410,8 @@ approvalsCmd.command("settings [mode]").description(
|
|
|
4195
4410
|
default:
|
|
4196
4411
|
throw new Error(`unknown mode ${mode} \u2014 use safe | all | reset, or --gates spend,destructive`);
|
|
4197
4412
|
}
|
|
4413
|
+
} else {
|
|
4414
|
+
gates = opts.gates.split(",").map((g) => g.trim()).filter(Boolean);
|
|
4198
4415
|
}
|
|
4199
4416
|
if (gates !== void 0) {
|
|
4200
4417
|
await client.setApprovalSettings(gates);
|
|
@@ -4212,19 +4429,100 @@ approvalsCmd.command("settings [mode]").description(
|
|
|
4212
4429
|
}
|
|
4213
4430
|
});
|
|
4214
4431
|
var attnCmd = program.command("attention").description("The attention feed \u2014 digests, choices, escalations awaiting a human");
|
|
4215
|
-
|
|
4432
|
+
function attentionItemLine(item) {
|
|
4433
|
+
return [item.slug, item.severity, item.kind, item.status, item.title].join(" ");
|
|
4434
|
+
}
|
|
4435
|
+
function renderAttentionItemReport(item, report) {
|
|
4436
|
+
console.log("");
|
|
4437
|
+
console.log("Safe default:");
|
|
4438
|
+
const expires = formatDate(item.expires_at);
|
|
4439
|
+
if (report.proposed_answer) {
|
|
4440
|
+
if (!expires) {
|
|
4441
|
+
console.log(` Erdo proposes: ${report.proposed_answer}`);
|
|
4442
|
+
console.log(" No deadline \u2014 nothing applies until somebody answers");
|
|
4443
|
+
} else if (report.safe_default_approves) {
|
|
4444
|
+
console.log(` If nobody answers: ${report.proposed_answer}`);
|
|
4445
|
+
console.log(` Applies ${expires}`);
|
|
4446
|
+
} else {
|
|
4447
|
+
console.log(` Erdo proposes: ${report.proposed_answer}`);
|
|
4448
|
+
console.log(` If nobody answers, Erdo will NOT do this \u2014 the item closes ${expires}`);
|
|
4449
|
+
}
|
|
4450
|
+
} else {
|
|
4451
|
+
console.log(expires ? ` Closes unanswered ${expires}` : " Does not expire");
|
|
4452
|
+
}
|
|
4453
|
+
if (report.has_pending_action) console.log(" Holds a pending engine action");
|
|
4454
|
+
const safeDefault = compactJSON(report.safe_default);
|
|
4455
|
+
if (safeDefault) console.log(` Default answer: ${safeDefault}`);
|
|
4456
|
+
if (report.workstream_slug || report.workstream_title) {
|
|
4457
|
+
console.log("");
|
|
4458
|
+
console.log("Workstream:");
|
|
4459
|
+
console.log(` ${report.workstream_title || report.workstream_slug} (${report.workstream_slug})`);
|
|
4460
|
+
}
|
|
4461
|
+
if (report.decision_slug) {
|
|
4462
|
+
console.log("");
|
|
4463
|
+
console.log("Decision:");
|
|
4464
|
+
if (report.decision) {
|
|
4465
|
+
const d = report.decision;
|
|
4466
|
+
console.log(` ${d.slug} \u2014 ${d.what}`);
|
|
4467
|
+
if (d.why) console.log(` why: ${d.why}`);
|
|
4468
|
+
if (d.alternatives_considered) console.log(` alternatives considered: ${d.alternatives_considered}`);
|
|
4469
|
+
console.log(` decided by: ${d.decider_kind}`);
|
|
4470
|
+
console.log(` status: ${d.status}`);
|
|
4471
|
+
if (d.outcome_label) console.log(` outcome: ${d.outcome_label}`);
|
|
4472
|
+
} else {
|
|
4473
|
+
console.log(` ${report.decision_slug} \u2014 exists, but in a project you cannot view`);
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
if (report.approval) renderApprovalCard(report.approval);
|
|
4477
|
+
else if (report.approval_unreadable) {
|
|
4478
|
+
console.log("");
|
|
4479
|
+
console.log("Approval:");
|
|
4480
|
+
console.log(" The approval card could not be loaded.");
|
|
4481
|
+
}
|
|
4482
|
+
if (report.effects?.length) {
|
|
4483
|
+
console.log("");
|
|
4484
|
+
console.log("Predicted vs measured:");
|
|
4485
|
+
for (const e of report.effects) renderReportEffect(e);
|
|
4486
|
+
}
|
|
4487
|
+
if (report.related_items?.length) {
|
|
4488
|
+
console.log("");
|
|
4489
|
+
console.log("Earlier items:");
|
|
4490
|
+
for (const r of report.related_items) {
|
|
4491
|
+
const reasons = [r.same_workstream ? "same workstream" : void 0, r.same_title ? "same title" : void 0].filter(Boolean).join(", ");
|
|
4492
|
+
const created = formatDate(r.created_at) ?? r.created_at;
|
|
4493
|
+
console.log(
|
|
4494
|
+
` [${r.severity}] ${r.status} \u2014 ${r.title} (${r.slug}) \u2014 ${created}${reasons ? ` \u2014 ${reasons}` : ""}`
|
|
4495
|
+
);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
attnCmd.command("list").description("List attention items").option("--status <status...>", "open | answered | dismissed | expired").option("--open", "shorthand for --status open").option("--engine-actions", "only engine-generated items").option("--item <idOrSlug>", "read one item: its slug or its uuid").option("--org <slug>", "read another organization's feed \u2014 yours, or a client you manage").option(
|
|
4500
|
+
"--subject-kind <kind>",
|
|
4501
|
+
"only items about this kind of thing: google_ads_campaign | meta_ads_campaign | google_ads_ad_group | google_ads_ad | page | dataset | workstream"
|
|
4502
|
+
).option("--subject-id <id>", "with --subject-kind, only items about this exact subject").option("-n, --limit <n>", "max items", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).option("--json", "output raw JSON").action(
|
|
4216
4503
|
async (opts) => {
|
|
4217
4504
|
try {
|
|
4218
4505
|
const statuses = opts.status?.length ? opts.status : opts.open ? ["open"] : void 0;
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4506
|
+
const res = await new ErdoClient().listAttentionItems({
|
|
4507
|
+
statuses,
|
|
4508
|
+
engine_actions_only: opts.engineActions,
|
|
4509
|
+
item_id: opts.item,
|
|
4510
|
+
organization_slug: opts.org,
|
|
4511
|
+
subject_kind: opts.subjectKind,
|
|
4512
|
+
subject_id: opts.subjectId,
|
|
4513
|
+
limit: opts.limit,
|
|
4514
|
+
offset: opts.offset
|
|
4515
|
+
});
|
|
4516
|
+
if (opts.json) {
|
|
4517
|
+
print(res);
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4520
|
+
if (!res.items.length) {
|
|
4521
|
+
console.log("No attention items match. Widen the filters.");
|
|
4522
|
+
return;
|
|
4523
|
+
}
|
|
4524
|
+
for (const item of res.items) console.log(attentionItemLine(item));
|
|
4525
|
+
if (res.report) renderAttentionItemReport(res.items[0], res.report);
|
|
4228
4526
|
} catch (e) {
|
|
4229
4527
|
fail(e);
|
|
4230
4528
|
}
|
|
@@ -4235,7 +4533,7 @@ attnCmd.command("respond <id>").description(
|
|
|
4235
4533
|
).option("--answer <json>", `a JSON answer, e.g. '{"q1":["option-a"]}'`).option(
|
|
4236
4534
|
"--flag-broken <json>",
|
|
4237
4535
|
'flag choice variants as broken: JSON array [{"variant_key":"...","reason":"..."}]'
|
|
4238
|
-
).option("--ack", "mark read / acknowledge").option("--dismiss", "dismiss the item").action(
|
|
4536
|
+
).option("--ack", "mark read / acknowledge").option("--dismiss", "dismiss the item").option("--org <slug>", "the organization holding the item \u2014 yours, or a client you manage").action(
|
|
4239
4537
|
async (id, opts) => {
|
|
4240
4538
|
try {
|
|
4241
4539
|
if (opts.ack || opts.dismiss) {
|
|
@@ -4247,7 +4545,8 @@ attnCmd.command("respond <id>").description(
|
|
|
4247
4545
|
}
|
|
4248
4546
|
print(
|
|
4249
4547
|
await new ErdoClient().respondAttentionItem(id, {
|
|
4250
|
-
action: opts.ack ? "acknowledge" : "dismiss"
|
|
4548
|
+
action: opts.ack ? "acknowledge" : "dismiss",
|
|
4549
|
+
organization_slug: opts.org
|
|
4251
4550
|
})
|
|
4252
4551
|
);
|
|
4253
4552
|
return;
|
|
@@ -4255,7 +4554,7 @@ attnCmd.command("respond <id>").description(
|
|
|
4255
4554
|
if (opts.answer === void 0 && opts.flagBroken === void 0) {
|
|
4256
4555
|
fail(new Error("provide one of --answer, --flag-broken, --ack, or --dismiss"));
|
|
4257
4556
|
}
|
|
4258
|
-
const input = { action: "answer" };
|
|
4557
|
+
const input = { action: "answer", organization_slug: opts.org };
|
|
4259
4558
|
if (opts.answer !== void 0) {
|
|
4260
4559
|
try {
|
|
4261
4560
|
input.answer = JSON.parse(opts.answer);
|
|
@@ -4311,7 +4610,19 @@ pageFeedbackCmd.command("record").description(
|
|
|
4311
4610
|
);
|
|
4312
4611
|
}
|
|
4313
4612
|
let items;
|
|
4314
|
-
if (opts.items
|
|
4613
|
+
if (opts.items === void 0) {
|
|
4614
|
+
if (!opts.experiment || !opts.variant || !opts.feedback) {
|
|
4615
|
+
throw new Error("provide --items, or all of --experiment, --variant, --feedback");
|
|
4616
|
+
}
|
|
4617
|
+
items = [
|
|
4618
|
+
{
|
|
4619
|
+
experiment_slug: opts.experiment,
|
|
4620
|
+
variant_key: opts.variant,
|
|
4621
|
+
feedback: opts.feedback,
|
|
4622
|
+
image_bucket_key: opts.imageKey
|
|
4623
|
+
}
|
|
4624
|
+
];
|
|
4625
|
+
} else {
|
|
4315
4626
|
let parsed;
|
|
4316
4627
|
try {
|
|
4317
4628
|
parsed = JSON.parse(readMaybeFile(opts.items));
|
|
@@ -4324,18 +4635,6 @@ pageFeedbackCmd.command("record").description(
|
|
|
4324
4635
|
throw new Error("--items must be a JSON array of {experiment_slug, variant_key, feedback} objects");
|
|
4325
4636
|
}
|
|
4326
4637
|
items = parsed;
|
|
4327
|
-
} else {
|
|
4328
|
-
if (!opts.experiment || !opts.variant || !opts.feedback) {
|
|
4329
|
-
throw new Error("provide --items, or all of --experiment, --variant, --feedback");
|
|
4330
|
-
}
|
|
4331
|
-
items = [
|
|
4332
|
-
{
|
|
4333
|
-
experiment_slug: opts.experiment,
|
|
4334
|
-
variant_key: opts.variant,
|
|
4335
|
-
feedback: opts.feedback,
|
|
4336
|
-
image_bucket_key: opts.imageKey
|
|
4337
|
-
}
|
|
4338
|
-
];
|
|
4339
4638
|
}
|
|
4340
4639
|
print(await new ErdoClient().recordPageFeedback(items));
|
|
4341
4640
|
} catch (e) {
|
|
@@ -4581,7 +4880,7 @@ reviewsCmd.command("decide <id>").description(
|
|
|
4581
4880
|
opts.apply ? "apply" : null,
|
|
4582
4881
|
opts.resolve ? "resolve" : null,
|
|
4583
4882
|
opts.reject ? "reject" : null,
|
|
4584
|
-
opts.snooze
|
|
4883
|
+
opts.snooze === void 0 ? null : "snooze"
|
|
4585
4884
|
].filter(Boolean);
|
|
4586
4885
|
if (chosen.length !== 1) {
|
|
4587
4886
|
fail(new Error("provide exactly one of --apply, --resolve, --reject, or --snooze"));
|
|
@@ -4601,6 +4900,11 @@ reviewsCmd.command("decide <id>").description(
|
|
|
4601
4900
|
}
|
|
4602
4901
|
);
|
|
4603
4902
|
var pagesCmd = program.command("pages").description("Create and manage pages/artifacts");
|
|
4903
|
+
function publicFlag(opts) {
|
|
4904
|
+
if (opts.public) return true;
|
|
4905
|
+
if (opts.private) return false;
|
|
4906
|
+
return void 0;
|
|
4907
|
+
}
|
|
4604
4908
|
function grantList(v) {
|
|
4605
4909
|
if (v === void 0) return void 0;
|
|
4606
4910
|
if (v.trim() === "[]") return [];
|
|
@@ -4676,7 +4980,9 @@ pagesCmd.command("update <id>").description(
|
|
|
4676
4980
|
writable_dataset_slugs: grantList(opts.writableDatasets),
|
|
4677
4981
|
kv_slugs: grantList(opts.kv),
|
|
4678
4982
|
writable_kv_slugs: grantList(opts.writableKv),
|
|
4679
|
-
|
|
4983
|
+
// --public and --private are mutually exclusive flags; neither set
|
|
4984
|
+
// leaves the server's default standing.
|
|
4985
|
+
public: publicFlag(opts),
|
|
4680
4986
|
request_review: !!opts.requestReview
|
|
4681
4987
|
});
|
|
4682
4988
|
console.log(`${res.id} ${res.public_url || res.url}`);
|
|
@@ -5041,11 +5347,13 @@ sendingDomainsCmd.command("remove <domain>").description(
|
|
|
5041
5347
|
});
|
|
5042
5348
|
emailCmd.command("received").description(
|
|
5043
5349
|
"Read the mail that arrived at the org's sending domain, newest first \u2014 usually leads replying to outreach an agent sent. Omit --domain: an org has one sending domain and the read resolves it."
|
|
5044
|
-
).option("--domain <domain>", "the sending domain to read (only needed if the org has more than one)").option("--limit <n>", "maximum messages to return, newest first").option("--json", "print the raw JSON result instead of the message list").action(async (opts) => {
|
|
5350
|
+
).option("--domain <domain>", "the sending domain to read (only needed if the org has more than one)").option("--limit <n>", "maximum messages to return, newest first").option("--lead-ref <ref>", "only one lead's email conversation with the desk").option("--dataset <slug>", "with --lead-ref, only that lead dataset").option("--json", "print the raw JSON result instead of the message list").action(async (opts) => {
|
|
5045
5351
|
try {
|
|
5046
5352
|
const res = await new ErdoClient().listReceivedEmails({
|
|
5047
5353
|
domain: opts.domain,
|
|
5048
|
-
limit: opts.limit ? Number(opts.limit) : void 0
|
|
5354
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
5355
|
+
leadRef: opts.leadRef,
|
|
5356
|
+
dataset: opts.dataset
|
|
5049
5357
|
});
|
|
5050
5358
|
if (opts.json) {
|
|
5051
5359
|
print(res);
|
|
@@ -5062,12 +5370,17 @@ emailCmd.command("received").description(
|
|
|
5062
5370
|
`);
|
|
5063
5371
|
for (const m of emails) {
|
|
5064
5372
|
const who = m.from_name ? `${m.from_name} <${m.from_email}>` : m.from_email;
|
|
5065
|
-
|
|
5373
|
+
const side = m.direction ? ` [${m.direction}${m.lead_email ? ` \xB7 lead ${m.lead_email}` : ""}]` : "";
|
|
5374
|
+
console.log(`${m.received_at} ${who} ${m.subject || "(no subject)"}${side}`);
|
|
5066
5375
|
const preview = (m.text_preview || "").replace(/\s+/g, " ").trim();
|
|
5067
5376
|
if (preview) console.log(` ${preview.length > 160 ? `${preview.slice(0, 160)}\u2026` : preview}`);
|
|
5068
|
-
|
|
5069
|
-
|
|
5070
|
-
|
|
5377
|
+
if (m.relay_skip_reason) {
|
|
5378
|
+
console.log(` kept in Erdo, not relayed (${m.relay_skip_reason})`);
|
|
5379
|
+
} else {
|
|
5380
|
+
console.log(
|
|
5381
|
+
m.forwarded_at ? ` ${m.relayed_email_id ? "relayed" : "forwarded"} ${m.forwarded_at}` : " not forwarded \u2014 this one is in Erdo only, so nobody on your side has necessarily seen it"
|
|
5382
|
+
);
|
|
5383
|
+
}
|
|
5071
5384
|
}
|
|
5072
5385
|
} catch (e) {
|
|
5073
5386
|
fail(e);
|
|
@@ -5265,6 +5578,8 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
|
|
|
5265
5578
|
"status",
|
|
5266
5579
|
"secs",
|
|
5267
5580
|
"transcript",
|
|
5581
|
+
"placed by",
|
|
5582
|
+
"dial",
|
|
5268
5583
|
"contact",
|
|
5269
5584
|
"lead",
|
|
5270
5585
|
"lead ref",
|
|
@@ -5279,6 +5594,10 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
|
|
|
5279
5594
|
call.status,
|
|
5280
5595
|
call.duration_seconds,
|
|
5281
5596
|
call.has_transcript ? "yes" : "no",
|
|
5597
|
+
// "-" rather than blank: a call the AI agent placed has no person
|
|
5598
|
+
// behind it, which is a fact, not a missing value.
|
|
5599
|
+
call.placed_by_name || "-",
|
|
5600
|
+
call.dial_outcome || "-",
|
|
5282
5601
|
contactLabel(call.contact),
|
|
5283
5602
|
leadStatusLabel(call.lead_status),
|
|
5284
5603
|
leadReferenceLabel(call.lead_reference),
|
|
@@ -5298,7 +5617,7 @@ voiceCallsCmd.command("list").description("List the organization's phone calls,
|
|
|
5298
5617
|
}
|
|
5299
5618
|
);
|
|
5300
5619
|
voiceCallsCmd.command("get <callID>").description(
|
|
5301
|
-
"Read one phone call in full: transcript, summary, per-turn LLM metrics, and the lead it produced (contact, lead_status, lead_saved_at, canonical_lead_id, lead_reference)"
|
|
5620
|
+
"Read one phone call in full: transcript, summary, per-turn LLM metrics, who placed it by hand (placed_by_name) and what became of the lead's leg (dial_outcome), and the lead it produced (contact, lead_status, lead_saved_at, canonical_lead_id, lead_reference)"
|
|
5302
5621
|
).action(async (callID) => {
|
|
5303
5622
|
try {
|
|
5304
5623
|
print(await new ErdoClient().getVoiceCall(callID));
|
|
@@ -5537,12 +5856,14 @@ voiceNumbersCmd.command("list").description("List the organization's numbers wit
|
|
|
5537
5856
|
if (numbers.length === 0) {
|
|
5538
5857
|
console.log("This organization holds no phone numbers.");
|
|
5539
5858
|
} else {
|
|
5859
|
+
const callable = new Set(res.person_call_numbers ?? []);
|
|
5540
5860
|
printAlignedTable(
|
|
5541
|
-
["number", "agent", "kind", "status", "waiting on", "reason"],
|
|
5861
|
+
["number", "agent", "kind", "calls", "status", "waiting on", "reason"],
|
|
5542
5862
|
numbers.map((n) => [
|
|
5543
5863
|
n.address,
|
|
5544
5864
|
n.agent_ref ?? "",
|
|
5545
5865
|
n.sender_kind,
|
|
5866
|
+
callable.has(n.address) ? "yes" : "",
|
|
5546
5867
|
n.status,
|
|
5547
5868
|
n.next_step ?? "",
|
|
5548
5869
|
n.reason ?? ""
|
|
@@ -5627,6 +5948,28 @@ voiceAgentsCmd.command("sms-replies <slug>").description("Turn a voice agent's a
|
|
|
5627
5948
|
fail(e);
|
|
5628
5949
|
}
|
|
5629
5950
|
});
|
|
5951
|
+
voiceAgentsCmd.command("whatsapp-replies <slug>").description("Turn a voice agent's answering on WhatsApp on or off").option("--on", "the agent answers WhatsApp messages sent to its own number").option("--off", "messages still arrive and stay readable; the agent does not write back").action(async (slug, opts) => {
|
|
5952
|
+
if (Boolean(opts.on) === Boolean(opts.off)) {
|
|
5953
|
+
fail(new Error("say which way: pass --on or --off (exactly one)"));
|
|
5954
|
+
}
|
|
5955
|
+
try {
|
|
5956
|
+
const agent = await new ErdoClient().setVoiceAgentWhatsAppReplies(slug, Boolean(opts.on));
|
|
5957
|
+
console.log(
|
|
5958
|
+
`${agent.slug} ${agent.whatsapp_replies_enabled ? "on" : "off"} ${agent.phone_number ?? "-"} ${agent.name}`
|
|
5959
|
+
);
|
|
5960
|
+
if (!agent.phone_number) {
|
|
5961
|
+
process.stderr.write(
|
|
5962
|
+
"this agent has no phone number yet \u2014 the setting is stored and applies once it gets one\n"
|
|
5963
|
+
);
|
|
5964
|
+
} else {
|
|
5965
|
+
process.stderr.write(
|
|
5966
|
+
"the number also has to be set up as a WhatsApp sender before any reply can go out\n"
|
|
5967
|
+
);
|
|
5968
|
+
}
|
|
5969
|
+
} catch (e) {
|
|
5970
|
+
fail(e);
|
|
5971
|
+
}
|
|
5972
|
+
});
|
|
5630
5973
|
var datasetsCmd = program.command("datasets").description("Datasets");
|
|
5631
5974
|
datasetsCmd.command("list").description("List datasets").option(
|
|
5632
5975
|
"--class <class>",
|
|
@@ -6070,8 +6413,10 @@ refreshCmd.command("set <slug>").description(
|
|
|
6070
6413
|
const slugs = opts.contextDatasets.split(",").map((s) => s.trim()).filter(Boolean);
|
|
6071
6414
|
if (slugs.length) body.context_dataset_slugs = slugs;
|
|
6072
6415
|
}
|
|
6073
|
-
if (
|
|
6074
|
-
if (opts.staleAfter !== void 0)
|
|
6416
|
+
if (opts.onView !== void 0) body.refresh_on_view_enabled = opts.onView;
|
|
6417
|
+
if (opts.staleAfter !== void 0) {
|
|
6418
|
+
body.refresh_on_view_stale_after_seconds = opts.staleAfter;
|
|
6419
|
+
}
|
|
6075
6420
|
if (opts.debounce !== void 0) body.refresh_on_view_debounce_seconds = opts.debounce;
|
|
6076
6421
|
if (opts.liveWriteMode) body.live_write_mode = opts.liveWriteMode;
|
|
6077
6422
|
if (opts.payloadSchema) {
|
|
@@ -6520,7 +6865,7 @@ List tables with: erdo integrations tables ${app} <schema>`);
|
|
|
6520
6865
|
return;
|
|
6521
6866
|
}
|
|
6522
6867
|
for (const t of res.tables ?? []) {
|
|
6523
|
-
const rows = t.estimated_row_count
|
|
6868
|
+
const rows = t.estimated_row_count == null ? "" : ` ~${t.estimated_row_count} rows`;
|
|
6524
6869
|
console.log(`${t.schema_name}.${t.table_name} ${t.columns.length} columns${rows}`);
|
|
6525
6870
|
}
|
|
6526
6871
|
} catch (e) {
|