@erdoai/cli 0.76.0 → 0.78.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.
Files changed (2) hide show
  1. package/dist/index.js +60 -20
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@
4
4
  import { readFileSync as readFileSync4 } from "fs";
5
5
  import { basename } from "path";
6
6
  import { select } from "@inquirer/prompts";
7
- import { Command } from "commander";
7
+ import { Command as Command2 } from "commander";
8
8
 
9
9
  // src/config.ts
10
10
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
@@ -940,8 +940,14 @@ var ErdoClient = class {
940
940
  }
941
941
  // The org's dataset vocabulary: every purpose in use, its description, and
942
942
  // the dataset that established it (the canonical write target for that role).
943
- listDatasetPurposes() {
944
- return this.request("GET", `/v1/datasets-purposes`);
943
+ // scope="managed" reads the same across every client organization a manager
944
+ // account operates; each entry then names its organization and its
945
+ // established_by slug is org-qualified, so it can be queried as-is.
946
+ listDatasetPurposes(params) {
947
+ const q = new URLSearchParams();
948
+ if (params?.scope) q.set("scope", params.scope);
949
+ const qs = q.toString();
950
+ return this.request("GET", `/v1/datasets-purposes${qs ? `?${qs}` : ""}`);
945
951
  }
946
952
  // Correct which dataset carries a purpose. Pass `purpose` to set one,
947
953
  // `clear_purpose` to remove it; moving a purpose is clearing it from the old
@@ -1582,6 +1588,32 @@ function readMaybeFile(value) {
1582
1588
  return value.startsWith("@") ? readFileSync2(value.slice(1), "utf8") : value;
1583
1589
  }
1584
1590
 
1591
+ // src/scope.ts
1592
+ import { Option } from "commander";
1593
+ var ORG_ENV = "ERDO_ORG";
1594
+ var PROJECT_ENV = "ERDO_PROJECT";
1595
+ function bindScopeFlags(program2) {
1596
+ program2.option("--org <idOrSlug>", "org to target for this command (overrides the active org)").option(
1597
+ "--project <slugOrId>",
1598
+ "project context for this command \u2014 slug or UUID (must belong to the active org)"
1599
+ );
1600
+ program2.on("option:org", (value) => {
1601
+ process.env[ORG_ENV] = value;
1602
+ });
1603
+ program2.on("option:project", (value) => {
1604
+ process.env[PROJECT_ENV] = value;
1605
+ });
1606
+ return program2;
1607
+ }
1608
+ function projectOption(description, opts = {}) {
1609
+ const option = new Option("--project <slug>", description).env(PROJECT_ENV);
1610
+ if (opts.mandatory) option.makeOptionMandatory();
1611
+ return option;
1612
+ }
1613
+ function orgOption(description) {
1614
+ return new Option("--org <idOrSlug>", description).env(ORG_ENV);
1615
+ }
1616
+
1585
1617
  // src/update.ts
1586
1618
  import { spawn as spawn2, spawnSync } from "child_process";
1587
1619
  import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
@@ -1796,14 +1828,9 @@ function compactEvalRunAttribution(run) {
1796
1828
  ].filter((part) => part !== void 0);
1797
1829
  return [run.triggered_by, ...revisions].join(" ");
1798
1830
  }
