@erdoai/cli 0.68.0 → 0.72.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 +411 -17
- package/package.json +3 -3
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
|
|
@@ -327,6 +332,33 @@ var ErdoClient = class {
|
|
|
327
332
|
{ successor_manager_slug: successorManagerSlug }
|
|
328
333
|
);
|
|
329
334
|
}
|
|
335
|
+
// Reads the Google Ads container a managed org already has under this manager
|
|
336
|
+
// and creates nothing, so it is safe to run against an org mid-setup: a 404
|
|
337
|
+
// means the org has no container here, not that anything is broken.
|
|
338
|
+
getManagedOrganizationAdsContainer(slug) {
|
|
339
|
+
return this.request(
|
|
340
|
+
"GET",
|
|
341
|
+
`/v1/managed-organizations/${encodeURIComponent(slug)}/ads-container`
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
// Creates the container — a sub-account under the manager's MCC plus the
|
|
345
|
+
// delegated connection inside the managed org. `replaceCurrentAccount` is the
|
|
346
|
+
// one input with consequences: provisioning refuses outright when the org is
|
|
347
|
+
// already running campaigns in another account, and passing this accepts that
|
|
348
|
+
// those campaigns are left behind, so the key is sent only when it is asked
|
|
349
|
+
// for rather than as a `false` the server has to read.
|
|
350
|
+
provisionManagedOrganizationAdsContainer(slug, input) {
|
|
351
|
+
const body = {};
|
|
352
|
+
if (input.descriptiveName) body.descriptive_name = input.descriptiveName;
|
|
353
|
+
if (input.currencyCode) body.currency_code = input.currencyCode;
|
|
354
|
+
if (input.timeZone) body.time_zone = input.timeZone;
|
|
355
|
+
if (input.replaceCurrentAccount) body.replace_current_account = true;
|
|
356
|
+
return this.request(
|
|
357
|
+
"POST",
|
|
358
|
+
`/v1/managed-organizations/${encodeURIComponent(slug)}/ads-container`,
|
|
359
|
+
body
|
|
360
|
+
);
|
|
361
|
+
}
|
|
330
362
|
// --- Custom page domains: the branded hostnames the org's pages serve from ---
|
|
331
363
|
listCustomDomains() {
|
|
332
364
|
return this.request("GET", "/v1/custom-domains");
|
|
@@ -434,6 +466,22 @@ var ErdoClient = class {
|
|
|
434
466
|
getSentEmail(emailID) {
|
|
435
467
|
return this.request("GET", `/v1/emails/${encodeURIComponent(emailID)}`);
|
|
436
468
|
}
|
|
469
|
+
// Phone conversation records: inbound calls to a voice agent's own number and
|
|
470
|
+
// the outbound calls Erdo placed. Website chat widget conversations are a
|
|
471
|
+
// different resource — they live under /v1/voice/widget-conversations.
|
|
472
|
+
listVoiceCalls(params) {
|
|
473
|
+
const q = new URLSearchParams();
|
|
474
|
+
if (params?.agent) q.set("agent", params.agent);
|
|
475
|
+
if (params?.direction) q.set("direction", params.direction);
|
|
476
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
477
|
+
if (params?.offset !== void 0) q.set("offset", String(params.offset));
|
|
478
|
+
if (params?.cursor) q.set("cursor", params.cursor);
|
|
479
|
+
const qs = q.toString();
|
|
480
|
+
return this.request("GET", `/v1/voice/calls${qs ? `?${qs}` : ""}`);
|
|
481
|
+
}
|
|
482
|
+
getVoiceCall(callID) {
|
|
483
|
+
return this.request("GET", `/v1/voice/calls/${encodeURIComponent(callID)}`);
|
|
484
|
+
}
|
|
437
485
|
// Run a read-only HogQL query against the org's page-analytics events. Rows are
|
|
438
486
|
// positional per columns; enabled:false means page analytics is off for the org
|
|
439
487
|
// (not zero traffic). A rejected query surfaces PostHog's message as the error.
|
|
@@ -869,6 +917,14 @@ var ErdoClient = class {
|
|
|
869
917
|
listDatasetPurposes() {
|
|
870
918
|
return this.request("GET", `/v1/datasets-purposes`);
|
|
871
919
|
}
|
|
920
|
+
// Correct which dataset carries a purpose. Pass `purpose` to set one,
|
|
921
|
+
// `clear_purpose` to remove it; moving a purpose is clearing it from the old
|
|
922
|
+
// dataset and setting it on the right one. Setting a purpose the org already
|
|
923
|
+
// uses elsewhere succeeds and answers with a warning naming what it shadows,
|
|
924
|
+
// since one purpose is meant to map to one dataset.
|
|
925
|
+
setDatasetPurpose(slug, input) {
|
|
926
|
+
return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/schema`, input);
|
|
927
|
+
}
|
|
872
928
|
// The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending // The endpoint's field is `question` (QueryDataNaturalLanguageInput). Sending
|
|
873
929
|
// `query` made every `erdo datasets query` fail with "question is required".
|
|
874
930
|
//
|
|
@@ -1165,17 +1221,36 @@ var ErdoClient = class {
|
|
|
1165
1221
|
`/v1/kv/${encodeURIComponent(slug)}/items/${encodeURIComponent(key)}`
|
|
1166
1222
|
);
|
|
1167
1223
|
}
|
|
1224
|
+
// --- page monitoring ---
|
|
1225
|
+
// These live here rather than being called through `request` from the command
|
|
1226
|
+
// layer: `request` is private, and reaching past it left the CLI's own
|
|
1227
|
+
// typecheck red.
|
|
1228
|
+
setPageMonitoring(id, body) {
|
|
1229
|
+
return this.request(
|
|
1230
|
+
"POST",
|
|
1231
|
+
`/v1/pages/monitoring/${encodeURIComponent(id)}`,
|
|
1232
|
+
body
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
getPageMonitoring(id) {
|
|
1236
|
+
return this.request(
|
|
1237
|
+
"GET",
|
|
1238
|
+
`/v1/pages/monitoring/${encodeURIComponent(id)}`
|
|
1239
|
+
);
|
|
1240
|
+
}
|
|
1168
1241
|
// --- agent runs ---
|
|
1169
1242
|
listAgentRuns(opts) {
|
|
1170
1243
|
const params = new URLSearchParams();
|
|
1171
1244
|
if (opts.agent) params.set("agent_key", opts.agent);
|
|
1172
1245
|
if (opts.thread) params.set("thread_id", opts.thread);
|
|
1246
|
+
if (opts.status) params.set("status", opts.status);
|
|
1173
1247
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
1174
1248
|
const qs = params.toString();
|
|
1175
1249
|
return this.request("GET", `/v1/runs${qs ? `?${qs}` : ""}`);
|
|
1176
1250
|
}
|
|
1177
|
-
getAgentRun(id) {
|
|
1178
|
-
|
|
1251
|
+
getAgentRun(id, include = []) {
|
|
1252
|
+
const qs = include.length ? `?include=${encodeURIComponent(include.join(","))}` : "";
|
|
1253
|
+
return this.request("GET", `/v1/runs/${encodeURIComponent(id)}${qs}`);
|
|
1179
1254
|
}
|
|
1180
1255
|
// --- approvals ---
|
|
1181
1256
|
listApprovals(opts) {
|
|
@@ -1910,9 +1985,20 @@ id: ${o.id}`);
|
|
|
1910
1985
|
fail(e);
|
|
1911
1986
|
}
|
|
1912
1987
|
});
|
|
1913
|
-
managed.command("adopt
|
|
1988
|
+
managed.command("adopt [token]").description(
|
|
1989
|
+
"Take over an existing org: redeem a one-time consent token, or --from-org <slug> for an org you own yourself (no token needed)"
|
|
1990
|
+
).option("--from-org <slug>", "slug of an org you are an owner of, adopted directly").action(async (token, opts) => {
|
|
1914
1991
|
try {
|
|
1915
|
-
|
|
1992
|
+
if (!token && !opts.fromOrg) {
|
|
1993
|
+
fail(new Error("provide a consent token, or --from-org <slug> for an org you own"));
|
|
1994
|
+
}
|
|
1995
|
+
if (token && opts.fromOrg) {
|
|
1996
|
+
fail(new Error("provide a token or --from-org, not both"));
|
|
1997
|
+
}
|
|
1998
|
+
const o = await new ErdoClient().adoptManagedOrganization({
|
|
1999
|
+
token,
|
|
2000
|
+
organizationSlug: opts.fromOrg
|
|
2001
|
+
});
|
|
1916
2002
|
console.log(`Adopted managed org: ${o.name}`);
|
|
1917
2003
|
console.log(`slug: ${o.slug}
|
|
1918
2004
|
id: ${o.id}`);
|
|
@@ -1942,6 +2028,76 @@ managed.command("add-member <orgSlug> <email>").description("Seat a person in a
|
|
|
1942
2028
|
fail(e);
|
|
1943
2029
|
}
|
|
1944
2030
|
});
|
|
2031
|
+
function printAdsContainer(c) {
|
|
2032
|
+
console.log(`org: ${c.org_slug}`);
|
|
2033
|
+
console.log(`customer id: ${c.customer_id}`);
|
|
2034
|
+
console.log(`manager (login customer id): ${c.login_customer_id}`);
|
|
2035
|
+
console.log(`integration: ${c.integration_id}`);
|
|
2036
|
+
if (c.descriptive_name) {
|
|
2037
|
+
console.log(`name: ${c.descriptive_name}`);
|
|
2038
|
+
}
|
|
2039
|
+
console.log(`billing: ${c.billing_status}`);
|
|
2040
|
+
if (c.billing_status === "unknown") {
|
|
2041
|
+
process.stderr.write(
|
|
2042
|
+
`Billing could not be checked for ${c.customer_id} \u2014 open the account in the Google Ads UI and confirm; this is not evidence that billing is missing.
|
|
2043
|
+
`
|
|
2044
|
+
);
|
|
2045
|
+
} else if (!c.billing_configured) {
|
|
2046
|
+
process.stderr.write(
|
|
2047
|
+
`Billing is ${c.billing_status}: ${c.customer_id} cannot serve until a payments profile is linked to it in the Google Ads UI.
|
|
2048
|
+
`
|
|
2049
|
+
);
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
managed.command("ads-container <orgSlug>").description(
|
|
2053
|
+
"Read the Google Ads account a managed org runs its campaigns in, or --provision one under your manager account"
|
|
2054
|
+
).option("--provision", "create the account and the org's delegated google_ads connection instead of reading them").option(
|
|
2055
|
+
"--replace-current-account",
|
|
2056
|
+
"with --provision: go ahead even though the org already runs campaigns in another account. Google Ads cannot move a campaign, so those campaigns stay behind and have to be rebuilt"
|
|
2057
|
+
).option("--name <descriptiveName>", "display name for the new account (default: the org's name and slug)").option("--currency <code>", "ISO currency code for the new account, e.g. USD").option("--time-zone <tz>", "IANA time zone for the new account, e.g. America/New_York").action(
|
|
2058
|
+
async (orgSlug, opts) => {
|
|
2059
|
+
try {
|
|
2060
|
+
if (opts.replaceCurrentAccount && !opts.provision) {
|
|
2061
|
+
fail(new Error("--replace-current-account only applies with --provision"));
|
|
2062
|
+
}
|
|
2063
|
+
const api = new ErdoClient();
|
|
2064
|
+
if (!opts.provision) {
|
|
2065
|
+
try {
|
|
2066
|
+
printAdsContainer(await api.getManagedOrganizationAdsContainer(orgSlug));
|
|
2067
|
+
} catch (e) {
|
|
2068
|
+
if (e instanceof ErdoApiError && e.status === 404) {
|
|
2069
|
+
fail(
|
|
2070
|
+
new Error(
|
|
2071
|
+
`${orgSlug} has no Google Ads container under this manager. Create one with: erdo org managed ads-container ${orgSlug} --provision`
|
|
2072
|
+
)
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
throw e;
|
|
2076
|
+
}
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
const c = await api.provisionManagedOrganizationAdsContainer(orgSlug, {
|
|
2080
|
+
descriptiveName: opts.name,
|
|
2081
|
+
currencyCode: opts.currency,
|
|
2082
|
+
timeZone: opts.timeZone,
|
|
2083
|
+
replaceCurrentAccount: opts.replaceCurrentAccount
|
|
2084
|
+
});
|
|
2085
|
+
console.log(
|
|
2086
|
+
c.already_provisioned ? `Google Ads container already existed for ${c.org_slug}` : `Created Google Ads container for ${c.org_slug}`
|
|
2087
|
+
);
|
|
2088
|
+
printAdsContainer(c);
|
|
2089
|
+
const leftBehind = c.previous_campaign_count ?? 0;
|
|
2090
|
+
if (c.previous_customer_id && leftBehind > 0) {
|
|
2091
|
+
process.stderr.write(
|
|
2092
|
+
`${leftBehind} campaign${leftBehind === 1 ? "" : "s"} stayed in ${c.previous_customer_id}, the account this replaced. Google Ads cannot move a campaign between accounts and nothing in ${c.org_slug} resolves that account any more, so they have to be rebuilt in ${c.customer_id}.
|
|
2093
|
+
`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
} catch (e) {
|
|
2097
|
+
fail(e);
|
|
2098
|
+
}
|
|
2099
|
+
}
|
|
2100
|
+
);
|
|
1945
2101
|
managed.command("handoff-token <orgSlug>").description("Mint a one-time token offering an ownerless managed org to another manager; shown ONCE").requiredOption("--successor <managerOrgSlug>", "the manager org allowed to redeem the token").action(async (orgSlug, opts) => {
|
|
1946
2102
|
try {
|
|
1947
2103
|
const res = await new ErdoClient().createManagedOrganizationHandoffToken(orgSlug, opts.successor);
|
|
@@ -3019,7 +3175,7 @@ agentCmd.command("messages <threadId>").description("Show a thread's messages (r
|
|
|
3019
3175
|
}
|
|
3020
3176
|
});
|
|
3021
3177
|
var runsCmd = program.command("runs").description("Inspect agent runs");
|
|
3022
|
-
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) => {
|
|
3178
|
+
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) => {
|
|
3023
3179
|
try {
|
|
3024
3180
|
const { runs } = await new ErdoClient().listAgentRuns(opts);
|
|
3025
3181
|
for (const r of runs) console.log(`${r.id} ${r.status} ${r.agent_key}`);
|
|
@@ -3027,13 +3183,130 @@ runsCmd.command("list").description("List agent runs (filter by agent OR thread
|
|
|
3027
3183
|
fail(e);
|
|
3028
3184
|
}
|
|
3029
3185
|
});
|
|
3030
|
-
runsCmd.command("get <id>").description("Show an agent run (status, output, trace metadata)").action(async (id) => {
|
|
3186
|
+
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) => {
|
|
3031
3187
|
try {
|
|
3032
|
-
|
|
3188
|
+
const include = [];
|
|
3189
|
+
if (opts.resources) include.push("resources");
|
|
3190
|
+
if (opts.steps) include.push("steps");
|
|
3191
|
+
const detail = await new ErdoClient().getAgentRun(id, include);
|
|
3192
|
+
if (include.length === 0) {
|
|
3193
|
+
print(detail);
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3196
|
+
printRunDetail(detail, opts.resources === true, opts.steps === true);
|
|
3033
3197
|
} catch (e) {
|
|
3034
3198
|
fail(e);
|
|
3035
3199
|
}
|
|
3036
3200
|
});
|
|
3201
|
+
var RUN_ARRIVAL_LABELS = {
|
|
3202
|
+
"knowledge.kind_pinned": "pinned for this kind",
|
|
3203
|
+
"knowledge.list": "pinned",
|
|
3204
|
+
"knowledge.search": "ambient search",
|
|
3205
|
+
"knowledge.tool_search": "fetched mid-run",
|
|
3206
|
+
"knowledge.tool_list": "listed mid-run",
|
|
3207
|
+
judge_lens: "review lens",
|
|
3208
|
+
persona_lens: "persona lens",
|
|
3209
|
+
read_knowledge_object: "read directly",
|
|
3210
|
+
definition: "definition",
|
|
3211
|
+
constraint_source: "constraint source"
|
|
3212
|
+
};
|
|
3213
|
+
function arrivalLabel(usageKind) {
|
|
3214
|
+
return RUN_ARRIVAL_LABELS[usageKind] || usageKind || "used";
|
|
3215
|
+
}
|
|
3216
|
+
function resourceKind(resource) {
|
|
3217
|
+
if (resource.resource_type === "query_constraint") return "query_constraint";
|
|
3218
|
+
return resource.type || resource.resource_type;
|
|
3219
|
+
}
|
|
3220
|
+
function printRunDetail(detail, wantResources, wantSteps) {
|
|
3221
|
+
const run = detail.run;
|
|
3222
|
+
console.log(`${run.id} ${run.status} ${run.agent_name || run.agent_key}`);
|
|
3223
|
+
if (wantResources) {
|
|
3224
|
+
const resources = detail.resources ?? [];
|
|
3225
|
+
console.log("");
|
|
3226
|
+
console.log("Resources used");
|
|
3227
|
+
if (resources.length === 0) {
|
|
3228
|
+
console.log(" (none recorded)");
|
|
3229
|
+
} else {
|
|
3230
|
+
const byKind = /* @__PURE__ */ new Map();
|
|
3231
|
+
for (const resource of resources) {
|
|
3232
|
+
const kind = resourceKind(resource);
|
|
3233
|
+
const key = resource.resource_id || resource.id;
|
|
3234
|
+
const group = byKind.get(kind) ?? /* @__PURE__ */ new Map();
|
|
3235
|
+
const entry = group.get(key) ?? {
|
|
3236
|
+
name: resource.display_name || resource.resource_id || resource.id,
|
|
3237
|
+
arrivals: []
|
|
3238
|
+
};
|
|
3239
|
+
const arrival = arrivalLabel(resource.usage_kind);
|
|
3240
|
+
if (!entry.arrivals.includes(arrival)) entry.arrivals.push(arrival);
|
|
3241
|
+
group.set(key, entry);
|
|
3242
|
+
byKind.set(kind, group);
|
|
3243
|
+
}
|
|
3244
|
+
const kinds = [...byKind.keys()].sort(
|
|
3245
|
+
(a, b) => a === "skill" ? -1 : b === "skill" ? 1 : a.localeCompare(b)
|
|
3246
|
+
);
|
|
3247
|
+
for (const kind of kinds) {
|
|
3248
|
+
console.log(` ${kind}`);
|
|
3249
|
+
for (const entry of byKind.get(kind).values()) {
|
|
3250
|
+
console.log(` ${entry.name} (${entry.arrivals.join(", ")})`);
|
|
3251
|
+
}
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
}
|
|
3255
|
+
if (wantSteps) {
|
|
3256
|
+
const steps = detail.steps ?? [];
|
|
3257
|
+
console.log("");
|
|
3258
|
+
console.log("Steps");
|
|
3259
|
+
if (steps.length === 0) {
|
|
3260
|
+
console.log(" (none recorded)");
|
|
3261
|
+
} else {
|
|
3262
|
+
printStepTree(steps);
|
|
3263
|
+
}
|
|
3264
|
+
}
|
|
3265
|
+
}
|
|
3266
|
+
function printStepTree(steps) {
|
|
3267
|
+
const childrenOf = /* @__PURE__ */ new Map();
|
|
3268
|
+
const known = new Set(steps.map((s) => s.step_id));
|
|
3269
|
+
const roots = [];
|
|
3270
|
+
for (const step of steps) {
|
|
3271
|
+
if (step.parent_step_id && known.has(step.parent_step_id)) {
|
|
3272
|
+
const siblings = childrenOf.get(step.parent_step_id) ?? [];
|
|
3273
|
+
siblings.push(step);
|
|
3274
|
+
childrenOf.set(step.parent_step_id, siblings);
|
|
3275
|
+
} else {
|
|
3276
|
+
roots.push(step);
|
|
3277
|
+
}
|
|
3278
|
+
}
|
|
3279
|
+
const printed = /* @__PURE__ */ new Set();
|
|
3280
|
+
const walk = (step, depth) => {
|
|
3281
|
+
if (printed.has(step.step_id)) return;
|
|
3282
|
+
printed.add(step.step_id);
|
|
3283
|
+
console.log(`${" ".repeat(depth + 1)}${stepLine(step)}`);
|
|
3284
|
+
for (const child of childrenOf.get(step.step_id) ?? []) walk(child, depth + 1);
|
|
3285
|
+
};
|
|
3286
|
+
for (const root of roots) walk(root, 0);
|
|
3287
|
+
for (const step of steps) {
|
|
3288
|
+
if (!printed.has(step.step_id)) {
|
|
3289
|
+
printed.add(step.step_id);
|
|
3290
|
+
console.log(` ${stepLine(step)}`);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
}
|
|
3294
|
+
function stepLine(step) {
|
|
3295
|
+
const parts = [];
|
|
3296
|
+
if (step.kind === "agent") {
|
|
3297
|
+
parts.push(`agent ${step.agent_key || step.name}`);
|
|
3298
|
+
if (step.agent_run_id) parts.push(`run ${step.agent_run_id}`);
|
|
3299
|
+
} else if (step.kind === "tool_call") {
|
|
3300
|
+
parts.push(`tool ${step.name}`);
|
|
3301
|
+
} else {
|
|
3302
|
+
parts.push(step.iteration ? `iteration ${step.iteration}` : "iteration");
|
|
3303
|
+
if (step.name) parts.push(step.name);
|
|
3304
|
+
}
|
|
3305
|
+
if (step.duration_ms !== void 0) parts.push(`${step.duration_ms}ms`);
|
|
3306
|
+
if (step.status) parts.push(step.status);
|
|
3307
|
+
if (step.error) parts.push(`error: ${step.error}`);
|
|
3308
|
+
return parts.join(" ");
|
|
3309
|
+
}
|
|
3037
3310
|
var decisionsCmd = program.command("decisions").description(
|
|
3038
3311
|
"The decision record \u2014 what your organization committed to, whether the change actually happened, and what the evidence said afterwards"
|
|
3039
3312
|
);
|
|
@@ -3833,7 +4106,13 @@ function grantList(v) {
|
|
|
3833
4106
|
pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/--css accept @file").requiredOption("--title <title>", "page title").requiredOption("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option("--runtime <runtime>", "react-tailwind (default) or none").option(
|
|
3834
4107
|
"--meta-title <title>",
|
|
3835
4108
|
"public SEO title (browser tab + shared-link headline) \u2014 distinct from --title, the internal name"
|
|
3836
|
-
).option("--meta-description <description>", "public SEO description (shared-link preview / search result blurb)").option(
|
|
4109
|
+
).option("--meta-description <description>", "public SEO description (shared-link preview / search result blurb)").option(
|
|
4110
|
+
"--meta-icon-asset <attachmentId>",
|
|
4111
|
+
"public favicon: a Brand Style attachment id (apple-icon-180x180 / favicon-32x32) for the browser-tab icon"
|
|
4112
|
+
).option(
|
|
4113
|
+
"--meta-image-asset <attachmentId>",
|
|
4114
|
+
"public share image: a Brand Style attachment id (og-image / hero photo) for the link-preview card"
|
|
4115
|
+
).option("--datasets <csv>", "dataset slugs the page reads").option("--writable-datasets <csv>", "dataset slugs the page may write via erdo.insertRows").option("--kv <csv>", "named KV-store slugs the page reads").option("--writable-kv <csv>", "named KV-store slugs the page may write").option("--public", "make the page publicly viewable").action(
|
|
3837
4116
|
async (opts) => {
|
|
3838
4117
|
try {
|
|
3839
4118
|
const csv = (v) => v ? v.split(",").map((s) => s.trim()) : void 0;
|
|
@@ -3845,6 +4124,8 @@ pagesCmd.command("deploy").description("Deploy a page (HTML/React); --html/--js/
|
|
|
3845
4124
|
runtime: opts.runtime,
|
|
3846
4125
|
meta_title: opts.metaTitle,
|
|
3847
4126
|
meta_description: opts.metaDescription,
|
|
4127
|
+
meta_icon_asset_id: opts.metaIconAsset,
|
|
4128
|
+
meta_image_asset_id: opts.metaImageAsset,
|
|
3848
4129
|
dataset_slugs: csv(opts.datasets),
|
|
3849
4130
|
writable_dataset_slugs: csv(opts.writableDatasets),
|
|
3850
4131
|
kv_slugs: csv(opts.kv),
|
|
@@ -3863,7 +4144,13 @@ pagesCmd.command("update <id>").description(
|
|
|
3863
4144
|
).option("--title <title>", "new title").option("--html <htmlOr@file>", "HTML (or @path to a file)").option("--js <jsOr@file>", "JS/JSX (or @path)").option("--css <cssOr@file>", "CSS (or @path)").option(
|
|
3864
4145
|
"--meta-title <title>",
|
|
3865
4146
|
'new public SEO title (omit to keep current; pass "" to clear back to the default)'
|
|
3866
|
-
).option("--meta-description <description>", 'new public SEO description (omit to keep current; pass "" to clear)').option(
|
|
4147
|
+
).option("--meta-description <description>", 'new public SEO description (omit to keep current; pass "" to clear)').option(
|
|
4148
|
+
"--meta-icon-asset <attachmentId>",
|
|
4149
|
+
'new public favicon: a Brand Style attachment id (apple-icon-180x180 / favicon-32x32) (omit to keep current; pass "" to clear back to the default)'
|
|
4150
|
+
).option(
|
|
4151
|
+
"--meta-image-asset <attachmentId>",
|
|
4152
|
+
'new public share image: a Brand Style attachment id (og-image / hero photo) (omit to keep current; pass "" to clear)'
|
|
4153
|
+
).option("--datasets <csv>", 'replacement read-dataset slugs ("[]" clears)').option("--writable-datasets <csv>", 'replacement writable-dataset slugs ("[]" clears)').option("--kv <csv>", 'replacement read KV-store slugs ("[]" clears)').option("--writable-kv <csv>", 'replacement writable KV-store slugs ("[]" clears)').option("--public", "make the page publicly viewable").option("--private", "make the page private").option(
|
|
3867
4154
|
"--request-review",
|
|
3868
4155
|
"ask the judge lenses to review the page after this update and report findings -- for material edits to a LIVE page (layout, forms/CTA, legal/compliance copy, restructured sections), not cosmetic tweaks. First builds and full rewrites are already reviewed automatically"
|
|
3869
4156
|
).action(
|
|
@@ -3876,6 +4163,8 @@ pagesCmd.command("update <id>").description(
|
|
|
3876
4163
|
css: readMaybeFile(opts.css),
|
|
3877
4164
|
meta_title: opts.metaTitle,
|
|
3878
4165
|
meta_description: opts.metaDescription,
|
|
4166
|
+
meta_icon_asset_id: opts.metaIconAsset,
|
|
4167
|
+
meta_image_asset_id: opts.metaImageAsset,
|
|
3879
4168
|
dataset_slugs: grantList(opts.datasets),
|
|
3880
4169
|
writable_dataset_slugs: grantList(opts.writableDatasets),
|
|
3881
4170
|
kv_slugs: grantList(opts.kv),
|
|
@@ -3943,6 +4232,32 @@ pagesCmd.command("restore <id>").description("Restore a previously deleted page
|
|
|
3943
4232
|
fail(e);
|
|
3944
4233
|
}
|
|
3945
4234
|
});
|
|
4235
|
+
pagesCmd.command("monitor <id>").description(
|
|
4236
|
+
"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"
|
|
4237
|
+
).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) => {
|
|
4238
|
+
try {
|
|
4239
|
+
const api = new ErdoClient();
|
|
4240
|
+
const body = {};
|
|
4241
|
+
if (opts.include) body.excluded = false;
|
|
4242
|
+
else if (typeof opts.exclude === "string") {
|
|
4243
|
+
body.excluded = true;
|
|
4244
|
+
body.excluded_reason = opts.exclude;
|
|
4245
|
+
} else if (opts.exclude === true) body.excluded = true;
|
|
4246
|
+
if (opts.ack) body.ack_add = opts.ack.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4247
|
+
if (opts.unack) body.ack_remove = opts.unack.split(",").map((s) => s.trim()).filter(Boolean);
|
|
4248
|
+
if (opts.reset) body.ack_reset = true;
|
|
4249
|
+
print(await api.setPageMonitoring(id, body));
|
|
4250
|
+
} catch (e) {
|
|
4251
|
+
fail(e);
|
|
4252
|
+
}
|
|
4253
|
+
});
|
|
4254
|
+
pagesCmd.command("monitor-get <id>").description("Read a page's monitoring controls (excluded flag + acknowledged failure signatures)").action(async (id) => {
|
|
4255
|
+
try {
|
|
4256
|
+
print(await new ErdoClient().getPageMonitoring(id));
|
|
4257
|
+
} catch (e) {
|
|
4258
|
+
fail(e);
|
|
4259
|
+
}
|
|
4260
|
+
});
|
|
3946
4261
|
pagesCmd.command("clone <id>").description(
|
|
3947
4262
|
"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"
|
|
3948
4263
|
).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) => {
|
|
@@ -4389,6 +4704,59 @@ sentEmailsCmd.command("get <emailID>").description("Read one sent email, includi
|
|
|
4389
4704
|
fail(e);
|
|
4390
4705
|
}
|
|
4391
4706
|
});
|
|
4707
|
+
var voiceCmd = program.command("voice").description("Read voice agent phone conversations");
|
|
4708
|
+
var voiceCallsCmd = voiceCmd.command("calls").description("Inbound and outbound phone call records");
|
|
4709
|
+
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(
|
|
4710
|
+
async (opts) => {
|
|
4711
|
+
try {
|
|
4712
|
+
const res = await new ErdoClient().listVoiceCalls({
|
|
4713
|
+
agent: opts.agent,
|
|
4714
|
+
direction: opts.direction,
|
|
4715
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
4716
|
+
offset: opts.offset ? Number(opts.offset) : void 0,
|
|
4717
|
+
cursor: opts.cursor
|
|
4718
|
+
});
|
|
4719
|
+
if (opts.json) {
|
|
4720
|
+
print(res);
|
|
4721
|
+
return;
|
|
4722
|
+
}
|
|
4723
|
+
const calls = res.conversations ?? [];
|
|
4724
|
+
if (calls.length === 0) {
|
|
4725
|
+
console.log("No calls match those filters.");
|
|
4726
|
+
return;
|
|
4727
|
+
}
|
|
4728
|
+
printAlignedTable(
|
|
4729
|
+
["call id", "direction", "from", "to", "status", "secs", "transcript", "started", "summary"],
|
|
4730
|
+
calls.map((call) => [
|
|
4731
|
+
call.call_id,
|
|
4732
|
+
call.direction,
|
|
4733
|
+
call.from_number ?? "",
|
|
4734
|
+
call.to_number,
|
|
4735
|
+
call.status,
|
|
4736
|
+
call.duration_seconds,
|
|
4737
|
+
call.has_transcript ? "yes" : "no",
|
|
4738
|
+
call.created_at,
|
|
4739
|
+
call.transcript_summary
|
|
4740
|
+
])
|
|
4741
|
+
);
|
|
4742
|
+
process.stderr.write(`showing ${calls.length} call(s)
|
|
4743
|
+
`);
|
|
4744
|
+
if (res.next_cursor) {
|
|
4745
|
+
process.stderr.write(`more available \u2014 re-run with --cursor ${res.next_cursor}
|
|
4746
|
+
`);
|
|
4747
|
+
}
|
|
4748
|
+
} catch (e) {
|
|
4749
|
+
fail(e);
|
|
4750
|
+
}
|
|
4751
|
+
}
|
|
4752
|
+
);
|
|
4753
|
+
voiceCallsCmd.command("get <callID>").description("Read one phone call in full: transcript, summary, and per-turn LLM metrics").action(async (callID) => {
|
|
4754
|
+
try {
|
|
4755
|
+
print(await new ErdoClient().getVoiceCall(callID));
|
|
4756
|
+
} catch (e) {
|
|
4757
|
+
fail(e);
|
|
4758
|
+
}
|
|
4759
|
+
});
|
|
4392
4760
|
var datasetsCmd = program.command("datasets").description("Datasets");
|
|
4393
4761
|
datasetsCmd.command("list").description("List datasets").option(
|
|
4394
4762
|
"--class <class>",
|
|
@@ -4433,6 +4801,32 @@ datasetsCmd.command("purposes").description(
|
|
|
4433
4801
|
fail(e);
|
|
4434
4802
|
}
|
|
4435
4803
|
});
|
|
4804
|
+
datasetsCmd.command("set-purpose <slug> [purpose]").description(
|
|
4805
|
+
"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."
|
|
4806
|
+
).option("--clear", "remove the dataset's purpose instead of setting one").option(
|
|
4807
|
+
"--description <text>",
|
|
4808
|
+
"description for a purpose new to this org; ignored when the org already has an entry for it"
|
|
4809
|
+
).action(async (slug, purpose, opts) => {
|
|
4810
|
+
try {
|
|
4811
|
+
if (opts.clear && purpose) {
|
|
4812
|
+
fail(new Error("pass a purpose or --clear, not both"));
|
|
4813
|
+
return;
|
|
4814
|
+
}
|
|
4815
|
+
if (!opts.clear && !purpose) {
|
|
4816
|
+
fail(new Error("pass a purpose to set, or --clear to remove one"));
|
|
4817
|
+
return;
|
|
4818
|
+
}
|
|
4819
|
+
const res = await new ErdoClient().setDatasetPurpose(slug, {
|
|
4820
|
+
...purpose ? { purpose } : {},
|
|
4821
|
+
...opts.clear ? { clear_purpose: true } : {},
|
|
4822
|
+
...opts.description ? { purpose_description: opts.description } : {}
|
|
4823
|
+
});
|
|
4824
|
+
for (const w of res.warnings ?? []) console.error(`warning: ${w}`);
|
|
4825
|
+
console.log(res.purpose ? `${slug} ${res.purpose}` : `${slug} (no purpose)`);
|
|
4826
|
+
} catch (e) {
|
|
4827
|
+
fail(e);
|
|
4828
|
+
}
|
|
4829
|
+
});
|
|
4436
4830
|
datasetsCmd.command("query <slug> <question>").description(
|
|
4437
4831
|
"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`."
|
|
4438
4832
|
).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.72.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"
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"overrides": {
|
|
54
54
|
"esbuild": "^0.28.1"
|
|
55
55
|
}
|
|
56
|
-
}
|
|
56
|
+
}
|