@wayai/cli 0.3.135 → 0.3.137

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 CHANGED
@@ -236,7 +236,7 @@ var init_mask_secrets = __esm({
236
236
  "use strict";
237
237
  CREDENTIAL_KEY_RE = /token|secret|credential|password|passwd|api[_-]?key|authorization|private[_-]?key|session[_-]?id|cookie|jwt/i;
238
238
  NUMERIC_CREDENTIAL_KEY_RE = /(?:password|passwd|pin|token|secret|credential|api[_-]?key|authorization|private[_-]?key|session[_-]?id|cookie|jwt)$/i;
239
- FULLY_MASKED_VALUE_RE = /^(?:(?:Bearer|Basic)\s+)?(?:way_|wst_)?\[REDACTED(?:_JWT|_BASE64)?\]$/;
239
+ FULLY_MASKED_VALUE_RE = /^(?:(?:Bearer|Basic)\s+)?(?:way_|wst_|rec_)?\[REDACTED(?:_JWT|_BASE64)?\]$/;
240
240
  isCredentialKey = (key) => CREDENTIAL_KEY_RE.test(key);
241
241
  isBase64UrlChar = (char) => {
242
242
  const code = char.charCodeAt(0);
@@ -249,6 +249,12 @@ var init_mask_secrets = __esm({
249
249
  [/\bway_[A-Za-z0-9_-]{8,}/g, "way_[REDACTED]"],
250
250
  // wst_ single-use WebSocket tickets (same opaque base64url format).
251
251
  [/\bwst_[A-Za-z0-9_-]{8,}/g, "wst_[REDACTED]"],
252
+ // rec_ Data-surface API tokens. Same opaque base64url shape, and needed for
253
+ // the same reason as way_: the credential-KEY rules below only fire when the
254
+ // token sits behind a recognisable key, so a `rec_` token that appears bare in
255
+ // an error body's prose would otherwise pass through unmasked. Unpadded
256
+ // base64url, so the trailing base64 rule does not cover it either.
257
+ [/\brec_[A-Za-z0-9_-]{8,}/g, "rec_[REDACTED]"],
252
258
  // Stripe API keys and webhook signing secrets. Keep this aligned with
253
259
  // packages/core/src/redact.ts; both modules protect observability sinks.
254
260
  [
@@ -7546,6 +7552,20 @@ var init_contracts = __esm({
7546
7552
  production_bases: external_exports.boolean(),
7547
7553
  /** Opaque to Rekor; compared for equality only, to reset `usage:` counters (§2.3). */
7548
7554
  window_key: external_exports.string().min(1),
7555
+ /**
7556
+ * The window's TRUE lower bound, for the reconcile's `operations_log` sum (§2.2).
7557
+ *
7558
+ * OPTIONAL, and that is not a rollout convenience — it is what "never gates" means
7559
+ * on the writer's side. The tier sources can be absent (a paid row carrying
7560
+ * `current_period_end` with no `current_period_start`) and inverted pairs are a
7561
+ * §2.2 diagnostic, so requiring it here would let an inert field throw the strict
7562
+ * parse and leave the org with NO projection — permanently stale under §2.3, with
7563
+ * §2.5's fail-closed gates refusing base creation and 503-ing every import. The
7564
+ * writer emits it whenever it has a sound value and omits it otherwise; Rekor's
7565
+ * observed-flip fallback covers the gap, and rollout step 3 keeps that fallback
7566
+ * for exactly this reason.
7567
+ */
7568
+ window_start: external_exports.string().datetime().optional(),
7549
7569
  /** The sole staleness authority (§2.3). Never triggers a counter reset. */
7550
7570
  window_resets_at: external_exports.string().datetime()
7551
7571
  }).strict();
@@ -8700,7 +8720,8 @@ __export(api_client_exports, {
8700
8720
  ApiClient: () => ApiClient,
8701
8721
  ApiError: () => ApiError,
8702
8722
  dataErrorMessage: () => dataErrorMessage,
8703
- toDataProxyPath: () => toDataProxyPath
8723
+ toDataProxyPath: () => toDataProxyPath,
8724
+ withOrgSelector: () => withOrgSelector
8704
8725
  });
8705
8726
  function isRetryableErrorBody(body) {
8706
8727
  try {
@@ -8716,6 +8737,11 @@ function toDataProxyPath(v1Path) {
8716
8737
  }
8717
8738
  return `${DATA_PROXY_PREFIX}${v1Path.slice(REKOR_V1_PREFIX.length)}`;
8718
8739
  }
8740
+ function withOrgSelector(path31, orgId) {
8741
+ if (!orgId) return path31;
8742
+ const separator = path31.includes("?") ? "&" : "?";
8743
+ return `${path31}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
8744
+ }
8719
8745
  function dataErrorMessage(err) {
8720
8746
  if (!(err instanceof ApiError)) return null;
8721
8747
  let parsed;
@@ -9328,12 +9354,12 @@ var init_api_client = __esm({
9328
9354
  `/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
9329
9355
  );
9330
9356
  }
9331
- async request(method, path31, body) {
9357
+ async request(method, path31, body, extraHeaders, contentType) {
9332
9358
  addApiBreadcrumb(method, path31);
9333
9359
  const url = `${this.apiUrl}${path31}`;
9334
9360
  let refreshedOn401 = false;
9335
9361
  for (let retry = 0; ; retry++) {
9336
- let response = await this.send(url, method, body);
9362
+ let response = await this.send(url, method, body, extraHeaders, contentType);
9337
9363
  if (response.status === 401 && this.onUnauthorized && !refreshedOn401) {
9338
9364
  refreshedOn401 = true;
9339
9365
  let refreshed;
@@ -9343,7 +9369,7 @@ var init_api_client = __esm({
9343
9369
  }
9344
9370
  if (refreshed) {
9345
9371
  this.accessToken = refreshed;
9346
- response = await this.send(url, method, body);
9372
+ response = await this.send(url, method, body, extraHeaders, contentType);
9347
9373
  }
9348
9374
  }
9349
9375
  if (response.ok) {
@@ -9384,30 +9410,98 @@ var init_api_client = __esm({
9384
9410
  * since it is WayAI's selector and not an input Rekor sees.
9385
9411
  */
9386
9412
  async dataRequest(method, v1Path, body, orgId) {
9387
- const separator = v1Path.includes("?") ? "&" : "?";
9388
- const orgQuery = orgId ? `${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}` : "";
9389
9413
  const envelope = await this.request(
9390
9414
  method,
9391
- `${toDataProxyPath(v1Path)}${orgQuery}`,
9415
+ withOrgSelector(toDataProxyPath(v1Path), orgId),
9392
9416
  body
9393
9417
  );
9394
9418
  return envelope ?? {};
9395
9419
  }
9420
+ /**
9421
+ * Upload raw bytes to a Data `/v1` path — file content and record
9422
+ * attachments, the two places that surface takes a body which is not JSON.
9423
+ * `extraHeaders` carries the surface's own metadata headers, which are
9424
+ * caller-supplied and so cannot displace the org selector or anything `send`
9425
+ * pins.
9426
+ */
9427
+ async dataUpload(v1Path, bytes, contentType, orgId, extraHeaders) {
9428
+ const envelope = await this.request(
9429
+ "PUT",
9430
+ withOrgSelector(toDataProxyPath(v1Path), orgId),
9431
+ bytes,
9432
+ extraHeaders,
9433
+ // NOT a header on the map above: `send` pins the content type out of
9434
+ // caller reach, and takes it here so it always describes the real body.
9435
+ contentType
9436
+ );
9437
+ return envelope ?? {};
9438
+ }
9439
+ /**
9440
+ * Download a Data `/v1` path's raw bytes. Distinct from `dataRequest`, which
9441
+ * parses the JSON envelope: the response here is the stored blob itself, so
9442
+ * this reads `arrayBuffer()`. Single-refresh-on-401 like `request`, but no
9443
+ * transient-retry loop — re-streaming a blob is not worth a cold-start probe.
9444
+ */
9445
+ async dataDownload(v1Path, orgId) {
9446
+ const path31 = withOrgSelector(toDataProxyPath(v1Path), orgId);
9447
+ addApiBreadcrumb("GET", path31);
9448
+ const url = `${this.apiUrl}${path31}`;
9449
+ let response = await this.send(url, "GET");
9450
+ if (response.status === 401 && this.onUnauthorized) {
9451
+ let refreshed;
9452
+ try {
9453
+ refreshed = await this.onUnauthorized();
9454
+ } catch {
9455
+ }
9456
+ if (refreshed) {
9457
+ this.accessToken = refreshed;
9458
+ response = await this.send(url, "GET");
9459
+ }
9460
+ }
9461
+ if (!response.ok) {
9462
+ throw new ApiError("GET", path31, response.status, await response.text());
9463
+ }
9464
+ return {
9465
+ bytes: new Uint8Array(await response.arrayBuffer()),
9466
+ contentType: response.headers.get("Content-Type") ?? "application/octet-stream"
9467
+ };
9468
+ }
9469
+ /**
9470
+ * The absolute URL of a Data `/v1` path, for handing the user a link they can
9471
+ * fetch themselves. Only the address — every request the CLI issues goes
9472
+ * through the methods above, which carry the session.
9473
+ */
9474
+ dataUrl(v1Path, orgId) {
9475
+ return `${this.apiUrl}${withOrgSelector(toDataProxyPath(v1Path), orgId)}`;
9476
+ }
9396
9477
  /** Issue a single authenticated request with the client's current token. */
9397
- send(url, method, body) {
9478
+ send(url, method, body, extraHeaders, contentType) {
9398
9479
  return fetch(url, {
9399
9480
  method,
9400
9481
  headers: {
9482
+ // Caller-supplied FIRST, so everything pinned below is out of their
9483
+ // reach: a metadata header cannot restate the credential or mislabel
9484
+ // the body.
9485
+ ...extraHeaders,
9401
9486
  Authorization: `Bearer ${this.accessToken}`,
9402
- "Content-Type": "application/json",
9403
- // Names the calling process, on every request. A HINT, not an
9487
+ // The content type describes THIS body, so it is derived from the body
9488
+ // rather than accepted from `extraHeaders` — which is pinned out above,
9489
+ // and would otherwise be the one way to mislabel a JSON request.
9490
+ // `||`, not `??`: an empty content type is not a content type, and an
9491
+ // unlabelled body should fall back rather than announce nothing.
9492
+ "Content-Type": body instanceof Uint8Array ? contentType || "application/octet-stream" : "application/json",
9493
+ // Names the calling surface for the Data Worker's audit rows, which
9494
+ // record `cli` vs `api` vs `mcp` (contracts §1.4). A HINT, not an
9404
9495
  // authorization input: any client can send it, so the backend must
9405
9496
  // normalize it against what it knows about its own ingress rather than
9406
9497
  // trust it. Nothing is granted or denied on its value; the Data Worker's
9407
9498
  // audit rows are its consumer (contracts §1.4).
9408
9499
  "X-WayAI-Client": "cli"
9409
9500
  },
9410
- body: body ? JSON.stringify(body) : void 0
9501
+ // Bytes go on the wire verbatim (file content, attachments); everything
9502
+ // else is JSON. Encoding a Uint8Array through JSON.stringify would send
9503
+ // `{"0":137,…}` — a corrupt upload rather than a failed one.
9504
+ body: !body ? void 0 : body instanceof Uint8Array ? body : JSON.stringify(body)
9411
9505
  });
9412
9506
  }
9413
9507
  };
@@ -12849,6 +12943,20 @@ var init_dist = __esm({
12849
12943
  production_bases: external_exports.boolean(),
12850
12944
  /** Opaque to Rekor; compared for equality only, to reset `usage:` counters (§2.3). */
12851
12945
  window_key: external_exports.string().min(1),
12946
+ /**
12947
+ * The window's TRUE lower bound, for the reconcile's `operations_log` sum (§2.2).
12948
+ *
12949
+ * OPTIONAL, and that is not a rollout convenience — it is what "never gates" means
12950
+ * on the writer's side. The tier sources can be absent (a paid row carrying
12951
+ * `current_period_end` with no `current_period_start`) and inverted pairs are a
12952
+ * §2.2 diagnostic, so requiring it here would let an inert field throw the strict
12953
+ * parse and leave the org with NO projection — permanently stale under §2.3, with
12954
+ * §2.5's fail-closed gates refusing base creation and 503-ing every import. The
12955
+ * writer emits it whenever it has a sound value and omits it otherwise; Rekor's
12956
+ * observed-flip fallback covers the gap, and rollout step 3 keeps that fallback
12957
+ * for exactly this reason.
12958
+ */
12959
+ window_start: external_exports.string().datetime().optional(),
12852
12960
  /** The sole staleness authority (§2.3). Never triggers a counter reset. */
12853
12961
  window_resets_at: external_exports.string().datetime()
12854
12962
  }).strict();
@@ -13979,6 +14087,18 @@ var init_dist = __esm({
13979
14087
  }
13980
14088
  });
13981
14089
 
14090
+ // src/lib/expected.ts
14091
+ function expected(message) {
14092
+ const err = new Error(message);
14093
+ err.isExpected = true;
14094
+ return err;
14095
+ }
14096
+ var init_expected = __esm({
14097
+ "src/lib/expected.ts"() {
14098
+ "use strict";
14099
+ }
14100
+ });
14101
+
13982
14102
  // src/lib/layout.ts
13983
14103
  import * as fs from "fs";
13984
14104
  import * as path from "path";
@@ -14700,11 +14820,31 @@ function isNewerVersion(latest, current) {
14700
14820
  }
14701
14821
  return false;
14702
14822
  }
14823
+ function resolveSecretSource(source) {
14824
+ if (source.stdin && source.prompt) {
14825
+ throw expected(`${source.stdinFlag} and ${source.promptHint} are mutually exclusive \u2014 pass one.`);
14826
+ }
14827
+ if (source.stdin) {
14828
+ if (process.stdin.isTTY) {
14829
+ throw expected(
14830
+ `${source.stdinFlag} needs piped input. Pipe it \u2014 printf %s "$SECRET" | wayai \u2026 ${source.stdinFlag} \u2014 or ${source.promptHint}.`
14831
+ );
14832
+ }
14833
+ return "stdin";
14834
+ }
14835
+ if (!process.stdin.isTTY) {
14836
+ throw expected(
14837
+ `A secret cannot be prompted for when stdin is not a terminal. Pipe it instead: printf %s "$SECRET" | wayai \u2026 ${source.stdinFlag}`
14838
+ );
14839
+ }
14840
+ return "prompt";
14841
+ }
14703
14842
  var UUID_RE2, slugify, OWN_HELP_COMMANDS;
14704
14843
  var init_utils = __esm({
14705
14844
  "src/lib/utils.ts"() {
14706
14845
  "use strict";
14707
14846
  init_dist();
14847
+ init_expected();
14708
14848
  UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
14709
14849
  slugify = slugifySkillName;
14710
14850
  OWN_HELP_COMMANDS = /* @__PURE__ */ new Set([
@@ -14789,7 +14929,7 @@ var init_registry = __esm({
14789
14929
  disposition: "top-level",
14790
14930
  surviving: "records",
14791
14931
  clause: 2,
14792
- shipped: false,
14932
+ shipped: true,
14793
14933
  reason: "Ontology entity. No WayAI command of this name."
14794
14934
  },
14795
14935
  {
@@ -14797,7 +14937,7 @@ var init_registry = __esm({
14797
14937
  disposition: "top-level",
14798
14938
  surviving: "record-types",
14799
14939
  clause: 2,
14800
- shipped: false,
14940
+ shipped: true,
14801
14941
  reason: "Ontology entity."
14802
14942
  },
14803
14943
  {
@@ -14805,7 +14945,7 @@ var init_registry = __esm({
14805
14945
  disposition: "top-level",
14806
14946
  surviving: "relationships",
14807
14947
  clause: 2,
14808
- shipped: false,
14948
+ shipped: true,
14809
14949
  reason: "Ontology entity."
14810
14950
  },
14811
14951
  {
@@ -14813,7 +14953,7 @@ var init_registry = __esm({
14813
14953
  disposition: "top-level",
14814
14954
  surviving: "relationship-types",
14815
14955
  clause: 2,
14816
- shipped: false,
14956
+ shipped: true,
14817
14957
  reason: "Ontology entity."
14818
14958
  },
14819
14959
  {
@@ -14821,7 +14961,7 @@ var init_registry = __esm({
14821
14961
  disposition: "top-level",
14822
14962
  surviving: "query-relationships",
14823
14963
  clause: 2,
14824
- shipped: false,
14964
+ shipped: true,
14825
14965
  reason: "Ontology traversal over relationships."
14826
14966
  },
14827
14967
  {
@@ -14829,7 +14969,7 @@ var init_registry = __esm({
14829
14969
  disposition: "top-level",
14830
14970
  surviving: "files",
14831
14971
  clause: 2,
14832
- shipped: false,
14972
+ shipped: true,
14833
14973
  reason: "Unambiguous: WayAI has no `files` command \u2014 hub-local resource files are reached through hub config-as-code, and the two file surfaces stay deliberately separate."
14834
14974
  },
14835
14975
  {
@@ -14837,7 +14977,7 @@ var init_registry = __esm({
14837
14977
  disposition: "top-level",
14838
14978
  surviving: "file-types",
14839
14979
  clause: 2,
14840
- shipped: false,
14980
+ shipped: true,
14841
14981
  reason: "Ontology entity."
14842
14982
  },
14843
14983
  {
@@ -14845,7 +14985,7 @@ var init_registry = __esm({
14845
14985
  disposition: "top-level",
14846
14986
  surviving: "attachments",
14847
14987
  clause: 2,
14848
- shipped: false,
14988
+ shipped: true,
14849
14989
  reason: "Ontology entity."
14850
14990
  },
14851
14991
  {
@@ -14853,7 +14993,7 @@ var init_registry = __esm({
14853
14993
  disposition: "top-level",
14854
14994
  surviving: "toolsets",
14855
14995
  clause: 2,
14856
- shipped: false,
14996
+ shipped: true,
14857
14997
  reason: "Ontology entity."
14858
14998
  },
14859
14999
  {
@@ -14861,7 +15001,7 @@ var init_registry = __esm({
14861
15001
  disposition: "top-level",
14862
15002
  surviving: "actions",
14863
15003
  clause: 2,
14864
- shipped: false,
15004
+ shipped: true,
14865
15005
  reason: "Ontology entity."
14866
15006
  },
14867
15007
  {
@@ -14869,7 +15009,7 @@ var init_registry = __esm({
14869
15009
  disposition: "top-level",
14870
15010
  surviving: "triggers",
14871
15011
  clause: 2,
14872
- shipped: false,
15012
+ shipped: true,
14873
15013
  reason: "Ontology entity."
14874
15014
  },
14875
15015
  {
@@ -14877,7 +15017,7 @@ var init_registry = __esm({
14877
15017
  disposition: "top-level",
14878
15018
  surviving: "inbound-webhooks",
14879
15019
  clause: 2,
14880
- shipped: false,
15020
+ shipped: true,
14881
15021
  reason: "Ontology entity."
14882
15022
  },
14883
15023
  {
@@ -14885,7 +15025,7 @@ var init_registry = __esm({
14885
15025
  disposition: "top-level",
14886
15026
  surviving: "seed",
14887
15027
  clause: 2,
14888
- shipped: false,
15028
+ shipped: true,
14889
15029
  reason: "Ontology entity. `wayai status` vs `wayai seed lease status` differ by depth, not by word."
14890
15030
  },
14891
15031
  // --- Clause 3: namespaced. ---
@@ -14918,7 +15058,7 @@ var init_registry = __esm({
14918
15058
  disposition: "namespaced",
14919
15059
  surviving: "bases tokens",
14920
15060
  clause: 3,
14921
- shipped: false,
15061
+ shipped: true,
14922
15062
  reason: "Mints base tokens; WayAI's own token surface mints platform tokens."
14923
15063
  },
14924
15064
  {
@@ -14926,7 +15066,7 @@ var init_registry = __esm({
14926
15066
  disposition: "namespaced",
14927
15067
  surviving: "bases secrets",
14928
15068
  clause: 3,
14929
- shipped: false,
15069
+ shipped: true,
14930
15070
  reason: "Base org vault; WayAI has connection credentials."
14931
15071
  },
14932
15072
  {
@@ -14934,7 +15074,7 @@ var init_registry = __esm({
14934
15074
  disposition: "namespaced",
14935
15075
  surviving: "bases sql",
14936
15076
  clause: 3,
14937
- shipped: false,
15077
+ shipped: true,
14938
15078
  reason: "WayAI already has tenant SQL over conversation analytics."
14939
15079
  },
14940
15080
  {
@@ -14942,7 +15082,7 @@ var init_registry = __esm({
14942
15082
  disposition: "namespaced",
14943
15083
  surviving: "bases import",
14944
15084
  clause: 3,
14945
- shipped: false,
15085
+ shipped: true,
14946
15086
  reason: "WayAI has outbound-contact import."
14947
15087
  },
14948
15088
  {
@@ -14950,7 +15090,7 @@ var init_registry = __esm({
14950
15090
  disposition: "namespaced",
14951
15091
  surviving: "bases batch",
14952
15092
  clause: 3,
14953
- shipped: false,
15093
+ shipped: true,
14954
15094
  reason: "Too generic to stand alone."
14955
15095
  },
14956
15096
  {
@@ -14958,7 +15098,7 @@ var init_registry = __esm({
14958
15098
  disposition: "namespaced",
14959
15099
  surviving: "bases providers",
14960
15100
  clause: 3,
14961
- shipped: false,
15101
+ shipped: true,
14962
15102
  reason: "WayAI's provider is a connector."
14963
15103
  },
14964
15104
  {
@@ -14967,14 +15107,14 @@ var init_registry = __esm({
14967
15107
  surviving: "admin bases",
14968
15108
  clause: 3,
14969
15109
  shipped: false,
14970
- reason: "Clause 3's group exception: `wayai admin` is an existing group with its own gating and subgroups, so the operator surface joins it as a peer rather than sitting under `bases`."
15110
+ reason: "Clause 3's group exception: `wayai admin` is an existing group with its own gating and subgroups, so the operator surface joins it as a peer rather than sitting under `bases`. UNSHIPPED pending a transport: the Data backend's operator routes live at `/admin/*`, a SIBLING of `/v1/*`, and the merged CLI reaches Data only through WayAI's `/api/data` proxy, which contains every forwarded path under `/v1` by construction (`rekor/upstream-url.ts`). The standalone CLI could call them because it held a second `api_url` pointed straight at the Data backend \u2014 the thing the merge deliberately removed. Shipping needs a proxy channel for operator routes plus platform-admin gating on it, which is a backend change, not a CLI one."
14971
15111
  },
14972
15112
  {
14973
15113
  original: "report",
14974
15114
  disposition: "namespaced",
14975
15115
  surviving: "bases report",
14976
15116
  clause: 3,
14977
- shipped: false,
15117
+ shipped: true,
14978
15118
  reason: "Two report queues still exist, so the merged CLI must be able to file to either. The two collapse when the queues do."
14979
15119
  },
14980
15120
  // --- Neither: one verb each, routing by workspace subtree. ---
@@ -15457,18 +15597,6 @@ var init_token_store = __esm({
15457
15597
  }
15458
15598
  });
15459
15599
 
15460
- // src/lib/expected.ts
15461
- function expected(message) {
15462
- const err = new Error(message);
15463
- err.isExpected = true;
15464
- return err;
15465
- }
15466
- var init_expected = __esm({
15467
- "src/lib/expected.ts"() {
15468
- "use strict";
15469
- }
15470
- });
15471
-
15472
15600
  // src/lib/auth.ts
15473
15601
  var auth_exports = {};
15474
15602
  __export(auth_exports, {
@@ -16746,15 +16874,15 @@ function scanResourceFiles(dir, prefix = "") {
16746
16874
  files.push(...scanResourceFiles(path12.join(dir, entry.name), relPath));
16747
16875
  } else if (entry.isFile()) {
16748
16876
  const fullPath = path12.join(dir, entry.name);
16749
- const stat = fs10.statSync(fullPath);
16750
- if (stat.size > MAX_RESOURCE_FILE_SIZE3) {
16751
- console.warn(` Warning: skipping ${relPath} (${(stat.size / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit)`);
16877
+ const stat2 = fs10.statSync(fullPath);
16878
+ if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
16879
+ console.warn(` Warning: skipping ${relPath} (${(stat2.size / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit)`);
16752
16880
  continue;
16753
16881
  }
16754
16882
  const fileEntry = {
16755
16883
  path: relPath,
16756
16884
  mime_type: guessMimeType(entry.name),
16757
- file_size: stat.size
16885
+ file_size: stat2.size
16758
16886
  };
16759
16887
  const data = fs10.readFileSync(fullPath);
16760
16888
  fileEntry.hash = computeHash(data);
@@ -16946,34 +17074,34 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
16946
17074
  if (abs !== root && !abs.startsWith(root + path13.sep)) {
16947
17075
  throw expected(`${label}: attachment "${entry}" escapes the hub folder.`);
16948
17076
  }
16949
- let stat;
17077
+ let stat2;
16950
17078
  try {
16951
- stat = fs11.statSync(abs);
17079
+ stat2 = fs11.statSync(abs);
16952
17080
  } catch {
16953
17081
  }
16954
- if (!stat?.isFile()) {
17082
+ if (!stat2?.isFile()) {
16955
17083
  throw expected(`${label}: attachment "${entry}" not found.`);
16956
17084
  }
16957
17085
  if (!realpathContained(root, abs)) {
16958
17086
  throw expected(`${label}: attachment "${entry}" resolves outside the hub folder (symlinks are not allowed).`);
16959
17087
  }
16960
- if (stat.size > MAX_RESOURCE_FILE_SIZE3) {
17088
+ if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
16961
17089
  throw expected(
16962
- `${label}: attachment "${entry}" is ${(stat.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
17090
+ `${label}: attachment "${entry}" is ${(stat2.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
16963
17091
  );
16964
17092
  }
16965
17093
  const data = fs11.readFileSync(abs);
16966
17094
  const hash = computeHash(data);
16967
17095
  const fileName = path13.basename(abs);
16968
17096
  const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
16969
- refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size: stat.size });
17097
+ refs.push({ hash, file_name: fileName, mime_type: mimeType, file_size: stat2.size });
16970
17098
  if (!bytesByHash.has(hash)) {
16971
17099
  bytesByHash.set(hash, {
16972
17100
  hash,
16973
17101
  content_base64: data.toString("base64"),
16974
17102
  file_name: fileName,
16975
17103
  mime_type: mimeType,
16976
- file_size: stat.size
17104
+ file_size: stat2.size
16977
17105
  });
16978
17106
  }
16979
17107
  }
@@ -17011,7 +17139,7 @@ function rewriteEvalAttachmentsToLocalPaths(payload) {
17011
17139
  return base === "" || base === "." || base === ".." ? ref.hash.slice(0, 16) : base;
17012
17140
  };
17013
17141
  const hashesByName = /* @__PURE__ */ new Map();
17014
- const collect = (turn) => {
17142
+ const collect2 = (turn) => {
17015
17143
  if (!Array.isArray(turn.attachments)) return;
17016
17144
  for (const ref of turn.attachments) {
17017
17145
  if (!ref?.hash || !ref.file_name) continue;
@@ -17024,7 +17152,7 @@ function rewriteEvalAttachmentsToLocalPaths(payload) {
17024
17152
  set.add(ref.hash);
17025
17153
  }
17026
17154
  };
17027
- forEachAttachableTurn(payload, collect);
17155
+ forEachAttachableTurn(payload, collect2);
17028
17156
  const pathByKey = /* @__PURE__ */ new Map();
17029
17157
  const usedLower = /* @__PURE__ */ new Set();
17030
17158
  const localPathFor = (ref) => {
@@ -19274,18 +19402,18 @@ __export(send_message_exports, {
19274
19402
  import * as fs19 from "fs";
19275
19403
  import * as path25 from "path";
19276
19404
  function statAttachment(filePath) {
19277
- let stat;
19405
+ let stat2;
19278
19406
  try {
19279
- stat = fs19.statSync(filePath);
19407
+ stat2 = fs19.statSync(filePath);
19280
19408
  } catch {
19281
19409
  console.error(`Error: file not found: ${filePath}`);
19282
19410
  process.exit(1);
19283
19411
  }
19284
- if (stat.isDirectory()) {
19412
+ if (stat2.isDirectory()) {
19285
19413
  console.error(`Error: not a file: ${filePath}`);
19286
19414
  process.exit(1);
19287
19415
  }
19288
- return { filePath, size: stat.size };
19416
+ return { filePath, size: stat2.size };
19289
19417
  }
19290
19418
  function readAttachment(filePath, size) {
19291
19419
  const fileName = path25.basename(filePath);
@@ -20552,7 +20680,7 @@ async function stopEvalSessionWithRetries(stop, opts = {}) {
20552
20680
  const attempts = opts.attempts ?? STOP_ATTEMPTS;
20553
20681
  const attemptTimeoutMs = opts.attemptTimeoutMs ?? STOP_ATTEMPT_TIMEOUT_MS;
20554
20682
  const backoffMs = opts.backoffMs ?? STOP_BACKOFF_MS;
20555
- const sleep2 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
20683
+ const sleep3 = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
20556
20684
  let lastError = new Error("Stop was never attempted");
20557
20685
  for (let attempt = 1; attempt <= attempts; attempt++) {
20558
20686
  let timeout;
@@ -20573,7 +20701,7 @@ async function stopEvalSessionWithRetries(stop, opts = {}) {
20573
20701
  if (timeout !== void 0) clearTimeout(timeout);
20574
20702
  }
20575
20703
  const delay2 = backoffMs[attempt - 1];
20576
- if (attempt < attempts && delay2) await sleep2(delay2);
20704
+ if (attempt < attempts && delay2) await sleep3(delay2);
20577
20705
  }
20578
20706
  throw lastError;
20579
20707
  }
@@ -22512,11 +22640,19 @@ async function updateCredentialCommand(args2) {
22512
22640
  const body = {};
22513
22641
  if (wantsSecret) {
22514
22642
  let input;
22515
- if (parsed.stdin) {
22516
- if (process.stdin.isTTY) {
22517
- console.error("--stdin requires piped input. Use --secret for an interactive masked prompt.");
22518
- process.exit(1);
22519
- }
22643
+ let secretSource;
22644
+ try {
22645
+ secretSource = resolveSecretSource({
22646
+ stdin: parsed.stdin,
22647
+ prompt: parsed.secretPrompt,
22648
+ stdinFlag: "--stdin",
22649
+ promptHint: "use --secret for an interactive masked prompt"
22650
+ });
22651
+ } catch (err) {
22652
+ console.error(err instanceof Error ? err.message : String(err));
22653
+ process.exit(1);
22654
+ }
22655
+ if (secretSource === "stdin") {
22520
22656
  input = await readStdin();
22521
22657
  } else if (authType === "basic_auth") {
22522
22658
  const username = await prompt("Enter username: ");
@@ -24851,8 +24987,8 @@ async function createDataClient(orgId) {
24851
24987
  }
24852
24988
  const org = selected ?? readRepoConfig()?.organization_id;
24853
24989
  return {
24854
- async request(method, v1Path, body) {
24855
- const envelope = await api.dataRequest(method, v1Path, body, org);
24990
+ async request(method, path31, body) {
24991
+ const envelope = await api.dataRequest(method, path31, body, org);
24856
24992
  return envelope.data;
24857
24993
  },
24858
24994
  async collectPages(makePath) {
@@ -24868,9 +25004,36 @@ async function createDataClient(orgId) {
24868
25004
  seen.add(cursor);
24869
25005
  }
24870
25006
  return all;
25007
+ },
25008
+ async upload(v1Path, bytes, contentType, headers) {
25009
+ const envelope = await api.dataUpload(v1Path, bytes, contentType, org, headers);
25010
+ return envelope.data;
25011
+ },
25012
+ download(v1Path) {
25013
+ return api.dataDownload(v1Path, org);
25014
+ },
25015
+ url(v1Path) {
25016
+ return api.dataUrl(v1Path, org);
24871
25017
  }
24872
25018
  };
24873
25019
  }
25020
+ function dataErrorEnvelope(err) {
25021
+ if (!(err instanceof ApiError)) return null;
25022
+ try {
25023
+ const parsed = JSON.parse(err.body);
25024
+ return parsed?.error ?? null;
25025
+ } catch {
25026
+ return null;
25027
+ }
25028
+ }
25029
+ function dataErrorCode(err) {
25030
+ const code = dataErrorEnvelope(err)?.code;
25031
+ return typeof code === "string" && code ? code : null;
25032
+ }
25033
+ function dataErrorDetails(err) {
25034
+ const details = dataErrorEnvelope(err)?.details;
25035
+ return details && typeof details === "object" ? details : void 0;
25036
+ }
24874
25037
  var MAX_PAGES;
24875
25038
  var init_client = __esm({
24876
25039
  "src/data/client.ts"() {
@@ -24940,6 +25103,36 @@ function printFields(obj) {
24940
25103
  console.log(`${key.padEnd(width)} ${formatCell2(value)}`);
24941
25104
  }
24942
25105
  }
25106
+ function printBatchResult(response) {
25107
+ const obj = response && typeof response === "object" ? response : {};
25108
+ const results = Array.isArray(obj.results) ? obj.results : [];
25109
+ const batchId = typeof obj.batch_id === "string" ? obj.batch_id : "(none)";
25110
+ console.log(
25111
+ `batch_id: ${sanitizeTerminalText(batchId)} \xB7 ${results.length} operation${results.length === 1 ? "" : "s"}`
25112
+ );
25113
+ if (results.length === 0) return;
25114
+ printRows(
25115
+ results.map((r, i) => ({
25116
+ "#": String(i),
25117
+ operation: r?.type ?? "",
25118
+ result: summarizeBatchResult(r)
25119
+ }))
25120
+ );
25121
+ }
25122
+ function summarizeBatchResult(r) {
25123
+ if (!r || typeof r !== "object") return String(r ?? "");
25124
+ if (r.deleted === true) return "deleted";
25125
+ const entity = r.record ?? r.relationship ?? r.record_type;
25126
+ if (entity && typeof entity === "object") {
25127
+ const e = entity;
25128
+ const id = typeof e.id === "string" ? e.id : void 0;
25129
+ const label = typeof e.record_type === "string" ? e.record_type : typeof e.rel_type === "string" ? e.rel_type : typeof e.name === "string" ? e.name : void 0;
25130
+ const version = e.version != null ? ` v${String(e.version)}` : "";
25131
+ if (id && label) return `${label}/${id}${version}`;
25132
+ if (id) return `${id}${version}`;
25133
+ }
25134
+ return JSON.stringify(r);
25135
+ }
24943
25136
  function cellAt(row, column) {
24944
25137
  return formatCell2(row?.[column]);
24945
25138
  }
@@ -24977,313 +25170,2672 @@ function pathSegment(id, label = "id") {
24977
25170
  }
24978
25171
  return encodeURIComponent(id);
24979
25172
  }
24980
- function parseData(data) {
24981
- if (data.startsWith("@")) {
24982
- return JSON.parse(readFileSync19(data.slice(1), "utf-8"));
25173
+ function foreignSegment(value, label = "id") {
25174
+ if (value === "." || value === "..") {
25175
+ throw expected(`Invalid ${label}: ${JSON.stringify(value)} is a path traversal segment.`);
25176
+ }
25177
+ return encodeURIComponent(value);
25178
+ }
25179
+ function pathSegments(value, label = "path") {
25180
+ return value.split("/").map((segment) => foreignSegment(segment, label)).join("/");
25181
+ }
25182
+ function parseData(data, flag) {
25183
+ const prefix = flag ? `${flag}: ` : "";
25184
+ const source = data.startsWith("@") ? data.slice(1) : void 0;
25185
+ let text = data;
25186
+ if (source !== void 0) {
25187
+ try {
25188
+ text = readFileSync19(source, "utf-8");
25189
+ } catch (e) {
25190
+ throw expected(`${prefix}${source}: ${e instanceof Error ? e.message : "could not be read"}`);
25191
+ }
25192
+ }
25193
+ try {
25194
+ return JSON.parse(text);
25195
+ } catch (e) {
25196
+ const where = source !== void 0 ? ` in ${source}` : "";
25197
+ throw expected(`${prefix}invalid JSON${where}: ${e instanceof Error ? e.message : String(e)}`);
24983
25198
  }
24984
- return JSON.parse(data);
25199
+ }
25200
+ function toolsetMcpUrl(slug) {
25201
+ return `https://data-mcp.wayai.pro/t/${slug}/mcp`;
24985
25202
  }
24986
25203
  function globals(cmd) {
24987
25204
  let namespace = cmd;
24988
25205
  while (namespace.parent?.parent) namespace = namespace.parent;
24989
25206
  return namespace.opts();
24990
25207
  }
25208
+ function withBaseOption(command2) {
25209
+ return command2.option("--base <id>", "Base id (or set WAYAI_BASE)");
25210
+ }
25211
+ function baseOptionHolder(cmd) {
25212
+ for (let c = cmd; c; c = c.parent ?? void 0) {
25213
+ if (c.options.some((o) => o.long === "--base")) return c;
25214
+ }
25215
+ return void 0;
25216
+ }
25217
+ function findBase(cmd) {
25218
+ return baseOptionHolder(cmd)?.opts()?.base;
25219
+ }
25220
+ function requireBase(cmd) {
25221
+ const base = findBase(cmd) ?? process.env.WAYAI_BASE ?? "";
25222
+ if (base) return base;
25223
+ console.error("Error: --base is required (or set WAYAI_BASE)");
25224
+ return process.exit(1);
25225
+ }
24991
25226
  function outputFormat(cmd) {
24992
25227
  const opts = globals(cmd);
24993
25228
  return opts.json || opts.output === "json" ? "json" : "table";
24994
25229
  }
25230
+ function historyQuery(opts) {
25231
+ const qs = new URLSearchParams();
25232
+ if (opts.limit) qs.set("limit", opts.limit);
25233
+ if (opts.offset) qs.set("offset", opts.offset);
25234
+ if (opts.diff) qs.set("diff", "true");
25235
+ const q = qs.toString();
25236
+ return q ? `?${q}` : "";
25237
+ }
24995
25238
  function splitList(value) {
24996
25239
  return value.split(",").map((v) => v.trim()).filter((v) => v.length > 0);
24997
25240
  }
25241
+ function parseByteCount(flag, value) {
25242
+ const n = Number(value);
25243
+ if (!Number.isInteger(n) || n < 0) {
25244
+ throw expected(`${flag} must be a whole number of bytes (0 = unlimited), not ${JSON.stringify(value)}.`);
25245
+ }
25246
+ return n;
25247
+ }
25248
+ async function readSecret(source, label) {
25249
+ const value = resolveSecretSource(source) === "stdin" ? (await readStdin()).trim() : (await promptSecret(`${label}: `)).trim();
25250
+ if (!value) throw expected(`${label} is required.`);
25251
+ return value;
25252
+ }
25253
+ function isInteractive() {
25254
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
25255
+ }
25256
+ function parseDuration(input, flag = "duration") {
25257
+ const match = DURATION_RE.exec(input.trim());
25258
+ const value = Number(match?.[1] ?? 0);
25259
+ const unit = match?.[2];
25260
+ const unitMs = unit ? DURATION_UNIT_MS[unit] : void 0;
25261
+ if (!unitMs || value <= 0) {
25262
+ throw expected(
25263
+ `Invalid ${flag} ${JSON.stringify(input)} \u2014 use <number><unit> with unit s/m/h/d (e.g. 10m, 2h, 30d).`
25264
+ );
25265
+ }
25266
+ return value * unitMs;
25267
+ }
25268
+ var DURATION_RE, DURATION_UNIT_MS;
24998
25269
  var init_helpers = __esm({
24999
25270
  "src/data/helpers.ts"() {
25000
25271
  "use strict";
25001
25272
  init_base_id();
25002
25273
  init_expected();
25274
+ init_utils();
25275
+ DURATION_RE = /^(\d+)([smhd])$/;
25276
+ DURATION_UNIT_MS = {
25277
+ s: 1e3,
25278
+ m: 6e4,
25279
+ h: 36e5,
25280
+ d: 864e5
25281
+ };
25003
25282
  }
25004
25283
  });
25005
25284
 
25006
- // src/lib/base-binding.ts
25007
- var binding2, getBaseBindingPath, readBaseBinding, writeBaseBinding, clearBaseBinding, autoBindBaseIfUnbound, assertBaseMatchesBinding, assertNoBindingBlocksBaseCreation;
25008
- var init_base_binding = __esm({
25009
- "src/lib/base-binding.ts"() {
25285
+ // src/data/commands/actions.ts
25286
+ import { Command } from "commander";
25287
+ function buildActionsCommand() {
25288
+ const actions = new Command("actions").description(
25289
+ "Manage Actions \u2014 reusable, named definitions: one record_type + one operation, or a composite of atomic write steps (--config with `steps`)"
25290
+ );
25291
+ actions.command("upsert <id>").description(
25292
+ "Create or update an Action \u2014 one record_type + one operation, or a composite (--config with `steps`)"
25293
+ ).option(
25294
+ "--record-type <record_type>",
25295
+ "The record_type this action operates over (single-op; omit for a composite)"
25296
+ ).option(
25297
+ "--operation <op>",
25298
+ "The operation: create | get | list | update | delete (single-op; omit for a composite)"
25299
+ ).option("--description <desc>", "Agent-facing description").option(
25300
+ "--config <json>",
25301
+ 'Full Action config as JSON. Single-op: writable_fields, filterable_fields (per field: field (required), param, match, enum, pattern, optional, description \u2014 a declared field generates a required param unless optional: true; a range match is always optional), base_filter (a Filter DSL predicate merged server-side into every list call, invisible to the agent; list only, over a native record_type or a source-backed one whose list endpoint declares local_filters; the value "$now" resolves to the instant of each call, on a date-time field with a range op), precondition, binding, external_source, expose_*/default_* (machinery params are hidden unless expose_<param>: true). Composite: `steps` \u2014 an ordered list of atomic write steps ({key, record_type|rel_type, operation, writable_fields?, precondition?}) run in one transaction. Use @file.json to read from file.'
25302
+ ).action(async function(id, opts) {
25303
+ let body;
25304
+ if (opts.config) {
25305
+ body = parseData(opts.config);
25306
+ if (opts.recordType) body.record_type = opts.recordType;
25307
+ if (opts.operation) body.operation = opts.operation;
25308
+ if (opts.description) body.description = opts.description;
25309
+ } else {
25310
+ if (!opts.recordType || !opts.operation) {
25311
+ throw expected("Provide --record-type and --operation (or --config <json>).");
25312
+ }
25313
+ body = { record_type: opts.recordType, operation: opts.operation };
25314
+ if (opts.description) body.description = opts.description;
25315
+ }
25316
+ const base = pathSegment(requireBase(this), "--base");
25317
+ const client = await createDataClient();
25318
+ printOutput(
25319
+ await client.request("PUT", `/v1/${base}/actions/${pathSegment(id, "action id")}`, body),
25320
+ outputFormat(this)
25321
+ );
25322
+ });
25323
+ actions.command("get <id>").description("Get an Action by id").action(async function(id) {
25324
+ const base = pathSegment(requireBase(this), "--base");
25325
+ const client = await createDataClient();
25326
+ printOutput(await client.request("GET", `/v1/${base}/actions/${pathSegment(id, "action id")}`), outputFormat(this));
25327
+ });
25328
+ actions.command("list").description("List Actions in this base").action(async function() {
25329
+ const base = pathSegment(requireBase(this), "--base");
25330
+ const client = await createDataClient();
25331
+ printOutput(await client.request("GET", `/v1/${base}/actions`), outputFormat(this));
25332
+ });
25333
+ actions.command("delete <id>").description("Delete an Action").option("-y, --yes", "Skip confirmation").action(async function(id, opts) {
25334
+ if (!opts.yes && !await confirm(`Delete action ${id}? This cannot be undone.`)) {
25335
+ console.log("Aborted.");
25336
+ return;
25337
+ }
25338
+ const base = pathSegment(requireBase(this), "--base");
25339
+ const client = await createDataClient();
25340
+ await client.request("DELETE", `/v1/${base}/actions/${pathSegment(id, "action id")}`);
25341
+ console.log(`Deleted action ${id}`);
25342
+ });
25343
+ return actions;
25344
+ }
25345
+ var init_actions = __esm({
25346
+ "src/data/commands/actions.ts"() {
25010
25347
  "use strict";
25011
- init_base_id();
25012
- init_worktree_binding();
25013
- binding2 = createWorktreeBinding({
25014
- filename: "wayai-base-binding",
25015
- isValidId: isValidBaseId,
25016
- noun: "base",
25017
- idLabel: "base id (must be a slug)",
25018
- unbindCommand: "wayai bases unbind",
25019
- useCommand: "wayai bases use"
25020
- });
25021
- getBaseBindingPath = binding2.getBindingPath;
25022
- readBaseBinding = binding2.readBinding;
25023
- writeBaseBinding = binding2.writeBinding;
25024
- clearBaseBinding = binding2.clearBinding;
25025
- autoBindBaseIfUnbound = binding2.autoBindIfUnbound;
25026
- assertBaseMatchesBinding = binding2.assertMatchesBinding;
25027
- assertNoBindingBlocksBaseCreation = binding2.assertNoBindingBlocksCreation;
25348
+ init_expected();
25349
+ init_client();
25350
+ init_output();
25351
+ init_helpers();
25352
+ init_utils();
25028
25353
  }
25029
25354
  });
25030
25355
 
25031
- // src/data/commands/bases.ts
25032
- import { Command } from "commander";
25033
- import * as fs24 from "fs";
25034
- import * as path30 from "path";
25035
- import * as yaml9 from "js-yaml";
25036
- function pageOf(path31, cursor) {
25037
- if (!cursor) return path31;
25038
- return `${path31}${path31.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
25356
+ // src/data/commands/attachments.ts
25357
+ import { Command as Command2 } from "commander";
25358
+ import { readFileSync as readFileSync20 } from "fs";
25359
+ function findAttachmentByFilename(attachments, filename) {
25360
+ return attachments.find((a) => a.key.endsWith(`/${filename}`)) ?? null;
25039
25361
  }
25040
- function parseEnum(flag, value, allowed) {
25041
- if (value === void 0) return void 0;
25042
- if (!allowed.includes(value)) {
25043
- throw expected(`${flag} must be one of: ${allowed.join(", ")}.`);
25362
+ function uploadPathFrom(uploadUrl) {
25363
+ if (typeof uploadUrl !== "string") {
25364
+ throw expected(
25365
+ `The server returned an upload target this CLI will not send credentials to: ${String(uploadUrl)}`
25366
+ );
25044
25367
  }
25045
- return value;
25368
+ let resolved;
25369
+ try {
25370
+ resolved = new URL(toDataProxyPath(uploadUrl), CONTAINMENT_ORIGIN);
25371
+ } catch {
25372
+ throw expected(
25373
+ `The server returned an upload target this CLI will not send credentials to: ${uploadUrl}`
25374
+ );
25375
+ }
25376
+ if (resolved.origin !== CONTAINMENT_ORIGIN || !resolved.pathname.startsWith(`${DATA_PROXY_PREFIX}/`)) {
25377
+ throw expected(
25378
+ `The server returned an upload target this CLI will not send credentials to: ${uploadUrl}`
25379
+ );
25380
+ }
25381
+ return uploadUrl;
25046
25382
  }
25047
- function buildBasesCommand() {
25048
- const bases = new Command("bases").description("Manage bases");
25049
- bases.command("list").description("List all bases").option("--tag <tag>", "Filter by tag").action(async function(opts) {
25383
+ function buildAttachmentsCommand() {
25384
+ const attachments = new Command2("attachments").description("Manage record attachments");
25385
+ attachments.command("upload <record_type> <id>").description(
25386
+ "Get a presigned upload URL for a record attachment. With --file, uploads the file content in one step."
25387
+ ).requiredOption("--filename <name>", "File name or path (e.g. docs/guide.md)").option("--content-type <type>", "MIME type", "application/octet-stream").option("--file <path>", "Local file to upload (skips presigned URL output, uploads directly)").action(async function(recordType2, id, opts) {
25388
+ const base = pathSegment(requireBase(this), "--base");
25050
25389
  const client = await createDataClient();
25051
- const qs = opts.tag ? `?tag=${encodeURIComponent(opts.tag)}` : "";
25390
+ const data = await client.request(
25391
+ "POST",
25392
+ `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments`,
25393
+ { filename: opts.filename, content_type: opts.contentType }
25394
+ );
25395
+ if (!opts.file) {
25396
+ printOutput(data, outputFormat(this));
25397
+ return;
25398
+ }
25399
+ const body = readFileSync20(opts.file);
25400
+ await client.upload(uploadPathFrom(data?.upload_url), body, opts.contentType);
25401
+ printOutput({ ...data, uploaded: true }, outputFormat(this));
25402
+ });
25403
+ attachments.command("url <record_type> <id>").description("Get a download URL for an attachment by filename").requiredOption("--filename <name>", "File name or path (e.g. docs/guide.md)").action(async function(recordType2, id, opts) {
25404
+ const base = pathSegment(requireBase(this), "--base");
25405
+ const client = await createDataClient();
25406
+ const rows = await client.request(
25407
+ "GET",
25408
+ `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments`
25409
+ );
25410
+ const match = findAttachmentByFilename(rows ?? [], opts.filename);
25411
+ if (!match) {
25412
+ throw expected(`Attachment "${opts.filename}" not found`);
25413
+ }
25052
25414
  printOutput(
25053
- await client.collectPages((cursor) => pageOf(`/v1/bases${qs}`, cursor)),
25415
+ { download_url: client.url(`/v1/${base}/attachments/${pathSegments(match.key, "attachment key")}`), ...match },
25054
25416
  outputFormat(this)
25055
25417
  );
25056
25418
  });
25057
- bases.command("get <id>").description("Get a base").action(async function(id) {
25419
+ attachments.command("list <record_type> <id>").description("List attachments for a record").option("--prefix <path>", "Filter by path prefix (e.g. docs/)").action(async function(recordType2, id, opts) {
25420
+ const base = pathSegment(requireBase(this), "--base");
25058
25421
  const client = await createDataClient();
25059
- printOutput(await client.request("GET", `/v1/bases/${pathSegment(id)}`), outputFormat(this));
25422
+ const query = opts.prefix ? `?prefix=${encodeURIComponent(opts.prefix)}` : "";
25423
+ printOutput(
25424
+ await client.request(
25425
+ "GET",
25426
+ `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments${query}`
25427
+ ),
25428
+ outputFormat(this)
25429
+ );
25060
25430
  });
25061
- bases.command("create <id>").description("Create a base").requiredOption("--name <name>", "Base name").option("--description <desc>", "Description").option("--tags <tags>", "Comma-separated tags (e.g. client:acme,billing)").option(
25062
- "--timezone <tz>",
25063
- "Default IANA timezone for datetime canonicalization (e.g. America/Sao_Paulo); a record type's own timezone overrides it"
25064
- ).option(
25065
- "--environment <env>",
25066
- "production (default) | preview. Production bases require a paid plan. A preview created here has no production origin and cannot be given one later, so it cannot be promoted directly \u2014 move its config to a linked preview with pull/push when you upgrade"
25067
- ).action(async function(id, opts) {
25068
- const environment = parseEnum("--environment", opts.environment, [
25069
- "production",
25070
- "preview"
25071
- ]);
25431
+ attachments.command("delete <record_type> <id> <attachment-id>").description("Delete an attachment").option("-y, --yes", "Skip confirmation prompt").action(async function(recordType2, id, attachmentId, opts) {
25432
+ if (!opts.yes && !await confirm(
25433
+ `Delete attachment ${attachmentId} on ${recordType2}/${id}? This cannot be undone.`
25434
+ )) {
25435
+ console.log("Aborted");
25436
+ return;
25437
+ }
25438
+ const base = pathSegment(requireBase(this), "--base");
25072
25439
  const client = await createDataClient();
25073
- const body = { name: opts.name, description: opts.description };
25074
- if (environment) body.environment = environment;
25075
- if (opts.tags) body.tags = splitList(opts.tags);
25076
- if (opts.timezone) body.settings = { timezone: opts.timezone };
25077
- printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
25440
+ await client.request(
25441
+ "DELETE",
25442
+ `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/attachments/${pathSegment(attachmentId, "attachment id")}`
25443
+ );
25444
+ console.log("Deleted");
25078
25445
  });
25079
- bases.command("update <id>").description(
25080
- "Update a base's mutable config (display name, description, tags, default timezone/settings). The id/slug is immutable."
25081
- ).option("--name <name>", "New display name").option("--description <desc>", "Description").option("--tags <tags>", "Comma-separated tags \u2014 replaces the existing tags").option(
25082
- "--timezone <tz>",
25083
- "Default IANA timezone for datetime canonicalization. Merges into existing settings."
25446
+ return attachments;
25447
+ }
25448
+ var CONTAINMENT_ORIGIN;
25449
+ var init_attachments = __esm({
25450
+ "src/data/commands/attachments.ts"() {
25451
+ "use strict";
25452
+ init_expected();
25453
+ init_contracts();
25454
+ init_api_client();
25455
+ init_client();
25456
+ init_output();
25457
+ init_helpers();
25458
+ init_utils();
25459
+ CONTAINMENT_ORIGIN = "https://containment.invalid";
25460
+ }
25461
+ });
25462
+
25463
+ // src/data/commands/batch.ts
25464
+ import { Command as Command3 } from "commander";
25465
+ function buildBasesBatchCommand() {
25466
+ return withBaseOption(new Command3("batch")).description("Execute atomic batch operations (up to 1,000 operations)").requiredOption("--operations <json>", "Operations array (inline JSON or @filename)").action(async function(opts) {
25467
+ const base = pathSegment(requireBase(this), "--base");
25468
+ const operations = parseData(opts.operations, "--operations");
25469
+ const client = await createDataClient();
25470
+ const data = await client.request("POST", `/v1/${base}/batch`, {
25471
+ operations: Array.isArray(operations) ? operations : [operations]
25472
+ });
25473
+ const format = outputFormat(this);
25474
+ if (format === "json") {
25475
+ printOutput(data, format);
25476
+ return;
25477
+ }
25478
+ printBatchResult(data);
25479
+ });
25480
+ }
25481
+ var init_batch = __esm({
25482
+ "src/data/commands/batch.ts"() {
25483
+ "use strict";
25484
+ init_client();
25485
+ init_output();
25486
+ init_helpers();
25487
+ }
25488
+ });
25489
+
25490
+ // src/data/commands/import.ts
25491
+ import { createReadStream } from "fs";
25492
+ import { access, constants, stat } from "fs/promises";
25493
+ import { createInterface as createInterface3 } from "readline";
25494
+ import { randomUUID } from "crypto";
25495
+ import { Command as Command4 } from "commander";
25496
+ function isRetryable(err) {
25497
+ if (isNetworkError(err)) return true;
25498
+ if (!(err instanceof ApiError)) return false;
25499
+ const code = dataErrorCode(err);
25500
+ if (code && RETRYABLE_CODES.has(code)) return true;
25501
+ return err.status >= 500 && err.status !== 501;
25502
+ }
25503
+ function isExpiredSession(err) {
25504
+ if (!(err instanceof ApiError) || err.status !== 409) return false;
25505
+ if (dataErrorCode(err) !== "IMPORT_SESSION_CLOSED") return false;
25506
+ return dataErrorDetails(err)?.status === "expired";
25507
+ }
25508
+ async function withRetry(state, fn, what) {
25509
+ let lastErr;
25510
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
25511
+ try {
25512
+ return await fn();
25513
+ } catch (err) {
25514
+ lastErr = err;
25515
+ if (!isRetryable(err) || attempt === MAX_ATTEMPTS) throw err;
25516
+ const code = err instanceof ApiError ? dataErrorCode(err) : null;
25517
+ if (!code || !RETRYABLE_CODES.has(code)) state.sawAmbiguous = true;
25518
+ const delay2 = BASE_BACKOFF_MS * 2 ** (attempt - 1);
25519
+ console.error(
25520
+ ` ${what}: ${err.message} \u2014 retrying in ${delay2}ms (${attempt}/${MAX_ATTEMPTS - 1})`
25521
+ );
25522
+ await sleep2(delay2);
25523
+ }
25524
+ }
25525
+ throw lastErr;
25526
+ }
25527
+ async function* readChunks(file, size, startChunk) {
25528
+ const rl = createInterface3({ input: createReadStream(file, "utf8"), crlfDelay: Infinity });
25529
+ let rows = [];
25530
+ let index = 0;
25531
+ let lineNo = 0;
25532
+ let inChunk = 0;
25533
+ for await (const line of rl) {
25534
+ lineNo++;
25535
+ const trimmed = line.trim();
25536
+ if (!trimmed) continue;
25537
+ if (index < startChunk) {
25538
+ if (++inChunk === size) {
25539
+ inChunk = 0;
25540
+ index++;
25541
+ }
25542
+ continue;
25543
+ }
25544
+ let parsed;
25545
+ try {
25546
+ parsed = JSON.parse(trimmed);
25547
+ } catch {
25548
+ throw expected(`${file}:${lineNo}: not valid JSON. Each line must be one JSON object.`);
25549
+ }
25550
+ rows.push(parsed);
25551
+ if (rows.length === size) {
25552
+ yield { index, rows };
25553
+ rows = [];
25554
+ index++;
25555
+ }
25556
+ }
25557
+ if (rows.length > 0) yield { index, rows };
25558
+ }
25559
+ async function beginSession(state, client, base, recordType2, requestKey) {
25560
+ const res = await withRetry(
25561
+ state,
25562
+ () => client.request("POST", `/v1/${base}/import/begin`, {
25563
+ request_key: requestKey,
25564
+ record_type: recordType2
25565
+ }),
25566
+ "begin"
25567
+ );
25568
+ return res.session;
25569
+ }
25570
+ function buildBasesImportCommand() {
25571
+ const importCmd = withBaseOption(new Command4("import")).description(
25572
+ "Bulk historical import: load an NDJSON backfill, list sessions, or undo one"
25573
+ );
25574
+ importCmd.command("run <record_type>").description(
25575
+ "Load an NDJSON file into a record type (one JSON object per line, each {external_id, data})"
25576
+ ).requiredOption("--file <path>", "NDJSON file to import").option(
25577
+ "--chunk-size <n>",
25578
+ "Rows per request, max 1000. Must match the original run when resuming.",
25579
+ String(MAX_CHUNK_ROWS)
25084
25580
  ).option(
25085
- "--settings <json>",
25086
- "Raw settings JSON object (inline JSON or @file.json) \u2014 replaces the entire settings object"
25581
+ "--request-key <key>",
25582
+ "Idempotency key for this logical run (default: generated). Re-use it to resume."
25087
25583
  ).option(
25088
- "--integrations <mode>",
25089
- "enabled | disabled. Preview-only: `disabled` neutralizes every external integration edge so the seeded preview behaves natively for production-safe agent evals"
25090
- ).action(async function(id, opts) {
25091
- const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
25092
- if (opts.name === void 0 && opts.description === void 0 && opts.tags === void 0 && opts.timezone === void 0 && opts.settings === void 0 && integrations === void 0) {
25584
+ "--start-chunk <n>",
25585
+ "Skip chunks before this index \u2014 resume a run that stopped partway",
25586
+ "0"
25587
+ ).option("--no-finalize", "Leave the session open (resume later with the same --request-key)").action(async function(recordType2, opts) {
25588
+ const base = pathSegment(requireBase(this), "--base");
25589
+ const state = { sawAmbiguous: false };
25590
+ const chunkSize = Number(opts.chunkSize);
25591
+ if (!Number.isInteger(chunkSize) || chunkSize < 1 || chunkSize > MAX_CHUNK_ROWS) {
25592
+ throw expected(`--chunk-size must be an integer between 1 and ${MAX_CHUNK_ROWS}.`);
25593
+ }
25594
+ const startChunk = Number(opts.startChunk);
25595
+ if (!Number.isInteger(startChunk) || startChunk < 0) {
25596
+ throw expected("--start-chunk must be a non-negative integer.");
25597
+ }
25598
+ if (startChunk > 0 && !this.getOptionValueSource("chunkSize")?.startsWith("cli")) {
25093
25599
  throw expected(
25094
- "Nothing to update. Provide at least one of --name, --description, --tags, --timezone, --settings, --integrations."
25600
+ "--start-chunk requires --chunk-size to be given explicitly, and it must match the original run."
25095
25601
  );
25096
25602
  }
25603
+ try {
25604
+ if ((await stat(opts.file)).isDirectory()) throw new Error("is a directory");
25605
+ await access(opts.file, constants.R_OK);
25606
+ } catch {
25607
+ throw expected(`--file ${opts.file}: not a readable file.`);
25608
+ }
25097
25609
  const client = await createDataClient();
25098
- const body = {};
25099
- if (opts.name !== void 0) body.name = opts.name;
25100
- if (opts.description !== void 0) body.description = opts.description;
25101
- if (opts.tags !== void 0) body.tags = splitList(opts.tags);
25102
- if (integrations !== void 0) body.integrations = integrations;
25103
- if (opts.settings !== void 0 || opts.timezone !== void 0) {
25104
- let settings;
25105
- if (opts.settings !== void 0) {
25106
- let parsed;
25610
+ const requestKey = opts.requestKey ?? `cli-${randomUUID()}`;
25611
+ if (!opts.requestKey) {
25612
+ console.error(`request_key: ${requestKey} (pass --request-key to resume this run)`);
25613
+ }
25614
+ let session = await beginSession(state, client, base, recordType2, requestKey);
25615
+ console.error(`import_id: ${session.import_id}`);
25616
+ const importIds = [session.import_id];
25617
+ let written = 0;
25618
+ let skipped = 0;
25619
+ let sent = 0;
25620
+ let nextIndex = startChunk;
25621
+ try {
25622
+ for await (const { index, rows } of readChunks(opts.file, chunkSize, startChunk)) {
25623
+ nextIndex = index;
25624
+ const body = { import_id: session.import_id, chunk_index: index, rows };
25625
+ let result;
25107
25626
  try {
25108
- parsed = parseData(opts.settings);
25109
- } catch (e) {
25110
- throw expected(`--settings: ${e instanceof Error ? e.message : "could not be read as JSON"}`);
25627
+ result = await withRetry(
25628
+ state,
25629
+ () => client.request("POST", `/v1/${base}/import/chunk`, body),
25630
+ `chunk ${index}`
25631
+ );
25632
+ } catch (err) {
25633
+ if (!isExpiredSession(err)) throw err;
25634
+ console.error(
25635
+ ` chunk ${index}: session expired (month boundary) \u2014 starting a fresh session and re-sending.`
25636
+ );
25637
+ console.error(
25638
+ ` NOTE: this run now spans two import_ids; undo needs both. Previous: ${session.import_id}`
25639
+ );
25640
+ session = await beginSession(state, client, base, recordType2, requestKey);
25641
+ importIds.push(session.import_id);
25642
+ console.error(` import_id: ${session.import_id}`);
25643
+ result = await withRetry(
25644
+ state,
25645
+ () => client.request("POST", `/v1/${base}/import/chunk`, {
25646
+ ...body,
25647
+ import_id: session.import_id
25648
+ }),
25649
+ `chunk ${index}`
25650
+ );
25111
25651
  }
25112
- if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
25113
- throw expected("--settings must be a valid JSON object");
25652
+ written += result.rows_written ?? 0;
25653
+ skipped += result.rows_skipped ?? 0;
25654
+ sent++;
25655
+ nextIndex = index + 1;
25656
+ if (sent % 10 === 0) {
25657
+ console.error(` ${sent} chunks \u2014 ${written} written, ${skipped} unchanged`);
25114
25658
  }
25115
- settings = parsed;
25659
+ }
25660
+ if (opts.finalize) {
25661
+ await withRetry(
25662
+ state,
25663
+ () => client.request(
25664
+ "POST",
25665
+ `/v1/${base}/import/${pathSegment(session.import_id, "import id")}/finalize`
25666
+ ),
25667
+ "finalize"
25668
+ );
25669
+ }
25670
+ } catch (err) {
25671
+ console.error(
25672
+ `
25673
+ Stopped at chunk ${nextIndex}. To resume, re-run the SAME command with:
25674
+ --start-chunk ${nextIndex} --chunk-size ${chunkSize} --request-key ${requestKey}
25675
+ (keep every other flag you passed \u2014 the base and org decide where the rows land, and the chunk size decides which rows each index covers.)`
25676
+ );
25677
+ throw err;
25678
+ }
25679
+ printOutput(
25680
+ {
25681
+ import_ids: importIds,
25682
+ rows_written: written,
25683
+ rows_skipped: skipped,
25684
+ chunks_sent: sent,
25685
+ finalized: opts.finalize
25686
+ },
25687
+ outputFormat(this)
25688
+ );
25689
+ });
25690
+ importCmd.command("list").description("List import sessions, newest first \u2014 find a stuck run or the id to undo").option("--limit <n>", "Maximum sessions to return").option("--offset <n>", "Sessions to skip").action(async function(opts) {
25691
+ const base = pathSegment(requireBase(this), "--base");
25692
+ const qs = new URLSearchParams();
25693
+ if (opts.limit) qs.set("limit", opts.limit);
25694
+ if (opts.offset) qs.set("offset", opts.offset);
25695
+ const suffix = qs.toString() ? `?${qs}` : "";
25696
+ const client = await createDataClient();
25697
+ printOutput(
25698
+ await client.request("GET", `/v1/${base}/import/sessions${suffix}`),
25699
+ outputFormat(this)
25700
+ );
25701
+ });
25702
+ importCmd.command("rollback <import_id>").description(
25703
+ "Undo an import \u2014 deletes only the rows it CREATED. Re-importing corrected data is usually the better fix."
25704
+ ).option("--yes", "Skip the confirmation prompt").action(async function(importId, opts) {
25705
+ const base = pathSegment(requireBase(this), "--base");
25706
+ const format = outputFormat(this);
25707
+ const state = { sawAmbiguous: false };
25708
+ const client = await createDataClient();
25709
+ if (!opts.yes && !isInteractive()) {
25710
+ throw expected(
25711
+ "Refusing to roll back an import without confirmation \u2014 pass --yes to run non-interactively."
25712
+ );
25713
+ }
25714
+ if (!opts.yes) {
25715
+ const plan = await client.request("GET", `/v1/${base}/import/${pathSegment(importId, "import id")}/rollback-plan?counts=true`);
25716
+ const n = plan.matched_records ?? 0;
25717
+ if (n === 0) {
25718
+ console.error("No rows left to undo \u2014 closing the import session.");
25116
25719
  } else {
25117
- const existing = await client.request("GET", `/v1/bases/${pathSegment(id)}`);
25118
- settings = { ...existing?.settings ?? {} };
25720
+ const ok = await confirm(
25721
+ `Delete ${n} record${n === 1 ? "" : "s"} created by this import into "${sanitizeTerminalText(String(plan.record_type))}", plus every relationship pointing at them?`
25722
+ );
25723
+ if (!ok) {
25724
+ console.error("Aborted.");
25725
+ return;
25726
+ }
25119
25727
  }
25120
- if (opts.timezone !== void 0) settings.timezone = opts.timezone;
25121
- body.settings = settings;
25122
25728
  }
25123
- printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
25729
+ let records = 0;
25730
+ let relationships = 0;
25731
+ let calls = 0;
25732
+ let incomplete = false;
25733
+ for (let sweep = 0; ; sweep++) {
25734
+ const res = await withRetry(
25735
+ state,
25736
+ () => client.request("POST", `/v1/${base}/import/${pathSegment(importId, "import id")}/rollback`),
25737
+ "rollback"
25738
+ );
25739
+ records += res.swept.records;
25740
+ relationships += res.swept.relationships;
25741
+ calls++;
25742
+ if (res.complete) break;
25743
+ const sweptNothing = res.swept.records === 0 && res.swept.relationships === 0;
25744
+ if (sweptNothing || sweep + 1 >= MAX_ROLLBACK_SWEEPS) {
25745
+ incomplete = true;
25746
+ console.error(
25747
+ sweptNothing ? " the sweep stopped making progress before the server reported it complete \u2014 re-run to continue." : ` stopped after ${MAX_ROLLBACK_SWEEPS} sweeps without completing \u2014 re-run to continue.`
25748
+ );
25749
+ break;
25750
+ }
25751
+ if (format !== "json") {
25752
+ console.error(` swept ${records} records, ${relationships} relationships so far\u2026`);
25753
+ }
25754
+ }
25755
+ if (state.sawAmbiguous && format !== "json") {
25756
+ console.error(
25757
+ "Note: a request was re-sent after its outcome was unknown, so the counts below are a lower bound \u2014 a page that committed before its response was lost is not included."
25758
+ );
25759
+ }
25760
+ printOutput(
25761
+ {
25762
+ import_id: importId,
25763
+ swept: { records, relationships },
25764
+ requests: calls,
25765
+ // The server's own completion claim, carried so a script can tell an
25766
+ // undo that FINISHED from one that stopped early and needs re-running.
25767
+ complete: !incomplete,
25768
+ ...state.sawAmbiguous ? { counts_are_lower_bound: true } : {}
25769
+ },
25770
+ format
25771
+ );
25772
+ if (incomplete) return process.exit(1);
25773
+ });
25774
+ return importCmd;
25775
+ }
25776
+ var MAX_CHUNK_ROWS, MAX_ATTEMPTS, MAX_ROLLBACK_SWEEPS, BASE_BACKOFF_MS, RETRYABLE_CODES, sleep2;
25777
+ var init_import = __esm({
25778
+ "src/data/commands/import.ts"() {
25779
+ "use strict";
25780
+ init_client();
25781
+ init_output();
25782
+ init_helpers();
25783
+ init_api_client();
25784
+ init_network_error();
25785
+ init_terminal_output();
25786
+ init_utils();
25787
+ init_expected();
25788
+ MAX_CHUNK_ROWS = 1e3;
25789
+ MAX_ATTEMPTS = 5;
25790
+ MAX_ROLLBACK_SWEEPS = 1e3;
25791
+ BASE_BACKOFF_MS = 500;
25792
+ RETRYABLE_CODES = /* @__PURE__ */ new Set(["BASE_OVERLOADED", "SERVICE_UNAVAILABLE", "RATE_LIMITED"]);
25793
+ sleep2 = (ms) => new Promise((r) => setTimeout(r, ms));
25794
+ }
25795
+ });
25796
+
25797
+ // src/data/commands/providers.ts
25798
+ import { Command as Command5 } from "commander";
25799
+ import { writeFileSync as writeFileSync15 } from "fs";
25800
+ function providerSegment(provider) {
25801
+ if (!VALID_PROVIDERS.includes(provider)) {
25802
+ throw expected(`Unknown provider ${JSON.stringify(provider)}. Expected one of: ${VALID_PROVIDERS_HELP}.`);
25803
+ }
25804
+ return pathSegment(provider, "provider");
25805
+ }
25806
+ function buildBasesProvidersCommand() {
25807
+ const providers = withBaseOption(new Command5("providers")).description(
25808
+ "Import/export tool definitions between LLM providers and record types"
25809
+ );
25810
+ providers.command("import <provider>").description(`Import tool definitions as record types. Providers: ${VALID_PROVIDERS_HELP}`).requiredOption("--tools <json>", "Tool definitions (inline JSON or @filename)").action(async function(provider, opts) {
25811
+ const base = pathSegment(requireBase(this), "--base");
25812
+ const tools = parseData(opts.tools, "--tools");
25813
+ const client = await createDataClient();
25814
+ printOutput(
25815
+ await client.request("POST", `/v1/${base}/providers/${providerSegment(provider)}/import`, {
25816
+ tools: Array.isArray(tools) ? tools : [tools]
25817
+ }),
25818
+ outputFormat(this)
25819
+ );
25820
+ });
25821
+ providers.command("export <provider>").description(`Export record types as tool definitions. Providers: ${VALID_PROVIDERS_HELP}`).option("--record-types <ids>", "Comma-separated record type ids (omit for all)").option("--to <file>", "Write the exported definitions to a file instead of stdout").action(async function(provider, opts) {
25822
+ const base = pathSegment(requireBase(this), "--base");
25823
+ const recordTypes = opts.recordTypes ? splitList(opts.recordTypes) : [];
25824
+ const query = recordTypes.length ? `?record_types=${encodeURIComponent(recordTypes.join(","))}` : "";
25825
+ const client = await createDataClient();
25826
+ const data = await client.request(
25827
+ "GET",
25828
+ `/v1/${base}/providers/${providerSegment(provider)}/export${query}`
25829
+ );
25830
+ if (opts.to) {
25831
+ try {
25832
+ writeFileSync15(opts.to, JSON.stringify(data, null, 2));
25833
+ } catch (e) {
25834
+ throw expected(`--to ${opts.to}: ${e instanceof Error ? e.message : "could not be written"}`);
25835
+ }
25836
+ printOutput({ written_to: opts.to }, outputFormat(this));
25837
+ return;
25838
+ }
25839
+ printOutput(data, outputFormat(this));
25840
+ });
25841
+ providers.command("import-call <provider> <record_type>").description(
25842
+ `Create a record from a tool call in provider format. Providers: ${VALID_PROVIDERS_HELP}`
25843
+ ).requiredOption("--data <json>", "Tool call data in provider format (inline JSON or @filename)").option("--external-id <id>", "External id for idempotent upsert").option("--external-source <source>", "External source identifier").action(async function(provider, recordType2, opts) {
25844
+ const base = pathSegment(requireBase(this), "--base");
25845
+ const callData = parseData(opts.data, "--data");
25846
+ const queryParts = [];
25847
+ if (opts.externalId) queryParts.push(`external_id=${encodeURIComponent(opts.externalId)}`);
25848
+ if (opts.externalSource) {
25849
+ queryParts.push(`external_source=${encodeURIComponent(opts.externalSource)}`);
25850
+ }
25851
+ const qs = queryParts.length ? `?${queryParts.join("&")}` : "";
25852
+ const client = await createDataClient();
25853
+ printOutput(
25854
+ await client.request(
25855
+ "POST",
25856
+ `/v1/${base}/providers/${providerSegment(provider)}/records/${pathSegment(recordType2, "record_type")}${qs}`,
25857
+ callData
25858
+ ),
25859
+ outputFormat(this)
25860
+ );
25861
+ });
25862
+ return providers;
25863
+ }
25864
+ var VALID_PROVIDERS, VALID_PROVIDERS_HELP;
25865
+ var init_providers = __esm({
25866
+ "src/data/commands/providers.ts"() {
25867
+ "use strict";
25868
+ init_client();
25869
+ init_output();
25870
+ init_helpers();
25871
+ init_expected();
25872
+ VALID_PROVIDERS = ["openai", "anthropic", "google", "mcp"];
25873
+ VALID_PROVIDERS_HELP = VALID_PROVIDERS.join(", ");
25874
+ }
25875
+ });
25876
+
25877
+ // src/data/commands/report.ts
25878
+ import { Command as Command6 } from "commander";
25879
+ import { readFileSync as readFileSync21 } from "fs";
25880
+ import { dirname as dirname9, join as join27 } from "path";
25881
+ import { fileURLToPath as fileURLToPath2 } from "url";
25882
+ function resolveCliVersion() {
25883
+ for (const candidate of [
25884
+ join27(here, "..", "package.json"),
25885
+ join27(here, "..", "..", "..", "package.json")
25886
+ ]) {
25887
+ try {
25888
+ const version = JSON.parse(readFileSync21(candidate, "utf-8")).version;
25889
+ if (typeof version === "string" && version) return version;
25890
+ } catch {
25891
+ }
25892
+ }
25893
+ return "unknown";
25894
+ }
25895
+ function resolveReportClientVersions(projectRoot, cliVersion) {
25896
+ const versions = { cli_version: cliVersion };
25897
+ try {
25898
+ const skillVersion = lowestInstalledVersion(findInstalledSkills(projectRoot));
25899
+ if (skillVersion) versions.skill_version = skillVersion;
25900
+ } catch {
25901
+ }
25902
+ return versions;
25903
+ }
25904
+ function reportActionError2(err) {
25905
+ if (err instanceof ApiError) {
25906
+ if (err.status === 404) {
25907
+ console.error("Report not found, or it was not filed by you.");
25908
+ } else if (err.status === 403) {
25909
+ console.error("This report is agent-authored and can only be handled by a platform admin.");
25910
+ } else if (err.status === 409) {
25911
+ console.error(err.message);
25912
+ } else if (err.status === 429) {
25913
+ console.error("Rate limit exceeded. Please try again later.");
25914
+ } else {
25915
+ console.error(`Request failed: ${err.message}`);
25916
+ }
25917
+ return process.exit(1);
25918
+ }
25919
+ throw err;
25920
+ }
25921
+ function buildBasesReportCommand() {
25922
+ const report = new Command6("report").description(
25923
+ "File a report to the Data triage queue and verify the outcome"
25924
+ );
25925
+ report.command("create").description("Submit a report to the Data triage queue (deduplicated)").requiredOption("--title <title>", "Short summary").requiredOption("--description <text>", "What happened").option("--source <source>", "Origin: cli_report (default) | review | security_audit").option("--classification <c>", "Nature: bug (default) | flaky_test | security | enhancement").option(
25926
+ "--dedup-key <key>",
25927
+ 'Stable dedup key (e.g. "<file>::<test>"); collapses repeat occurrences'
25928
+ ).option("--severity <level>", "low | medium | high | critical").option("--steps <text>", "Steps to reproduce").option("--error-message <text>", "Exact error text if any").option("--context <text>", "Additional context (logs, request ids)").option("--locale <code>", "Notification locale (en | pt | es)", "en").option("--reporter-email <addr>", "Override reporter email (defaults to session email)").option("--base-id <id>", "Base the error relates to (defaults to the configured base)").option("--record-type <name>", "Record type the error relates to").option("--record-id <id>", "Record id (UUID) the error relates to").option("--external-id <id>", "External id of the related record").option("--external-source <src>", "External source of the related record").option("--relationship-id <id>", "Relationship id the error relates to").action(async function(opts) {
25929
+ const body = {
25930
+ title: opts.title,
25931
+ description: opts.description,
25932
+ locale: opts.locale
25933
+ };
25934
+ if (opts.source) body.source = opts.source;
25935
+ if (opts.classification) body.classification = opts.classification;
25936
+ if (opts.dedupKey) body.dedup_key = opts.dedupKey;
25937
+ if (opts.severity) body.severity = opts.severity;
25938
+ if (opts.steps) body.steps = opts.steps;
25939
+ if (opts.errorMessage) body.error_message = opts.errorMessage;
25940
+ if (opts.context) body.context = opts.context;
25941
+ if (opts.reporterEmail) body.reporter_email = opts.reporterEmail;
25942
+ const baseId = opts.baseId ?? findBase(this) ?? process.env.WAYAI_BASE;
25943
+ if (baseId) body.base_id = baseId;
25944
+ if (opts.recordType) body.record_type = opts.recordType;
25945
+ if (opts.recordId) body.record_id = opts.recordId;
25946
+ if (opts.externalId) body.external_id = opts.externalId;
25947
+ if (opts.externalSource) body.external_source = opts.externalSource;
25948
+ if (opts.relationshipId) body.relationship_id = opts.relationshipId;
25949
+ const versions = resolveReportClientVersions(
25950
+ findGitRoot() ?? process.cwd(),
25951
+ resolveCliVersion()
25952
+ );
25953
+ body.cli_version = versions.cli_version;
25954
+ if (versions.skill_version) body.skill_version = versions.skill_version;
25955
+ const client = await createDataClient();
25956
+ const data = await client.request("POST", "/v1/reports", body);
25957
+ if (outputFormat(this) === "json") {
25958
+ printOutput(data, "json");
25959
+ return;
25960
+ }
25961
+ if (data.created) {
25962
+ console.log(`Report submitted (id: ${data.report_id}).`);
25963
+ console.log("Our team will review and follow up by email if you provided one.");
25964
+ } else if (data.recurrence?.reopened) {
25965
+ console.log(`Report reopened for another look (id: ${data.report_id}).`);
25966
+ } else if (data.recurrence) {
25967
+ console.log(
25968
+ `A matching report remains closed (id: ${data.report_id}); this occurrence was recorded.`
25969
+ );
25970
+ } else {
25971
+ console.log(
25972
+ `A matching report already exists (id: ${data.report_id}) \u2014 your submission was merged into it.`
25973
+ );
25974
+ }
25975
+ });
25976
+ report.command("list").description("List your reports, newest first").option("--status <s>", "Filter by status (e.g. shipped \u2014 awaiting your verification)").action(async function(opts) {
25977
+ const client = await createDataClient();
25978
+ const qs = opts.status ? `?status=${encodeURIComponent(opts.status)}` : "";
25979
+ try {
25980
+ const rows = await client.request("GET", `/v1/reports${qs}`);
25981
+ if (outputFormat(this) === "json") {
25982
+ printOutput(rows, "json");
25983
+ return;
25984
+ }
25985
+ if (rows.length === 0) {
25986
+ console.log("No reports found.");
25987
+ return;
25988
+ }
25989
+ for (const r of rows) {
25990
+ const title = sanitizeTerminalText(r.title?.replace(/\s+/g, " ").trim() || "(untitled)");
25991
+ console.log(
25992
+ `${r.report_id} ${r.status.padEnd(10)} ${(r.classification ?? "bug").padEnd(12)} ${title}`
25993
+ );
25994
+ }
25995
+ } catch (err) {
25996
+ reportActionError2(err);
25997
+ }
25998
+ });
25999
+ report.command("get <report_id>").description("Show one of your reports: status + the message thread").action(async function(reportId) {
26000
+ const client = await createDataClient();
26001
+ try {
26002
+ const data = await client.request("GET", `/v1/reports/${pathSegment(reportId, "report id")}`);
26003
+ if (outputFormat(this) === "json") {
26004
+ printOutput(data, "json");
26005
+ return;
26006
+ }
26007
+ const { report: row, messages } = data;
26008
+ console.log(`Report ${row.report_id}`);
26009
+ console.log(` title: ${sanitizeTerminalText(row.title ?? "(unavailable)")}`);
26010
+ console.log(
26011
+ ` description: ${sanitizeTerminalText(row.description ?? "(unavailable)")}`
26012
+ );
26013
+ console.log(` status: ${row.status}`);
26014
+ console.log(` classification: ${row.classification ?? "bug"}`);
26015
+ if (row.contest_count > 0) console.log(` contests: ${row.contest_count}`);
26016
+ if (row.dismissal_final) console.log(" dismissal: final (non-contestable)");
26017
+ if (messages.length > 0) {
26018
+ console.log("\n Thread:");
26019
+ for (const m of messages) {
26020
+ console.log(` [${m.author_role}] ${sanitizeTerminalText(m.body)}`);
26021
+ }
26022
+ }
26023
+ if (row.status === "shipped") {
26024
+ console.log(
26025
+ `
26026
+ This fix is shipped. Run \`wayai bases report accept ${row.report_id}\` if it works,`
26027
+ );
26028
+ console.log(
26029
+ ` or \`wayai bases report contest ${row.report_id} --reason "..."\` if it does not.`
26030
+ );
26031
+ }
26032
+ } catch (err) {
26033
+ reportActionError2(err);
26034
+ }
26035
+ });
26036
+ report.command("accept <report_id>").description("Accept a shipped fix (\u2192 addressed)").action(async function(reportId) {
26037
+ const client = await createDataClient();
26038
+ try {
26039
+ const data = await client.request(
26040
+ "POST",
26041
+ `/v1/reports/${pathSegment(reportId, "report id")}/accept`
26042
+ );
26043
+ if (outputFormat(this) === "json") {
26044
+ printOutput(data, "json");
26045
+ return;
26046
+ }
26047
+ console.log(`Accepted. Report ${data.report.report_id} \u2192 ${data.report.status}.`);
26048
+ } catch (err) {
26049
+ reportActionError2(err);
26050
+ }
26051
+ });
26052
+ report.command("contest <report_id>").description("Contest a shipped fix or a dismissal (\u2192 back to triage)").requiredOption("--reason <text>", "Why the fix/dismissal is wrong").action(async function(reportId, opts) {
26053
+ const client = await createDataClient();
26054
+ try {
26055
+ const data = await client.request(
26056
+ "POST",
26057
+ `/v1/reports/${pathSegment(reportId, "report id")}/contest`,
26058
+ { reason: opts.reason }
26059
+ );
26060
+ if (outputFormat(this) === "json") {
26061
+ printOutput(data, "json");
26062
+ return;
26063
+ }
26064
+ console.log(
26065
+ `Contested. Report ${data.report.report_id} \u2192 ${data.report.status} (back to triage).`
26066
+ );
26067
+ } catch (err) {
26068
+ reportActionError2(err);
26069
+ }
26070
+ });
26071
+ return report;
26072
+ }
26073
+ var here;
26074
+ var init_report2 = __esm({
26075
+ "src/data/commands/report.ts"() {
26076
+ "use strict";
26077
+ init_client();
26078
+ init_output();
26079
+ init_helpers();
26080
+ init_api_client();
26081
+ init_terminal_output();
26082
+ init_workspace();
26083
+ init_skill_version();
26084
+ here = dirname9(fileURLToPath2(import.meta.url));
26085
+ }
26086
+ });
26087
+
26088
+ // src/data/commands/secrets.ts
26089
+ import { Command as Command7 } from "commander";
26090
+ import { readFileSync as readFileSync22 } from "fs";
26091
+ function withValueSourceOptions(cmd, what) {
26092
+ return cmd.option(
26093
+ "--file <path>",
26094
+ `Read ${what} from a file and base64-encode it (certificates, keystores, service-account JSON)`
26095
+ ).option("--value-stdin", `Read ${what} from stdin (recommended for CI)`).option("--value-prompt", `Prompt for ${what}, masked`);
26096
+ }
26097
+ async function resolveValue(opts, label) {
26098
+ if (opts.file !== void 0) {
26099
+ if (opts.valueStdin || opts.valuePrompt) {
26100
+ throw expected("--file cannot be combined with --value-stdin or --value-prompt \u2014 pass one.");
26101
+ }
26102
+ try {
26103
+ return readFileSync22(opts.file).toString("base64");
26104
+ } catch (e) {
26105
+ throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
26106
+ }
26107
+ }
26108
+ return readSecret(
26109
+ {
26110
+ stdin: opts.valueStdin,
26111
+ prompt: opts.valuePrompt,
26112
+ stdinFlag: "--value-stdin",
26113
+ promptHint: "--value-prompt"
26114
+ },
26115
+ label
26116
+ );
26117
+ }
26118
+ function listAllSecrets(client, expiringDays) {
26119
+ return client.collectPages((cursor) => {
26120
+ const params = new URLSearchParams({ limit: String(SECRET_LIST_PAGE_LIMIT) });
26121
+ if (expiringDays !== void 0) params.set("expiring_within_days", expiringDays);
26122
+ if (cursor) params.set("cursor", cursor);
26123
+ return `/v1/vault/secrets?${params.toString()}`;
26124
+ });
26125
+ }
26126
+ function buildBasesSecretsCommand() {
26127
+ const secrets = new Command7("secrets").description("Manage organization vault secrets");
26128
+ withValueSourceOptions(
26129
+ secrets.command("create").description("Store a vault secret (value read from stdin, a masked prompt, or --file)").requiredOption("--name <name>", "Secret name").option("--content-type <mime>", "MIME type of the value (e.g. application/x-pkcs12)").option(
26130
+ "--expires-at <date>",
26131
+ "Expiry as ISO-8601 (e.g. 2027-01-01T00:00:00Z) \u2014 surfaced by `list --expiring`"
26132
+ ).option("--tags <tags>", "Comma-separated tags"),
26133
+ "the secret value"
26134
+ ).action(async function(opts) {
26135
+ const body = {
26136
+ name: opts.name,
26137
+ value: await resolveValue(opts, "Secret value")
26138
+ };
26139
+ if (opts.contentType) body.content_type = opts.contentType;
26140
+ if (opts.expiresAt) body.expires_at = opts.expiresAt;
26141
+ if (opts.tags) body.tags = splitList(opts.tags);
26142
+ const client = await createDataClient();
26143
+ printOutput(await client.request("POST", "/v1/vault/secrets", body), outputFormat(this));
26144
+ });
26145
+ secrets.command("list").description("List vault secrets (values masked)").option("--expiring", "Only secrets expiring within --days (default 30)").option("--days <n>", "Window for --expiring, in days", "30").action(async function(opts) {
26146
+ const client = await createDataClient();
26147
+ const secretRows = await listAllSecrets(client, opts.expiring ? opts.days : void 0);
26148
+ printOutput(secretRows, outputFormat(this));
26149
+ });
26150
+ secrets.command("get <id>").description("Get vault secret metadata (value masked)").action(async function(id) {
26151
+ const client = await createDataClient();
26152
+ printOutput(
26153
+ await client.request("GET", `/v1/vault/secrets/${pathSegment(id, "secret id")}`),
26154
+ outputFormat(this)
26155
+ );
26156
+ });
26157
+ withValueSourceOptions(
26158
+ secrets.command("rotate <id>").description("Rotate a secret by installing a new caller-supplied value").option("--expires-at <date>", "Set the rotated credential's new expiry (ISO-8601)"),
26159
+ "the new secret value"
26160
+ ).action(async function(id, opts) {
26161
+ const body = { value: await resolveValue(opts, "New secret value") };
26162
+ if (opts.expiresAt) body.expires_at = opts.expiresAt;
26163
+ const client = await createDataClient();
26164
+ printOutput(
26165
+ await client.request("POST", `/v1/vault/secrets/${pathSegment(id, "secret id")}/rotate`, body),
26166
+ outputFormat(this)
26167
+ );
26168
+ });
26169
+ secrets.command("delete <id>").description("Delete a vault secret").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
26170
+ if (!opts.yes) {
26171
+ if (!isInteractive()) {
26172
+ throw expected("Refusing to delete without confirmation \u2014 pass --yes to delete non-interactively.");
26173
+ }
26174
+ if (!await confirm(`Delete vault secret ${id}? This cannot be undone.`)) {
26175
+ console.log("Aborted");
26176
+ return;
26177
+ }
26178
+ }
26179
+ const client = await createDataClient();
26180
+ await client.request("DELETE", `/v1/vault/secrets/${pathSegment(id, "secret id")}`);
26181
+ printOutput({ id, deleted: true }, outputFormat(this));
26182
+ });
26183
+ return secrets;
26184
+ }
26185
+ var SECRET_LIST_PAGE_LIMIT;
26186
+ var init_secrets = __esm({
26187
+ "src/data/commands/secrets.ts"() {
26188
+ "use strict";
26189
+ init_client();
26190
+ init_output();
26191
+ init_helpers();
26192
+ init_utils();
26193
+ init_expected();
26194
+ SECRET_LIST_PAGE_LIMIT = 250;
26195
+ }
26196
+ });
26197
+
26198
+ // src/data/commands/sql.ts
26199
+ import { Command as Command8 } from "commander";
26200
+ import { readFileSync as readFileSync23 } from "fs";
26201
+ function buildBasesSqlCommand() {
26202
+ return withBaseOption(new Command8("sql")).description("Execute a read-only SQL query against base data").argument("[query]", "SQL query (SELECT only)").option("--file <path>", "Read SQL from a file instead of the argument").option(
26203
+ "--param <kv...>",
26204
+ "Named parameters as key=value pairs (e.g. --param status=issued)"
26205
+ ).action(async function(queryArg, opts) {
26206
+ const base = pathSegment(requireBase(this), "--base");
26207
+ let query;
26208
+ if (opts.file) {
26209
+ try {
26210
+ query = readFileSync23(opts.file, "utf-8").trim();
26211
+ } catch (e) {
26212
+ throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
26213
+ }
26214
+ if (!query) throw expected(`--file ${opts.file} is empty \u2014 it must contain a SELECT query.`);
26215
+ } else if (queryArg) {
26216
+ query = queryArg;
26217
+ } else {
26218
+ throw expected("Provide a SQL query as an argument or via --file.");
26219
+ }
26220
+ const params = {};
26221
+ for (const kv of opts.param ?? []) {
26222
+ const eqIdx = kv.indexOf("=");
26223
+ if (eqIdx === -1) {
26224
+ throw expected(`Invalid --param ${JSON.stringify(kv)}, expected key=value.`);
26225
+ }
26226
+ params[kv.slice(0, eqIdx)] = kv.slice(eqIdx + 1);
26227
+ }
26228
+ const body = { query };
26229
+ if (Object.keys(params).length > 0) body.params = params;
26230
+ const client = await createDataClient();
26231
+ printOutput(await client.request("POST", `/v1/${base}/sql`, body), outputFormat(this));
26232
+ });
26233
+ }
26234
+ var init_sql = __esm({
26235
+ "src/data/commands/sql.ts"() {
26236
+ "use strict";
26237
+ init_client();
26238
+ init_output();
26239
+ init_helpers();
26240
+ init_expected();
26241
+ }
26242
+ });
26243
+
26244
+ // src/data/commands/tokens.ts
26245
+ import { Command as Command9 } from "commander";
26246
+ function addTokenLifecycleOptions(cmd) {
26247
+ return cmd.option("--expires-at <date>", "Expiration date (ISO 8601, e.g. 2026-06-01T00:00:00Z)").option(
26248
+ "--ttl <duration>",
26249
+ "Relative expiry, e.g. 10m, 2h, 7d (use for disposable probe/test tokens)"
26250
+ ).option(
26251
+ "--description <text>",
26252
+ 'What holds this token (e.g. "behind credential X"); shown in list and revoke warnings'
26253
+ ).addHelpText("after", TOKEN_OUTPUT_HELP);
26254
+ }
26255
+ function tokenCreationOutput(cmd, opts) {
26256
+ return opts.tokenOnly ? "token" : outputFormat(cmd);
26257
+ }
26258
+ function printCreatedToken(data, output) {
26259
+ if (output !== "token") {
26260
+ printOutput(data, output);
26261
+ return;
26262
+ }
26263
+ const token = typeof data === "object" && data !== null && "token" in data ? data.token : void 0;
26264
+ if (typeof token !== "string" || !RAW_TOKEN_PATTERN.test(token)) {
26265
+ throw new Error("Token creation response did not contain a valid rec_ token");
26266
+ }
26267
+ console.log(token);
26268
+ }
26269
+ function resolveExpiresAt(opts) {
26270
+ if (opts.ttl && opts.expiresAt) {
26271
+ throw expected("--ttl and --expires-at are mutually exclusive \u2014 pass one.");
26272
+ }
26273
+ if (opts.ttl) return new Date(Date.now() + parseDuration(opts.ttl, "--ttl")).toISOString();
26274
+ return opts.expiresAt;
26275
+ }
26276
+ function applyTokenLifecycleOpts(body, opts) {
26277
+ const expiresAt = resolveExpiresAt(opts);
26278
+ if (expiresAt) body.expires_at = expiresAt;
26279
+ if (opts.description) body.description = opts.description;
26280
+ }
26281
+ function listAllTokens(client) {
26282
+ return client.collectPages(
26283
+ (cursor) => `/v1/tokens?limit=${TOKEN_LIST_PAGE_LIMIT}${cursor ? `&cursor=${encodeURIComponent(cursor)}` : ""}`
26284
+ );
26285
+ }
26286
+ function pruneRow(t, reason) {
26287
+ return {
26288
+ token_id: t.token_id,
26289
+ name: t.name,
26290
+ reason,
26291
+ last_used: t.last_used_at ?? "never"
26292
+ };
26293
+ }
26294
+ function buildBasesTokensCommand() {
26295
+ const tokens = new Command9("tokens").description("Manage API tokens");
26296
+ addTokenLifecycleOptions(
26297
+ tokens.command("create").description("Create a scoped API token").requiredOption("--name <name>", "Token name").requiredOption("--grants <json>", "Grant definitions as JSON array").option(
26298
+ "--token-only",
26299
+ "Print only the raw rec_ token for secure command substitution (shown once)"
26300
+ )
26301
+ ).action(async function(opts) {
26302
+ const grants = parseData(opts.grants, "--grants");
26303
+ const body = { name: opts.name, grants };
26304
+ applyTokenLifecycleOpts(body, opts);
26305
+ const client = await createDataClient();
26306
+ const data = await client.request("POST", "/v1/tokens", body);
26307
+ printCreatedToken(data, tokenCreationOutput(this, opts));
26308
+ });
26309
+ addTokenLifecycleOptions(
26310
+ // The only token verb that addresses a base, so it alone declares `--base`.
26311
+ // Putting it on the `tokens` group would advertise a flag that does nothing
26312
+ // on `list`, `revoke` and `prune` — the defect `withBaseOption` exists to
26313
+ // avoid.
26314
+ withBaseOption(tokens.command("create-for-toolset <slug>")).description(
26315
+ "Create a toolset-bound (consumer) token whose authorization IS the toolset tool surface \u2014 it can call exactly the tools that toolset exposes (those record types + operations, relationships, batch, and sql only if enabled) and nothing else. Binds to the configured --base."
26316
+ ).option("--name <name>", 'Token name (default: "<slug> consumer")').option(
26317
+ "--token-only",
26318
+ "Print only the raw rec_ token for secure command substitution (shown once)"
26319
+ )
26320
+ ).action(async function(slug, opts) {
26321
+ const base = requireBase(this);
26322
+ const body = {
26323
+ name: opts.name ?? `${slug} consumer`,
26324
+ toolset_binding: { base_id: base, toolset_id: slug }
26325
+ };
26326
+ applyTokenLifecycleOpts(body, opts);
26327
+ const client = await createDataClient();
26328
+ const data = await client.request("POST", "/v1/tokens", body);
26329
+ const output = tokenCreationOutput(this, opts);
26330
+ printCreatedToken(data, output);
26331
+ if (output === "table") {
26332
+ console.log(`
26333
+ MCP URL: ${toolsetMcpUrl(slug)}`);
26334
+ }
26335
+ });
26336
+ tokens.command("list").description("List API tokens (status, last_used_at, expires_at, description)").action(async function() {
26337
+ const client = await createDataClient();
26338
+ printOutput(await listAllTokens(client), outputFormat(this));
26339
+ });
26340
+ tokens.command("revoke <token_id>").description(
26341
+ "Revoke an API token (permanent). A recently-used or toolset-bound token requires --force."
26342
+ ).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
26343
+ const client = await createDataClient();
26344
+ const id = pathSegment(tokenId, "token id");
26345
+ const path31 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
26346
+ const format = outputFormat(this);
26347
+ const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
26348
+ if (opts.force) {
26349
+ await client.request("DELETE", path31(true));
26350
+ revoked(true);
26351
+ return;
26352
+ }
26353
+ try {
26354
+ await client.request("DELETE", path31(false));
26355
+ revoked(false);
26356
+ } catch (err) {
26357
+ if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
26358
+ throw err;
26359
+ }
26360
+ console.error(err.message);
26361
+ if (!isInteractive()) {
26362
+ console.error("\nRe-run with --force to revoke anyway.");
26363
+ return process.exit(1);
26364
+ }
26365
+ if (!await confirm("Revoke anyway?")) {
26366
+ console.error("Aborted.");
26367
+ return process.exit(1);
26368
+ }
26369
+ await client.request("DELETE", path31(true));
26370
+ revoked(true);
26371
+ }
26372
+ });
26373
+ tokens.command("prune").description(
26374
+ "Revoke stale tokens, selected by staleness only (expired, or unused past --unused-since) \u2014 never by name. Previews by default; revoking requires --yes or an interactive confirmation."
26375
+ ).option(
26376
+ "--unused-since <duration>",
26377
+ "Also prune tokens with no activity for this long (e.g. 30d). Skips toolset-bound and legacy usage-untracked tokens."
26378
+ ).option("--include-bound", "Allow pruning stale toolset-bound (consumer) tokens").option("-y, --yes", "Revoke without prompting (required when non-interactive)").action(async function(opts) {
26379
+ const staleMs = opts.unusedSince ? parseDuration(opts.unusedSince, "--unused-since") : void 0;
26380
+ const client = await createDataClient();
26381
+ const allTokens = await listAllTokens(client);
26382
+ const now = Date.now();
26383
+ const targets = [];
26384
+ const skipped = [];
26385
+ for (const t of allTokens) {
26386
+ const expired = t.status ? t.status === "expired" : !!t.expires_at && Date.parse(t.expires_at) <= now;
26387
+ if (expired) {
26388
+ targets.push(pruneRow(t, "expired"));
26389
+ continue;
26390
+ }
26391
+ if (staleMs === void 0) continue;
26392
+ const lastActivity = Date.parse(t.last_used_at ?? t.created_at);
26393
+ if (!(now - lastActivity > staleMs)) continue;
26394
+ if (t.usage_tracked !== true) {
26395
+ skipped.push(
26396
+ pruneRow(
26397
+ t,
26398
+ "usage untracked (legacy token) \u2014 revoke individually if truly unused"
26399
+ )
26400
+ );
26401
+ } else if (t.toolset_binding && !opts.includeBound) {
26402
+ skipped.push(
26403
+ pruneRow(
26404
+ t,
26405
+ `toolset-bound ('${t.toolset_binding.toolset_id}') \u2014 pass --include-bound to prune`
26406
+ )
26407
+ );
26408
+ } else {
26409
+ targets.push(pruneRow(t, `unused for over ${opts.unusedSince}`));
26410
+ }
26411
+ }
26412
+ const format = outputFormat(this);
26413
+ const json = format === "json";
26414
+ const note = (line) => json ? console.error(line) : console.log(line);
26415
+ const printPreview = () => {
26416
+ if (targets.length === 0) {
26417
+ note("Nothing to prune.");
26418
+ } else {
26419
+ note(`Would revoke ${targets.length} token(s):`);
26420
+ if (!json) printOutput(targets, format);
26421
+ }
26422
+ if (skipped.length === 0) return;
26423
+ note(`
26424
+ Skipped ${skipped.length} stale token(s):`);
26425
+ if (!json) printOutput(skipped, format);
26426
+ };
26427
+ printPreview();
26428
+ if (targets.length === 0) {
26429
+ if (json) printOutput({ revoked: 0, failed: 0, targets, skipped }, format);
26430
+ return;
26431
+ }
26432
+ if (!opts.yes) {
26433
+ if (!isInteractive()) {
26434
+ console.error("\nPreview only \u2014 re-run with --yes to revoke these tokens.");
26435
+ if (json) printOutput({ preview: true, revoked: 0, failed: 0, targets, skipped }, format);
26436
+ return process.exit(1);
26437
+ }
26438
+ if (!await confirm(`
26439
+ Revoke ${targets.length} token(s)?`)) {
26440
+ console.error("Aborted.");
26441
+ return process.exit(1);
26442
+ }
26443
+ }
26444
+ let revoked = 0;
26445
+ let failed = 0;
26446
+ for (const t of targets) {
26447
+ try {
26448
+ await client.request(
26449
+ "DELETE",
26450
+ `/v1/tokens/${pathSegment(t.token_id, "token id")}?force=true`
26451
+ );
26452
+ revoked++;
26453
+ } catch (err) {
26454
+ failed++;
26455
+ console.error(
26456
+ `Failed to revoke ${t.token_id} ('${sanitizeTerminalText(t.name)}'): ${err instanceof Error ? err.message : String(err)}`
26457
+ );
26458
+ }
26459
+ }
26460
+ if (json) printOutput({ revoked, failed, targets, skipped }, format);
26461
+ else console.log(`Revoked ${revoked} token(s)${failed > 0 ? `, ${failed} failed` : ""}.`);
26462
+ if (failed > 0) return process.exit(1);
26463
+ });
26464
+ return tokens;
26465
+ }
26466
+ var RAW_TOKEN_PATTERN, TOKEN_OUTPUT_HELP, TOKEN_LIST_PAGE_LIMIT;
26467
+ var init_tokens = __esm({
26468
+ "src/data/commands/tokens.ts"() {
26469
+ "use strict";
26470
+ init_client();
26471
+ init_output();
26472
+ init_helpers();
26473
+ init_api_client();
26474
+ init_terminal_output();
26475
+ init_utils();
26476
+ init_expected();
26477
+ RAW_TOKEN_PATTERN = /^rec_[A-Za-z0-9_-]{43}$/;
26478
+ TOKEN_OUTPUT_HELP = `
26479
+ Output:
26480
+ --json prints the unwrapped response object; the secret is at top-level .token, never .data.token.
26481
+ --token-only prints only a validated rec_ token and overrides table/JSON formatting.
26482
+ The raw token is shown once. Capture it securely and do not log it.
26483
+ `;
26484
+ TOKEN_LIST_PAGE_LIMIT = 250;
26485
+ }
26486
+ });
26487
+
26488
+ // src/lib/base-binding.ts
26489
+ var binding2, getBaseBindingPath, readBaseBinding, writeBaseBinding, clearBaseBinding, autoBindBaseIfUnbound, assertBaseMatchesBinding, assertNoBindingBlocksBaseCreation;
26490
+ var init_base_binding = __esm({
26491
+ "src/lib/base-binding.ts"() {
26492
+ "use strict";
26493
+ init_base_id();
26494
+ init_worktree_binding();
26495
+ binding2 = createWorktreeBinding({
26496
+ filename: "wayai-base-binding",
26497
+ isValidId: isValidBaseId,
26498
+ noun: "base",
26499
+ idLabel: "base id (must be a slug)",
26500
+ unbindCommand: "wayai bases unbind",
26501
+ useCommand: "wayai bases use"
26502
+ });
26503
+ getBaseBindingPath = binding2.getBindingPath;
26504
+ readBaseBinding = binding2.readBinding;
26505
+ writeBaseBinding = binding2.writeBinding;
26506
+ clearBaseBinding = binding2.clearBinding;
26507
+ autoBindBaseIfUnbound = binding2.autoBindIfUnbound;
26508
+ assertBaseMatchesBinding = binding2.assertMatchesBinding;
26509
+ assertNoBindingBlocksBaseCreation = binding2.assertNoBindingBlocksCreation;
26510
+ }
26511
+ });
26512
+
26513
+ // src/data/commands/bases.ts
26514
+ import { Command as Command10 } from "commander";
26515
+ import * as fs24 from "fs";
26516
+ import * as path30 from "path";
26517
+ import * as yaml9 from "js-yaml";
26518
+ function pageOf(path31, cursor) {
26519
+ if (!cursor) return path31;
26520
+ return `${path31}${path31.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
26521
+ }
26522
+ function parseEnum(flag, value, allowed) {
26523
+ if (value === void 0) return void 0;
26524
+ if (!allowed.includes(value)) {
26525
+ throw expected(`${flag} must be one of: ${allowed.join(", ")}.`);
26526
+ }
26527
+ return value;
26528
+ }
26529
+ function buildBasesCommand() {
26530
+ const bases = new Command10("bases").description("Manage bases");
26531
+ bases.command("list").description("List all bases").option("--tag <tag>", "Filter by tag").action(async function(opts) {
26532
+ const client = await createDataClient();
26533
+ const qs = opts.tag ? `?tag=${encodeURIComponent(opts.tag)}` : "";
26534
+ printOutput(
26535
+ await client.collectPages((cursor) => pageOf(`/v1/bases${qs}`, cursor)),
26536
+ outputFormat(this)
26537
+ );
26538
+ });
26539
+ bases.command("get <id>").description("Get a base").action(async function(id) {
26540
+ const client = await createDataClient();
26541
+ printOutput(await client.request("GET", `/v1/bases/${pathSegment(id)}`), outputFormat(this));
26542
+ });
26543
+ bases.command("create <id>").description("Create a base").requiredOption("--name <name>", "Base name").option("--description <desc>", "Description").option("--tags <tags>", "Comma-separated tags (e.g. client:acme,billing)").option(
26544
+ "--timezone <tz>",
26545
+ "Default IANA timezone for datetime canonicalization (e.g. America/Sao_Paulo); a record type's own timezone overrides it"
26546
+ ).option(
26547
+ "--environment <env>",
26548
+ "production (default) | preview. Production bases require a paid plan. A preview created here has no production origin and cannot be given one later, so it cannot be promoted directly \u2014 move its config to a linked preview with pull/push when you upgrade"
26549
+ ).action(async function(id, opts) {
26550
+ const environment = parseEnum("--environment", opts.environment, [
26551
+ "production",
26552
+ "preview"
26553
+ ]);
26554
+ const client = await createDataClient();
26555
+ const body = { name: opts.name, description: opts.description };
26556
+ if (environment) body.environment = environment;
26557
+ if (opts.tags) body.tags = splitList(opts.tags);
26558
+ if (opts.timezone) body.settings = { timezone: opts.timezone };
26559
+ printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
26560
+ });
26561
+ bases.command("update <id>").description(
26562
+ "Update a base's mutable config (display name, description, tags, default timezone/settings). The id/slug is immutable."
26563
+ ).option("--name <name>", "New display name").option("--description <desc>", "Description").option("--tags <tags>", "Comma-separated tags \u2014 replaces the existing tags").option(
26564
+ "--timezone <tz>",
26565
+ "Default IANA timezone for datetime canonicalization. Merges into existing settings."
26566
+ ).option(
26567
+ "--settings <json>",
26568
+ "Raw settings JSON object (inline JSON or @file.json) \u2014 replaces the entire settings object"
26569
+ ).option(
26570
+ "--integrations <mode>",
26571
+ "enabled | disabled. Preview-only: `disabled` neutralizes every external integration edge so the seeded preview behaves natively for production-safe agent evals"
26572
+ ).action(async function(id, opts) {
26573
+ const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
26574
+ if (opts.name === void 0 && opts.description === void 0 && opts.tags === void 0 && opts.timezone === void 0 && opts.settings === void 0 && integrations === void 0) {
26575
+ throw expected(
26576
+ "Nothing to update. Provide at least one of --name, --description, --tags, --timezone, --settings, --integrations."
26577
+ );
26578
+ }
26579
+ const client = await createDataClient();
26580
+ const body = {};
26581
+ if (opts.name !== void 0) body.name = opts.name;
26582
+ if (opts.description !== void 0) body.description = opts.description;
26583
+ if (opts.tags !== void 0) body.tags = splitList(opts.tags);
26584
+ if (integrations !== void 0) body.integrations = integrations;
26585
+ if (opts.settings !== void 0 || opts.timezone !== void 0) {
26586
+ let settings;
26587
+ if (opts.settings !== void 0) {
26588
+ let parsed;
26589
+ try {
26590
+ parsed = parseData(opts.settings);
26591
+ } catch (e) {
26592
+ throw expected(`--settings: ${e instanceof Error ? e.message : "could not be read as JSON"}`);
26593
+ }
26594
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
26595
+ throw expected("--settings must be a valid JSON object");
26596
+ }
26597
+ settings = parsed;
26598
+ } else {
26599
+ const existing = await client.request("GET", `/v1/bases/${pathSegment(id)}`);
26600
+ settings = { ...existing?.settings ?? {} };
26601
+ }
26602
+ if (opts.timezone !== void 0) settings.timezone = opts.timezone;
26603
+ body.settings = settings;
26604
+ }
26605
+ printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
26606
+ });
26607
+ bases.command("rename <id>").description(
26608
+ "Rename a base's display name (the id/slug is immutable \u2014 it scopes token grants, MCP endpoints, and references)"
26609
+ ).requiredOption("--name <name>", "New display name").action(async function(id, opts) {
26610
+ const client = await createDataClient();
26611
+ printOutput(
26612
+ await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { name: opts.name }),
26613
+ outputFormat(this)
26614
+ );
26615
+ });
26616
+ bases.command("tag <id>").description("Set tags on a base").requiredOption("--tags <tags>", "Comma-separated tags (e.g. client:acme,billing)").action(async function(id, opts) {
26617
+ const client = await createDataClient();
26618
+ printOutput(
26619
+ await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { tags: splitList(opts.tags) }),
26620
+ outputFormat(this)
26621
+ );
26622
+ });
26623
+ bases.command("delete <id>").description(
26624
+ "Delete a base. By default this is a tombstone: the base stops being listed, but its storage is retained and recreating the id restores it. Add --purge (preview bases only) to also reclaim the storage"
26625
+ ).option("-y, --yes", "Skip confirmation prompt").option(
26626
+ "--purge",
26627
+ "Preview bases only. Permanently destroy the stored records, relationships, file metadata and config of this base instead of tombstoning it. The id is then retired and cannot be recreated"
26628
+ ).action(async function(id, opts) {
26629
+ const prompt3 = opts.purge ? `Purge base "${id}"? Its records, relationships, file metadata and config are destroyed permanently, and the id "${id}" is retired for good.` : `Delete base "${id}"? This cannot be undone.`;
26630
+ if (!opts.yes && !await confirm(prompt3)) {
26631
+ console.log("Aborted");
26632
+ return;
26633
+ }
26634
+ const client = await createDataClient();
26635
+ await client.request("DELETE", `/v1/bases/${pathSegment(id)}${opts.purge ? "?purge=true" : ""}`);
26636
+ printOutput({ id, deleted: true, purged: opts.purge === true }, outputFormat(this));
26637
+ });
26638
+ bases.command("create-preview <origin-id>").description(
26639
+ "Create a preview base by cloning the config of another base. The origin may be a production base or another preview; the new preview id is <origin-id>--<name>, linked to the origin it was cloned from (so a preview of a preview promotes through that origin, never straight to production)"
26640
+ ).requiredOption("--name <name>", "Preview base name").option("--description <desc>", "Description").option(
26641
+ "--integrations <mode>",
26642
+ "enabled (default) | disabled. `disabled` makes this preview a seeded, production-safe agent-eval target: every external integration edge is inert and it behaves natively"
26643
+ ).option(
26644
+ "--create-only",
26645
+ "Fail with 409 if the derived preview id already exists, instead of re-applying config onto it. Use for per-session ephemeral bases, where landing on a live sibling would corrupt both runs"
26646
+ ).action(async function(originId, opts) {
26647
+ const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
26648
+ const client = await createDataClient();
26649
+ printOutput(
26650
+ await client.request("POST", `/v1/${pathSegment(originId, "origin base id")}/preview`, {
26651
+ name: opts.name,
26652
+ description: opts.description,
26653
+ ...integrations !== void 0 ? { integrations } : {},
26654
+ ...opts.createOnly ? { create_only: true } : {}
26655
+ }),
26656
+ outputFormat(this)
26657
+ );
26658
+ });
26659
+ bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
26660
+ const client = await createDataClient();
26661
+ const path31 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
26662
+ printOutput(
26663
+ await client.collectPages((cursor) => pageOf(path31, cursor)),
26664
+ outputFormat(this)
26665
+ );
26666
+ });
26667
+ bases.command("promote <production-id>").description("Promote config from a preview base to production (human-only)").requiredOption("--from <preview-id>", "Source preview base id").option("--dry-run", "Show what would change without applying").option("--record-types <ids>", "Comma-separated record type ids to promote", splitList).option("--triggers <ids>", "Comma-separated trigger ids to promote", splitList).option("--inbound-webhooks <ids>", "Comma-separated inbound webhook ids to promote", splitList).action(async function(productionId, opts) {
26668
+ const client = await createDataClient();
26669
+ const data = await client.request("POST", `/v1/${pathSegment(productionId, "production base id")}/promote`, {
26670
+ source_base_id: opts.from,
26671
+ dry_run: opts.dryRun ?? false,
26672
+ record_types: opts.recordTypes,
26673
+ triggers: opts.triggers,
26674
+ inbound_webhooks: opts.inboundWebhooks
26675
+ });
26676
+ printOutput(data, outputFormat(this));
26677
+ const warnings = data?.source_credential_warnings ?? [];
26678
+ if (warnings.length > 0) {
26679
+ console.warn(
26680
+ "\nExternal source credentials are NOT carried over by promotion \u2014 set these on production before the source will work:"
26681
+ );
26682
+ for (const w of warnings) console.warn(` - ${sanitizeTerminalText(w.message)}`);
26683
+ }
26684
+ });
26685
+ bases.command("rollback <production-id>").description("Roll back a promotion (human-only)").requiredOption("--promotion <promotion-id>", "Promotion id to roll back").action(async function(productionId, opts) {
26686
+ const client = await createDataClient();
26687
+ printOutput(
26688
+ await client.request("POST", `/v1/${pathSegment(productionId, "production base id")}/promote/rollback`, {
26689
+ promotion_id: opts.promotion
26690
+ }),
26691
+ outputFormat(this)
26692
+ );
26693
+ });
26694
+ bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
26695
+ const client = await createDataClient();
26696
+ const path31 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
26697
+ printOutput(
26698
+ await client.collectPages((cursor) => pageOf(path31, cursor)),
26699
+ outputFormat(this)
26700
+ );
26701
+ });
26702
+ bases.command("use <base>").description(
26703
+ "Bind this worktree to a base so push/pull refuse to run against a different one"
26704
+ ).action(async function(selector) {
26705
+ const baseId = resolveSelectorToBaseId(selector);
26706
+ const previous = readBaseBinding();
26707
+ if (previous !== baseId) {
26708
+ try {
26709
+ writeBaseBinding(baseId);
26710
+ } catch (err) {
26711
+ throw expected(err instanceof Error ? err.message : String(err));
26712
+ }
26713
+ }
26714
+ if (outputFormat(this) === "json") {
26715
+ printOutput({ base_id: baseId, previous_base_id: previous, changed: previous !== baseId }, "json");
26716
+ return;
26717
+ }
26718
+ if (previous === baseId) {
26719
+ console.log(`Worktree already bound to base ${baseId}.`);
26720
+ } else {
26721
+ console.log(
26722
+ previous ? `Worktree rebound: ${previous} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
26723
+ );
26724
+ }
26725
+ });
26726
+ bases.command("unbind").description("Clear the base binding for this worktree").action(async function() {
26727
+ const previous = readBaseBinding();
26728
+ if (previous) clearBaseBinding();
26729
+ if (outputFormat(this) === "json") {
26730
+ printOutput({ previous_base_id: previous, cleared: previous !== null }, "json");
26731
+ return;
26732
+ }
26733
+ console.log(
26734
+ previous ? `Worktree unbound from base (was: ${previous}).` : "No base binding to clear."
26735
+ );
26736
+ });
26737
+ bases.addCommand(buildBasesTokensCommand());
26738
+ bases.addCommand(buildBasesSecretsCommand());
26739
+ bases.addCommand(buildBasesSqlCommand());
26740
+ bases.addCommand(buildBasesImportCommand());
26741
+ bases.addCommand(buildBasesBatchCommand());
26742
+ bases.addCommand(buildBasesProvidersCommand());
26743
+ bases.addCommand(buildBasesReportCommand());
26744
+ return bases;
26745
+ }
26746
+ function resolveSelectorToBaseId(selector) {
26747
+ const gitRoot = findGitRoot();
26748
+ if (!gitRoot || selector.includes("/") || selector.includes(path30.sep)) return selector;
26749
+ if (!isValidBaseId(selector)) return selector;
26750
+ const metaPath = path30.join(resolveBasesDir(gitRoot), selector, BASE_META_FILE);
26751
+ let meta;
26752
+ try {
26753
+ meta = yaml9.load(fs24.readFileSync(metaPath, "utf-8"));
26754
+ } catch {
26755
+ return selector;
26756
+ }
26757
+ const id = meta?.[BASE_ID_FIELD];
26758
+ return typeof id === "string" && isValidBaseId(id) ? id : selector;
26759
+ }
26760
+ var BASE_META_FILE, BASE_ID_FIELD;
26761
+ var init_bases = __esm({
26762
+ "src/data/commands/bases.ts"() {
26763
+ "use strict";
26764
+ init_batch();
26765
+ init_import();
26766
+ init_providers();
26767
+ init_report2();
26768
+ init_secrets();
26769
+ init_sql();
26770
+ init_tokens();
26771
+ init_client();
26772
+ init_output();
26773
+ init_helpers();
26774
+ init_utils();
26775
+ init_terminal_output();
26776
+ init_workspace();
26777
+ init_layout();
26778
+ init_base_binding();
26779
+ init_base_id();
26780
+ init_expected();
26781
+ BASE_META_FILE = "base.yaml";
26782
+ BASE_ID_FIELD = "base_id";
26783
+ }
26784
+ });
26785
+
26786
+ // src/data/commands/file-types.ts
26787
+ import { Command as Command11 } from "commander";
26788
+ function buildFileTypesCommand() {
26789
+ const fileTypes = new Command11("file-types").description(
26790
+ "Manage file types (Buckets) \u2014 storage policy + optional metadata schema for files"
26791
+ );
26792
+ fileTypes.command("list").description("List all file types in a base").action(async function() {
26793
+ const base = pathSegment(requireBase(this), "--base");
26794
+ const client = await createDataClient();
26795
+ printOutput(await client.request("GET", `/v1/${base}/file-types`), outputFormat(this));
26796
+ });
26797
+ fileTypes.command("get <id>").description("Get a file type").action(async function(id) {
26798
+ const base = pathSegment(requireBase(this), "--base");
26799
+ const client = await createDataClient();
26800
+ printOutput(await client.request("GET", `/v1/${base}/file-types/${pathSegment(id, "file_type")}`), outputFormat(this));
26801
+ });
26802
+ fileTypes.command("upsert <id>").description("Create or update a file type (preview bases only)").requiredOption("--name <name>", "File type name").option("--description <desc>", "Description").option("--max-size <bytes>", "Max bytes per file (0 = unlimited)").option(
26803
+ "--allowed-content-types <json>",
26804
+ "Allowed content-types, JSON array (inline or @filename)"
26805
+ ).option("--metadata-schema <json>", "JSON Schema for file metadata (inline JSON or @filename)").action(async function(id, opts) {
26806
+ const base = pathSegment(requireBase(this), "--base");
26807
+ const client = await createDataClient();
26808
+ const body = { name: opts.name };
26809
+ if (opts.description) body.description = opts.description;
26810
+ if (opts.maxSize !== void 0) body.max_size = parseByteCount("--max-size", opts.maxSize);
26811
+ if (opts.allowedContentTypes) {
26812
+ body.allowed_content_types = parseData(opts.allowedContentTypes);
26813
+ }
26814
+ if (opts.metadataSchema) body.metadata_schema = parseData(opts.metadataSchema);
26815
+ printOutput(
26816
+ await client.request("PUT", `/v1/${base}/file-types/${pathSegment(id, "file_type")}`, body),
26817
+ outputFormat(this)
26818
+ );
26819
+ });
26820
+ fileTypes.command("delete <id>").description("Delete a file type (cascades its files)").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
26821
+ if (!opts.yes && !await confirm(
26822
+ `Delete file type "${id}"? This cascades its files and cannot be undone.`
26823
+ )) {
26824
+ console.log("Aborted");
26825
+ return;
26826
+ }
26827
+ const base = pathSegment(requireBase(this), "--base");
26828
+ const client = await createDataClient();
26829
+ await client.request("DELETE", `/v1/${base}/file-types/${pathSegment(id, "file_type")}`);
26830
+ console.log("Deleted");
26831
+ });
26832
+ return fileTypes;
26833
+ }
26834
+ var init_file_types = __esm({
26835
+ "src/data/commands/file-types.ts"() {
26836
+ "use strict";
26837
+ init_client();
26838
+ init_output();
26839
+ init_helpers();
26840
+ init_utils();
26841
+ }
26842
+ });
26843
+
26844
+ // src/data/commands/files.ts
26845
+ import { Command as Command12 } from "commander";
26846
+ import { readFileSync as readFileSync25, writeFileSync as writeFileSync16 } from "fs";
26847
+ import { basename as basename14 } from "path";
26848
+ function renderFileDiff(fileType, filePath, from, to, d) {
26849
+ console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
26850
+ const md = d.metadata_delta;
26851
+ if (md) {
26852
+ for (const f of ["path", "size", "content_type", "label"]) {
26853
+ const change = md[f];
26854
+ if (change) console.log(sanitizeTerminalText(` ${f}: ${JSON.stringify(change.from)} \u2192 ${JSON.stringify(change.to)}`));
26855
+ }
26856
+ const meta = md.metadata;
26857
+ for (const [k, v] of Object.entries(meta?.added ?? {})) {
26858
+ console.log(sanitizeTerminalText(` metadata + ${k}: ${JSON.stringify(v)}`));
26859
+ }
26860
+ for (const k of Object.keys(meta?.removed ?? {})) console.log(sanitizeTerminalText(` metadata - ${k}`));
26861
+ for (const [k, v] of Object.entries(meta?.changed ?? {})) {
26862
+ console.log(sanitizeTerminalText(` metadata ~ ${k}: ${JSON.stringify(v.from)} \u2192 ${JSON.stringify(v.to)}`));
26863
+ }
26864
+ }
26865
+ const content = d.content;
26866
+ if (!content?.changed) {
26867
+ console.log(" content: unchanged");
26868
+ return;
26869
+ }
26870
+ if (content.kind === "text" && content.lines) {
26871
+ console.log("");
26872
+ for (const l of content.lines) console.log(sanitizeTerminalText(`${l.op} ${l.text}`));
26873
+ } else {
26874
+ console.log(sanitizeTerminalText(` content: changed (${content.kind})`));
26875
+ }
26876
+ }
26877
+ function downloadTarget(remotePath, to) {
26878
+ if (to) return to;
26879
+ const derived = basename14(remotePath);
26880
+ if (derived === "" || derived === "." || derived === "..") {
26881
+ throw expected(
26882
+ `Cannot derive a local filename from "${remotePath}" \u2014 pass --to <local> to name it.`
26883
+ );
26884
+ }
26885
+ return derived;
26886
+ }
26887
+ function buildFilesCommand() {
26888
+ const files = new Command12("files").description(
26889
+ "Manage files (path-addressed, versioned content) within a file type"
26890
+ );
26891
+ files.command("put <file_type> <path>").description(
26892
+ "Upload a local file to a path (e.g. wayai files put reports q3/summary.pdf --file ./summary.pdf)"
26893
+ ).requiredOption("--file <local>", "Local file to upload").option("--content-type <type>", "MIME type", "application/octet-stream").action(async function(fileType, filePath, opts) {
26894
+ const base = pathSegment(requireBase(this), "--base");
26895
+ const body = readFileSync25(opts.file);
26896
+ const client = await createDataClient();
26897
+ printOutput(
26898
+ await client.upload(
26899
+ `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}`,
26900
+ body,
26901
+ opts.contentType
26902
+ ),
26903
+ outputFormat(this)
26904
+ );
26905
+ });
26906
+ files.command("get <file_type> <path>").description(
26907
+ "Download a file (or --meta for metadata only). --version N reads a specific retained content version."
26908
+ ).option("--to <local>", "Local file to save to (defaults to the file basename)").option("--meta", "Print metadata JSON instead of downloading the content").option("--version <n>", "Read a specific content version (default: latest)").action(async function(fileType, filePath, opts) {
26909
+ const encoded = pathSegments(filePath);
26910
+ const versionQs = opts.version !== void 0 ? `version=${encodeURIComponent(opts.version)}` : "";
26911
+ const base = pathSegment(requireBase(this), "--base");
26912
+ if (opts.meta) {
26913
+ const qs = ["meta=1", versionQs].filter(Boolean).join("&");
26914
+ const client2 = await createDataClient();
26915
+ printOutput(
26916
+ await client2.request("GET", `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${encoded}?${qs}`),
26917
+ outputFormat(this)
26918
+ );
26919
+ return;
26920
+ }
26921
+ const out = downloadTarget(filePath, opts.to);
26922
+ const client = await createDataClient();
26923
+ const { bytes } = await client.download(
26924
+ `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${encoded}${versionQs ? `?${versionQs}` : ""}`
26925
+ );
26926
+ writeFileSync16(out, bytes);
26927
+ console.log(`Downloaded to ${out}`);
26928
+ });
26929
+ files.command("history <file_type> <path>").description("List the content versions of a file (newest first)").option("--limit <n>", "Max versions to return").option("--offset <n>", "Pagination offset").action(async function(fileType, filePath, opts) {
26930
+ const base = pathSegment(requireBase(this), "--base");
26931
+ const client = await createDataClient();
26932
+ const qs = new URLSearchParams({ history: "1" });
26933
+ if (opts.limit) qs.set("limit", opts.limit);
26934
+ if (opts.offset) qs.set("offset", opts.offset);
26935
+ printOutput(
26936
+ await client.request(
26937
+ "GET",
26938
+ `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}?${qs}`
26939
+ ),
26940
+ outputFormat(this)
26941
+ );
26942
+ });
26943
+ files.command("diff <file_type> <path>").description("Diff two content versions of a file \u2014 metadata delta plus a text content diff").requiredOption("--from <n>", "Base version number").requiredOption("--to <n>", "Target version number").action(async function(fileType, filePath, opts) {
26944
+ const base = pathSegment(requireBase(this), "--base");
26945
+ const client = await createDataClient();
26946
+ const qs = new URLSearchParams({ diff: "1", from: opts.from, to: opts.to });
26947
+ const data = await client.request(
26948
+ "GET",
26949
+ `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}?${qs}`
26950
+ );
26951
+ if (outputFormat(this) === "json") {
26952
+ printOutput(data, "json");
26953
+ return;
26954
+ }
26955
+ renderFileDiff(fileType, filePath, opts.from, opts.to, data);
26956
+ });
26957
+ files.command("list <file_type>").description("List files by path prefix").option("--prefix <path>", "Path prefix filter (e.g. q3/)").option("--depth <n>", "Max folder depth below the prefix (1 = immediate children)").option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").action(async function(fileType, opts) {
26958
+ const base = pathSegment(requireBase(this), "--base");
26959
+ const client = await createDataClient();
26960
+ const qs = new URLSearchParams();
26961
+ if (opts.prefix) qs.set("prefix", opts.prefix);
26962
+ if (opts.depth) qs.set("depth", opts.depth);
26963
+ if (opts.limit) qs.set("limit", opts.limit);
26964
+ if (opts.offset) qs.set("offset", opts.offset);
26965
+ const q = qs.toString();
26966
+ printOutput(
26967
+ await client.request("GET", `/v1/${base}/files/${pathSegment(fileType, "file_type")}${q ? `?${q}` : ""}`),
26968
+ outputFormat(this)
26969
+ );
26970
+ });
26971
+ files.command("mv <file_type> <from> <to>").description("Rename/move a file (the blob is untouched)").action(async function(fileType, from, to) {
26972
+ const base = pathSegment(requireBase(this), "--base");
26973
+ const client = await createDataClient();
26974
+ printOutput(
26975
+ await client.request("PATCH", `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(from)}`, {
26976
+ new_path: to
26977
+ }),
26978
+ outputFormat(this)
26979
+ );
26980
+ });
26981
+ files.command("rm <file_type> <path>").description("Delete a file").option("-y, --yes", "Skip confirmation prompt").action(async function(fileType, filePath, opts) {
26982
+ if (!opts.yes && !await confirm(`Delete file "${fileType}/${filePath}"? This cannot be undone.`)) {
26983
+ console.log("Aborted");
26984
+ return;
26985
+ }
26986
+ const base = pathSegment(requireBase(this), "--base");
26987
+ const client = await createDataClient();
26988
+ await client.request("DELETE", `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${pathSegments(filePath)}`);
26989
+ console.log("Deleted");
26990
+ });
26991
+ files.command("mount <file_type>").description(
26992
+ "Mint an S3 credential so any harness can mount this file type as a filesystem (s3fs, cloud mount SDKs). Read-only by default; the secret is shown once."
26993
+ ).option("--expires-in <seconds>", "Credential lifetime in seconds (default 30 days, max 90)").option(
26994
+ "--mode <mode>",
26995
+ "read_only (default) or read_write \u2014 the whole-bucket mode when no --path is given"
26996
+ ).option(
26997
+ "--path <prefix>",
26998
+ "Confine to a path prefix (repeatable); optionally suffix :read_only|:read_write per prefix (e.g. drafts/:read_write)",
26999
+ (v, acc) => {
27000
+ acc.push(v);
27001
+ return acc;
27002
+ },
27003
+ []
27004
+ ).action(async function(fileType, opts) {
27005
+ const body = { file_type: fileType };
27006
+ if (opts.mode) {
27007
+ if (opts.mode !== "read_only" && opts.mode !== "read_write") {
27008
+ throw expected("--mode must be 'read_only' or 'read_write'.");
27009
+ }
27010
+ body.mode = opts.mode;
27011
+ }
27012
+ if (opts.path.length > 0) {
27013
+ body.paths = opts.path.map((p) => {
27014
+ const m = /^(.*):(read_only|read_write)$/.exec(p);
27015
+ return m ? { prefix: m[1], mode: m[2] } : { prefix: p, mode: opts.mode ?? "read_only" };
27016
+ });
27017
+ }
27018
+ if (opts.expiresIn !== void 0) {
27019
+ const n = Number(opts.expiresIn);
27020
+ if (!Number.isFinite(n)) {
27021
+ throw expected("--expires-in must be a number of seconds.");
27022
+ }
27023
+ body.expires_in = n;
27024
+ }
27025
+ const base = pathSegment(requireBase(this), "--base");
27026
+ const client = await createDataClient();
27027
+ printOutput(
27028
+ await client.request("POST", `/v1/${base}/s3-credentials`, body),
27029
+ outputFormat(this)
27030
+ );
27031
+ });
27032
+ return files;
27033
+ }
27034
+ var init_files = __esm({
27035
+ "src/data/commands/files.ts"() {
27036
+ "use strict";
27037
+ init_expected();
27038
+ init_client();
27039
+ init_output();
27040
+ init_helpers();
27041
+ init_utils();
27042
+ init_terminal_output();
27043
+ }
27044
+ });
27045
+
27046
+ // src/data/commands/inbound-webhooks.ts
27047
+ import { randomUUID as randomUUID2 } from "crypto";
27048
+ import { Command as Command13 } from "commander";
27049
+ function buildInboundWebhooksCommand() {
27050
+ const inboundWebhooks = new Command13("inbound-webhooks").description(
27051
+ "Manage inbound webhook endpoints"
27052
+ );
27053
+ inboundWebhooks.command("create").description("Create a new inbound webhook").requiredOption("--name <name>", "Inbound webhook name").option("--secret-stdin", "Read the HMAC shared secret from stdin (recommended for CI)").option("--id <id>", "Inbound webhook ID (auto-generated if omitted)").option("--record-type-scope <record_types>", "Comma-separated record_type scope").option(
27054
+ "--field-mapping <json>",
27055
+ 'Inline mapping applied to the payload before write \u2014 a field_mapping JSON, e.g. {"to_external":{"status":"state"}} (to_external renames auto-invert on read); an explicit inbound-rename block and {"computed":{...}} are also accepted. Mutually exclusive with --source-binding.'
27056
+ ).option(
27057
+ "--source-binding <json>",
27058
+ `Reuse a record_type source's field_mapping for the inbound translation \u2014 {"record_type":"<id>","source":"<name>"}. Mutually exclusive with --field-mapping.`
27059
+ ).option(
27060
+ "--ingest-auth <json>",
27061
+ 'Inbound auth scheme. Default (omitted) = HMAC signature. Static-header senders: {"type":"static_header","header":"X-Account-Key"} \u2014 the configured header is compared to the shared secret (constant-time) instead of an HMAC.'
27062
+ ).option(
27063
+ "--hydration <json>",
27064
+ `Make this a reference-style ("notification + fetch") webhook: a thin delivery names a record id; the base fetches the full record via the bound source's read endpoint and stores it. Requires --source-binding and a single native --record-type-scope. JSON: {"id_path":"record_id","event_path":"event","event_map":{"ResourceDeleted":"delete"},"default_op":"upsert","merge":true}. Set "merge":true to refresh only the mapped (upstream-owned) fields on re-sync and preserve the locally-owned fields (status/tags/scores) on the same record.`
27065
+ ).action(async function(opts) {
27066
+ const secret = await readSecret(
27067
+ {
27068
+ stdin: opts.secretStdin,
27069
+ // No prompt flag here — the prompt is this command's default.
27070
+ prompt: void 0,
27071
+ stdinFlag: "--secret-stdin",
27072
+ promptHint: "omit --secret-stdin to be prompted"
27073
+ },
27074
+ "HMAC shared secret"
27075
+ );
27076
+ const body = {
27077
+ name: opts.name,
27078
+ secret,
27079
+ enabled: true
27080
+ };
27081
+ if (opts.recordTypeScope) body.record_type_scope = splitList(opts.recordTypeScope);
27082
+ if (opts.fieldMapping) body.field_mapping = parseData(opts.fieldMapping);
27083
+ if (opts.sourceBinding) body.source_binding = parseData(opts.sourceBinding);
27084
+ if (opts.ingestAuth) body.ingest_auth = parseData(opts.ingestAuth);
27085
+ if (opts.hydration) body.hydration = parseData(opts.hydration);
27086
+ const base = pathSegment(requireBase(this), "--base");
27087
+ const client = await createDataClient();
27088
+ const inboundWebhookId = opts.id ?? randomUUID2();
27089
+ printOutput(
27090
+ await client.request(
27091
+ "PUT",
27092
+ `/v1/${base}/inbound-webhooks/${pathSegment(inboundWebhookId, "--id")}`,
27093
+ body
27094
+ ),
27095
+ outputFormat(this)
27096
+ );
27097
+ });
27098
+ inboundWebhooks.command("get <id>").description("Get an inbound webhook").action(async function(id) {
27099
+ const base = pathSegment(requireBase(this), "--base");
27100
+ const client = await createDataClient();
27101
+ printOutput(
27102
+ await client.request("GET", `/v1/${base}/inbound-webhooks/${pathSegment(id, "inbound webhook id")}`),
27103
+ outputFormat(this)
27104
+ );
27105
+ });
27106
+ inboundWebhooks.command("list").description("List all inbound webhooks").action(async function() {
27107
+ const base = pathSegment(requireBase(this), "--base");
27108
+ const client = await createDataClient();
27109
+ printOutput(await client.request("GET", `/v1/${base}/inbound-webhooks`), outputFormat(this));
27110
+ });
27111
+ inboundWebhooks.command("deliveries").description(
27112
+ 'List hydration delivery status for reference-style ("notification + fetch") webhooks'
27113
+ ).option("--status <status>", "Filter by status (pending|delivered|failed|dead)").option("--inbound-webhook <id>", "Filter to one inbound webhook").action(async function(opts) {
27114
+ const base = pathSegment(requireBase(this), "--base");
27115
+ const client = await createDataClient();
27116
+ const params = new URLSearchParams();
27117
+ if (opts.status) params.set("status", opts.status);
27118
+ if (opts.inboundWebhook) params.set("inbound_webhook_id", opts.inboundWebhook);
27119
+ const qs = params.toString() ? `?${params.toString()}` : "";
27120
+ printOutput(
27121
+ await client.request("GET", `/v1/${base}/inbound-webhooks/deliveries${qs}`),
27122
+ outputFormat(this)
27123
+ );
27124
+ });
27125
+ inboundWebhooks.command("delete <id>").description("Delete an inbound webhook").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
27126
+ if (!opts.yes && !await confirm(`Delete inbound webhook ${id}? This cannot be undone.`)) {
27127
+ console.log("Aborted");
27128
+ return;
27129
+ }
27130
+ const base = pathSegment(requireBase(this), "--base");
27131
+ const client = await createDataClient();
27132
+ await client.request("DELETE", `/v1/${base}/inbound-webhooks/${pathSegment(id, "inbound webhook id")}`);
27133
+ console.log("Deleted");
27134
+ });
27135
+ return inboundWebhooks;
27136
+ }
27137
+ var init_inbound_webhooks = __esm({
27138
+ "src/data/commands/inbound-webhooks.ts"() {
27139
+ "use strict";
27140
+ init_client();
27141
+ init_output();
27142
+ init_helpers();
27143
+ init_utils();
27144
+ }
27145
+ });
27146
+
27147
+ // src/data/commands/query-relationships.ts
27148
+ import { Command as Command14 } from "commander";
27149
+ function buildQueryRelationshipsCommand() {
27150
+ return new Command14("query-relationships").description("Query related records").argument("<record_type>", "RecordType of the source record").argument("<id>", "Source record ID").option("--type <type>", "Filter by relationship type").option("--direction <dir>", "Direction: outgoing, incoming, or both", "both").option("--limit <n>", "Max results", "50").option("--offset <n>", "Skip results", "0").action(async function(recordType2, id, opts) {
27151
+ const base = pathSegment(requireBase(this), "--base");
27152
+ const client = await createDataClient();
27153
+ const params = new URLSearchParams();
27154
+ if (opts.type) params.set("rel_type", opts.type);
27155
+ params.set("direction", DIRECTIONS[opts.direction] ?? opts.direction);
27156
+ params.set("limit", opts.limit);
27157
+ params.set("offset", opts.offset);
27158
+ printOutput(
27159
+ await client.request(
27160
+ "GET",
27161
+ `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(id, "record id")}/related?${params.toString()}`
27162
+ ),
27163
+ outputFormat(this)
27164
+ );
27165
+ });
27166
+ }
27167
+ var DIRECTIONS;
27168
+ var init_query_relationships = __esm({
27169
+ "src/data/commands/query-relationships.ts"() {
27170
+ "use strict";
27171
+ init_client();
27172
+ init_output();
27173
+ init_helpers();
27174
+ DIRECTIONS = {
27175
+ outgoing: "source",
27176
+ incoming: "target",
27177
+ both: "both"
27178
+ };
27179
+ }
27180
+ });
27181
+
27182
+ // src/data/commands/record-types.ts
27183
+ import { Command as Command15 } from "commander";
27184
+ function buildRecordTypesCommand() {
27185
+ const recordTypes = new Command15("record-types").description("Manage record_types");
27186
+ recordTypes.command("list").description("List all record_types in a base").action(async function() {
27187
+ const base = pathSegment(requireBase(this), "--base");
27188
+ const client = await createDataClient();
27189
+ printOutput(await client.request("GET", `/v1/${base}/record-types`), outputFormat(this));
27190
+ });
27191
+ recordTypes.command("get <id>").description("Get a record_type").action(async function(id) {
27192
+ const base = pathSegment(requireBase(this), "--base");
27193
+ const client = await createDataClient();
27194
+ printOutput(await client.request("GET", `/v1/${base}/record-types/${pathSegment(id, "record_type")}`), outputFormat(this));
27195
+ });
27196
+ recordTypes.command("upsert <id>").description("Create or update a record_type").requiredOption("--name <name>", "RecordType name").option("--description <desc>", "Description").option("--schema <json>", "JSON Schema (inline JSON or @filename)").option("--icon <icon>", "Icon name").option("--color <color>", "Hex color").option("--sources <json>", "External sources config (inline JSON or @filename)").action(async function(id, opts) {
27197
+ const base = pathSegment(requireBase(this), "--base");
27198
+ const client = await createDataClient();
27199
+ const body = { name: opts.name };
27200
+ if (opts.description) body.description = opts.description;
27201
+ if (opts.schema) body.json_schema = parseData(opts.schema);
27202
+ if (opts.icon) body.icon = opts.icon;
27203
+ if (opts.color) body.color = opts.color;
27204
+ if (opts.sources) body.sources = parseData(opts.sources);
27205
+ printOutput(
27206
+ await client.request("PUT", `/v1/${base}/record-types/${pathSegment(id, "record_type")}`, body),
27207
+ outputFormat(this)
27208
+ );
27209
+ });
27210
+ recordTypes.command("delete <id>").description("Delete a record_type").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
27211
+ if (!opts.yes && !await confirm(`Delete record_type "${id}"? This cannot be undone.`)) {
27212
+ console.log("Aborted");
27213
+ return;
27214
+ }
27215
+ const base = pathSegment(requireBase(this), "--base");
27216
+ const client = await createDataClient();
27217
+ await client.request("DELETE", `/v1/${base}/record-types/${pathSegment(id, "record_type")}`);
27218
+ console.log("Deleted");
27219
+ });
27220
+ recordTypes.command("history <id>").description(
27221
+ "View the change history (audit log) for a record_type \u2014 every schema/config version, who changed it, and when"
27222
+ ).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
27223
+ const base = pathSegment(requireBase(this), "--base");
27224
+ const client = await createDataClient();
27225
+ printOutput(
27226
+ await client.request("GET", `/v1/${base}/audit/record-types/${pathSegment(id, "record_type")}${historyQuery(opts)}`),
27227
+ outputFormat(this)
27228
+ );
27229
+ });
27230
+ return recordTypes;
27231
+ }
27232
+ var init_record_types = __esm({
27233
+ "src/data/commands/record-types.ts"() {
27234
+ "use strict";
27235
+ init_client();
27236
+ init_output();
27237
+ init_helpers();
27238
+ init_utils();
27239
+ }
27240
+ });
27241
+
27242
+ // src/data/commands/records.ts
27243
+ import { Command as Command16 } from "commander";
27244
+ function recordKey(id, externalSource) {
27245
+ return externalSource ? foreignSegment(id, "external id") : pathSegment(id, "record id");
27246
+ }
27247
+ function buildRecordsCommand() {
27248
+ const records = new Command16("records").description("Manage records");
27249
+ records.command("upsert <record_type>").description("Create or update a record").requiredOption("--data <json>", "Record data (inline JSON or @filename)").option("--id <id>", "Internal record ID (UUID) to update a known record").option("--external-id <id>", "External/agent-supplied ID for idempotent upsert").option("--external-source <source>", "Source system for external_id (e.g. stripe)").action(async function(recordType2, opts) {
27250
+ const base = pathSegment(requireBase(this), "--base");
27251
+ const client = await createDataClient();
27252
+ const body = { data: parseData(opts.data) };
27253
+ if (opts.externalId) body.external_id = opts.externalId;
27254
+ if (opts.externalSource) body.external_source = opts.externalSource;
27255
+ const path31 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
27256
+ printOutput(await client.request("PUT", path31, body), outputFormat(this));
27257
+ });
27258
+ records.command("query <record_type>").description("List/search records: exact filters + fuzzy `search`, sorting, pagination").option(
27259
+ "--external-source <source>",
27260
+ "List from an external source (proxy-backed record_type). Combine with --filter when the source forwards filters."
27261
+ ).option(
27262
+ "--filter <json>",
27263
+ "Filter DSL expression (inline JSON or @filename). Use the `search` operator for fuzzy matching."
27264
+ ).option(
27265
+ "--sort <json>",
27266
+ 'Sort expressions (inline JSON or @filename), e.g. [{"field":"data.created","direction":"desc"}]'
27267
+ ).option("--fields <list>", "Comma-separated field projection").option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").action(async function(recordType2, opts) {
27268
+ const base = pathSegment(requireBase(this), "--base");
27269
+ const client = await createDataClient();
27270
+ const qs = new URLSearchParams();
27271
+ if (opts.externalSource) qs.set("external_source", opts.externalSource);
27272
+ if (opts.filter) qs.set("filter", JSON.stringify(parseData(opts.filter)));
27273
+ if (opts.sort) qs.set("sort", JSON.stringify(parseData(opts.sort)));
27274
+ if (opts.fields) qs.set("fields", opts.fields);
27275
+ if (opts.limit) qs.set("limit", opts.limit);
27276
+ if (opts.offset) qs.set("offset", opts.offset);
27277
+ const q = qs.toString();
27278
+ printOutput(
27279
+ await client.request("GET", `/v1/${base}/records/${pathSegment(recordType2, "record_type")}${q ? `?${q}` : ""}`),
27280
+ outputFormat(this)
27281
+ );
27282
+ });
27283
+ records.command("get <record_type> <id>").description(
27284
+ "Get a record by internal ID, or by external_id (pass --external-source to scope it)"
27285
+ ).option(
27286
+ "--external-source <source>",
27287
+ "Treat <id> as an external_id scoped to this source system (e.g. stripe)"
27288
+ ).action(async function(recordType2, id, opts) {
27289
+ const base = pathSegment(requireBase(this), "--base");
27290
+ const client = await createDataClient();
27291
+ let path31 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
27292
+ if (opts.externalSource) path31 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
27293
+ printOutput(await client.request("GET", path31), outputFormat(this));
27294
+ });
27295
+ records.command("delete <record_type> <id>").description(
27296
+ "Delete a record by internal ID, or by external_id (pass --external-source to scope it)"
27297
+ ).option("-y, --yes", "Skip confirmation prompt").option(
27298
+ "--external-source <source>",
27299
+ "Treat <id> as an external_id scoped to this source system (e.g. stripe)"
27300
+ ).action(async function(recordType2, id, opts) {
27301
+ if (!opts.yes && !await confirm(`Delete record ${recordType2}/${id}? This cannot be undone.`)) {
27302
+ console.log("Aborted");
27303
+ return;
27304
+ }
27305
+ const base = pathSegment(requireBase(this), "--base");
27306
+ const client = await createDataClient();
27307
+ let path31 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
27308
+ if (opts.externalSource) {
27309
+ path31 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
27310
+ }
27311
+ await client.request("DELETE", path31);
27312
+ console.log("Deleted");
27313
+ });
27314
+ records.command("history <id>").description(
27315
+ "View the change history (audit log) for a record \u2014 every version, who changed it, and when"
27316
+ ).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
27317
+ const base = pathSegment(requireBase(this), "--base");
27318
+ const client = await createDataClient();
27319
+ printOutput(
27320
+ await client.request("GET", `/v1/${base}/audit/records/${pathSegment(id, "record id")}${historyQuery(opts)}`),
27321
+ outputFormat(this)
27322
+ );
27323
+ });
27324
+ return records;
27325
+ }
27326
+ var init_records = __esm({
27327
+ "src/data/commands/records.ts"() {
27328
+ "use strict";
27329
+ init_client();
27330
+ init_output();
27331
+ init_helpers();
27332
+ init_utils();
27333
+ }
27334
+ });
27335
+
27336
+ // src/data/commands/relationship-types.ts
27337
+ import { Command as Command17 } from "commander";
27338
+ function buildRelationshipTypesCommand() {
27339
+ const relationshipTypes = new Command17("relationship-types").description(
27340
+ "Manage relationship types (the schema for a relationship's metadata)"
27341
+ );
27342
+ relationshipTypes.command("list").description("List all relationship types in a base").action(async function() {
27343
+ const base = pathSegment(requireBase(this), "--base");
27344
+ const client = await createDataClient();
27345
+ printOutput(
27346
+ await client.request("GET", `/v1/${base}/relationship-types`),
27347
+ outputFormat(this)
27348
+ );
25124
27349
  });
25125
- bases.command("rename <id>").description(
25126
- "Rename a base's display name (the id/slug is immutable \u2014 it scopes token grants, MCP endpoints, and references)"
25127
- ).requiredOption("--name <name>", "New display name").action(async function(id, opts) {
27350
+ relationshipTypes.command("get <rel_type>").description("Get a relationship type").action(async function(relType) {
27351
+ const base = pathSegment(requireBase(this), "--base");
25128
27352
  const client = await createDataClient();
25129
27353
  printOutput(
25130
- await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { name: opts.name }),
27354
+ await client.request("GET", `/v1/${base}/relationship-types/${pathSegment(relType, "rel_type")}`),
25131
27355
  outputFormat(this)
25132
27356
  );
25133
27357
  });
25134
- bases.command("tag <id>").description("Set tags on a base").requiredOption("--tags <tags>", "Comma-separated tags (e.g. client:acme,billing)").action(async function(id, opts) {
27358
+ relationshipTypes.command("upsert <rel_type>").description("Create or update a relationship type").option("--description <desc>", "Description").option(
27359
+ "--schema <json>",
27360
+ "JSON Schema for the relationship data (inline JSON or @filename). Omit to allow any data."
27361
+ ).option(
27362
+ "--source-record-types <json>",
27363
+ "JSON array of allowed source record_types (inline JSON or @filename)"
27364
+ ).option(
27365
+ "--target-record-types <json>",
27366
+ "JSON array of allowed target record_types (inline JSON or @filename)"
27367
+ ).action(async function(relType, opts) {
27368
+ const base = pathSegment(requireBase(this), "--base");
25135
27369
  const client = await createDataClient();
27370
+ const body = {};
27371
+ if (opts.description) body.description = opts.description;
27372
+ if (opts.schema) body.data_schema = parseData(opts.schema);
27373
+ if (opts.sourceRecordTypes) body.source_record_types = parseData(opts.sourceRecordTypes);
27374
+ if (opts.targetRecordTypes) body.target_record_types = parseData(opts.targetRecordTypes);
25136
27375
  printOutput(
25137
- await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { tags: splitList(opts.tags) }),
27376
+ await client.request("PUT", `/v1/${base}/relationship-types/${pathSegment(relType, "rel_type")}`, body),
25138
27377
  outputFormat(this)
25139
27378
  );
25140
27379
  });
25141
- bases.command("delete <id>").description(
25142
- "Delete a base. By default this is a tombstone: the base stops being listed, but its storage is retained and recreating the id restores it. Add --purge (preview bases only) to also reclaim the storage"
25143
- ).option("-y, --yes", "Skip confirmation prompt").option(
25144
- "--purge",
25145
- "Preview bases only. Permanently destroy the stored records, relationships, file metadata and config of this base instead of tombstoning it. The id is then retired and cannot be recreated"
25146
- ).action(async function(id, opts) {
25147
- const prompt3 = opts.purge ? `Purge base "${id}"? Its records, relationships, file metadata and config are destroyed permanently, and the id "${id}" is retired for good.` : `Delete base "${id}"? This cannot be undone.`;
25148
- if (!opts.yes && !await confirm(prompt3)) {
27380
+ relationshipTypes.command("delete <rel_type>").description("Delete a relationship type").option("-y, --yes", "Skip confirmation prompt").action(async function(relType, opts) {
27381
+ if (!opts.yes && !await confirm(`Delete relationship type "${relType}"? This cannot be undone.`)) {
25149
27382
  console.log("Aborted");
25150
27383
  return;
25151
27384
  }
27385
+ const base = pathSegment(requireBase(this), "--base");
25152
27386
  const client = await createDataClient();
25153
- await client.request("DELETE", `/v1/bases/${pathSegment(id)}${opts.purge ? "?purge=true" : ""}`);
25154
- printOutput({ id, deleted: true, purged: opts.purge === true }, outputFormat(this));
27387
+ await client.request("DELETE", `/v1/${base}/relationship-types/${pathSegment(relType, "rel_type")}`);
27388
+ console.log("Deleted");
25155
27389
  });
25156
- bases.command("create-preview <origin-id>").description(
25157
- "Create a preview base by cloning the config of another base. The origin may be a production base or another preview; the new preview id is <origin-id>--<name>, linked to the origin it was cloned from (so a preview of a preview promotes through that origin, never straight to production)"
25158
- ).requiredOption("--name <name>", "Preview base name").option("--description <desc>", "Description").option(
25159
- "--integrations <mode>",
25160
- "enabled (default) | disabled. `disabled` makes this preview a seeded, production-safe agent-eval target: every external integration edge is inert and it behaves natively"
27390
+ relationshipTypes.command("history <id>").description(
27391
+ "View the change history (audit log) for a relationship type \u2014 every config version, who changed it, and when"
27392
+ ).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
27393
+ const base = pathSegment(requireBase(this), "--base");
27394
+ const client = await createDataClient();
27395
+ printOutput(
27396
+ await client.request(
27397
+ "GET",
27398
+ `/v1/${base}/audit/relationship-types/${pathSegment(id, "rel_type")}${historyQuery(opts)}`
27399
+ ),
27400
+ outputFormat(this)
27401
+ );
27402
+ });
27403
+ return relationshipTypes;
27404
+ }
27405
+ var init_relationship_types = __esm({
27406
+ "src/data/commands/relationship-types.ts"() {
27407
+ "use strict";
27408
+ init_client();
27409
+ init_output();
27410
+ init_helpers();
27411
+ init_utils();
27412
+ }
27413
+ });
27414
+
27415
+ // src/data/commands/relationships.ts
27416
+ import { Command as Command18 } from "commander";
27417
+ function relationshipKey(id, relType) {
27418
+ return relType ? foreignSegment(id, "external id") : pathSegment(id, "relationship id");
27419
+ }
27420
+ function endpointBodyFields(role, byId, byExternal, externalSource) {
27421
+ if ((byId ? 1 : 0) + (byExternal ? 1 : 0) !== 1) {
27422
+ throw expected(`Provide exactly one of --${role} or --${role}-external.`);
27423
+ }
27424
+ const ref = byId ?? byExternal;
27425
+ const segments = ref.split("/");
27426
+ const [recordType2, key] = segments;
27427
+ if (segments.length !== 2 || !recordType2 || !key) {
27428
+ throw expected(
27429
+ `--${role}${byId ? "" : "-external"} must be <record_type/${byId ? "id" : "external_id"}>, got '${ref}'.`
27430
+ );
27431
+ }
27432
+ if (externalSource && !byExternal) {
27433
+ throw expected(`--${role}-external-source requires --${role}-external.`);
27434
+ }
27435
+ const fields = { [`${role}_record_type`]: recordType2 };
27436
+ if (byId) {
27437
+ fields[`${role}_id`] = key;
27438
+ } else {
27439
+ fields[`${role}_external_id`] = key;
27440
+ if (externalSource) fields[`${role}_external_source`] = externalSource;
27441
+ }
27442
+ return fields;
27443
+ }
27444
+ function byKeyQuery(relType, externalSource) {
27445
+ const qs = new URLSearchParams();
27446
+ if (relType) qs.set("rel_type", relType);
27447
+ if (externalSource) qs.set("external_source", externalSource);
27448
+ const q = qs.toString();
27449
+ return q ? `?${q}` : "";
27450
+ }
27451
+ function buildRelationshipsCommand() {
27452
+ const relationships = new Command18("relationships").description(
27453
+ "Manage relationships between records"
27454
+ );
27455
+ relationships.command("upsert").description("Create or update a relationship").option("--source <record_type/id>", "Source record by internal id").option(
27456
+ "--source-external <record_type/external_id>",
27457
+ "Source record by external_id (the record must exist)"
27458
+ ).option("--source-external-source <name>", "Source system scoping --source-external").option("--target <record_type/id>", "Target record by internal id").option(
27459
+ "--target-external <record_type/external_id>",
27460
+ "Target record by external_id (the record must exist)"
27461
+ ).option("--target-external-source <name>", "Source system scoping --target-external").requiredOption("--type <type>", "Relationship type").option("--id <id>", "Relationship ID").option(
27462
+ "--external-id <key>",
27463
+ "The relationship's own external key: a retried upsert with the same key updates the first row instead of duplicating it"
27464
+ ).option("--external-source <name>", "Source system scoping the relationship's --external-id").option("--data <json>", "Relationship metadata (inline JSON or @filename)").action(async function(opts) {
27465
+ const body = {
27466
+ rel_type: opts.type,
27467
+ ...endpointBodyFields("source", opts.source, opts.sourceExternal, opts.sourceExternalSource),
27468
+ ...endpointBodyFields("target", opts.target, opts.targetExternal, opts.targetExternalSource)
27469
+ };
27470
+ if (opts.id) body.id = opts.id;
27471
+ if (opts.externalId) body.external_id = opts.externalId;
27472
+ if (opts.externalSource) body.external_source = opts.externalSource;
27473
+ if (opts.data) body.data = parseData(opts.data);
27474
+ const base = pathSegment(requireBase(this), "--base");
27475
+ const client = await createDataClient();
27476
+ const path31 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
27477
+ printOutput(await client.request("PUT", path31, body), outputFormat(this));
27478
+ });
27479
+ relationships.command("get <id>").description(
27480
+ "Get a relationship by ID (or by its external key: pass the external_id with --rel-type)"
25161
27481
  ).option(
25162
- "--create-only",
25163
- "Fail with 409 if the derived preview id already exists, instead of re-applying config onto it. Use for per-session ephemeral bases, where landing on a live sibling would corrupt both runs"
25164
- ).action(async function(originId, opts) {
25165
- const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
27482
+ "--rel-type <type>",
27483
+ "Treat <id> as the relationship external_id, scoped to this relationship type"
27484
+ ).option("--external-source <name>", "Source system scoping the external_id (with --rel-type)").action(async function(id, opts) {
27485
+ const base = pathSegment(requireBase(this), "--base");
25166
27486
  const client = await createDataClient();
25167
27487
  printOutput(
25168
- await client.request("POST", `/v1/${pathSegment(originId, "origin base id")}/preview`, {
25169
- name: opts.name,
25170
- description: opts.description,
25171
- ...integrations !== void 0 ? { integrations } : {},
25172
- ...opts.createOnly ? { create_only: true } : {}
25173
- }),
27488
+ await client.request(
27489
+ "GET",
27490
+ `/v1/${base}/relationships/${relationshipKey(id, opts.relType)}${byKeyQuery(opts.relType, opts.externalSource)}`
27491
+ ),
25174
27492
  outputFormat(this)
25175
27493
  );
25176
27494
  });
25177
- bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
27495
+ relationships.command("delete <id>").description(
27496
+ "Delete a relationship by ID (or by its external key: pass the external_id with --rel-type)"
27497
+ ).option(
27498
+ "--rel-type <type>",
27499
+ "Treat <id> as the relationship external_id, scoped to this relationship type"
27500
+ ).option("--external-source <name>", "Source system scoping the external_id (with --rel-type)").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
27501
+ if (!opts.yes && !await confirm(`Delete relationship ${id}? This cannot be undone.`)) {
27502
+ console.log("Aborted");
27503
+ return;
27504
+ }
27505
+ const base = pathSegment(requireBase(this), "--base");
27506
+ const client = await createDataClient();
27507
+ await client.request(
27508
+ "DELETE",
27509
+ `/v1/${base}/relationships/${relationshipKey(id, opts.relType)}${byKeyQuery(opts.relType, opts.externalSource)}`
27510
+ );
27511
+ console.log("Deleted");
27512
+ });
27513
+ relationships.command("history <id>").description(
27514
+ "View the change history (audit log) for a relationship \u2014 every version, who changed it, and when"
27515
+ ).option("--limit <n>", "Max results").option("--offset <n>", "Pagination offset").option("--diff", "Include field-level diffs between versions").action(async function(id, opts) {
27516
+ const base = pathSegment(requireBase(this), "--base");
25178
27517
  const client = await createDataClient();
25179
- const path31 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
25180
27518
  printOutput(
25181
- await client.collectPages((cursor) => pageOf(path31, cursor)),
27519
+ await client.request("GET", `/v1/${base}/audit/relationships/${pathSegment(id, "relationship id")}${historyQuery(opts)}`),
25182
27520
  outputFormat(this)
25183
27521
  );
25184
27522
  });
25185
- bases.command("promote <production-id>").description("Promote config from a preview base to production (human-only)").requiredOption("--from <preview-id>", "Source preview base id").option("--dry-run", "Show what would change without applying").option("--record-types <ids>", "Comma-separated record type ids to promote", splitList).option("--triggers <ids>", "Comma-separated trigger ids to promote", splitList).option("--inbound-webhooks <ids>", "Comma-separated inbound webhook ids to promote", splitList).action(async function(productionId, opts) {
27523
+ return relationships;
27524
+ }
27525
+ var init_relationships = __esm({
27526
+ "src/data/commands/relationships.ts"() {
27527
+ "use strict";
27528
+ init_expected();
27529
+ init_client();
27530
+ init_output();
27531
+ init_helpers();
27532
+ init_utils();
27533
+ }
27534
+ });
27535
+
27536
+ // src/data/commands/seed.ts
27537
+ import { Command as Command19 } from "commander";
27538
+ function buildSeedCommand() {
27539
+ const seed = new Command19("seed").description(
27540
+ "Apply, reset, or clear a seed fixture on a preview base (hermetic eval data)"
27541
+ );
27542
+ seed.command("apply <name>").description(
27543
+ "Idempotently upsert the fixture (keyed by external_id, so re-applying never twins). Preview-only."
27544
+ ).action(async function(name) {
27545
+ const base = pathSegment(requireBase(this), "--base");
25186
27546
  const client = await createDataClient();
25187
- const data = await client.request("POST", `/v1/${pathSegment(productionId, "production base id")}/promote`, {
25188
- source_base_id: opts.from,
25189
- dry_run: opts.dryRun ?? false,
25190
- record_types: opts.recordTypes,
25191
- triggers: opts.triggers,
25192
- inbound_webhooks: opts.inboundWebhooks
25193
- });
25194
- printOutput(data, outputFormat(this));
25195
- const warnings = data?.source_credential_warnings ?? [];
25196
- if (warnings.length > 0) {
25197
- console.warn(
25198
- "\nExternal source credentials are NOT carried over by promotion \u2014 set these on production before the source will work:"
25199
- );
25200
- for (const w of warnings) console.warn(` - ${sanitizeTerminalText(w.message)}`);
25201
- }
27547
+ printOutput(
27548
+ await client.request("POST", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}/apply`),
27549
+ outputFormat(this)
27550
+ );
25202
27551
  });
25203
- bases.command("rollback <production-id>").description("Roll back a promotion (human-only)").requiredOption("--promotion <promotion-id>", "Promotion id to roll back").action(async function(productionId, opts) {
27552
+ seed.command("reset <name>").description(
27553
+ "Restore the declared baseline: re-apply in place (fixing trigger-mutated records) and prune owned rows the fixture no longer declares. Record types listed in exclusive_record_types are treated as authoritative \u2014 reset also removes their non-declared records (agent-created residue) and any relationships referencing them. Preview-only."
27554
+ ).action(async function(name) {
27555
+ const base = pathSegment(requireBase(this), "--base");
25204
27556
  const client = await createDataClient();
25205
27557
  printOutput(
25206
- await client.request("POST", `/v1/${pathSegment(productionId, "production base id")}/promote/rollback`, {
25207
- promotion_id: opts.promotion
27558
+ await client.request("POST", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}/reset`),
27559
+ outputFormat(this)
27560
+ );
27561
+ });
27562
+ seed.command("clear <name>").description("Delete every row the fixture owns (teardown). Preview-only.").action(async function(name) {
27563
+ const base = pathSegment(requireBase(this), "--base");
27564
+ const client = await createDataClient();
27565
+ printOutput(
27566
+ await client.request("POST", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}/clear`),
27567
+ outputFormat(this)
27568
+ );
27569
+ });
27570
+ seed.command("list").description("List the seed fixtures defined on a base").action(async function() {
27571
+ const base = pathSegment(requireBase(this), "--base");
27572
+ const client = await createDataClient();
27573
+ printOutput(await client.request("GET", `/v1/${base}/seeds`), outputFormat(this));
27574
+ });
27575
+ seed.command("get <name>").description("Show a seed fixture definition (its declared records and relationships)").action(async function(name) {
27576
+ const base = pathSegment(requireBase(this), "--base");
27577
+ const client = await createDataClient();
27578
+ printOutput(await client.request("GET", `/v1/${base}/seeds/${pathSegment(name, "fixture name")}`), outputFormat(this));
27579
+ });
27580
+ const lease = seed.command("lease").description("Coordinate an isolated fixture lease");
27581
+ lease.command("acquire <fixture>").description("Reset a fixture and atomically acquire the preview base for one eval run").requiredOption("--lease-id <uuidv7>", "Client-generated UUIDv7 (permanently single-use)").requiredOption(
27582
+ "--owner-ref <ref>",
27583
+ "Safe ID: 1-200 letters/digits or ._:@/+~=- (descriptive only)"
27584
+ ).action(async function(fixture, opts) {
27585
+ const base = pathSegment(requireBase(this), "--base");
27586
+ const client = await createDataClient();
27587
+ printOutput(
27588
+ await client.request("POST", `/v1/${base}/seed-leases/acquire`, {
27589
+ lease_id: opts.leaseId,
27590
+ fixture,
27591
+ owner_ref: opts.ownerRef
25208
27592
  }),
25209
27593
  outputFormat(this)
25210
27594
  );
25211
27595
  });
25212
- bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
27596
+ lease.command("status").description("Inspect your lease, or learn only whether another actor has locked the base").option("--lease-id <uuidv7>", "Inspect a specific lease ID").action(async function(opts) {
27597
+ const base = pathSegment(requireBase(this), "--base");
25213
27598
  const client = await createDataClient();
25214
- const path31 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
27599
+ const params = new URLSearchParams();
27600
+ if (opts.leaseId !== void 0) params.set("lease_id", opts.leaseId);
27601
+ const query = params.size > 0 ? `?${params.toString()}` : "";
25215
27602
  printOutput(
25216
- await client.collectPages((cursor) => pageOf(path31, cursor)),
27603
+ await client.request("GET", `/v1/${base}/seed-leases/status${query}`),
25217
27604
  outputFormat(this)
25218
27605
  );
25219
27606
  });
25220
- bases.command("use <base>").description(
25221
- "Bind this worktree to a base so push/pull refuse to run against a different one"
25222
- ).action(async function(selector) {
25223
- const baseId = resolveSelectorToBaseId(selector);
25224
- const previous = readBaseBinding();
25225
- if (previous !== baseId) {
25226
- try {
25227
- writeBaseBinding(baseId);
25228
- } catch (err) {
25229
- throw expected(err instanceof Error ? err.message : String(err));
27607
+ lease.command("release <fixture>").description("Clear fixture-owned data and release the matching lease").requiredOption("--lease-id <uuidv7>", "The UUIDv7 used to acquire the lease").requiredOption("--owner-ref <ref>", "Same safe ID: 1-200 letters/digits or ._:@/+~=-").action(async function(fixture, opts) {
27608
+ const base = pathSegment(requireBase(this), "--base");
27609
+ const client = await createDataClient();
27610
+ printOutput(
27611
+ await client.request("POST", `/v1/${base}/seed-leases/release`, {
27612
+ lease_id: opts.leaseId,
27613
+ fixture,
27614
+ owner_ref: opts.ownerRef
27615
+ }),
27616
+ outputFormat(this)
27617
+ );
27618
+ });
27619
+ return seed;
27620
+ }
27621
+ var init_seed = __esm({
27622
+ "src/data/commands/seed.ts"() {
27623
+ "use strict";
27624
+ init_client();
27625
+ init_output();
27626
+ init_helpers();
27627
+ }
27628
+ });
27629
+
27630
+ // src/data/commands/toolsets.ts
27631
+ import { Command as Command20 } from "commander";
27632
+ function collect(value, previous) {
27633
+ return [...previous, value];
27634
+ }
27635
+ function parseSpec(spec) {
27636
+ const [key, opsStr] = spec.split(":");
27637
+ if (!key || !opsStr) {
27638
+ throw expected(`Invalid spec '${spec}'. Expected format: name:op1,op2`);
27639
+ }
27640
+ return { key, ops: splitList(opsStr) };
27641
+ }
27642
+ function parseActionRef(spec) {
27643
+ const [action, actionName] = spec.split("=");
27644
+ if (!action) {
27645
+ throw expected(`Invalid --action '${spec}'. Expected an Action id, or <action_id>=<surface_name>.`);
27646
+ }
27647
+ return actionName ? { action, action_name: actionName } : { action };
27648
+ }
27649
+ function parseRelSpec(spec) {
27650
+ const { key, ops } = parseSpec(spec);
27651
+ return { rel_type: key, operations: ops };
27652
+ }
27653
+ function buildToolsetsCommand() {
27654
+ const toolsets = new Command20("toolsets").description("Manage MCP toolsets");
27655
+ toolsets.command("upsert <slug>").description("Create or update an MCP toolset").option("--name <name>", "Toolset display name (required unless using --config)").option("--description <desc>", "Toolset description").option(
27656
+ "--action <ref>",
27657
+ "Reference an Action by id, optionally renamed: <action_id> or <action_id>=<surface_name> (repeatable). Create Actions first with `wayai actions upsert`.",
27658
+ collect,
27659
+ []
27660
+ ).option("--relationship <spec>", "Relationship tool spec: rel_type:op1,op2 (repeatable)", collect, []).option("--batch <spec>", "Batch operation spec: record_type_or_rel:op1,op2 (repeatable)", collect, []).option("--sql-query", "Enable sql_query tool").option(
27661
+ "--config <json>",
27662
+ "Full toolset config as JSON. A toolset composes first-class Actions by reference \u2014 its `actions` are refs {action: <action_id>, action_name?, description_override?}. Create the Actions with `wayai actions upsert` (each carries its own record_type, operation, and guard/shaping config: filterable_fields, writable_fields, precondition, binding). Use @file.json to read from file."
27663
+ ).option(
27664
+ "--mint-token",
27665
+ "After upsert, also mint a toolset-bound (consumer) token for this base and print it (handy for sandbox-testing the preview toolset)"
27666
+ ).action(async function(slug, opts) {
27667
+ let body;
27668
+ if (opts.config) {
27669
+ body = parseData(opts.config);
27670
+ } else {
27671
+ if (!opts.name) {
27672
+ throw expected("--name is required (or use --config for full JSON config)");
27673
+ }
27674
+ body = { name: opts.name, actions: opts.action.map(parseActionRef) };
27675
+ if (opts.description) body.description = opts.description;
27676
+ if (opts.relationship.length > 0) {
27677
+ body.relationships = opts.relationship.map(parseRelSpec);
27678
+ }
27679
+ if (opts.batch.length > 0) {
27680
+ const operations = {};
27681
+ for (const spec of opts.batch) {
27682
+ const parsed = parseSpec(spec);
27683
+ operations[parsed.key] = parsed.ops;
27684
+ }
27685
+ body.batch = { enabled: true, operations };
25230
27686
  }
27687
+ if (opts.sqlQuery) body.sql_query = true;
25231
27688
  }
25232
- if (outputFormat(this) === "json") {
25233
- printOutput({ base_id: baseId, previous_base_id: previous, changed: previous !== baseId }, "json");
27689
+ const base = pathSegment(requireBase(this), "--base");
27690
+ const client = await createDataClient();
27691
+ const format = outputFormat(this);
27692
+ printOutput(await client.request("PUT", `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`, body), format);
27693
+ if (opts.mintToken) {
27694
+ const tokenData = await client.request("POST", "/v1/tokens", {
27695
+ name: `${slug} consumer`,
27696
+ toolset_binding: { base_id: base, toolset_id: slug }
27697
+ });
27698
+ if (format === "json") {
27699
+ printOutput(tokenData, format);
27700
+ } else {
27701
+ console.log("\nToolset-bound token:");
27702
+ printOutput(tokenData, format);
27703
+ console.log(`MCP URL: ${toolsetMcpUrl(slug)}`);
27704
+ }
27705
+ }
27706
+ });
27707
+ toolsets.command("get <slug>").description("Get a toolset").option("--resolved", "Include resolved record_type schemas").action(async function(slug, opts) {
27708
+ const base = pathSegment(requireBase(this), "--base");
27709
+ const client = await createDataClient();
27710
+ const path31 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
27711
+ printOutput(await client.request("GET", path31), outputFormat(this));
27712
+ });
27713
+ toolsets.command("list").description("List all toolsets").action(async function() {
27714
+ const base = pathSegment(requireBase(this), "--base");
27715
+ const client = await createDataClient();
27716
+ printOutput(await client.request("GET", `/v1/${base}/toolsets`), outputFormat(this));
27717
+ });
27718
+ toolsets.command("delete <slug>").description("Delete a toolset").option("-y, --yes", "Skip confirmation prompt").action(async function(slug, opts) {
27719
+ if (!opts.yes && !await confirm(`Delete toolset "${slug}"? This cannot be undone.`)) {
27720
+ console.log("Aborted");
25234
27721
  return;
25235
27722
  }
25236
- if (previous === baseId) {
25237
- console.log(`Worktree already bound to base ${baseId}.`);
27723
+ const base = pathSegment(requireBase(this), "--base");
27724
+ const client = await createDataClient();
27725
+ await client.request("DELETE", `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`);
27726
+ console.log("Deleted");
27727
+ });
27728
+ toolsets.command("url <slug>").description("Get the MCP connection URL for a toolset").action((slug) => {
27729
+ console.log(toolsetMcpUrl(slug));
27730
+ });
27731
+ return toolsets;
27732
+ }
27733
+ var init_toolsets = __esm({
27734
+ "src/data/commands/toolsets.ts"() {
27735
+ "use strict";
27736
+ init_expected();
27737
+ init_client();
27738
+ init_output();
27739
+ init_helpers();
27740
+ init_utils();
27741
+ }
27742
+ });
27743
+
27744
+ // src/data/commands/triggers.ts
27745
+ import { randomUUID as randomUUID3 } from "crypto";
27746
+ import { Command as Command21 } from "commander";
27747
+ function buildTriggersCommand() {
27748
+ const triggers = new Command21("triggers").description(
27749
+ "Manage triggers (outbound webhooks, internal writes, external writes)"
27750
+ );
27751
+ triggers.command("create").description(
27752
+ "Create a trigger (a webhook, an internal_write that patches a referenced record, or an external_write that mirrors the record to an external source)"
27753
+ ).requiredOption("--name <name>", "Trigger name").requiredOption("--events <events>", "Comma-separated event types").option("--url <url>", "Webhook target URL (webhook actions)").option("--secret-stdin", "Read the webhook HMAC signing secret from stdin (webhook actions)").option("--secret-prompt", "Prompt for the webhook HMAC signing secret, masked (webhook actions)").option(
27754
+ "--action <json>",
27755
+ `Action JSON. webhook {"type":"webhook","url":...,"secret":...}; internal_write {"type":"internal_write","target":"slots","match":{"source_field":"data.slot_id"},"patch":{"status":"busy"},"precondition":{"field":"data.status","op":"eq","value":"free"},"mode":"async"} (mode: "async" applies after the write, "transactional" applies atomically so a precondition miss rolls the write back); external_write {"type":"external_write","record_type":"upstream_appointments","source":"legacy","op":"create","watched_fields":"mapped"} (writes the triggering record through that source's write path \u2014 translation, endpoint, auth \u2014 and links the upstream-assigned id back as external_id; "record_type" defaults to the triggering record_type, "op" defaults from the event; "watched_fields" scopes UPDATE dispatch to writes that changed an upstream-mapped field \u2014 "mapped" = the source's to_external keys, or an explicit array of data fields \u2014 so an overlay-only write skips the redundant upstream push; omit to fire on every update)`
27756
+ ).option("--id <id>", "Trigger ID (auto-generated if omitted)").option("--record-type-scope <record_types>", "Comma-separated record_type scope").option("--filter <json>", "Filter expression (JSON)").option(
27757
+ "--skip-cascade-writes <bool>",
27758
+ "Skip firing on cascade-originated (internal_write) writes (default true)"
27759
+ ).action(async function(opts) {
27760
+ if (!opts.action && !opts.url) {
27761
+ throw expected("Provide --url (webhook) or --action <json> (e.g. an internal_write action).");
27762
+ }
27763
+ const body = {
27764
+ name: opts.name,
27765
+ events: splitList(opts.events),
27766
+ enabled: true
27767
+ };
27768
+ if (opts.action) {
27769
+ body.action = parseData(opts.action);
25238
27770
  } else {
25239
- console.log(
25240
- previous ? `Worktree rebound: ${previous} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
25241
- );
27771
+ body.url = opts.url;
27772
+ if (opts.secretStdin || opts.secretPrompt) {
27773
+ body.secret = await readSecret(
27774
+ {
27775
+ stdin: opts.secretStdin,
27776
+ prompt: opts.secretPrompt,
27777
+ stdinFlag: "--secret-stdin",
27778
+ promptHint: "--secret-prompt"
27779
+ },
27780
+ "Webhook signing secret"
27781
+ );
27782
+ }
27783
+ }
27784
+ if (opts.recordTypeScope) body.record_type_scope = splitList(opts.recordTypeScope);
27785
+ if (opts.filter) body.filter = parseData(opts.filter);
27786
+ if (opts.skipCascadeWrites !== void 0) {
27787
+ body.skip_cascade_writes = opts.skipCascadeWrites !== "false";
25242
27788
  }
27789
+ const base = pathSegment(requireBase(this), "--base");
27790
+ const client = await createDataClient();
27791
+ const triggerId = opts.id ?? randomUUID3();
27792
+ printOutput(
27793
+ await client.request("PUT", `/v1/${base}/triggers/${pathSegment(triggerId, "--id")}`, body),
27794
+ outputFormat(this)
27795
+ );
25243
27796
  });
25244
- bases.command("unbind").description("Clear the base binding for this worktree").action(async function() {
25245
- const previous = readBaseBinding();
25246
- if (previous) clearBaseBinding();
25247
- if (outputFormat(this) === "json") {
25248
- printOutput({ previous_base_id: previous, cleared: previous !== null }, "json");
27797
+ triggers.command("get <id>").description("Get a trigger").action(async function(id) {
27798
+ const base = pathSegment(requireBase(this), "--base");
27799
+ const client = await createDataClient();
27800
+ printOutput(await client.request("GET", `/v1/${base}/triggers/${pathSegment(id, "trigger id")}`), outputFormat(this));
27801
+ });
27802
+ triggers.command("list").description("List all triggers").action(async function() {
27803
+ const base = pathSegment(requireBase(this), "--base");
27804
+ const client = await createDataClient();
27805
+ printOutput(await client.request("GET", `/v1/${base}/triggers`), outputFormat(this));
27806
+ });
27807
+ triggers.command("deliveries").description("List trigger delivery attempts (status, retries, errors)").option("--status <status>", "Filter by status (pending | delivered | failed | dead)").option("--trigger-id <id>", "Filter by trigger id").action(async function(opts) {
27808
+ const base = pathSegment(requireBase(this), "--base");
27809
+ const client = await createDataClient();
27810
+ const params = new URLSearchParams();
27811
+ if (opts.status) params.set("status", opts.status);
27812
+ if (opts.triggerId) params.set("trigger_id", opts.triggerId);
27813
+ const qs = params.toString();
27814
+ printOutput(
27815
+ await client.request("GET", `/v1/${base}/triggers/deliveries${qs ? `?${qs}` : ""}`),
27816
+ outputFormat(this)
27817
+ );
27818
+ });
27819
+ triggers.command("delete <id>").description("Delete a trigger").option("-y, --yes", "Skip confirmation prompt").action(async function(id, opts) {
27820
+ if (!opts.yes && !await confirm(`Delete trigger ${id}? This cannot be undone.`)) {
27821
+ console.log("Aborted");
25249
27822
  return;
25250
27823
  }
25251
- console.log(
25252
- previous ? `Worktree unbound from base (was: ${previous}).` : "No base binding to clear."
25253
- );
27824
+ const base = pathSegment(requireBase(this), "--base");
27825
+ const client = await createDataClient();
27826
+ await client.request("DELETE", `/v1/${base}/triggers/${pathSegment(id, "trigger id")}`);
27827
+ console.log("Deleted");
25254
27828
  });
25255
- return bases;
25256
- }
25257
- function resolveSelectorToBaseId(selector) {
25258
- const gitRoot = findGitRoot();
25259
- if (!gitRoot || selector.includes("/") || selector.includes(path30.sep)) return selector;
25260
- if (!isValidBaseId(selector)) return selector;
25261
- const metaPath = path30.join(resolveBasesDir(gitRoot), selector, BASE_META_FILE);
25262
- let meta;
25263
- try {
25264
- meta = yaml9.load(fs24.readFileSync(metaPath, "utf-8"));
25265
- } catch {
25266
- return selector;
25267
- }
25268
- const id = meta?.[BASE_ID_FIELD];
25269
- return typeof id === "string" && isValidBaseId(id) ? id : selector;
27829
+ return triggers;
25270
27830
  }
25271
- var BASE_META_FILE, BASE_ID_FIELD;
25272
- var init_bases = __esm({
25273
- "src/data/commands/bases.ts"() {
27831
+ var init_triggers = __esm({
27832
+ "src/data/commands/triggers.ts"() {
25274
27833
  "use strict";
27834
+ init_expected();
25275
27835
  init_client();
25276
27836
  init_output();
25277
27837
  init_helpers();
25278
27838
  init_utils();
25279
- init_terminal_output();
25280
- init_workspace();
25281
- init_layout();
25282
- init_base_binding();
25283
- init_base_id();
25284
- init_expected();
25285
- BASE_META_FILE = "base.yaml";
25286
- BASE_ID_FIELD = "base_id";
25287
27839
  }
25288
27840
  });
25289
27841
 
@@ -25292,27 +27844,55 @@ var program_exports = {};
25292
27844
  __export(program_exports, {
25293
27845
  DATA_NAMESPACES: () => DATA_NAMESPACES,
25294
27846
  buildDataProgram: () => buildDataProgram,
27847
+ handleParserExit: () => handleParserExit,
25295
27848
  isDataNamespace: () => isDataNamespace,
27849
+ routeErrorsToCli: () => routeErrorsToCli,
25296
27850
  runDataCommand: () => runDataCommand,
25297
27851
  withBaseOption: () => withBaseOption,
25298
27852
  withDataGlobals: () => withDataGlobals
25299
27853
  });
25300
- import { Command as Command2 } from "commander";
27854
+ import { Command as Command22 } from "commander";
25301
27855
  function withDataGlobals(command2) {
25302
27856
  return command2.option("--org <uuid>", "Organization to operate against (overrides .wayai.yaml)").option("--output <format>", "Output format: json or table", "table").option("--json", "Shorthand for --output json");
25303
27857
  }
25304
- function withBaseOption(command2) {
25305
- return command2.option("--base <id>", "Base id (or set WAYAI_BASE)");
25306
- }
25307
27858
  function routeErrorsToCli(command2) {
25308
27859
  command2.exitOverride().configureOutput({ outputError: () => {
25309
27860
  } });
25310
27861
  for (const child of command2.commands) routeErrorsToCli(child);
25311
27862
  return command2;
25312
27863
  }
27864
+ function handleParserExit(err) {
27865
+ const { code, exitCode } = err ?? {};
27866
+ if (code === "commander.helpDisplayed" || code === "commander.version") return;
27867
+ if (code === "commander.help") {
27868
+ process.exitCode = exitCode || 1;
27869
+ return;
27870
+ }
27871
+ if (typeof code === "string" && code.startsWith("commander.")) {
27872
+ throw expected(err instanceof Error ? err.message : String(err));
27873
+ }
27874
+ throw err;
27875
+ }
25313
27876
  function buildDataProgram() {
25314
- const program = new Command2("wayai");
27877
+ const program = new Command22("wayai");
25315
27878
  program.addCommand(withDataGlobals(buildBasesCommand()));
27879
+ for (const build of [
27880
+ buildRecordsCommand,
27881
+ buildRecordTypesCommand,
27882
+ buildRelationshipsCommand,
27883
+ buildRelationshipTypesCommand,
27884
+ buildQueryRelationshipsCommand,
27885
+ buildFilesCommand,
27886
+ buildFileTypesCommand,
27887
+ buildAttachmentsCommand,
27888
+ buildToolsetsCommand,
27889
+ buildActionsCommand,
27890
+ buildTriggersCommand,
27891
+ buildInboundWebhooksCommand,
27892
+ buildSeedCommand
27893
+ ]) {
27894
+ program.addCommand(withBaseOption(withDataGlobals(build())));
27895
+ }
25316
27896
  program.hook("preAction", (_thisCommand, actionCommand) => {
25317
27897
  setDataOrgOverride(globals(actionCommand).org);
25318
27898
  });
@@ -25326,27 +27906,32 @@ async function runDataCommand(namespace, args2) {
25326
27906
  try {
25327
27907
  await program.parseAsync(["node", "wayai", namespace, ...args2]);
25328
27908
  } catch (err) {
25329
- const { code, exitCode } = err ?? {};
25330
- if (code === "commander.helpDisplayed" || code === "commander.version") return;
25331
- if (code === "commander.help") {
25332
- process.exitCode = exitCode || 1;
25333
- return;
25334
- }
25335
- if (typeof code === "string" && code.startsWith("commander.")) {
25336
- throw expected(err instanceof Error ? err.message : String(err));
25337
- }
25338
- throw err;
27909
+ handleParserExit(err);
25339
27910
  }
25340
27911
  }
25341
27912
  var init_program = __esm({
25342
27913
  "src/data/program.ts"() {
25343
27914
  "use strict";
27915
+ init_actions();
27916
+ init_attachments();
25344
27917
  init_bases();
27918
+ init_file_types();
27919
+ init_files();
27920
+ init_inbound_webhooks();
27921
+ init_query_relationships();
27922
+ init_record_types();
27923
+ init_records();
27924
+ init_relationship_types();
27925
+ init_relationships();
27926
+ init_seed();
27927
+ init_toolsets();
27928
+ init_triggers();
25345
27929
  init_helpers();
25346
27930
  init_expected();
25347
27931
  init_org_context();
25348
27932
  init_registry();
25349
27933
  init_registry();
27934
+ init_helpers();
25350
27935
  }
25351
27936
  });
25352
27937
 
@@ -25356,9 +27941,9 @@ init_errors2();
25356
27941
  init_mask_secrets();
25357
27942
  init_utils();
25358
27943
  init_registry();
25359
- import { readFileSync as readFileSync21 } from "fs";
25360
- import { fileURLToPath as fileURLToPath2 } from "url";
25361
- import { dirname as dirname9, join as join28 } from "path";
27944
+ import { readFileSync as readFileSync26 } from "fs";
27945
+ import { fileURLToPath as fileURLToPath3 } from "url";
27946
+ import { dirname as dirname10, join as join29 } from "path";
25362
27947
 
25363
27948
  // src/lib/version-refresh.ts
25364
27949
  init_version_cache();
@@ -25513,14 +28098,14 @@ Run \`wayai admin skill install\` to update.`);
25513
28098
  }
25514
28099
 
25515
28100
  // src/index.ts
25516
- var __dirname = dirname9(fileURLToPath2(import.meta.url));
25517
- var pkg = JSON.parse(readFileSync21(join28(__dirname, "..", "package.json"), "utf-8"));
28101
+ var __dirname = dirname10(fileURLToPath3(import.meta.url));
28102
+ var pkg = JSON.parse(readFileSync26(join29(__dirname, "..", "package.json"), "utf-8"));
25518
28103
  var [, , command, ...args] = process.argv;
25519
28104
  var isBackgroundRefresh = command === REFRESH_COMMAND;
25520
28105
  if (!isBackgroundRefresh) initSentry(command, pkg.version);
25521
28106
  async function main() {
25522
28107
  if (shouldInterceptHelp(command, args, /* @__PURE__ */ new Set([...OWN_HELP_COMMANDS, ...DATA_NAMESPACES]))) {
25523
- printHelp3();
28108
+ await printHelp3();
25524
28109
  return;
25525
28110
  }
25526
28111
  if (command && !wantsHelp(args)) {
@@ -25712,7 +28297,7 @@ async function main() {
25712
28297
  case "--help":
25713
28298
  case "-h":
25714
28299
  case void 0:
25715
- printHelp3();
28300
+ await printHelp3();
25716
28301
  break;
25717
28302
  case "--version":
25718
28303
  case "-v":
@@ -25725,12 +28310,18 @@ async function main() {
25725
28310
  break;
25726
28311
  }
25727
28312
  console.error(`Unknown command: ${command}`);
25728
- printHelp3();
28313
+ await printHelp3();
25729
28314
  process.exit(1);
25730
28315
  }
25731
28316
  }
25732
28317
  }
25733
- function printHelp3() {
28318
+ async function dataHelpBlock() {
28319
+ const { buildDataProgram: buildDataProgram2 } = await Promise.resolve().then(() => (init_program(), program_exports));
28320
+ const namespaces = buildDataProgram2().commands;
28321
+ const width = Math.max(...namespaces.map((c) => c.name().length));
28322
+ return namespaces.map((c) => ` ${c.name().padEnd(width)} ${c.description()}`.trimEnd()).join("\n");
28323
+ }
28324
+ async function printHelp3() {
25734
28325
  console.log(`
25735
28326
  wayai \u2014 WayAI CLI
25736
28327
 
@@ -25778,12 +28369,11 @@ Commands:
25778
28369
  report edit Amend your own pending report (title/description/error/steps/context)
25779
28370
  update Update CLI to the latest version
25780
28371
 
25781
- Data (bases):
25782
- bases Manage bases (list/get/create/update/delete, previews, promote)
28372
+ Data \u2014 one namespace per entity. Run \`wayai <namespace> --help\` for its tree.
28373
+ ${await dataHelpBlock()}
25783
28374
  bases promote Promote a preview base to production (distinct from \`publish\`, which promotes a hub)
25784
28375
  bases use <base> Bind this worktree to a base (\`wayai use\` binds a hub)
25785
28376
  bases unbind Clear this worktree's base binding
25786
- Run \`wayai bases --help\` for the full tree.
25787
28377
 
25788
28378
  Flags:
25789
28379
  --yes, -y Skip confirmation prompts (useful for CI and scripting)
@@ -25854,7 +28444,7 @@ async function runForeground() {
25854
28444
  try {
25855
28445
  await main();
25856
28446
  if (command && !SKIP_UPDATE_CHECK.includes(command) && !wantsHelp(args)) {
25857
- showUpdateNudges(pkg.version, fileURLToPath2(import.meta.url));
28447
+ showUpdateNudges(pkg.version, fileURLToPath3(import.meta.url));
25858
28448
  }
25859
28449
  await closeSentry();
25860
28450
  } catch (err) {