@wayai/cli 0.3.76 → 0.3.77

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
@@ -165,11 +165,11 @@ function captureException2(error, context) {
165
165
  Sentry.captureException(error);
166
166
  });
167
167
  }
168
- function addApiBreadcrumb(method, path24) {
168
+ function addApiBreadcrumb(method, path25) {
169
169
  if (!initialized) return;
170
170
  Sentry.addBreadcrumb({
171
171
  category: "http",
172
- message: `${method} ${path24}`,
172
+ message: `${method} ${path25}`,
173
173
  level: "info"
174
174
  });
175
175
  }
@@ -231,9 +231,9 @@ var init_api_client = __esm({
231
231
  ApiError = class extends Error {
232
232
  status;
233
233
  body;
234
- constructor(method, path24, status, body) {
234
+ constructor(method, path25, status, body) {
235
235
  const safeBody = redactSecrets(body);
236
- super(`API request failed: ${method} ${path24} (${status}): ${safeBody}`);
236
+ super(`API request failed: ${method} ${path25} (${status}): ${safeBody}`);
237
237
  this.name = "ApiError";
238
238
  this.status = status;
239
239
  this.body = safeBody;
@@ -325,8 +325,8 @@ var init_api_client = __esm({
325
325
  ...organizationId && { organization_id: organizationId }
326
326
  });
327
327
  }
328
- async lookup(path24, opts) {
329
- const params = new URLSearchParams({ path: path24 });
328
+ async lookup(path25, opts) {
329
+ const params = new URLSearchParams({ path: path25 });
330
330
  if (opts?.organizationId) params.set("organization_id", opts.organizationId);
331
331
  return this.request("GET", `/api/ci/lookup?${params.toString()}`);
332
332
  }
@@ -543,6 +543,30 @@ var init_api_client = __esm({
543
543
  preview_label: previewLabel ?? null
544
544
  });
545
545
  }
546
+ /**
547
+ * Preview → production diff — the source of truth for what `wayai publish`
548
+ * will do. `data.is_first_publish` distinguishes a first publish (no linked
549
+ * production hub yet, all entities listed as added) from a subsequent sync.
550
+ * Requires only `hub:read`, so it also drives the pre-confirm preview.
551
+ */
552
+ async getHubDiff(hubId) {
553
+ return this.request("GET", `/api/setup/hubs/${hubId}/diff`);
554
+ }
555
+ /**
556
+ * First publish: clone the preview hub into a NEW production hub. Errors if the
557
+ * preview is already linked to production (use `syncHub` instead). Returns the
558
+ * new production hub id (POST /api/setup/hubs/:id/publish — HTTP 201).
559
+ */
560
+ async publishHub(hubId) {
561
+ return this.request("POST", `/api/setup/hubs/${hubId}/publish`);
562
+ }
563
+ /**
564
+ * Sync a preview hub's changes into its linked production hub
565
+ * (POST /api/setup/hubs/:id/sync). No-op-safe when already in sync.
566
+ */
567
+ async syncHub(hubId) {
568
+ return this.request("POST", `/api/setup/hubs/${hubId}/sync`);
569
+ }
546
570
  /**
547
571
  * Set or clear a preview hub's label (PATCH /api/setup/hubs/:id). Pass null to
548
572
  * clear. The backend trims and normalizes empty → null (max 60 chars).
@@ -715,9 +739,9 @@ var init_api_client = __esm({
715
739
  `/api/admin/data-explorer/debug/observability/${encodeURIComponent(hubId)}/conversations/${encodeURIComponent(conversationId)}${qs}`
716
740
  );
717
741
  }
718
- async request(method, path24, body) {
719
- addApiBreadcrumb(method, path24);
720
- const url = `${this.apiUrl}${path24}`;
742
+ async request(method, path25, body) {
743
+ addApiBreadcrumb(method, path25);
744
+ const url = `${this.apiUrl}${path25}`;
721
745
  let refreshedOn401 = false;
722
746
  for (let retry = 0; ; retry++) {
723
747
  let response = await this.send(url, method, body);
@@ -741,7 +765,7 @@ var init_api_client = __esm({
741
765
  await delay(RETRYABLE_BACKOFF_MS[retry]);
742
766
  continue;
743
767
  }
744
- throw new ApiError(method, path24, response.status, errorBody);
768
+ throw new ApiError(method, path25, response.status, errorBody);
745
769
  }
746
770
  }
747
771
  /** Issue a single authenticated request with the client's current token. */
@@ -1176,8 +1200,8 @@ var init_parseUtil = __esm({
1176
1200
  init_errors();
1177
1201
  init_en();
1178
1202
  makeIssue = (params) => {
1179
- const { data, path: path24, errorMaps, issueData } = params;
1180
- const fullPath = [...path24, ...issueData.path || []];
1203
+ const { data, path: path25, errorMaps, issueData } = params;
1204
+ const fullPath = [...path25, ...issueData.path || []];
1181
1205
  const fullIssue = {
1182
1206
  ...issueData,
1183
1207
  path: fullPath
@@ -1488,11 +1512,11 @@ var init_types = __esm({
1488
1512
  init_parseUtil();
1489
1513
  init_util();
1490
1514
  ParseInputLazyPath = class {
1491
- constructor(parent, value, path24, key) {
1515
+ constructor(parent, value, path25, key) {
1492
1516
  this._cachedPath = [];
1493
1517
  this.parent = parent;
1494
1518
  this.data = value;
1495
- this._path = path24;
1519
+ this._path = path25;
1496
1520
  this._key = key;
1497
1521
  }
1498
1522
  get path() {
@@ -4946,12 +4970,12 @@ function typeMatches(typeField, allowed) {
4946
4970
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
4947
4971
  return false;
4948
4972
  }
4949
- function validateSchema(schema, path24, errors, opts = {}) {
4973
+ function validateSchema(schema, path25, errors, opts = {}) {
4950
4974
  if (typeof schema === "boolean") return;
4951
4975
  const depth = opts.depth ?? 0;
4952
4976
  if (depth > MAX_SCHEMA_DEPTH) {
4953
4977
  errors.push({
4954
- path: path24 || "<root>",
4978
+ path: path25 || "<root>",
4955
4979
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH} levels`,
4956
4980
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
4957
4981
  });
@@ -4959,7 +4983,7 @@ function validateSchema(schema, path24, errors, opts = {}) {
4959
4983
  }
4960
4984
  if (!isRecord(schema)) {
4961
4985
  errors.push({
4962
- path: path24,
4986
+ path: path25,
4963
4987
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
4964
4988
  });
4965
4989
  return;
@@ -4967,14 +4991,14 @@ function validateSchema(schema, path24, errors, opts = {}) {
4967
4991
  if (opts.isRoot) {
4968
4992
  if ("type" in schema && schema.type !== "object") {
4969
4993
  errors.push({
4970
- path: path24 ? `${path24}.type` : "type",
4994
+ path: path25 ? `${path25}.type` : "type",
4971
4995
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
4972
4996
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
4973
4997
  });
4974
4998
  }
4975
4999
  } else if ("type" in schema && !typeMatches(schema.type, ALLOWED_TYPES)) {
4976
5000
  errors.push({
4977
- path: `${path24}.type`,
5001
+ path: `${path25}.type`,
4978
5002
  message: `type must be one of ${[...ALLOWED_TYPES].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
4979
5003
  });
4980
5004
  }
@@ -4983,25 +5007,25 @@ function validateSchema(schema, path24, errors, opts = {}) {
4983
5007
  if (typeof e === "string") {
4984
5008
  const isPlaceholder = PLACEHOLDER_TOKENS.includes(e);
4985
5009
  errors.push({
4986
- path: `${path24}.enum`,
5010
+ path: `${path25}.enum`,
4987
5011
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
4988
5012
  suggestion: 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"]`.'
4989
5013
  });
4990
5014
  } else if (!Array.isArray(e)) {
4991
5015
  errors.push({
4992
- path: `${path24}.enum`,
5016
+ path: `${path25}.enum`,
4993
5017
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
4994
5018
  });
4995
5019
  } else if (e.length === 0) {
4996
5020
  errors.push({
4997
- path: `${path24}.enum`,
5021
+ path: `${path25}.enum`,
4998
5022
  message: "enum must not be empty"
4999
5023
  });
5000
5024
  } else {
5001
5025
  for (let i = 0; i < e.length; i++) {
5002
5026
  if (!isPrimitive(e[i])) {
5003
5027
  errors.push({
5004
- path: `${path24}.enum[${i}]`,
5028
+ path: `${path25}.enum[${i}]`,
5005
5029
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
5006
5030
  });
5007
5031
  }
@@ -5011,7 +5035,7 @@ function validateSchema(schema, path24, errors, opts = {}) {
5011
5035
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
5012
5036
  if (key in schema && typeof schema[key] === "boolean") {
5013
5037
  errors.push({
5014
- path: `${path24}.${key}`,
5038
+ path: `${path25}.${key}`,
5015
5039
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
5016
5040
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
5017
5041
  });
@@ -5020,45 +5044,45 @@ function validateSchema(schema, path24, errors, opts = {}) {
5020
5044
  if ("properties" in schema) {
5021
5045
  if (!isRecord(schema.properties)) {
5022
5046
  errors.push({
5023
- path: `${path24}.properties`,
5047
+ path: `${path25}.properties`,
5024
5048
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
5025
5049
  });
5026
5050
  } else {
5027
5051
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
5028
- validateSchema(propSchema, `${path24}.properties.${propName}`, errors, { depth: depth + 1 });
5052
+ validateSchema(propSchema, `${path25}.properties.${propName}`, errors, { depth: depth + 1 });
5029
5053
  }
5030
5054
  }
5031
5055
  }
5032
5056
  if (schemaTypeIncludes(schema, "array") && "items" in schema) {
5033
5057
  if (Array.isArray(schema.items)) {
5034
- schema.items.forEach((sub, i) => validateSchema(sub, `${path24}.items[${i}]`, errors, { depth: depth + 1 }));
5058
+ schema.items.forEach((sub, i) => validateSchema(sub, `${path25}.items[${i}]`, errors, { depth: depth + 1 }));
5035
5059
  } else {
5036
- validateSchema(schema.items, `${path24}.items`, errors, { depth: depth + 1 });
5060
+ validateSchema(schema.items, `${path25}.items`, errors, { depth: depth + 1 });
5037
5061
  }
5038
5062
  }
5039
5063
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS) {
5040
5064
  if (key in schema && isRecord(schema[key])) {
5041
- validateSchema(schema[key], `${path24}.${key}`, errors, { depth: depth + 1 });
5065
+ validateSchema(schema[key], `${path25}.${key}`, errors, { depth: depth + 1 });
5042
5066
  }
5043
5067
  }
5044
5068
  for (const key of SUBSCHEMA_LIST_KEYWORDS) {
5045
5069
  const list = schema[key];
5046
5070
  if (Array.isArray(list)) {
5047
- list.forEach((sub, i) => validateSchema(sub, `${path24}.${key}[${i}]`, errors, { depth: depth + 1 }));
5071
+ list.forEach((sub, i) => validateSchema(sub, `${path25}.${key}[${i}]`, errors, { depth: depth + 1 }));
5048
5072
  }
5049
5073
  }
5050
5074
  for (const key of SUBSCHEMA_MAP_KEYWORDS) {
5051
5075
  const map = schema[key];
5052
5076
  if (isRecord(map)) {
5053
5077
  for (const [name, sub] of Object.entries(map)) {
5054
- validateSchema(sub, `${path24}.${key}.${name}`, errors, { depth: depth + 1 });
5078
+ validateSchema(sub, `${path25}.${key}.${name}`, errors, { depth: depth + 1 });
5055
5079
  }
5056
5080
  }
5057
5081
  }
5058
5082
  const reportedPaths = new Set(errors.map((e) => e.path));
5059
5083
  for (const [k, v] of Object.entries(schema)) {
5060
5084
  if (typeof v !== "string") continue;
5061
- const fieldPath = `${path24}.${k}`;
5085
+ const fieldPath = `${path25}.${k}`;
5062
5086
  if (reportedPaths.has(fieldPath)) continue;
5063
5087
  for (const token of PLACEHOLDER_TOKENS) {
5064
5088
  if (v === token) {
@@ -5214,10 +5238,10 @@ function refineHubAsCodeLanes(config, ctx) {
5214
5238
  }
5215
5239
  }
5216
5240
  }
5217
- function isSafeTemplatePath(path24) {
5218
- if (path24.length === 0 || path24.length > 300) return false;
5219
- if (path24.startsWith("/") || path24.includes("\\")) return false;
5220
- const segments = path24.split("/");
5241
+ function isSafeTemplatePath(path25) {
5242
+ if (path25.length === 0 || path25.length > 300) return false;
5243
+ if (path25.startsWith("/") || path25.includes("\\")) return false;
5244
+ const segments = path25.split("/");
5221
5245
  return segments.every(
5222
5246
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
5223
5247
  );
@@ -8536,9 +8560,9 @@ function getVersionCachePath(filename = CLI_CACHE_FILE) {
8536
8560
  }
8537
8561
  function readVersionCache(filename = CLI_CACHE_FILE) {
8538
8562
  try {
8539
- const path24 = getVersionCachePath(filename);
8540
- if (!existsSync4(path24)) return null;
8541
- const parsed = JSON.parse(readFileSync5(path24, "utf-8"));
8563
+ const path25 = getVersionCachePath(filename);
8564
+ if (!existsSync4(path25)) return null;
8565
+ const parsed = JSON.parse(readFileSync5(path25, "utf-8"));
8542
8566
  if (typeof parsed.lastCheck !== "number") return null;
8543
8567
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
8544
8568
  return parsed;
@@ -8558,10 +8582,10 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
8558
8582
  return Date.now() - cache.lastCheck > maxAgeMs;
8559
8583
  }
8560
8584
  function writeVersionCache(filename, cache) {
8561
- const path24 = getVersionCachePath(filename);
8562
- const dir = dirname3(path24);
8585
+ const path25 = getVersionCachePath(filename);
8586
+ const dir = dirname3(path25);
8563
8587
  if (!existsSync4(dir)) mkdirSync(dir, { recursive: true });
8564
- writeFileSync2(path24, JSON.stringify(cache));
8588
+ writeFileSync2(path25, JSON.stringify(cache));
8565
8589
  }
8566
8590
  function touchVersionCache(filename) {
8567
8591
  writeVersionCache(filename, { lastCheck: Date.now(), latest: readVersionCache(filename)?.latest ?? null });
@@ -8600,14 +8624,14 @@ function parseFrontmatterVersion(content) {
8600
8624
  function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
8601
8625
  const found = [];
8602
8626
  for (const rel of paths) {
8603
- const path24 = join7(projectRoot, rel);
8604
- if (!existsSync5(path24)) continue;
8627
+ const path25 = join7(projectRoot, rel);
8628
+ if (!existsSync5(path25)) continue;
8605
8629
  let version = null;
8606
8630
  try {
8607
- version = parseFrontmatterVersion(readFileSync6(path24, "utf-8"));
8631
+ version = parseFrontmatterVersion(readFileSync6(path25, "utf-8"));
8608
8632
  } catch {
8609
8633
  }
8610
- found.push({ path: path24, version });
8634
+ found.push({ path: path25, version });
8611
8635
  }
8612
8636
  return found;
8613
8637
  }
@@ -11577,12 +11601,12 @@ function typeMatches2(typeField, allowed) {
11577
11601
  if (Array.isArray(typeField)) return typeField.every((t) => typeof t === "string" && allowed.has(t));
11578
11602
  return false;
11579
11603
  }
11580
- function validateSchema2(schema, path24, errors, opts = {}) {
11604
+ function validateSchema2(schema, path25, errors, opts = {}) {
11581
11605
  if (typeof schema === "boolean") return;
11582
11606
  const depth = opts.depth ?? 0;
11583
11607
  if (depth > MAX_SCHEMA_DEPTH2) {
11584
11608
  errors.push({
11585
- path: path24 || "<root>",
11609
+ path: path25 || "<root>",
11586
11610
  message: `schema nesting exceeds ${MAX_SCHEMA_DEPTH2} levels`,
11587
11611
  suggestion: "Flatten the schema; LLM providers reject deeply nested tool input_schemas."
11588
11612
  });
@@ -11590,7 +11614,7 @@ function validateSchema2(schema, path24, errors, opts = {}) {
11590
11614
  }
11591
11615
  if (!isRecord2(schema)) {
11592
11616
  errors.push({
11593
- path: path24,
11617
+ path: path25,
11594
11618
  message: `expected object, got ${schema === null ? "null" : typeof schema}`
11595
11619
  });
11596
11620
  return;
@@ -11598,14 +11622,14 @@ function validateSchema2(schema, path24, errors, opts = {}) {
11598
11622
  if (opts.isRoot) {
11599
11623
  if ("type" in schema && schema.type !== "object") {
11600
11624
  errors.push({
11601
- path: path24 ? `${path24}.type` : "type",
11625
+ path: path25 ? `${path25}.type` : "type",
11602
11626
  message: `root type must be 'object', got ${JSON.stringify(schema.type)}`,
11603
11627
  suggestion: "Tool parameters at the root must be an object: `{ type: 'object', properties: { ... } }`."
11604
11628
  });
11605
11629
  }
11606
11630
  } else if ("type" in schema && !typeMatches2(schema.type, ALLOWED_TYPES2)) {
11607
11631
  errors.push({
11608
- path: `${path24}.type`,
11632
+ path: `${path25}.type`,
11609
11633
  message: `type must be one of ${[...ALLOWED_TYPES2].join("/")} or an array of those, got ${JSON.stringify(schema.type)}`
11610
11634
  });
11611
11635
  }
@@ -11614,25 +11638,25 @@ function validateSchema2(schema, path24, errors, opts = {}) {
11614
11638
  if (typeof e === "string") {
11615
11639
  const isPlaceholder = PLACEHOLDER_TOKENS2.includes(e);
11616
11640
  errors.push({
11617
- path: `${path24}.enum`,
11641
+ path: `${path25}.enum`,
11618
11642
  message: `enum must be a non-empty array of primitives, got string ${JSON.stringify(e)}`,
11619
11643
  suggestion: 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"]`.'
11620
11644
  });
11621
11645
  } else if (!Array.isArray(e)) {
11622
11646
  errors.push({
11623
- path: `${path24}.enum`,
11647
+ path: `${path25}.enum`,
11624
11648
  message: `enum must be a non-empty array of primitives, got ${typeof e}`
11625
11649
  });
11626
11650
  } else if (e.length === 0) {
11627
11651
  errors.push({
11628
- path: `${path24}.enum`,
11652
+ path: `${path25}.enum`,
11629
11653
  message: "enum must not be empty"
11630
11654
  });
11631
11655
  } else {
11632
11656
  for (let i = 0; i < e.length; i++) {
11633
11657
  if (!isPrimitive2(e[i])) {
11634
11658
  errors.push({
11635
- path: `${path24}.enum[${i}]`,
11659
+ path: `${path25}.enum[${i}]`,
11636
11660
  message: `enum entry must be a primitive (string/number/boolean/null), got ${typeof e[i]}`
11637
11661
  });
11638
11662
  }
@@ -11642,7 +11666,7 @@ function validateSchema2(schema, path24, errors, opts = {}) {
11642
11666
  for (const key of ["exclusiveMinimum", "exclusiveMaximum"]) {
11643
11667
  if (key in schema && typeof schema[key] === "boolean") {
11644
11668
  errors.push({
11645
- path: `${path24}.${key}`,
11669
+ path: `${path25}.${key}`,
11646
11670
  message: `${key} as boolean is draft-04 syntax; draft 2020-12 requires a numeric value`,
11647
11671
  suggestion: `Replace with the numeric bound, e.g. \`${key}: 0\`.`
11648
11672
  });
@@ -11651,45 +11675,45 @@ function validateSchema2(schema, path24, errors, opts = {}) {
11651
11675
  if ("properties" in schema) {
11652
11676
  if (!isRecord2(schema.properties)) {
11653
11677
  errors.push({
11654
- path: `${path24}.properties`,
11678
+ path: `${path25}.properties`,
11655
11679
  message: `properties must be an object, got ${Array.isArray(schema.properties) ? "array" : typeof schema.properties}`
11656
11680
  });
11657
11681
  } else {
11658
11682
  for (const [propName, propSchema] of Object.entries(schema.properties)) {
11659
- validateSchema2(propSchema, `${path24}.properties.${propName}`, errors, { depth: depth + 1 });
11683
+ validateSchema2(propSchema, `${path25}.properties.${propName}`, errors, { depth: depth + 1 });
11660
11684
  }
11661
11685
  }
11662
11686
  }
11663
11687
  if (schemaTypeIncludes2(schema, "array") && "items" in schema) {
11664
11688
  if (Array.isArray(schema.items)) {
11665
- schema.items.forEach((sub, i) => validateSchema2(sub, `${path24}.items[${i}]`, errors, { depth: depth + 1 }));
11689
+ schema.items.forEach((sub, i) => validateSchema2(sub, `${path25}.items[${i}]`, errors, { depth: depth + 1 }));
11666
11690
  } else {
11667
- validateSchema2(schema.items, `${path24}.items`, errors, { depth: depth + 1 });
11691
+ validateSchema2(schema.items, `${path25}.items`, errors, { depth: depth + 1 });
11668
11692
  }
11669
11693
  }
11670
11694
  for (const key of SUBSCHEMA_OBJECT_KEYWORDS2) {
11671
11695
  if (key in schema && isRecord2(schema[key])) {
11672
- validateSchema2(schema[key], `${path24}.${key}`, errors, { depth: depth + 1 });
11696
+ validateSchema2(schema[key], `${path25}.${key}`, errors, { depth: depth + 1 });
11673
11697
  }
11674
11698
  }
11675
11699
  for (const key of SUBSCHEMA_LIST_KEYWORDS2) {
11676
11700
  const list = schema[key];
11677
11701
  if (Array.isArray(list)) {
11678
- list.forEach((sub, i) => validateSchema2(sub, `${path24}.${key}[${i}]`, errors, { depth: depth + 1 }));
11702
+ list.forEach((sub, i) => validateSchema2(sub, `${path25}.${key}[${i}]`, errors, { depth: depth + 1 }));
11679
11703
  }
11680
11704
  }
11681
11705
  for (const key of SUBSCHEMA_MAP_KEYWORDS2) {
11682
11706
  const map = schema[key];
11683
11707
  if (isRecord2(map)) {
11684
11708
  for (const [name, sub] of Object.entries(map)) {
11685
- validateSchema2(sub, `${path24}.${key}.${name}`, errors, { depth: depth + 1 });
11709
+ validateSchema2(sub, `${path25}.${key}.${name}`, errors, { depth: depth + 1 });
11686
11710
  }
11687
11711
  }
11688
11712
  }
11689
11713
  const reportedPaths = new Set(errors.map((e) => e.path));
11690
11714
  for (const [k, v] of Object.entries(schema)) {
11691
11715
  if (typeof v !== "string") continue;
11692
- const fieldPath = `${path24}.${k}`;
11716
+ const fieldPath = `${path25}.${k}`;
11693
11717
  if (reportedPaths.has(fieldPath)) continue;
11694
11718
  for (const token of PLACEHOLDER_TOKENS2) {
11695
11719
  if (v === token) {
@@ -11845,10 +11869,10 @@ function refineHubAsCodeLanes2(config, ctx) {
11845
11869
  }
11846
11870
  }
11847
11871
  }
11848
- function isSafeTemplatePath2(path24) {
11849
- if (path24.length === 0 || path24.length > 300) return false;
11850
- if (path24.startsWith("/") || path24.includes("\\")) return false;
11851
- const segments = path24.split("/");
11872
+ function isSafeTemplatePath2(path25) {
11873
+ if (path25.length === 0 || path25.length > 300) return false;
11874
+ if (path25.startsWith("/") || path25.includes("\\")) return false;
11875
+ const segments = path25.split("/");
11852
11876
  return segments.every(
11853
11877
  (seg) => seg.length > 0 && seg !== "." && seg !== ".." && /^[A-Za-z0-9._-]+$/.test(seg)
11854
11878
  );
@@ -11876,9 +11900,9 @@ function isPlainObject(value) {
11876
11900
  const proto = Object.getPrototypeOf(value);
11877
11901
  return proto === Object.prototype || proto === null;
11878
11902
  }
11879
- function isSanitizableSliceFile(path24) {
11880
- if (!/\.ya?ml$/i.test(path24)) return false;
11881
- return path24 !== "resources" && !path24.startsWith("resources/");
11903
+ function isSanitizableSliceFile(path25) {
11904
+ if (!/\.ya?ml$/i.test(path25)) return false;
11905
+ return path25 !== "resources" && !path25.startsWith("resources/");
11882
11906
  }
11883
11907
  function distinctUsesBackends(uses) {
11884
11908
  return [...new Set((uses ?? []).map((u) => u.backend))];
@@ -14567,12 +14591,153 @@ var init_relabel = __esm({
14567
14591
  }
14568
14592
  });
14569
14593
 
14594
+ // src/commands/publish.ts
14595
+ var publish_exports = {};
14596
+ __export(publish_exports, {
14597
+ parseArgs: () => parseArgs9,
14598
+ publishCommand: () => publishCommand,
14599
+ renderHubDiff: () => renderHubDiff,
14600
+ runPublish: () => runPublish
14601
+ });
14602
+ import * as path17 from "path";
14603
+ function parseArgs9(args2) {
14604
+ let autoConfirm = false;
14605
+ let hubSelector;
14606
+ for (let i = 0; i < args2.length; i++) {
14607
+ const arg = args2[i];
14608
+ if (arg === "--yes" || arg === "-y") {
14609
+ autoConfirm = true;
14610
+ } else if (arg === "--hub" && args2[i + 1]) {
14611
+ hubSelector = args2[++i];
14612
+ }
14613
+ }
14614
+ return { autoConfirm, hubSelector };
14615
+ }
14616
+ function renderSection(title, entries) {
14617
+ if (entries.length === 0) return;
14618
+ console.log(`
14619
+ \x1B[1m${title}\x1B[0m`);
14620
+ for (const e of entries) {
14621
+ const { sym, color } = STATUS_STYLE[e.status];
14622
+ const type = e.type ? ` (${e.type})` : "";
14623
+ console.log(`\x1B[${color}m${sym} ${e.name}${type}\x1B[0m`);
14624
+ }
14625
+ }
14626
+ function renderHubDiff(result) {
14627
+ if (result.hub_changed) {
14628
+ const { sym, color } = STATUS_STYLE[result.is_first_publish ? "added" : "updated"];
14629
+ console.log("\n\x1B[1mHub\x1B[0m");
14630
+ console.log(`\x1B[${color}m${sym} hub settings\x1B[0m`);
14631
+ }
14632
+ renderSection("Agents", result.agents);
14633
+ renderSection("Connections", result.connections);
14634
+ renderSection("Tools", result.tools);
14635
+ renderSection("Channels", result.channels);
14636
+ renderSection("States", result.states);
14637
+ renderSection("Resources", result.resources);
14638
+ console.log();
14639
+ }
14640
+ async function runPublish(client, opts) {
14641
+ const { hubId, localConfig, organizationId, autoConfirm } = opts;
14642
+ const confirmFn = opts.confirmFn ?? confirm;
14643
+ if (localConfig) {
14644
+ const pushDiff = await client.diff(hubId, localConfig, "push", organizationId);
14645
+ if (pushDiff.has_changes) {
14646
+ console.log("\n\x1B[33mYou have local changes not yet pushed to this preview.\x1B[0m");
14647
+ console.log("Publishing promotes the PREVIEW (server) state \u2014 these local edits are NOT included.");
14648
+ console.log("Run `wayai push` first if you want them in production.");
14649
+ if (!autoConfirm) {
14650
+ const ok = await confirmFn("Continue with the current preview state anyway?");
14651
+ if (!ok) {
14652
+ console.log("Cancelled.");
14653
+ return "cancelled";
14654
+ }
14655
+ }
14656
+ }
14657
+ }
14658
+ const { data: diff } = await client.getHubDiff(hubId);
14659
+ if (!diff.has_changes && !diff.is_first_publish) {
14660
+ console.log("Production is already up to date. Nothing to sync.");
14661
+ return "up-to-date";
14662
+ }
14663
+ console.log(
14664
+ diff.is_first_publish ? "\nPublish preview \u2192 new production hub:" : "\nSync preview \u2192 production:"
14665
+ );
14666
+ renderHubDiff(diff);
14667
+ if (!autoConfirm) {
14668
+ const ok = await confirmFn(
14669
+ diff.is_first_publish ? "Publish to production?" : "Sync these changes to production?"
14670
+ );
14671
+ if (!ok) {
14672
+ console.log("Cancelled.");
14673
+ return "cancelled";
14674
+ }
14675
+ }
14676
+ if (diff.is_first_publish) {
14677
+ console.log("Publishing...");
14678
+ const { data } = await client.publishHub(hubId);
14679
+ console.log(`
14680
+ Published. Production hub: ${data.production_hub_id}`);
14681
+ return "published";
14682
+ }
14683
+ console.log("Syncing...");
14684
+ await client.syncHub(hubId);
14685
+ console.log("\nSynced preview changes to production.");
14686
+ return "synced";
14687
+ }
14688
+ async function publishCommand(args2) {
14689
+ const { autoConfirm, hubSelector } = parseArgs9(args2);
14690
+ const repoConfig = requireRepoConfig();
14691
+ const { organization_id: organizationId } = repoConfig;
14692
+ const { config, accessToken } = await requireAuth();
14693
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
14694
+ const workspaceDir = resolveWorkspaceDir();
14695
+ const gitRoot = findGitRoot();
14696
+ if (gitRoot) warnLayoutOnce(gitRoot);
14697
+ const { hubId, hubFolder } = resolveHubTarget(workspaceDir, hubSelector);
14698
+ let localConfig = null;
14699
+ if (hubFolder) {
14700
+ localConfig = parseHubFolder(hubFolder);
14701
+ if (localConfig.hub_environment === "production") {
14702
+ console.log(
14703
+ "This is a read-only production mirror \u2014 production hubs cannot be published. Publish the linked preview hub instead."
14704
+ );
14705
+ return;
14706
+ }
14707
+ }
14708
+ assertHubMatchesBinding(hubId, hubFolder ? path17.basename(hubFolder) : void 0);
14709
+ await runPublish(client, { hubId, localConfig, organizationId, autoConfirm });
14710
+ }
14711
+ var STATUS_STYLE;
14712
+ var init_publish = __esm({
14713
+ "src/commands/publish.ts"() {
14714
+ "use strict";
14715
+ init_auth();
14716
+ init_api_client();
14717
+ init_parser();
14718
+ init_pull();
14719
+ init_utils();
14720
+ init_workspace();
14721
+ init_layout();
14722
+ init_repo_config();
14723
+ init_hub_binding();
14724
+ STATUS_STYLE = {
14725
+ added: { sym: "+", color: 32 },
14726
+ // green
14727
+ removed: { sym: "-", color: 31 },
14728
+ // red
14729
+ updated: { sym: "~", color: 33 }
14730
+ // yellow
14731
+ };
14732
+ }
14733
+ });
14734
+
14570
14735
  // src/commands/use.ts
14571
14736
  var use_exports = {};
14572
14737
  __export(use_exports, {
14573
14738
  useCommand: () => useCommand
14574
14739
  });
14575
- import * as path17 from "path";
14740
+ import * as path18 from "path";
14576
14741
  async function useCommand(args2) {
14577
14742
  const target = args2[0];
14578
14743
  if (!target || target.startsWith("-")) {
@@ -14595,7 +14760,7 @@ async function useCommand(args2) {
14595
14760
  if (!match) {
14596
14761
  console.error(`No hub matching "${target}" found in ${wsLabel}/. Pass a UUID or a folder name from:`);
14597
14762
  for (const h of scanWorkspaceHubs(workspaceDir)) {
14598
- console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
14763
+ console.error(` ${path18.basename(h.hubFolder)} (${h.hubId})`);
14599
14764
  }
14600
14765
  process.exit(1);
14601
14766
  }
@@ -14635,10 +14800,10 @@ __export(migrate_exports, {
14635
14800
  });
14636
14801
  import { execFileSync as execFileSync3 } from "child_process";
14637
14802
  import * as fs14 from "fs";
14638
- import * as path18 from "path";
14803
+ import * as path19 from "path";
14639
14804
  function isTracked(gitRoot, p) {
14640
14805
  try {
14641
- execFileSync3("git", ["ls-files", "--error-unmatch", "--", path18.relative(gitRoot, p)], {
14806
+ execFileSync3("git", ["ls-files", "--error-unmatch", "--", path19.relative(gitRoot, p)], {
14642
14807
  cwd: gitRoot,
14643
14808
  stdio: ["pipe", "pipe", "pipe"]
14644
14809
  });
@@ -14648,10 +14813,10 @@ function isTracked(gitRoot, p) {
14648
14813
  }
14649
14814
  }
14650
14815
  function moveDir(gitRoot, from, to) {
14651
- fs14.mkdirSync(path18.dirname(to), { recursive: true });
14816
+ fs14.mkdirSync(path19.dirname(to), { recursive: true });
14652
14817
  if (isTracked(gitRoot, from)) {
14653
14818
  try {
14654
- execFileSync3("git", ["mv", path18.relative(gitRoot, from), path18.relative(gitRoot, to)], {
14819
+ execFileSync3("git", ["mv", path19.relative(gitRoot, from), path19.relative(gitRoot, to)], {
14655
14820
  cwd: gitRoot,
14656
14821
  stdio: ["pipe", "pipe", "pipe"]
14657
14822
  });
@@ -14668,12 +14833,12 @@ async function migrateCommand(_args) {
14668
14833
  console.error("Not inside a git repository.");
14669
14834
  process.exit(1);
14670
14835
  }
14671
- const rel = (p) => path18.relative(gitRoot, p);
14672
- const newWs = path18.join(gitRoot, WAYAI_LAYOUT.wsDir);
14673
- const legacyWs = path18.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
14674
- const legacyOrg = path18.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
14675
- const newHubs = path18.join(newWs, WAYAI_LAYOUT.hubsSubdir);
14676
- const newOrg = path18.join(newWs, WAYAI_LAYOUT.orgSubdir);
14836
+ const rel = (p) => path19.relative(gitRoot, p);
14837
+ const newWs = path19.join(gitRoot, WAYAI_LAYOUT.wsDir);
14838
+ const legacyWs = path19.join(gitRoot, WAYAI_LAYOUT.legacy.wsDir);
14839
+ const legacyOrg = path19.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
14840
+ const newHubs = path19.join(newWs, WAYAI_LAYOUT.hubsSubdir);
14841
+ const newOrg = path19.join(newWs, WAYAI_LAYOUT.orgSubdir);
14677
14842
  const hasLegacyWs = isDirectory(legacyWs);
14678
14843
  const hasLegacyOrg = isDirectory(legacyOrg);
14679
14844
  if (!hasLegacyWs && !hasLegacyOrg) {
@@ -15307,10 +15472,10 @@ var init_delete_history = __esm({
15307
15472
  // src/commands/sync-skills.ts
15308
15473
  var sync_skills_exports = {};
15309
15474
  __export(sync_skills_exports, {
15310
- parseArgs: () => parseArgs9,
15475
+ parseArgs: () => parseArgs10,
15311
15476
  syncSkillsCommand: () => syncSkillsCommand
15312
15477
  });
15313
- function parseArgs9(args2) {
15478
+ function parseArgs10(args2) {
15314
15479
  let connectionId;
15315
15480
  const idx = args2.indexOf("--connection-id");
15316
15481
  if (idx !== -1 && args2[idx + 1]) {
@@ -15333,7 +15498,7 @@ function printSyncResults(response) {
15333
15498
  }
15334
15499
  async function syncSkillsCommand(args2) {
15335
15500
  const hubId = resolveActiveHubId(args2);
15336
- const { connectionId } = parseArgs9(args2);
15501
+ const { connectionId } = parseArgs10(args2);
15337
15502
  requireRepoConfig();
15338
15503
  const { config, accessToken } = await requireAuth();
15339
15504
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
@@ -15358,10 +15523,10 @@ var init_sync_skills = __esm({
15358
15523
  // src/commands/sync-mcp.ts
15359
15524
  var sync_mcp_exports = {};
15360
15525
  __export(sync_mcp_exports, {
15361
- parseArgs: () => parseArgs10,
15526
+ parseArgs: () => parseArgs11,
15362
15527
  syncMcpCommand: () => syncMcpCommand
15363
15528
  });
15364
- function parseArgs10(args2) {
15529
+ function parseArgs11(args2) {
15365
15530
  let connection;
15366
15531
  for (let i = 0; i < args2.length; i++) {
15367
15532
  const arg = args2[i];
@@ -15373,7 +15538,7 @@ function parseArgs10(args2) {
15373
15538
  }
15374
15539
  async function syncMcpCommand(args2) {
15375
15540
  const hubId = resolveActiveHubId(args2);
15376
- const { connection } = parseArgs10(args2);
15541
+ const { connection } = parseArgs11(args2);
15377
15542
  if (!connection) {
15378
15543
  console.error("Usage: wayai sync-mcp --connection <name> [--hub <uuid|folder-name>]");
15379
15544
  console.error("");
@@ -15982,14 +16147,14 @@ __export(eval_capture_exports, {
15982
16147
  evalCaptureCommand: () => evalCaptureCommand
15983
16148
  });
15984
16149
  import * as fs15 from "fs";
15985
- import * as path19 from "path";
16150
+ import * as path20 from "path";
15986
16151
  import * as yaml7 from "js-yaml";
15987
16152
  function isValidSetName(name) {
15988
16153
  if (name.length === 0 || name === "." || name === "..") return false;
15989
16154
  if (name.startsWith(".")) return false;
15990
16155
  return !/[\/\\\0]/.test(name);
15991
16156
  }
15992
- function parseArgs11(args2) {
16157
+ function parseArgs12(args2) {
15993
16158
  if (args2.length === 0 || args2[0].startsWith("-")) {
15994
16159
  console.error("Usage: wayai eval capture <conversation_id> [--set <name>] [--name <eval_name>] [--instructions <text>]");
15995
16160
  process.exit(1);
@@ -16035,7 +16200,7 @@ function parseArgs11(args2) {
16035
16200
  }
16036
16201
  async function evalCaptureCommand(args2) {
16037
16202
  const hubId = resolveActiveHubId(args2);
16038
- const parsed = parseArgs11(args2);
16203
+ const parsed = parseArgs12(args2);
16039
16204
  const { config, accessToken } = await requireAuth();
16040
16205
  requireRepoConfig();
16041
16206
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
@@ -16048,15 +16213,15 @@ async function evalCaptureCommand(args2) {
16048
16213
  const setFolderName = targetSetName;
16049
16214
  const scenarioName = parsed.evalName ?? `Capture ${parsed.conversationId.slice(0, 8)}`;
16050
16215
  const slug = slugify(scenarioName);
16051
- const evalsDir = path19.join(hubFolder, "evals");
16052
- const targetDir = path19.join(evalsDir, setFolderName);
16053
- const targetPath = path19.join(targetDir, `${slug}.yaml`);
16054
- if (!targetPath.startsWith(evalsDir + path19.sep)) {
16055
- console.error(`Resolved path "${path19.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
16216
+ const evalsDir = path20.join(hubFolder, "evals");
16217
+ const targetDir = path20.join(evalsDir, setFolderName);
16218
+ const targetPath = path20.join(targetDir, `${slug}.yaml`);
16219
+ if (!targetPath.startsWith(evalsDir + path20.sep)) {
16220
+ console.error(`Resolved path "${path20.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
16056
16221
  process.exit(1);
16057
16222
  }
16058
16223
  if (fs15.existsSync(targetPath)) {
16059
- console.error(`File already exists: ${path19.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
16224
+ console.error(`File already exists: ${path20.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
16060
16225
  process.exit(1);
16061
16226
  }
16062
16227
  console.log("Resolving scenario set...");
@@ -16090,7 +16255,7 @@ async function evalCaptureCommand(args2) {
16090
16255
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
16091
16256
  fs15.mkdirSync(targetDir, { recursive: true });
16092
16257
  fs15.writeFileSync(targetPath, yaml7.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
16093
- const relPath = path19.relative(process.cwd(), targetPath);
16258
+ const relPath = path20.relative(process.cwd(), targetPath);
16094
16259
  console.log(`
16095
16260
  Wrote ${relPath}`);
16096
16261
  console.log("Run `wayai pull` to refresh the agent display name, then commit. The scenario is already on the platform.");
@@ -16112,7 +16277,7 @@ var journey_capture_exports = {};
16112
16277
  __export(journey_capture_exports, {
16113
16278
  journeyCaptureCommand: () => journeyCaptureCommand
16114
16279
  });
16115
- function parseArgs12(args2) {
16280
+ function parseArgs13(args2) {
16116
16281
  if (args2.length === 0 || args2[0].startsWith("-")) {
16117
16282
  console.error("Usage: wayai eval journey capture <conversation_id> [--name <journey_name>] [--instructions <text>]");
16118
16283
  process.exit(1);
@@ -16147,7 +16312,7 @@ function parseArgs12(args2) {
16147
16312
  }
16148
16313
  async function journeyCaptureCommand(args2) {
16149
16314
  const hubId = resolveActiveHubId(args2);
16150
- const parsed = parseArgs12(args2);
16315
+ const parsed = parseArgs13(args2);
16151
16316
  const { config, accessToken } = await requireAuth();
16152
16317
  requireRepoConfig();
16153
16318
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
@@ -16944,9 +17109,9 @@ var init_credential_utils = __esm({
16944
17109
  var create_credential_exports = {};
16945
17110
  __export(create_credential_exports, {
16946
17111
  createCredentialCommand: () => createCredentialCommand,
16947
- parseArgs: () => parseArgs13
17112
+ parseArgs: () => parseArgs14
16948
17113
  });
16949
- function parseArgs13(args2) {
17114
+ function parseArgs14(args2) {
16950
17115
  let name;
16951
17116
  let type;
16952
17117
  let orgId;
@@ -16974,7 +17139,7 @@ function parseArgs13(args2) {
16974
17139
  return { name, type, orgId, description, tags: splitTagRefs(tags), environment, stdin };
16975
17140
  }
16976
17141
  async function createCredentialCommand(args2) {
16977
- const parsed = parseArgs13(args2);
17142
+ const parsed = parseArgs14(args2);
16978
17143
  if (!parsed.name) {
16979
17144
  console.error("Missing required flag: --name <credential-name>");
16980
17145
  console.error('Usage: wayai create-credential --name "openai-key" --type "Bearer Token"');
@@ -17081,10 +17246,10 @@ var init_create_credential = __esm({
17081
17246
  // src/commands/update-credential.ts
17082
17247
  var update_credential_exports = {};
17083
17248
  __export(update_credential_exports, {
17084
- parseArgs: () => parseArgs14,
17249
+ parseArgs: () => parseArgs15,
17085
17250
  updateCredentialCommand: () => updateCredentialCommand
17086
17251
  });
17087
- function parseArgs14(args2) {
17252
+ function parseArgs15(args2) {
17088
17253
  let name;
17089
17254
  let rename;
17090
17255
  let orgId;
@@ -17117,7 +17282,7 @@ function parseArgs14(args2) {
17117
17282
  return { name, rename, orgId, description, tags: splitTagRefs(tags), hasTagFlag, environment, stdin, secretPrompt };
17118
17283
  }
17119
17284
  async function updateCredentialCommand(args2) {
17120
- const parsed = parseArgs14(args2);
17285
+ const parsed = parseArgs15(args2);
17121
17286
  if (!parsed.name) {
17122
17287
  console.error("Missing required flag: --name <credential-name>");
17123
17288
  console.error('Usage: wayai update-credential --name "my-key" --stdin');
@@ -17222,10 +17387,10 @@ var init_update_credential = __esm({
17222
17387
  // src/commands/set-connection-credential.ts
17223
17388
  var set_connection_credential_exports = {};
17224
17389
  __export(set_connection_credential_exports, {
17225
- parseArgs: () => parseArgs15,
17390
+ parseArgs: () => parseArgs16,
17226
17391
  setConnectionCredentialCommand: () => setConnectionCredentialCommand
17227
17392
  });
17228
- function parseArgs15(args2) {
17393
+ function parseArgs16(args2) {
17229
17394
  let connection;
17230
17395
  let orgCredential;
17231
17396
  let field;
@@ -17248,7 +17413,7 @@ function usage(msg) {
17248
17413
  }
17249
17414
  async function setConnectionCredentialCommand(args2) {
17250
17415
  const hubId = resolveActiveHubId(args2);
17251
- const parsed = parseArgs15(args2);
17416
+ const parsed = parseArgs16(args2);
17252
17417
  if (!parsed.connection) usage("Missing required flag: --connection <name|id>");
17253
17418
  const orgMode = !!parsed.orgCredential;
17254
17419
  const directMode = !!parsed.field;
@@ -17318,22 +17483,22 @@ var init_set_connection_credential = __esm({
17318
17483
 
17319
17484
  // src/lib/org-workspace.ts
17320
17485
  import * as fs16 from "fs";
17321
- import * as path20 from "path";
17486
+ import * as path21 from "path";
17322
17487
  import * as yaml8 from "js-yaml";
17323
17488
  function getOrgDir(gitRoot) {
17324
17489
  return resolveLayout(gitRoot).orgDir;
17325
17490
  }
17326
17491
  function orgManifestExists(orgDir) {
17327
- return fs16.existsSync(path20.join(orgDir, ORG_MANIFEST_NAME));
17492
+ return fs16.existsSync(path21.join(orgDir, ORG_MANIFEST_NAME));
17328
17493
  }
17329
17494
  function parseOrgResources(orgDir) {
17330
- const manifestPath = path20.join(orgDir, ORG_MANIFEST_NAME);
17495
+ const manifestPath = path21.join(orgDir, ORG_MANIFEST_NAME);
17331
17496
  let manifest = {};
17332
17497
  if (fs16.existsSync(manifestPath)) {
17333
17498
  manifest = yaml8.load(fs16.readFileSync(manifestPath, "utf-8")) ?? {};
17334
17499
  }
17335
17500
  const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
17336
- const resourcesDir = path20.join(orgDir, "resources");
17501
+ const resourcesDir = path21.join(orgDir, "resources");
17337
17502
  const resources = rawResources.map((res) => {
17338
17503
  const resource = { name: res.name };
17339
17504
  if (res.id) resource.id = res.id;
@@ -17345,7 +17510,7 @@ function parseOrgResources(orgDir) {
17345
17510
  if (res.environment) resource.environment = res.environment;
17346
17511
  if (Array.isArray(res.tags)) resource.tags = res.tags;
17347
17512
  if (Array.isArray(res.folders)) resource.folders = res.folders;
17348
- const resDir = path20.join(resourcesDir, slugify(resource.name));
17513
+ const resDir = path21.join(resourcesDir, slugify(resource.name));
17349
17514
  if (fs16.existsSync(resDir)) {
17350
17515
  const files = scanResourceFiles(resDir, "");
17351
17516
  if (files.length > 0) resource.files = files;
@@ -17362,21 +17527,21 @@ function writeOrgResources(orgDir, payload) {
17362
17527
  return rest;
17363
17528
  });
17364
17529
  fs16.writeFileSync(
17365
- path20.join(orgDir, ORG_MANIFEST_NAME),
17530
+ path21.join(orgDir, ORG_MANIFEST_NAME),
17366
17531
  yaml8.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
17367
17532
  "utf-8"
17368
17533
  );
17369
- const resourcesDir = path20.join(orgDir, "resources");
17534
+ const resourcesDir = path21.join(orgDir, "resources");
17370
17535
  const currentSlugs = /* @__PURE__ */ new Set();
17371
17536
  for (const resource of resources) {
17372
17537
  const resSlug = slugify(resource.name);
17373
17538
  currentSlugs.add(resSlug);
17374
- writeResourceFileTree(path20.join(resourcesDir, resSlug), resource.files || []);
17539
+ writeResourceFileTree(path21.join(resourcesDir, resSlug), resource.files || []);
17375
17540
  }
17376
17541
  if (fs16.existsSync(resourcesDir)) {
17377
17542
  for (const entry of fs16.readdirSync(resourcesDir, { withFileTypes: true })) {
17378
17543
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
17379
- fs16.rmSync(path20.join(resourcesDir, entry.name), { recursive: true, force: true });
17544
+ fs16.rmSync(path21.join(resourcesDir, entry.name), { recursive: true, force: true });
17380
17545
  }
17381
17546
  }
17382
17547
  }
@@ -17385,7 +17550,7 @@ async function downloadOrgBinaryFiles(orgDir, payload) {
17385
17550
  let count = 0;
17386
17551
  for (const resource of payload.resources ?? []) {
17387
17552
  if (!resource.files) continue;
17388
- const resDir = path20.join(orgDir, "resources", slugify(resource.name));
17553
+ const resDir = path21.join(orgDir, "resources", slugify(resource.name));
17389
17554
  count += await downloadBinaryFiles(resDir, resource.files);
17390
17555
  }
17391
17556
  if (count > 0) console.log(`Downloaded ${count} binary resource file(s).`);
@@ -17573,9 +17738,9 @@ var init_org = __esm({
17573
17738
  var list_exports = {};
17574
17739
  __export(list_exports, {
17575
17740
  listCommand: () => listCommand,
17576
- parseArgs: () => parseArgs16
17741
+ parseArgs: () => parseArgs17
17577
17742
  });
17578
- function parseArgs16(args2) {
17743
+ function parseArgs17(args2) {
17579
17744
  let orgId;
17580
17745
  let json = false;
17581
17746
  for (let i = 0; i < args2.length; i++) {
@@ -17588,7 +17753,7 @@ function parseArgs16(args2) {
17588
17753
  return { orgId, json };
17589
17754
  }
17590
17755
  async function listCommand(args2) {
17591
- const { orgId, json } = parseArgs16(args2);
17756
+ const { orgId, json } = parseArgs17(args2);
17592
17757
  const { config, accessToken } = await requireAuth();
17593
17758
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
17594
17759
  const { organizations } = await client.organizations();
@@ -17642,13 +17807,13 @@ var init_list = __esm({
17642
17807
 
17643
17808
  // src/lib/template-files.ts
17644
17809
  import * as fs17 from "fs";
17645
- import * as path21 from "path";
17810
+ import * as path22 from "path";
17646
17811
  function readDirToFileMap(rootDir) {
17647
17812
  const out = {};
17648
17813
  function walk(dir, prefix) {
17649
17814
  const entries = fs17.readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
17650
17815
  for (const entry of entries) {
17651
- const abs = path21.join(dir, entry.name);
17816
+ const abs = path22.join(dir, entry.name);
17652
17817
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
17653
17818
  if (entry.isDirectory()) walk(abs, rel);
17654
17819
  else if (entry.isFile()) out[rel] = fs17.readFileSync(abs, "utf-8");
@@ -17663,7 +17828,7 @@ function detectCollisions(targetDir, files) {
17663
17828
  if (!isSafeTemplatePath2(rel)) {
17664
17829
  throw new Error(`Refusing to inspect unsafe template path: ${rel}`);
17665
17830
  }
17666
- if (fs17.existsSync(path21.join(targetDir, rel))) collisions.push(rel);
17831
+ if (fs17.existsSync(path22.join(targetDir, rel))) collisions.push(rel);
17667
17832
  }
17668
17833
  return collisions.sort((a, b) => a.localeCompare(b));
17669
17834
  }
@@ -17673,8 +17838,8 @@ function writeFileMap(targetDir, files) {
17673
17838
  if (!isSafeTemplatePath2(rel)) {
17674
17839
  throw new Error(`Refusing to write unsafe template path: ${rel}`);
17675
17840
  }
17676
- const abs = path21.join(targetDir, rel);
17677
- fs17.mkdirSync(path21.dirname(abs), { recursive: true });
17841
+ const abs = path22.join(targetDir, rel);
17842
+ fs17.mkdirSync(path22.dirname(abs), { recursive: true });
17678
17843
  fs17.writeFileSync(abs, body, "utf-8");
17679
17844
  written.push(rel);
17680
17845
  }
@@ -17685,7 +17850,7 @@ function listDirRelFiles(dir) {
17685
17850
  function walk(d, prefix) {
17686
17851
  for (const entry of fs17.readdirSync(d, { withFileTypes: true })) {
17687
17852
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
17688
- if (entry.isDirectory()) walk(path21.join(d, entry.name), rel);
17853
+ if (entry.isDirectory()) walk(path22.join(d, entry.name), rel);
17689
17854
  else if (entry.isFile()) out.push(rel);
17690
17855
  }
17691
17856
  }
@@ -17712,7 +17877,7 @@ var templates_exports = {};
17712
17877
  __export(templates_exports, {
17713
17878
  templateCommand: () => templateCommand
17714
17879
  });
17715
- import * as path22 from "path";
17880
+ import * as path23 from "path";
17716
17881
  async function templateCommand(args2) {
17717
17882
  const [sub, ...rest] = args2;
17718
17883
  switch (sub) {
@@ -17828,8 +17993,8 @@ async function runTemplatePull(rest) {
17828
17993
  console.log(`Template "${slug}" has no WayAI slice${hint}`);
17829
17994
  return;
17830
17995
  }
17831
- const targetDir = path22.join(resolveLayout(gitRoot).hubsDir, slug);
17832
- const relTarget = path22.relative(gitRoot, targetDir);
17996
+ const targetDir = path23.join(resolveLayout(gitRoot).hubsDir, slug);
17997
+ const relTarget = path23.relative(gitRoot, targetDir);
17833
17998
  const collisions = detectCollisions(targetDir, detail.files);
17834
17999
  if (dryRun) {
17835
18000
  const planned = Object.keys(detail.files).sort((a, b) => a.localeCompare(b));
@@ -18004,7 +18169,7 @@ __export(admin_exports, {
18004
18169
  adminCommand: () => adminCommand
18005
18170
  });
18006
18171
  import * as fs18 from "fs";
18007
- import * as path23 from "path";
18172
+ import * as path24 from "path";
18008
18173
  import * as yaml10 from "js-yaml";
18009
18174
  async function adminCommand(args2) {
18010
18175
  const [group, ...afterGroup] = args2;
@@ -18383,10 +18548,10 @@ function loadTemplateManifest(slug) {
18383
18548
  console.error("Not in a git repository. Run from the tutorials/ authoring repo.");
18384
18549
  process.exit(1);
18385
18550
  }
18386
- const templateDir = path23.join(resolveTemplatesDir(gitRoot), slug);
18387
- const manifestPath = path23.join(templateDir, "template.yaml");
18551
+ const templateDir = path24.join(resolveTemplatesDir(gitRoot), slug);
18552
+ const manifestPath = path24.join(templateDir, "template.yaml");
18388
18553
  if (!fs18.existsSync(manifestPath)) {
18389
- console.error(`template.yaml not found at ${path23.relative(gitRoot, manifestPath)}`);
18554
+ console.error(`template.yaml not found at ${path24.relative(gitRoot, manifestPath)}`);
18390
18555
  process.exit(1);
18391
18556
  }
18392
18557
  let manifest;
@@ -18421,19 +18586,19 @@ async function runTemplatePush(positional) {
18421
18586
  const wayaiEntry = manifest.wayai.locales[locale];
18422
18587
  let wayaiFiles;
18423
18588
  if (wayaiEntry) {
18424
- const sliceDir = path23.resolve(templateDir, wayaiEntry.source);
18589
+ const sliceDir = path24.resolve(templateDir, wayaiEntry.source);
18425
18590
  if (!fs18.existsSync(sliceDir)) {
18426
18591
  console.error(`wayai source not found for ${locale}: ${wayaiEntry.source} (resolved ${sliceDir})`);
18427
18592
  process.exit(1);
18428
18593
  }
18429
18594
  if (!isInsideTemplateDir(sliceDir, templateDir)) {
18430
18595
  console.warn(
18431
- `Note: ${locale} source points at a live hub (${wayaiEntry.source}); it can go stale on a hub recreate. Run \`wayai admin template freeze ${slug} --locale ${locale} --from ${path23.basename(sliceDir)}\` to commit a stable frozen slice.`
18596
+ `Note: ${locale} source points at a live hub (${wayaiEntry.source}); it can go stale on a hub recreate. Run \`wayai admin template freeze ${slug} --locale ${locale} --from ${path24.basename(sliceDir)}\` to commit a stable frozen slice.`
18432
18597
  );
18433
18598
  }
18434
18599
  wayaiFiles = readDirToFileMap(sliceDir);
18435
18600
  }
18436
- const readmePath = path23.join(templateDir, `README.${locale}.md`);
18601
+ const readmePath = path24.join(templateDir, `README.${locale}.md`);
18437
18602
  const readmeMarkdown = fs18.existsSync(readmePath) ? fs18.readFileSync(readmePath, "utf-8") : "";
18438
18603
  locales[locale] = {
18439
18604
  readmeMarkdown,
@@ -18445,9 +18610,9 @@ async function runTemplatePush(positional) {
18445
18610
  }
18446
18611
  let hero;
18447
18612
  if (manifest.hero) {
18448
- const heroPath = path23.resolve(templateDir, manifest.hero);
18613
+ const heroPath = path24.resolve(templateDir, manifest.hero);
18449
18614
  if (fs18.existsSync(heroPath)) {
18450
- hero = { filename: path23.basename(heroPath), dataBase64: fs18.readFileSync(heroPath).toString("base64") };
18615
+ hero = { filename: path24.basename(heroPath), dataBase64: fs18.readFileSync(heroPath).toString("base64") };
18451
18616
  } else {
18452
18617
  console.error(`Warning: hero not found at ${manifest.hero}; skipping.`);
18453
18618
  }
@@ -18473,7 +18638,7 @@ async function runTemplatePush(positional) {
18473
18638
  }
18474
18639
  }
18475
18640
  function isInsideTemplateDir(dest, templateDir) {
18476
- return dest === templateDir || dest.startsWith(templateDir + path23.sep);
18641
+ return dest === templateDir || dest.startsWith(templateDir + path24.sep);
18477
18642
  }
18478
18643
  async function runTemplateFreeze(positional, flagArgs) {
18479
18644
  const slug = positional[0];
@@ -18534,7 +18699,7 @@ async function runTemplateFreeze(positional, flagArgs) {
18534
18699
  process.exit(1);
18535
18700
  }
18536
18701
  const entry = manifest.wayai.locales[targetLocale];
18537
- const dest = path23.resolve(templateDir, entry.source);
18702
+ const dest = path24.resolve(templateDir, entry.source);
18538
18703
  if (!isInsideTemplateDir(dest, templateDir) || dest === templateDir) {
18539
18704
  console.error(
18540
18705
  `source for "${targetLocale}" must be a committed path inside ${slug}/ (e.g. "./${targetLocale}/slice"), but resolved to:
@@ -18553,7 +18718,7 @@ Set it to a subdir of the template folder and re-run freeze.`
18553
18718
  const hubs = scanWorkspaceHubs(hubsDir);
18554
18719
  if (hubs.length) {
18555
18720
  console.error("Available hubs:");
18556
- for (const h of hubs) console.error(` ${path23.basename(h.hubFolder)} (${h.hubId})`);
18721
+ for (const h of hubs) console.error(` ${path24.basename(h.hubFolder)} (${h.hubId})`);
18557
18722
  }
18558
18723
  console.error(`
18559
18724
  Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`);
@@ -18561,12 +18726,12 @@ Pass --from <folder|uuid>${selector ? "." : " or run `wayai use <hub>` first."}`
18561
18726
  }
18562
18727
  const liveFiles = readDirToFileMap(hubFolder);
18563
18728
  if (Object.keys(liveFiles).length === 0) {
18564
- console.error(`Source hub folder has no files to freeze: ${path23.relative(gitRoot, hubFolder)}`);
18729
+ console.error(`Source hub folder has no files to freeze: ${path24.relative(gitRoot, hubFolder)}`);
18565
18730
  process.exit(1);
18566
18731
  }
18567
18732
  const sanitized = sanitizeWayaiSliceFiles(liveFiles);
18568
- const relDest = path23.relative(gitRoot, dest);
18569
- const relSrc = path23.relative(gitRoot, hubFolder);
18733
+ const relDest = path24.relative(gitRoot, dest);
18734
+ const relSrc = path24.relative(gitRoot, hubFolder);
18570
18735
  if (dryRun) {
18571
18736
  const existing = new Set(listDirRelFiles(dest));
18572
18737
  const nextKeys = new Set(Object.keys(sanitized));
@@ -18626,7 +18791,7 @@ async function runTemplatePull2(positional, flagArgs) {
18626
18791
  console.log(JSON.stringify(detail, null, 2));
18627
18792
  return;
18628
18793
  }
18629
- const target = outDir ? path23.resolve(outDir) : path23.join(process.cwd(), "template-store-pull", slug);
18794
+ const target = outDir ? path24.resolve(outDir) : path24.join(process.cwd(), "template-store-pull", slug);
18630
18795
  const written = writeFileMap(target, detail.files);
18631
18796
  console.log(`Pulled "${slug}" from the store \u2192 ${target} (${written.length} file${written.length === 1 ? "" : "s"}).`);
18632
18797
  const uses = detail.uses ?? [];
@@ -18649,13 +18814,13 @@ async function runSkillInstall(positional) {
18649
18814
  throw err;
18650
18815
  }
18651
18816
  const root = findGitRoot() ?? process.cwd();
18652
- const present = HARNESS_SKILL_DIRS.filter((dir) => fs18.existsSync(path23.join(root, dir)));
18817
+ const present = HARNESS_SKILL_DIRS.filter((dir) => fs18.existsSync(path24.join(root, dir)));
18653
18818
  const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
18654
18819
  const fileCount = Object.keys(res.files).length;
18655
18820
  const relDirs = targets.map((harness) => {
18656
- const targetDir = path23.join(root, harness, "skills", name);
18821
+ const targetDir = path24.join(root, harness, "skills", name);
18657
18822
  writeFileMap(targetDir, res.files);
18658
- return `${path23.relative(root, targetDir)}/`;
18823
+ return `${path24.relative(root, targetDir)}/`;
18659
18824
  });
18660
18825
  console.log(`Installed skill "${name}" (${fileCount} file${fileCount === 1 ? "" : "s"}) \u2192 ${relDirs.join(", ")}`);
18661
18826
  console.log("Reload your agent (e.g. restart Claude Code) to pick up the skill.");
@@ -19549,6 +19714,9 @@ function friendlyHint(err) {
19549
19714
  return "Couldn't reach WayAI \u2014 check your network connection and try again.";
19550
19715
  }
19551
19716
  if (!(err instanceof ApiError)) return null;
19717
+ if (err.body.includes("FREE_PLAN_RESTRICTION")) {
19718
+ return "Publishing to production requires a paid plan. Upgrade your organization to publish or sync.";
19719
+ }
19552
19720
  const { status } = err;
19553
19721
  if (status === 401) return "Your session may have expired. Run `wayai login` to re-authenticate.";
19554
19722
  if (status === 402) return "Quota exceeded for your plan. Review usage or upgrade your plan.";
@@ -19785,6 +19953,14 @@ async function main() {
19785
19953
  await relabelCommand2(args);
19786
19954
  break;
19787
19955
  }
19956
+ // `sync` is a bare alias of `publish` (distinct from `sync-skills` / `sync-mcp`,
19957
+ // which are exact-string cases). Both promote preview → production.
19958
+ case "publish":
19959
+ case "sync": {
19960
+ const { publishCommand: publishCommand2 } = await Promise.resolve().then(() => (init_publish(), publish_exports));
19961
+ await publishCommand2(args);
19962
+ break;
19963
+ }
19788
19964
  case "use": {
19789
19965
  const { useCommand: useCommand2 } = await Promise.resolve().then(() => (init_use(), use_exports));
19790
19966
  await useCommand2(args);
@@ -19935,6 +20111,8 @@ Commands:
19935
20111
  diff Dry-run diff of local files vs the platform (--production diffs vs the linked production hub)
19936
20112
  replicate Clone a hub into a new sibling preview (--label to name it)
19937
20113
  relabel Set a preview hub's label (--clear to remove it)
20114
+ publish Promote the preview to production (first publish clones it; subsequent runs sync changes)
20115
+ sync Alias of \`publish\` (not to be confused with sync-skills / sync-mcp)
19938
20116
  org create Create a new organization (you become its owner)
19939
20117
  org <pull|push|diff> Sync org-scoped resources as code (wayai-ws/org/)
19940
20118
  use <hub> Bind this worktree to a specific hub (UUID or folder name)
@@ -19960,7 +20138,7 @@ Commands:
19960
20138
 
19961
20139
  Flags:
19962
20140
  --yes, -y Skip confirmation prompts (useful for CI and scripting)
19963
- --hub <uuid|name> Target hub when the workspace has more than one (push, pull, diff, replicate, relabel)
20141
+ --hub <uuid|name> Target hub when the workspace has more than one (push, pull, diff, replicate, relabel, publish)
19964
20142
  --production Diff local preview files against the linked production hub (diff)
19965
20143
  --label <name> Preview label \u2014 for the new sibling (replicate) or a brand-new hub (push on create)
19966
20144
  --clear Remove the preview label (relabel)