@erdoai/cli 0.99.0 → 0.101.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 +48 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1072,17 +1072,20 @@ var ErdoClient = class {
1072
1072
  const qs = q.toString();
1073
1073
  return this.request("GET", `/v1/datasets${qs ? `?${qs}` : ""}`);
1074
1074
  }
1075
- // The org's dataset vocabulary: every purpose in use, its description, and
1076
- // the dataset that established it (the canonical write target for that role).
1075
+ // The org's dataset vocabulary: every purpose in use, its current dataset,
1076
+ // and the declared producer contract on that same dataset.
1077
1077
  // scope="managed" reads the same across every client organization a manager
1078
1078
  // account operates; each entry then names its organization and its
1079
- // established_by slug is org-qualified, so it can be queried as-is.
1079
+ // current dataset slug is org-qualified, so it can be queried as-is.
1080
1080
  listDatasetPurposes(params) {
1081
1081
  const q = new URLSearchParams();
1082
1082
  if (params?.scope) q.set("scope", params.scope);
1083
1083
  const qs = q.toString();
1084
1084
  return this.request("GET", `/v1/datasets-purposes${qs ? `?${qs}` : ""}`);
1085
1085
  }
1086
+ ensureDataset(input) {
1087
+ return this.request("POST", "/v1/datasets-ensure", input);
1088
+ }
1086
1089
  // Correct which dataset carries a purpose. Pass `purpose` to set one,
1087
1090
  // `clear_purpose` to remove it; moving a purpose is clearing it from the old
1088
1091
  // dataset and setting it on the right one. Setting a purpose the org already
@@ -4132,7 +4135,7 @@ function renderDecision(detail) {
4132
4135
  ["class", d.decision_class],
4133
4136
  ["source", d.source],
4134
4137
  ["applies", d.applicability === "standing" ? "standing (until superseded)" : "one-off authorization"],
4135
- ["decided by", d.decider_kind]
4138
+ ["decided by", d.decided_by_name ? `${d.decided_by_name} (${d.decider_kind})` : d.decider_kind]
4136
4139
  ];
4137
4140
  if (d.subject_label) facts.push(["subject", d.subject_label]);
4138
4141
  if (d.execution_status) facts.push(["execution", d.execution_status]);
@@ -4545,7 +4548,7 @@ function renderAttentionItemReport(item, report) {
4545
4548
  console.log(` ${d.slug} \u2014 ${d.what}`);
4546
4549
  if (d.why) console.log(` why: ${d.why}`);
4547
4550
  if (d.alternatives_considered) console.log(` alternatives considered: ${d.alternatives_considered}`);
4548
- console.log(` decided by: ${d.decider_kind}`);
4551
+ console.log(` decided by: ${d.decided_by_name ? `${d.decided_by_name} (${d.decider_kind})` : d.decider_kind}`);
4549
4552
  console.log(` status: ${d.status}`);
4550
4553
  if (d.outcome_label) console.log(` outcome: ${d.outcome_label}`);
4551
4554
  } else {
@@ -6097,7 +6100,7 @@ datasetsCmd.command("list").description("List datasets").option(
6097
6100
  }
6098
6101
  });
