@wayai/cli 0.3.107 → 0.3.109

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
@@ -11185,6 +11185,20 @@ var init_init = __esm({
11185
11185
  // src/lib/fs-safety.ts
11186
11186
  import * as fs9 from "fs";
11187
11187
  import * as path11 from "path";
11188
+ function fileHasBytes(abs, data) {
11189
+ let st;
11190
+ try {
11191
+ st = fs9.lstatSync(abs);
11192
+ } catch {
11193
+ return false;
11194
+ }
11195
+ if (!st.isFile() || st.isSymbolicLink()) return false;
11196
+ try {
11197
+ return fs9.readFileSync(abs).equals(data);
11198
+ } catch {
11199
+ return false;
11200
+ }
11201
+ }
11188
11202
  function realpathContained(root, abs) {
11189
11203
  let realRoot;
11190
11204
  let realAbs;
@@ -14838,13 +14852,13 @@ function assertResourcePayloadFitsWithoutPruning(payload, cause) {
14838
14852
  }
14839
14853
  }
14840
14854
  }
14841
- function writeResourceFileTree(resDir, files, root) {
14855
+ function writeResourceFileTree(resDir, files, root, log) {
14842
14856
  if (!ensureRealSubdirNoSymlink(root, resDir, files.length > 0)) {
14843
14857
  console.warn(` Warning: skipping resource dir (not reachable inside the workspace without a symlink)`);
14844
14858
  return;
14845
14859
  }
14846
14860
  if (files.length === 0) {
14847
- if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set());
14861
+ if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set(), root, log);
14848
14862
  return;
14849
14863
  }
14850
14864
  const writtenPaths = /* @__PURE__ */ new Set();
@@ -14855,34 +14869,33 @@ function writeResourceFileTree(resDir, files, root) {
14855
14869
  continue;
14856
14870
  }
14857
14871
  writtenPaths.add(file.path);
14858
- if (file.content !== void 0) {
14859
- if (!writeFileNoFollow(resDir, filePath, Buffer.from(file.content, "utf-8"))) {
14860
- console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
14861
- writtenPaths.delete(file.path);
14862
- }
14863
- } else if (file.content_base64) {
14864
- if (!writeFileNoFollow(resDir, filePath, Buffer.from(file.content_base64, "base64"))) {
14865
- console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
14866
- writtenPaths.delete(file.path);
14867
- }
14872
+ const buf = file.content !== void 0 ? Buffer.from(file.content, "utf-8") : file.content_base64 ? Buffer.from(file.content_base64, "base64") : void 0;
14873
+ if (buf === void 0) continue;
14874
+ if (fileHasBytes(filePath, buf)) continue;
14875
+ if (writeFileNoFollow(resDir, filePath, buf)) {
14876
+ log?.changed.push(path12.relative(root, filePath));
14877
+ } else {
14878
+ console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
14879
+ writtenPaths.delete(file.path);
14868
14880
  }
14869
14881
  }
14870
- if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths);
14882
+ if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths, root, log);
14871
14883
  }
