@erdoai/cli 0.100.0 → 0.101.1

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 +43 -10
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1072,22 +1072,27 @@ 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
- // `clear_purpose` to remove it; moving a purpose is clearing it from the old
1088
- // dataset and setting it on the right one. Setting a purpose the org already
1089
- // uses elsewhere succeeds and answers with a warning naming what it shadows,
1090
- // since one purpose is meant to map to one dataset.
1090
+ // `clear_purpose` to remove it. Setting a purpose another dataset carries is
1091
+ // an atomic MOVE — the old carrier is cleared in the same transaction — and
1092
+ // the response warns naming the dataset it moved from. Pass
1093
+ // `purpose_sibling: true` only to declare a deliberate parallel carrier (a
1094
+ // separate program a human chose to run), which leaves the other carriers
1095
+ // alone.
1091
1096
  setDatasetPurpose(slug, input) {
1092
1097
  return this.request("POST", `/v1/datasets/${encodeURIComponent(slug)}/schema`, input);
1093
1098
  }
@@ -6097,7 +6102,7 @@ datasetsCmd.command("list").description("List datasets").option(
6097
6102
  }
6098
6103
  });
6099
6104
  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)."
6105
+ "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
6106
  ).option(
6102
6107
  "--scope <own|managed>",
6103
6108
  "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 +6119,7 @@ datasetsCmd.command("purposes").description(
6114
6119
  return;
6115
6120
  }
6116
6121
  for (const p of purposes) {
6117
- const target = p.established_by_dataset_slug ?? p.established_by_dataset_id;
6122
+ const target = p.dataset_count > 1 ? `(split: ${p.dataset_count} datasets)` : p.dataset_slug ?? p.dataset_id ?? "(unbound)";
6118
6123
  const count = `${p.dataset_count} dataset${p.dataset_count === 1 ? "" : "s"}`;
6119
6124
  console.log(
6120
6125
  scope === "managed" ? `${p.organization_slug ?? "-"} ${p.purpose} ${target} ${count} ${p.description}` : `${p.purpose} ${target} ${count} ${p.description}`
@@ -6124,9 +6129,36 @@ datasetsCmd.command("purposes").description(
6124
6129
  fail(e);
6125
6130
  }
6126
6131
  });
6132
+ datasetsCmd.command("ensure <purpose>").description(
6133
+ "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."
6134
+ ).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) => {
6135
+ try {
6136
+ const parsed = JSON.parse(readFileSync4(opts.schema, "utf8"));
6137
+ const declaredSchema = Array.isArray(parsed) ? { columns: parsed } : parsed;
6138
+ if (!declaredSchema || !Array.isArray(declaredSchema.columns)) {
6139
+ throw new Error('--schema must contain a JSON columns array or {"columns": [...]} object');
6140
+ }
6141
+ const result = await new ErdoClient().ensureDataset({
6142
+ purpose,
6143
+ ...opts.name ? { name: opts.name } : {},
6144
+ ...opts.description ? { description: opts.description } : {},
6145
+ ...opts.purposeDescription ? { purpose_description: opts.purposeDescription } : {},
6146
+ ...opts.preferredDataset ? { preferred_dataset: opts.preferredDataset } : {},
6147
+ ...opts.emailColumn ? { email_column: opts.emailColumn } : {},
6148
+ ...opts.phoneColumn ? { phone_column: opts.phoneColumn } : {},
6149
+ declared_schema: declaredSchema
6150
+ });
6151
+ console.log(`${result.slug} ${result.created ? "created" : "reused"} ${result.purpose}`);
6152
+ } catch (e) {
6153
+ fail(e);
6154
+ }
6155
+ });
6127
6156
  datasetsCmd.command("set-purpose <slug> [purpose]").description(
6128
- "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."
6157
+ "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. Setting a purpose another dataset carries is an atomic MOVE: the old carrier is cleared in the same transaction, so one call on the right dataset lands it (the response says which dataset it moved from). Use --sibling only when deliberately running a parallel table for the same purpose."
6129
6158
  ).option("--clear", "remove the dataset's purpose instead of setting one").option(
6159
+ "--sibling",
6160
+ "declare this dataset a deliberate parallel carrier of the purpose (a separate program a human chose to run) instead of moving the purpose off other datasets"
6161
+ ).option(
6130
6162
  "--description <text>",
6131
6163
  "description for a purpose new to this org; ignored when the org already has an entry for it"
6132
6164
  ).action(async (slug, purpose, opts) => {
@@ -6142,6 +6174,7 @@ datasetsCmd.command("set-purpose <slug> [purpose]").description(
6142
6174
  const res = await new ErdoClient().setDatasetPurpose(slug, {
6143
6175
  ...purpose ? { purpose } : {},
6144
6176
  ...opts.clear ? { clear_purpose: true } : {},
6177
+ ...opts.sibling ? { purpose_sibling: true } : {},
6145
6178
  ...opts.description ? { purpose_description: opts.description } : {}
6146
6179
  });
6147
6180
  for (const w of res.warnings ?? []) console.error(`warning: ${w}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erdoai/cli",
3
- "version": "0.100.0",
3
+ "version": "0.101.1",
4
4
  "description": "Erdo CLI — drive datasets, pages, and evals from the terminal or CI",
5
5
  "type": "module",
6
6
  "bin": {