@erdoai/cli 0.49.1 → 0.51.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 +303 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -745,6 +745,19 @@ var ErdoClient = class {
|
|
|
745
745
|
listIntegrations() {
|
|
746
746
|
return this.request("GET", "/v1/integrations");
|
|
747
747
|
}
|
|
748
|
+
getConnectionScope(integrationId) {
|
|
749
|
+
return this.request(
|
|
750
|
+
"GET",
|
|
751
|
+
`/v1/integrations/${encodeURIComponent(integrationId)}/connection-scope`
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
setConnectionScope(integrationId, accountId) {
|
|
755
|
+
return this.request(
|
|
756
|
+
"POST",
|
|
757
|
+
`/v1/integrations/${encodeURIComponent(integrationId)}/connection-scope`,
|
|
758
|
+
{ account_id: accountId }
|
|
759
|
+
);
|
|
760
|
+
}
|
|
748
761
|
searchIntegrationApps(query) {
|
|
749
762
|
const qs = query ? `?query=${encodeURIComponent(query)}` : "";
|
|
750
763
|
return this.request("GET", `/v1/integration-apps${qs}`);
|
|
@@ -916,6 +929,48 @@ var ErdoClient = class {
|
|
|
916
929
|
input
|
|
917
930
|
);
|
|
918
931
|
}
|
|
932
|
+
// --- decisions ---
|
|
933
|
+
// The decision record: what the organization committed to, what executed, and
|
|
934
|
+
// what the evidence said afterwards. Referenced by slug, never UUID.
|
|
935
|
+
listDecisions(opts) {
|
|
936
|
+
const params = new URLSearchParams();
|
|
937
|
+
if (opts.workstream_slug) params.set("workstream_slug", opts.workstream_slug);
|
|
938
|
+
if (opts.source) params.set("source", opts.source);
|
|
939
|
+
if (opts.decision_class) params.set("decision_class", opts.decision_class);
|
|
940
|
+
if (opts.subject_kind) params.set("subject_kind", opts.subject_kind);
|
|
941
|
+
if (opts.subject_ref) params.set("subject_ref", opts.subject_ref);
|
|
942
|
+
if (opts.status) params.set("status", opts.status);
|
|
943
|
+
if (opts.applicability) params.set("applicability", opts.applicability);
|
|
944
|
+
if (opts.outcome) params.set("outcome", opts.outcome);
|
|
945
|
+
if (opts.approval_id) params.set("approval_id", opts.approval_id);
|
|
946
|
+
if (opts.limit) params.set("limit", String(opts.limit));
|
|
947
|
+
if (opts.offset) params.set("offset", String(opts.offset));
|
|
948
|
+
const qs = params.toString();
|
|
949
|
+
return this.request("GET", `/v1/decisions${qs ? `?${qs}` : ""}`);
|
|
950
|
+
}
|
|
951
|
+
// One decision in full. A slug belonging to another organization reads as 404,
|
|
952
|
+
// so it cannot be probed for existence.
|
|
953
|
+
getDecision(slug) {
|
|
954
|
+
return this.request(
|
|
955
|
+
"GET",
|
|
956
|
+
`/v1/decisions/${encodeURIComponent(slug)}`
|
|
957
|
+
);
|
|
958
|
+
}
|
|
959
|
+
// Raw aggregates with their denominators — no eligibility verdict, and no single
|
|
960
|
+
// pooled "worked rate" across evidence kinds and decision classes.
|
|
961
|
+
decisionScorecard(opts) {
|
|
962
|
+
const params = new URLSearchParams();
|
|
963
|
+
if (opts.workstream_slug) params.set("workstream_slug", opts.workstream_slug);
|
|
964
|
+
if (opts.source) params.set("source", opts.source);
|
|
965
|
+
if (opts.decision_class) params.set("decision_class", opts.decision_class);
|
|
966
|
+
if (opts.since) params.set("since", opts.since);
|
|
967
|
+
if (opts.until) params.set("until", opts.until);
|
|
968
|
+
const qs = params.toString();
|
|
969
|
+
return this.request(
|
|
970
|
+
"GET",
|
|
971
|
+
`/v1/decisions-scorecard${qs ? `?${qs}` : ""}`
|
|
972
|
+
);
|
|
973
|
+
}
|
|
919
974
|
// --- pages / artifacts ---
|
|
920
975
|
deployPage(input) {
|
|
921
976
|
return this.request("POST", "/v1/pages", input);
|
|
@@ -2437,6 +2492,214 @@ runsCmd.command("get <id>").description("Show an agent run (status, output, trac
|
|
|
2437
2492
|
fail(e);
|
|
2438
2493
|
}
|
|
2439
2494
|
});
|
|
2495
|
+
var decisionsCmd = program.command("decisions").description(
|
|
2496
|
+
"The decision record \u2014 what your organization committed to, whether the change actually happened, and what the evidence said afterwards"
|
|
2497
|
+
);
|
|
2498
|
+
function decisionLine(d) {
|
|
2499
|
+
const parts = [d.slug, d.status];
|
|
2500
|
+
if (d.execution_status) parts.push(d.execution_status);
|
|
2501
|
+
parts.push(d.what);
|
|
2502
|
+
if (d.outcome_label) {
|
|
2503
|
+
parts.push(d.outcome_label);
|
|
2504
|
+
} else if (d.outcome_status) {
|
|
2505
|
+
parts.push(d.outcome_status);
|
|
2506
|
+
}
|
|
2507
|
+
return parts.join(" ");
|
|
2508
|
+
}
|
|
2509
|
+
decisionsCmd.command("list").description("List decisions, newest first").option("--workstream <slug>", "only decisions filed under this workstream/Strategy").option(
|
|
2510
|
+
"--source <source>",
|
|
2511
|
+
"producer family: approval | escalation | engine_gate | allocator | experiment | workstream_commitment"
|
|
2512
|
+
).option("--class <class>", "decision class, e.g. paid_media.ad_group.pause").option("--subject-kind <kind>", "only decisions about this kind of subject").option("--subject-ref <id>", "only decisions about this exact subject").option(
|
|
2513
|
+
"--status <status>",
|
|
2514
|
+
"proposed | authorized | executing | effective | measuring | settled | rejected | failed | censored"
|
|
2515
|
+
).option("--applicability <kind>", "standing | one_shot").option("--outcome <outcome>", "only decisions with a settled effect that came back met | not_met | inconclusive").option(
|
|
2516
|
+
"--approval <id>",
|
|
2517
|
+
"only the decisions produced by answering this approval (approvals are referenced by id, not slug)"
|
|
2518
|
+
).option("-l, --limit <n>", "max rows (default 50, max 200)", (v) => parseInt(v, 10)).option("--offset <n>", "pagination offset", (v) => parseInt(v, 10)).option("--json", "output raw JSON").action(
|
|
2519
|
+
async (opts) => {
|
|
2520
|
+
try {
|
|
2521
|
+
const res = await new ErdoClient().listDecisions({
|
|
2522
|
+
workstream_slug: opts.workstream,
|
|
2523
|
+
source: opts.source,
|
|
2524
|
+
decision_class: opts.class,
|
|
2525
|
+
subject_kind: opts.subjectKind,
|
|
2526
|
+
subject_ref: opts.subjectRef,
|
|
2527
|
+
status: opts.status,
|
|
2528
|
+
applicability: opts.applicability,
|
|
2529
|
+
outcome: opts.outcome,
|
|
2530
|
+
approval_id: opts.approval,
|
|
2531
|
+
limit: opts.limit,
|
|
2532
|
+
offset: opts.offset
|
|
2533
|
+
});
|
|
2534
|
+
if (opts.json) {
|
|
2535
|
+
print(res);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
if (!res.decisions.length) {
|
|
2539
|
+
console.log("No decisions match. Widen the filters, or check that the work engine is on for this org.");
|
|
2540
|
+
return;
|
|
2541
|
+
}
|
|
2542
|
+
for (const d of res.decisions) console.log(decisionLine(d));
|
|
2543
|
+
} catch (e) {
|
|
2544
|
+
fail(e);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
);
|
|
2548
|
+
function renderDecision(detail) {
|
|
2549
|
+
const d = detail.decision;
|
|
2550
|
+
console.log(d.what);
|
|
2551
|
+
const facts = [
|
|
2552
|
+
["status", d.status],
|
|
2553
|
+
["class", d.decision_class],
|
|
2554
|
+
["source", d.source],
|
|
2555
|
+
["applies", d.applicability === "standing" ? "standing (until superseded)" : "one-off authorization"],
|
|
2556
|
+
["decided by", d.decider_kind]
|
|
2557
|
+
];
|
|
2558
|
+
if (d.subject_label) facts.push(["subject", d.subject_label]);
|
|
2559
|
+
if (d.execution_status) facts.push(["execution", d.execution_status]);
|
|
2560
|
+
if (d.outcome_status) facts.push(["outcome", d.outcome_label || d.outcome_status]);
|
|
2561
|
+
facts.push(["proposed", d.proposed_at]);
|
|
2562
|
+
if (d.authorized_at) facts.push(["authorized", d.authorized_at]);
|
|
2563
|
+
for (const [label, value] of facts) console.log(` ${label.padEnd(11)} ${value}`);
|
|
2564
|
+
if (d.why) {
|
|
2565
|
+
console.log("");
|
|
2566
|
+
console.log(`Why: ${d.why}`);
|
|
2567
|
+
}
|
|
2568
|
+
if (d.decider_rationale) console.log(`Decider said: ${d.decider_rationale}`);
|
|
2569
|
+
if (detail.actions.length) {
|
|
2570
|
+
console.log("");
|
|
2571
|
+
console.log(detail.actions.length === 1 ? "Action:" : `Actions (${detail.actions.length}):`);
|
|
2572
|
+
detail.actions.forEach((a, i) => {
|
|
2573
|
+
console.log(` ${i + 1}. ${a.action_key} \u2014 ${a.execution_status}`);
|
|
2574
|
+
if (a.subject_label) console.log(` subject: ${a.subject_label}`);
|
|
2575
|
+
if (a.effective_at) console.log(` effective: ${a.effective_at}`);
|
|
2576
|
+
if (a.result_summary) console.log(` result: ${a.result_summary}`);
|
|
2577
|
+
});
|
|
2578
|
+
}
|
|
2579
|
+
if (detail.effects.length) {
|
|
2580
|
+
console.log("");
|
|
2581
|
+
console.log(detail.effects.length === 1 ? "Expected effect:" : `Expected effects (${detail.effects.length}):`);
|
|
2582
|
+
detail.effects.forEach((e, i) => {
|
|
2583
|
+
const predicate = [e.metric, e.success_operator, e.target_value ?? e.min_delta].filter((p) => p !== void 0 && p !== null && p !== "").join(" ");
|
|
2584
|
+
console.log(` ${i + 1}. ${predicate || e.measurability} \u2014 ${e.status}`);
|
|
2585
|
+
if (e.outcome) {
|
|
2586
|
+
console.log(` outcome: ${e.outcome_label || e.outcome} (${e.evidence_kind} evidence)`);
|
|
2587
|
+
}
|
|
2588
|
+
if (e.baseline_value !== void 0 && e.baseline_value !== null && e.outcome_value !== void 0 && e.outcome_value !== null) {
|
|
2589
|
+
console.log(` measured: ${e.baseline_value} \u2192 ${e.outcome_value}`);
|
|
2590
|
+
}
|
|
2591
|
+
if (e.unmeasurable_reason) console.log(` reason: ${e.unmeasurable_reason}`);
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
if (detail.lineage.supersedes_slug || detail.lineage.superseded_by_slugs?.length) {
|
|
2595
|
+
console.log("");
|
|
2596
|
+
console.log("Lineage:");
|
|
2597
|
+
if (detail.lineage.supersedes_slug) console.log(` replaced ${detail.lineage.supersedes_slug}`);
|
|
2598
|
+
for (const slug of detail.lineage.superseded_by_slugs ?? []) {
|
|
2599
|
+
console.log(` replaced by ${slug}`);
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
decisionsCmd.command("show <slug>").description(
|
|
2604
|
+
"Show one decision in full: the commitment and its authority, every exact action it authorized with how each one ended, every declared effect with the evidence that settled it, and what it replaced or was replaced by"
|
|
2605
|
+
).option("--json", "output raw JSON").action(async (slug, opts) => {
|
|
2606
|
+
try {
|
|
2607
|
+
const detail = await new ErdoClient().getDecision(slug);
|
|
2608
|
+
if (opts.json) {
|
|
2609
|
+
print(detail);
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
renderDecision(detail);
|
|
2613
|
+
} catch (e) {
|
|
2614
|
+
fail(e);
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
function renderStrata(title, strata) {
|
|
2618
|
+
if (!strata.length) return;
|
|
2619
|
+
console.log("");
|
|
2620
|
+
console.log(title);
|
|
2621
|
+
for (const s of strata) {
|
|
2622
|
+
const axis = s.decision_class || s.source || "(unclassified)";
|
|
2623
|
+
console.log(` ${axis} \xB7 ${s.evidence_kind} evidence \u2014 ${s.settled} settled`);
|
|
2624
|
+
console.log(` ${s.met} ${s.met_label} \xB7 ${s.not_met} ${s.not_met_label} \xB7 ${s.inconclusive} inconclusive`);
|
|
2625
|
+
}
|
|
2626
|
+
}
|
|
2627
|
+
function renderScorecard(card) {
|
|
2628
|
+
console.log("Decision record");
|
|
2629
|
+
console.log(` decisions ${card.totals.decisions}`);
|
|
2630
|
+
for (const [status, n] of Object.entries(card.totals.by_status)) {
|
|
2631
|
+
console.log(` ${status.padEnd(11)} ${n}`);
|
|
2632
|
+
}
|
|
2633
|
+
if (card.totals.legacy_unclassified > 0) {
|
|
2634
|
+
console.log(` legacy ${card.totals.legacy_unclassified} (predates expected-effect capture; excluded from outcomes below)`);
|
|
2635
|
+
}
|
|
2636
|
+
console.log("");
|
|
2637
|
+
console.log("Execution");
|
|
2638
|
+
console.log(` decisions with an external action ${card.execution.decisions_with_actions} of ${card.totals.decisions}`);
|
|
2639
|
+
console.log(` actions ${card.execution.actions}`);
|
|
2640
|
+
for (const [status, n] of Object.entries(card.execution.by_status)) {
|
|
2641
|
+
console.log(` ${status.padEnd(11)} ${n}`);
|
|
2642
|
+
}
|
|
2643
|
+
console.log("");
|
|
2644
|
+
console.log("Measurement coverage");
|
|
2645
|
+
console.log(
|
|
2646
|
+
` decisions that declared an effect ${card.measurement.decisions_with_declared_effect} of ${card.totals.decisions}`
|
|
2647
|
+
);
|
|
2648
|
+
console.log(` effects ${card.measurement.effects}`);
|
|
2649
|
+
console.log(` settled ${card.measurement.settled}`);
|
|
2650
|
+
console.log(` censored ${card.measurement.censored}`);
|
|
2651
|
+
console.log(` measurement unavailable ${card.measurement.measurement_unavailable}`);
|
|
2652
|
+
for (const [kind, n] of Object.entries(card.measurement.by_measurability)) {
|
|
2653
|
+
console.log(` ${kind.padEnd(29)} ${n}`);
|
|
2654
|
+
}
|
|
2655
|
+
renderStrata("Outcomes by decision class and evidence", card.outcomes_by_class_and_evidence);
|
|
2656
|
+
renderStrata("Outcomes by source and evidence", card.outcomes_by_source_and_evidence);
|
|
2657
|
+
console.log("");
|
|
2658
|
+
console.log("Deciders");
|
|
2659
|
+
console.log(` answered ${card.deciders.answered}`);
|
|
2660
|
+
for (const [kind, n] of Object.entries(card.deciders.by_decider_kind)) {
|
|
2661
|
+
console.log(` ${kind.padEnd(21)} ${n}`);
|
|
2662
|
+
}
|
|
2663
|
+
console.log(
|
|
2664
|
+
` safe default applied ${card.deciders.safe_default} of ${card.deciders.answered} (${(card.deciders.safe_default_rate * 100).toFixed(1)}%)`
|
|
2665
|
+
);
|
|
2666
|
+
console.log(
|
|
2667
|
+
` human refused a proposal ${card.deciders.human_override} of ${card.deciders.answered} (${(card.deciders.human_override_rate * 100).toFixed(1)}%)`
|
|
2668
|
+
);
|
|
2669
|
+
if (card.decide_latency.decided > 0) {
|
|
2670
|
+
console.log("");
|
|
2671
|
+
console.log("Time to authorize");
|
|
2672
|
+
console.log(` decisions authorized ${card.decide_latency.decided}`);
|
|
2673
|
+
console.log(` median ${(card.decide_latency.p50_seconds / 3600).toFixed(1)}h`);
|
|
2674
|
+
console.log(` 90th percentile ${(card.decide_latency.p90_seconds / 3600).toFixed(1)}h`);
|
|
2675
|
+
}
|
|
2676
|
+
console.log("");
|
|
2677
|
+
console.log(
|
|
2678
|
+
"Raw counts only \u2014 no eligibility verdict, and no single pooled rate: deterministic confirmation, experimental results and observational movement are counted separately because they support different claims."
|
|
2679
|
+
);
|
|
2680
|
+
}
|
|
2681
|
+
decisionsCmd.command("scorecard").description(
|
|
2682
|
+
"Raw aggregates over the decision record with their denominators: totals by status, how much executed, how much can be measured at all, and the settled outcome split within each comparable stratum"
|
|
2683
|
+
).option("--workstream <slug>", "score only this workstream/Strategy's decisions").option("--source <source>", "score only one producer family").option("--class <class>", "score only one decision class").option("--since <rfc3339>", "inclusive lower bound on when the decision was recorded").option("--until <rfc3339>", "exclusive upper bound on when the decision was recorded").option("--json", "output raw JSON").action(
|
|
2684
|
+
async (opts) => {
|
|
2685
|
+
try {
|
|
2686
|
+
const card = await new ErdoClient().decisionScorecard({
|
|
2687
|
+
workstream_slug: opts.workstream,
|
|
2688
|
+
source: opts.source,
|
|
2689
|
+
decision_class: opts.class,
|
|
2690
|
+
since: opts.since,
|
|
2691
|
+
until: opts.until
|
|
2692
|
+
});
|
|
2693
|
+
if (opts.json) {
|
|
2694
|
+
print(card);
|
|
2695
|
+
return;
|
|
2696
|
+
}
|
|
2697
|
+
renderScorecard(card);
|
|
2698
|
+
} catch (e) {
|
|
2699
|
+
fail(e);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
);
|
|
2440
2703
|
var approvalsCmd = program.command("approvals").description("List and decide approval requests (actions agents paused on)");
|
|
2441
2704
|
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(
|
|
2442
2705
|
"--subject-type <type>",
|
|
@@ -3374,6 +3637,46 @@ integrationsCmd.command("status <app>").description("Check whether an app is con
|
|
|
3374
3637
|
fail(e);
|
|
3375
3638
|
}
|
|
3376
3639
|
});
|
|
3640
|
+
var accountCmd = integrationsCmd.command("account").description("Read or set which provider account a connection operates");
|
|
3641
|
+
accountCmd.command("show <integration-id>").description("Show which account a connection operates, and which it could (see: erdo integrations list)").action(async (integrationId) => {
|
|
3642
|
+
try {
|
|
3643
|
+
const scope = await new ErdoClient().getConnectionScope(integrationId);
|
|
3644
|
+
if (!scope.has_connection_scope) {
|
|
3645
|
+
console.log("This integration does not choose a provider account per connection.");
|
|
3646
|
+
return;
|
|
3647
|
+
}
|
|
3648
|
+
console.log(scope.description);
|
|
3649
|
+
const selected = new Set(scope.selected.map((a) => a.id));
|
|
3650
|
+
for (const account of scope.options) {
|
|
3651
|
+
const marker = selected.has(account.id) ? "*" : " ";
|
|
3652
|
+
const owner = account.parent ? ` ${account.parent}` : "";
|
|
3653
|
+
console.log(`${marker} ${account.id} ${account.name} ${account.type}${owner}`);
|
|
3654
|
+
}
|
|
3655
|
+
if (scope.options.length === 0) {
|
|
3656
|
+
console.error(
|
|
3657
|
+
`
|
|
3658
|
+
This login reaches no account yet. Set one here once it exists:
|
|
3659
|
+
erdo integrations account set ${integrationId} <account-id>`
|
|
3660
|
+
);
|
|
3661
|
+
} else if (selected.size === 0) {
|
|
3662
|
+
console.error(
|
|
3663
|
+
`
|
|
3664
|
+
No account chosen yet${scope.required ? " \u2014 this connection cannot resolve one until you set it" : ""}.
|
|
3665
|
+
Set it with: erdo integrations account set ${integrationId} <account-id>`
|
|
3666
|
+
);
|
|
3667
|
+
}
|
|
3668
|
+
} catch (e) {
|
|
3669
|
+
fail(e);
|
|
3670
|
+
}
|
|
3671
|
+
});
|
|
3672
|
+
accountCmd.command("set <integration-id> <account-id>").description("Record which account this connection operates \u2014 every dataset, sync, and agent action it serves runs against this account").action(async (integrationId, accountId) => {
|
|
3673
|
+
try {
|
|
3674
|
+
const scope = await new ErdoClient().setConnectionScope(integrationId, accountId);
|
|
3675
|
+
print(scope);
|
|
3676
|
+
} catch (e) {
|
|
3677
|
+
fail(e);
|
|
3678
|
+
}
|
|
3679
|
+
});
|
|
3377
3680
|
integrationsCmd.command("tables <app> [schema]").description("List a connected database integration's schemas, or a schema's tables with columns").action(async (app, schema) => {
|
|
3378
3681
|
try {
|
|
3379
3682
|
const res = await new ErdoClient().discoverIntegrationTables(app, schema);
|