14872
- function cleanOrphanFiles(dir, prefix, writtenPaths) {
14884
+ function cleanOrphanFiles(dir, prefix, writtenPaths, root, log) {
14873
14885
  const entries = fs10.readdirSync(dir, { withFileTypes: true });
14874
14886
  for (const entry of entries) {
14875
14887
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
14876
14888
  const fullPath = path12.join(dir, entry.name);
14877
14889
  if (entry.isDirectory()) {
14878
- cleanOrphanFiles(fullPath, relPath, writtenPaths);
14890
+ cleanOrphanFiles(fullPath, relPath, writtenPaths, root, log);
14879
14891
  if (fs10.readdirSync(fullPath).length === 0) fs10.rmdirSync(fullPath);
14880
14892
  } else if (!writtenPaths.has(relPath)) {
14881
14893
  fs10.unlinkSync(fullPath);
14894
+ if (log && root) log.removed.push(path12.relative(root, fullPath));
14882
14895
  }
14883
14896
  }
14884
14897
  }
14885
- async function downloadBinaryFiles(resDir, files, root) {
14898
+ async function downloadBinaryFiles(resDir, files, root, log) {
14886
14899
  if (!ensureRealSubdirNoSymlink(root, resDir, true)) {
14887
14900
  console.warn(` Warning: skipping resource downloads (dir not reachable inside the workspace without a symlink)`);
14888
14901
  return 0;
@@ -14896,14 +14909,26 @@ async function downloadBinaryFiles(resDir, files, root) {
14896
14909
  console.warn(` Warning: skipping ${file.path} (path traversal detected)`);
14897
14910
  continue;
14898
14911
  }
14912
+ if (file.hash) {
14913
+ try {
14914
+ const st = fs10.lstatSync(filePath);
14915
+ if (st.isFile() && !st.isSymbolicLink() && computeHash(fs10.readFileSync(filePath)) === file.hash) continue;
14916
+ } catch {
14917
+ }
14918
+ }
14899
14919
  const response = await fetch(file.download_url);
14900
14920
  if (!response.ok) {
14901
14921
  console.warn(` Warning: failed to download ${file.path} (${response.status})`);
14902
14922
  continue;
14903
14923
  }
14904
14924
  const buffer = Buffer.from(await response.arrayBuffer());
14905
- if (writeFileNoFollow(resDir, filePath, buffer)) count++;
14906
- else console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
14925
+ if (fileHasBytes(filePath, buffer)) continue;
14926
+ if (writeFileNoFollow(resDir, filePath, buffer)) {
14927
+ count++;
14928
+ log?.changed.push(path12.relative(root, filePath));
14929
+ } else {
14930
+ console.warn(` Warning: skipping ${file.path} (destination escapes the resource folder via a symlink)`);
14931
+ }
14907
14932
  } catch (err) {
14908
14933
  console.warn(` Warning: failed to download ${file.path} (${err instanceof Error ? err.message : String(err)})`);
14909
14934
  }
@@ -15082,12 +15107,12 @@ function rewriteEvalAttachmentsToLocalPaths(payload) {
15082
15107
  }
15083
15108
  async function downloadEvalAttachments(hubFolder, downloads) {
15084
15109
  const attachDir = path13.resolve(hubFolder, ATTACHMENTS_DIR);
15085
- let written = 0;
15110
+ const result = { written: 0, changed: [], removed: [] };
15086
15111
  if (!ensureRealSubdirNoSymlink(hubFolder, attachDir, downloads.length > 0)) {
15087
15112
  console.warn(` Warning: ${ATTACHMENTS_DIR}/ is not reachable inside the hub folder without a symlink; skipping attachment sync.`);
15088
- return written;
15113
+ return result;
15089
15114
  }
15090
- if (!fs11.existsSync(attachDir)) return written;
15115
+ if (!fs11.existsSync(attachDir)) return result;
15091
15116
  for (const dl of downloads) {
15092
15117
  const abs = path13.resolve(hubFolder, dl.relPath);
15093
15118
  if (abs !== attachDir && !abs.startsWith(attachDir + path13.sep)) continue;
@@ -15105,7 +15130,10 @@ async function downloadEvalAttachments(hubFolder, downloads) {
15105
15130
  console.warn(` Warning: failed to download ${dl.relPath} (HTTP ${res.status})`);
15106
15131
  continue;
15107
15132
  }
15108
- if (writeFileNoFollow(hubFolder, abs, Buffer.from(await res.arrayBuffer()))) written++;
15133
+ if (writeFileNoFollow(hubFolder, abs, Buffer.from(await res.arrayBuffer()))) {
15134
+ result.written++;
15135
+ result.changed.push(dl.relPath);
15136
+ }
15109
15137
  } catch (err) {
15110
15138
  console.warn(` Warning: failed to download ${dl.relPath}: ${err instanceof Error ? err.message : String(err)}`);
15111
15139
  }
@@ -15114,9 +15142,12 @@ async function downloadEvalAttachments(hubFolder, downloads) {
15114
15142
  for (const name of fs11.readdirSync(attachDir)) {
15115
15143
  const abs = path13.join(attachDir, name);
15116
15144
  const st = fs11.lstatSync(abs);
15117
- if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) fs11.rmSync(abs);
15145
+ if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) {
15146
+ fs11.rmSync(abs);
15147
+ result.removed.push(path13.relative(hubFolder, abs));
15148
+ }
15118
15149
  }
15119
- return written;
15150
+ return result;
15120
15151
  }
15121
15152
  var ATTACHMENTS_DIR;
15122
15153
  var init_eval_attachments = __esm({
@@ -15536,6 +15567,15 @@ var init_diff_display = __esm({
15536
15567
  import * as fs13 from "fs";
15537
15568
  import * as path15 from "path";
15538
15569
  import * as yaml5 from "js-yaml";
15570
+ function writeFileIfChanged(hubFolder, absPath, content, log) {
15571
+ const buf = Buffer.from(content, "utf-8");
15572
+ if (fileHasBytes(absPath, buf)) return;
15573
+ if (writeFileNoFollow(hubFolder, absPath, buf)) {
15574
+ log.changed.push(path15.relative(hubFolder, absPath));
15575
+ } else {
15576
+ console.warn(` Warning: skipping ${path15.relative(hubFolder, absPath)} (destination escapes the hub folder via a symlink)`);
15577
+ }
15578
+ }
15539
15579
  function buildEvalYamlObject(evalEntry, slug) {
15540
15580
  const out = {};
15541
15581
  if (evalEntry.id) out.id = evalEntry.id;
@@ -15570,6 +15610,7 @@ function buildEvalYamlObject(evalEntry, slug) {
15570
15610
  return out;
15571
15611
  }
15572
15612
  function writeHubFolder(hubFolder, payload, options = {}) {
15613
+ const log = { changed: [], removed: [] };
15573
15614
  const agentsDir = path15.join(hubFolder, "agents");
15574
15615
  if (!fs13.existsSync(hubFolder)) {
15575
15616
  fs13.mkdirSync(hubFolder, { recursive: true });
@@ -15580,38 +15621,48 @@ function writeHubFolder(hubFolder, payload, options = {}) {
15580
15621
  const yamlPayload = buildYamlPayload(payload);
15581
15622
  const agentFiles = extractAgentFiles(payload);
15582
15623
  const yamlContent = yaml5.dump(yamlPayload, YAML_DUMP_OPTIONS);
15583
- fs13.writeFileSync(path15.join(hubFolder, "hub.yaml"), yamlContent, "utf-8");
15624
+ writeFileIfChanged(hubFolder, path15.join(hubFolder, "hub.yaml"), yamlContent, log);
15584
15625
  if (options.seedAgentContext ?? true) {
15585
- writeIfAbsent(path15.join(hubFolder, "AGENTS.md"), perHubAgentsMd(path15.basename(hubFolder)));
15586
- writeIfAbsent(path15.join(hubFolder, "CLAUDE.md"), CLAUDE_MD_SHIM);
15626
+ for (const seeded of [
15627
+ writeIfAbsent(path15.join(hubFolder, "AGENTS.md"), perHubAgentsMd(path15.basename(hubFolder))),
15628
+ writeIfAbsent(path15.join(hubFolder, "CLAUDE.md"), CLAUDE_MD_SHIM)
15629
+ ]) {
15630
+ if (seeded) log.changed.push(seeded);
15631
+ }
15587
15632
  }
15588
15633
  const oldYamlPath = path15.join(hubFolder, "wayai.yaml");
15589
15634
  if (fs13.existsSync(oldYamlPath)) {
15590
15635
  fs13.unlinkSync(oldYamlPath);
15636
+ log.removed.push(path15.relative(hubFolder, oldYamlPath));
15591
15637
  }
15592
- const yamlSlugs = writeAgentYamlFiles(agentsDir, payload.agents || []);
15638
+ const yamlSlugs = writeAgentYamlFiles(hubFolder, agentsDir, payload.agents || [], log);
15593
15639
  const mdSlugs = /* @__PURE__ */ new Set();
15594
15640
  for (const { slug, content } of agentFiles) {
15595
15641
  mdSlugs.add(slug);
15596
- fs13.writeFileSync(path15.join(agentsDir, `${slug}.md`), content, "utf-8");
15642
+ writeFileIfChanged(hubFolder, path15.join(agentsDir, `${slug}.md`), content, log);
15597
15643
  }
15598
15644
  const existingFiles = fs13.readdirSync(agentsDir);
15599
15645
  for (const file of existingFiles) {
15600
15646
  if (file.endsWith(".yaml")) {
15601
15647
  const slug = file.slice(0, -5);
15602
15648
  if (!yamlSlugs.has(slug)) {
15603
- fs13.unlinkSync(path15.join(agentsDir, file));
15649
+ const orphan = path15.join(agentsDir, file);
15650
+ fs13.unlinkSync(orphan);
15651
+ log.removed.push(path15.relative(hubFolder, orphan));
15604
15652
  }
15605
15653
  } else if (file.endsWith(".md")) {
15606
15654
  const slug = file.slice(0, -3);
15607
15655
  if (!mdSlugs.has(slug)) {
15608
- fs13.unlinkSync(path15.join(agentsDir, file));
15656
+ const orphan = path15.join(agentsDir, file);
15657
+ fs13.unlinkSync(orphan);
15658
+ log.removed.push(path15.relative(hubFolder, orphan));
15609
15659
  }
15610
15660
  }
15611
15661
  }
15612
- writeResourceFiles(hubFolder, payload.resources || []);
15613
- writeEvalYamlFiles(hubFolder, payload.evals || []);
15614
- writeJourneyYamlFiles(hubFolder, payload.journeys || []);
15662
+ writeResourceFiles(hubFolder, payload.resources || [], log);
15663
+ writeEvalYamlFiles(hubFolder, payload.evals || [], log);
15664
+ writeJourneyYamlFiles(hubFolder, payload.journeys || [], log);
15665
+ return log;
15615
15666
  }
15616
15667
  function setPreviewLabelInHubYaml(hubFolder, label) {
15617
15668
  const yamlPath = resolveHubYamlPath(hubFolder);
@@ -15657,7 +15708,7 @@ function buildYamlPayload(payload) {
15657
15708
  }
15658
15709
  return result;
15659
15710
  }
15660
- function writeAgentYamlFiles(agentsDir, agents) {
15711
+ function writeAgentYamlFiles(hubFolder, agentsDir, agents, log) {
15661
15712
  const slugs = /* @__PURE__ */ new Set();
15662
15713
  for (const agent of agents) {
15663
15714
  const slug = slugify(agent.name);
@@ -15665,7 +15716,7 @@ function writeAgentYamlFiles(agentsDir, agents) {
15665
15716
  const entry = { ...agent };
15666
15717
  delete entry.instructions;
15667
15718
  const yamlContent = yaml5.dump(entry, YAML_DUMP_OPTIONS);
15668
- fs13.writeFileSync(path15.join(agentsDir, `${slug}.yaml`), yamlContent, "utf-8");
15719
+ writeFileIfChanged(hubFolder, path15.join(agentsDir, `${slug}.yaml`), yamlContent, log);
15669
15720
  }
15670
15721
  return slugs;
15671
15722
  }
@@ -15681,11 +15732,11 @@ function extractAgentFiles(payload) {
15681
15732
  }
15682
15733
  return files;
15683
15734
  }
15684
- function writeEvalYamlFiles(hubFolder, evals) {
15735
+ function writeEvalYamlFiles(hubFolder, evals, log) {
15685
15736
  const evalsDir = path15.join(hubFolder, "evals");
15686
15737
  if (evals.length === 0) {
15687
15738
  if (fs13.existsSync(evalsDir)) {
15688
- cleanEvalOrphans(evalsDir, /* @__PURE__ */ new Set());
15739
+ cleanEvalOrphans(hubFolder, evalsDir, /* @__PURE__ */ new Set(), log);
15689
15740
  try {
15690
15741
  if (fs13.readdirSync(evalsDir).length === 0) fs13.rmdirSync(evalsDir);
15691
15742
  } catch {
@@ -15713,18 +15764,19 @@ function writeEvalYamlFiles(hubFolder, evals) {
15713
15764
  }
15714
15765
  }
15715
15766
  const yamlContent = yaml5.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
15716
- fs13.writeFileSync(targetPath, yamlContent, "utf-8");
15767
+ writeFileIfChanged(hubFolder, targetPath, yamlContent, log);
15717
15768
  writtenRelPaths.add(relPath);
15718
15769
  }
15719
- cleanEvalOrphans(evalsDir, writtenRelPaths);
15770
+ cleanEvalOrphans(hubFolder, evalsDir, writtenRelPaths, log);
15720
15771
  }
15721
- function cleanEvalOrphans(evalsDir, writtenRelPaths) {
15772
+ function cleanEvalOrphans(hubFolder, evalsDir, writtenRelPaths, log) {
15722
15773
  const entries = fs13.readdirSync(evalsDir, { withFileTypes: true });
15723
15774
  for (const entry of entries) {
15724
15775
  const fullPath = path15.join(evalsDir, entry.name);
15725
15776
  if (entry.isFile()) {
15726
15777
  if (entry.name.endsWith(".yaml") && !writtenRelPaths.has(entry.name)) {
15727
15778
  fs13.unlinkSync(fullPath);
15779
+ log.removed.push(path15.relative(hubFolder, fullPath));
15728
15780
  }
15729
15781
  continue;
15730
15782
  }
@@ -15735,7 +15787,9 @@ function cleanEvalOrphans(evalsDir, writtenRelPaths) {
15735
15787
  if (sub.isFile() && sub.name.endsWith(".yaml")) {
15736
15788
  const relPath = `${setName}/${sub.name}`;
15737
15789
  if (!writtenRelPaths.has(relPath)) {
15738
- fs13.unlinkSync(path15.join(fullPath, sub.name));
15790
+ const orphan = path15.join(fullPath, sub.name);
15791
+ fs13.unlinkSync(orphan);
15792
+ log.removed.push(path15.relative(hubFolder, orphan));
15739
15793
  }
15740
15794
  }
15741
15795
  }
@@ -15776,11 +15830,11 @@ function buildJourneyYamlObject(journeyEntry, slug) {
15776
15830
  }
15777
15831
  return out;
15778
15832
  }
15779
- function writeJourneyYamlFiles(hubFolder, journeys) {
15833
+ function writeJourneyYamlFiles(hubFolder, journeys, log) {
15780
15834
  const journeysDir = path15.join(hubFolder, "journeys");
15781
15835
  if (journeys.length === 0) {
15782
15836
  if (fs13.existsSync(journeysDir)) {
15783
- cleanJourneyOrphans(journeysDir, /* @__PURE__ */ new Set());
15837
+ cleanJourneyOrphans(hubFolder, journeysDir, /* @__PURE__ */ new Set(), log);
15784
15838
  try {
15785
15839
  if (fs13.readdirSync(journeysDir).length === 0) fs13.rmdirSync(journeysDir);
15786
15840
  } catch {
@@ -15797,33 +15851,37 @@ function writeJourneyYamlFiles(hubFolder, journeys) {
15797
15851
  const slug = ensureUniqueSlug(slugify(journeyEntry.name), usedSlugs);
15798
15852
  const fileName = `${slug}.yaml`;
15799
15853
  const yamlContent = yaml5.dump(buildJourneyYamlObject(journeyEntry, slug), YAML_DUMP_OPTIONS);
15800
- fs13.writeFileSync(path15.join(journeysDir, fileName), yamlContent, "utf-8");
15854
+ writeFileIfChanged(hubFolder, path15.join(journeysDir, fileName), yamlContent, log);
15801
15855
  writtenFiles.add(fileName);
15802
15856
  }
15803
- cleanJourneyOrphans(journeysDir, writtenFiles);
15857
+ cleanJourneyOrphans(hubFolder, journeysDir, writtenFiles, log);
15804
15858
  }
15805
- function cleanJourneyOrphans(journeysDir, writtenFiles) {
15859
+ function cleanJourneyOrphans(hubFolder, journeysDir, writtenFiles, log) {
15806
15860
  const entries = fs13.readdirSync(journeysDir, { withFileTypes: true });
15807
15861
  for (const entry of entries) {
15808
15862
  if (entry.isFile() && entry.name.endsWith(".yaml") && !writtenFiles.has(entry.name)) {
15809
- fs13.unlinkSync(path15.join(journeysDir, entry.name));
15863
+ const orphan = path15.join(journeysDir, entry.name);
15864
+ fs13.unlinkSync(orphan);
15865
+ log.removed.push(path15.relative(hubFolder, orphan));
15810
15866
  }
15811
15867
  }
15812
15868
  }
15813
- function writeResourceFiles(hubFolder, resources) {
15869
+ function writeResourceFiles(hubFolder, resources, log) {
15814
15870
  const resourcesDir = path15.join(hubFolder, "resources");
15815
15871
  const currentSlugs = /* @__PURE__ */ new Set();
15816
15872
  for (const resource of resources) {
15817
15873
  const resSlug = slugify(resource.name);
15818
15874
  currentSlugs.add(resSlug);
15819
15875
  const resDir = path15.join(resourcesDir, resSlug);
15820
- writeResourceFileTree(resDir, resource.files || [], hubFolder);
15876
+ writeResourceFileTree(resDir, resource.files || [], hubFolder, log);
15821
15877
  }
15822
15878
  if (fs13.existsSync(resourcesDir)) {
15823
15879
  const existingDirs = fs13.readdirSync(resourcesDir, { withFileTypes: true });
15824
15880
  for (const entry of existingDirs) {
15825
15881
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
15826
- fs13.rmSync(path15.join(resourcesDir, entry.name), { recursive: true, force: true });
15882
+ const orphanDir = path15.join(resourcesDir, entry.name);
15883
+ fs13.rmSync(orphanDir, { recursive: true, force: true });
15884
+ log.removed.push(`${path15.relative(hubFolder, orphanDir)}/`);
15827
15885
  }
15828
15886
  }
15829
15887
  }
@@ -15837,6 +15895,7 @@ var init_yaml_writer = __esm({
15837
15895
  init_resource_files();
15838
15896
  init_eval_attachments();
15839
15897
  init_workspace_files();
15898
+ init_fs_safety();
15840
15899
  YAML_DUMP_OPTIONS = {
15841
15900
  lineWidth: -1,
15842
15901
  // don't wrap lines
@@ -15859,22 +15918,28 @@ async function materializeHubFolder(hubFolder, payload, options = {}) {
15859
15918
  }
15860
15919
  materialized.add(payload);
15861
15920
  const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(payload);
15862
- writeHubFolder(hubFolder, payload, options);
15863
- await downloadBinaryResourceFiles(hubFolder, payload);
15864
- const attachmentsWritten = await downloadEvalAttachments(hubFolder, attachmentDownloads);
15865
- return { attachmentsWritten };
15921
+ const writeLog = writeHubFolder(hubFolder, payload, options);
15922
+ const resourceDownloads = await downloadBinaryResourceFiles(hubFolder, payload);
15923
+ const attachments = await downloadEvalAttachments(hubFolder, attachmentDownloads);
15924
+ return {
15925
+ attachmentsWritten: attachments.written,
15926
+ changed: [...writeLog.changed, ...resourceDownloads.changed, ...attachments.changed],
15927
+ removed: [...writeLog.removed, ...attachments.removed]
15928
+ };
15866
15929
  }
15867
15930
  async function downloadBinaryResourceFiles(hubFolder, payload) {
15868
15931
  const resources = payload.resources || [];
15932
+ const log = { changed: [], removed: [] };
15869
15933
  let downloadCount = 0;
15870
15934
  for (const resource of resources) {
15871
15935
  if (!resource.files) continue;
15872
15936
  const resDir = path16.join(hubFolder, "resources", slugify(resource.name));
15873
- downloadCount += await downloadBinaryFiles(resDir, resource.files, hubFolder);
15937
+ downloadCount += await downloadBinaryFiles(resDir, resource.files, hubFolder, log);
15874
15938
  }
15875
15939
  if (downloadCount > 0) {
15876
15940
  console.log(`Downloaded ${downloadCount} binary resource file(s).`);
15877
15941
  }
15942
+ return { changed: log.changed };
15878
15943
  }
15879
15944
  var materialized;
15880
15945
  var init_hub_materializer = __esm({
@@ -15888,207 +15953,13 @@ var init_hub_materializer = __esm({
15888
15953
  }
15889
15954
  });
15890
15955
 
15891
- // src/commands/pull.ts
15892
- var pull_exports = {};
15893
- __export(pull_exports, {
15894
- connectionsEqual: () => connectionsEqual,
15895
- pullCommand: () => pullCommand,
15896
- resolveHubTarget: () => resolveHubTarget,
15897
- writeProductionMirror: () => writeProductionMirror
15898
- });
15899
- import * as fs14 from "fs";
15900
- import * as path17 from "path";
15901
- function parseArgs4(args2) {
15902
- let autoConfirm = false;
15903
- let hubSelector;
15904
- for (let i = 0; i < args2.length; i++) {
15905
- const arg = args2[i];
15906
- if (arg === "--yes" || arg === "-y") {
15907
- autoConfirm = true;
15908
- } else if (arg === "--hub" && args2[i + 1]) {
15909
- hubSelector = args2[++i];
15910
- }
15911
- }
15912
- return { autoConfirm, hubSelector };
15913
- }
15914
- function resolveHubTarget(workspaceDir, selector) {
15915
- const wsLabel = hubsDirLabel(findGitRoot());
15916
- const allHubs = scanWorkspaceHubs(workspaceDir);
15917
- if (selector) {
15918
- const match = allHubs.find(
15919
- (h) => h.hubId === selector || path17.basename(h.hubFolder) === selector
15920
- );
15921
- if (match) return { hubId: match.hubId, hubFolder: match.hubFolder };
15922
- if (UUID_RE2.test(selector)) return { hubId: selector, hubFolder: null };
15923
- console.error(`No hub matching --hub ${selector} found in ${wsLabel}/. Pass a UUID for first-time pulls.`);
15924
- process.exit(1);
15925
- }
15926
- const enclosing = findEnclosingHubFolder(workspaceDir);
15927
- if (enclosing) return { hubId: enclosing.hubId, hubFolder: enclosing.hubFolder };
15928
- const previewHubs = filterPreviewHubs(allHubs);
15929
- if (previewHubs.length === 1) return { hubId: previewHubs[0].hubId, hubFolder: previewHubs[0].hubFolder };
15930
- if (previewHubs.length === 0) {
15931
- console.error(`No hub folders found in ${wsLabel}/. Pass --hub <uuid> to fetch a hub for the first time.`);
15932
- process.exit(1);
15933
- }
15934
- console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
15935
- for (const h of previewHubs) {
15936
- console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
15937
- }
15938
- process.exit(1);
15939
- }
15940
- async function pullCommand(args2) {
15941
- const { autoConfirm, hubSelector } = parseArgs4(args2);
15942
- const repoConfig = requireRepoConfig();
15943
- const { organization_id: organizationId } = repoConfig;
15944
- const { config, accessToken } = await requireAuth();
15945
- const client = new ApiClient({ apiUrl: config.api_url, accessToken });
15946
- const workspaceDir = resolveWorkspaceDir();
15947
- const gitRoot = findGitRoot();
15948
- if (gitRoot) warnLayoutOnce(gitRoot);
15949
- if (gitRoot) {
15950
- for (const file of ensureWorkspaceFiles(gitRoot)) {
15951
- console.error(`Created ${file}`);
15952
- }
15953
- }
15954
- const { hubId, hubFolder: existingFolder } = resolveHubTarget(workspaceDir, hubSelector);
15955
- console.log("Fetching hub configuration...");
15956
- const payload = await client.pull(hubId, organizationId);
15957
- if (payload.hub_environment === "production") {
15958
- const folder = await writeProductionMirror(workspaceDir, payload);
15959
- console.log(`Production hub mirrored (read-only) \u2192 ${folder}`);
15960
- return;
15961
- }
15962
- assertHubMatchesBinding(hubId);
15963
- let hubFolder = existingFolder;
15964
- let wroteFiles = false;
15965
- if (!hubFolder) {
15966
- hubFolder = resolveHubFolder(
15967
- workspaceDir,
15968
- hubId,
15969
- payload.hub.name,
15970
- payload.hub_environment,
15971
- payload.preview_label,
15972
- payload.branch_name
15973
- );
15974
- fs14.mkdirSync(path17.dirname(hubFolder), { recursive: true });
15975
- console.log("Writing hub configuration...");
15976
- await materializeHubFolder(hubFolder, payload);
15977
- const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
15978
- console.log(`Hub configuration written to ${finalFolder}`);
15979
- wroteFiles = true;
15980
- } else {
15981
- let action = "overwrite";
15982
- try {
15983
- const localPayload = parseHubFolder(hubFolder);
15984
- const diffResult = await client.diff(hubId, prepareResourcePayload(localPayload, payload), "pull", organizationId);
15985
- const connectionsChanged = !connectionsEqual(localPayload.connections, payload.connections);
15986
- if (!diffResult.has_changes && !connectionsChanged) {
15987
- action = "sync_metadata";
15988
- } else {
15989
- console.log("\nChanges to apply locally:");
15990
- if (connectionsChanged) {
15991
- console.log("**Connections:** updated");
15992
- }
15993
- printDiff(diffResult.summary);
15994
- if (!autoConfirm) {
15995
- const ok = await confirm("Apply these changes?");
15996
- if (!ok) {
15997
- console.log("Cancelled.");
15998
- return;
15999
- }
16000
- }
16001
- action = "apply";
16002
- }
16003
- } catch {
16004
- action = "overwrite";
16005
- }
16006
- if (action === "overwrite") console.log("Writing hub configuration...");
16007
- await materializeHubFolder(hubFolder, payload);
16008
- const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
16009
- wroteFiles = true;
16010
- if (action === "sync_metadata") {
16011
- if (finalFolder === hubFolder) {
16012
- console.log("Metadata synced \u2014 no config changes.");
16013
- } else {
16014
- console.log("Metadata synced; folder renamed.");
16015
- }
16016
- } else {
16017
- console.log(`Hub configuration written to ${finalFolder}`);
16018
- }
16019
- }
16020
- if (wroteFiles) autoBindIfUnbound(hubId);
16021
- if (payload.production_hub_id) {
16022
- await mirrorLinkedProduction(client, workspaceDir, payload.production_hub_id, organizationId);
16023
- }
16024
- }
16025
- async function writeProductionMirror(workspaceDir, prodPayload) {
16026
- const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
16027
- fs14.mkdirSync(path17.dirname(folder), { recursive: true });
16028
- await materializeHubFolder(folder, prodPayload, { seedAgentContext: false });
16029
- const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
16030
- prependMirrorMarker(finalFolder, prodPayload.hub_id);
16031
- return finalFolder;
16032
- }
16033
- async function mirrorLinkedProduction(client, workspaceDir, productionHubId, organizationId) {
16034
- try {
16035
- const prodPayload = await client.pull(productionHubId, organizationId);
16036
- const folder = await writeProductionMirror(workspaceDir, prodPayload);
16037
- console.log(`Mirrored production hub \u2192 ${folder} (read-only)`);
16038
- } catch (err) {
16039
- console.warn(`Warning: could not mirror production hub (${err instanceof Error ? err.message : String(err)}). Preview pull is unaffected.`);
16040
- }
16041
- }
16042
- function prependMirrorMarker(hubFolder, productionHubId) {
16043
- const hubYaml = path17.join(hubFolder, "hub.yaml");
16044
- try {
16045
- const content = fs14.readFileSync(hubYaml, "utf-8");
16046
- if (content.startsWith(MIRROR_MARKER_PREFIX)) return;
16047
- const marker = `${MIRROR_MARKER_PREFIX} ${productionHubId}. Edits are ignored; push is blocked. Edit the linked preview hub instead.
16048
- `;
16049
- fs14.writeFileSync(hubYaml, marker + content, "utf-8");
16050
- } catch {
16051
- }
16052
- }
16053
- function connectionsEqual(a, b) {
16054
- const listA = a || [];
16055
- const listB = b || [];
16056
- if (listA.length !== listB.length) return false;
16057
- const key = (c) => c.id || `${c.name}|${c.type}|${c.service || ""}`;
16058
- const setA = new Set(listA.map(key));
16059
- const setB = new Set(listB.map(key));
16060
- if (setA.size !== setB.size) return false;
16061
- for (const k of setA) {
16062
- if (!setB.has(k)) return false;
16063
- }
16064
- return true;
16065
- }
16066
- var MIRROR_MARKER_PREFIX;
16067
- var init_pull = __esm({
16068
- "src/commands/pull.ts"() {
16069
- "use strict";
16070
- init_auth();
16071
- init_api_client();
16072
- init_parser();
16073
- init_diff_display();
16074
- init_utils();
16075
- init_resource_files();
16076
- init_hub_materializer();
16077
- init_workspace();
16078
- init_layout();
16079
- init_workspace_files();
16080
- init_repo_config();
16081
- init_hub_binding();
16082
- MIRROR_MARKER_PREFIX = "# Read-only mirror of production hub";
16083
- }
16084
- });
16085
-
16086
15956
  // src/commands/push.ts
16087
15957
  var push_exports = {};
16088
15958
  __export(push_exports, {
16089
15959
  autoRenameAgentFiles: () => autoRenameAgentFiles,
16090
15960
  createAndPushNewHub: () => createAndPushNewHub,
16091
15961
  printErrors: () => printErrors,
15962
+ printLocalFileChanges: () => printLocalFileChanges,
16092
15963
  printSyncResult: () => printSyncResult,
16093
15964
  printWarnings: () => printWarnings,
16094
15965
  pushCommand: () => pushCommand,
@@ -16097,10 +15968,10 @@ __export(push_exports, {
16097
15968
  shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel,
16098
15969
  syncAfterPush: () => syncAfterPush
16099
15970
  });
16100
- import * as fs15 from "fs";
16101
- import * as path18 from "path";
15971
+ import * as fs14 from "fs";
15972
+ import * as path17 from "path";
16102
15973
  import * as yaml6 from "js-yaml";
16103
- function parseArgs5(args2) {
15974
+ function parseArgs4(args2) {
16104
15975
  let autoConfirm = false;
16105
15976
  let hubSelector;
16106
15977
  let label;
@@ -16190,14 +16061,30 @@ function printErrors(errors) {
16190
16061
  console.error(` \x1B[31m! ${error}\x1B[0m`);
16191
16062
  }
16192
16063
  }
16064
+ function printLocalFileChanges(delta) {
16065
+ if (delta.changed.length === 0 && delta.removed.length === 0) {
16066
+ console.log("Local files already in sync.");
16067
+ return;
16068
+ }
16069
+ console.log("Local files updated from server:");
16070
+ const emit = (paths, sigil) => {
16071
+ const sorted = [...paths].sort();
16072
+ for (const p of sorted.slice(0, LOCAL_CHANGE_LIST_CAP)) console.log(` ${sigil} ${p}`);
16073
+ if (sorted.length > LOCAL_CHANGE_LIST_CAP) {
16074
+ console.log(` \u2026and ${sorted.length - LOCAL_CHANGE_LIST_CAP} more ${sigil === "-" ? "removed" : "changed"}`);
16075
+ }
16076
+ };
16077
+ emit(delta.removed, "-");
16078
+ emit(delta.changed, "~");
16079
+ }
16193
16080
  async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, remoteConfig) {
16194
- const agentsDir = path18.join(hubFolder, "agents");
16081
+ const agentsDir = path17.join(hubFolder, "agents");
16195
16082
  let agentsWithIds = [];
16196
- if (fs15.existsSync(agentsDir)) {
16197
- const yamlFiles = fs15.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
16083
+ if (fs14.existsSync(agentsDir)) {
16084
+ const yamlFiles = fs14.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
16198
16085
  for (const file of yamlFiles) {
16199
16086
  try {
16200
- const content = fs15.readFileSync(path18.join(agentsDir, file), "utf-8");
16087
+ const content = fs14.readFileSync(path17.join(agentsDir, file), "utf-8");
16201
16088
  const agent = yaml6.load(content);
16202
16089
  if (agent?.id && agent.name) {
16203
16090
  agentsWithIds.push({ id: agent.id, name: agent.name });
@@ -16210,7 +16097,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16210
16097
  if (agentsWithIds.length === 0) {
16211
16098
  const yamlPath = resolveHubYamlPath(hubFolder);
16212
16099
  if (!yamlPath) return;
16213
- const yamlContent = fs15.readFileSync(yamlPath, "utf-8");
16100
+ const yamlContent = fs14.readFileSync(yamlPath, "utf-8");
16214
16101
  const config = yaml6.load(yamlContent);
16215
16102
  agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
16216
16103
  }
@@ -16242,18 +16129,18 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16242
16129
  }
16243
16130
  }
16244
16131
  if (renames.length === 0) return;
16245
- if (!fs15.existsSync(agentsDir)) return;
16246
- for (const file of fs15.readdirSync(agentsDir)) {
16132
+ if (!fs14.existsSync(agentsDir)) return;
16133
+ for (const file of fs14.readdirSync(agentsDir)) {
16247
16134
  if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
16248
16135
  console.warn(` Warning: removing orphaned temp file agents/${file}`);
16249
- fs15.unlinkSync(path18.join(agentsDir, file));
16136
+ fs14.unlinkSync(path17.join(agentsDir, file));
16250
16137
  }
16251
16138
  }
16252
16139
  const renameFileIfExists = (dir, oldName, newName) => {
16253
- const oldPath = path18.join(dir, oldName);
16254
- const newPath = path18.join(dir, newName);
16255
- if (!fs15.existsSync(oldPath)) return false;
16256
- fs15.renameSync(oldPath, newPath);
16140
+ const oldPath = path17.join(dir, oldName);
16141
+ const newPath = path17.join(dir, newName);
16142
+ if (!fs14.existsSync(oldPath)) return false;
16143
+ fs14.renameSync(oldPath, newPath);
16257
16144
  return true;
16258
16145
  };
16259
16146
  const oldSlugs = new Set(renames.map((r) => r.oldSlug));
@@ -16286,9 +16173,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16286
16173
  }
16287
16174
  } else {
16288
16175
  for (const { oldSlug, newSlug } of renames) {
16289
- const hasOldFile = extensions.some((ext) => fs15.existsSync(path18.join(agentsDir, `${oldSlug}${ext}`)));
16176
+ const hasOldFile = extensions.some((ext) => fs14.existsSync(path17.join(agentsDir, `${oldSlug}${ext}`)));
16290
16177
  if (!hasOldFile) continue;
16291
- const hasNewFile = extensions.some((ext) => fs15.existsSync(path18.join(agentsDir, `${newSlug}${ext}`)));
16178
+ const hasNewFile = extensions.some((ext) => fs14.existsSync(path17.join(agentsDir, `${newSlug}${ext}`)));
16292
16179
  if (hasNewFile) {
16293
16180
  console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
16294
16181
  continue;
@@ -16303,7 +16190,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16303
16190
  if (completedRenames.length === 0) return;
16304
16191
  const mainYamlPath = resolveHubYamlPath(hubFolder);
16305
16192
  if (mainYamlPath) {
16306
- const mainYamlContent = fs15.readFileSync(mainYamlPath, "utf-8");
16193
+ const mainYamlContent = fs14.readFileSync(mainYamlPath, "utf-8");
16307
16194
  const substitutionMap = /* @__PURE__ */ new Map();
16308
16195
  for (const { oldSlug, newSlug } of completedRenames) {
16309
16196
  substitutionMap.set(`agents/${oldSlug}.md`, `agents/${newSlug}.md`);
@@ -16314,16 +16201,16 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
16314
16201
  (_match, prefix, pathMatch) => `${prefix}${substitutionMap.get(pathMatch) ?? pathMatch}`
16315
16202
  );
16316
16203
  if (updatedYaml !== mainYamlContent) {
16317
- fs15.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
16318
- console.log(` Updated instructions paths in ${path18.basename(mainYamlPath)}`);
16204
+ fs14.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
16205
+ console.log(` Updated instructions paths in ${path17.basename(mainYamlPath)}`);
16319
16206
  }
16320
16207
  }
16321
16208
  }
16322
16209
  async function syncAfterPush(client, hubId, hubFolder, organizationId) {
16323
16210
  try {
16324
16211
  const remoteConfig = await client.pull(hubId, organizationId);
16325
- await materializeHubFolder(hubFolder, remoteConfig);
16326
- console.log("Local files synced with server.");
16212
+ const delta = await materializeHubFolder(hubFolder, remoteConfig);
16213
+ printLocalFileChanges(delta);
16327
16214
  const finalFolder = autoRenameHubFolder(hubFolder, remoteConfig.hub.name, remoteConfig.hub_environment, hubId, remoteConfig.preview_label, remoteConfig.branch_name);
16328
16215
  if (finalFolder !== hubFolder) {
16329
16216
  console.log(`Hub folder is now ${finalFolder}`);
@@ -16383,7 +16270,7 @@ async function pushSingleHub(client, hubId, hubFolder, autoConfirm, organization
16383
16270
  function selectExistingHub(workspaceDir, allHubs, selector, wsLabel) {
16384
16271
  if (selector) {
16385
16272
  const match = allHubs.find(
16386
- (h) => h.hubId === selector || path18.basename(h.hubFolder) === selector
16273
+ (h) => h.hubId === selector || path17.basename(h.hubFolder) === selector
16387
16274
  );
16388
16275
  if (!match) {
16389
16276
  console.error(`No hub matching --hub ${selector} found in ${wsLabel}/.`);
@@ -16426,7 +16313,7 @@ New hub: "${newHub.hubName}" (${hubType})`);
16426
16313
  process.exit(1);
16427
16314
  }
16428
16315
  console.log(`Hub created: ${createdHub.hub_name} (${hubId})`);
16429
- const content = fs15.readFileSync(hubYamlPath, "utf-8");
16316
+ const content = fs14.readFileSync(hubYamlPath, "utf-8");
16430
16317
  const hasVersion = content.match(/^version:\s/m);
16431
16318
  let updated;
16432
16319
  if (hasVersion) {
@@ -16442,7 +16329,7 @@ hub_id: "${hubId}"
16442
16329
  hub_environment: preview
16443
16330
  ${content}`;
16444
16331
  }
16445
- fs15.writeFileSync(hubYamlPath, updated, "utf-8");
16332
+ fs14.writeFileSync(hubYamlPath, updated, "utf-8");
16446
16333
  autoBindIfUnbound(hubId);
16447
16334
  await pushSingleHub(client, hubId, newHub.hubFolder, opts.autoConfirm, opts.organizationId, { skipAgentRename: true });
16448
16335
  }
@@ -16451,7 +16338,7 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
16451
16338
  if (existingHubs.length > 0) {
16452
16339
  console.error(`No pushable hub found in ${wsLabel}/ \u2014 only read-only production mirror folder(s). Edit the linked preview hub, pull one, or create a new hub with \`wayai create\`:`);
16453
16340
  for (const h of existingHubs) {
16454
- console.error(` ${path18.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
16341
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId}, production mirror)`);
16455
16342
  }
16456
16343
  } else {
16457
16344
  console.error(`No hub folders found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` and run \`wayai push\` (or \`wayai create\`).`);
@@ -16464,15 +16351,15 @@ function reportUnresolvedPushTarget(reason, existingHubs, newHubs, selector, wsL
16464
16351
  console.error(`Multiple hub folders found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
16465
16352
  }
16466
16353
  for (const h of existingHubs) {
16467
- console.error(` ${path18.basename(h.hubFolder)} (${h.hubId})`);
16354
+ console.error(` ${path17.basename(h.hubFolder)} (${h.hubId})`);
16468
16355
  }
16469
16356
  for (const h of newHubs) {
16470
- console.error(` ${path18.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
16357
+ console.error(` ${path17.basename(h.hubFolder)} (new \u2014 "${h.hubName}")`);
16471
16358
  }
16472
16359
  process.exit(1);
16473
16360
  }
16474
16361
  async function pushCommand(args2) {
16475
- const { autoConfirm, hubSelector, label } = parseArgs5(args2);
16362
+ const { autoConfirm, hubSelector, label } = parseArgs4(args2);
16476
16363
  const repoConfig = requireRepoConfig();
16477
16364
  const { organization_id: organizationId } = repoConfig;
16478
16365
  const { config, accessToken } = await requireAuth();
@@ -16486,7 +16373,7 @@ async function pushCommand(args2) {
16486
16373
  console.error(`Created ${file}`);
16487
16374
  }
16488
16375
  }
16489
- if (!fs15.existsSync(workspaceDir)) {
16376
+ if (!fs14.existsSync(workspaceDir)) {
16490
16377
  console.error(`No ${wsLabel}/ directory found. Run \`wayai pull\` first or create hub files in ${wsLabel}/<hub>/hub.yaml.`);
16491
16378
  process.exit(1);
16492
16379
  }
@@ -16505,14 +16392,15 @@ async function pushCommand(args2) {
16505
16392
  if (label) {
16506
16393
  console.warn("Note: --label is only used when creating a new hub. To change an existing hub's label, use `wayai relabel`.");
16507
16394
  }
16508
- console.log(`Target hub: ${path18.basename(existing.hubFolder)} (${existing.hubId})`);
16509
- assertHubMatchesBinding(existing.hubId, path18.basename(existing.hubFolder));
16395
+ console.log(`Target hub: ${path17.basename(existing.hubFolder)} (${existing.hubId})`);
16396
+ assertHubMatchesBinding(existing.hubId, path17.basename(existing.hubFolder));
16510
16397
  await pushSingleHub(client, existing.hubId, existing.hubFolder, autoConfirm, organizationId);
16511
16398
  return;
16512
16399
  }
16513
16400
  assertNoBindingBlocksHubCreation(resolution.target.hub.hubName);
16514
16401
  await createAndPushNewHub(client, resolution.target.hub, { autoConfirm, label, organizationId });
16515
16402
  }
16403
+ var LOCAL_CHANGE_LIST_CAP;
16516
16404
  var init_push = __esm({
16517
16405
  "src/commands/push.ts"() {
16518
16406
  "use strict";
@@ -16528,6 +16416,204 @@ var init_push = __esm({
16528
16416
  init_workspace_files();
16529
16417
  init_repo_config();
16530
16418
  init_hub_binding();
16419
+ LOCAL_CHANGE_LIST_CAP = 20;
16420
+ }
16421
+ });
16422
+
16423
+ // src/commands/pull.ts
16424
+ var pull_exports = {};
16425
+ __export(pull_exports, {
16426
+ connectionsEqual: () => connectionsEqual,
16427
+ pullCommand: () => pullCommand,
16428
+ resolveHubTarget: () => resolveHubTarget,
16429
+ writeProductionMirror: () => writeProductionMirror
16430
+ });
16431
+ import * as fs15 from "fs";
16432
+ import * as path18 from "path";
16433
+ function parseArgs5(args2) {
16434
+ let autoConfirm = false;
16435
+ let hubSelector;
16436
+ for (let i = 0; i < args2.length; i++) {
16437
+ const arg = args2[i];
16438
+ if (arg === "--yes" || arg === "-y") {
16439
+ autoConfirm = true;
16440
+ } else if (arg === "--hub" && args2[i + 1]) {
16441
+ hubSelector = args2[++i];
16442
+ }
16443
+ }
16444
+ return { autoConfirm, hubSelector };
16445
+ }
16446
+ function resolveHubTarget(workspaceDir, selector) {
16447
+ const wsLabel = hubsDirLabel(findGitRoot());
16448
+ const allHubs = scanWorkspaceHubs(workspaceDir);
16449
+ if (selector) {
16450
+ const match = allHubs.find(
16451
+ (h) => h.hubId === selector || path18.basename(h.hubFolder) === selector
16452
+ );
16453
+ if (match) return { hubId: match.hubId, hubFolder: match.hubFolder };
16454
+ if (UUID_RE2.test(selector)) return { hubId: selector, hubFolder: null };
16455
+ console.error(`No hub matching --hub ${selector} found in ${wsLabel}/. Pass a UUID for first-time pulls.`);
16456
+ process.exit(1);
16457
+ }
16458
+ const enclosing = findEnclosingHubFolder(workspaceDir);
16459
+ if (enclosing) return { hubId: enclosing.hubId, hubFolder: enclosing.hubFolder };
16460
+ const previewHubs = filterPreviewHubs(allHubs);
16461
+ if (previewHubs.length === 1) return { hubId: previewHubs[0].hubId, hubFolder: previewHubs[0].hubFolder };
16462
+ if (previewHubs.length === 0) {
16463
+ console.error(`No hub folders found in ${wsLabel}/. Pass --hub <uuid> to fetch a hub for the first time.`);
16464
+ process.exit(1);
16465
+ }
16466
+ console.error(`Multiple hubs found in ${wsLabel}/. Pass --hub <uuid|folder-name> to choose, or run from inside a hub folder:`);
16467
+ for (const h of previewHubs) {
16468
+ console.error(` ${path18.basename(h.hubFolder)} (${h.hubId})`);
16469
+ }
16470
+ process.exit(1);
16471
+ }
16472
+ async function pullCommand(args2) {
16473
+ const { autoConfirm, hubSelector } = parseArgs5(args2);
16474
+ const repoConfig = requireRepoConfig();
16475
+ const { organization_id: organizationId } = repoConfig;
16476
+ const { config, accessToken } = await requireAuth();
16477
+ const client = new ApiClient({ apiUrl: config.api_url, accessToken });
16478
+ const workspaceDir = resolveWorkspaceDir();
16479
+ const gitRoot = findGitRoot();
16480
+ if (gitRoot) warnLayoutOnce(gitRoot);
16481
+ if (gitRoot) {
16482
+ for (const file of ensureWorkspaceFiles(gitRoot)) {
16483
+ console.error(`Created ${file}`);
16484
+ }
16485
+ }
16486
+ const { hubId, hubFolder: existingFolder } = resolveHubTarget(workspaceDir, hubSelector);
16487
+ console.log("Fetching hub configuration...");
16488
+ const payload = await client.pull(hubId, organizationId);
16489
+ if (payload.hub_environment === "production") {
16490
+ const folder = await writeProductionMirror(workspaceDir, payload);
16491
+ console.log(`Production hub mirrored (read-only) \u2192 ${folder}`);
16492
+ return;
16493
+ }
16494
+ assertHubMatchesBinding(hubId);
16495
+ let hubFolder = existingFolder;
16496
+ let wroteFiles = false;
16497
+ if (!hubFolder) {
16498
+ hubFolder = resolveHubFolder(
16499
+ workspaceDir,
16500
+ hubId,
16501
+ payload.hub.name,
16502
+ payload.hub_environment,
16503
+ payload.preview_label,
16504
+ payload.branch_name
16505
+ );
16506
+ fs15.mkdirSync(path18.dirname(hubFolder), { recursive: true });
16507
+ console.log("Writing hub configuration...");
16508
+ await materializeHubFolder(hubFolder, payload);
16509
+ const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
16510
+ console.log(`Hub configuration written to ${finalFolder}`);
16511
+ wroteFiles = true;
16512
+ } else {
16513
+ let action = "overwrite";
16514
+ try {
16515
+ const localPayload = parseHubFolder(hubFolder);
16516
+ const diffResult = await client.diff(hubId, prepareResourcePayload(localPayload, payload), "pull", organizationId);
16517
+ const connectionsChanged = !connectionsEqual(localPayload.connections, payload.connections);
16518
+ if (!diffResult.has_changes && !connectionsChanged) {
16519
+ action = "sync_metadata";
16520
+ } else {
16521
+ console.log("\nChanges to apply locally:");
16522
+ if (connectionsChanged) {
16523
+ console.log("**Connections:** updated");
16524
+ }
16525
+ printDiff(diffResult.summary);
16526
+ if (!autoConfirm) {
16527
+ const ok = await confirm("Apply these changes?");
16528
+ if (!ok) {
16529
+ console.log("Cancelled.");
16530
+ return;
16531
+ }
16532
+ }
16533
+ action = "apply";
16534
+ }
16535
+ } catch {
16536
+ action = "overwrite";
16537
+ }
16538
+ if (action === "overwrite") console.log("Writing hub configuration...");
16539
+ const delta = await materializeHubFolder(hubFolder, payload);
16540
+ const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
16541
+ wroteFiles = true;
16542
+ if (action === "sync_metadata") {
16543
+ if (finalFolder === hubFolder) {
16544
+ printLocalFileChanges(delta);
16545
+ } else {
16546
+ console.log("Metadata synced; folder renamed.");
16547
+ }
16548
+ } else {
16549
+ console.log(`Hub configuration written to ${finalFolder}`);
16550
+ printLocalFileChanges(delta);
16551
+ }
16552
+ }
16553
+ if (wroteFiles) autoBindIfUnbound(hubId);
16554
+ if (payload.production_hub_id) {
16555
+ await mirrorLinkedProduction(client, workspaceDir, payload.production_hub_id, organizationId);
16556
+ }
16557
+ }
16558
+ async function writeProductionMirror(workspaceDir, prodPayload) {
16559
+ const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
16560
+ fs15.mkdirSync(path18.dirname(folder), { recursive: true });
16561
+ await materializeHubFolder(folder, prodPayload, { seedAgentContext: false });
16562
+ const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
16563
+ prependMirrorMarker(finalFolder, prodPayload.hub_id);
16564
+ return finalFolder;
16565
+ }
16566
+ async function mirrorLinkedProduction(client, workspaceDir, productionHubId, organizationId) {
16567
+ try {
16568
+ const prodPayload = await client.pull(productionHubId, organizationId);
16569
+ const folder = await writeProductionMirror(workspaceDir, prodPayload);
16570
+ console.log(`Mirrored production hub \u2192 ${folder} (read-only)`);
16571
+ } catch (err) {
16572
+ console.warn(`Warning: could not mirror production hub (${err instanceof Error ? err.message : String(err)}). Preview pull is unaffected.`);
16573
+ }
16574
+ }
16575
+ function prependMirrorMarker(hubFolder, productionHubId) {
16576
+ const hubYaml = path18.join(hubFolder, "hub.yaml");
16577
+ try {
16578
+ const content = fs15.readFileSync(hubYaml, "utf-8");
16579
+ if (content.startsWith(MIRROR_MARKER_PREFIX)) return;
16580
+ const marker = `${MIRROR_MARKER_PREFIX} ${productionHubId}. Edits are ignored; push is blocked. Edit the linked preview hub instead.
16581
+ `;
16582
+ fs15.writeFileSync(hubYaml, marker + content, "utf-8");
16583
+ } catch {
16584
+ }
16585
+ }
16586
+ function connectionsEqual(a, b) {
16587
+ const listA = a || [];
16588
+ const listB = b || [];
16589
+ if (listA.length !== listB.length) return false;
16590
+ const key = (c) => c.id || `${c.name}|${c.type}|${c.service || ""}`;
16591
+ const setA = new Set(listA.map(key));
16592
+ const setB = new Set(listB.map(key));
16593
+ if (setA.size !== setB.size) return false;
16594
+ for (const k of setA) {
16595
+ if (!setB.has(k)) return false;
16596
+ }
16597
+ return true;
16598
+ }
16599
+ var MIRROR_MARKER_PREFIX;
16600
+ var init_pull = __esm({
16601
+ "src/commands/pull.ts"() {
16602
+ "use strict";
16603
+ init_auth();
16604
+ init_api_client();
16605
+ init_parser();
16606
+ init_diff_display();
16607
+ init_utils();
16608
+ init_resource_files();
16609
+ init_hub_materializer();
16610
+ init_push();
16611
+ init_workspace();
16612
+ init_layout();
16613
+ init_workspace_files();
16614
+ init_repo_config();
16615
+ init_hub_binding();
16616
+ MIRROR_MARKER_PREFIX = "# Read-only mirror of production hub";
16531
16617
  }
16532
16618
  });
16533
16619
 
@@ -16745,8 +16831,9 @@ async function replicateCommand(args2) {
16745
16831
  payload.preview_label,
16746
16832
  payload.branch_name
16747
16833
  );
16834
+ const folderPreExisted = fs17.existsSync(hubFolder);
16748
16835
  fs17.mkdirSync(path20.dirname(hubFolder), { recursive: true });
16749
- await materializeHubFolder(hubFolder, payload);
16836
+ const delta = await materializeHubFolder(hubFolder, payload);
16750
16837
  hubFolder = autoRenameHubFolder(
16751
16838
  hubFolder,
16752
16839
  payload.hub.name,
@@ -16756,6 +16843,7 @@ async function replicateCommand(args2) {
16756
16843
  payload.branch_name
16757
16844
  );
16758
16845
  autoBindIfUnbound(previewHubId);
16846
+ if (folderPreExisted) printLocalFileChanges(delta);
16759
16847
  console.log(`Preview written to ${path20.basename(hubFolder)}`);
16760
16848
  console.log(` Switch to it with: wayai use ${path20.basename(hubFolder)}`);
16761
16849
  }
@@ -16766,6 +16854,7 @@ var init_replicate = __esm({
16766
16854
  init_api_client();
16767
16855
  init_hub_materializer();
16768
16856
  init_pull();
16857
+ init_push();
16769
16858
  init_workspace();
16770
16859
  init_layout();
16771
16860
  init_repo_config();
@@ -22865,7 +22954,7 @@ var init_update = __esm({
22865
22954
 
22866
22955
  // src/index.ts
22867
22956
  init_sentry();
22868
- import { readFileSync as readFileSync20 } from "fs";
22957
+ import { readFileSync as readFileSync21 } from "fs";
22869
22958
  import { fileURLToPath as fileURLToPath2 } from "url";
22870
22959
  import { dirname as dirname9, join as join28 } from "path";
22871
22960
 
@@ -23047,7 +23136,7 @@ Run \`wayai admin skill install\` to update.`);
23047
23136
 
23048
23137
  // src/index.ts
23049
23138
  var __dirname = dirname9(fileURLToPath2(import.meta.url));
23050
- var pkg = JSON.parse(readFileSync20(join28(__dirname, "..", "package.json"), "utf-8"));
23139
+ var pkg = JSON.parse(readFileSync21(join28(__dirname, "..", "package.json"), "utf-8"));
23051
23140
  var [, , command, ...args] = process.argv;
23052
23141
  var isBackgroundRefresh = command === REFRESH_COMMAND;
23053
23142
  if (!isBackgroundRefresh) initSentry(command, pkg.version);