@erdoai/cli 0.45.0 → 0.47.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 +211 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -281,12 +281,41 @@ var ErdoClient = class {
|
|
|
281
281
|
createManagerKey() {
|
|
282
282
|
return this.request("POST", "/v1/manager-key");
|
|
283
283
|
}
|
|
284
|
+
// --- Custom page domains: the branded hostnames the org's pages serve from ---
|
|
285
|
+
listCustomDomains() {
|
|
286
|
+
return this.request("GET", "/v1/custom-domains");
|
|
287
|
+
}
|
|
288
|
+
createCustomDomain(domain) {
|
|
289
|
+
return this.request("POST", "/v1/custom-domains", { domain });
|
|
290
|
+
}
|
|
291
|
+
deleteCustomDomain(domain) {
|
|
292
|
+
return this.request(
|
|
293
|
+
"DELETE",
|
|
294
|
+
`/v1/custom-domains/${encodeURIComponent(domain)}`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
// Moves a domain to another org you also administer, keeping its CDN hostname
|
|
298
|
+
// and certificate — the cutover is atomic, with no re-validation downtime.
|
|
299
|
+
transferCustomDomain(domain, targetOrg) {
|
|
300
|
+
return this.request(
|
|
301
|
+
"POST",
|
|
302
|
+
`/v1/custom-domains/${encodeURIComponent(domain)}/transfer`,
|
|
303
|
+
{ target_org: targetOrg }
|
|
304
|
+
);
|
|
305
|
+
}
|
|
284
306
|
// Run a read-only HogQL query against the org's page-analytics events. Rows are
|
|
285
307
|
// positional per columns; enabled:false means page analytics is off for the org
|
|
286
308
|
// (not zero traffic). A rejected query surfaces PostHog's message as the error.
|
|
287
309
|
queryPageAnalytics(query) {
|
|
288
310
|
return this.request("POST", "/v1/page-analytics/query", { query });
|
|
289
311
|
}
|
|
312
|
+
// Read which analytics destinations the org's published pages send visitor data
|
|
313
|
+
// to — the check that stops "this audience is big enough to retarget" being
|
|
314
|
+
// concluded from traffic no pixel was ever tagging. Read-only: destinations are
|
|
315
|
+
// Erdo-provisioned, never caller-set.
|
|
316
|
+
getPageTracking() {
|
|
317
|
+
return this.request("GET", "/v1/page-tracking");
|
|
318
|
+
}
|
|
290
319
|
listEvalSuites() {
|
|
291
320
|
return this.request("GET", "/v1/evals/suites");
|
|
292
321
|
}
|
|
@@ -655,6 +684,8 @@ var ErdoClient = class {
|
|
|
655
684
|
listDatasets(params) {
|
|
656
685
|
const q = new URLSearchParams();
|
|
657
686
|
if (params?.class) q.set("class", params.class);
|
|
687
|
+
if (params?.limit !== void 0) q.set("limit", String(params.limit));
|
|
688
|
+
if (params?.offset !== void 0) q.set("offset", String(params.offset));
|
|
658
689
|
const qs = q.toString();
|
|
659
690
|
return this.request("GET", `/v1/datasets${qs ? `?${qs}` : ""}`);
|
|
660
691
|
}
|
|
@@ -859,6 +890,8 @@ var ErdoClient = class {
|
|
|
859
890
|
if (opts.status) params.set("status", opts.status);
|
|
860
891
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
861
892
|
if (opts.workstream_slug) params.set("workstream_slug", opts.workstream_slug);
|
|
893
|
+
if (opts.subject_resource_type) params.set("subject_resource_type", opts.subject_resource_type);
|
|
894
|
+
if (opts.subject_resource_id) params.set("subject_resource_id", opts.subject_resource_id);
|
|
862
895
|
const qs = params.toString();
|
|
863
896
|
return this.request(
|
|
864
897
|
"GET",
|
|
@@ -2372,45 +2405,89 @@ runsCmd.command("get <id>").description("Show an agent run (status, output, trac
|
|
|
2372
2405
|
}
|
|
2373
2406
|
});
|
|
2374
2407
|
var approvalsCmd = program.command("approvals").description("List and decide approval requests (actions agents paused on)");
|
|
2375
|
-
approvalsCmd.command("list").description("List approval requests, optionally filtered by status").option("-s, --status <status>", "pending | approved | rejected | expired (default: all)").option("-l, --limit <n>", "max requests", (v) => parseInt(v, 10)).option("--workstream <slug>", "only approvals for this workstream").option(
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2408
|
+
approvalsCmd.command("list").description("List approval requests, optionally filtered by status").option("-s, --status <status>", "pending | approved | rejected | expired (default: all)").option("-l, --limit <n>", "max requests", (v) => parseInt(v, 10)).option("--workstream <slug>", "only approvals for this workstream").option(
|
|
2409
|
+
"--subject-type <type>",
|
|
2410
|
+
"only approvals whose subject resource is of this type (e.g. paid_media_campaign)"
|
|
2411
|
+
).option("--subject-id <id>", "only approvals whose subject resource has this id").option("--json", "output raw JSON").action(
|
|
2412
|
+
async (opts) => {
|
|
2413
|
+
try {
|
|
2414
|
+
const res = await new ErdoClient().listApprovals({
|
|
2415
|
+
status: opts.status,
|
|
2416
|
+
limit: opts.limit,
|
|
2417
|
+
workstream_slug: opts.workstream,
|
|
2418
|
+
subject_resource_type: opts.subjectType,
|
|
2419
|
+
subject_resource_id: opts.subjectId
|
|
2420
|
+
});
|
|
2421
|
+
if (opts.json) {
|
|
2422
|
+
print(res);
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
for (const r of res.requests) {
|
|
2426
|
+
const occ = r.occurrence_count && r.occurrence_count > 1 ? ` \xD7${r.occurrence_count}` : "";
|
|
2427
|
+
console.log(
|
|
2428
|
+
`${r.id} ${r.status} ${r.action_key} ${r.action_display} ${r.created_at}${occ}`
|
|
2429
|
+
);
|
|
2430
|
+
}
|
|
2431
|
+
} catch (e) {
|
|
2432
|
+
fail(e);
|
|
2391
2433
|
}
|
|
2392
|
-
} catch (e) {
|
|
2393
|
-
fail(e);
|
|
2394
2434
|
}
|
|
2395
|
-
|
|
2435
|
+
);
|
|
2396
2436
|
approvalsCmd.command("decide <id>").description("Approve or reject a pending approval request").option("--approve", "approve the request").option("--reject", "reject the request").option(
|
|
2397
2437
|
"--scope <scope>",
|
|
2398
2438
|
"once (default) | always_this_job | always_this_workstream | always_org | always_user",
|
|
2399
2439
|
"once"
|
|
2400
|
-
).
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2440
|
+
).option(
|
|
2441
|
+
"--option <n>",
|
|
2442
|
+
"for standing (non-once) approvals: use the request's Nth scope option (1-based) as the policy's parameter constraints \u2014 list them with `erdo approvals list --json`",
|
|
2443
|
+
(v) => parseInt(v, 10)
|
|
2444
|
+
).option(
|
|
2445
|
+
"--constraints <json>",
|
|
2446
|
+
`explicit parameter constraints for the standing policy, e.g. '{"spreadsheet_id":{"values":["..."]}}'`
|
|
2447
|
+
).action(
|
|
2448
|
+
async (id, opts) => {
|
|
2449
|
+
try {
|
|
2450
|
+
if (opts.approve === opts.reject) {
|
|
2451
|
+
throw new Error("specify exactly one of --approve or --reject");
|
|
2452
|
+
}
|
|
2453
|
+
if (opts.option !== void 0 && opts.constraints !== void 0) {
|
|
2454
|
+
throw new Error("specify at most one of --option or --constraints");
|
|
2455
|
+
}
|
|
2456
|
+
const client = new ErdoClient();
|
|
2457
|
+
let constraints;
|
|
2458
|
+
if (opts.constraints !== void 0) {
|
|
2459
|
+
try {
|
|
2460
|
+
constraints = JSON.parse(opts.constraints);
|
|
2461
|
+
} catch {
|
|
2462
|
+
throw new Error(
|
|
2463
|
+
`--constraints must be JSON, e.g. '{"spreadsheet_id":{"values":["abc"]}}'`
|
|
2464
|
+
);
|
|
2465
|
+
}
|
|
2466
|
+
} else if (opts.option !== void 0) {
|
|
2467
|
+
const res2 = await client.listApprovals({ status: "pending" });
|
|
2468
|
+
const req = res2.requests.find((r) => r.id === id);
|
|
2469
|
+
if (!req) {
|
|
2470
|
+
throw new Error(`pending approval ${id} not found`);
|
|
2471
|
+
}
|
|
2472
|
+
const options = req.scope_options ?? [];
|
|
2473
|
+
if (opts.option < 1 || opts.option > options.length) {
|
|
2474
|
+
const listing = options.length ? options.map((o, i) => ` ${i + 1}. ${o.label}`).join("\n") : " (none \u2014 pass --constraints instead)";
|
|
2475
|
+
throw new Error(`--option must be 1..${options.length}; available options:
|
|
2476
|
+
${listing}`);
|
|
2477
|
+
}
|
|
2478
|
+
constraints = options[opts.option - 1].constraints;
|
|
2479
|
+
}
|
|
2480
|
+
const res = await client.decideApproval(id, {
|
|
2481
|
+
decision: opts.approve ? "approved" : "rejected",
|
|
2482
|
+
scope: opts.scope,
|
|
2483
|
+
parameter_constraints: constraints
|
|
2484
|
+
});
|
|
2485
|
+
console.log(res.message);
|
|
2486
|
+
} catch (e) {
|
|
2487
|
+
fail(e);
|
|
2404
2488
|
}
|
|
2405
|
-
const res = await new ErdoClient().decideApproval(id, {
|
|
2406
|
-
decision: opts.approve ? "approved" : "rejected",
|
|
2407
|
-
scope: opts.scope
|
|
2408
|
-
});
|
|
2409
|
-
console.log(res.message);
|
|
2410
|
-
} catch (e) {
|
|
2411
|
-
fail(e);
|
|
2412
2489
|
}
|
|
2413
|
-
|
|
2490
|
+
);
|
|
2414
2491
|
var attnCmd = program.command("attention").description("The attention feed \u2014 digests, choices, escalations awaiting a human");
|
|
2415
2492
|
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("-n, --limit <n>", "max items", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).action(
|
|
2416
2493
|
async (opts) => {
|
|
@@ -2910,15 +2987,76 @@ pagesCmd.command("review-result <id>").description("Read a page review by id \u2
|
|
|
2910
2987
|
fail(e);
|
|
2911
2988
|
}
|
|
2912
2989
|
});
|
|
2990
|
+
var domainsCmd = pagesCmd.command("domains").description("Custom domains the org's published pages serve from");
|
|
2991
|
+
domainsCmd.command("list").description("List the org's custom domains with live lifecycle status").action(async () => {
|
|
2992
|
+
try {
|
|
2993
|
+
const { domains } = await new ErdoClient().listCustomDomains();
|
|
2994
|
+
if (domains.length === 0) {
|
|
2995
|
+
console.log("No custom domains. Register one with: erdo pages domains add <domain>");
|
|
2996
|
+
return;
|
|
2997
|
+
}
|
|
2998
|
+
for (const d of domains) {
|
|
2999
|
+
const checked = d.last_checked_at ? `checked ${d.last_checked_at}` : "never checked";
|
|
3000
|
+
const reason = d.error_reason ? ` ${d.error_reason}` : "";
|
|
3001
|
+
console.log(`${d.domain} ${d.status} ${checked}${reason}`);
|
|
3002
|
+
}
|
|
3003
|
+
} catch (e) {
|
|
3004
|
+
fail(e);
|
|
3005
|
+
}
|
|
3006
|
+
});
|
|
3007
|
+
domainsCmd.command("add <domain>").description("Register a custom domain (a direct subdomain, e.g. pages.acme.com) and print the DNS records to create").action(async (domain) => {
|
|
3008
|
+
try {
|
|
3009
|
+
const d = await new ErdoClient().createCustomDomain(domain);
|
|
3010
|
+
console.log(`${d.domain}: ${d.status}`);
|
|
3011
|
+
if (d.dns_records.length > 0) {
|
|
3012
|
+
process.stderr.write("Create these records at the domain's DNS provider:\n");
|
|
3013
|
+
for (const r of d.dns_records) {
|
|
3014
|
+
console.log(`${r.type} ${r.name} ${r.value}`);
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
} catch (e) {
|
|
3018
|
+
fail(e);
|
|
3019
|
+
}
|
|
3020
|
+
});
|
|
3021
|
+
domainsCmd.command("remove <domain>").description("Remove a custom domain registration (stops it serving pages)").action(async (domain) => {
|
|
3022
|
+
try {
|
|
3023
|
+
const res = await new ErdoClient().deleteCustomDomain(domain);
|
|
3024
|
+
console.log(`removed: ${res.domain}`);
|
|
3025
|
+
} catch (e) {
|
|
3026
|
+
fail(e);
|
|
3027
|
+
}
|
|
3028
|
+
});
|
|
3029
|
+
domainsCmd.command("transfer <domain> <targetOrg>").description("Move a domain to another org you administer, keeping its certificate (no serving downtime)").action(async (domain, targetOrg) => {
|
|
3030
|
+
try {
|
|
3031
|
+
const d = await new ErdoClient().transferCustomDomain(domain, targetOrg);
|
|
3032
|
+
console.log(`${d.domain} now belongs to ${targetOrg} (status: ${d.status})`);
|
|
3033
|
+
} catch (e) {
|
|
3034
|
+
fail(e);
|
|
3035
|
+
}
|
|
3036
|
+
});
|
|
2913
3037
|
var datasetsCmd = program.command("datasets").description("Datasets");
|
|
2914
3038
|
datasetsCmd.command("list").description("List datasets").option(
|
|
2915
3039
|
"--class <class>",
|
|
2916
3040
|
"filter by structural class: 'table' or 'scratch', or 'all' to include scratch analysis by-products (default hides scratch)"
|
|
2917
|
-
).
|
|
3041
|
+
).option(
|
|
3042
|
+
"-l, --limit <n>",
|
|
3043
|
+
"max datasets to return (default 20, max 100)",
|
|
3044
|
+
(v) => parseInt(v, 10)
|
|
3045
|
+
).option("--offset <n>", "skip the first n datasets (newest first)", (v) => parseInt(v, 10)).action(async (opts) => {
|
|
2918
3046
|
try {
|
|
2919
|
-
const { datasets } = await new ErdoClient().listDatasets({
|
|
3047
|
+
const { datasets, total } = await new ErdoClient().listDatasets({
|
|
3048
|
+
class: opts.class,
|
|
3049
|
+
limit: opts.limit,
|
|
3050
|
+
offset: opts.offset
|
|
3051
|
+
});
|
|
2920
3052
|
for (const d of datasets)
|
|
2921
3053
|
console.log(`${d.slug} ${d.type} ${d.status} ${d.class ?? "-"} ${d.name}`);
|
|
3054
|
+
const shown = (opts.offset ?? 0) + datasets.length;
|
|
3055
|
+
if (total > shown) {
|
|
3056
|
+
console.error(`(showing ${datasets.length} of ${total} \u2014 use --limit/--offset for more)`);
|
|
3057
|
+
} else {
|
|
3058
|
+
console.error(`(${total} dataset${total === 1 ? "" : "s"})`);
|
|
3059
|
+
}
|
|
2922
3060
|
} catch (e) {
|
|
2923
3061
|
fail(e);
|
|
2924
3062
|
}
|
|
@@ -3019,7 +3157,7 @@ function printAnalyticsTable(columns, rows) {
|
|
|
3019
3157
|
console.log(widths.map((w) => "-".repeat(w)).join(" "));
|
|
3020
3158
|
for (const r of rows) console.log(line(columns.map((_, i) => cell(r[i]))));
|
|
3021
3159
|
}
|
|
3022
|
-
var analytics = program.command("analytics").description("
|
|
3160
|
+
var analytics = program.command("analytics").description("Page analytics \u2014 what is tracking your published pages, and how they perform with real visitors");
|
|
3023
3161
|
analytics.command("query <hogql>").description("Run a read-only HogQL query against this org's page-analytics events").option("--json", "print the raw JSON result instead of a table").action(async (hogql, opts) => {
|
|
3024
3162
|
try {
|
|
3025
3163
|
const res = await new ErdoClient().queryPageAnalytics(hogql);
|
|
@@ -3038,6 +3176,44 @@ analytics.command("query <hogql>").description("Run a read-only HogQL query agai
|
|
|
3038
3176
|
fail(e);
|
|
3039
3177
|
}
|
|
3040
3178
|
});
|
|
3179
|
+
var TRACKING_KINDS = [
|
|
3180
|
+
{ kind: "session_analytics", what: "heatmaps, session replay, and the events behind `erdo analytics query`" },
|
|
3181
|
+
{ kind: "google_analytics", what: "GA4 measurement" },
|
|
3182
|
+
{ kind: "meta_pixel", what: "cookies visitors so Meta retargeting audiences can be built" }
|
|
3183
|
+
];
|
|
3184
|
+
analytics.command("tracking").description("Show which analytics destinations this org's published pages send visitor data to").option("--json", "print the raw JSON result instead of a table").action(async (opts) => {
|
|
3185
|
+
try {
|
|
3186
|
+
const res = await new ErdoClient().getPageTracking();
|
|
3187
|
+
if (opts.json) {
|
|
3188
|
+
print(res);
|
|
3189
|
+
return;
|
|
3190
|
+
}
|
|
3191
|
+
const destinations = res.destinations ?? [];
|
|
3192
|
+
const byKind = new Map(destinations.map((d) => [d.kind, d]));
|
|
3193
|
+
const rows = TRACKING_KINDS.map(({ kind, what }) => {
|
|
3194
|
+
const d = byKind.get(kind);
|
|
3195
|
+
const status = d === void 0 ? "not configured" : d.enabled ? "on" : "configured, off";
|
|
3196
|
+
return [kind, status, d?.public_id ?? "", d?.provider ?? "", what];
|
|
3197
|
+
});
|
|
3198
|
+
for (const d of destinations) {
|
|
3199
|
+
if (!TRACKING_KINDS.some((k) => k.kind === d.kind)) {
|
|
3200
|
+
rows.push([d.kind, d.enabled ? "on" : "configured, off", d.public_id ?? "", d.provider ?? "", ""]);
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
printAnalyticsTable(["kind", "status", "public id", "provider", "what it does"], rows);
|
|
3204
|
+
console.log(`
|
|
3205
|
+
session replay input masking: ${res.mask_inputs ? "on" : "OFF \u2014 form values are recorded"}`);
|
|
3206
|
+
console.log(`consent: ${res.consent}${res.consent === "required" ? " (recording waits on a consent banner, so thin volume may be the gate, not the traffic)" : ""}`);
|
|
3207
|
+
if (!byKind.get("meta_pixel")?.enabled) {
|
|
3208
|
+
console.log(`
|
|
3209
|
+
No Meta pixel is firing on these pages, so no retargeting audience is accumulating \u2014 page traffic alone does not mean those visitors can be served ads.`);
|
|
3210
|
+
}
|
|
3211
|
+
console.log(`
|
|
3212
|
+
Erdo's own page-events beacon is provisioned per page at render time and is not listed above \u2014 it records independently of every vendor here.`);
|
|
3213
|
+
} catch (e) {
|
|
3214
|
+
fail(e);
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3041
3217
|
var OPERATORS = ["equals", "not_equals", "greater_than", "less_than", "contains", "not_contains", "between"];
|
|
3042
3218
|
function parseCondition(s) {
|
|
3043
3219
|
const m = s.trim().match(/^(\S+)\s+(\S+)\s+([\s\S]+)$/);
|
|
@@ -3075,7 +3251,7 @@ integrationsCmd.command("apps [query]").description("Search connectable apps (na
|
|
|
3075
3251
|
fail(e);
|
|
3076
3252
|
}
|
|
3077
3253
|
});
|
|
3078
|
-
integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass credentials with -c, or get a browser connect URL for OAuth apps").option("-c, --credential <key=value...>", "credential field (repeatable)", (v, acc) => acc.concat(v), []).option("-n, --name <name>", "display name for the connection").action(async (app, opts) => {
|
|
3254
|
+
integrationsCmd.command("connect <app>").description("Connect an app \u2014 pass its API key or other credentials with -c, or omit them to get a browser connect URL for OAuth apps").option("-c, --credential <key=value...>", "credential field, e.g. -c api_key=\u2026 (repeatable); rejected by OAuth apps, which have no credential to pass", (v, acc) => acc.concat(v), []).option("-n, --name <name>", "display name for the connection").action(async (app, opts) => {
|
|
3079
3255
|
try {
|
|
3080
3256
|
let credentials;
|
|
3081
3257
|
if (opts.credential.length) {
|