@wayai/cli 0.3.149 → 0.3.151

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
@@ -447,11 +447,11 @@ function captureException2(error, context) {
447
447
  Sentry.captureException(error);
448
448
  });
449
449
  }
450
- function addApiBreadcrumb(method, path36) {
450
+ function addApiBreadcrumb(method, path35) {
451
451
  if (!initialized) return;
452
452
  Sentry.addBreadcrumb({
453
453
  category: "http",
454
- message: `${method} ${path36}`,
454
+ message: `${method} ${path35}`,
455
455
  level: "info"
456
456
  });
457
457
  }
@@ -905,8 +905,8 @@ var init_parseUtil = __esm({
905
905
  init_errors();
906
906
  init_en();
907
907
  makeIssue = (params) => {
908
- const { data, path: path36, errorMaps, issueData } = params;
909
- const fullPath = [...path36, ...issueData.path || []];
908
+ const { data, path: path35, errorMaps, issueData } = params;
909
+ const fullPath = [...path35, ...issueData.path || []];
910
910
  const fullIssue = {
911
911
  ...issueData,
912
912
  path: fullPath
@@ -1217,11 +1217,11 @@ var init_types = __esm({
1217
1217
  init_parseUtil();
1218
1218
  init_util();
1219
1219
  ParseInputLazyPath = class {
1220
- constructor(parent, value, path36, key) {
1220
+ constructor(parent, value, path35, key) {
1221
1221
  this._cachedPath = [];
1222
1222
  this.parent = parent;
1223
1223
  this.data = value;
1224
- this._path = path36;
1224
+ this._path = path35;
1225
1225
  this._key = key;
1226
1226
  }
1227
1227
  get path() {
@@ -4634,12 +4634,12 @@ function validateFollowupLinks(followups, label, ctx) {
4634
4634
  const linkTargets = /* @__PURE__ */ new Set();
4635
4635
  followups.forEach((followup, i) => {
4636
4636
  const ref = followup?.after_followup_id;
4637
- const path36 = ["followups", i, "after_followup_id"];
4637
+ const path35 = ["followups", i, "after_followup_id"];
4638
4638
  if (followup?.type !== "inactivity_after_before_event") {
4639
4639
  if (ref !== void 0) {
4640
4640
  ctx.addIssue({
4641
4641
  code: external_exports.ZodIssueCode.custom,
4642
- path: path36,
4642
+ path: path35,
4643
4643
  message: `kanban status ${label}: after_followup_id is only valid on inactivity_after_before_event followups (this one is ${followup?.type})`
4644
4644
  });
4645
4645
  }
@@ -4648,7 +4648,7 @@ function validateFollowupLinks(followups, label, ctx) {
4648
4648
  if (ref === void 0) {
4649
4649
  ctx.addIssue({
4650
4650
  code: external_exports.ZodIssueCode.custom,
4651
- path: path36,
4651
+ path: path35,
4652
4652
  message: `kanban status ${label}: inactivity_after_before_event requires after_followup_id naming the id of a before_event followup in the same status`
4653
4653
  });
4654
4654
  return;
@@ -4658,7 +4658,7 @@ function validateFollowupLinks(followups, label, ctx) {
4658
4658
  if (matches.length > 1) {
4659
4659
  ctx.addIssue({
4660
4660
  code: external_exports.ZodIssueCode.custom,
4661
- path: path36,
4661
+ path: path35,
4662
4662
  message: `kanban status ${label}: after_followup_id "${ref}" matches ${matches.length} followups \u2014 a link must name exactly one`
4663
4663
  });
4664
4664
  return;
@@ -4670,7 +4670,7 @@ function validateFollowupLinks(followups, label, ctx) {
4670
4670
  );
4671
4671
  ctx.addIssue({
4672
4672
  code: external_exports.ZodIssueCode.custom,
4673
- path: path36,
4673
+ path: path35,
4674
4674
  message: hasIdlessBeforeEvent ? `kanban status ${label}: after_followup_id "${ref}" matches no followup \u2014 a link target must declare an explicit id, and this status has before_event followups without one` : `kanban status ${label}: after_followup_id "${ref}" matches no followup in this status`
4675
4675
  });
4676
4676
  return;
@@ -4678,7 +4678,7 @@ function validateFollowupLinks(followups, label, ctx) {
4678
4678
  if (target.type !== "before_event") {
4679
4679
  ctx.addIssue({
4680
4680
  code: external_exports.ZodIssueCode.custom,
4681
- path: path36,
4681
+ path: path35,
4682
4682
  message: `kanban status ${label}: after_followup_id "${ref}" points at a ${target.type} followup \u2014 only before_event followups can anchor a chain`
4683
4683
  });
4684
4684
  }
@@ -4754,12 +4754,12 @@ function typeMatches(typeField, allowed) {
4754
4754
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
4755
4755
  return false;
4756
4756
  }
4757
- function validateSchema(schema, path36, errors, opts = {}) {
4757
+ function validateSchema(schema, path35, errors, opts = {}) {
4758
4758
  if (typeof schema === "boolean") return;
4759
4759
  const depth = opts.depth ?? 0;
4760
4760
  if (depth > MAX_SCHEMA_DEPTH) {
4761
4761
  errors.push({
4762
- path: path36 || "<root>",
4762
+ path: path35 || "<root>",
4763
4763
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
4764
4764
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
4765
4765
  });
@@ -4767,7 +4767,7 @@ function validateSchema(schema, path36, errors, opts = {}) {
4767
4767
  }
4768
4768
  if (!isRecord(schema)) {
4769
4769
  errors.push({
4770
- path: path36,
4770
+ path: path35,
4771
4771
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
4772
4772
  });
4773
4773
  return;
@@ -4775,14 +4775,14 @@ function validateSchema(schema, path36, errors, opts = {}) {
4775
4775
  if (opts.isRoot) {
4776
4776
  if ("type" in schema && schema.type !== "object") {
4777
4777
  errors.push({
4778
- path: path36 ? `${path36}.type` : "type",
4778
+ path: path35 ? `${path35}.type` : "type",
4779
4779
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
4780
4780
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
4781
4781
  });
4782
4782
  }
4783
4783
  } else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
4784
4784
  errors.push({
4785
- path: `${path36}.type`,
4785
+ path: `${path35}.type`,
4786
4786
  message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
4787
4787
  });
4788
4788
  }
@@ -4792,25 +4792,25 @@ function validateSchema(schema, path36, errors, opts = {}) {
4792
4792
  const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
4793
4793
  const isOutcomePlaceholder = e === "[KANBAN_OUTCOME_VALUES]";
4794
4794
  errors.push({
4795
- path: `${path36}.enum`,
4795
+ path: `${path35}.enum`,
4796
4796
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
4797
4797
  suggestion: isOutcomePlaceholder ? "Declare `outcomes` on the hub's terminal kanban status so the platform can render the placeholder into a valid array; with none configured the platform drops the parameter instead." : isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
4798
4798
  });
4799
4799
  } else if (!Array.isArray(e)) {
4800
4800
  errors.push({
4801
- path: `${path36}.enum`,
4801
+ path: `${path35}.enum`,
4802
4802
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
4803
4803
  });
4804
4804
  } else if (e.length === 0) {
4805
4805
  errors.push({
4806
- path: `${path36}.enum`,
4806
+ path: `${path35}.enum`,
4807
4807
  message: "enum must not be empty"
4808
4808
  });
4809
4809
  } else {
4810
4810
  for (let i = 0; i < e.length; i++) {
4811
4811
  if (!isPrimitive(e[i])) {
4812
4812
  errors.push({
4813
- path: `${path36}.enum[${i}]`,
4813
+ path: `${path35}.enum[${i}]`,
4814
4814
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
4815
4815
  });
4816
4816
  }
@@ -4820,7 +4820,7 @@ function validateSchema(schema, path36, errors, opts = {}) {
4820
4820
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
4821
4821
  if (key in schema && typeof schema[key] === "boolean") {
4822
4822
  errors.push({
4823
- path: `${path36}.${key}`,
4823
+ path: `${path35}.${key}`,
4824
4824
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
4825
4825
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
4826
4826
  });
@@ -4829,45 +4829,45 @@ function validateSchema(schema, path36, errors, opts = {}) {
4829
4829
  if ("properties" in schema) {
4830
4830
  if (!isRecord(schema.properties)) {
4831
4831
  errors.push({
4832
- path: `${path36}.properties`,
4832
+ path: `${path35}.properties`,
4833
4833
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
4834
4834
  });
4835
4835
  } else {
4836
4836
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
4837
- validateSchema(propSchema, `${path36}.properties.${propName}`, errors, { depth: depth + 1 });
4837
+ validateSchema(propSchema, `${path35}.properties.${propName}`, errors, { depth: depth + 1 });
4838
4838
  }
4839
4839
  }
4840
4840
  }
4841
4841
  if (schemaTypeIncludes(schema, "array") && "items" in schema) {
4842
4842
  if (Array.isArray(schema.items)) {
4843
- schema.items.forEach((sub, i) => validateSchema(sub, `${path36}.items[${i}]`, errors, { depth: depth + 1 }));
4843
+ schema.items.forEach((sub, i) => validateSchema(sub, `${path35}.items[${i}]`, errors, { depth: depth + 1 }));
4844
4844
  } else {
4845
- validateSchema(schema.items, `${path36}.items`, errors, { depth: depth + 1 });
4845
+ validateSchema(schema.items, `${path35}.items`, errors, { depth: depth + 1 });
4846
4846
  }
4847
4847
  }
4848
4848
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
4849
4849
  if (key in schema && isRecord(schema[key])) {
4850
- validateSchema(schema[key], `${path36}.${key}`, errors, { depth: depth + 1 });
4850
+ validateSchema(schema[key], `${path35}.${key}`, errors, { depth: depth + 1 });
4851
4851
  }
4852
4852
  }
4853
4853
  for (const key of SUBSCHEMA_LIST_KEYWORDS) {
4854
4854
  const list = schema[key];
4855
4855
  if (Array.isArray(list)) {
4856
- list.forEach((sub, i) => validateSchema(sub, `${path36}.${key}[${i}]`, errors, { depth: depth + 1 }));
4856
+ list.forEach((sub, i) => validateSchema(sub, `${path35}.${key}[${i}]`, errors, { depth: depth + 1 }));
4857
4857
  }
4858
4858
  }
4859
4859
  for (const key of SUBSCHEMA_MAP_KEYWORDS) {
4860
4860
  const map = schema[key];
4861
4861
  if (isRecord(map)) {
4862
4862
  for (const [name, sub] of Object.entries(map)) {
4863
- validateSchema(sub, `${path36}.${key}.${name}`, errors, { depth: depth + 1 });
4863
+ validateSchema(sub, `${path35}.${key}.${name}`, errors, { depth: depth + 1 });
4864
4864
  }
4865
4865
  }
4866
4866
  }
4867
4867
  const reportedPaths = new Set(errors.map((e) => e.path));
4868
4868
  for (const [k, v] of Object.entries(schema)) {
4869
4869
  if (typeof v !== "string") continue;
4870
- const fieldPath = `${path36}.${k}`;
4870
+ const fieldPath = `${path35}.${k}`;
4871
4871
  if (reportedPaths.has(fieldPath)) continue;
4872
4872
  for (const token of PLACEHOLDER_TOKENS) {
4873
4873
  if (v === token) {
@@ -5012,8 +5012,8 @@ function evalInitialStateError(input) {
5012
5012
  const parsed = evalInitialStateEntry.safeParse(entries[i]);
5013
5013
  if (!parsed.success) {
5014
5014
  const issue = parsed.error.issues[0];
5015
- const path36 = issue?.path.join(".") || "?";
5016
- return `initial_state[${i}].${path36} is invalid: ${issue?.message ?? "malformed"}`;
5015
+ const path35 = issue?.path.join(".") || "?";
5016
+ return `initial_state[${i}].${path35} is invalid: ${issue?.message ?? "malformed"}`;
5017
5017
  }
5018
5018
  if (seenSlugs.has(parsed.data.slug)) {
5019
5019
  return `initial_state[${i}].slug "${parsed.data.slug}" is declared more than once`;
@@ -5201,10 +5201,10 @@ function refineHubAsCodeEvals(config, ctx) {
5201
5201
  function refineHubAsCodeEvalAttachments(config, ctx) {
5202
5202
  const cfg = config;
5203
5203
  const referencedHashes = /* @__PURE__ */ new Set();
5204
- const addTurnIssue = (path36, turn, name) => {
5204
+ const addTurnIssue = (path35, turn, name) => {
5205
5205
  const error = evalTurnAttachmentsError(turn);
5206
5206
  if (error) {
5207
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path36, message: `${name}: ${error}` });
5207
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path35, message: `${name}: ${error}` });
5208
5208
  return;
5209
5209
  }
5210
5210
  for (const hash of collectTurnAttachmentHashes(turn)) referencedHashes.add(hash);
@@ -5350,11 +5350,11 @@ function refineHubAsCodeDelegation(config, ctx) {
5350
5350
  }
5351
5351
  }
5352
5352
  if (del.context_boundary === void 0) return;
5353
- const path36 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
5353
+ const path35 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
5354
5354
  if (del.type !== "hub") {
5355
5355
  ctx.addIssue({
5356
5356
  code: external_exports.ZodIssueCode.custom,
5357
- path: path36,
5357
+ path: path35,
5358
5358
  message: `context_boundary applies only to \`type: hub\` delegations (got type "${String(del.type)}")`
5359
5359
  });
5360
5360
  return;
@@ -5362,7 +5362,7 @@ function refineHubAsCodeDelegation(config, ctx) {
5362
5362
  if (!CONTEXT_BOUNDARIES.includes(del.context_boundary)) {
5363
5363
  ctx.addIssue({
5364
5364
  code: external_exports.ZodIssueCode.custom,
5365
- path: path36,
5365
+ path: path35,
5366
5366
  message: `context_boundary must be one of: ${CONTEXT_BOUNDARIES.join(", ")}`
5367
5367
  });
5368
5368
  }
@@ -8915,10 +8915,10 @@ function toDataProxyPath(v1Path) {
8915
8915
  }
8916
8916
  return `${DATA_PROXY_PREFIX}${v1Path.slice(REKOR_V1_PREFIX.length)}`;
8917
8917
  }
8918
- function withOrgSelector(path36, orgId) {
8919
- if (!orgId) return path36;
8920
- const separator = path36.includes("?") ? "&" : "?";
8921
- return `${path36}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
8918
+ function withOrgSelector(path35, orgId) {
8919
+ if (!orgId) return path35;
8920
+ const separator = path35.includes("?") ? "&" : "?";
8921
+ return `${path35}${separator}${DATA_PROXY_ORG_QUERY_PARAM}=${encodeURIComponent(orgId)}`;
8922
8922
  }
8923
8923
  function dataErrorMessage(err) {
8924
8924
  if (!(err instanceof ApiError)) return null;
@@ -8952,13 +8952,13 @@ var init_api_client = __esm({
8952
8952
  * telling them apart by body shape misreads one for the other.
8953
8953
  */
8954
8954
  path;
8955
- constructor(method, path36, status, body) {
8955
+ constructor(method, path35, status, body) {
8956
8956
  const safeBody = maskSecretsInMessage(body);
8957
- super(`API request failed: ${method} ${path36} (${status}): ${safeBody}`);
8957
+ super(`API request failed: ${method} ${path35} (${status}): ${safeBody}`);
8958
8958
  this.name = "ApiError";
8959
8959
  this.status = status;
8960
8960
  this.body = safeBody;
8961
- this.path = path36;
8961
+ this.path = path35;
8962
8962
  }
8963
8963
  /** True when the status code is a 4xx client error (expected user-facing condition, not a bug). */
8964
8964
  get isExpected() {
@@ -9060,8 +9060,8 @@ var init_api_client = __esm({
9060
9060
  ...opts?.check && { check: true }
9061
9061
  });
9062
9062
  }
9063
- async lookup(path36, opts) {
9064
- const params = new URLSearchParams({ path: path36 });
9063
+ async lookup(path35, opts) {
9064
+ const params = new URLSearchParams({ path: path35 });
9065
9065
  if (opts?.organizationId) params.set("organization_id", opts.organizationId);
9066
9066
  return this.request("GET", `/api/ci/lookup?${params.toString()}`);
9067
9067
  }
@@ -9504,9 +9504,9 @@ var init_api_client = __esm({
9504
9504
  * sandbox for this conversation, or the blob was purged).
9505
9505
  */
9506
9506
  async downloadArchiveSandboxFs(hubId, conversationId) {
9507
- const path36 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
9508
- addApiBreadcrumb("GET", path36);
9509
- const url = `${this.apiUrl}${path36}`;
9507
+ const path35 = `/api/archive/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}/sandbox`;
9508
+ addApiBreadcrumb("GET", path35);
9509
+ const url = `${this.apiUrl}${path35}`;
9510
9510
  let response = await this.send(url, "GET");
9511
9511
  if (response.status === 401 && this.onUnauthorized) {
9512
9512
  let refreshed;
@@ -9520,7 +9520,7 @@ var init_api_client = __esm({
9520
9520
  }
9521
9521
  }
9522
9522
  if (!response.ok) {
9523
- throw new ApiError("GET", path36, response.status, await response.text());
9523
+ throw new ApiError("GET", path35, response.status, await response.text());
9524
9524
  }
9525
9525
  return new Uint8Array(await response.arrayBuffer());
9526
9526
  }
@@ -9560,9 +9560,9 @@ var init_api_client = __esm({
9560
9560
  `/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
9561
9561
  );
9562
9562
  }
9563
- async request(method, path36, body, extraHeaders, contentType) {
9564
- addApiBreadcrumb(method, path36);
9565
- const url = `${this.apiUrl}${path36}`;
9563
+ async request(method, path35, body, extraHeaders, contentType) {
9564
+ addApiBreadcrumb(method, path35);
9565
+ const url = `${this.apiUrl}${path35}`;
9566
9566
  let refreshedOn401 = false;
9567
9567
  for (let retry = 0; ; retry++) {
9568
9568
  let response = await this.send(url, method, body, extraHeaders, contentType);
@@ -9587,7 +9587,7 @@ var init_api_client = __esm({
9587
9587
  await delay(RETRYABLE_BACKOFF_MS[retry]);
9588
9588
  continue;
9589
9589
  }
9590
- throw new ApiError(method, path36, response.status, errorBody);
9590
+ throw new ApiError(method, path35, response.status, errorBody);
9591
9591
  }
9592
9592
  }
9593
9593
  /**
@@ -9649,9 +9649,9 @@ var init_api_client = __esm({
9649
9649
  * transient-retry loop — re-streaming a blob is not worth a cold-start probe.
9650
9650
  */
9651
9651
  async dataDownload(v1Path, orgId) {
9652
- const path36 = withOrgSelector(toDataProxyPath(v1Path), orgId);
9653
- addApiBreadcrumb("GET", path36);
9654
- const url = `${this.apiUrl}${path36}`;
9652
+ const path35 = withOrgSelector(toDataProxyPath(v1Path), orgId);
9653
+ addApiBreadcrumb("GET", path35);
9654
+ const url = `${this.apiUrl}${path35}`;
9655
9655
  let response = await this.send(url, "GET");
9656
9656
  if (response.status === 401 && this.onUnauthorized) {
9657
9657
  let refreshed;
@@ -9665,7 +9665,7 @@ var init_api_client = __esm({
9665
9665
  }
9666
9666
  }
9667
9667
  if (!response.ok) {
9668
- throw new ApiError("GET", path36, response.status, await response.text());
9668
+ throw new ApiError("GET", path35, response.status, await response.text());
9669
9669
  }
9670
9670
  return {
9671
9671
  bytes: new Uint8Array(await response.arrayBuffer()),
@@ -9817,12 +9817,12 @@ function validateFollowupLinks2(followups, label, ctx) {
9817
9817
  const linkTargets = /* @__PURE__ */ new Set();
9818
9818
  followups.forEach((followup, i) => {
9819
9819
  const ref = followup?.after_followup_id;
9820
- const path36 = ["followups", i, "after_followup_id"];
9820
+ const path35 = ["followups", i, "after_followup_id"];
9821
9821
  if (followup?.type !== "inactivity_after_before_event") {
9822
9822
  if (ref !== void 0) {
9823
9823
  ctx.addIssue({
9824
9824
  code: external_exports.ZodIssueCode.custom,
9825
- path: path36,
9825
+ path: path35,
9826
9826
  message: `kanban status ${label}: after_followup_id is only valid on inactivity_after_before_event followups (this one is ${followup?.type})`
9827
9827
  });
9828
9828
  }
@@ -9831,7 +9831,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9831
9831
  if (ref === void 0) {
9832
9832
  ctx.addIssue({
9833
9833
  code: external_exports.ZodIssueCode.custom,
9834
- path: path36,
9834
+ path: path35,
9835
9835
  message: `kanban status ${label}: inactivity_after_before_event requires after_followup_id naming the id of a before_event followup in the same status`
9836
9836
  });
9837
9837
  return;
@@ -9841,7 +9841,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9841
9841
  if (matches.length > 1) {
9842
9842
  ctx.addIssue({
9843
9843
  code: external_exports.ZodIssueCode.custom,
9844
- path: path36,
9844
+ path: path35,
9845
9845
  message: `kanban status ${label}: after_followup_id "${ref}" matches ${matches.length} followups \u2014 a link must name exactly one`
9846
9846
  });
9847
9847
  return;
@@ -9853,7 +9853,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9853
9853
  );
9854
9854
  ctx.addIssue({
9855
9855
  code: external_exports.ZodIssueCode.custom,
9856
- path: path36,
9856
+ path: path35,
9857
9857
  message: hasIdlessBeforeEvent ? `kanban status ${label}: after_followup_id "${ref}" matches no followup \u2014 a link target must declare an explicit id, and this status has before_event followups without one` : `kanban status ${label}: after_followup_id "${ref}" matches no followup in this status`
9858
9858
  });
9859
9859
  return;
@@ -9861,7 +9861,7 @@ function validateFollowupLinks2(followups, label, ctx) {
9861
9861
  if (target.type !== "before_event") {
9862
9862
  ctx.addIssue({
9863
9863
  code: external_exports.ZodIssueCode.custom,
9864
- path: path36,
9864
+ path: path35,
9865
9865
  message: `kanban status ${label}: after_followup_id "${ref}" points at a ${target.type} followup \u2014 only before_event followups can anchor a chain`
9866
9866
  });
9867
9867
  }
@@ -9937,12 +9937,12 @@ function typeMatches2(typeField, allowed) {
9937
9937
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
9938
9938
  return false;
9939
9939
  }
9940
- function validateSchema2(schema, path36, errors, opts = {}) {
9940
+ function validateSchema2(schema, path35, errors, opts = {}) {
9941
9941
  if (typeof schema === "boolean") return;
9942
9942
  const depth = opts.depth ?? 0;
9943
9943
  if (depth > MAX_SCHEMA_DEPTH2) {
9944
9944
  errors.push({
9945
- path: path36 || "<root>",
9945
+ path: path35 || "<root>",
9946
9946
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
9947
9947
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
9948
9948
  });
@@ -9950,7 +9950,7 @@ function validateSchema2(schema, path36, errors, opts = {}) {
9950
9950
  }
9951
9951
  if (!isRecord2(schema)) {
9952
9952
  errors.push({
9953
- path: path36,
9953
+ path: path35,
9954
9954
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
9955
9955
  });
9956
9956
  return;
@@ -9958,14 +9958,14 @@ function validateSchema2(schema, path36, errors, opts = {}) {
9958
9958
  if (opts.isRoot) {
9959
9959
  if ("type" in schema && schema.type !== "object") {
9960
9960
  errors.push({
9961
- path: path36 ? `${path36}.type` : "type",
9961
+ path: path35 ? `${path35}.type` : "type",
9962
9962
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
9963
9963
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
9964
9964
  });
9965
9965
  }
9966
9966
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
9967
9967
  errors.push({
9968
- path: `${path36}.type`,
9968
+ path: `${path35}.type`,
9969
9969
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
9970
9970
  });
9971
9971
  }
@@ -9975,25 +9975,25 @@ function validateSchema2(schema, path36, errors, opts = {}) {
9975
9975
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
9976
9976
  const isOutcomePlaceholder = e === "[KANBAN_OUTCOME_VALUES]";
9977
9977
  errors.push({
9978
- path: `${path36}.enum`,
9978
+ path: `${path35}.enum`,
9979
9979
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
9980
9980
  suggestion: isOutcomePlaceholder ? "Declare `outcomes` on the hub's terminal kanban status so the platform can render the placeholder into a valid array; with none configured the platform drops the parameter instead." : isPlaceholder ? "Set `operation: 'update_kanban_status'` on the tool and ensure at least one hub.kanban_status has `allowsAgentUpdate: true` so the platform can render the placeholder into a valid array." : 'Wrap the value in an array: `enum: ["value"]`.'
9981
9981
  });
9982
9982
  } else if (!Array.isArray(e)) {
9983
9983
  errors.push({
9984
- path: `${path36}.enum`,
9984
+ path: `${path35}.enum`,
9985
9985
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
9986
9986
  });
9987
9987
  } else if (e.length === 0) {
9988
9988
  errors.push({
9989
- path: `${path36}.enum`,
9989
+ path: `${path35}.enum`,
9990
9990
  message: "enum must not be empty"
9991
9991
  });
9992
9992
  } else {
9993
9993
  for (let i = 0; i < e.length; i++) {
9994
9994
  if (!isPrimitive2(e[i])) {
9995
9995
  errors.push({
9996
- path: `${path36}.enum[${i}]`,
9996
+ path: `${path35}.enum[${i}]`,
9997
9997
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
9998
9998
  });
9999
9999
  }
@@ -10003,7 +10003,7 @@ function validateSchema2(schema, path36, errors, opts = {}) {
10003
10003
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
10004
10004
  if (key in schema && typeof schema[key] === "boolean") {
10005
10005
  errors.push({
10006
- path: `${path36}.${key}`,
10006
+ path: `${path35}.${key}`,
10007
10007
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
10008
10008
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
10009
10009
  });
@@ -10012,45 +10012,45 @@ function validateSchema2(schema, path36, errors, opts = {}) {
10012
10012
  if ("properties" in schema) {
10013
10013
  if (!isRecord2(schema.properties)) {
10014
10014
  errors.push({
10015
- path: `${path36}.properties`,
10015
+ path: `${path35}.properties`,
10016
10016
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
10017
10017
  });
10018
10018
  } else {
10019
10019
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
10020
- validateSchema2(propSchema, `${path36}.properties.${propName}`, errors, { depth: depth + 1 });
10020
+ validateSchema2(propSchema, `${path35}.properties.${propName}`, errors, { depth: depth + 1 });
10021
10021
  }
10022
10022
  }
10023
10023
  }
10024
10024
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
10025
10025
  if (Array.isArray(schema.items)) {
10026
- schema.items.forEach((sub, i) => validateSchema2(sub, `${path36}.items[${i}]`, errors, { depth: depth + 1 }));
10026
+ schema.items.forEach((sub, i) => validateSchema2(sub, `${path35}.items[${i}]`, errors, { depth: depth + 1 }));
10027
10027
  } else {
10028
- validateSchema2(schema.items, `${path36}.items`, errors, { depth: depth + 1 });
10028
+ validateSchema2(schema.items, `${path35}.items`, errors, { depth: depth + 1 });
10029
10029
  }
10030
10030
  }
10031
10031
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
10032
10032
  if (key in schema && isRecord2(schema[key])) {
10033
- validateSchema2(schema[key], `${path36}.${key}`, errors, { depth: depth + 1 });
10033
+ validateSchema2(schema[key], `${path35}.${key}`, errors, { depth: depth + 1 });
10034
10034
  }
10035
10035
  }
10036
10036
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
10037
10037
  const list = schema[key];
10038
10038
  if (Array.isArray(list)) {
10039
- list.forEach((sub, i) => validateSchema2(sub, `${path36}.${key}[${i}]`, errors, { depth: depth + 1 }));
10039
+ list.forEach((sub, i) => validateSchema2(sub, `${path35}.${key}[${i}]`, errors, { depth: depth + 1 }));
10040
10040
  }
10041
10041
  }
10042
10042
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
10043
10043
  const map = schema[key];
10044
10044
  if (isRecord2(map)) {
10045
10045
  for (const [name, sub] of Object.entries(map)) {
10046
- validateSchema2(sub, `${path36}.${key}.${name}`, errors, { depth: depth + 1 });
10046
+ validateSchema2(sub, `${path35}.${key}.${name}`, errors, { depth: depth + 1 });
10047
10047
  }
10048
10048
  }
10049
10049
  }
10050
10050
  const reportedPaths = new Set(errors.map((e) => e.path));
10051
10051
  for (const [k, v] of Object.entries(schema)) {
10052
10052
  if (typeof v !== "string") continue;
10053
- const fieldPath = `${path36}.${k}`;
10053
+ const fieldPath = `${path35}.${k}`;
10054
10054
  if (reportedPaths.has(fieldPath)) continue;
10055
10055
  for (const token of PLACEHOLDER_TOKENS2) {
10056
10056
  if (v === token) {
@@ -10195,8 +10195,8 @@ function evalInitialStateError2(input) {
10195
10195
  const parsed = evalInitialStateEntry2.safeParse(entries[i]);
10196
10196
  if (!parsed.success) {
10197
10197
  const issue = parsed.error.issues[0];
10198
- const path36 = issue?.path.join(".") || "?";
10199
- return `initial_state[${i}].${path36} is invalid: ${issue?.message ?? "malformed"}`;
10198
+ const path35 = issue?.path.join(".") || "?";
10199
+ return `initial_state[${i}].${path35} is invalid: ${issue?.message ?? "malformed"}`;
10200
10200
  }
10201
10201
  if (seenSlugs.has(parsed.data.slug)) {
10202
10202
  return `initial_state[${i}].slug "${parsed.data.slug}" is declared more than once`;
@@ -10401,10 +10401,10 @@ function refineHubAsCodeEvals2(config, ctx) {
10401
10401
  function refineHubAsCodeEvalAttachments2(config, ctx) {
10402
10402
  const cfg = config;
10403
10403
  const referencedHashes = /* @__PURE__ */ new Set();
10404
- const addTurnIssue = (path36, turn, name) => {
10404
+ const addTurnIssue = (path35, turn, name) => {
10405
10405
  const error = evalTurnAttachmentsError2(turn);
10406
10406
  if (error) {
10407
- ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path36, message: `${name}: ${error}` });
10407
+ ctx.addIssue({ code: external_exports.ZodIssueCode.custom, path: path35, message: `${name}: ${error}` });
10408
10408
  return;
10409
10409
  }
10410
10410
  for (const hash of collectTurnAttachmentHashes2(turn)) referencedHashes.add(hash);
@@ -10550,11 +10550,11 @@ function refineHubAsCodeDelegation2(config, ctx) {
10550
10550
  }
10551
10551
  }
10552
10552
  if (del.context_boundary === void 0) return;
10553
- const path36 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
10553
+ const path35 = ["agents", agentIndex, "tools", "delegation", delIndex, "context_boundary"];
10554
10554
  if (del.type !== "hub") {
10555
10555
  ctx.addIssue({
10556
10556
  code: external_exports.ZodIssueCode.custom,
10557
- path: path36,
10557
+ path: path35,
10558
10558
  message: `context_boundary applies only to \`type: hub\` delegations (got type "${String(del.type)}")`
10559
10559
  });
10560
10560
  return;
@@ -10562,7 +10562,7 @@ function refineHubAsCodeDelegation2(config, ctx) {
10562
10562
  if (!CONTEXT_BOUNDARIES2.includes(del.context_boundary)) {
10563
10563
  ctx.addIssue({
10564
10564
  code: external_exports.ZodIssueCode.custom,
10565
- path: path36,
10565
+ path: path35,
10566
10566
  message: `context_boundary must be one of: ${CONTEXT_BOUNDARIES2.join(", ")}`
10567
10567
  });
10568
10568
  }
@@ -15442,7 +15442,7 @@ var init_registry = __esm({
15442
15442
  surviving: "bases use",
15443
15443
  clause: 3,
15444
15444
  shipped: true,
15445
- reason: "WayAI's `use` binds a hub; this binds a base."
15445
+ reason: "Deprecated since the worktree scope merged into one set-valued file: `wayai use bases/<id>` covers this. Kept as an alias for its JSON output shape."
15446
15446
  },
15447
15447
  {
15448
15448
  original: "unbind",
@@ -15450,7 +15450,7 @@ var init_registry = __esm({
15450
15450
  surviving: "bases unbind",
15451
15451
  clause: 3,
15452
15452
  shipped: true,
15453
- reason: "WayAI's `unbind` clears the hub binding; this clears the base binding."
15453
+ reason: "Deprecated alongside `bases use`: `wayai unbind bases/<id>` covers this. It still clears only the bases axis of the worktree scope, never the hubs one."
15454
15454
  },
15455
15455
  {
15456
15456
  original: "bases promote",
@@ -15571,9 +15571,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
15571
15571
  }
15572
15572
  function readVersionCache(filename = CLI_CACHE_FILE) {
15573
15573
  try {
15574
- const path36 = getVersionCachePath(filename);
15575
- if (!existsSync4(path36)) return null;
15576
- const parsed = JSON.parse(readFileSync5(path36, "utf-8"));
15574
+ const path35 = getVersionCachePath(filename);
15575
+ if (!existsSync4(path35)) return null;
15576
+ const parsed = JSON.parse(readFileSync5(path35, "utf-8"));
15577
15577
  if (typeof parsed.lastCheck !== "number") return null;
15578
15578
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
15579
15579
  return parsed;
@@ -15593,10 +15593,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
15593
15593
  return Date.now() - cache.lastCheck > maxAgeMs;
15594
15594
  }
15595
15595
  function writeVersionCache(filename, cache) {
15596
- const path36 = getVersionCachePath(filename);
15597
- const dir = dirname3(path36);
15596
+ const path35 = getVersionCachePath(filename);
15597
+ const dir = dirname3(path35);
15598
15598
  if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
15599
- writeFileSync2(path36, JSON.stringify(cache));
15599
+ writeFileSync2(path35, JSON.stringify(cache));
15600
15600
  }
15601
15601
  function touchVersionCache(filename) {
15602
15602
  writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
@@ -15635,14 +15635,14 @@ function parseFrontmatterVersion(content) {
15635
15635
  function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
15636
15636
  const found = [];
15637
15637
  for (const rel of paths) {
15638
- const path36 = join7(projectRoot, rel);
15639
- if (!existsSync5(path36)) continue;
15638
+ const path35 = join7(projectRoot, rel);
15639
+ if (!existsSync5(path35)) continue;
15640
15640
  let version = null;
15641
15641
  try {
15642
- version = parseFrontmatterVersion(readFileSync6(path36, "utf-8"));
15642
+ version = parseFrontmatterVersion(readFileSync6(path35, "utf-8"));
15643
15643
  } catch {
15644
15644
  }
15645
- found.push({ path: path36, version });
15645
+ found.push({ path: path35, version });
15646
15646
  }
15647
15647
  return found;
15648
15648
  }
@@ -16520,10 +16520,33 @@ var init_logout = __esm({
16520
16520
  }
16521
16521
  });
16522
16522
 
16523
- // src/lib/worktree-binding.ts
16523
+ // src/lib/base-id.ts
16524
+ function isValidBaseId(id) {
16525
+ return id.length > 0 && id.length <= BASE_ID_MAX_LENGTH && BASE_ID_RE.test(id);
16526
+ }
16527
+ function isPathSafeId(id) {
16528
+ return PATH_SAFE_ID_RE.test(id);
16529
+ }
16530
+ var BASE_ID_MAX_LENGTH, BASE_ID_RE, BASE_ID_RULE, PATH_SAFE_ID_RE, PATH_SAFE_ID_RULE;
16531
+ var init_base_id = __esm({
16532
+ "src/lib/base-id.ts"() {
16533
+ "use strict";
16534
+ BASE_ID_MAX_LENGTH = 128;
16535
+ BASE_ID_RE = /^[A-Za-z0-9_][A-Za-z0-9_-]*$/;
16536
+ BASE_ID_RULE = `letters, digits, \`-\` and \`_\`, starting with a letter, digit or \`_\`, ${BASE_ID_MAX_LENGTH} characters or fewer`;
16537
+ PATH_SAFE_ID_RE = /^(?!\.\.?$)[A-Za-z0-9._-]{1,256}$/;
16538
+ PATH_SAFE_ID_RULE = "letters, digits, dot, dash and underscore, with no path separators, not `.` or `..` alone, 256 characters or fewer";
16539
+ }
16540
+ });
16541
+
16542
+ // src/lib/worktree-scope.ts
16524
16543
  import { execFileSync as execFileSync2 } from "child_process";
16525
16544
  import * as fs7 from "fs";
16526
16545
  import * as path7 from "path";
16546
+ import * as yaml4 from "js-yaml";
16547
+ function axisNoun(axis) {
16548
+ return AXES[axis].noun;
16549
+ }
16527
16550
  function resolveGitDir() {
16528
16551
  const cwd = process.cwd();
16529
16552
  if (_gitDirCache && _gitDirCache.cwd === cwd) return _gitDirCache.gitDir;
@@ -16541,158 +16564,227 @@ function resolveGitDir() {
16541
16564
  _gitDirCache = { cwd, gitDir };
16542
16565
  return gitDir;
16543
16566
  }
16544
- function createWorktreeBinding(descriptor) {
16545
- const {
16546
- filename,
16547
- legacyFilename,
16548
- isValidId,
16549
- noun,
16550
- idLabel,
16551
- unbindCommand: unbindCommand2,
16552
- useCommand: useCommand2
16553
- } = descriptor;
16554
- function getBindingPath2() {
16555
- const gitDir = resolveGitDir();
16556
- return gitDir ? path7.join(gitDir, filename) : null;
16557
- }
16558
- function getLegacyBindingPath() {
16559
- if (!legacyFilename) return null;
16560
- const gitDir = resolveGitDir();
16561
- return gitDir ? path7.join(gitDir, legacyFilename) : null;
16562
- }
16563
- function readBinding2() {
16564
- for (const bindingPath of [getBindingPath2(), getLegacyBindingPath()]) {
16565
- if (!bindingPath) continue;
16566
- let raw;
16567
- try {
16568
- raw = fs7.readFileSync(bindingPath, "utf-8");
16569
- } catch (err) {
16570
- if (err.code === "ENOENT") continue;
16571
- throw err;
16572
- }
16573
- const trimmed = raw.trim();
16574
- return isValidId(trimmed) ? trimmed : null;
16567
+ function getScopePath() {
16568
+ const gitDir = resolveGitDir();
16569
+ return gitDir ? path7.join(gitDir, SCOPE_FILE) : null;
16570
+ }
16571
+ function legacyPaths() {
16572
+ const gitDir = resolveGitDir();
16573
+ if (!gitDir) return [];
16574
+ return AXIS_ORDER.flatMap((axis) => AXES[axis].legacyFiles).map((f) => path7.join(gitDir, f));
16575
+ }
16576
+ function emptyScope() {
16577
+ return { hubs: [], bases: [] };
16578
+ }
16579
+ function parseAxis(raw, axis) {
16580
+ if (!Array.isArray(raw)) return [];
16581
+ const { isValidId } = AXES[axis];
16582
+ const out = [];
16583
+ for (const entry of raw) {
16584
+ if (typeof entry !== "string") continue;
16585
+ const id = entry.trim();
16586
+ if (!isValidId(id) || out.includes(id)) continue;
16587
+ out.push(id);
16588
+ }
16589
+ return out;
16590
+ }
16591
+ function readLegacyAxis(axis) {
16592
+ const gitDir = resolveGitDir();
16593
+ if (!gitDir) return [];
16594
+ const { isValidId, legacyFiles } = AXES[axis];
16595
+ for (const filename of legacyFiles) {
16596
+ let raw;
16597
+ try {
16598
+ raw = fs7.readFileSync(path7.join(gitDir, filename), "utf-8");
16599
+ } catch (err) {
16600
+ if (err.code === "ENOENT") continue;
16601
+ throw err;
16575
16602
  }
16576
- return null;
16603
+ const trimmed = raw.trim();
16604
+ return isValidId(trimmed) ? [trimmed] : [];
16577
16605
  }
16578
- function removeLegacyBinding() {
16579
- const legacyPath = getLegacyBindingPath();
16580
- if (!legacyPath) return;
16606
+ return [];
16607
+ }
16608
+ function readScope() {
16609
+ const scopePath = getScopePath();
16610
+ if (!scopePath) return emptyScope();
16611
+ let raw;
16612
+ try {
16613
+ raw = fs7.readFileSync(scopePath, "utf-8");
16614
+ } catch (err) {
16615
+ if (err.code === "ENOENT") {
16616
+ return { hubs: readLegacyAxis("hubs"), bases: readLegacyAxis("bases") };
16617
+ }
16618
+ throw err;
16619
+ }
16620
+ let doc;
16621
+ try {
16622
+ doc = yaml4.load(raw);
16623
+ } catch {
16624
+ return emptyScope();
16625
+ }
16626
+ if (!doc || typeof doc !== "object" || Array.isArray(doc)) return emptyScope();
16627
+ const map = doc;
16628
+ return { hubs: parseAxis(map.hubs, "hubs"), bases: parseAxis(map.bases, "bases") };
16629
+ }
16630
+ function sweepLegacyFiles() {
16631
+ for (const legacyPath of legacyPaths()) {
16581
16632
  try {
16582
16633
  fs7.unlinkSync(legacyPath);
16583
16634
  } catch {
16584
16635
  }
16585
16636
  }
16586
- function writeBinding2(id) {
16587
- if (!isValidId(id)) {
16588
- throw new Error(`Invalid ${idLabel}: ${id}`);
16589
- }
16590
- const bindingPath = getBindingPath2();
16591
- if (!bindingPath) {
16592
- throw new Error(`Not inside a git repository \u2014 cannot write ${noun} binding.`);
16637
+ }
16638
+ function writeScope(scope) {
16639
+ for (const axis of AXIS_ORDER) {
16640
+ const { isValidId, idLabel } = AXES[axis];
16641
+ for (const id of scope[axis]) {
16642
+ if (!isValidId(id)) throw expected(`Invalid ${idLabel}: ${id}`);
16593
16643
  }
16594
- fs7.writeFileSync(bindingPath, `${id}
16595
- `, "utf-8");
16596
- removeLegacyBinding();
16597
16644
  }
16598
- function clearBinding2() {
16599
- let removed = false;
16600
- for (const bindingPath of [getBindingPath2(), getLegacyBindingPath()]) {
16601
- if (!bindingPath) continue;
16602
- try {
16603
- fs7.unlinkSync(bindingPath);
16604
- removed = true;
16605
- } catch (err) {
16606
- if (err.code !== "ENOENT") throw err;
16607
- }
16608
- }
16609
- return removed;
16645
+ const scopePath = getScopePath();
16646
+ if (!scopePath) {
16647
+ throw expected("Not inside a git repository \u2014 cannot write the worktree scope.");
16610
16648
  }
16611
- function assertMatchesBinding(resolvedId, label) {
16612
- const bound = readBinding2();
16613
- if (!bound) return;
16614
- if (bound === resolvedId) return;
16615
- const target = label ? `${label} (${resolvedId})` : resolvedId;
16616
- console.error(
16617
- [
16618
- `This worktree is bound to ${noun} ${bound}, but the command resolved to ${target}.`,
16619
- "",
16620
- "This usually means a prompt was routed to the wrong worktree.",
16621
- "Confirm with the user before unbinding. To override:",
16622
- ` ${unbindCommand2} # clear the binding for this worktree`,
16623
- ` ${useCommand2} ${resolvedId} # rebind this worktree to the new ${noun}`
16624
- ].join("\n")
16625
- );
16626
- process.exit(1);
16649
+ const body = yaml4.dump({ hubs: scope.hubs, bases: scope.bases }, { flowLevel: 1 });
16650
+ const tmpPath = `${scopePath}.tmp-${process.pid}`;
16651
+ try {
16652
+ fs7.writeFileSync(tmpPath, body, "utf-8");
16653
+ fs7.renameSync(tmpPath, scopePath);
16654
+ } catch (err) {
16655
+ try {
16656
+ fs7.unlinkSync(tmpPath);
16657
+ } catch {
16658
+ }
16659
+ throw asExpectedEnvironmentError(err);
16627
16660
  }
16628
- function assertNoBindingBlocksCreation(newName) {
16629
- const bound = readBinding2();
16630
- if (!bound) return;
16631
- console.error(
16632
- [
16633
- `This worktree is bound to ${noun} ${bound}, but you're about to create a new ${noun} "${newName}" here.`,
16634
- "",
16635
- "This usually means a prompt was routed to the wrong worktree.",
16636
- "Confirm with the user before unbinding. To override:",
16637
- ` ${unbindCommand2} # clear the binding for this worktree`
16638
- ].join("\n")
16639
- );
16640
- process.exit(1);
16661
+ sweepLegacyFiles();
16662
+ }
16663
+ function asExpectedEnvironmentError(err) {
16664
+ if (err instanceof Error && typeof err.code === "string") {
16665
+ return expected(err.message);
16641
16666
  }
16642
- function autoBindIfUnbound2(id) {
16667
+ return err;
16668
+ }
16669
+ function setScopeAxis(axis, ids) {
16670
+ writeScope({ ...readScope(), [axis]: ids });
16671
+ return ids;
16672
+ }
16673
+ function addToScope(axis, id) {
16674
+ const scope = readScope();
16675
+ if (scope[axis].includes(id)) return null;
16676
+ const next = [...scope[axis], id];
16677
+ writeScope({ ...scope, [axis]: next });
16678
+ return next;
16679
+ }
16680
+ function removeFromScope(axis, id) {
16681
+ const scope = readScope();
16682
+ if (!scope[axis].includes(id)) return null;
16683
+ const next = scope[axis].filter((entry) => entry !== id);
16684
+ writeScope({ ...scope, [axis]: next });
16685
+ return next;
16686
+ }
16687
+ function clearScope() {
16688
+ const scopePath = getScopePath();
16689
+ let removed = false;
16690
+ for (const target of [...scopePath ? [scopePath] : [], ...legacyPaths()]) {
16643
16691
  try {
16644
- if (readBinding2()) return false;
16645
- writeBinding2(id);
16646
- console.log(`Worktree bound to ${noun} ${id} (run \`${unbindCommand2}\` to clear).`);
16647
- return true;
16692
+ fs7.unlinkSync(target);
16693
+ removed = true;
16648
16694
  } catch (err) {
16649
- console.warn(
16650
- ` Warning: could not auto-bind worktree (${err instanceof Error ? err.message : String(err)})`
16651
- );
16652
- return false;
16695
+ if (err.code !== "ENOENT") throw err;
16653
16696
  }
16654
16697
  }
16655
- return {
16656
- getBindingPath: getBindingPath2,
16657
- readBinding: readBinding2,
16658
- writeBinding: writeBinding2,
16659
- clearBinding: clearBinding2,
16660
- assertMatchesBinding,
16661
- assertNoBindingBlocksCreation,
16662
- autoBindIfUnbound: autoBindIfUnbound2
16663
- };
16698
+ return removed;
16664
16699
  }
16665
- var _gitDirCache;
16666
- var init_worktree_binding = __esm({
16667
- "src/lib/worktree-binding.ts"() {
16668
- "use strict";
16700
+ function describeAxis(axis, ids) {
16701
+ const { noun } = AXES[axis];
16702
+ return ids.length === 1 ? `${noun} ${ids[0]}` : `${noun}s ${ids.join(", ")}`;
16703
+ }
16704
+ function describeScope(scope) {
16705
+ return AXIS_ORDER.filter((axis) => scope[axis].length > 0).map((axis) => describeAxis(axis, scope[axis])).join("; ");
16706
+ }
16707
+ function assertInScope(axis, resolvedId, label) {
16708
+ const scoped = readScope()[axis];
16709
+ if (scoped.length === 0) return;
16710
+ if (scoped.includes(resolvedId)) return;
16711
+ const { noun, selectorFor } = AXES[axis];
16712
+ const selector = selectorFor(resolvedId);
16713
+ const target = label ? `${label} (${resolvedId})` : resolvedId;
16714
+ console.error(
16715
+ [
16716
+ `This worktree is scoped to ${describeAxis(axis, scoped)}, but the command resolved to ${target}.`,
16717
+ "",
16718
+ "This usually means a prompt was routed to the wrong worktree.",
16719
+ "Confirm with the user before changing this scope. To override:",
16720
+ ` ${REPLACE_COMMAND} ${selector} # scope this worktree to that ${noun} INSTEAD`,
16721
+ ` ${ADD_COMMAND} ${selector} # scope it to that ${noun} AS WELL`,
16722
+ ` ${CLEAR_COMMAND} # clear the whole worktree scope (both hubs and bases)`
16723
+ ].join("\n")
16724
+ );
16725
+ process.exit(1);
16726
+ }
16727
+ function assertScopeAllowsCreation(axis, newName) {
16728
+ const scoped = readScope()[axis];
16729
+ if (scoped.length === 0) return;
16730
+ const { noun } = AXES[axis];
16731
+ console.error(
16732
+ [
16733
+ `This worktree is scoped to ${describeAxis(axis, scoped)}, but you're about to create a new ${noun} "${newName}" here.`,
16734
+ "",
16735
+ "This usually means a prompt was routed to the wrong worktree.",
16736
+ "Confirm with the user before clearing this scope. To override:",
16737
+ ` ${CLEAR_COMMAND} # clear the whole worktree scope (both hubs and bases)`
16738
+ ].join("\n")
16739
+ );
16740
+ process.exit(1);
16741
+ }
16742
+ function seedScopeIfEmpty(axis, id) {
16743
+ try {
16744
+ if (readScope()[axis].length > 0) return false;
16745
+ setScopeAxis(axis, [id]);
16746
+ console.log(
16747
+ `Worktree bound to ${AXES[axis].noun} ${id} (run \`${CLEAR_COMMAND}\` to clear).`
16748
+ );
16749
+ return true;
16750
+ } catch (err) {
16751
+ console.warn(
16752
+ ` Warning: could not bind worktree (${err instanceof Error ? err.message : String(err)})`
16753
+ );
16754
+ return false;
16669
16755
  }
16670
- });
16671
-
16672
- // src/lib/hub-binding.ts
16673
- var binding, getBindingPath, readBinding, writeBinding, clearBinding, autoBindIfUnbound, assertHubMatchesBinding, assertNoBindingBlocksHubCreation;
16674
- var init_hub_binding = __esm({
16675
- "src/lib/hub-binding.ts"() {
16756
+ }
16757
+ var SCOPE_FILE, LEGACY_HUB_SCOPE_FILE, LEGACY_HUB_SCOPE_FILE_PRE_RENAME, LEGACY_BASE_SCOPE_FILE, AXES, AXIS_ORDER, ADD_COMMAND, REPLACE_COMMAND, CLEAR_COMMAND, _gitDirCache;
16758
+ var init_worktree_scope = __esm({
16759
+ "src/lib/worktree-scope.ts"() {
16676
16760
  "use strict";
16761
+ init_base_id();
16762
+ init_expected();
16677
16763
  init_utils();
16678
- init_worktree_binding();
16679
- binding = createWorktreeBinding({
16680
- filename: "wayai-binding",
16681
- // Pre-rename filename, from when this was called a "lock".
16682
- legacyFilename: "wayai-lock",
16683
- isValidId: (id) => UUID_RE2.test(id),
16684
- noun: "hub",
16685
- idLabel: "hub_id (must be a UUID)",
16686
- unbindCommand: "wayai unbind",
16687
- useCommand: "wayai use"
16688
- });
16689
- getBindingPath = binding.getBindingPath;
16690
- readBinding = binding.readBinding;
16691
- writeBinding = binding.writeBinding;
16692
- clearBinding = binding.clearBinding;
16693
- autoBindIfUnbound = binding.autoBindIfUnbound;
16694
- assertHubMatchesBinding = binding.assertMatchesBinding;
16695
- assertNoBindingBlocksHubCreation = binding.assertNoBindingBlocksCreation;
16764
+ SCOPE_FILE = "wayai-scope";
16765
+ LEGACY_HUB_SCOPE_FILE = "wayai-binding";
16766
+ LEGACY_HUB_SCOPE_FILE_PRE_RENAME = "wayai-lock";
16767
+ LEGACY_BASE_SCOPE_FILE = "wayai-base-binding";
16768
+ AXES = {
16769
+ hubs: {
16770
+ noun: "hub",
16771
+ idLabel: "hub_id (must be a UUID)",
16772
+ isValidId: (id) => UUID_RE2.test(id),
16773
+ selectorFor: (id) => id,
16774
+ legacyFiles: [LEGACY_HUB_SCOPE_FILE, LEGACY_HUB_SCOPE_FILE_PRE_RENAME]
16775
+ },
16776
+ bases: {
16777
+ noun: "base",
16778
+ idLabel: `base id (${PATH_SAFE_ID_RULE})`,
16779
+ isValidId: isPathSafeId,
16780
+ selectorFor: (id) => `bases/${id}`,
16781
+ legacyFiles: [LEGACY_BASE_SCOPE_FILE]
16782
+ }
16783
+ };
16784
+ AXIS_ORDER = ["hubs", "bases"];
16785
+ ADD_COMMAND = "wayai use --add";
16786
+ REPLACE_COMMAND = "wayai use";
16787
+ CLEAR_COMMAND = "wayai unbind";
16696
16788
  }
16697
16789
  });
16698
16790
 
@@ -16738,12 +16830,13 @@ async function statusCommand(args2, pkg2) {
16738
16830
  const cachedCliLatest = readCachedLatest(CLI_CACHE_FILE);
16739
16831
  const cliLatest = cachedCliLatest && isNewerVersion(cachedCliLatest, pkg2.version) ? cachedCliLatest : null;
16740
16832
  const skill = buildSkillState();
16741
- let boundHubId = null;
16833
+ let scope;
16742
16834
  try {
16743
- boundHubId = readBinding();
16835
+ scope = readScope();
16744
16836
  } catch {
16745
- boundHubId = null;
16837
+ scope = emptyScope();
16746
16838
  }
16839
+ const boundHubId = scope.hubs[0] ?? null;
16747
16840
  if (!config) {
16748
16841
  const snapshot2 = {
16749
16842
  logged_in: false,
@@ -16756,6 +16849,7 @@ async function statusCommand(args2, pkg2) {
16756
16849
  orgs: [],
16757
16850
  active_org: null,
16758
16851
  workspace,
16852
+ worktree_scope: scope,
16759
16853
  worktree_binding: { hub_id: boundHubId },
16760
16854
  worktree_lock: { hub_id: boundHubId }
16761
16855
  };
@@ -16811,6 +16905,7 @@ async function statusCommand(args2, pkg2) {
16811
16905
  orgs,
16812
16906
  active_org: activeOrg,
16813
16907
  workspace,
16908
+ worktree_scope: scope,
16814
16909
  worktree_binding: { hub_id: boundHubId },
16815
16910
  worktree_lock: { hub_id: boundHubId }
16816
16911
  };
@@ -16842,8 +16937,10 @@ async function statusCommand(args2, pkg2) {
16842
16937
  } else {
16843
16938
  console.log("No .wayai.yaml found \u2014 run `wayai init` to scope this repo to an organization.");
16844
16939
  }
16845
- if (boundHubId) {
16846
- console.log(`Worktree binding: ${boundHubId} (run \`wayai unbind\` to clear)`);
16940
+ if (scope.hubs.length > 0 || scope.bases.length > 0) {
16941
+ console.log("Worktree scope: (run `wayai unbind` to clear)");
16942
+ if (scope.hubs.length > 0) console.log(` Hubs: ${scope.hubs.join(", ")}`);
16943
+ if (scope.bases.length > 0) console.log(` Bases: ${scope.bases.join(", ")}`);
16847
16944
  }
16848
16945
  if (tokenValid === true && accessToken) {
16849
16946
  await nudgeReportActions(config.api_url, accessToken);
@@ -16856,7 +16953,7 @@ var init_status = __esm({
16856
16953
  init_auth();
16857
16954
  init_repo_config();
16858
16955
  init_workspace();
16859
- init_hub_binding();
16956
+ init_worktree_scope();
16860
16957
  init_api_client();
16861
16958
  init_version_cache();
16862
16959
  init_skill_version();
@@ -17684,7 +17781,7 @@ var init_eval_attachments = __esm({
17684
17781
  // src/lib/parser.ts
17685
17782
  import * as fs12 from "fs";
17686
17783
  import * as path14 from "path";
17687
- import * as yaml4 from "js-yaml";
17784
+ import * as yaml5 from "js-yaml";
17688
17785
  function parseHubFolder(hubFolder, opts) {
17689
17786
  const yamlPath = resolveHubYamlPath(hubFolder);
17690
17787
  if (!yamlPath) {
@@ -17693,9 +17790,9 @@ function parseHubFolder(hubFolder, opts) {
17693
17790
  const yamlContent = fs12.readFileSync(yamlPath, "utf-8");
17694
17791
  let config;
17695
17792
  try {
17696
- config = yaml4.load(yamlContent);
17793
+ config = yaml5.load(yamlContent);
17697
17794
  } catch (err) {
17698
- if (err instanceof yaml4.YAMLException) {
17795
+ if (err instanceof yaml5.YAMLException) {
17699
17796
  throw expected(`Invalid YAML in ${yamlPath}: ${err.message}`);
17700
17797
  }
17701
17798
  throw err;
@@ -17825,9 +17922,9 @@ function parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash) {
17825
17922
  const content = fs12.readFileSync(filePath, "utf-8");
17826
17923
  let raw;
17827
17924
  try {
17828
- raw = yaml4.load(content);
17925
+ raw = yaml5.load(content);
17829
17926
  } catch (err) {
17830
- if (err instanceof yaml4.YAMLException) {
17927
+ if (err instanceof yaml5.YAMLException) {
17831
17928
  throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
17832
17929
  }
17833
17930
  throw err;
@@ -17952,9 +18049,9 @@ function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
17952
18049
  const content = fs12.readFileSync(filePath, "utf-8");
17953
18050
  let raw;
17954
18051
  try {
17955
- raw = yaml4.load(content);
18052
+ raw = yaml5.load(content);
17956
18053
  } catch (err) {
17957
- if (err instanceof yaml4.YAMLException) {
18054
+ if (err instanceof yaml5.YAMLException) {
17958
18055
  throw expected(`Invalid YAML in ${relPath}: ${err.message}`);
17959
18056
  }
17960
18057
  throw err;
@@ -18014,9 +18111,9 @@ function scanAgentYamlFiles(hubFolder) {
18014
18111
  const content = fs12.readFileSync(filePath, "utf-8");
18015
18112
  let agent;
18016
18113
  try {
18017
- agent = yaml4.load(content);
18114
+ agent = yaml5.load(content);
18018
18115
  } catch (err) {
18019
- if (err instanceof yaml4.YAMLException) {
18116
+ if (err instanceof yaml5.YAMLException) {
18020
18117
  throw expected(`Invalid YAML in agents/${file}: ${err.message}`);
18021
18118
  }
18022
18119
  throw err;
@@ -18096,7 +18193,7 @@ var init_diff_display = __esm({
18096
18193
  // src/lib/yaml-writer.ts
18097
18194
  import * as fs13 from "fs";
18098
18195
  import * as path15 from "path";
18099
- import * as yaml5 from "js-yaml";
18196
+ import * as yaml6 from "js-yaml";
18100
18197
  function writeFileIfChanged(hubFolder, absPath, content, log) {
18101
18198
  const buf = Buffer.from(content, "utf-8");
18102
18199
  if (fileHasBytes(absPath, buf)) return;
@@ -18153,7 +18250,7 @@ function writeHubFolder(hubFolder, payload, options = {}) {
18153
18250
  }
18154
18251
  const yamlPayload = buildYamlPayload(payload);
18155
18252
  const agentFiles = extractAgentFiles(payload);
18156
- const yamlContent = yaml5.dump(yamlPayload, YAML_DUMP_OPTIONS);
18253
+ const yamlContent = yaml6.dump(yamlPayload, YAML_DUMP_OPTIONS);
18157
18254
  writeFileIfChanged(hubFolder, path15.join(hubFolder, "hub.yaml"), yamlContent, log);
18158
18255
  if (options.seedAgentContext ?? true) {
18159
18256
  for (const seeded of [
@@ -18200,10 +18297,10 @@ function writeHubFolder(hubFolder, payload, options = {}) {
18200
18297
  function setPreviewLabelInHubYaml(hubFolder, label) {
18201
18298
  const yamlPath = resolveHubYamlPath(hubFolder);
18202
18299
  if (!yamlPath) return;
18203
- const obj = yaml5.load(fs13.readFileSync(yamlPath, "utf-8")) ?? {};
18300
+ const obj = yaml6.load(fs13.readFileSync(yamlPath, "utf-8")) ?? {};
18204
18301
  if (label) obj.preview_label = label;
18205
18302
  else delete obj.preview_label;
18206
- fs13.writeFileSync(yamlPath, yaml5.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
18303
+ fs13.writeFileSync(yamlPath, yaml6.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
18207
18304
  }
18208
18305
  function buildYamlPayload(payload) {
18209
18306
  const result = {
@@ -18248,7 +18345,7 @@ function writeAgentYamlFiles(hubFolder, agentsDir, agents, log) {
18248
18345
  slugs.add(slug);
18249
18346
  const entry = { ...agent };
18250
18347
  delete entry.instructions;
18251
- const yamlContent = yaml5.dump(entry, YAML_DUMP_OPTIONS);
18348
+ const yamlContent = yaml6.dump(entry, YAML_DUMP_OPTIONS);
18252
18349
  writeFileIfChanged(hubFolder, path15.join(agentsDir, `${slug}.yaml`), yamlContent, log);
18253
18350
  }
18254
18351
  return slugs;
@@ -18296,7 +18393,7 @@ function writeEvalYamlFiles(hubFolder, evals, log) {
18296
18393
  fs13.mkdirSync(setDir, { recursive: true });
18297
18394
  }
18298
18395
  }
18299
- const yamlContent = yaml5.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
18396
+ const yamlContent = yaml6.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
18300
18397
  writeFileIfChanged(hubFolder, targetPath, yamlContent, log);
18301
18398
  writtenRelPaths.add(relPath);
18302
18399
  }
@@ -18383,7 +18480,7 @@ function writeJourneyYamlFiles(hubFolder, journeys, log) {
18383
18480
  for (const journeyEntry of journeys) {
18384
18481
  const slug = ensureUniqueSlug(slugify(journeyEntry.name), usedSlugs);
18385
18482
  const fileName = `${slug}.yaml`;
18386
- const yamlContent = yaml5.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
18483
+ const yamlContent = yaml6.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
18387
18484
  writeFileIfChanged(hubFolder, path15.join(journeysDir, fileName), yamlContent, log);
18388
18485
  writtenFiles.add(fileName);
18389
18486
  }
@@ -18501,28 +18598,16 @@ var init_terminal_output = __esm({
18501
18598
  }
18502
18599
  });
18503
18600
 
18504
- // src/lib/base-id.ts
18505
- function isValidBaseId(id) {
18506
- return BASE_ID_RE.test(id);
18507
- }
18508
- var BASE_ID_RE;
18509
- var init_base_id = __esm({
18510
- "src/lib/base-id.ts"() {
18511
- "use strict";
18512
- BASE_ID_RE = /^(?!\.\.?$)[A-Za-z0-9._-]{1,256}$/;
18513
- }
18514
- });
18515
-
18516
18601
  // src/lib/base-workspace.ts
18517
18602
  import * as fs14 from "fs";
18518
18603
  import * as path17 from "path";
18519
- import * as yaml6 from "js-yaml";
18604
+ import * as yaml7 from "js-yaml";
18520
18605
  function readBaseMeta(folder) {
18521
18606
  const bytes = readFileNoFollow(folder, path17.join(folder, BASE_META_FILE));
18522
18607
  if (bytes === null) return null;
18523
18608
  let doc;
18524
18609
  try {
18525
- doc = yaml6.load(bytes.toString("utf-8"));
18610
+ doc = yaml7.load(bytes.toString("utf-8"));
18526
18611
  } catch {
18527
18612
  return null;
18528
18613
  }
@@ -18572,9 +18657,9 @@ function folderForSelector(basesDir, selector) {
18572
18657
  return path17.join(basesDir, selector);
18573
18658
  }
18574
18659
  function assertValidBaseSelector(selector, source = "base") {
18575
- if (!isValidBaseId(selector)) {
18660
+ if (!isPathSafeId(selector)) {
18576
18661
  throw expected(
18577
- `Invalid ${source} "${selector}". Base ids are slugs \u2014 letters, digits, dot, dash and underscore only, with no path separators.`
18662
+ `Invalid ${source} "${selector}". A base selector is one folder name \u2014 ${PATH_SAFE_ID_RULE}.`
18578
18663
  );
18579
18664
  }
18580
18665
  }
@@ -18611,6 +18696,12 @@ function targetBaseId(target, selector) {
18611
18696
  if (selector && !selector.includes("/") && !selector.includes(path17.sep)) return selector;
18612
18697
  return target.exists ? null : path17.basename(target.folder);
18613
18698
  }
18699
+ function resolveBaseSelectorToId(gitRoot, selector) {
18700
+ if (!gitRoot || selector.includes("/") || selector.includes(path17.sep)) return selector;
18701
+ if (!isPathSafeId(selector)) return selector;
18702
+ const id = readBaseMeta(path17.join(resolveBasesDir(gitRoot), selector))?.base_id;
18703
+ return typeof id === "string" && isPathSafeId(id) ? id : selector;
18704
+ }
18614
18705
  function hasBaseMetaFile(folder) {
18615
18706
  try {
18616
18707
  return fs14.lstatSync(path17.join(folder, BASE_META_FILE)).isFile();
@@ -18633,12 +18724,12 @@ var init_base_workspace = __esm({
18633
18724
  // src/lib/subtree-routing.ts
18634
18725
  import * as fs15 from "fs";
18635
18726
  import * as path18 from "path";
18636
- import * as yaml7 from "js-yaml";
18727
+ import * as yaml8 from "js-yaml";
18637
18728
  function readRepoDefaults(gitRoot) {
18638
18729
  const file = path18.join(gitRoot, WAYAI_LAYOUT.wsDir, REPO_DEFAULTS_FILE);
18639
18730
  let doc;
18640
18731
  try {
18641
- doc = yaml7.load(fs15.readFileSync(file, "utf-8"));
18732
+ doc = yaml8.load(fs15.readFileSync(file, "utf-8"));
18642
18733
  } catch {
18643
18734
  return {};
18644
18735
  }
@@ -18683,7 +18774,7 @@ function firstSegment(p) {
18683
18774
  const [head] = normalized.split("/").filter(Boolean);
18684
18775
  return head ?? p;
18685
18776
  }
18686
- function selectorSubtree(layout, selector, cwd) {
18777
+ function resolveSelectorSubtree(layout, selector, cwd) {
18687
18778
  const parts = selector.split(path18.sep).join("/").split("/").filter(Boolean);
18688
18779
  const qualified = parts[0] === WAYAI_LAYOUT.wsDir ? parts.slice(1) : parts;
18689
18780
  if (qualified.length > 1) {
@@ -18749,7 +18840,7 @@ function routeSubtree(options) {
18749
18840
  if (hubFlag !== void 0 && baseFlag !== void 0) {
18750
18841
  return { ok: false, refusal: mixedInvocationRefusal(verb, hubFlag, baseFlag) };
18751
18842
  }
18752
- const fromPositional = positional !== void 0 ? selectorSubtree(layout, positional, cwd) : void 0;
18843
+ const fromPositional = positional !== void 0 ? resolveSelectorSubtree(layout, positional, cwd) : void 0;
18753
18844
  if (fromPositional?.kind === "both") {
18754
18845
  return {
18755
18846
  ok: false,
@@ -18854,12 +18945,12 @@ async function createDataClient(orgId) {
18854
18945
  return orgId2;
18855
18946
  }
18856
18947
  return {
18857
- async request(method, path36, body) {
18858
- const envelope = await api.dataRequest(method, path36, body, org);
18948
+ async request(method, path35, body) {
18949
+ const envelope = await api.dataRequest(method, path35, body, org);
18859
18950
  return envelope.data;
18860
18951
  },
18861
- async requestRaw(method, path36, body) {
18862
- const body_ = await api.dataRequest(method, path36, body, org);
18952
+ async requestRaw(method, path35, body) {
18953
+ const body_ = await api.dataRequest(method, path35, body, org);
18863
18954
  return body_;
18864
18955
  },
18865
18956
  async collectPages(makePath) {
@@ -18933,38 +19024,17 @@ var init_client = __esm({
18933
19024
  }
18934
19025
  });
18935
19026
 
18936
- // src/lib/base-binding.ts
18937
- var binding2, getBaseBindingPath, readBaseBinding, writeBaseBinding, clearBaseBinding, autoBindBaseIfUnbound, assertBaseMatchesBinding, assertNoBindingBlocksBaseCreation;
18938
- var init_base_binding = __esm({
18939
- "src/lib/base-binding.ts"() {
18940
- "use strict";
18941
- init_base_id();
18942
- init_worktree_binding();
18943
- binding2 = createWorktreeBinding({
18944
- filename: "wayai-base-binding",
18945
- isValidId: isValidBaseId,
18946
- noun: "base",
18947
- idLabel: "base id (must be a slug)",
18948
- unbindCommand: "wayai bases unbind",
18949
- useCommand: "wayai bases use"
18950
- });
18951
- getBaseBindingPath = binding2.getBindingPath;
18952
- readBaseBinding = binding2.readBinding;
18953
- writeBaseBinding = binding2.writeBinding;
18954
- clearBaseBinding = binding2.clearBinding;
18955
- autoBindBaseIfUnbound = binding2.autoBindIfUnbound;
18956
- assertBaseMatchesBinding = binding2.assertMatchesBinding;
18957
- assertNoBindingBlocksBaseCreation = binding2.assertNoBindingBlocksCreation;
18958
- }
18959
- });
18960
-
18961
19027
  // src/data/helpers.ts
18962
19028
  import { readFileSync as readFileSync16 } from "fs";
18963
19029
  function pathSegment(id, label = "id") {
19030
+ if (!isPathSafeId(id)) {
19031
+ throw expected(`Invalid ${label}: ${JSON.stringify(id)}. An id may use ${PATH_SAFE_ID_RULE}.`);
19032
+ }
19033
+ return encodeURIComponent(id);
19034
+ }
19035
+ function basePathSegment(id, label = "base id") {
18964
19036
  if (!isValidBaseId(id)) {
18965
- throw expected(
18966
- `Invalid ${label}: ${JSON.stringify(id)}. Ids are slugs \u2014 letters, digits, dot, dash and underscore only.`
18967
- );
19037
+ throw expected(`Invalid ${label}: ${JSON.stringify(id)}. Base ids are slugs \u2014 ${BASE_ID_RULE}.`);
18968
19038
  }
18969
19039
  return encodeURIComponent(id);
18970
19040
  }
@@ -19110,9 +19180,9 @@ var init_types2 = __esm({
19110
19180
  // src/data/config-as-code/config-writer.ts
19111
19181
  import * as fs16 from "fs";
19112
19182
  import * as path19 from "path";
19113
- import * as yaml8 from "js-yaml";
19114
- function dump4(value) {
19115
- return yaml8.dump(value, YAML_DUMP_OPTIONS);
19183
+ import * as yaml9 from "js-yaml";
19184
+ function dump5(value) {
19185
+ return yaml9.dump(value, YAML_DUMP_OPTIONS);
19116
19186
  }
19117
19187
  function omitUndefined(obj) {
19118
19188
  const out = {};
@@ -19170,7 +19240,7 @@ function writeBaseFolder(folder, meta, config) {
19170
19240
  `Refusing to write ${folder}: the path crosses a symlink. Remove it and pull again.`
19171
19241
  );
19172
19242
  }
19173
- writeFileIfChanged(folder, path19.join(folder, BASE_META_FILE), dump4(metaFileObject(meta)), delta);
19243
+ writeFileIfChanged(folder, path19.join(folder, BASE_META_FILE), dump5(metaFileObject(meta)), delta);
19174
19244
  for (const kind of ENTITY_KINDS) {
19175
19245
  const dir = path19.join(folder, ENTITY_DIRS[kind]);
19176
19246
  const entities = config[kind] ?? [];
@@ -19178,13 +19248,13 @@ function writeBaseFolder(folder, meta, config) {
19178
19248
  for (const raw of entities) {
19179
19249
  const entity = raw;
19180
19250
  const id = String(entity.id);
19181
- if (!isValidBaseId(id)) {
19251
+ if (!isPathSafeId(id)) {
19182
19252
  console.warn(` Warning: skipping ${kind} entity with unusable id ${JSON.stringify(id)}.`);
19183
19253
  continue;
19184
19254
  }
19185
19255
  const file = `${id}.yaml`;
19186
19256
  keep.add(file);
19187
- writeFileIfChanged(folder, path19.join(dir, file), dump4(toFileObject(kind, entity)), delta);
19257
+ writeFileIfChanged(folder, path19.join(dir, file), dump5(toFileObject(kind, entity)), delta);
19188
19258
  }
19189
19259
  pruneOrphans(folder, dir, keep, delta);
19190
19260
  }
@@ -19345,7 +19415,7 @@ var init_api = __esm({
19345
19415
  // src/data/config-as-code/config-parser.ts
19346
19416
  import * as fs17 from "fs";
19347
19417
  import * as path20 from "path";
19348
- import * as yaml9 from "js-yaml";
19418
+ import * as yaml10 from "js-yaml";
19349
19419
  function readEntityDir(folder, dir) {
19350
19420
  if (!ensureRealSubdirNoSymlink(folder, dir, false)) {
19351
19421
  throw expected(`Refusing to read ${dir}: it is a symlink, not a config directory.`);
@@ -19361,7 +19431,7 @@ function readEntityDir(folder, dir) {
19361
19431
  }
19362
19432
  let parsed;
19363
19433
  try {
19364
- parsed = yaml9.load(bytes.toString("utf-8"));
19434
+ parsed = yaml10.load(bytes.toString("utf-8"));
19365
19435
  } catch (err) {
19366
19436
  throw expected(`Failed to parse ${abs}: ${err instanceof Error ? err.message : String(err)}`);
19367
19437
  }
@@ -19502,7 +19572,7 @@ function requireGitRoot(gitRoot) {
19502
19572
  return gitRoot;
19503
19573
  }
19504
19574
  async function writeProductionMirror(client, basesDir, prodId, record) {
19505
- if (!isValidBaseId(prodId)) {
19575
+ if (!isPathSafeId(prodId)) {
19506
19576
  throw expected(`Refusing to mirror base ${JSON.stringify(prodId)}: not a usable base id.`);
19507
19577
  }
19508
19578
  const config = await getConfig(client, prodId);
@@ -19543,7 +19613,7 @@ async function pullBase(gitRootOrNull, selector, args2) {
19543
19613
  console.log(`Production base mirrored (read-only) \u2192 ${folder}`);
19544
19614
  return;
19545
19615
  }
19546
- assertBaseMatchesBinding(baseId);
19616
+ assertInScope("bases", baseId);
19547
19617
  if (target.exists && !autoConfirm) {
19548
19618
  const ok = await confirm(
19549
19619
  `Overwrite local files in ${path21.relative(gitRoot, target.folder)} with the server config for "${sanitizeTerminalText(baseId)}"?`
@@ -19562,7 +19632,7 @@ async function pullBase(gitRootOrNull, selector, args2) {
19562
19632
  );
19563
19633
  if (!isFirstPull) printLocalFileChanges(delta);
19564
19634
  console.log(" Credential values are not written. Manage them with `wayai bases credentials`.");
19565
- autoBindBaseIfUnbound(baseId);
19635
+ seedScopeIfEmpty("bases", baseId);
19566
19636
  if (record.origin_base_id) {
19567
19637
  await mirrorLinkedProduction(client, basesDir, record.origin_base_id);
19568
19638
  }
@@ -19587,7 +19657,7 @@ async function createDeclaredPreview(client, target, parsed, autoConfirm) {
19587
19657
  );
19588
19658
  }
19589
19659
  const name = meta.name ?? path21.basename(target.folder);
19590
- assertNoBindingBlocksBaseCreation(name);
19660
+ assertScopeAllowsCreation("bases", name);
19591
19661
  if (!autoConfirm) {
19592
19662
  const ok = await confirm(
19593
19663
  `Create a new preview "${sanitizeTerminalText(name)}" from "${sanitizeTerminalText(meta.origin_base_id)}"?`
@@ -19606,7 +19676,7 @@ async function createDeclaredPreview(client, target, parsed, autoConfirm) {
19606
19676
  writeBaseFolder(target.folder, { ...meta, ...metaFromRecord(record, id) }, config);
19607
19677
  console.log(`Created preview "${sanitizeTerminalText(id)}".`);
19608
19678
  printWarnings((record.warnings ?? []).map((w) => sanitizeTerminalText(w.message)));
19609
- autoBindBaseIfUnbound(id);
19679
+ seedScopeIfEmpty("bases", id);
19610
19680
  return id;
19611
19681
  }
19612
19682
  async function pushBase(gitRootOrNull, selector, args2) {
@@ -19644,13 +19714,13 @@ async function pushBase(gitRootOrNull, selector, args2) {
19644
19714
  `"${sanitizeTerminalText(baseId)}" is a ${sanitizeTerminalText(record.environment)} base. Push operates on previews \u2014 promote with \`wayai bases promote\`.`
19645
19715
  );
19646
19716
  }
19647
- assertBaseMatchesBinding(baseId);
19717
+ assertInScope("bases", baseId);
19648
19718
  }
19649
19719
  const diff = await diffConfig(client, baseId, config);
19650
19720
  printLintWarnings(diff.warnings);
19651
19721
  if (isDiffEmpty(diff)) {
19652
19722
  console.log(`No changes \u2014 preview "${sanitizeTerminalText(baseId)}" already matches your files.`);
19653
- if (!dryRun) autoBindBaseIfUnbound(baseId);
19723
+ if (!dryRun) seedScopeIfEmpty("bases", baseId);
19654
19724
  return;
19655
19725
  }
19656
19726
  console.log(renderDiff(diff));
@@ -19702,7 +19772,7 @@ Delete ${removals.length} server ${word} not in your files? Record types cascade
19702
19772
  }
19703
19773
  console.log(`
19704
19774
  Pushed to preview "${sanitizeTerminalText(baseId)}".`);
19705
- autoBindBaseIfUnbound(baseId);
19775
+ seedScopeIfEmpty("bases", baseId);
19706
19776
  }
19707
19777
  var init_sync = __esm({
19708
19778
  "src/data/config-as-code/sync.ts"() {
@@ -19714,7 +19784,7 @@ var init_sync = __esm({
19714
19784
  init_terminal_output();
19715
19785
  init_base_id();
19716
19786
  init_push();
19717
- init_base_binding();
19787
+ init_worktree_scope();
19718
19788
  init_api();
19719
19789
  init_config_parser();
19720
19790
  init_config_writer();
@@ -19741,7 +19811,7 @@ __export(push_exports, {
19741
19811
  });
19742
19812
  import * as fs18 from "fs";
19743
19813
  import * as path22 from "path";
19744
- import * as yaml10 from "js-yaml";
19814
+ import * as yaml11 from "js-yaml";
19745
19815
  function parseArgs5(args2) {
19746
19816
  let autoConfirm = false;
19747
19817
  let label;
@@ -19855,7 +19925,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
19855
19925
  for (const file of yamlFiles) {
19856
19926
  try {
19857
19927
  const content = fs18.readFileSync(path22.join(agentsDir, file), "utf-8");
19858
- const agent = yaml10.load(content);
19928
+ const agent = yaml11.load(content);
19859
19929
  if (agent?.id && agent.name) {
19860
19930
  agentsWithIds.push({ id: agent.id, name: agent.name });
19861
19931
  }
@@ -19868,7 +19938,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
19868
19938
  const yamlPath = resolveHubYamlPath(hubFolder);
19869
19939
  if (!yamlPath) return;
19870
19940
  const yamlContent = fs18.readFileSync(yamlPath, "utf-8");
19871
- const config = yaml10.load(yamlContent);
19941
+ const config = yaml11.load(yamlContent);
19872
19942
  agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
19873
19943
  }
19874
19944
  if (agentsWithIds.length === 0) return;
@@ -20018,7 +20088,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
20018
20088
  } else {
20019
20089
  console.log("No changes to push; folder renamed.");
20020
20090
  }
20021
- autoBindIfUnbound(hubId);
20091
+ seedScopeIfEmpty("hubs", hubId);
20022
20092
  return true;
20023
20093
  }
20024
20094
  console.log("\nChanges to push:");
@@ -20034,7 +20104,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
20034
20104
  const result = await client.push(hubId, wireConfig, organizationId);
20035
20105
  printSyncResult(result);
20036
20106
  await syncAfterPush(client, hubId, hubFolder, organizationId);
20037
- autoBindIfUnbound(hubId);
20107
+ seedScopeIfEmpty("hubs", hubId);
20038
20108
  return true;
20039
20109
  }
20040
20110
  function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
@@ -20100,7 +20170,7 @@ hub_environment: preview
20100
20170
  ${content}`;
20101
20171
  }
20102
20172
  fs18.writeFileSync(hubYamlPath, updated, "utf-8");
20103
- autoBindIfUnbound(hubId);
20173
+ seedScopeIfEmpty("hubs", hubId);
20104
20174
  await pushSingleHub(client, hubId, newHub.hubFolder, opts.autoConfirm, opts.organizationId, { skipAgentRename: true });
20105
20175
  }
20106
20176
  function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsLabel) {
@@ -20170,11 +20240,11 @@ async function pushCommand(args2) {
20170
20240
  console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
20171
20241
  }
20172
20242
  console.log(`Target hub: ${path22.basename(existing.hubFolder)} (${existing.hubId})`);
20173
- assertHubMatchesBinding(existing.hubId, path22.basename(existing.hubFolder));
20243
+ assertInScope("hubs", existing.hubId, path22.basename(existing.hubFolder));
20174
20244
  await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
20175
20245
  return;
20176
20246
  }
20177
- assertNoBindingBlocksHubCreation(resolution.target.hub.hubName);
20247
+ assertScopeAllowsCreation("hubs", resolution.target.hub.hubName);
20178
20248
  await createAndPushNewHub(client, resolution.target.hub, { autoConfirm, label, organizationId });
20179
20249
  }
20180
20250
  var LOCAL_CHANGE_LIST_CAP;
@@ -20193,7 +20263,7 @@ var init_push = __esm({
20193
20263
  init_layout();
20194
20264
  init_workspace_files();
20195
20265
  init_repo_config();
20196
- init_hub_binding();
20266
+ init_worktree_scope();
20197
20267
  init_subtree_routing();
20198
20268
  LOCAL_CHANGE_LIST_CAP = 20;
20199
20269
  }
@@ -20267,7 +20337,7 @@ async function pullCommand(args2) {
20267
20337
  console.log(`Production hub mirrored (read-only) \u2192 ${folder}`);
20268
20338
  return;
20269
20339
  }
20270
- assertHubMatchesBinding(hubId);
20340
+ assertInScope("hubs", hubId);
20271
20341
  let hubFolder = existingFolder;
20272
20342
  let wroteFiles = false;
20273
20343
  if (!hubFolder) {
@@ -20326,7 +20396,7 @@ async function pullCommand(args2) {
20326
20396
  printLocalFileChanges(delta);
20327
20397
  }
20328
20398
  }
20329
- if (wroteFiles) autoBindIfUnbound(hubId);
20399
+ if (wroteFiles) seedScopeIfEmpty("hubs", hubId);
20330
20400
  if (payload.production_hub_id) {
20331
20401
  await mirrorLinkedProduction2(client, workspaceDir, payload.production_hub_id, organizationId);
20332
20402
  }
@@ -20388,7 +20458,7 @@ var init_pull = __esm({
20388
20458
  init_layout();
20389
20459
  init_workspace_files();
20390
20460
  init_repo_config();
20391
- init_hub_binding();
20461
+ init_worktree_scope();
20392
20462
  init_subtree_routing();
20393
20463
  MIRROR_MARKER_PREFIX2 = "# Read-only mirror of production hub";
20394
20464
  }
@@ -20454,7 +20524,7 @@ async function createCommand(args2) {
20454
20524
  }
20455
20525
  process.exit(1);
20456
20526
  }
20457
- assertNoBindingBlocksHubCreation(resolution.hub.hubName);
20527
+ assertScopeAllowsCreation("hubs", resolution.hub.hubName);
20458
20528
  await createAndPushNewHub(client, resolution.hub, { autoConfirm, label, organizationId });
20459
20529
  }
20460
20530
  var init_create = __esm({
@@ -20466,7 +20536,7 @@ var init_create = __esm({
20466
20536
  init_workspace();
20467
20537
  init_layout();
20468
20538
  init_repo_config();
20469
- init_hub_binding();
20539
+ init_worktree_scope();
20470
20540
  }
20471
20541
  });
20472
20542
 
@@ -20619,7 +20689,7 @@ async function replicateCommand(args2) {
20619
20689
  payload.preview_label,
20620
20690
  payload.branch_name
20621
20691
  );
20622
- autoBindIfUnbound(previewHubId);
20692
+ seedScopeIfEmpty("hubs", previewHubId);
20623
20693
  if (folderPreExisted) printLocalFileChanges(delta);
20624
20694
  console.log(`Preview written to ${path25.basename(hubFolder)}`);
20625
20695
  console.log(` Switch to it with: wayai use ${path25.basename(hubFolder)}`);
@@ -20635,7 +20705,7 @@ var init_replicate = __esm({
20635
20705
  init_workspace();
20636
20706
  init_layout();
20637
20707
  init_repo_config();
20638
- init_hub_binding();
20708
+ init_worktree_scope();
20639
20709
  init_contracts();
20640
20710
  }
20641
20711
  });
@@ -20698,7 +20768,7 @@ async function relabelCommand(args2) {
20698
20768
  console.log("This is a read-only production mirror \u2014 production hubs have no preview label. Relabel the linked preview hub instead.");
20699
20769
  return;
20700
20770
  }
20701
- assertHubMatchesBinding(target.hubId, path26.basename(target.hubFolder));
20771
+ assertInScope("hubs", target.hubId, path26.basename(target.hubFolder));
20702
20772
  console.log(normalizedLabel ? `Setting preview label to "${normalizedLabel}"...` : "Clearing preview label...");
20703
20773
  const { data } = await client.relabelPreview(target.hubId, normalizedLabel);
20704
20774
  const row = data[0];
@@ -20727,7 +20797,7 @@ var init_relabel = __esm({
20727
20797
  init_workspace();
20728
20798
  init_layout();
20729
20799
  init_repo_config();
20730
- init_hub_binding();
20800
+ init_worktree_scope();
20731
20801
  init_contracts();
20732
20802
  }
20733
20803
  });
@@ -20854,7 +20924,7 @@ async function publishCommand(args2) {
20854
20924
  return;
20855
20925
  }
20856
20926
  }
20857
- assertHubMatchesBinding(hubId, hubFolder ? path27.basename(hubFolder) : void 0);
20927
+ assertInScope("hubs", hubId, hubFolder ? path27.basename(hubFolder) : void 0);
20858
20928
  await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
20859
20929
  }
20860
20930
  var STATUS_STYLE;
@@ -20870,7 +20940,7 @@ var init_publish = __esm({
20870
20940
  init_layout();
20871
20941
  init_resource_files();
20872
20942
  init_repo_config();
20873
- init_hub_binding();
20943
+ init_worktree_scope();
20874
20944
  STATUS_STYLE = {
20875
20945
  added: { sym: "+", color: 32 },
20876
20946
  // green
@@ -20882,77 +20952,151 @@ var init_publish = __esm({
20882
20952
  }
20883
20953
  });
20884
20954
 
20955
+ // src/lib/scope-selector.ts
20956
+ import * as path28 from "path";
20957
+ function findAddFlag(args2) {
20958
+ return args2.find((arg) => arg === "--add" || arg.startsWith("--add="));
20959
+ }
20960
+ function isValuedFlag(token) {
20961
+ return token.includes("=");
20962
+ }
20963
+ function resolveScopeSelector(verb, args2) {
20964
+ const { hubFlag, baseFlag, positional } = parseRoutingTokens(args2);
20965
+ if (hubFlag !== void 0 && baseFlag !== void 0) {
20966
+ throw expected(mixedInvocationRefusal(verb, hubFlag, baseFlag));
20967
+ }
20968
+ assertValueIsNotAFlag(verb, "--hub", hubFlag);
20969
+ assertValueIsNotAFlag(verb, "--base", baseFlag);
20970
+ assertFlagWasGivenAValue(verb, "--hub", args2, hubFlag);
20971
+ assertFlagWasGivenAValue(verb, "--base", args2, baseFlag);
20972
+ if (hubFlag !== void 0) return { axis: "hubs", target: hubFlag, gitRoot: findGitRoot() };
20973
+ if (baseFlag !== void 0) return { axis: "bases", target: baseFlag, gitRoot: findGitRoot() };
20974
+ if (positional === void 0) return null;
20975
+ const gitRoot = findGitRoot();
20976
+ const layout = gitRoot ? resolveLayout(gitRoot) : null;
20977
+ const resolved = resolveSelectorSubtree(layout, positional, process.cwd());
20978
+ if (resolved.kind === "both") {
20979
+ throw expected(mixedInvocationRefusal(verb, resolved.target, resolved.target));
20980
+ }
20981
+ if (resolved.kind === "hubs" || resolved.kind === "bases") {
20982
+ return { axis: resolved.kind, target: resolved.target, gitRoot };
20983
+ }
20984
+ return { axis: "hubs", target: positional, gitRoot };
20985
+ }
20986
+ function assertValueIsNotAFlag(verb, flag, captured) {
20987
+ if (captured === void 0 || !captured.startsWith("-")) return;
20988
+ throw refusal(verb, flag, `but got ${JSON.stringify(captured)} \u2014 which is a flag, not a hub or base`);
20989
+ }
20990
+ function assertFlagWasGivenAValue(verb, flag, args2, captured) {
20991
+ if (captured !== void 0 && captured !== "") return;
20992
+ if (!args2.some((arg) => arg === flag || arg.startsWith(`${flag}=`))) return;
20993
+ throw refusal(verb, flag, "but no target followed it");
20994
+ }
20995
+ function refusal(verb, flag, problem) {
20996
+ return expected(
20997
+ `\`wayai ${verb} ${flag}\` needs a target, ${problem}.
20998
+ wayai ${verb} ${flag} <hub-or-base>`
20999
+ );
21000
+ }
21001
+ function resolveHubSelectorToId(gitRoot, selector) {
21002
+ if (UUID_RE2.test(selector)) return selector;
21003
+ const workspaceDir = detectWorkspace()?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
21004
+ if (!workspaceDir) {
21005
+ throw expected("Not inside a git repository, and no UUID provided.");
21006
+ }
21007
+ const match = findHubByFolderName(workspaceDir, selector);
21008
+ if (match) return match.hubId;
21009
+ const newHubs = scanNewHubs(workspaceDir);
21010
+ const pending = newHubs.find((h) => path28.basename(h.hubFolder) === selector);
21011
+ if (pending) {
21012
+ throw expected(
21013
+ `"${selector}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
21014
+ Create it first \u2014 the worktree scope picks it up automatically afterward:
21015
+ wayai create ${selector}`
21016
+ );
21017
+ }
21018
+ throw expected(
21019
+ [
21020
+ `No hub matching "${selector}" found in ${hubsDirLabel(gitRoot)}/. Pass a UUID or a folder name from:`,
21021
+ ...scanWorkspaceHubs(workspaceDir).map((h) => ` ${path28.basename(h.hubFolder)} (${h.hubId})`),
21022
+ ...newHubs.map(
21023
+ (h) => ` ${path28.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path28.basename(h.hubFolder)}\`)`
21024
+ )
21025
+ ].join("\n")
21026
+ );
21027
+ }
21028
+ function resolveScopeTarget(selector) {
21029
+ const { axis, target, gitRoot } = selector;
21030
+ if (axis === "hubs") {
21031
+ return { axis, id: resolveHubSelectorToId(gitRoot, target) };
21032
+ }
21033
+ const id = resolveBaseSelectorToId(gitRoot, target);
21034
+ assertValidBaseSelector(id, "base id");
21035
+ return { axis, id };
21036
+ }
21037
+ var init_scope_selector = __esm({
21038
+ "src/lib/scope-selector.ts"() {
21039
+ "use strict";
21040
+ init_expected();
21041
+ init_base_workspace();
21042
+ init_layout();
21043
+ init_subtree_routing();
21044
+ init_utils();
21045
+ init_workspace();
21046
+ }
21047
+ });
21048
+
20885
21049
  // src/commands/use.ts
20886
21050
  var use_exports = {};
20887
21051
  __export(use_exports, {
20888
21052
  useCommand: () => useCommand
20889
21053
  });
20890
- import * as path28 from "path";
20891
21054
  async function useCommand(args2) {
20892
- const target = args2[0];
20893
- if (!target || target.startsWith("-")) {
20894
- console.error("Usage: wayai use <hub_uuid|folder-name>");
20895
- process.exit(1);
20896
- }
20897
- let hubId;
20898
- if (UUID_RE2.test(target)) {
20899
- hubId = target;
20900
- } else {
20901
- const workspace = detectWorkspace();
20902
- const gitRoot = findGitRoot();
20903
- const workspaceDir = workspace?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
20904
- if (!workspaceDir) {
20905
- console.error("Not inside a git repository, and no UUID provided.");
20906
- process.exit(1);
20907
- }
20908
- const wsLabel = hubsDirLabel(gitRoot);
20909
- const match = findHubByFolderName(workspaceDir, target);
20910
- if (!match) {
20911
- const newHubs = scanNewHubs(workspaceDir);
20912
- const pending = newHubs.find((h) => path28.basename(h.hubFolder) === target);
20913
- if (pending) {
20914
- console.error(
20915
- `"${target}" is a new hub that hasn't been created on the platform yet, so it has no id to bind to.
20916
- Create it first \u2014 the worktree binds automatically afterward:
20917
- wayai create ${target}`
20918
- );
20919
- process.exit(1);
20920
- }
20921
- console.error(`No hub matching "${target}" found in ${wsLabel}/. Pass a UUID or a folder name from:`);
20922
- for (const h of scanWorkspaceHubs(workspaceDir)) {
20923
- console.error(` ${path28.basename(h.hubFolder)} (${h.hubId})`);
20924
- }
20925
- for (const h of newHubs) {
20926
- console.error(` ${path28.basename(h.hubFolder)} (new \u2014 "${h.hubName}", run \`wayai create ${path28.basename(h.hubFolder)}\`)`);
20927
- }
20928
- process.exit(1);
20929
- }
20930
- hubId = match.hubId;
21055
+ const addFlag = findAddFlag(args2);
21056
+ if (addFlag !== void 0 && isValuedFlag(addFlag)) {
21057
+ throw expected(
21058
+ [
21059
+ `\`--add\` is a boolean flag \u2014 write it bare, not ${JSON.stringify(addFlag)}.`,
21060
+ " wayai use --add <selector> # widen the worktree scope",
21061
+ " wayai use <selector> # replace that axis instead"
21062
+ ].join("\n")
21063
+ );
20931
21064
  }
20932
- const previous = readBinding();
20933
- if (previous === hubId) {
20934
- console.log(`Worktree already bound to ${hubId}.`);
21065
+ const add = addFlag !== void 0;
21066
+ const selector = resolveScopeSelector("use", args2);
21067
+ if (!selector) throw expected(USAGE);
21068
+ const { axis, id } = resolveScopeTarget(selector);
21069
+ const noun = axisNoun(axis);
21070
+ if (add) {
21071
+ const widened = addToScope(axis, id);
21072
+ console.log(
21073
+ widened ? `Worktree scope widened \u2014 now ${describeAxis(axis, widened)}.` : `Worktree scope already includes ${noun} ${id}.`
21074
+ );
20935
21075
  return;
20936
21076
  }
20937
- try {
20938
- writeBinding(hubId);
20939
- } catch (err) {
20940
- console.error(err instanceof Error ? err.message : String(err));
20941
- process.exit(1);
20942
- }
20943
- if (previous) {
20944
- console.log(`Worktree rebound: ${previous} \u2192 ${hubId}`);
20945
- } else {
20946
- console.log(`Worktree bound to hub ${hubId}.`);
21077
+ const previous = readScope()[axis];
21078
+ if (previous.length === 1 && previous[0] === id) {
21079
+ console.log(`Worktree already bound to ${noun} ${id}.`);
21080
+ return;
20947
21081
  }
21082
+ setScopeAxis(axis, [id]);
21083
+ console.log(
21084
+ previous.length > 0 ? `Worktree rebound: ${previous.join(", ")} \u2192 ${id}.` : `Worktree bound to ${noun} ${id}.`
21085
+ );
20948
21086
  }
21087
+ var USAGE;
20949
21088
  var init_use = __esm({
20950
21089
  "src/commands/use.ts"() {
20951
21090
  "use strict";
20952
- init_hub_binding();
20953
- init_workspace();
20954
- init_layout();
20955
- init_utils();
21091
+ init_expected();
21092
+ init_scope_selector();
21093
+ init_worktree_scope();
21094
+ USAGE = [
21095
+ "Usage: wayai use [--add] <hub_uuid|folder-name|hubs/<name>|bases/<name>>",
21096
+ " wayai use [--add] --hub <hub> | --base <base>",
21097
+ "",
21098
+ " --add widen the worktree scope instead of replacing that axis"
21099
+ ].join("\n");
20956
21100
  }
20957
21101
  });
20958
21102
 
@@ -21054,19 +21198,71 @@ var unbind_exports = {};
21054
21198
  __export(unbind_exports, {
21055
21199
  unbindCommand: () => unbindCommand
21056
21200
  });
21057
- async function unbindCommand(_args) {
21058
- const previous = readBinding();
21059
- if (!previous) {
21060
- console.log("No worktree binding to clear.");
21201
+ async function unbindCommand(args2) {
21202
+ const addFlag = findAddFlag(args2);
21203
+ if (addFlag !== void 0) {
21204
+ throw expected(
21205
+ [
21206
+ `\`wayai unbind\` does not take ${JSON.stringify(addFlag)}.`,
21207
+ " wayai unbind <selector> # remove one entry from the worktree scope",
21208
+ " wayai unbind # clear the whole scope (both hubs and bases)",
21209
+ " wayai use --add <selector> # WIDEN the scope instead (the opposite change)"
21210
+ ].join("\n")
21211
+ );
21212
+ }
21213
+ const selector = resolveScopeSelector("unbind", args2);
21214
+ if (!selector) {
21215
+ if (args2.length > 0) {
21216
+ throw expected(
21217
+ [
21218
+ `\`wayai unbind ${args2.join(" ")}\` does not name anything to remove.`,
21219
+ "",
21220
+ "Refusing rather than clearing the whole scope \u2014 that is what a BARE",
21221
+ "`wayai unbind` does, and it is not what this invocation asked for.",
21222
+ " wayai unbind <selector> # remove one entry",
21223
+ " wayai unbind # clear the whole scope (both hubs and bases)"
21224
+ ].join("\n")
21225
+ );
21226
+ }
21227
+ const described = describeScope(readScope());
21228
+ clearScope();
21229
+ console.log(
21230
+ described ? `Worktree scope cleared (was ${described}).` : "No worktree scope to clear."
21231
+ );
21061
21232
  return;
21062
21233
  }
21063
- clearBinding();
21064
- console.log(`Worktree unbound (was: ${previous}).`);
21234
+ const scope = readScope();
21235
+ const literal = AXIS_ORDER.filter((axis2) => scope[axis2].includes(selector.target));
21236
+ const literalAxis = literal.includes(selector.axis) ? selector.axis : literal.length === 1 ? literal[0] : null;
21237
+ if (literalAxis) {
21238
+ announce(literalAxis, selector.target, removeFromScope(literalAxis, selector.target) ?? []);
21239
+ return;
21240
+ }
21241
+ const { axis, id } = resolveScopeTarget(selector);
21242
+ const remaining = removeFromScope(axis, id);
21243
+ if (!remaining) {
21244
+ const described = describeScope(scope);
21245
+ throw expected(
21246
+ [
21247
+ `${axisNoun(axis)} ${id} is not in this worktree's scope.`,
21248
+ described ? `This worktree is scoped to ${described}.` : "This worktree has no scope."
21249
+ ].join("\n")
21250
+ );
21251
+ }
21252
+ announce(axis, id, remaining);
21253
+ }
21254
+ function announce(axis, id, remaining) {
21255
+ const noun = axisNoun(axis);
21256
+ console.log(
21257
+ remaining.length > 0 ? `Removed ${noun} ${id} from the worktree scope (${axis}: ${remaining.join(", ")}).` : `Removed ${noun} ${id} \u2014 this worktree is no longer scoped to any ${noun}.`
21258
+ );
21065
21259
  }
21066
21260
  var init_unbind = __esm({
21067
21261
  "src/commands/unbind.ts"() {
21068
21262
  "use strict";
21069
- init_hub_binding();
21263
+ init_expected();
21264
+ init_scope_selector();
21265
+ init_worktree_scope();
21070
21266
  }
21071
21267
  });
21072
21268
 
@@ -23010,7 +23206,7 @@ __export(eval_capture_exports, {
23010
23206
  });
23011
23207
  import * as fs24 from "fs";
23012
23208
  import * as path31 from "path";
23013
- import * as yaml11 from "js-yaml";
23209
+ import * as yaml12 from "js-yaml";
23014
23210
  function isValidSetName(name) {
23015
23211
  if (name.length === 0 || name === "." || name === "..") return false;
23016
23212
  if (name.startsWith(".")) return false;
@@ -23116,7 +23312,7 @@ async function evalCaptureCommand(args2) {
23116
23312
  };
23117
23313
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
23118
23314
  fs24.mkdirSync(targetDir, { recursive: true });
23119
- fs24.writeFileSync(targetPath, yaml11.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
23315
+ fs24.writeFileSync(targetPath, yaml12.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
23120
23316
  const relPath = path31.relative(process.cwd(), targetPath);
23121
23317
  console.log(`
23122
23318
  Wrote ${relPath}`);
@@ -24489,7 +24685,7 @@ var init_set_connection_credential = __esm({
24489
24685
  // src/lib/org-workspace.ts
24490
24686
  import * as fs25 from "fs";
24491
24687
  import * as path32 from "path";
24492
- import * as yaml12 from "js-yaml";
24688
+ import * as yaml13 from "js-yaml";
24493
24689
  function getOrgDir(gitRoot) {
24494
24690
  return resolveLayout(gitRoot).orgDir;
24495
24691
  }
@@ -24500,7 +24696,7 @@ function parseOrgResources(orgDir) {
24500
24696
  const manifestPath = path32.join(orgDir, ORG_MANIFEST_NAME);
24501
24697
  let manifest = {};
24502
24698
  if (fs25.existsSync(manifestPath)) {
24503
- manifest = yaml12.load(fs25.readFileSync(manifestPath, "utf-8")) ?? {};
24699
+ manifest = yaml13.load(fs25.readFileSync(manifestPath, "utf-8")) ?? {};
24504
24700
  }
24505
24701
  const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
24506
24702
  const resourcesDir = path32.join(orgDir, "resources");
@@ -24533,7 +24729,7 @@ function writeOrgResources(orgDir, payload) {
24533
24729
  });
24534
24730
  fs25.writeFileSync(
24535
24731
  path32.join(orgDir, ORG_MANIFEST_NAME),
24536
- yaml12.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
24732
+ yaml13.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
24537
24733
  "utf-8"
24538
24734
  );
24539
24735
  const resourcesDir = path32.join(orgDir, "resources");
@@ -25355,7 +25551,7 @@ async function runSkillInstall(positional) {
25355
25551
  console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
25356
25552
  }
25357
25553
  async function runSandboxExec(positional, flagArgs) {
25358
- const USAGE = 'wayai admin sandbox exec [--cmd "<command>" | <command>] --hub <hub_id> --connection <connection_id> [--egress open|allowlist|isolated] [--allow <host> ...] [--timeout <ms>] [--json]';
25554
+ const USAGE2 = 'wayai admin sandbox exec [--cmd "<command>" | <command>] --hub <hub_id> --connection <connection_id> [--egress open|allowlist|isolated] [--allow <host> ...] [--timeout <ms>] [--json]';
25359
25555
  let hubId;
25360
25556
  let connectionId;
25361
25557
  let egress;
@@ -25409,7 +25605,7 @@ async function runSandboxExec(positional, flagArgs) {
25409
25605
  const positionalCmd = positional[0];
25410
25606
  if (positional.length > 1) {
25411
25607
  console.error("Provide the command as a single quoted argument (or use --cmd).");
25412
- console.error(USAGE);
25608
+ console.error(USAGE2);
25413
25609
  process.exit(1);
25414
25610
  }
25415
25611
  if (positionalCmd && cmdFlag) {
@@ -25418,7 +25614,7 @@ async function runSandboxExec(positional, flagArgs) {
25418
25614
  }
25419
25615
  const cmd = cmdFlag ?? positionalCmd;
25420
25616
  if (!cmd || !hubId || !connectionId) {
25421
- console.error(USAGE);
25617
+ console.error(USAGE2);
25422
25618
  process.exit(1);
25423
25619
  }
25424
25620
  const body = {
@@ -25515,8 +25711,8 @@ function parseScopedTargetFlags(flagArgs, usage3, opts = {}) {
25515
25711
  return { body: { scope: "hub", hub_id: hubId, dry_run: dryRun }, targetLabel: `hub ${hubId}`, dryRun, yes, jsonOutput };
25516
25712
  }
25517
25713
  async function runRepairTeamOrphans(flagArgs) {
25518
- const USAGE = "wayai admin hubs repair-team-orphans --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25519
- const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE);
25714
+ const USAGE2 = "wayai admin hubs repair-team-orphans --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25715
+ const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE2);
25520
25716
  if (!dryRun && !yes && !jsonOutput) {
25521
25717
  console.log(`About to PURGE dangling team references in: ${targetLabel}.`);
25522
25718
  console.log("Memberships pointing at a deleted team are removed (HubDO + UserDO mirror); conversations pinned to one are unassigned.");
@@ -25562,8 +25758,8 @@ async function runRepairTeamOrphans(flagArgs) {
25562
25758
  }
25563
25759
  }
25564
25760
  async function runRepairLegacyOrgGrants(flagArgs) {
25565
- const USAGE = "wayai admin orgs repair-legacy-grants --all | --org <org_id> [--dry-run] [--yes] [--json]";
25566
- const scoped = parseScopedTargetFlags(flagArgs, USAGE, {
25761
+ const USAGE2 = "wayai admin orgs repair-legacy-grants --all | --org <org_id> [--dry-run] [--yes] [--json]";
25762
+ const scoped = parseScopedTargetFlags(flagArgs, USAGE2, {
25567
25763
  scopes: ["all", "org"],
25568
25764
  allLabel: "ALL organizations (platform-wide)"
25569
25765
  });
@@ -25615,8 +25811,8 @@ async function runRepairLegacyOrgGrants(flagArgs) {
25615
25811
  }
25616
25812
  }
25617
25813
  async function runHarnessMassDestroy(flagArgs) {
25618
- const USAGE = "wayai admin harness mass-destroy --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25619
- const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE);
25814
+ const USAGE2 = "wayai admin harness mass-destroy --all | --org <org_id> | --hub <hub_id> [--dry-run] [--yes] [--json]";
25815
+ const { body, targetLabel, dryRun, yes, jsonOutput } = parseScopedTargetFlags(flagArgs, USAGE2);
25620
25816
  if (!dryRun && !yes && !jsonOutput) {
25621
25817
  console.log(`About to REAP every live harness sandbox / token / slot in: ${targetLabel}.`);
25622
25818
  console.log("This destroys running sandboxes and revokes their tokens. Run with --dry-run first to see the blast radius.");
@@ -25670,8 +25866,8 @@ function rejectHarnessFlags(flagArgs, usage3) {
25670
25866
  process.exit(1);
25671
25867
  }
25672
25868
  async function runHarnessToggle(enable, flagArgs) {
25673
- const USAGE = "wayai admin harness enable|disable";
25674
- rejectHarnessFlags(flagArgs, USAGE);
25869
+ const USAGE2 = "wayai admin harness enable|disable";
25870
+ rejectHarnessFlags(flagArgs, USAGE2);
25675
25871
  const { config, accessToken } = await requireAuth();
25676
25872
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
25677
25873
  let value;
@@ -25692,8 +25888,8 @@ async function runHarnessToggle(enable, flagArgs) {
25692
25888
  }
25693
25889
  }
25694
25890
  async function runHarnessStatus(flagArgs) {
25695
- const USAGE = "wayai admin harness status";
25696
- rejectHarnessFlags(flagArgs, USAGE);
25891
+ const USAGE2 = "wayai admin harness status";
25892
+ rejectHarnessFlags(flagArgs, USAGE2);
25697
25893
  const { config, accessToken } = await requireAuth();
25698
25894
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
25699
25895
  let value = 0;
@@ -27932,16 +28128,16 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
27932
28128
  ).option("--force", "Revoke even if the token appears to be in live use").action(async function(tokenId, opts) {
27933
28129
  const client = await createDataClient();
27934
28130
  const id = pathSegment(tokenId, "token id");
27935
- const path36 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
28131
+ const path35 = (force) => `/v1/tokens/${id}${force ? "?force=true" : ""}`;
27936
28132
  const format = outputFormat(this);
27937
28133
  const revoked = (forced) => printOutput({ token_id: tokenId, revoked: true, forced }, format);
27938
28134
  if (opts.force) {
27939
- await client.request("DELETE", path36(true));
28135
+ await client.request("DELETE", path35(true));
27940
28136
  revoked(true);
27941
28137
  return;
27942
28138
  }
27943
28139
  try {
27944
- await client.request("DELETE", path36(false));
28140
+ await client.request("DELETE", path35(false));
27945
28141
  revoked(false);
27946
28142
  } catch (err) {
27947
28143
  if (!(err instanceof ApiError) || err.status !== 409 || dataErrorDetails(err)?.requires_force !== true) {
@@ -27956,7 +28152,7 @@ MCP URL: ${toolsetMcpUrl(slug)}`);
27956
28152
  console.error("Aborted.");
27957
28153
  return process.exit(1);
27958
28154
  }
27959
- await client.request("DELETE", path36(true));
28155
+ await client.request("DELETE", path35(true));
27960
28156
  revoked(true);
27961
28157
  }
27962
28158
  });
@@ -28104,10 +28300,9 @@ var init_base_tags = __esm({
28104
28300
 
28105
28301
  // src/data/commands/bases.ts
28106
28302
  import { Command as Command10 } from "commander";
28107
- import * as path35 from "path";
28108
- function pageOf(path36, cursor) {
28109
- if (!cursor) return path36;
28110
- return `${path36}${path36.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
28303
+ function pageOf(path35, cursor) {
28304
+ if (!cursor) return path35;
28305
+ return `${path35}${path35.includes("?") ? "&" : "?"}cursor=${encodeURIComponent(cursor)}`;
28111
28306
  }
28112
28307
  function parseEnum(flag, value, allowed) {
28113
28308
  if (value === void 0) return void 0;
@@ -28141,12 +28336,13 @@ function buildBasesCommand() {
28141
28336
  "production",
28142
28337
  "preview"
28143
28338
  ]);
28339
+ const target = basePathSegment(id);
28144
28340
  const client = await createDataClient();
28145
28341
  const body = { name: opts.name, description: opts.description };
28146
28342
  if (environment) body.environment = environment;
28147
28343
  if (opts.tags) body.tags = await resolveBaseTags(opts.tags, client);
28148
28344
  if (opts.timezone) body.settings = { timezone: opts.timezone };
28149
- printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
28345
+ printOutput(await client.request("PUT", `/v1/bases/${target}`, body), outputFormat(this));
28150
28346
  });
28151
28347
  bases.command("update <id>").description(
28152
28348
  "Update a base's mutable config (display name, description, tags, default timezone/settings). The id/slug is immutable."
@@ -28169,6 +28365,7 @@ function buildBasesCommand() {
28169
28365
  "Nothing to update. Provide at least one of --name, --description, --tags, --timezone, --settings, --integrations."
28170
28366
  );
28171
28367
  }
28368
+ const target = basePathSegment(id);
28172
28369
  const client = await createDataClient();
28173
28370
  const body = {};
28174
28371
  if (opts.name !== void 0) body.name = opts.name;
@@ -28189,20 +28386,21 @@ function buildBasesCommand() {
28189
28386
  }
28190
28387
  settings = parsed;
28191
28388
  } else {
28192
- const existing = await client.request("GET", `/v1/bases/${pathSegment(id)}`);
28389
+ const existing = await client.request("GET", `/v1/bases/${target}`);
28193
28390
  settings = { ...existing?.settings ?? {} };
28194
28391
  }
28195
28392
  if (opts.timezone !== void 0) settings.timezone = opts.timezone;
28196
28393
  body.settings = settings;
28197
28394
  }
28198
- printOutput(await client.request("PUT", `/v1/bases/${pathSegment(id)}`, body), outputFormat(this));
28395
+ printOutput(await client.request("PUT", `/v1/bases/${target}`, body), outputFormat(this));
28199
28396
  });
28200
28397
  bases.command("rename <id>").description(
28201
28398
  "Rename a base's display name (the id/slug is immutable \u2014 it scopes token grants, MCP endpoints, and references)"
28202
28399
  ).requiredOption("--name <name>", "New display name").action(async function(id, opts) {
28400
+ const target = basePathSegment(id);
28203
28401
  const client = await createDataClient();
28204
28402
  printOutput(
28205
- await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { name: opts.name }),
28403
+ await client.request("PUT", `/v1/bases/${target}`, { name: opts.name }),
28206
28404
  outputFormat(this)
28207
28405
  );
28208
28406
  });
@@ -28210,10 +28408,11 @@ function buildBasesCommand() {
28210
28408
  "--tags <tags>",
28211
28409
  "Comma-separated organization tag names (e.g. clinica-medica,billing). An empty value clears them"
28212
28410
  ).action(async function(id, opts) {
28411
+ const target = basePathSegment(id);
28213
28412
  const client = await createDataClient();
28214
28413
  const tags = await resolveBaseTags(opts.tags, client);
28215
28414
  printOutput(
28216
- await client.request("PUT", `/v1/bases/${pathSegment(id)}`, { tags }),
28415
+ await client.request("PUT", `/v1/bases/${target}`, { tags }),
28217
28416
  outputFormat(this)
28218
28417
  );
28219
28418
  });
@@ -28244,6 +28443,11 @@ function buildBasesCommand() {
28244
28443
  const integrations = parseEnum("--integrations", opts.integrations, ["enabled", "disabled"]);
28245
28444
  const client = await createDataClient();
28246
28445
  printOutput(
28446
+ // `pathSegment`, not `basePathSegment`: the id at fault here is the
28447
+ // ORIGIN's, which is immutable and not the caller's to re-spell. The API's
28448
+ // `assertDerivablePreviewId` answers that case with the remedy instead of
28449
+ // the charset, and refusing locally would replace it with a rule the
28450
+ // caller cannot act on.
28247
28451
  await client.request("POST", `/v1/${pathSegment(originId, "origin base id")}/preview`, {
28248
28452
  name: opts.name,
28249
28453
  description: opts.description,
@@ -28255,9 +28459,9 @@ function buildBasesCommand() {
28255
28459
  });
28256
28460
  bases.command("list-previews <origin-id>").description("List preview bases cloned from a base (production or preview)").action(async function(originId) {
28257
28461
  const client = await createDataClient();
28258
- const path36 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
28462
+ const path35 = `/v1/${pathSegment(originId, "origin base id")}/previews`;
28259
28463
  printOutput(
28260
- await client.collectPages((cursor) => pageOf(path36, cursor)),
28464
+ await client.collectPages((cursor) => pageOf(path35, cursor)),
28261
28465
  outputFormat(this)
28262
28466
  );
28263
28467
  });
@@ -28304,45 +28508,52 @@ function buildBasesCommand() {
28304
28508
  });
28305
28509
  bases.command("promotions <production-id>").description("List promotion history for a production base").action(async function(productionId) {
28306
28510
  const client = await createDataClient();
28307
- const path36 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
28511
+ const path35 = `/v1/${pathSegment(productionId, "production base id")}/promotions`;
28308
28512
  printOutput(
28309
- await client.collectPages((cursor) => pageOf(path36, cursor)),
28513
+ await client.collectPages((cursor) => pageOf(path35, cursor)),
28310
28514
  outputFormat(this)
28311
28515
  );
28312
28516
  });
28313
28517
  bases.command("use <base>").description(
28314
- "Bind this worktree to a base so push/pull refuse to run against a different one"
28518
+ "Deprecated alias of `wayai use bases/<id>` \u2014 scope this worktree to a base"
28315
28519
  ).action(async function(selector) {
28316
- const baseId = resolveSelectorToBaseId(selector);
28317
- const previous = readBaseBinding();
28318
- if (previous !== baseId) {
28520
+ const baseId = resolveBaseSelectorToId(findGitRoot(), selector);
28521
+ const previous = readScope().bases;
28522
+ const changed = previous.length !== 1 || previous[0] !== baseId;
28523
+ if (changed) {
28319
28524
  try {
28320
- writeBaseBinding(baseId);
28525
+ setScopeAxis("bases", [baseId]);
28321
28526
  } catch (err) {
28322
28527
  throw expected(err instanceof Error ? err.message : String(err));
28323
28528
  }
28324
28529
  }
28325
28530
  if (outputFormat(this) === "json") {
28326
- printOutput({ base_id: baseId, previous_base_id: previous, changed: previous !== baseId }, "json");
28531
+ printOutput(
28532
+ { base_id: baseId, previous_base_id: previous[0] ?? null, changed },
28533
+ "json"
28534
+ );
28327
28535
  return;
28328
28536
  }
28329
- if (previous === baseId) {
28537
+ if (!changed) {
28330
28538
  console.log(`Worktree already bound to base ${baseId}.`);
28331
28539
  } else {
28332
28540
  console.log(
28333
- previous ? `Worktree rebound: ${previous} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
28541
+ previous.length > 0 ? `Worktree rebound: ${previous.join(", ")} -> ${baseId}.` : `Worktree bound to base ${baseId}.`
28334
28542
  );
28335
28543
  }
28336
28544
  });
28337
- bases.command("unbind").description("Clear the base binding for this worktree").action(async function() {
28338
- const previous = readBaseBinding();
28339
- if (previous) clearBaseBinding();
28545
+ bases.command("unbind").description("Deprecated alias of `wayai unbind bases/<id>` \u2014 clear the base scope").action(async function() {
28546
+ const previous = readScope().bases;
28547
+ if (previous.length > 0) setScopeAxis("bases", []);
28340
28548
  if (outputFormat(this) === "json") {
28341
- printOutput({ previous_base_id: previous, cleared: previous !== null }, "json");
28549
+ printOutput(
28550
+ { previous_base_id: previous[0] ?? null, cleared: previous.length > 0 },
28551
+ "json"
28552
+ );
28342
28553
  return;
28343
28554
  }
28344
28555
  console.log(
28345
- previous ? `Worktree unbound from base (was: ${previous}).` : "No base binding to clear."
28556
+ previous.length > 0 ? `Worktree unbound from base (was: ${previous.join(", ")}).` : "No base binding to clear."
28346
28557
  );
28347
28558
  });
28348
28559
  bases.addCommand(buildBasesTokensCommand());
@@ -28354,13 +28565,6 @@ function buildBasesCommand() {
28354
28565
  bases.addCommand(buildBasesReportCommand());
28355
28566
  return bases;
28356
28567
  }
28357
- function resolveSelectorToBaseId(selector) {
28358
- const gitRoot = findGitRoot();
28359
- if (!gitRoot || selector.includes("/") || selector.includes(path35.sep)) return selector;
28360
- if (!isValidBaseId(selector)) return selector;
28361
- const id = readBaseMeta(path35.join(resolveBasesDir(gitRoot), selector))?.base_id;
28362
- return typeof id === "string" && isValidBaseId(id) ? id : selector;
28363
- }
28364
28568
  var init_bases = __esm({
28365
28569
  "src/data/commands/bases.ts"() {
28366
28570
  "use strict";
@@ -28378,10 +28582,8 @@ var init_bases = __esm({
28378
28582
  init_utils();
28379
28583
  init_terminal_output();
28380
28584
  init_workspace();
28381
- init_layout();
28382
28585
  init_base_workspace();
28383
- init_base_binding();
28384
- init_base_id();
28586
+ init_worktree_scope();
28385
28587
  init_expected();
28386
28588
  }
28387
28589
  });
@@ -28855,8 +29057,8 @@ function buildRecordsCommand() {
28855
29057
  const body = { data: parseData(opts.data) };
28856
29058
  if (opts.externalId) body.external_id = opts.externalId;
28857
29059
  if (opts.externalSource) body.external_source = opts.externalSource;
28858
- const path36 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
28859
- printOutput(await client.request("PUT", path36, body), outputFormat(this));
29060
+ const path35 = opts.id ? `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${pathSegment(opts.id, "--id")}` : `/v1/${base}/records/${pathSegment(recordType2, "record_type")}`;
29061
+ printOutput(await client.request("PUT", path35, body), outputFormat(this));
28860
29062
  });
28861
29063
  records.command("query <record_type>").description("List/search records: exact filters + fuzzy `search`, sorting, pagination").option(
28862
29064
  "--external-source <source>",
@@ -28891,9 +29093,9 @@ function buildRecordsCommand() {
28891
29093
  ).action(async function(recordType2, id, opts) {
28892
29094
  const base = pathSegment(requireBase(this), "--base");
28893
29095
  const client = await createDataClient();
28894
- let path36 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
28895
- if (opts.externalSource) path36 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
28896
- printOutput(await client.request("GET", path36), outputFormat(this));
29096
+ let path35 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
29097
+ if (opts.externalSource) path35 += `?external_source=${encodeURIComponent(opts.externalSource)}`;
29098
+ printOutput(await client.request("GET", path35), outputFormat(this));
28897
29099
  });
28898
29100
  records.command("delete <record_type> <id>").description(
28899
29101
  "Delete a record by internal ID, or by external_id (pass --external-source to scope it)"
@@ -28907,11 +29109,11 @@ function buildRecordsCommand() {
28907
29109
  }
28908
29110
  const base = pathSegment(requireBase(this), "--base");
28909
29111
  const client = await createDataClient();
28910
- let path36 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
29112
+ let path35 = `/v1/${base}/records/${pathSegment(recordType2, "record_type")}/${recordKey(id, opts.externalSource)}`;
28911
29113
  if (opts.externalSource) {
28912
- path36 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
29114
+ path35 += `?external_source=${encodeURIComponent(opts.externalSource)}&external_id=${encodeURIComponent(id)}`;
28913
29115
  }
28914
- await client.request("DELETE", path36);
29116
+ await client.request("DELETE", path35);
28915
29117
  console.log("Deleted");
28916
29118
  });
28917
29119
  records.command("history <id>").description(
@@ -29076,8 +29278,8 @@ function buildRelationshipsCommand() {
29076
29278
  if (opts.data) body.data = parseData(opts.data);
29077
29279
  const base = pathSegment(requireBase(this), "--base");
29078
29280
  const client = await createDataClient();
29079
- const path36 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
29080
- printOutput(await client.request("PUT", path36, body), outputFormat(this));
29281
+ const path35 = opts.id ? `/v1/${base}/relationships/${pathSegment(opts.id, "--id")}` : `/v1/${base}/relationships`;
29282
+ printOutput(await client.request("PUT", path35, body), outputFormat(this));
29081
29283
  });
29082
29284
  relationships.command("get <id>").description(
29083
29285
  "Get a relationship by ID (or by its external key: pass the external_id with --rel-type)"
@@ -29310,8 +29512,8 @@ function buildToolsetsCommand() {
29310
29512
  toolsets.command("get <slug>").description("Get a toolset").option("--resolved", "Include resolved record_type schemas").action(async function(slug, opts) {
29311
29513
  const base = pathSegment(requireBase(this), "--base");
29312
29514
  const client = await createDataClient();
29313
- const path36 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
29314
- printOutput(await client.request("GET", path36), outputFormat(this));
29515
+ const path35 = opts.resolved ? `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}/resolved` : `/v1/${base}/toolsets/${pathSegment(slug, "toolset slug")}`;
29516
+ printOutput(await client.request("GET", path35), outputFormat(this));
29315
29517
  });
29316
29518
  toolsets.command("list").description("List all toolsets").action(async function() {
29317
29519
  const base = pathSegment(requireBase(this), "--base");
@@ -29546,7 +29748,7 @@ init_utils();
29546
29748
  init_registry();
29547
29749
  import { readFileSync as readFileSync26 } from "fs";
29548
29750
  import { fileURLToPath as fileURLToPath3 } from "url";
29549
- import { dirname as dirname12, join as join34 } from "path";
29751
+ import { dirname as dirname12, join as join33 } from "path";
29550
29752
 
29551
29753
  // src/lib/version-refresh.ts
29552
29754
  init_version_cache();
@@ -29702,7 +29904,7 @@ Run \`wayai admin skill install\` to update.`);
29702
29904
 
29703
29905
  // src/index.ts
29704
29906
  var __dirname = dirname12(fileURLToPath3(import.meta.url));
29705
- var pkg = JSON.parse(readFileSync26(join34(__dirname, "..", "package.json"), "utf-8"));
29907
+ var pkg = JSON.parse(readFileSync26(join33(__dirname, "..", "package.json"), "utf-8"));
29706
29908
  var [, , command, ...args] = process.argv;
29707
29909
  var isBackgroundRefresh = command === REFRESH_COMMAND;
29708
29910
  if (!isBackgroundRefresh) initSentry(command, pkg.version);
@@ -29951,9 +30153,9 @@ Commands:
29951
30153
  sync Alias of \`publish\` (not to be confused with sync-skills / sync-mcp)
29952
30154
  org create Create a new organization (you become its owner)
29953
30155
  org <pull|push|diff> Sync org-scoped resources as code (wayai-ws/org/)
29954
- use <hub> Bind this worktree to a specific hub (UUID or folder name)
30156
+ use [--add] <sel> Scope this worktree to a hub or base (UUID, folder name, hubs/<n>, bases/<n>; --add widens)
29955
30157
  migrate Move a legacy repo to the wayai-ws/{hubs,org} layout
29956
- unbind Clear the worktree hub binding
30158
+ unbind [<sel>] Drop one entry from the worktree scope, or clear the whole scope
29957
30159
  send-message Send a test message to a hub (preview or production; -f <path> to attach a file)
29958
30160
  conversations List or inspect conversations
29959
30161
  alerts Show a hub's active connection/credential alerts (run this FIRST when a hub misbehaves)
@@ -29975,8 +30177,8 @@ Commands:
29975
30177
  Data \u2014 one namespace per entity. Run \`wayai <namespace> --help\` for its tree.
29976
30178
  ${await dataHelpBlock()}
29977
30179
  bases promote Promote a preview base to production (distinct from \`publish\`, which promotes a hub)
29978
- bases use <base> Bind this worktree to a base (\`wayai use\` binds a hub)
29979
- bases unbind Clear this worktree's base binding
30180
+ bases use <base> Deprecated alias of \`wayai use bases/<id>\`
30181
+ bases unbind Deprecated alias of \`wayai unbind bases/<id>\` (clears the base axis)
29980
30182
 
29981
30183
  Flags:
29982
30184
  --yes, -y Skip confirmation prompts (useful for CI and scripting)
@@ -30028,12 +30230,15 @@ Org-scoped mode:
30028
30230
  pull/push operate on the single hub in the workspace, or pass
30029
30231
  \`--hub <uuid|name>\` to disambiguate when multiple hubs exist.
30030
30232
 
30031
- Worktree hub binding:
30032
- Each git checkout (main or linked worktree) can be bound to a single hub.
30033
- push/pull refuse to run against a different hub once bound. The binding is
30034
- auto-set on first successful pull (or new-hub creation) into an unbound
30035
- checkout. Use \`wayai use <hub>\` to bind manually and \`wayai unbind\` to clear.
30036
- The binding lives at \`<git-dir>/wayai-binding\` and is naturally per-checkout.
30233
+ Worktree scope:
30234
+ Each git checkout (main or linked worktree) carries a scope: a set of hubs and
30235
+ a set of bases, at \`<git-dir>/wayai-scope\` \u2014 per-checkout, never tracked.
30236
+ push/pull/publish refuse to run against anything outside a non-empty set. Each
30237
+ axis is seeded by the first successful pull/push/create into a checkout that
30238
+ has none, and only widens on purpose: \`wayai use --add <sel>\` adds one,
30239
+ \`wayai use <sel>\` replaces that axis, \`wayai unbind <sel>\` drops one entry and
30240
+ bare \`wayai unbind\` clears everything. It is a routing tripwire, not a lock
30241
+ and not authorization.
30037
30242
 
30038
30243
  Examples:
30039
30244
  wayai login