6099
6102
  datasetsCmd.command("purposes").description(
6100
- "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)."
6103
+ "List the org's dataset purposes \u2014 the vocabulary of dataset roles in use (e.g. leads, page events). Each purpose resolves to the current dataset and its declared schema: write there rather than creating a sibling (one purpose = one dataset per org)."
6101
6104
  ).option(
6102
6105
  "--scope <own|managed>",
6103
6106
  "whose vocabulary to read: own (default) or managed, which reads every client organization a manager account operates and prints the organization alongside each purpose"
@@ -6114,7 +6117,7 @@ datasetsCmd.command("purposes").description(
6114
6117
  return;
6115
6118
  }
6116
6119
  for (const p of purposes) {
6117
- const target = p.established_by_dataset_slug ?? p.established_by_dataset_id;
6120
+ const target = p.dataset_count > 1 ? `(split: ${p.dataset_count} datasets)` : p.dataset_slug ?? p.dataset_id ?? "(unbound)";
6118
6121
  const count = `${p.dataset_count} dataset${p.dataset_count === 1 ? "" : "s"}`;
6119
6122
  console.log(
6120
6123
  scope === "managed" ? `${p.organization_slug ?? "-"} ${p.purpose} ${target} ${count} ${p.description}` : `${p.purpose} ${target} ${count} ${p.description}`
@@ -6124,6 +6127,30 @@ datasetsCmd.command("purposes").description(
6124
6127
  fail(e);
6125
6128
  }
6126
6129
  });
6130
+ datasetsCmd.command("ensure <purpose>").description(
6131
+ "Create or reuse the one dataset for a purpose and verify its declared producer contract. Refuses legacy splits and incompatible populated datasets instead of choosing silently."
6132
+ ).requiredOption("--schema <file>", "JSON file containing { columns: [...] } or a columns array").option("--name <name>", "dataset name when creation is required").option("--description <text>", "dataset description when creation is required").option("--purpose-description <text>", "what writes and reads this purpose").option("--preferred-dataset <slug>", "existing bound dataset slug or id to adopt").option("--email-column <name>", "for a leads purpose: which declared column holds the email (default email)").option("--phone-column <name>", "for a leads purpose: which declared column holds the phone (default phone)").action(async (purpose, opts) => {
6133
+ try {
6134
+ const parsed = JSON.parse(readFileSync4(opts.schema, "utf8"));
6135
+ const declaredSchema = Array.isArray(parsed) ? { columns: parsed } : parsed;
6136
+ if (!declaredSchema || !Array.isArray(declaredSchema.columns)) {
6137
+ throw new Error('--schema must contain a JSON columns array or {"columns": [...]} object');
6138
+ }
6139
+ const result = await new ErdoClient().ensureDataset({
6140
+ purpose,
6141
+ ...opts.name ? { name: opts.name } : {},
6142
+ ...opts.description ? { description: opts.description } : {},
6143
+ ...opts.purposeDescription ? { purpose_description: opts.purposeDescription } : {},
6144
+ ...opts.preferredDataset ? { preferred_dataset: opts.preferredDataset } : {},
6145
+ ...opts.emailColumn ? { email_column: opts.emailColumn } : {},
6146
+ ...opts.phoneColumn ? { phone_column: opts.phoneColumn } : {},
6147
+ declared_schema: declaredSchema
6148
+ });
6149
+ console.log(`${result.slug} ${result.created ? "created" : "reused"} ${result.purpose}`);
6150
+ } catch (e) {
6151
+ fail(e);
6152
+ }
6153
+ });
6127
6154
  datasetsCmd.command("set-purpose <slug> [purpose]").description(
6128
6155
  "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."
6129
6156
  ).option("--clear", "remove the dataset's purpose instead of setting one").option(
@@ -7432,7 +7459,7 @@ pipelinesCmd.command("get <slug>").description("Show one event pipeline \u2014 s
7432
7459
  });
7433
7460
  pipelinesCmd.command("update <slug>").description(
7434
7461
  "Update an event pipeline's editable fields \u2014 omitted fields keep their current values (merge). Use get first for the current transform/actions; --transform-js/--pipeline accept @file"
7435
- ).option("--name <name>", "new name").option("--description <description>", "new description").option("--state <state>", "new state: active, disabled, or proposed").option("--transform-js <jsOr@file>", "new transform \u2014 function transform(event) { \u2026 } (or @path to a file)").option(
7462
+ ).option("--name <name>", "new name").option("--description <description>", "new description").option("--state <state>", "new state: active, disabled, or proposed").option("--transform-js <jsOr@file>", "new transform \u2014 function transform(event) { \u2026 } (or @path to a file)").option("--turnstile <jsonOr@file>", `optional managed form protection settings; use @file for widget credentials, or '{"enabled":false}' to disable`).option(
7436
7463
  "--pipeline <jsonOr@file>",
7437
7464
  `replacement ordered actions array as JSON (or @path to a file), e.g. '[{"type":"dataset.write","key_column":"email","dataset_slug":"acme-leads"}]'`
7438
7465
  ).action(
@@ -7441,6 +7468,18 @@ pipelinesCmd.command("update <slug>").description(
7441
7468
  if (opts.name !== void 0) body.name = opts.name;
7442
7469
  if (opts.description !== void 0) body.description = opts.description;
7443
7470
  if (opts.state !== void 0) body.state = opts.state;
7471
+ const turnstileRaw = readMaybeFile(opts.turnstile);
7472
+ if (turnstileRaw !== void 0) {
7473
+ try {
7474
+ const protection = JSON.parse(turnstileRaw);
7475
+ if (!protection || Array.isArray(protection) || typeof protection !== "object" || typeof protection.enabled !== "boolean") {
7476
+ throw new Error("invalid settings");
7477
+ }
7478
+ body.turnstile = protection;
7479
+ } catch {
7480
+ fail(new Error("--turnstile must be a JSON object with an enabled boolean"));
7481
+ }
7482
+ }
7444
7483
  const transformJs = readMaybeFile(opts.transformJs);
7445
7484
  if (transformJs !== void 0) body.transform_js = transformJs;
7446
7485
  const pipelineRaw = readMaybeFile(opts.pipeline);
@@ -7456,7 +7495,7 @@ pipelinesCmd.command("update <slug>").description(
7456
7495
  if (Object.keys(body).length === 0) {
7457
7496
  fail(
7458
7497
  new Error(
7459
- "nothing to update \u2014 pass at least one of --name, --description, --state, --transform-js, --pipeline"
7498
+ "nothing to update \u2014 pass at least one of --name, --description, --state, --transform-js, --pipeline, --turnstile"
7460
7499
  )
7461
7500
  );
7462
7501
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.99.0",
3
+ "version": "0.101.0",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {