@erdoai/cli 0.67.0 → 0.71.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 +516 -21
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -297,14 +297,19 @@ var ErdoClient = class {
|
|
|
297
297
|
createManagerKey() {
|
|
298
298
|
return this.request("POST", "/v1/manager-key");
|
|
299
299
|
}
|
|
300
|
-
// Brings an EXISTING org under your manager org
|
|
301
|
-
// token
|
|
302
|
-
//
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// a
|
|
306
|
-
|
|
307
|
-
|
|
300
|
+
// Brings an EXISTING org under your manager org. Two consents redeem here: a
|
|
301
|
+
// one-time token minted by the target org's owner (or, for an ownerless managed
|
|
302
|
+
// org, its current manager) — a manager credential on its own can never adopt an
|
|
303
|
+
// arbitrary org — or, DIRECT adoption, the org's slug when the caller themselves
|
|
304
|
+
// is a seated owner of it (both halves of the consent in one person, so no token
|
|
305
|
+
// ferrying). The path is a sibling of /v1/managed-organizations rather than a
|
|
306
|
+
// nested `adopt` because Encore rejects a static segment alongside the
|
|
307
|
+
// parameterized /:orgSlug routes.
|
|
308
|
+
adoptManagedOrganization(input) {
|
|
309
|
+
return this.request("POST", "/v1/managed-organization-adoptions", {
|
|
310
|
+
token: input.token ?? "",
|
|
311
|
+
organization_slug: input.organizationSlug ?? ""
|
|
312
|
+
});
|
|
308
313
|
}
|
|
309
314
|
// Seats a person in a managed org. The role is capped at admin — ownership of a
|
|
310
315
|
// client org stays with the client — and an omitted role means the backend's
|
|
@@ -331,8 +336,30 @@ var ErdoClient = class {
|
|
|
331
336
|
listCustomDomains() {
|
|
332
337
|
return this.request("GET", "/v1/custom-domains");
|
|
333
338
|
}
|
|
334
|
-
|
|
335
|
-
|
|
339
|
+
// `managerDefault` registers the domain and points the orgs this one manages at
|
|
340
|
+
// it in one step, for a manager setting up its very first branded hostname. The
|
|
341
|
+
// key is left out of the body when false so an ordinary registration sends what
|
|
342
|
+
// it always sent; changing the flag on a domain that is already live goes
|
|
343
|
+
// through setCustomDomainManagerDefault, which does not touch the certificate.
|
|
344
|
+
createCustomDomain(domain, managerDefault) {
|
|
345
|
+
return this.request(
|
|
346
|
+
"POST",
|
|
347
|
+
"/v1/custom-domains",
|
|
348
|
+
managerDefault ? { domain, manager_default: true } : { domain }
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
// Marks a domain the org already owns as the one its managed orgs serve their
|
|
352
|
+
// pages from, or clears it. At most one domain per org carries the flag, so
|
|
353
|
+
// promoting a second demotes the first in the same write — the managed orgs
|
|
354
|
+
// never see a moment with no default and fall back to platform URLs. Only a
|
|
355
|
+
// manager account with at least one client org can set it; clearing is always
|
|
356
|
+
// allowed, so an org that has since let its last client go can still tidy up.
|
|
357
|
+
setCustomDomainManagerDefault(domain, managerDefault) {
|
|
358
|
+
return this.request(
|
|
359
|
+
"PATCH",
|
|
360
|
+
`/v1/custom-domains/${encodeURIComponent(domain)}/manager-default`,
|
|
361
|
+
{ manager_default: managerDefault }
|
|
362
|
+
);
|
|
336
363
|
}
|
|
337
364
|
deleteCustomDomain(domain) {
|
|
338
365
|
return this.request(
|
|
@@ -412,6 +439,22 @@ var ErdoClient = class {
|
|
|
412
439
|
getSentEmail(emailID) {
|
|
413
440
|
return this.request("GET", `/v1/emails/${encodeURIComponent(emailID)}`);
|
|
414
441
|
}
|
|
442
|
+
// Phone conversation records: inbound calls to a voice agent's own number and
|
|
443
|
+
// the outbound calls Erdo placed. Website chat widget conversations are a
|
|
444
|
+
// different resource — they live under /v1/voice/widget-conversations.
|
|
445
|
+
listVoiceCalls(params) {
|
|
446
|
+
const q = new URLSearchParams();
|
|
447
|
+
if (params?.agent) q.set("agent", params.agent);
|
|
448
|
+
if (params?.direction) q.set("direction", params.direction);
|
|
449
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
450
|
+
if (params?.offset !== void 0) q.set("offset", String(params.offset));
|
|
451
|
+
if (params?.cursor) q.set("cursor", params.cursor);
|
|
452
|
+
const qs = q.toString();
|
|
453
|
+
return this.request("GET", `/v1/voice/calls${qs ? `?${qs}` : ""}`);
|
|
454
|
+
}
|
|
455
|
+
getVoiceCall(callID) {
|
|
456
|
+
return this.request("GET", `/v1/voice/calls/${encodeURIComponent(callID)}`);
|
|
457
|
+
}
|
|
415
458
|
// Run a read-only HogQL query against the org's page-analytics events. Rows are
|
|
416
459
|
// positional per columns; enabled:false means page analytics is off for the org
|
|
417
460
|
// (not zero traffic). A rejected query surfaces PostHog's message as the error.
|
|
@@ -847,6 +890,14 @@ var ErdoClient = class {
|
|
|
847
890
|
listDatasetPurposes() {
|
|
848
891
|
return this.request("GET", `/v1/datasets-purposes`);
|
|
849
892
|
}
|
|
893
|
+
// Correct which dataset carries a purpose. Pass `purpose` to set one,
|
|
894
|
+
// `clear_purpose` to remove it; moving a purpose is clearing it from the old
|
|
895
|
+
// dataset and setting it on the right one. Setting a purpose the org already
|
|
896
|
+
// uses elsewhere succeeds and answers with a warning naming what it shadows,
|
|
897
|
+
// since one purpose is meant to map to one dataset.
|
|
898
|
+
setDatasetPurpose(slug, input) {
|
|
899
|
+
return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/schema`, input);
|
|
900
|
+
}
|
|
850
901
|
// The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending // The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending
|
|
851
902
|
// `query` made every `erdo datasets query` fail with "question is required".
|
|
852
903
|
//
|
|
@@ -888,6 +939,69 @@ var ErdoClient = class {
|
|
|
888
939
|
listDatasetRevisions(slug) {
|
|
889
940
|
return this.request("GET", `/v1/datasets/${encodeURIComponent(slug)}/revisions`);
|
|
890
941
|
}
|
|
942
|
+
// --- bounded outreach ---
|
|
943
|
+
putOutreachBatch(batch, body) {
|
|
944
|
+
return this.request(
|
|
945
|
+
"PUT",
|
|
946
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}`,
|
|
947
|
+
body
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
listOutreachBatches(opts = {}) {
|
|
951
|
+
const q = new URLSearchParams();
|
|
952
|
+
if (opts.limit !== void 0) q.set("limit", String(opts.limit));
|
|
953
|
+
if (opts.cursor) q.set("cursor", opts.cursor);
|
|
954
|
+
if (opts.initiative_ref) q.set("initiative_ref", opts.initiative_ref);
|
|
955
|
+
const qs = q.toString();
|
|
956
|
+
return this.request(
|
|
957
|
+
"GET",
|
|
958
|
+
`/v1/outreach-batches${qs ? `?${qs}` : ""}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
getOutreachBatch(batch) {
|
|
962
|
+
return this.request(
|
|
963
|
+
"GET",
|
|
964
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}`
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
listOutreachBatchRecipients(batch, opts = {}) {
|
|
968
|
+
const q = new URLSearchParams();
|
|
969
|
+
if (opts.limit !== void 0) q.set("limit", String(opts.limit));
|
|
970
|
+
if (opts.cursor) q.set("cursor", opts.cursor);
|
|
971
|
+
const qs = q.toString();
|
|
972
|
+
return this.request(
|
|
973
|
+
"GET",
|
|
974
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}/recipients${qs ? `?${qs}` : ""}`
|
|
975
|
+
);
|
|
976
|
+
}
|
|
977
|
+
actOnOutreachBatch(batch, body) {
|
|
978
|
+
return this.request(
|
|
979
|
+
"POST",
|
|
980
|
+
`/v1/outreach-batches/${encodeURIComponent(batch)}/actions`,
|
|
981
|
+
body
|
|
982
|
+
);
|
|
983
|
+
}
|
|
984
|
+
putOutreachConsent(body) {
|
|
985
|
+
return this.request("PUT", "/v1/outreach-consents", body);
|
|
986
|
+
}
|
|
987
|
+
listOutreachConsents(opts = {}) {
|
|
988
|
+
const q = new URLSearchParams();
|
|
989
|
+
if (opts.limit !== void 0) q.set("limit", String(opts.limit));
|
|
990
|
+
if (opts.cursor) q.set("cursor", opts.cursor);
|
|
991
|
+
const qs = q.toString();
|
|
992
|
+
return this.request(
|
|
993
|
+
"GET",
|
|
994
|
+
`/v1/outreach-consents${qs ? `?${qs}` : ""}`
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
getOutreachConsent(opts) {
|
|
998
|
+
const q = new URLSearchParams();
|
|
999
|
+
if (opts.grant_ref) q.set("grant_ref", opts.grant_ref);
|
|
1000
|
+
if (opts.recipient_ref) q.set("recipient_ref", opts.recipient_ref);
|
|
1001
|
+
if (opts.phone_raw) q.set("phone_raw", opts.phone_raw);
|
|
1002
|
+
if (opts.default_country) q.set("default_country", opts.default_country);
|
|
1003
|
+
return this.request("GET", `/v1/outreach-consents?${q.toString()}`);
|
|
1004
|
+
}
|
|
891
1005
|
uploadDatasetFile(body) {
|
|
892
1006
|
return this.request("POST", "/v1/datasets-upload", body);
|
|
893
1007
|
}
|
|
@@ -1080,17 +1194,36 @@ var ErdoClient = class {
|
|
|
1080
1194
|
`/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
|
|
1081
1195
|
);
|
|
1082
1196
|
}
|
|
1197
|
+
// --- page monitoring ---
|
|
1198
|
+
// These live here rather than being called through `request` from the command
|
|
1199
|
+
// layer: `request` is private, and reaching past it left the CLI's own
|
|
1200
|
+
// typecheck red.
|
|
1201
|
+
setPageMonitoring(id, body) {
|
|
1202
|
+
return this.request(
|
|
1203
|
+
"POST",
|
|
1204
|
+
`/v1/pages/monitoring/${encodeURIComponent(id)}`,
|
|
1205
|
+
body
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
getPageMonitoring(id) {
|
|
1209
|
+
return this.request(
|
|
1210
|
+
"GET",
|
|
1211
|
+
`/v1/pages/monitoring/${encodeURIComponent(id)}`
|
|
1212
|
+
);
|
|
1213
|
+
}
|
|
1083
1214
|
// --- agent runs ---
|
|
1084
1215
|
listAgentRuns(opts) {
|
|
1085
1216
|
const params = new URLSearchParams();
|
|
1086
1217
|
if (opts.agent) params.set("agent_key", opts.agent);
|
|
1087
1218
|
if (opts.thread) params.set("thread_id", opts.thread);
|
|
1219
|
+
if (opts.status) params.set("status", opts.status);
|
|
1088
1220
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
1089
1221
|
const qs = params.toString();
|
|
1090
1222
|
return this.request("GET", `/v1/runs${qs ? `?${qs}` : ""}`);
|
|
1091
1223
|
}
|
|
1092
|
-
getAgentRun(id) {
|
|
1093
|
-
|
|
1224
|
+
getAgentRun(id, include = []) {
|
|
1225
|
+
const qs = include.length ? `?include=${encodeURIComponent(include.join(","))}` : "";
|
|
1226
|
+
return this.request("GET", `/v1/runs/${encodeURIComponent(id)}${qs}`);
|
|
1094
1227
|
}
|
|
1095
1228
|
// --- approvals ---
|
|
1096
1229
|
listApprovals(opts) {
|
|
@@ -1534,6 +1667,25 @@ function timedOutMessage(threadID) {
|
|
|
1534
1667
|
function print(value) {
|
|
1535
1668
|
console.log(JSON.stringify(value, null, 2));
|
|
1536
1669
|
}
|
|
1670
|
+
function readJSONObject(file, label) {
|
|
1671
|
+
let parsed;
|
|
1672
|
+
try {
|
|
1673
|
+
parsed = JSON.parse(readFileSync4(file, "utf8"));
|
|
1674
|
+
} catch (error) {
|
|
1675
|
+
throw new Error(`${label} must be a readable JSON file: ${error instanceof Error ? error.message : String(error)}`);
|
|
1676
|
+
}
|
|
1677
|
+
if (parsed === null || Array.isArray(parsed) || typeof parsed !== "object") {
|
|
1678
|
+
throw new Error(`${label} must contain one JSON object`);
|
|
1679
|
+
}
|
|
1680
|
+
return parsed;
|
|
1681
|
+
}
|
|
1682
|
+
function requireOutreachProject() {
|
|
1683
|
+
if (!process.env.ERDO_PROJECT?.trim()) {
|
|
1684
|
+
throw new Error(
|
|
1685
|
+
"outreach commands require a project: pass --project <uuid> (and --org when the key can reach more than one organization)"
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1537
1689
|
function printPageReview(review) {
|
|
1538
1690
|
if (!review) return;
|
|
1539
1691
|
if (!review.reviewed) {
|
|
@@ -1806,9 +1958,20 @@ id: ${o.id}`);
|
|
|
1806
1958
|
fail(e);
|
|
1807
1959
|
}
|
|
1808
1960
|
});
|
|
1809
|
-
managed.command("adopt
|
|
1961
|
+
managed.command("adopt [token]").description(
|
|
1962
|
+
"Take over an existing org: redeem a one-time consent token, or --from-org <slug> for an org you own yourself (no token needed)"
|
|
1963
|
+
).option("--from-org <slug>", "slug of an org you are an owner of, adopted directly").action(async (token, opts) => {
|
|
1810
1964
|
try {
|
|
1811
|
-
|
|
1965
|
+
if (!token && !opts.fromOrg) {
|
|
1966
|
+
fail(new Error("provide a consent token, or --from-org <slug> for an org you own"));
|
|
1967
|
+
}
|
|
1968
|
+
if (token && opts.fromOrg) {
|
|
1969
|
+
fail(new Error("provide a token or --from-org, not both"));
|
|
1970
|
+
}
|
|
1971
|
+
const o = await new ErdoClient().adoptManagedOrganization({
|
|
1972
|
+
token,
|
|
1973
|
+
organizationSlug: opts.fromOrg
|
|
1974
|
+
});
|
|
1812
1975
|
console.log(`Adopted managed org: ${o.name}`);
|
|
1813
1976
|
console.log(`slug: ${o.slug}
|
|
1814
1977
|
id: ${o.id}`);
|
|
@@ -2915,7 +3078,7 @@ agentCmd.command("messages <threadId>").description("Show a thread's messages (r
|
|
|
2915
3078
|
}
|
|
2916
3079
|
});
|
|
2917
3080
|
var runsCmd = program.command("runs").description("Inspect agent runs");
|
|
2918
|
-
runsCmd.command("list").description("List agent runs (filter by agent OR thread \u2014 mutually exclusive)").option("-a, --agent <key>", "filter by agent key").option("-t, --thread <id>", "filter to a thread").option("-l, --limit <n>", "max runs", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
3081
|
+
runsCmd.command("list").description("List agent runs (filter by agent OR thread \u2014 mutually exclusive)").option("-a, --agent <key>", "filter by agent key").option("-t, --thread <id>", "filter to a thread").option("-s, --status <status>", "filter by status (running, completed, failed, cancelled, pending)").option("-l, --limit <n>", "max runs", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
2919
3082
|
try {
|
|
2920
3083
|
const { runs } = await new ErdoClient().listAgentRuns(opts);
|
|
2921
3084
|
for (const r of runs) console.log(`${r.id} ${r.status} ${r.agent_key}`);
|
|
@@ -2923,13 +3086,130 @@ runsCmd.command("list").description("List agent runs (filter by agent OR thread
|
|
|
2923
3086
|
fail(e);
|
|
2924
3087
|
}
|
|
2925
3088
|
});
|
|
2926
|
-
runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").action(async (id) => {
|
|
3089
|
+
runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").option("--resources", "also show the skills, knowledge and entities the run used").option("--steps", "also show the run's iterations, tool calls and sub-agent invocations").action(async (id, opts) => {
|
|
2927
3090
|
try {
|
|
2928
|
-
|
|
3091
|
+
const include = [];
|
|
3092
|
+
if (opts.resources) include.push("resources");
|
|
3093
|
+
if (opts.steps) include.push("steps");
|
|
3094
|
+
const detail = await new ErdoClient().getAgentRun(id, include);
|
|
3095
|
+
if (include.length === 0) {
|
|
3096
|
+
print(detail);
|
|
3097
|
+
return;
|
|
3098
|
+
}
|
|
3099
|
+
printRunDetail(detail, opts.resources === true, opts.steps === true);
|
|
2929
3100
|
} catch (e) {
|
|
2930
3101
|
fail(e);
|
|
2931
3102
|
}
|
|
2932
3103
|
});
|
|
3104
|
+
var RUN_ARRIVAL_LABELS = {
|
|
3105
|
+
"knowledge.kind_pinned": "pinned for this kind",
|
|
3106
|
+
"knowledge.list": "pinned",
|
|
3107
|
+
"knowledge.search": "ambient search",
|
|
3108
|
+
"knowledge.tool_search": "fetched mid-run",
|
|
3109
|
+
"knowledge.tool_list": "listed mid-run",
|
|
3110
|
+
judge_lens: "review lens",
|
|
3111
|
+
persona_lens: "persona lens",
|
|
3112
|
+
read_knowledge_object: "read directly",
|
|
3113
|
+
definition: "definition",
|
|
3114
|
+
constraint_source: "constraint source"
|
|
3115
|
+
};
|
|
3116
|
+
function arrivalLabel(usageKind) {
|
|
3117
|
+
return RUN_ARRIVAL_LABELS[usageKind] || usageKind || "used";
|
|
3118
|
+
}
|
|
3119
|
+
function resourceKind(resource) {
|
|
3120
|
+
if (resource.resource_type === "query_constraint") return "query_constraint";
|
|
3121
|
+
return resource.type || resource.resource_type;
|
|
3122
|
+
}
|
|
3123
|
+
function printRunDetail(detail, wantResources, wantSteps) {
|
|
3124
|
+
const run = detail.run;
|
|
3125
|
+
console.log(`${run.id} ${run.status} ${run.agent_name || run.agent_key}`);
|
|
3126
|
+
if (wantResources) {
|
|
3127
|
+
const resources = detail.resources ?? [];
|
|
3128
|
+
console.log("");
|
|
3129
|
+
console.log("Resources used");
|
|
3130
|
+
if (resources.length === 0) {
|
|
3131
|
+
console.log(" (none recorded)");
|
|
3132
|
+
} else {
|
|
3133
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
3134
|
+
for (const resource of resources) {
|
|
3135
|
+
const kind = resourceKind(resource);
|
|
3136
|
+
const key = resource.resource_id || resource.id;
|
|
3137
|
+
const group = byKind.get(kind) ?? /* @__PURE__ */ new Map();
|
|
3138
|
+
const entry = group.get(key) ?? {
|
|
3139
|
+
name: resource.display_name || resource.resource_id || resource.id,
|
|
3140
|
+
arrivals: []
|
|
3141
|
+
};
|
|
3142
|
+
const arrival = arrivalLabel(resource.usage_kind);
|
|
3143
|
+
if (!entry.arrivals.includes(arrival)) entry.arrivals.push(arrival);
|
|
3144
|
+
group.set(key, entry);
|
|
3145
|
+
byKind.set(kind, group);
|
|
3146
|
+
}
|
|
3147
|
+
const kinds = [...byKind.keys()].sort(
|
|
3148
|
+
(a, b) => a === "skill" ? -1 : b === "skill" ? 1 : a.localeCompare(b)
|
|
3149
|
+
);
|
|
3150
|
+
for (const kind of kinds) {
|
|
3151
|
+
console.log(` ${kind}`);
|
|
3152
|
+
for (const entry of byKind.get(kind).values()) {
|
|
3153
|
+
console.log(` ${entry.name} (${entry.arrivals.join(", ")})`);
|
|
3154
|
+
}
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
}
|
|
3158
|
+
if (wantSteps) {
|
|
3159
|
+
const steps = detail.steps ?? [];
|
|
3160
|
+
console.log("");
|
|
3161
|
+
console.log("Steps");
|
|
3162
|
+
if (steps.length === 0) {
|
|
3163
|
+
console.log(" (none recorded)");
|
|
3164
|
+
} else {
|
|
3165
|
+
printStepTree(steps);
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
}
|
|
3169
|
+
function printStepTree(steps) {
|
|
3170
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
3171
|
+
const known = new Set(steps.map((s) => s.step_id));
|
|
3172
|
+
const roots = [];
|
|
3173
|
+
for (const step of steps) {
|
|
3174
|
+
if (step.parent_step_id && known.has(step.parent_step_id)) {
|
|
3175
|
+
const siblings = childrenOf.get(step.parent_step_id) ?? [];
|
|
3176
|
+
siblings.push(step);
|
|
3177
|
+
childrenOf.set(step.parent_step_id, siblings);
|
|
3178
|
+
} else {
|
|
3179
|
+
roots.push(step);
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
const printed = /* @__PURE__ */ new Set();
|
|
3183
|
+
const walk = (step, depth) => {
|
|
3184
|
+
if (printed.has(step.step_id)) return;
|
|
3185
|
+
printed.add(step.step_id);
|
|
3186
|
+
console.log(`${" ".repeat(depth + 1)}${stepLine(step)}`);
|
|
3187
|
+
for (const child of childrenOf.get(step.step_id) ?? []) walk(child, depth + 1);
|
|
3188
|
+
};
|
|
3189
|
+
for (const root of roots) walk(root, 0);
|
|
3190
|
+
for (const step of steps) {
|
|
3191
|
+
if (!printed.has(step.step_id)) {
|
|
3192
|
+
printed.add(step.step_id);
|
|
3193
|
+
console.log(` ${stepLine(step)}`);
|
|
3194
|
+
}
|
|
3195
|
+
}
|
|
3196
|
+
}
|
|
3197
|
+
function stepLine(step) {
|
|
3198
|
+
const parts = [];
|
|
3199
|
+
if (step.kind === "agent") {
|
|
3200
|
+
parts.push(`agent ${step.agent_key || step.name}`);
|
|
3201
|
+
if (step.agent_run_id) parts.push(`run ${step.agent_run_id}`);
|
|
3202
|
+
} else if (step.kind === "tool_call") {
|
|
3203
|
+
parts.push(`tool ${step.name}`);
|
|
3204
|
+
} else {
|
|
3205
|
+
parts.push(step.iteration ? `iteration ${step.iteration}` : "iteration");
|
|
3206
|
+
if (step.name) parts.push(step.name);
|
|
3207
|
+
}
|
|
3208
|
+
if (step.duration_ms !== void 0) parts.push(`${step.duration_ms}ms`);
|
|
3209
|
+
if (step.status) parts.push(step.status);
|
|
3210
|
+
if (step.error) parts.push(`error: ${step.error}`);
|
|
3211
|
+
return parts.join(" ");
|
|
3212
|
+
}
|
|
2933
3213
|
var decisionsCmd = program.command("decisions").description(
|
|
2934
3214
|
"The decision record \u2014 what your organization committed to, whether the change actually happened, and what the evidence said afterwards"
|
|
2935
3215
|
);
|
|
@@ -3839,6 +4119,32 @@ pagesCmd.command("restore <id>").description("Restore a previously deleted page
|
|
|
3839
4119
|
fail(e);
|
|
3840
4120
|
}
|
|
3841
4121
|
});
|
|
4122
|
+
pagesCmd.command("monitor <id>").description(
|
|
4123
|
+
"Control health monitoring for a published page: --exclude stops all probing (dashboards, trackers, demos); --ack <sig> acknowledges a known failure class (mobile:overflow, desktop:resource) so it stops alerting and never auto-repairs while a NEW failure class still alerts"
|
|
4124
|
+
).option("--exclude [reason]", "exclude this page from monitoring entirely (optional reason)").option("--include", "re-include the page in monitoring").option("--ack <sigs>", "comma-separated failure signatures to acknowledge (e.g. mobile:overflow,desktop:resource)").option("--unack <sigs>", "comma-separated failure signatures to un-acknowledge").option("--reset", "clear all acknowledged failure signatures").action(async (id, opts) => {
|
|
4125
|
+
try {
|
|
4126
|
+
const api = new ErdoClient();
|
|
4127
|
+
const body = {};
|
|
4128
|
+
if (opts.include) body.excluded = false;
|
|
4129
|
+
else if (typeof opts.exclude === "string") {
|
|
4130
|
+
body.excluded = true;
|
|
4131
|
+
body.excluded_reason = opts.exclude;
|
|
4132
|
+
} else if (opts.exclude === true) body.excluded = true;
|
|
4133
|
+
if (opts.ack) body.ack_add = opts.ack.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4134
|
+
if (opts.unack) body.ack_remove = opts.unack.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4135
|
+
if (opts.reset) body.ack_reset = true;
|
|
4136
|
+
print(await api.setPageMonitoring(id, body));
|
|
4137
|
+
} catch (e) {
|
|
4138
|
+
fail(e);
|
|
4139
|
+
}
|
|
4140
|
+
});
|
|
4141
|
+
pagesCmd.command("monitor-get <id>").description("Read a page's monitoring controls (excluded flag + acknowledged failure signatures)").action(async (id) => {
|
|
4142
|
+
try {
|
|
4143
|
+
print(await new ErdoClient().getPageMonitoring(id));
|
|
4144
|
+
} catch (e) {
|
|
4145
|
+
fail(e);
|
|
4146
|
+
}
|
|
4147
|
+
});
|
|
3842
4148
|
pagesCmd.command("clone <id>").description(
|
|
3843
4149
|
"Copy a page. The copy is byte-identical and starts private (publish state is never inherited); the source's lead-form pipelines are duplicated onto it and the ids in its content rewritten, so it captures its own leads"
|
|
3844
4150
|
).option("--title <title>", 'title for the copy (default: "Copy of <source title>")').option("--json", "print the full JSON, including the cloned pipelines and id rewrites").action(async (id, opts) => {
|
|
@@ -3924,17 +4230,21 @@ domainsCmd.command("list").description("List the org's custom domains with live
|
|
|
3924
4230
|
}
|
|
3925
4231
|
for (const d of domains) {
|
|
3926
4232
|
const checked = d.last_checked_at ? `checked ${d.last_checked_at}` : "never checked";
|
|
4233
|
+
const managerDefault = d.manager_default ? " manager default" : "";
|
|
3927
4234
|
const reason = d.error_reason ? ` ${d.error_reason}` : "";
|
|
3928
|
-
console.log(`${d.domain} ${d.status} ${checked}${reason}`);
|
|
4235
|
+
console.log(`${d.domain} ${d.status} ${checked}${managerDefault}${reason}`);
|
|
3929
4236
|
}
|
|
3930
4237
|
} catch (e) {
|
|
3931
4238
|
fail(e);
|
|
3932
4239
|
}
|
|
3933
4240
|
});
|
|
3934
|
-
domainsCmd.command("add <domain>").description("Register a custom domain (a direct subdomain, e.g. pages.acme.com) and print the DNS records to create").
|
|
4241
|
+
domainsCmd.command("add <domain>").description("Register a custom domain (a direct subdomain, e.g. pages.acme.com) and print the DNS records to create").option(
|
|
4242
|
+
"--manager-default",
|
|
4243
|
+
"also point the orgs you manage at this domain, for a manager registering its first branded hostname"
|
|
4244
|
+
).action(async (domain, opts) => {
|
|
3935
4245
|
try {
|
|
3936
|
-
const d = await new ErdoClient().createCustomDomain(domain);
|
|
3937
|
-
console.log(`${d.domain}: ${d.status}`);
|
|
4246
|
+
const d = await new ErdoClient().createCustomDomain(domain, opts.managerDefault);
|
|
4247
|
+
console.log(`${d.domain}: ${d.status}${d.manager_default ? " (manager default)" : ""}`);
|
|
3938
4248
|
if (d.dns_records.length > 0) {
|
|
3939
4249
|
process.stderr.write("Create these records at the domain's DNS provider:\n");
|
|
3940
4250
|
for (const r of d.dns_records) {
|
|
@@ -3945,6 +4255,20 @@ domainsCmd.command("add <domain>").description("Register a custom domain (a dire
|
|
|
3945
4255
|
fail(e);
|
|
3946
4256
|
}
|
|
3947
4257
|
});
|
|
4258
|
+
domainsCmd.command("manager-default <domain>").description(
|
|
4259
|
+
"Make this the domain the orgs you manage serve their pages from when they have registered none of their own \u2014 a client with its own domain keeps it \u2014 or clear that with --unset"
|
|
4260
|
+
).option("--unset", "clear the flag instead, returning the managed orgs to the platform pages host").action(async (domain, opts) => {
|
|
4261
|
+
try {
|
|
4262
|
+
const d = await new ErdoClient().setCustomDomainManagerDefault(domain, !opts.unset);
|
|
4263
|
+
if (d.manager_default) {
|
|
4264
|
+
console.log(`${d.domain} is now the default domain for the orgs you manage (status: ${d.status})`);
|
|
4265
|
+
} else {
|
|
4266
|
+
console.log(`${d.domain} is no longer the default domain for the orgs you manage`);
|
|
4267
|
+
}
|
|
4268
|
+
} catch (e) {
|
|
4269
|
+
fail(e);
|
|
4270
|
+
}
|
|
4271
|
+
});
|
|
3948
4272
|
domainsCmd.command("remove <domain>").description("Remove a custom domain registration (stops it serving pages)").action(async (domain) => {
|
|
3949
4273
|
try {
|
|
3950
4274
|
const res = await new ErdoClient().deleteCustomDomain(domain);
|
|
@@ -4168,6 +4492,98 @@ sentEmailsCmd.command("list").description("List sent email, newest first, includ
|
|
|
4168
4492
|
}
|
|
4169
4493
|
}
|
|
4170
4494
|
);
|
|
4495
|
+
var outreachCmd = program.command("outreach").description("Consent-gated SMS outreach to an explicit reviewed selection of up to 700 people");
|
|
4496
|
+
var outreachBatchesCmd = outreachCmd.command("batches").description("Prepare and control outreach batches");
|
|
4497
|
+
outreachBatchesCmd.command("put <batch>").description("Create one immutable draft preview from a JSON declaration; this never starts sending").requiredOption("-f, --file <path>", "JSON object containing the batch declaration and recipients").action(async (batch, opts) => {
|
|
4498
|
+
try {
|
|
4499
|
+
requireOutreachProject();
|
|
4500
|
+
print(await new ErdoClient().putOutreachBatch(batch, readJSONObject(opts.file, "--file")));
|
|
4501
|
+
} catch (e) {
|
|
4502
|
+
fail(e);
|
|
4503
|
+
}
|
|
4504
|
+
});
|
|
4505
|
+
outreachBatchesCmd.command("list").description("List outreach batches in the required project").option("-l, --limit <n>", "max batches to return", (v) => parseInt(v, 10)).option("--cursor <cursor>", "opaque next_cursor from the preceding page").option("--initiative <ref>", "return only this outreach initiative before pagination").action(async (opts) => {
|
|
4506
|
+
try {
|
|
4507
|
+
requireOutreachProject();
|
|
4508
|
+
print(await new ErdoClient().listOutreachBatches({
|
|
4509
|
+
limit: opts.limit,
|
|
4510
|
+
cursor: opts.cursor,
|
|
4511
|
+
initiative_ref: opts.initiative
|
|
4512
|
+
}));
|
|
4513
|
+
} catch (e) {
|
|
4514
|
+
fail(e);
|
|
4515
|
+
}
|
|
4516
|
+
});
|
|
4517
|
+
outreachBatchesCmd.command("get <batch>").description("Read a batch summary, or its paged recipient receipts").option("--recipients", "return recipient eligibility, delivery, and response receipts").option("-l, --limit <n>", "max recipients to return", (v) => parseInt(v, 10)).option("--cursor <cursor>", "opaque next_cursor from the preceding recipient page").action(async (batch, opts) => {
|
|
4518
|
+
try {
|
|
4519
|
+
requireOutreachProject();
|
|
4520
|
+
const client = new ErdoClient();
|
|
4521
|
+
print(
|
|
4522
|
+
opts.recipients ? await client.listOutreachBatchRecipients(batch, opts) : await client.getOutreachBatch(batch)
|
|
4523
|
+
);
|
|
4524
|
+
} catch (e) {
|
|
4525
|
+
fail(e);
|
|
4526
|
+
}
|
|
4527
|
+
});
|
|
4528
|
+
outreachBatchesCmd.command("act <batch> <action>").description("Apply a replay-safe arm, pause, resume, or cancel action").requiredOption("--action-ref <ref>", "stable idempotency reference for this operator action").option("--preview-revision <revision>", "arm only this reviewed preview revision").option("--eligible-count <n>", "arm only when this many recipients remain eligible", (v) => parseInt(v, 10)).option("--message-hash <hash>", "arm only the reviewed message hash").option("--source-snapshot <ref>", "pause, resume, or cancel only the rendered source snapshot").option("--batch-state <state>", "pause, resume, or cancel only the rendered batch state").option("--batch-updated-at <timestamp>", "pause, resume, or cancel only the rendered batch version").action(
|
|
4529
|
+
async (batch, action, opts) => {
|
|
4530
|
+
try {
|
|
4531
|
+
requireOutreachProject();
|
|
4532
|
+
print(
|
|
4533
|
+
await new ErdoClient().actOnOutreachBatch(batch, {
|
|
4534
|
+
action,
|
|
4535
|
+
action_ref: opts.actionRef,
|
|
4536
|
+
expected_preview_revision: opts.previewRevision,
|
|
4537
|
+
expected_eligible_count: opts.eligibleCount,
|
|
4538
|
+
expected_message_hash: opts.messageHash,
|
|
4539
|
+
expected_source_snapshot_ref: opts.sourceSnapshot,
|
|
4540
|
+
expected_batch_state: opts.batchState,
|
|
4541
|
+
expected_batch_updated_at: opts.batchUpdatedAt
|
|
4542
|
+
})
|
|
4543
|
+
);
|
|
4544
|
+
} catch (e) {
|
|
4545
|
+
fail(e);
|
|
4546
|
+
}
|
|
4547
|
+
}
|
|
4548
|
+
);
|
|
4549
|
+
var outreachConsentsCmd = outreachCmd.command("consents").description("Declare and read auditable SMS consent evidence");
|
|
4550
|
+
outreachConsentsCmd.command("put").description("Declare or revoke one consent grant from a JSON object").requiredOption("-f, --file <path>", "JSON object containing the consent declaration").action(async (opts) => {
|
|
4551
|
+
try {
|
|
4552
|
+
requireOutreachProject();
|
|
4553
|
+
print(await new ErdoClient().putOutreachConsent(readJSONObject(opts.file, "--file")));
|
|
4554
|
+
} catch (e) {
|
|
4555
|
+
fail(e);
|
|
4556
|
+
}
|
|
4557
|
+
});
|
|
4558
|
+
outreachConsentsCmd.command("list").description("List consent grants in the required project").option("-l, --limit <n>", "max grants to return", (v) => parseInt(v, 10)).option("--cursor <cursor>", "opaque next_cursor from the preceding page").action(async (opts) => {
|
|
4559
|
+
try {
|
|
4560
|
+
requireOutreachProject();
|
|
4561
|
+
print(await new ErdoClient().listOutreachConsents(opts));
|
|
4562
|
+
} catch (e) {
|
|
4563
|
+
fail(e);
|
|
4564
|
+
}
|
|
4565
|
+
});
|
|
4566
|
+
outreachConsentsCmd.command("get [grant-ref]").description("Read one consent grant by reference, or by recipient and phone").option("--recipient-ref <ref>", "stable lead identity when the grant reference is not known").option("--phone <phone>", "source phone recorded for the recipient").option("--country <code>", "ISO country hint used to normalize --phone").action(async (grantRef, opts) => {
|
|
4567
|
+
try {
|
|
4568
|
+
requireOutreachProject();
|
|
4569
|
+
const byGrant = Boolean(grantRef);
|
|
4570
|
+
const byRecipient = Boolean(opts.recipientRef || opts.phone || opts.country);
|
|
4571
|
+
if (byGrant === byRecipient) {
|
|
4572
|
+
throw new Error("consents get requires either <grant-ref> or all of --recipient-ref, --phone, and --country");
|
|
4573
|
+
}
|
|
4574
|
+
if (!byGrant && !(opts.recipientRef && opts.phone && opts.country)) {
|
|
4575
|
+
throw new Error("consents get by recipient requires --recipient-ref, --phone, and --country together");
|
|
4576
|
+
}
|
|
4577
|
+
print(await new ErdoClient().getOutreachConsent({
|
|
4578
|
+
grant_ref: grantRef,
|
|
4579
|
+
recipient_ref: opts.recipientRef,
|
|
4580
|
+
phone_raw: opts.phone,
|
|
4581
|
+
default_country: opts.country
|
|
4582
|
+
}));
|
|
4583
|
+
} catch (e) {
|
|
4584
|
+
fail(e);
|
|
4585
|
+
}
|
|
4586
|
+
});
|
|
4171
4587
|
sentEmailsCmd.command("get <emailID>").description("Read one sent email, including exact bodies and delivery evidence").action(async (emailID) => {
|
|
4172
4588
|
try {
|
|
4173
4589
|
print(await new ErdoClient().getSentEmail(emailID));
|
|
@@ -4175,6 +4591,59 @@ sentEmailsCmd.command("get <emailID>").description("Read one sent email, includi
|
|
|
4175
4591
|
fail(e);
|
|
4176
4592
|
}
|
|
4177
4593
|
});
|
|
4594
|
+
var voiceCmd = program.command("voice").description("Read voice agent phone conversations");
|
|
4595
|
+
var voiceCallsCmd = voiceCmd.command("calls").description("Inbound and outbound phone call records");
|
|
4596
|
+
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(
|
|
4597
|
+
async (opts) => {
|
|
4598
|
+
try {
|
|
4599
|
+
const res = await new ErdoClient().listVoiceCalls({
|
|
4600
|
+
agent: opts.agent,
|
|
4601
|
+
direction: opts.direction,
|
|
4602
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
4603
|
+
offset: opts.offset ? Number(opts.offset) : void 0,
|
|
4604
|
+
cursor: opts.cursor
|
|
4605
|
+
});
|
|
4606
|
+
if (opts.json) {
|
|
4607
|
+
print(res);
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4610
|
+
const calls = res.conversations ?? [];
|
|
4611
|
+
if (calls.length === 0) {
|
|
4612
|
+
console.log("No calls match those filters.");
|
|
4613
|
+
return;
|
|
4614
|
+
}
|
|
4615
|
+
printAlignedTable(
|
|
4616
|
+
["call id", "direction", "from", "to", "status", "secs", "transcript", "started", "summary"],
|
|
4617
|
+
calls.map((call) => [
|
|
4618
|
+
call.call_id,
|
|
4619
|
+
call.direction,
|
|
4620
|
+
call.from_number ?? "",
|
|
4621
|
+
call.to_number,
|
|
4622
|
+
call.status,
|
|
4623
|
+
call.duration_seconds,
|
|
4624
|
+
call.has_transcript ? "yes" : "no",
|
|
4625
|
+
call.created_at,
|
|
4626
|
+
call.transcript_summary
|
|
4627
|
+
])
|
|
4628
|
+
);
|
|
4629
|
+
process.stderr.write(`showing ${calls.length} call(s)
|
|
4630
|
+
`);
|
|
4631
|
+
if (res.next_cursor) {
|
|
4632
|
+
process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
|
|
4633
|
+
`);
|
|
4634
|
+
}
|
|
4635
|
+
} catch (e) {
|
|
4636
|
+
fail(e);
|
|
4637
|
+
}
|
|
4638
|
+
}
|
|
4639
|
+
);
|
|
4640
|
+
voiceCallsCmd.command("get <callID>").description("Read one phone call in full: transcript, summary, and per-turn LLM metrics").action(async (callID) => {
|
|
4641
|
+
try {
|
|
4642
|
+
print(await new ErdoClient().getVoiceCall(callID));
|
|
4643
|
+
} catch (e) {
|
|
4644
|
+
fail(e);
|
|
4645
|
+
}
|
|
4646
|
+
});
|
|
4178
4647
|
var datasetsCmd = program.command("datasets").description("Datasets");
|
|
4179
4648
|
datasetsCmd.command("list").description("List datasets").option(
|
|
4180
4649
|
"--class <class>",
|
|
@@ -4219,6 +4688,32 @@ datasetsCmd.command("purposes").description(
|
|
|
4219
4688
|
fail(e);
|
|
4220
4689
|
}
|
|
4221
4690
|
});
|
|
4691
|
+
datasetsCmd.command("set-purpose <slug> [purpose]").description(
|
|
4692
|
+
"Set (or with --clear, remove) the dataset's purpose \u2014 the org-vocabulary role `datasets purposes` reports. Use it to CORRECT a purpose that names the wrong dataset: one purpose maps to one dataset per org, so a purpose left on the wrong one points everything that resolves through the vocabulary at the wrong rows. Moving a purpose is two calls \u2014 clear it from the old dataset, set it on the right one. Setting a purpose the org already uses elsewhere succeeds and prints a warning naming what it now shadows."
|
|
4693
|
+
).option("--clear", "remove the dataset's purpose instead of setting one").option(
|
|
4694
|
+
"--description <text>",
|
|
4695
|
+
"description for a purpose new to this org; ignored when the org already has an entry for it"
|
|
4696
|
+
).action(async (slug, purpose, opts) => {
|
|
4697
|
+
try {
|
|
4698
|
+
if (opts.clear && purpose) {
|
|
4699
|
+
fail(new Error("pass a purpose or --clear, not both"));
|
|
4700
|
+
return;
|
|
4701
|
+
}
|
|
4702
|
+
if (!opts.clear && !purpose) {
|
|
4703
|
+
fail(new Error("pass a purpose to set, or --clear to remove one"));
|
|
4704
|
+
return;
|
|
4705
|
+
}
|
|
4706
|
+
const res = await new ErdoClient().setDatasetPurpose(slug, {
|
|
4707
|
+
...purpose ? { purpose } : {},
|
|
4708
|
+
...opts.clear ? { clear_purpose: true } : {},
|
|
4709
|
+
...opts.description ? { purpose_description: opts.description } : {}
|
|
4710
|
+
});
|
|
4711
|
+
for (const w of res.warnings ?? []) console.error(`warning: ${w}`);
|
|
4712
|
+
console.log(res.purpose ? `${slug} ${res.purpose}` : `${slug} (no purpose)`);
|
|
4713
|
+
} catch (e) {
|
|
4714
|
+
fail(e);
|
|
4715
|
+
}
|
|
4716
|
+
});
|
|
4222
4717
|
datasetsCmd.command("query <slug> <question>").description(
|
|
4223
4718
|
"Ask a natural-language question of a dataset \u2014 Erdo writes and runs the SQL, and answers with that SQL alongside the values. It runs an agent, so it is slower and two identical questions can produce two different queries: for a deterministic or scripted read, write the SQL yourself with `datasets fetch --sql`."
|
|
4224
4719
|
).action(async (slug, question) => {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@erdoai/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Erdo CLI
|
|
3
|
+
"version": "0.71.0",
|
|
4
|
+
"description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"erdo": "dist/index.js"
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"scripts": {
|
|
36
36
|
"build": "tsup src/index.ts --format esm --clean",
|
|
37
37
|
"build:check": "tsc --noEmit",
|
|
38
|
-
"test": "node --import tsx --test src
|
|
38
|
+
"test": "node --import tsx --test src/*.test.ts",
|
|
39
39
|
"dev": "tsx src/index.ts",
|
|
40
40
|
"postinstall": "node scripts/postinstall.mjs",
|
|
41
41
|
"prepublishOnly": "npm run build"
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"overrides": {
|
|
54
54
|
"esbuild": "^0.28.1"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|