1799
- var program = new Command();
1800
- program.name("erdo").description("Erdo CLI").version(pkg.version).option("--org <idOrSlug>", "org to target for this command (overrides the active org)").option("--project <uuid>", "project context for this command (must belong to the active org)");
1801
- program.hook("preAction", (thisCommand) => {
1802
- const opts = thisCommand.opts();
1803
- const org2 = opts.org;
1804
- if (org2) process.env.ERDO_ORG = org2;
1805
- if (opts.project) process.env.ERDO_PROJECT = opts.project;
1806
- });
1831
+ var program = new Command2();
1832
+ program.name("erdo").description("Erdo CLI").version(pkg.version);
1833
+ bindScopeFlags(program);
1807
1834
  async function doLogin(opts) {
1808
1835
  let token;
1809
1836
  let me;
@@ -2170,7 +2197,9 @@ This key operates every org you manage; target one with X-Organization-ID (or \`
2170
2197
  }
2171
2198
  });
2172
2199
  var tokenCmd = program.command("token").description("Manage your API tokens (account-level credentials)");
2173
- tokenCmd.command("create").description("Mint a new API token; the secret is printed ONCE").requiredOption("--name <name>", "a label for the token").option("--expires-days <n>", "days until expiry (default: 30)", (v) => parseInt(v, 10)).option("--org <idOrSlug>", "the token's default org (defaults to your active org; must be one you belong to)").action(async (opts) => {
2200
+ tokenCmd.command("create").description("Mint a new API token; the secret is printed ONCE").requiredOption("--name <name>", "a label for the token").option("--expires-days <n>", "days until expiry (default: 30)", (v) => parseInt(v, 10)).addOption(
2201
+ orgOption("the token's default org (defaults to your active org; must be one you belong to)")
2202
+ ).action(async (opts) => {
2174
2203
  try {
2175
2204
  const res = await new ErdoClient().createToken({
2176
2205
  name: opts.name,
@@ -2272,7 +2301,7 @@ wsCmd.command("get <slug>").description("Show a workstream \u2014 status, phases
2272
2301
  fail(e);
2273
2302
  }
2274
2303
  });
2275
- wsCmd.command("create").description("Create a workstream in a project").requiredOption("--project <slug>", "project slug").requiredOption("--slug <slug>", "workstream slug (unique per org)").requiredOption("--title <title>", "human-readable title").option("--description <text>", "free-form description").option(
2304
+ wsCmd.command("create").description("Create a workstream in a project").addOption(projectOption("project slug", { mandatory: true })).requiredOption("--slug <slug>", "workstream slug (unique per org)").requiredOption("--title <title>", "human-readable title").option("--description <text>", "free-form description").option(
2276
2305
  "--token-budget <millicents>",
2277
2306
  "spend ceiling in millicents \u2014 100,000 per dollar, so 10000000 = $100 and 25000000 = $250; omit for unlimited",
2278
2307
  (v) => parseInt(v, 10)
@@ -2566,7 +2595,7 @@ expCmd.command("policy <slug>").description(
2566
2595
  fail(e);
2567
2596
  }
2568
2597
  });
2569
- expCmd.command("create").description("Create an experiment with its variants, evidence datasets and decision rule").requiredOption("--project <slug>", "project slug").requiredOption("--slug <slug>", "experiment slug (unique per org)").requiredOption("--title <title>", "title").option("--workstream <slug>", "host workstream").option("--scope <scope>", "local (default), platform_canary, platform_global").option("--hypothesis <text>", "hypothesis markdown").option("--primary-metric <metric>", "the metric the decision rule reads").option("--decision-rule <text>", "the rule that decides ship/stop from the primary metric").option("--guardrail <metric>", "a metric that must not regress (repeatable)", collect, []).option(
2598
+ expCmd.command("create").description("Create an experiment with its variants, evidence datasets and decision rule").addOption(projectOption("project slug", { mandatory: true })).requiredOption("--slug <slug>", "experiment slug (unique per org)").requiredOption("--title <title>", "title").option("--workstream <slug>", "host workstream").option("--scope <scope>", "local (default), platform_canary, platform_global").option("--hypothesis <text>", "hypothesis markdown").option("--primary-metric <metric>", "the metric the decision rule reads").option("--decision-rule <text>", "the rule that decides ship/stop from the primary metric").option("--guardrail <metric>", "a metric that must not regress (repeatable)", collect, []).option(
2570
2599
  "--variant <spec>",
2571
2600
  `a variant "key=b,label=3-field form,page=<artifact-id>,alloc=50,class=form_length_change,control" (repeatable). Fields: key=, label=, page= (Erdo artifact id) OR url=<https://\u2026> (an external page the persona panel visits, e.g. a client's live landing page \u2014 mutually exclusive with page=), alloc=, class=, and the bare flag control (or control=true) to mark the baseline. class= declares the structural change vs control for cross-experiment learning: headline_change | hero_media_change | cta_change | form_length_change | social_proof_add | layout_reorder | offer_change | full_page_rebuild`,
2572
2601
  collect,
@@ -2704,7 +2733,7 @@ expCmd.command("set-status <slug> <status>").description("Set an experiment's st
2704
2733
  });
2705
2734
  expCmd.command("calibration").description(
2706
2735
  "Read each judge's track record \u2014 paired predictions and pairwise agreement vs measured reality (the trust dial)"
2707
- ).option("--project <slug>", "limit to one project (default: every project you can view)").option("--judge <slug>", "show only judges whose slug contains this text").option("-n, --limit <n>", "max ledger rows scanned (default 5000)", (v) => parseInt(v, 10)).action(async (opts) => {
2736
+ ).addOption(projectOption("limit to one project (default: every project you can view)")).option("--judge <slug>", "show only judges whose slug contains this text").option("-n, --limit <n>", "max ledger rows scanned (default 5000)", (v) => parseInt(v, 10)).action(async (opts) => {
2708
2737
  try {
2709
2738
  const res = await new ErdoClient().judgeCalibration({
2710
2739
  project_slug: opts.project,
@@ -4954,17 +4983,28 @@ datasetsCmd.command("list").description("List datasets").option(
4954
4983
  });
4955
4984
  datasetsCmd.command("purposes").description(
4956
4985
  "List the org's dataset purposes \u2014 the vocabulary of dataset roles in use (e.g. leads, page events). The established_by slug is the canonical dataset for that role: write there rather than creating a sibling (one purpose = one dataset per org)."
4957
- ).action(async () => {
4986
+ ).option(
4987
+ "--scope <own|managed>",
4988
+ "whose vocabulary to read: own (default) or managed, which reads every client organization a manager account operates and prints the organization alongside each purpose"
4989
+ ).action(async (opts) => {
4958
4990
  try {
4959
- const { purposes } = await new ErdoClient().listDatasetPurposes();
4991
+ const scope = opts.scope?.toLowerCase();
4992
+ if (scope && scope !== "own" && scope !== "managed") {
4993
+ fail(new Error("--scope must be own or managed"));
4994
+ return;
4995
+ }
4996
+ const { purposes, message } = await new ErdoClient().listDatasetPurposes({ scope });
4960
4997
  if (purposes.length === 0) {
4961
- console.error("(no dataset purposes established yet)");
4998
+ console.error(message ? `(${message})` : "(no dataset purposes established yet)");
4962
4999
  return;
4963
5000
  }
4964
- for (const p of purposes)
5001
+ for (const p of purposes) {
5002
+ const target = p.established_by_dataset_slug ?? p.established_by_dataset_id;
5003
+ const count = `${p.dataset_count} dataset${p.dataset_count === 1 ? "" : "s"}`;
4965
5004
  console.log(
4966
- `${p.purpose} ${p.established_by_dataset_slug ?? p.established_by_dataset_id} ${p.dataset_count} dataset${p.dataset_count === 1 ? "" : "s"} ${p.description}`
5005
+ scope === "managed" ? `${p.organization_slug ?? "-"} ${p.purpose} ${target} ${count} ${p.description}` : `${p.purpose} ${target} ${count} ${p.description}`
4967
5006
  );
5007
+ }
4968
5008
  } catch (e) {
4969
5009
  fail(e);
4970
5010
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.76.0",
3
+ "version": "0.78.0",
4
4
  "description": "Erdo CLI \u2014 drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {