@wayai/cli 0.3.164 → 0.3.166

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
@@ -17455,40 +17455,228 @@ var init_expected = __esm({
17455
17455
  }
17456
17456
  });
17457
17457
 
17458
- // src/lib/layout.ts
17458
+ // src/lib/fs-safety.ts
17459
17459
  import * as fs from "fs";
17460
17460
  import * as path from "path";
17461
+ function fileHasBytes(abs, data) {
17462
+ let st;
17463
+ try {
17464
+ st = fs.lstatSync(abs);
17465
+ } catch {
17466
+ return false;
17467
+ }
17468
+ if (!st.isFile() || st.isSymbolicLink()) return false;
17469
+ try {
17470
+ return fs.readFileSync(abs).equals(data);
17471
+ } catch {
17472
+ return false;
17473
+ }
17474
+ }
17475
+ function realpathContained(root, abs) {
17476
+ let realRoot;
17477
+ let realAbs;
17478
+ try {
17479
+ realRoot = fs.realpathSync(root);
17480
+ realAbs = fs.realpathSync(abs);
17481
+ } catch {
17482
+ return false;
17483
+ }
17484
+ return realAbs === realRoot || realAbs.startsWith(realRoot + path.sep);
17485
+ }
17486
+ function mkdirTolerateRace(dir) {
17487
+ try {
17488
+ fs.mkdirSync(dir);
17489
+ return true;
17490
+ } catch (err) {
17491
+ if (err.code !== "EEXIST") throw err;
17492
+ let st;
17493
+ try {
17494
+ st = fs.lstatSync(dir);
17495
+ } catch {
17496
+ st = void 0;
17497
+ }
17498
+ return !!st?.isDirectory();
17499
+ }
17500
+ }
17501
+ function ensureRealSubdirNoSymlink(root, target, create) {
17502
+ const rootResolved = path.resolve(root);
17503
+ const targetResolved = path.resolve(target);
17504
+ if (targetResolved !== rootResolved && !targetResolved.startsWith(rootResolved + path.sep)) return false;
17505
+ try {
17506
+ if (!fs.lstatSync(rootResolved).isDirectory()) return false;
17507
+ } catch {
17508
+ return false;
17509
+ }
17510
+ const rel = path.relative(rootResolved, targetResolved);
17511
+ if (rel === "") return true;
17512
+ let cur = rootResolved;
17513
+ for (const segment of rel.split(path.sep)) {
17514
+ cur = path.join(cur, segment);
17515
+ let st;
17516
+ try {
17517
+ st = fs.lstatSync(cur);
17518
+ } catch {
17519
+ st = void 0;
17520
+ }
17521
+ if (st) {
17522
+ if (!st.isDirectory()) return false;
17523
+ } else if (create) {
17524
+ if (!mkdirTolerateRace(cur)) return false;
17525
+ } else {
17526
+ return true;
17527
+ }
17528
+ }
17529
+ return true;
17530
+ }
17531
+ function isUnder(parent, child) {
17532
+ const rel = path.relative(parent, child);
17533
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel);
17534
+ }
17535
+ function isSymlink(p) {
17536
+ try {
17537
+ return fs.lstatSync(p).isSymbolicLink();
17538
+ } catch {
17539
+ return false;
17540
+ }
17541
+ }
17542
+ function workspaceRefusal(message) {
17543
+ const err = expected(message);
17544
+ err.isWorkspaceRefusal = true;
17545
+ return err;
17546
+ }
17547
+ function isWorkspaceRefusal(err) {
17548
+ return err?.isWorkspaceRefusal === true;
17549
+ }
17550
+ function symlinkRefusal(rel, kind) {
17551
+ return workspaceRefusal(
17552
+ `${rel} is a symlink, is reached through one, or is not a ${kind}. The CLI never reads or writes workspace config through a symlink \u2014 make it a real ${kind}.`
17553
+ );
17554
+ }
17555
+ function requireRealSubdirNoSymlink(root, target, create) {
17556
+ if (ensureRealSubdirNoSymlink(root, target, create)) return;
17557
+ throw symlinkRefusal(path.relative(path.dirname(root), target), "directory");
17558
+ }
17559
+ function requireRealHubFolder(hubFolder, create) {
17560
+ if (!create) {
17561
+ try {
17562
+ fs.lstatSync(hubFolder);
17563
+ } catch {
17564
+ return;
17565
+ }
17566
+ }
17567
+ requireRealSubdirNoSymlink(path.dirname(hubFolder), hubFolder, create);
17568
+ for (const sub of HUB_MANAGED_SUBDIRS) {
17569
+ requireRealSubdirNoSymlink(hubFolder, path.join(hubFolder, sub), false);
17570
+ }
17571
+ for (const name of HUB_CONFIG_FILES) {
17572
+ let st;
17573
+ try {
17574
+ st = fs.lstatSync(path.join(hubFolder, name));
17575
+ } catch {
17576
+ continue;
17577
+ }
17578
+ if (!st.isFile()) throw symlinkRefusal(path.join(path.basename(hubFolder), name), "file");
17579
+ }
17580
+ }
17581
+ function readRealFileOrThrow(root, abs) {
17582
+ let st;
17583
+ try {
17584
+ st = fs.lstatSync(abs);
17585
+ } catch {
17586
+ return null;
17587
+ }
17588
+ const bytes = st.isFile() ? readFileNoFollow(root, abs) : null;
17589
+ if (!bytes) throw symlinkRefusal(path.relative(path.dirname(root), abs), "file");
17590
+ return bytes.toString("utf-8");
17591
+ }
17592
+ function readFileNoFollow(root, abs) {
17593
+ if (!ensureRealSubdirNoSymlink(root, path.dirname(abs), false)) return null;
17594
+ try {
17595
+ if (!fs.lstatSync(abs).isFile()) return null;
17596
+ return fs.readFileSync(abs);
17597
+ } catch {
17598
+ return null;
17599
+ }
17600
+ }
17601
+ function writeFileNoFollow(root, abs, data) {
17602
+ const parent = path.dirname(abs);
17603
+ if (!ensureRealSubdirNoSymlink(root, parent, true)) return false;
17604
+ let st;
17605
+ try {
17606
+ st = fs.lstatSync(abs);
17607
+ } catch {
17608
+ }
17609
+ if (st?.isSymbolicLink()) fs.rmSync(abs);
17610
+ fs.writeFileSync(abs, data);
17611
+ return true;
17612
+ }
17613
+ function createFileNoFollow(root, abs, data) {
17614
+ if (!ensureRealSubdirNoSymlink(root, path.dirname(abs), true)) return "refused";
17615
+ try {
17616
+ fs.writeFileSync(abs, data, { flag: "wx" });
17617
+ } catch (err) {
17618
+ if (err.code === "EEXIST") return "exists";
17619
+ throw err;
17620
+ }
17621
+ return "created";
17622
+ }
17623
+ var HUB_MANAGED_SUBDIRS, HUB_CONFIG_FILES;
17624
+ var init_fs_safety = __esm({
17625
+ "src/lib/fs-safety.ts"() {
17626
+ "use strict";
17627
+ init_expected();
17628
+ HUB_MANAGED_SUBDIRS = ["agents", "evals", "journeys", "resources", "attachments"];
17629
+ HUB_CONFIG_FILES = ["hub.yaml", "wayai.yaml"];
17630
+ }
17631
+ });
17632
+
17633
+ // src/lib/layout.ts
17634
+ import * as fs2 from "fs";
17635
+ import * as path2 from "path";
17461
17636
  function isDirectory(p) {
17462
17637
  try {
17463
- return fs.statSync(p).isDirectory();
17638
+ return fs2.statSync(p).isDirectory();
17639
+ } catch {
17640
+ return false;
17641
+ }
17642
+ }
17643
+ function isRealDirectory(p) {
17644
+ try {
17645
+ return fs2.lstatSync(p).isDirectory();
17464
17646
  } catch {
17465
17647
  return false;
17466
17648
  }
17467
17649
  }
17468
17650
  function resolveLayout(gitRoot, layout = WAYAI_LAYOUT) {
17469
- const newWs = path.join(gitRoot, layout.wsDir);
17470
- const legacyWs = path.join(gitRoot, layout.legacy.wsDir);
17471
- const legacyOrg = path.join(gitRoot, layout.legacy.orgAtRoot);
17472
- const legacyExists = isDirectory(legacyWs) || isDirectory(legacyOrg);
17473
- const basesDir = path.join(newWs, layout.basesSubdir);
17474
- const hubsDir = path.join(newWs, layout.hubsSubdir);
17475
- const orgDir = path.join(newWs, layout.orgSubdir);
17651
+ const newWs = path2.join(gitRoot, layout.wsDir);
17652
+ const legacyWs = path2.join(gitRoot, layout.legacy.wsDir);
17653
+ const legacyOrg = path2.join(gitRoot, layout.legacy.orgAtRoot);
17654
+ const legacyExists = isRealDirectory(legacyWs) || isRealDirectory(legacyOrg);
17655
+ const basesDir = path2.join(newWs, layout.basesSubdir);
17656
+ const hubsDir = path2.join(newWs, layout.hubsSubdir);
17657
+ const orgDir = path2.join(newWs, layout.orgSubdir);
17476
17658
  const newDisplacesLegacy = () => isDirectory(hubsDir) || isDirectory(orgDir) || isDirectory(basesDir);
17659
+ let resolved;
17477
17660
  if (legacyExists && !newDisplacesLegacy()) {
17478
- return { hubsDir: legacyWs, orgDir: legacyOrg, basesDir, isLegacy: true, legacyAlsoPresent: false };
17661
+ resolved = { hubsDir: legacyWs, orgDir: legacyOrg, basesDir, isLegacy: true, legacyAlsoPresent: false };
17662
+ } else {
17663
+ resolved = { hubsDir, orgDir, basesDir, isLegacy: false, legacyAlsoPresent: legacyExists };
17479
17664
  }
17480
- return { hubsDir, orgDir, basesDir, isLegacy: false, legacyAlsoPresent: legacyExists };
17665
+ for (const dir of [resolved.hubsDir, resolved.orgDir, resolved.basesDir]) {
17666
+ requireRealSubdirNoSymlink(gitRoot, dir, false);
17667
+ }
17668
+ return resolved;
17481
17669
  }
17482
17670
  function resolveBasesDir(gitRoot) {
17483
17671
  return resolveLayout(gitRoot).basesDir;
17484
17672
  }
17485
17673
  function hubsDirLabel(gitRoot) {
17486
- if (!gitRoot) return path.join(WAYAI_LAYOUT.wsDir, WAYAI_LAYOUT.hubsSubdir);
17487
- return path.relative(gitRoot, resolveLayout(gitRoot).hubsDir);
17674
+ if (!gitRoot) return path2.join(WAYAI_LAYOUT.wsDir, WAYAI_LAYOUT.hubsSubdir);
17675
+ return path2.relative(gitRoot, resolveLayout(gitRoot).hubsDir);
17488
17676
  }
17489
17677
  function basesDirLabel(gitRoot) {
17490
- if (!gitRoot) return path.join(WAYAI_LAYOUT.wsDir, WAYAI_LAYOUT.basesSubdir);
17491
- return path.relative(gitRoot, resolveLayout(gitRoot).basesDir);
17678
+ if (!gitRoot) return path2.join(WAYAI_LAYOUT.wsDir, WAYAI_LAYOUT.basesSubdir);
17679
+ return path2.relative(gitRoot, resolveLayout(gitRoot).basesDir);
17492
17680
  }
17493
17681
  function warnLayoutOnce(gitRoot) {
17494
17682
  const r = resolveLayout(gitRoot);
@@ -17516,6 +17704,7 @@ var init_layout = __esm({
17516
17704
  "src/lib/layout.ts"() {
17517
17705
  "use strict";
17518
17706
  init_dist();
17707
+ init_fs_safety();
17519
17708
  WAYAI_LAYOUT = {
17520
17709
  ...WAYAI_WORKSPACE_LAYOUT,
17521
17710
  legacy: { wsDir: "workspace", orgAtRoot: "org" }
@@ -17550,8 +17739,8 @@ __export(workspace_exports, {
17550
17739
  scanWorkspaceHubs: () => scanWorkspaceHubs
17551
17740
  });
17552
17741
  import { execFileSync } from "child_process";
17553
- import * as fs2 from "fs";
17554
- import * as path2 from "path";
17742
+ import * as fs3 from "fs";
17743
+ import * as path3 from "path";
17555
17744
  import * as yaml from "js-yaml";
17556
17745
  function findGitRoot() {
17557
17746
  try {
@@ -17566,8 +17755,8 @@ function findGitRoot() {
17566
17755
  function findRepoRootSync() {
17567
17756
  let current = process.cwd();
17568
17757
  while (true) {
17569
- if (fs2.existsSync(path2.join(current, ".git"))) return current;
17570
- const parent = path2.dirname(current);
17758
+ if (fs3.existsSync(path3.join(current, ".git"))) return current;
17759
+ const parent = path3.dirname(current);
17571
17760
  if (parent === current) return null;
17572
17761
  current = parent;
17573
17762
  }
@@ -17576,7 +17765,7 @@ function detectWorkspace() {
17576
17765
  const gitRoot = findGitRoot();
17577
17766
  if (!gitRoot) return null;
17578
17767
  const { hubsDir } = resolveLayout(gitRoot);
17579
- if (!fs2.existsSync(hubsDir) || !fs2.statSync(hubsDir).isDirectory()) {
17768
+ if (!fs3.existsSync(hubsDir) || !fs3.statSync(hubsDir).isDirectory()) {
17580
17769
  return null;
17581
17770
  }
17582
17771
  return { gitRoot, workspaceDir: hubsDir };
@@ -17592,10 +17781,9 @@ function resolveWorkspaceDir() {
17592
17781
  return resolveLayout(gitRoot).hubsDir;
17593
17782
  }
17594
17783
  function readHubYaml(yamlPath) {
17595
- if (!fs2.existsSync(yamlPath)) return null;
17784
+ if (!fs3.existsSync(yamlPath) || isSymlink(yamlPath)) return null;
17596
17785
  try {
17597
- const content = fs2.readFileSync(yamlPath, "utf-8");
17598
- const config = yaml.load(content);
17786
+ const config = yaml.load(fs3.readFileSync(yamlPath, "utf-8"));
17599
17787
  if (!config?.hub_id || !config.hub_environment) return null;
17600
17788
  return { hubId: config.hub_id, hubEnvironment: config.hub_environment };
17601
17789
  } catch {
@@ -17605,9 +17793,9 @@ function readHubYaml(yamlPath) {
17605
17793
  function scanWorkspaceHubs(workspaceDir) {
17606
17794
  const hubs = [];
17607
17795
  for (const top of safeReaddir(workspaceDir)) {
17608
- const topDir = path2.join(workspaceDir, top);
17609
- if (!isDirectory(topDir)) continue;
17610
- const meta = readHubYaml(path2.join(topDir, "hub.yaml")) || readHubYaml(path2.join(topDir, "wayai.yaml"));
17796
+ const topDir = path3.join(workspaceDir, top);
17797
+ if (!isRealDirectory(topDir)) continue;
17798
+ const meta = readHubYaml(path3.join(topDir, "hub.yaml")) || readHubYaml(path3.join(topDir, "wayai.yaml"));
17611
17799
  if (meta) {
17612
17800
  hubs.push({ hubFolder: topDir, hubId: meta.hubId, hubEnvironment: meta.hubEnvironment });
17613
17801
  }
@@ -17621,10 +17809,9 @@ function filterPreviewHubs(hubs) {
17621
17809
  return hubs.filter(isPreviewHub);
17622
17810
  }
17623
17811
  function readNewHubYaml(yamlPath) {
17624
- if (!fs2.existsSync(yamlPath)) return null;
17812
+ if (!fs3.existsSync(yamlPath) || isSymlink(yamlPath)) return null;
17625
17813
  try {
17626
- const content = fs2.readFileSync(yamlPath, "utf-8");
17627
- const config = yaml.load(content);
17814
+ const config = yaml.load(fs3.readFileSync(yamlPath, "utf-8"));
17628
17815
  if (config?.hub_id) return null;
17629
17816
  if (!config?.hub?.name) return null;
17630
17817
  return { hubName: config.hub.name, hubType: config.hub.hub_type };
@@ -17635,9 +17822,9 @@ function readNewHubYaml(yamlPath) {
17635
17822
  function scanNewHubs(workspaceDir) {
17636
17823
  const hubs = [];
17637
17824
  for (const top of safeReaddir(workspaceDir)) {
17638
- const topDir = path2.join(workspaceDir, top);
17639
- if (!isDirectory(topDir)) continue;
17640
- const meta = readNewHubYaml(path2.join(topDir, "hub.yaml")) || readNewHubYaml(path2.join(topDir, "wayai.yaml"));
17825
+ const topDir = path3.join(workspaceDir, top);
17826
+ if (!isRealDirectory(topDir)) continue;
17827
+ const meta = readNewHubYaml(path3.join(topDir, "hub.yaml")) || readNewHubYaml(path3.join(topDir, "wayai.yaml"));
17641
17828
  if (meta) {
17642
17829
  hubs.push({ hubFolder: topDir, hubName: meta.hubName, hubType: meta.hubType });
17643
17830
  }
@@ -17657,22 +17844,22 @@ function resolveHubFolder(workspaceDir, hubId, hubName, hubEnvironment, previewL
17657
17844
  if (match) return match.hubFolder;
17658
17845
  }
17659
17846
  const hubFolderSlug = getHubFolderSlug(hubName, hubEnvironment, hubId || "", previewLabel, branchName);
17660
- return path2.join(workspaceDir, hubFolderSlug);
17847
+ return path3.join(workspaceDir, hubFolderSlug);
17661
17848
  }
17662
17849
  function findLocalHubFolder(hubId) {
17663
17850
  const workspace = detectWorkspace();
17664
17851
  const gitRoot = findGitRoot();
17665
17852
  const workspaceDir = workspace?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
17666
- if (!workspaceDir || !fs2.existsSync(workspaceDir)) return null;
17853
+ if (!workspaceDir || !fs3.existsSync(workspaceDir)) return null;
17667
17854
  const match = scanWorkspaceHubs(workspaceDir).find((h) => h.hubId === hubId);
17668
17855
  return match ? match.hubFolder : null;
17669
17856
  }
17670
17857
  function getChangedHubs(workspaceDir) {
17671
17858
  const gitRoot = findGitRoot();
17672
17859
  if (!gitRoot) return [];
17673
- const relWorkspace = path2.relative(gitRoot, workspaceDir);
17674
- const normalizedRel = path2.normalize(relWorkspace);
17675
- if (normalizedRel.startsWith("..") || path2.isAbsolute(normalizedRel)) {
17860
+ const relWorkspace = path3.relative(gitRoot, workspaceDir);
17861
+ const normalizedRel = path3.normalize(relWorkspace);
17862
+ if (normalizedRel.startsWith("..") || path3.isAbsolute(normalizedRel)) {
17676
17863
  return [];
17677
17864
  }
17678
17865
  let statusOutput;
@@ -17697,9 +17884,9 @@ function getChangedHubs(workspaceDir) {
17697
17884
  if (hubFiles.length === 0) return [];
17698
17885
  const hubFolders = /* @__PURE__ */ new Set();
17699
17886
  for (const file of hubFiles) {
17700
- const absFile = path2.resolve(gitRoot, file);
17887
+ const absFile = path3.resolve(gitRoot, file);
17701
17888
  if (file.endsWith("hub.yaml") || file.endsWith("wayai.yaml")) {
17702
- hubFolders.add(path2.dirname(absFile));
17889
+ hubFolders.add(path3.dirname(absFile));
17703
17890
  } else if (file.includes("/agents/")) {
17704
17891
  const agentsIndex = absFile.lastIndexOf("/agents/");
17705
17892
  hubFolders.add(absFile.substring(0, agentsIndex));
@@ -17707,7 +17894,8 @@ function getChangedHubs(workspaceDir) {
17707
17894
  }
17708
17895
  const hubs = [];
17709
17896
  for (const hubFolder of hubFolders) {
17710
- const meta = readHubYaml(path2.join(hubFolder, "hub.yaml")) || readHubYaml(path2.join(hubFolder, "wayai.yaml"));
17897
+ if (!isRealDirectory(hubFolder)) continue;
17898
+ const meta = readHubYaml(path3.join(hubFolder, "hub.yaml")) || readHubYaml(path3.join(hubFolder, "wayai.yaml"));
17711
17899
  if (meta) {
17712
17900
  hubs.push({ hubFolder, hubId: meta.hubId, hubEnvironment: meta.hubEnvironment });
17713
17901
  }
@@ -17742,19 +17930,19 @@ function resolveExplicitHubPath(hubPath) {
17742
17930
  }
17743
17931
  function findHubByFolderName(workspaceDir, hubFolderName) {
17744
17932
  const all = scanWorkspaceHubs(workspaceDir);
17745
- return all.find((h) => path2.basename(h.hubFolder) === hubFolderName) ?? null;
17933
+ return all.find((h) => path3.basename(h.hubFolder) === hubFolderName) ?? null;
17746
17934
  }
17747
17935
  function autoRenameHubFolder(currentFolder, hubName, hubEnvironment, hubId, previewLabel, branchName) {
17748
17936
  const expectedSlug = getHubFolderSlug(hubName, hubEnvironment, hubId, previewLabel, branchName);
17749
- const currentSlug = path2.basename(currentFolder);
17937
+ const currentSlug = path3.basename(currentFolder);
17750
17938
  if (currentSlug === expectedSlug) return currentFolder;
17751
- const targetFolder = path2.join(path2.dirname(currentFolder), expectedSlug);
17752
- if (fs2.existsSync(targetFolder)) {
17939
+ const targetFolder = path3.join(path3.dirname(currentFolder), expectedSlug);
17940
+ if (fs3.existsSync(targetFolder)) {
17753
17941
  console.warn(` Warning: skipping hub folder rename ${currentSlug} \u2192 ${expectedSlug} (target already exists)`);
17754
17942
  return currentFolder;
17755
17943
  }
17756
17944
  try {
17757
- fs2.renameSync(currentFolder, targetFolder);
17945
+ fs3.renameSync(currentFolder, targetFolder);
17758
17946
  } catch (err) {
17759
17947
  console.warn(` Warning: could not rename ${currentSlug} \u2192 ${expectedSlug}: ${err instanceof Error ? err.message : String(err)}`);
17760
17948
  return currentFolder;
@@ -17765,39 +17953,37 @@ function autoRenameHubFolder(currentFolder, hubName, hubEnvironment, hubId, prev
17765
17953
  }
17766
17954
  function findEnclosingHubFolder(workspaceDir) {
17767
17955
  let current = process.cwd();
17768
- const stop = path2.resolve(workspaceDir);
17769
- while (true) {
17770
- const meta = readHubYaml(path2.join(current, "hub.yaml")) || readHubYaml(path2.join(current, "wayai.yaml"));
17956
+ const stop = path3.resolve(workspaceDir);
17957
+ while (isUnder(stop, current)) {
17958
+ const meta = readHubYaml(path3.join(current, "hub.yaml")) || readHubYaml(path3.join(current, "wayai.yaml"));
17771
17959
  if (meta) {
17772
17960
  return { hubFolder: current, hubId: meta.hubId, hubEnvironment: meta.hubEnvironment };
17773
17961
  }
17774
17962
  if (current === stop) return null;
17775
- const parent = path2.dirname(current);
17776
- if (parent === current) return null;
17777
- current = parent;
17963
+ current = path3.dirname(current);
17778
17964
  }
17965
+ return null;
17779
17966
  }
17780
17967
  function findEnclosingNewHubFolder(workspaceDir) {
17781
17968
  let current = process.cwd();
17782
- const stop = path2.resolve(workspaceDir);
17783
- while (true) {
17784
- const meta = readNewHubYaml(path2.join(current, "hub.yaml")) || readNewHubYaml(path2.join(current, "wayai.yaml"));
17969
+ const stop = path3.resolve(workspaceDir);
17970
+ while (isUnder(stop, current)) {
17971
+ const meta = readNewHubYaml(path3.join(current, "hub.yaml")) || readNewHubYaml(path3.join(current, "wayai.yaml"));
17785
17972
  if (meta) {
17786
17973
  return { hubFolder: current, hubName: meta.hubName, hubType: meta.hubType };
17787
17974
  }
17788
17975
  if (current === stop) return null;
17789
- const parent = path2.dirname(current);
17790
- if (parent === current) return null;
17791
- current = parent;
17976
+ current = path3.dirname(current);
17792
17977
  }
17978
+ return null;
17793
17979
  }
17794
17980
  function resolvePushTarget(workspaceDir, existingHubs, newHubs, selector) {
17795
17981
  if (selector) {
17796
17982
  const existingMatch = existingHubs.find(
17797
- (h) => h.hubId === selector || path2.basename(h.hubFolder) === selector
17983
+ (h) => h.hubId === selector || path3.basename(h.hubFolder) === selector
17798
17984
  );
17799
17985
  if (existingMatch) return { ok: true, target: { kind: "existing", hub: existingMatch } };
17800
- const newMatch = newHubs.find((h) => path2.basename(h.hubFolder) === selector);
17986
+ const newMatch = newHubs.find((h) => path3.basename(h.hubFolder) === selector);
17801
17987
  if (newMatch) return { ok: true, target: { kind: "new", hub: newMatch } };
17802
17988
  return { ok: false, reason: "selector_miss" };
17803
17989
  }
@@ -17813,10 +17999,10 @@ function resolvePushTarget(workspaceDir, existingHubs, newHubs, selector) {
17813
17999
  }
17814
18000
  function resolveNewHubForCreate(workspaceDir, existingHubs, newHubs, selector) {
17815
18001
  if (selector) {
17816
- const newMatch = newHubs.find((h) => path2.basename(h.hubFolder) === selector);
18002
+ const newMatch = newHubs.find((h) => path3.basename(h.hubFolder) === selector);
17817
18003
  if (newMatch) return { ok: true, hub: newMatch };
17818
18004
  const existingMatch = existingHubs.find(
17819
- (h) => h.hubId === selector || path2.basename(h.hubFolder) === selector
18005
+ (h) => h.hubId === selector || path3.basename(h.hubFolder) === selector
17820
18006
  );
17821
18007
  if (existingMatch) return { ok: false, reason: "exists", existing: existingMatch };
17822
18008
  return { ok: false, reason: "selector_miss" };
@@ -17843,7 +18029,7 @@ function resolveActiveHubId(args2) {
17843
18029
  const gitRoot = findGitRoot();
17844
18030
  const workspaceDir = workspace?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
17845
18031
  const wsLabel = hubsDirLabel(gitRoot);
17846
- if (!workspaceDir || !fs2.existsSync(workspaceDir)) {
18032
+ if (!workspaceDir || !fs3.existsSync(workspaceDir)) {
17847
18033
  if (selector && UUID_RE2.test(selector)) return selector;
17848
18034
  console.error(`No ${wsLabel}/ directory found. Run \`wayai pull --hub <uuid>\` to fetch a hub, or pass --hub <uuid>.`);
17849
18035
  process.exit(1);
@@ -17851,7 +18037,7 @@ function resolveActiveHubId(args2) {
17851
18037
  const allHubs = scanWorkspaceHubs(workspaceDir);
17852
18038
  if (selector) {
17853
18039
  const match = allHubs.find(
17854
- (h) => h.hubId === selector || path2.basename(h.hubFolder) === selector
18040
+ (h) => h.hubId === selector || path3.basename(h.hubFolder) === selector
17855
18041
  );
17856
18042
  if (match) return match.hubId;
17857
18043
  if (UUID_RE2.test(selector)) return selector;
@@ -17868,7 +18054,7 @@ function resolveActiveHubId(args2) {
17868
18054
  }
17869
18055
  console.error(`Multiple hubs in ${wsLabel}/. Pass --hub <uuid|folder-name> or run from inside a hub folder:`);
17870
18056
  for (const h of previewHubs) {
17871
- console.error(` ${path2.basename(h.hubFolder)} (${h.hubId})`);
18057
+ console.error(` ${path3.basename(h.hubFolder)} (${h.hubId})`);
17872
18058
  }
17873
18059
  process.exit(1);
17874
18060
  }
@@ -17876,12 +18062,12 @@ function localHubEnvironment(hubId) {
17876
18062
  const workspace = detectWorkspace();
17877
18063
  const gitRoot = findGitRoot();
17878
18064
  const workspaceDir = workspace?.workspaceDir ?? (gitRoot ? resolveLayout(gitRoot).hubsDir : null);
17879
- if (!workspaceDir || !fs2.existsSync(workspaceDir)) return null;
18065
+ if (!workspaceDir || !fs3.existsSync(workspaceDir)) return null;
17880
18066
  return scanWorkspaceHubs(workspaceDir).find((h) => h.hubId === hubId)?.hubEnvironment ?? null;
17881
18067
  }
17882
18068
  function safeReaddir(dir) {
17883
18069
  try {
17884
- return fs2.readdirSync(dir).filter((entry) => !entry.startsWith("."));
18070
+ return fs3.readdirSync(dir).filter((entry) => !entry.startsWith("."));
17885
18071
  } catch {
17886
18072
  return [];
17887
18073
  }
@@ -17891,23 +18077,29 @@ var init_workspace = __esm({
17891
18077
  "use strict";
17892
18078
  init_utils();
17893
18079
  init_layout();
18080
+ init_fs_safety();
17894
18081
  }
17895
18082
  });
17896
18083
 
17897
18084
  // src/lib/workspace-manifest.ts
17898
- import * as fs3 from "fs";
17899
- import * as path3 from "path";
18085
+ import * as fs4 from "fs";
18086
+ import * as path4 from "path";
17900
18087
  import * as yaml2 from "js-yaml";
17901
18088
  function workspaceManifestPath(gitRoot) {
17902
- return path3.join(gitRoot, WAYAI_LAYOUT.wsDir, WORKSPACE_MANIFEST_FILE);
18089
+ return path4.join(gitRoot, WAYAI_LAYOUT.wsDir, WORKSPACE_MANIFEST_FILE);
17903
18090
  }
17904
18091
  function rootConfigPath(gitRoot) {
17905
- return path3.join(gitRoot, ROOT_CONFIG_FILE);
18092
+ return path4.join(gitRoot, ROOT_CONFIG_FILE);
17906
18093
  }
17907
18094
  function loadYamlMapping(file) {
18095
+ for (const p of [file, path4.dirname(file)]) {
18096
+ if (isSymlink(p)) {
18097
+ return { kind: "malformed", reason: `${path4.basename(p)} is a symlink \u2014 make it a real ${p === file ? "file" : "directory"}` };
18098
+ }
18099
+ }
17908
18100
  let raw;
17909
18101
  try {
17910
- raw = fs3.readFileSync(file, "utf-8");
18102
+ raw = fs4.readFileSync(file, "utf-8");
17911
18103
  } catch (err) {
17912
18104
  if (err.code === "ENOENT") return { kind: "absent" };
17913
18105
  return { kind: "malformed", reason: err instanceof Error ? err.message : String(err) };
@@ -17932,6 +18124,7 @@ var init_workspace_manifest = __esm({
17932
18124
  "src/lib/workspace-manifest.ts"() {
17933
18125
  "use strict";
17934
18126
  init_layout();
18127
+ init_fs_safety();
17935
18128
  WORKSPACE_MANIFEST_FILE = "wayai.yaml";
17936
18129
  WORKSPACE_MANIFEST_LABEL = `${WAYAI_LAYOUT.wsDir}/${WORKSPACE_MANIFEST_FILE}`;
17937
18130
  ROOT_CONFIG_FILE = ".wayai.yaml";
@@ -17950,8 +18143,7 @@ __export(repo_config_exports, {
17950
18143
  resolveRepoConfig: () => resolveRepoConfig,
17951
18144
  writeRepoConfig: () => writeRepoConfig
17952
18145
  });
17953
- import * as fs4 from "fs";
17954
- import * as path4 from "path";
18146
+ import * as path5 from "path";
17955
18147
  import * as yaml3 from "js-yaml";
17956
18148
  function noticeOnce(key, emit) {
17957
18149
  if (noticed.has(key)) return;
@@ -17989,7 +18181,7 @@ function legacyFieldsIn(load11) {
17989
18181
  if (load11.kind !== "ok") return [];
17990
18182
  return LEGACY_FIELDS.filter((f) => load11.doc[f] !== void 0);
17991
18183
  }
17992
- function resolve2(gitRoot) {
18184
+ function resolve3(gitRoot) {
17993
18185
  const manifestFile = workspaceManifestPath(gitRoot);
17994
18186
  const manifest = declarationFrom(loadWorkspaceManifest(gitRoot));
17995
18187
  if (manifest.kind === "invalid") {
@@ -18098,7 +18290,7 @@ function report(r, posture) {
18098
18290
  function resolveRepoConfig(root) {
18099
18291
  const gitRoot = root ?? findGitRoot();
18100
18292
  if (!gitRoot) return null;
18101
- const r = resolve2(gitRoot);
18293
+ const r = resolve3(gitRoot);
18102
18294
  report(r, "soft");
18103
18295
  return r.kind === "ok" ? r.resolved : null;
18104
18296
  }
@@ -18108,7 +18300,7 @@ function readRepoConfig(root) {
18108
18300
  function readRepoConfigUnlessBlocked(root) {
18109
18301
  const gitRoot = root ?? findGitRoot();
18110
18302
  if (!gitRoot) return null;
18111
- const r = resolve2(gitRoot);
18303
+ const r = resolve3(gitRoot);
18112
18304
  switch (r.kind) {
18113
18305
  case "ok":
18114
18306
  report(r, "soft");
@@ -18123,7 +18315,7 @@ function readRepoConfigUnlessBlocked(root) {
18123
18315
  }
18124
18316
  function requireRepoConfig(root) {
18125
18317
  const gitRoot = root ?? findGitRoot();
18126
- const r = gitRoot ? resolve2(gitRoot) : { kind: "none" };
18318
+ const r = gitRoot ? resolve3(gitRoot) : { kind: "none" };
18127
18319
  report(r, "hard");
18128
18320
  if (r.kind === "ok") return r.resolved.config;
18129
18321
  return process.exit(1);
@@ -18131,7 +18323,7 @@ function requireRepoConfig(root) {
18131
18323
  function readRepoScopeBlocker(root) {
18132
18324
  const gitRoot = root ?? findGitRoot();
18133
18325
  if (!gitRoot) return null;
18134
- const r = resolve2(gitRoot);
18326
+ const r = resolve3(gitRoot);
18135
18327
  if (r.kind === "conflict") {
18136
18328
  return {
18137
18329
  kind: "conflict",
@@ -18168,6 +18360,7 @@ function writeRepoConfig(config, root) {
18168
18360
  if (!gitRoot) {
18169
18361
  throw expected("Not inside a git repository. Run `git init` first.");
18170
18362
  }
18363
+ readRealFileOrThrow(gitRoot, workspaceManifestPath(gitRoot));
18171
18364
  const stale = declarationFrom(loadYamlMapping(rootConfigPath(gitRoot)));
18172
18365
  if (stale.kind === "declared" && stale.config.organization_id !== config.organization_id) {
18173
18366
  throw expected(rebindRefusal(config.organization_id, stale.config.organization_id));
@@ -18183,12 +18376,8 @@ function writeRepoConfig(config, root) {
18183
18376
  if (config.organization_name?.trim()) doc.organization_name = config.organization_name.trim();
18184
18377
  Object.assign(doc, rest);
18185
18378
  const file = workspaceManifestPath(gitRoot);
18186
- fs4.mkdirSync(path4.dirname(file), { recursive: true });
18187
- fs4.writeFileSync(
18188
- file,
18189
- yaml3.dump(doc, { lineWidth: -1, quotingType: '"', forceQuotes: false }),
18190
- "utf-8"
18191
- );
18379
+ requireRealSubdirNoSymlink(gitRoot, path5.dirname(file), true);
18380
+ writeFileNoFollow(gitRoot, file, Buffer.from(yaml3.dump(doc, { lineWidth: -1, quotingType: '"', forceQuotes: false }), "utf-8"));
18192
18381
  return file;
18193
18382
  }
18194
18383
  var LEGACY_FIELDS, noticed, RECONCILE_REMEDY;
@@ -18196,6 +18385,7 @@ var init_repo_config = __esm({
18196
18385
  "src/lib/repo-config.ts"() {
18197
18386
  "use strict";
18198
18387
  init_expected();
18388
+ init_fs_safety();
18199
18389
  init_workspace();
18200
18390
  init_utils();
18201
18391
  init_workspace_manifest();
@@ -18210,7 +18400,7 @@ var init_repo_config = __esm({
18210
18400
 
18211
18401
  // src/lib/utils.ts
18212
18402
  import * as fs5 from "fs";
18213
- import * as path5 from "path";
18403
+ import * as path6 from "path";
18214
18404
  import * as readline from "readline";
18215
18405
  function prompt(question) {
18216
18406
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -18264,7 +18454,7 @@ function readStdin() {
18264
18454
  }
18265
18455
  function resolveHubYamlPath(hubFolder) {
18266
18456
  for (const filename of ["hub.yaml", "wayai.yaml"]) {
18267
- const yamlPath = path5.join(hubFolder, filename);
18457
+ const yamlPath = path6.join(hubFolder, filename);
18268
18458
  if (fs5.existsSync(yamlPath)) return yamlPath;
18269
18459
  }
18270
18460
  return null;
@@ -18730,17 +18920,17 @@ var init_registry = __esm({
18730
18920
  });
18731
18921
 
18732
18922
  // src/lib/version-cache.ts
18733
- import { existsSync as existsSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
18734
- import { dirname as dirname4, join as join6 } from "path";
18923
+ import { existsSync as existsSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
18924
+ import { dirname as dirname6, join as join7 } from "path";
18735
18925
  import { homedir as homedir2 } from "os";
18736
18926
  function getVersionCachePath(filename = CLI_CACHE_FILE) {
18737
- return join6(homedir2(), ".wayai", filename);
18927
+ return join7(homedir2(), ".wayai", filename);
18738
18928
  }
18739
18929
  function readVersionCache(filename = CLI_CACHE_FILE) {
18740
18930
  try {
18741
18931
  const path35 = getVersionCachePath(filename);
18742
18932
  if (!existsSync3(path35)) return null;
18743
- const parsed = JSON.parse(readFileSync5(path35, "utf-8"));
18933
+ const parsed = JSON.parse(readFileSync6(path35, "utf-8"));
18744
18934
  if (typeof parsed.lastCheck !== "number") return null;
18745
18935
  if (parsed.latest !== null && typeof parsed.latest !== "string") return null;
18746
18936
  return parsed;
@@ -18761,7 +18951,7 @@ function isVersionCacheStale(filename = CLI_CACHE_FILE, maxAgeMs = MAX_AGE_24H_M
18761
18951
  }
18762
18952
  function writeVersionCache(filename, cache) {
18763
18953
  const path35 = getVersionCachePath(filename);
18764
- const dir = dirname4(path35);
18954
+ const dir = dirname6(path35);
18765
18955
  if (!existsSync3(dir)) mkdirSync2(dir, { recursive: true });
18766
18956
  writeFileSync2(path35, JSON.stringify(cache));
18767
18957
  }
@@ -18780,8 +18970,8 @@ var init_version_cache = __esm({
18780
18970
  });
18781
18971
 
18782
18972
  // src/lib/skill-version.ts
18783
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
18784
- import { join as join7 } from "path";
18973
+ import { existsSync as existsSync4, readFileSync as readFileSync7 } from "fs";
18974
+ import { join as join8 } from "path";
18785
18975
  import * as yaml4 from "js-yaml";
18786
18976
  function skillInstallPaths(skillName) {
18787
18977
  return HARNESS_SKILL_DIRS.map((dir) => `${dir}/skills/${skillName}/${SKILL_FILENAME}`);
@@ -18802,11 +18992,11 @@ function parseFrontmatterVersion(content) {
18802
18992
  function findInstalledSkills(projectRoot, paths = SKILL_INSTALL_PATHS) {
18803
18993
  const found = [];
18804
18994
  for (const rel of paths) {
18805
- const path35 = join7(projectRoot, rel);
18995
+ const path35 = join8(projectRoot, rel);
18806
18996
  if (!existsSync4(path35)) continue;
18807
18997
  let version = null;
18808
18998
  try {
18809
- version = parseFrontmatterVersion(readFileSync6(path35, "utf-8"));
18999
+ version = parseFrontmatterVersion(readFileSync7(path35, "utf-8"));
18810
19000
  } catch {
18811
19001
  }
18812
19002
  found.push({ path: path35, version });
@@ -18841,13 +19031,13 @@ __export(config_exports, {
18841
19031
  writeConfig: () => writeConfig
18842
19032
  });
18843
19033
  import * as fs6 from "fs";
18844
- import * as path6 from "path";
19034
+ import * as path7 from "path";
18845
19035
  import * as os from "os";
18846
19036
  function configDir() {
18847
- return path6.join(os.homedir(), ".wayai");
19037
+ return path7.join(os.homedir(), ".wayai");
18848
19038
  }
18849
19039
  function configPath() {
18850
- return path6.join(configDir(), "config.json");
19040
+ return path7.join(configDir(), "config.json");
18851
19041
  }
18852
19042
  function getConfigPath() {
18853
19043
  return configPath();
@@ -18900,13 +19090,13 @@ var init_config = __esm({
18900
19090
 
18901
19091
  // src/lib/token-store.ts
18902
19092
  import * as fs7 from "fs";
18903
- import * as path7 from "path";
19093
+ import * as path8 from "path";
18904
19094
  import * as os2 from "os";
18905
19095
  function configDir2() {
18906
- return path7.join(os2.homedir(), ".wayai");
19096
+ return path8.join(os2.homedir(), ".wayai");
18907
19097
  }
18908
19098
  function configPath2() {
18909
- return path7.join(configDir2(), "config.json");
19099
+ return path8.join(configDir2(), "config.json");
18910
19100
  }
18911
19101
  function manualCleanupCommand(account) {
18912
19102
  if (process.platform === "darwin") {
@@ -19406,26 +19596,26 @@ __export(skill_symlink_exports, {
19406
19596
  healClaudeSkillLink: () => healClaudeSkillLink,
19407
19597
  healSkillLinkForCommand: () => healSkillLinkForCommand
19408
19598
  });
19409
- import { existsSync as existsSync7, lstatSync, mkdirSync as mkdirSync5, rmSync, symlinkSync } from "fs";
19410
- import { join as join10 } from "path";
19599
+ import { existsSync as existsSync7, lstatSync as lstatSync3, mkdirSync as mkdirSync5, rmSync as rmSync2, symlinkSync } from "fs";
19600
+ import { join as join11 } from "path";
19411
19601
  function healClaudeSkillLink(root) {
19412
19602
  try {
19413
- const source = join10(root, ".agents", "skills", SKILL_NAME);
19414
- if (!existsSync7(join10(source, SKILL_FILENAME))) return false;
19415
- const link = join10(root, ".claude", "skills", SKILL_NAME);
19416
- if (existsSync7(join10(link, SKILL_FILENAME))) return false;
19603
+ const source = join11(root, ".agents", "skills", SKILL_NAME);
19604
+ if (!existsSync7(join11(source, SKILL_FILENAME))) return false;
19605
+ const link = join11(root, ".claude", "skills", SKILL_NAME);
19606
+ if (existsSync7(join11(link, SKILL_FILENAME))) return false;
19417
19607
  let entry = null;
19418
19608
  try {
19419
- entry = lstatSync(link);
19609
+ entry = lstatSync3(link);
19420
19610
  } catch {
19421
19611
  entry = null;
19422
19612
  }
19423
19613
  if (entry) {
19424
19614
  if (!entry.isSymbolicLink()) return false;
19425
- rmSync(link, { force: true });
19615
+ rmSync2(link, { force: true });
19426
19616
  }
19427
- mkdirSync5(join10(root, ".claude", "skills"), { recursive: true });
19428
- symlinkSync(join10("..", "..", ".agents", "skills", SKILL_NAME), link, "dir");
19617
+ mkdirSync5(join11(root, ".claude", "skills"), { recursive: true });
19618
+ symlinkSync(join11("..", "..", ".agents", "skills", SKILL_NAME), link, "dir");
19429
19619
  return true;
19430
19620
  } catch {
19431
19621
  return false;
@@ -19709,7 +19899,7 @@ var init_base_id = __esm({
19709
19899
  // src/lib/worktree-scope.ts
19710
19900
  import { execFileSync as execFileSync2 } from "child_process";
19711
19901
  import * as fs8 from "fs";
19712
- import * as path8 from "path";
19902
+ import * as path9 from "path";
19713
19903
  import * as yaml5 from "js-yaml";
19714
19904
  function axisNoun(axis) {
19715
19905
  return AXES[axis].noun;
@@ -19724,7 +19914,7 @@ function resolveGitDir() {
19724
19914
  encoding: "utf-8",
19725
19915
  stdio: ["pipe", "pipe", "pipe"]
19726
19916
  }).trim();
19727
- gitDir = raw ? path8.resolve(cwd, raw) : null;
19917
+ gitDir = raw ? path9.resolve(cwd, raw) : null;
19728
19918
  } catch {
19729
19919
  gitDir = null;
19730
19920
  }
@@ -19733,12 +19923,12 @@ function resolveGitDir() {
19733
19923
  }
19734
19924
  function getScopePath() {
19735
19925
  const gitDir = resolveGitDir();
19736
- return gitDir ? path8.join(gitDir, SCOPE_FILE) : null;
19926
+ return gitDir ? path9.join(gitDir, SCOPE_FILE) : null;
19737
19927
  }
19738
19928
  function legacyPaths() {
19739
19929
  const gitDir = resolveGitDir();
19740
19930
  if (!gitDir) return [];
19741
- return AXIS_ORDER.flatMap((axis) => AXES[axis].legacyFiles).map((f) => path8.join(gitDir, f));
19931
+ return AXIS_ORDER.flatMap((axis) => AXES[axis].legacyFiles).map((f) => path9.join(gitDir, f));
19742
19932
  }
19743
19933
  function emptyScope() {
19744
19934
  return { hubs: [], bases: [] };
@@ -19762,7 +19952,7 @@ function readLegacyAxis(axis) {
19762
19952
  for (const filename of legacyFiles) {
19763
19953
  let raw;
19764
19954
  try {
19765
- raw = fs8.readFileSync(path8.join(gitDir, filename), "utf-8");
19955
+ raw = fs8.readFileSync(path9.join(gitDir, filename), "utf-8");
19766
19956
  } catch (err) {
19767
19957
  if (err.code === "ENOENT") continue;
19768
19958
  throw err;
@@ -19982,8 +20172,13 @@ function buildSkillState() {
19982
20172
  }
19983
20173
  function buildWorkspaceState(resolved) {
19984
20174
  if (!resolved) return { scoped: false, path: null, hub_count: 0 };
19985
- const ws = detectWorkspace();
19986
- const hubCount = ws ? scanWorkspaceHubs(ws.workspaceDir).length : 0;
20175
+ let hubCount = 0;
20176
+ try {
20177
+ const ws = detectWorkspace();
20178
+ hubCount = ws ? scanWorkspaceHubs(ws.workspaceDir).length : 0;
20179
+ } catch {
20180
+ hubCount = 0;
20181
+ }
19987
20182
  return { scoped: true, path: resolved.path, hub_count: hubCount };
19988
20183
  }
19989
20184
  async function statusCommand(args2, pkg2) {
@@ -20221,12 +20416,12 @@ __export(init_exports, {
20221
20416
  parseArgs: () => parseArgs3
20222
20417
  });
20223
20418
  import { mkdirSync as mkdirSync6 } from "fs";
20224
- import path9 from "path";
20419
+ import path10 from "path";
20225
20420
  function ensureHubsDir(gitRoot) {
20226
20421
  const hubsDir = resolveLayout(gitRoot).hubsDir;
20227
20422
  if (isDirectory(hubsDir)) return null;
20228
20423
  mkdirSync6(hubsDir, { recursive: true });
20229
- return `${path9.relative(gitRoot, hubsDir)}/`;
20424
+ return `${path10.relative(gitRoot, hubsDir)}/`;
20230
20425
  }
20231
20426
  function parseArgs3(args2) {
20232
20427
  let orgId;
@@ -20355,118 +20550,8 @@ var init_init = __esm({
20355
20550
  }
20356
20551
  });
20357
20552
 
20358
- // src/lib/fs-safety.ts
20359
- import * as fs9 from "fs";
20360
- import * as path10 from "path";
20361
- function fileHasBytes(abs, data) {
20362
- let st;
20363
- try {
20364
- st = fs9.lstatSync(abs);
20365
- } catch {
20366
- return false;
20367
- }
20368
- if (!st.isFile() || st.isSymbolicLink()) return false;
20369
- try {
20370
- return fs9.readFileSync(abs).equals(data);
20371
- } catch {
20372
- return false;
20373
- }
20374
- }
20375
- function realpathContained(root, abs) {
20376
- let realRoot;
20377
- let realAbs;
20378
- try {
20379
- realRoot = fs9.realpathSync(root);
20380
- realAbs = fs9.realpathSync(abs);
20381
- } catch {
20382
- return false;
20383
- }
20384
- return realAbs === realRoot || realAbs.startsWith(realRoot + path10.sep);
20385
- }
20386
- function mkdirTolerateRace(dir) {
20387
- try {
20388
- fs9.mkdirSync(dir);
20389
- return true;
20390
- } catch (err) {
20391
- if (err.code !== "EEXIST") throw err;
20392
- let st;
20393
- try {
20394
- st = fs9.lstatSync(dir);
20395
- } catch {
20396
- st = void 0;
20397
- }
20398
- return !!st?.isDirectory();
20399
- }
20400
- }
20401
- function ensureRealSubdirNoSymlink(root, target, create) {
20402
- const rootResolved = path10.resolve(root);
20403
- const targetResolved = path10.resolve(target);
20404
- if (targetResolved !== rootResolved && !targetResolved.startsWith(rootResolved + path10.sep)) return false;
20405
- try {
20406
- if (!fs9.lstatSync(rootResolved).isDirectory()) return false;
20407
- } catch {
20408
- return false;
20409
- }
20410
- const rel = path10.relative(rootResolved, targetResolved);
20411
- if (rel === "") return true;
20412
- let cur = rootResolved;
20413
- for (const segment of rel.split(path10.sep)) {
20414
- cur = path10.join(cur, segment);
20415
- let st;
20416
- try {
20417
- st = fs9.lstatSync(cur);
20418
- } catch {
20419
- st = void 0;
20420
- }
20421
- if (st) {
20422
- if (!st.isDirectory()) return false;
20423
- } else if (create) {
20424
- if (!mkdirTolerateRace(cur)) return false;
20425
- } else {
20426
- return true;
20427
- }
20428
- }
20429
- return true;
20430
- }
20431
- function readFileNoFollow(root, abs) {
20432
- if (!ensureRealSubdirNoSymlink(root, path10.dirname(abs), false)) return null;
20433
- try {
20434
- if (!fs9.lstatSync(abs).isFile()) return null;
20435
- return fs9.readFileSync(abs);
20436
- } catch {
20437
- return null;
20438
- }
20439
- }
20440
- function writeFileNoFollow(root, abs, data) {
20441
- const parent = path10.dirname(abs);
20442
- if (!ensureRealSubdirNoSymlink(root, parent, true)) return false;
20443
- let st;
20444
- try {
20445
- st = fs9.lstatSync(abs);
20446
- } catch {
20447
- }
20448
- if (st?.isSymbolicLink()) fs9.rmSync(abs);
20449
- fs9.writeFileSync(abs, data);
20450
- return true;
20451
- }
20452
- function createFileNoFollow(root, abs, data) {
20453
- if (!ensureRealSubdirNoSymlink(root, path10.dirname(abs), true)) return "refused";
20454
- try {
20455
- fs9.writeFileSync(abs, data, { flag: "wx" });
20456
- } catch (err) {
20457
- if (err.code === "EEXIST") return "exists";
20458
- throw err;
20459
- }
20460
- return "created";
20461
- }
20462
- var init_fs_safety = __esm({
20463
- "src/lib/fs-safety.ts"() {
20464
- "use strict";
20465
- }
20466
- });
20467
-
20468
20553
  // src/lib/resource-files.ts
20469
- import * as fs10 from "fs";
20554
+ import * as fs9 from "fs";
20470
20555
  import * as path11 from "path";
20471
20556
  import * as crypto3 from "crypto";
20472
20557
  function isBinaryFile(filename) {
@@ -20513,14 +20598,16 @@ function computeHash(data) {
20513
20598
  }
20514
20599
  function scanResourceFiles(dir, prefix = "") {
20515
20600
  const files = [];
20516
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
20601
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
20517
20602
  for (const entry of entries) {
20518
20603
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
20519
- if (entry.isDirectory()) {
20604
+ if (entry.isSymbolicLink()) {
20605
+ throw symlinkRefusal(relPath, "file or folder");
20606
+ } else if (entry.isDirectory()) {
20520
20607
  files.push(...scanResourceFiles(path11.join(dir, entry.name), relPath));
20521
20608
  } else if (entry.isFile()) {
20522
20609
  const fullPath = path11.join(dir, entry.name);
20523
- const stat2 = fs10.statSync(fullPath);
20610
+ const stat2 = fs9.statSync(fullPath);
20524
20611
  if (stat2.size > MAX_RESOURCE_FILE_SIZE3) {
20525
20612
  console.warn(` Warning: skipping ${relPath} (${(stat2.size / 1024 / 1024).toFixed(1)}MB exceeds 10MB limit)`);
20526
20613
  continue;
@@ -20530,7 +20617,7 @@ function scanResourceFiles(dir, prefix = "") {
20530
20617
  mime_type: guessMimeType(entry.name),
20531
20618
  file_size: stat2.size
20532
20619
  };
20533
- const data = fs10.readFileSync(fullPath);
20620
+ const data = fs9.readFileSync(fullPath);
20534
20621
  fileEntry.hash = computeHash(data);
20535
20622
  if (isBinaryFile(entry.name)) {
20536
20623
  fileEntry.content_base64 = data.toString("base64");
@@ -20590,7 +20677,7 @@ function writeResourceFileTree(resDir, files, root, log) {
20590
20677
  return;
20591
20678
  }
20592
20679
  if (files.length === 0) {
20593
- if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set(), root, log);
20680
+ if (fs9.existsSync(resDir)) cleanOrphanFiles(resDir, "", /* @__PURE__ */ new Set(), root, log);
20594
20681
  return;
20595
20682
  }
20596
20683
  const writtenPaths = /* @__PURE__ */ new Set();
@@ -20611,18 +20698,18 @@ function writeResourceFileTree(resDir, files, root, log) {
20611
20698
  writtenPaths.delete(file.path);
20612
20699
  }
20613
20700
  }
20614
- if (fs10.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths, root, log);
20701
+ if (fs9.existsSync(resDir)) cleanOrphanFiles(resDir, "", writtenPaths, root, log);
20615
20702
  }
20616
20703
  function cleanOrphanFiles(dir, prefix, writtenPaths, root, log) {
20617
- const entries = fs10.readdirSync(dir, { withFileTypes: true });
20704
+ const entries = fs9.readdirSync(dir, { withFileTypes: true });
20618
20705
  for (const entry of entries) {
20619
20706
  const relPath = prefix ? `${prefix}/${entry.name}` : entry.name;
20620
20707
  const fullPath = path11.join(dir, entry.name);
20621
20708
  if (entry.isDirectory()) {
20622
20709
  cleanOrphanFiles(fullPath, relPath, writtenPaths, root, log);
20623
- if (fs10.readdirSync(fullPath).length === 0) fs10.rmdirSync(fullPath);
20710
+ if (fs9.readdirSync(fullPath).length === 0) fs9.rmdirSync(fullPath);
20624
20711
  } else if (!writtenPaths.has(relPath)) {
20625
- fs10.unlinkSync(fullPath);
20712
+ fs9.unlinkSync(fullPath);
20626
20713
  if (log && root) log.removed.push(path11.relative(root, fullPath));
20627
20714
  }
20628
20715
  }
@@ -20643,8 +20730,8 @@ async function downloadBinaryFiles(resDir, files, root, log) {
20643
20730
  }
20644
20731
  if (file.hash) {
20645
20732
  try {
20646
- const st = fs10.lstatSync(filePath);
20647
- if (st.isFile() && !st.isSymbolicLink() && computeHash(fs10.readFileSync(filePath)) === file.hash) continue;
20733
+ const st = fs9.lstatSync(filePath);
20734
+ if (st.isFile() && !st.isSymbolicLink() && computeHash(fs9.readFileSync(filePath)) === file.hash) continue;
20648
20735
  } catch {
20649
20736
  }
20650
20737
  }
@@ -20698,7 +20785,7 @@ var init_resource_files = __esm({
20698
20785
  });
20699
20786
 
20700
20787
  // src/lib/eval-attachments.ts
20701
- import * as fs11 from "fs";
20788
+ import * as fs10 from "fs";
20702
20789
  import * as path12 from "path";
20703
20790
  function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
20704
20791
  const raw = turn.attachments;
@@ -20722,7 +20809,7 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
20722
20809
  }
20723
20810
  let stat2;
20724
20811
  try {
20725
- stat2 = fs11.statSync(abs);
20812
+ stat2 = fs10.statSync(abs);
20726
20813
  } catch {
20727
20814
  }
20728
20815
  if (!stat2?.isFile()) {
@@ -20736,7 +20823,7 @@ function resolveTurnAttachments(hubFolder, turn, label, bytesByHash) {
20736
20823
  `${label}: attachment "${entry}" is ${(stat2.size / 1024 / 1024).toFixed(1)}MB, over the 10MB limit.`
20737
20824
  );
20738
20825
  }
20739
- const data = fs11.readFileSync(abs);
20826
+ const data = fs10.readFileSync(abs);
20740
20827
  const hash = computeHash(data);
20741
20828
  const fileName = path12.basename(abs);
20742
20829
  const mimeType = guessMimeType(fileName) ?? "application/octet-stream";
@@ -20844,16 +20931,16 @@ async function downloadEvalAttachments(hubFolder, downloads) {
20844
20931
  console.warn(` Warning: ${ATTACHMENTS_DIR}/ is not reachable inside the hub folder without a symlink; skipping attachment sync.`);
20845
20932
  return result;
20846
20933
  }
20847
- if (!fs11.existsSync(attachDir)) return result;
20934
+ if (!fs10.existsSync(attachDir)) return result;
20848
20935
  for (const dl of downloads) {
20849
20936
  const abs = path12.resolve(hubFolder, dl.relPath);
20850
20937
  if (abs !== attachDir && !abs.startsWith(attachDir + path12.sep)) continue;
20851
20938
  let existing;
20852
20939
  try {
20853
- existing = fs11.lstatSync(abs);
20940
+ existing = fs10.lstatSync(abs);
20854
20941
  } catch {
20855
20942
  }
20856
- if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs11.readFileSync(abs)) === dl.hash) {
20943
+ if (existing?.isFile() && !existing.isSymbolicLink() && computeHash(fs10.readFileSync(abs)) === dl.hash) {
20857
20944
  continue;
20858
20945
  }
20859
20946
  try {
@@ -20871,11 +20958,11 @@ async function downloadEvalAttachments(hubFolder, downloads) {
20871
20958
  }
20872
20959
  }
20873
20960
  const keep = new Set(downloads.map((d) => path12.resolve(hubFolder, d.relPath)));
20874
- for (const name of fs11.readdirSync(attachDir)) {
20961
+ for (const name of fs10.readdirSync(attachDir)) {
20875
20962
  const abs = path12.join(attachDir, name);
20876
- const st = fs11.lstatSync(abs);
20963
+ const st = fs10.lstatSync(abs);
20877
20964
  if ((st.isFile() || st.isSymbolicLink()) && !keep.has(abs)) {
20878
- fs11.rmSync(abs);
20965
+ fs10.rmSync(abs);
20879
20966
  result.removed.push(path12.relative(hubFolder, abs));
20880
20967
  }
20881
20968
  }
@@ -20893,15 +20980,28 @@ var init_eval_attachments = __esm({
20893
20980
  });
20894
20981
 
20895
20982
  // src/lib/parser.ts
20896
- import * as fs12 from "fs";
20983
+ import * as fs11 from "fs";
20897
20984
  import * as path13 from "path";
20898
20985
  import * as yaml6 from "js-yaml";
20986
+ function refuseConsumedLink(hubFolder, dir, entry) {
20987
+ if (!entry.isSymbolicLink()) return;
20988
+ const abs = path13.join(dir, entry.name);
20989
+ let toDir = false;
20990
+ try {
20991
+ toDir = fs11.statSync(abs).isDirectory();
20992
+ } catch {
20993
+ }
20994
+ if (toDir || entry.name.endsWith(".yaml")) {
20995
+ throw symlinkRefusal(path13.relative(hubFolder, abs), "file or folder");
20996
+ }
20997
+ }
20899
20998
  function parseHubFolder(hubFolder, opts) {
20999
+ requireRealHubFolder(hubFolder, false);
20900
21000
  const yamlPath = resolveHubYamlPath(hubFolder);
20901
21001
  if (!yamlPath) {
20902
21002
  throw expected(`hub.yaml not found in ${hubFolder}`);
20903
21003
  }
20904
- const yamlContent = fs12.readFileSync(yamlPath, "utf-8");
21004
+ const yamlContent = readRealFileOrThrow(hubFolder, yamlPath) ?? "";
20905
21005
  let config;
20906
21006
  try {
20907
21007
  config = yaml6.load(yamlContent);
@@ -20929,8 +21029,14 @@ function parseHubFolder(hubFolder, opts) {
20929
21029
  if (typeof resolved.instructions === "string" && resolved.instructions.endsWith(".md")) {
20930
21030
  const instrValue = resolved.instructions;
20931
21031
  const instructionsPath = instrValue.startsWith("agents/") ? path13.join(hubFolder, instrValue) : path13.join(hubFolder, "agents", instrValue);
20932
- if (fs12.existsSync(instructionsPath)) {
20933
- resolved.instructions = fs12.readFileSync(instructionsPath, "utf-8");
21032
+ if (!isUnder(path13.join(hubFolder, "agents"), instructionsPath)) {
21033
+ throw workspaceRefusal(
21034
+ `Agent instructions path "${instrValue}" (agent "${agent.name}") must stay inside agents/.`
21035
+ );
21036
+ }
21037
+ const instructions = readRealFileOrThrow(hubFolder, instructionsPath);
21038
+ if (instructions !== null) {
21039
+ resolved.instructions = instructions;
20934
21040
  } else {
20935
21041
  throw expected(
20936
21042
  `Agent instructions file not found: ${instructionsPath} (referenced by agent "${agent.name}")`
@@ -20938,9 +21044,8 @@ function parseHubFolder(hubFolder, opts) {
20938
21044
  }
20939
21045
  } else if (resolved.instructions === void 0 && typeof agent.name === "string") {
20940
21046
  const conventionPath = path13.join(hubFolder, "agents", `${slugify(agent.name)}.md`);
20941
- if (fs12.existsSync(conventionPath)) {
20942
- resolved.instructions = fs12.readFileSync(conventionPath, "utf-8");
20943
- }
21047
+ const instructions = readRealFileOrThrow(hubFolder, conventionPath);
21048
+ if (instructions !== null) resolved.instructions = instructions;
20944
21049
  }
20945
21050
  return resolved;
20946
21051
  });
@@ -20994,18 +21099,20 @@ function parseHubFolder(hubFolder, opts) {
20994
21099
  }
20995
21100
  function scanEvalYamlFiles(hubFolder, bytesByHash) {
20996
21101
  const evalsDir = path13.join(hubFolder, "evals");
20997
- if (!fs12.existsSync(evalsDir)) return [];
21102
+ if (!fs11.existsSync(evalsDir)) return [];
20998
21103
  const evals = [];
20999
21104
  const seen = /* @__PURE__ */ new Set();
21000
- const topEntries = fs12.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21105
+ const topEntries = fs11.readdirSync(evalsDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21001
21106
  for (const entry of topEntries) {
21107
+ refuseConsumedLink(hubFolder, evalsDir, entry);
21002
21108
  if (entry.isFile() && entry.name.endsWith(".yaml")) {
21003
21109
  collectEval(evals, seen, path13.join(evalsDir, entry.name), `evals/${entry.name}`, null, hubFolder, bytesByHash);
21004
21110
  } else if (entry.isDirectory()) {
21005
21111
  const setName = entry.name;
21006
21112
  const setDir = path13.join(evalsDir, setName);
21007
- const setEntries = fs12.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21113
+ const setEntries = fs11.readdirSync(setDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21008
21114
  for (const sub of setEntries) {
21115
+ refuseConsumedLink(hubFolder, setDir, sub);
21009
21116
  if (sub.isDirectory()) {
21010
21117
  throw expected(
21011
21118
  `Nested scenario sets are not supported: ${path13.relative(hubFolder, path13.join(setDir, sub.name))}. Move scenarios up to evals/${setName}/.`
@@ -21033,7 +21140,7 @@ function collectEval(evals, seen, filePath, relPath, setName, hubFolder, bytesBy
21033
21140
  evals.push(evalEntry);
21034
21141
  }
21035
21142
  function parseEvalYaml(filePath, relPath, setName, hubFolder, bytesByHash) {
21036
- const content = fs12.readFileSync(filePath, "utf-8");
21143
+ const content = fs11.readFileSync(filePath, "utf-8");
21037
21144
  let raw;
21038
21145
  try {
21039
21146
  raw = yaml6.load(content);
@@ -21136,11 +21243,12 @@ function parseFixtureField(raw, label, key) {
21136
21243
  }
21137
21244
  function scanJourneyYamlFiles(hubFolder, bytesByHash) {
21138
21245
  const journeysDir = path13.join(hubFolder, "journeys");
21139
- if (!fs12.existsSync(journeysDir)) return [];
21246
+ if (!fs11.existsSync(journeysDir)) return [];
21140
21247
  const journeys = [];
21141
21248
  const seen = /* @__PURE__ */ new Set();
21142
- const entries = fs12.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21249
+ const entries = fs11.readdirSync(journeysDir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
21143
21250
  for (const entry of entries) {
21251
+ refuseConsumedLink(hubFolder, journeysDir, entry);
21144
21252
  if (entry.isDirectory()) {
21145
21253
  throw expected(
21146
21254
  `Subfolders are not supported under journeys/: ${path13.relative(hubFolder, path13.join(journeysDir, entry.name))}. A journey owns its own managed scenario set \u2014 put each journey in a single journeys/<slug>.yaml file.`
@@ -21160,7 +21268,7 @@ function scanJourneyYamlFiles(hubFolder, bytesByHash) {
21160
21268
  return journeys;
21161
21269
  }
21162
21270
  function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
21163
- const content = fs12.readFileSync(filePath, "utf-8");
21271
+ const content = fs11.readFileSync(filePath, "utf-8");
21164
21272
  let raw;
21165
21273
  try {
21166
21274
  raw = yaml6.load(content);
@@ -21216,13 +21324,13 @@ function parseJourneyYaml(filePath, relPath, hubFolder, bytesByHash) {
21216
21324
  }
21217
21325
  function scanAgentYamlFiles(hubFolder) {
21218
21326
  const agentsDir = path13.join(hubFolder, "agents");
21219
- if (!fs12.existsSync(agentsDir)) return [];
21220
- const yamlFiles = fs12.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
21327
+ if (!fs11.existsSync(agentsDir)) return [];
21328
+ const yamlFiles = fs11.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml")).sort();
21221
21329
  if (yamlFiles.length === 0) return [];
21222
21330
  const agents = [];
21223
21331
  for (const file of yamlFiles) {
21224
21332
  const filePath = path13.join(agentsDir, file);
21225
- const content = fs12.readFileSync(filePath, "utf-8");
21333
+ const content = readRealFileOrThrow(hubFolder, filePath) ?? "";
21226
21334
  let agent;
21227
21335
  try {
21228
21336
  agent = yaml6.load(content);
@@ -21257,7 +21365,8 @@ function parseResources(hubFolder, configResources) {
21257
21365
  if (res.skill_name) resource.skill_name = res.skill_name;
21258
21366
  const resSlug = slugify(resource.name);
21259
21367
  const resDir = path13.join(resourcesDir, resSlug);
21260
- if (fs12.existsSync(resDir)) {
21368
+ requireRealSubdirNoSymlink(hubFolder, resDir, false);
21369
+ if (fs11.existsSync(resDir)) {
21261
21370
  const files = scanResourceFiles(resDir, "");
21262
21371
  if (files.length > 0) {
21263
21372
  resource.files = files;
@@ -21274,6 +21383,7 @@ var init_parser = __esm({
21274
21383
  init_resource_files();
21275
21384
  init_eval_attachments();
21276
21385
  init_expected();
21386
+ init_fs_safety();
21277
21387
  }
21278
21388
  });
21279
21389
 
@@ -21331,7 +21441,7 @@ var init_workspace_files = __esm({
21331
21441
  });
21332
21442
 
21333
21443
  // src/lib/yaml-writer.ts
21334
- import * as fs13 from "fs";
21444
+ import * as fs12 from "fs";
21335
21445
  import * as path15 from "path";
21336
21446
  import * as yaml7 from "js-yaml";
21337
21447
  function writeFileIfChanged(hubFolder, absPath, content, log) {
@@ -21382,11 +21492,11 @@ function buildEvalYamlObject(evalEntry, slug) {
21382
21492
  function writeHubFolder(hubFolder, payload, options = {}) {
21383
21493
  const log = { changed: [], removed: [] };
21384
21494
  const agentsDir = path15.join(hubFolder, "agents");
21385
- if (!fs13.existsSync(hubFolder)) {
21386
- fs13.mkdirSync(hubFolder, { recursive: true });
21495
+ if (!fs12.existsSync(hubFolder)) {
21496
+ fs12.mkdirSync(hubFolder, { recursive: true });
21387
21497
  }
21388
- if (!fs13.existsSync(agentsDir)) {
21389
- fs13.mkdirSync(agentsDir, { recursive: true });
21498
+ if (!fs12.existsSync(agentsDir)) {
21499
+ fs12.mkdirSync(agentsDir, { recursive: true });
21390
21500
  }
21391
21501
  const yamlPayload = buildYamlPayload(payload);
21392
21502
  const agentFiles = extractAgentFiles(payload);
@@ -21401,8 +21511,8 @@ function writeHubFolder(hubFolder, payload, options = {}) {
21401
21511
  if (seeded) log.changed.push(seeded);
21402
21512
  }
21403
21513
  const oldYamlPath = path15.join(hubFolder, "wayai.yaml");
21404
- if (fs13.existsSync(oldYamlPath)) {
21405
- fs13.unlinkSync(oldYamlPath);
21514
+ if (fs12.existsSync(oldYamlPath)) {
21515
+ fs12.unlinkSync(oldYamlPath);
21406
21516
  log.removed.push(path15.relative(hubFolder, oldYamlPath));
21407
21517
  }
21408
21518
  const yamlSlugs = writeAgentYamlFiles(hubFolder, agentsDir, payload.agents || [], log);
@@ -21411,20 +21521,20 @@ function writeHubFolder(hubFolder, payload, options = {}) {
21411
21521
  mdSlugs.add(slug);
21412
21522
  writeFileIfChanged(hubFolder, path15.join(agentsDir, `${slug}.md`), content, log);
21413
21523
  }
21414
- const existingFiles = fs13.readdirSync(agentsDir);
21524
+ const existingFiles = fs12.readdirSync(agentsDir);
21415
21525
  for (const file of existingFiles) {
21416
21526
  if (file.endsWith(".yaml")) {
21417
21527
  const slug = file.slice(0, -5);
21418
21528
  if (!yamlSlugs.has(slug)) {
21419
21529
  const orphan = path15.join(agentsDir, file);
21420
- fs13.unlinkSync(orphan);
21530
+ fs12.unlinkSync(orphan);
21421
21531
  log.removed.push(path15.relative(hubFolder, orphan));
21422
21532
  }
21423
21533
  } else if (file.endsWith(".md")) {
21424
21534
  const slug = file.slice(0, -3);
21425
21535
  if (!mdSlugs.has(slug)) {
21426
21536
  const orphan = path15.join(agentsDir, file);
21427
- fs13.unlinkSync(orphan);
21537
+ fs12.unlinkSync(orphan);
21428
21538
  log.removed.push(path15.relative(hubFolder, orphan));
21429
21539
  }
21430
21540
  }
@@ -21435,12 +21545,13 @@ function writeHubFolder(hubFolder, payload, options = {}) {
21435
21545
  return log;
21436
21546
  }
21437
21547
  function setPreviewLabelInHubYaml(hubFolder, label) {
21548
+ requireRealHubFolder(hubFolder, false);
21438
21549
  const yamlPath = resolveHubYamlPath(hubFolder);
21439
21550
  if (!yamlPath) return;
21440
- const obj = yaml7.load(fs13.readFileSync(yamlPath, "utf-8")) ?? {};
21551
+ const obj = yaml7.load(readRealFileOrThrow(hubFolder, yamlPath) ?? "") ?? {};
21441
21552
  if (label) obj.preview_label = label;
21442
21553
  else delete obj.preview_label;
21443
- fs13.writeFileSync(yamlPath, yaml7.dump(obj, YAML_DUMP_OPTIONS), "utf-8");
21554
+ writeFileNoFollow(hubFolder, yamlPath, Buffer.from(yaml7.dump(obj, YAML_DUMP_OPTIONS), "utf-8"));
21444
21555
  }
21445
21556
  function buildYamlPayload(payload) {
21446
21557
  const result = {
@@ -21505,17 +21616,17 @@ function extractAgentFiles(payload) {
21505
21616
  function writeEvalYamlFiles(hubFolder, evals, log) {
21506
21617
  const evalsDir = path15.join(hubFolder, "evals");
21507
21618
  if (evals.length === 0) {
21508
- if (fs13.existsSync(evalsDir)) {
21619
+ if (fs12.existsSync(evalsDir)) {
21509
21620
  cleanEvalOrphans(hubFolder, evalsDir, /* @__PURE__ */ new Set(), log);
21510
21621
  try {
21511
- if (fs13.readdirSync(evalsDir).length === 0) fs13.rmdirSync(evalsDir);
21622
+ if (fs12.readdirSync(evalsDir).length === 0) fs12.rmdirSync(evalsDir);
21512
21623
  } catch {
21513
21624
  }
21514
21625
  }
21515
21626
  return;
21516
21627
  }
21517
- if (!fs13.existsSync(evalsDir)) {
21518
- fs13.mkdirSync(evalsDir, { recursive: true });
21628
+ if (!fs12.existsSync(evalsDir)) {
21629
+ fs12.mkdirSync(evalsDir, { recursive: true });
21519
21630
  }
21520
21631
  const writtenRelPaths = /* @__PURE__ */ new Set();
21521
21632
  for (const evalEntry of evals) {
@@ -21527,12 +21638,6 @@ function writeEvalYamlFiles(hubFolder, evals, log) {
21527
21638
  console.warn(` Warning: skipping eval "${evalEntry.name}" (scenario set "${setName}" escapes evals/ \u2014 possible bad backend data)`);
21528
21639
  continue;
21529
21640
  }
21530
- if (setName) {
21531
- const setDir = path15.join(evalsDir, setName);
21532
- if (!fs13.existsSync(setDir)) {
21533
- fs13.mkdirSync(setDir, { recursive: true });
21534
- }
21535
- }
21536
21641
  const yamlContent = yaml7.dump(buildEvalYamlObject(evalEntry, slug), YAML_DUMP_OPTIONS);
21537
21642
  writeFileIfChanged(hubFolder, targetPath, yamlContent, log);
21538
21643
  writtenRelPaths.add(relPath);
@@ -21540,31 +21645,31 @@ function writeEvalYamlFiles(hubFolder, evals, log) {
21540
21645
  cleanEvalOrphans(hubFolder, evalsDir, writtenRelPaths, log);
21541
21646
  }
21542
21647
  function cleanEvalOrphans(hubFolder, evalsDir, writtenRelPaths, log) {
21543
- const entries = fs13.readdirSync(evalsDir, { withFileTypes: true });
21648
+ const entries = fs12.readdirSync(evalsDir, { withFileTypes: true });
21544
21649
  for (const entry of entries) {
21545
21650
  const fullPath = path15.join(evalsDir, entry.name);
21546
21651
  if (entry.isFile()) {
21547
21652
  if (entry.name.endsWith(".yaml") && !writtenRelPaths.has(entry.name)) {
21548
- fs13.unlinkSync(fullPath);
21653
+ fs12.unlinkSync(fullPath);
21549
21654
  log.removed.push(path15.relative(hubFolder, fullPath));
21550
21655
  }
21551
21656
  continue;
21552
21657
  }
21553
21658
  if (entry.isDirectory()) {
21554
21659
  const setName = entry.name;
21555
- const subEntries = fs13.readdirSync(fullPath, { withFileTypes: true });
21660
+ const subEntries = fs12.readdirSync(fullPath, { withFileTypes: true });
21556
21661
  for (const sub of subEntries) {
21557
21662
  if (sub.isFile() && sub.name.endsWith(".yaml")) {
21558
21663
  const relPath = `${setName}/${sub.name}`;
21559
21664
  if (!writtenRelPaths.has(relPath)) {
21560
21665
  const orphan = path15.join(fullPath, sub.name);
21561
- fs13.unlinkSync(orphan);
21666
+ fs12.unlinkSync(orphan);
21562
21667
  log.removed.push(path15.relative(hubFolder, orphan));
21563
21668
  }
21564
21669
  }
21565
21670
  }
21566
21671
  try {
21567
- if (fs13.readdirSync(fullPath).length === 0) fs13.rmdirSync(fullPath);
21672
+ if (fs12.readdirSync(fullPath).length === 0) fs12.rmdirSync(fullPath);
21568
21673
  } catch {
21569
21674
  }
21570
21675
  }
@@ -21603,17 +21708,17 @@ function buildJourneyYamlObject(journeyEntry, slug) {
21603
21708
  function writeJourneyYamlFiles(hubFolder, journeys, log) {
21604
21709
  const journeysDir = path15.join(hubFolder, "journeys");
21605
21710
  if (journeys.length === 0) {
21606
- if (fs13.existsSync(journeysDir)) {
21711
+ if (fs12.existsSync(journeysDir)) {
21607
21712
  cleanJourneyOrphans(hubFolder, journeysDir, /* @__PURE__ */ new Set(), log);
21608
21713
  try {
21609
- if (fs13.readdirSync(journeysDir).length === 0) fs13.rmdirSync(journeysDir);
21714
+ if (fs12.readdirSync(journeysDir).length === 0) fs12.rmdirSync(journeysDir);
21610
21715
  } catch {
21611
21716
  }
21612
21717
  }
21613
21718
  return;
21614
21719
  }
21615
- if (!fs13.existsSync(journeysDir)) {
21616
- fs13.mkdirSync(journeysDir, { recursive: true });
21720
+ if (!fs12.existsSync(journeysDir)) {
21721
+ fs12.mkdirSync(journeysDir, { recursive: true });
21617
21722
  }
21618
21723
  const writtenFiles = /* @__PURE__ */ new Set();
21619
21724
  const usedSlugs = /* @__PURE__ */ new Set();
@@ -21627,11 +21732,11 @@ function writeJourneyYamlFiles(hubFolder, journeys, log) {
21627
21732
  cleanJourneyOrphans(hubFolder, journeysDir, writtenFiles, log);
21628
21733
  }
21629
21734
  function cleanJourneyOrphans(hubFolder, journeysDir, writtenFiles, log) {
21630
- const entries = fs13.readdirSync(journeysDir, { withFileTypes: true });
21735
+ const entries = fs12.readdirSync(journeysDir, { withFileTypes: true });
21631
21736
  for (const entry of entries) {
21632
21737
  if (entry.isFile() && entry.name.endsWith(".yaml") && !writtenFiles.has(entry.name)) {
21633
21738
  const orphan = path15.join(journeysDir, entry.name);
21634
- fs13.unlinkSync(orphan);
21739
+ fs12.unlinkSync(orphan);
21635
21740
  log.removed.push(path15.relative(hubFolder, orphan));
21636
21741
  }
21637
21742
  }
@@ -21645,12 +21750,12 @@ function writeResourceFiles(hubFolder, resources, log) {
21645
21750
  const resDir = path15.join(resourcesDir, resSlug);
21646
21751
  writeResourceFileTree(resDir, resource.files || [], hubFolder, log);
21647
21752
  }
21648
- if (fs13.existsSync(resourcesDir)) {
21649
- const existingDirs = fs13.readdirSync(resourcesDir, { withFileTypes: true });
21753
+ if (fs12.existsSync(resourcesDir)) {
21754
+ const existingDirs = fs12.readdirSync(resourcesDir, { withFileTypes: true });
21650
21755
  for (const entry of existingDirs) {
21651
21756
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
21652
21757
  const orphanDir = path15.join(resourcesDir, entry.name);
21653
- fs13.rmSync(orphanDir, { recursive: true, force: true });
21758
+ fs12.rmSync(orphanDir, { recursive: true, force: true });
21654
21759
  log.removed.push(`${path15.relative(hubFolder, orphanDir)}/`);
21655
21760
  }
21656
21761
  }
@@ -21687,6 +21792,7 @@ async function materializeHubFolder(hubFolder, payload, options = {}) {
21687
21792
  );
21688
21793
  }
21689
21794
  materialized.add(payload);
21795
+ requireRealHubFolder(hubFolder, true);
21690
21796
  const attachmentDownloads = rewriteEvalAttachmentsToLocalPaths(payload);
21691
21797
  const writeLog = writeHubFolder(hubFolder, payload, options);
21692
21798
  const resourceDownloads = await downloadBinaryResourceFiles(hubFolder, payload);
@@ -21718,6 +21824,7 @@ var init_hub_materializer = __esm({
21718
21824
  init_yaml_writer();
21719
21825
  init_eval_attachments();
21720
21826
  init_resource_files();
21827
+ init_fs_safety();
21721
21828
  init_utils();
21722
21829
  materialized = /* @__PURE__ */ new WeakSet();
21723
21830
  }
@@ -21739,7 +21846,7 @@ var init_terminal_output = __esm({
21739
21846
  });
21740
21847
 
21741
21848
  // src/lib/base-workspace.ts
21742
- import * as fs14 from "fs";
21849
+ import * as fs13 from "fs";
21743
21850
  import * as path17 from "path";
21744
21851
  import * as yaml8 from "js-yaml";
21745
21852
  function readBaseMeta(folder) {
@@ -21757,7 +21864,7 @@ function readBaseMeta(folder) {
21757
21864
  function listBaseFolders(basesDir) {
21758
21865
  let entries;
21759
21866
  try {
21760
- entries = fs14.readdirSync(basesDir).filter((entry) => !entry.startsWith("."));
21867
+ entries = fs13.readdirSync(basesDir).filter((entry) => !entry.startsWith("."));
21761
21868
  } catch {
21762
21869
  return [];
21763
21870
  }
@@ -21776,10 +21883,6 @@ function isProductionFolder(meta) {
21776
21883
  function filterEditableBases(bases) {
21777
21884
  return bases.filter((b) => !isProductionFolder(b.meta));
21778
21885
  }
21779
- function isUnder(parent, child) {
21780
- const rel = path17.relative(parent, child);
21781
- return rel === "" || !rel.startsWith("..") && !path17.isAbsolute(rel);
21782
- }
21783
21886
  function findEnclosingBaseFolder(basesDir, cwd) {
21784
21887
  let dir = path17.resolve(cwd);
21785
21888
  const stop = path17.resolve(basesDir);
@@ -21844,7 +21947,7 @@ function resolveBaseSelectorToId(gitRoot, selector) {
21844
21947
  }
21845
21948
  function hasBaseMetaFile(folder) {
21846
21949
  try {
21847
- return fs14.lstatSync(path17.join(folder, BASE_META_FILE)).isFile();
21950
+ return fs13.lstatSync(path17.join(folder, BASE_META_FILE)).isFile();
21848
21951
  } catch {
21849
21952
  return false;
21850
21953
  }
@@ -21857,6 +21960,7 @@ var init_base_workspace = __esm({
21857
21960
  init_expected();
21858
21961
  init_base_id();
21859
21962
  init_fs_safety();
21963
+ init_fs_safety();
21860
21964
  BASE_META_FILE = "base.yaml";
21861
21965
  }
21862
21966
  });
@@ -22157,7 +22261,7 @@ var init_client = __esm({
22157
22261
  });
22158
22262
 
22159
22263
  // src/data/helpers.ts
22160
- import { readFileSync as readFileSync15 } from "fs";
22264
+ import { readFileSync as readFileSync14 } from "fs";
22161
22265
  function pathSegment(id, label = "id") {
22162
22266
  if (!isPathSafeId(id)) {
22163
22267
  throw expected(`Invalid ${label}: ${JSON.stringify(id)}. An id may use ${PATH_SAFE_ID_RULE}.`);
@@ -22185,7 +22289,7 @@ function parseData(data, flag) {
22185
22289
  let text = data;
22186
22290
  if (source !== void 0) {
22187
22291
  try {
22188
- text = readFileSync15(source, "utf-8");
22292
+ text = readFileSync14(source, "utf-8");
22189
22293
  } catch (e) {
22190
22294
  throw expected(`${prefix}${source}: ${e instanceof Error ? e.message : "could not be read"}`);
22191
22295
  }
@@ -22310,7 +22414,7 @@ var init_types2 = __esm({
22310
22414
  });
22311
22415
 
22312
22416
  // src/data/config-as-code/config-writer.ts
22313
- import * as fs15 from "fs";
22417
+ import * as fs14 from "fs";
22314
22418
  import * as path19 from "path";
22315
22419
  import * as yaml9 from "js-yaml";
22316
22420
  function dump5(value) {
@@ -22345,14 +22449,14 @@ function pruneOrphans(folder, dir, keep, log) {
22345
22449
  if (!ensureRealSubdirNoSymlink(folder, dir, false)) return;
22346
22450
  let entries;
22347
22451
  try {
22348
- entries = fs15.readdirSync(dir);
22452
+ entries = fs14.readdirSync(dir);
22349
22453
  } catch {
22350
22454
  return;
22351
22455
  }
22352
22456
  for (const file of entries) {
22353
22457
  if (!file.endsWith(".yaml") || keep.has(file)) continue;
22354
22458
  const abs = path19.join(dir, file);
22355
- fs15.rmSync(abs);
22459
+ fs14.rmSync(abs);
22356
22460
  log.removed.push(path19.relative(folder, abs));
22357
22461
  }
22358
22462
  }
@@ -22366,7 +22470,7 @@ function metaFileObject(meta) {
22366
22470
  function writeBaseFolder(folder, meta, config) {
22367
22471
  const delta = { changed: [], removed: [] };
22368
22472
  const parent = path19.dirname(folder);
22369
- fs15.mkdirSync(parent, { recursive: true });
22473
+ fs14.mkdirSync(parent, { recursive: true });
22370
22474
  if (!ensureRealSubdirNoSymlink(parent, folder, true)) {
22371
22475
  throw expected(
22372
22476
  `Refusing to write ${folder}: the path crosses a symlink. Remove it and pull again.`
@@ -22393,7 +22497,7 @@ function writeBaseFolder(folder, meta, config) {
22393
22497
  for (const deprecated of DEPRECATED_ENTITY_DIRS) {
22394
22498
  const dir = path19.join(folder, deprecated);
22395
22499
  if (ensureRealSubdirNoSymlink(folder, dir, false)) {
22396
- fs15.rmSync(dir, { recursive: true, force: true });
22500
+ fs14.rmSync(dir, { recursive: true, force: true });
22397
22501
  }
22398
22502
  }
22399
22503
  return delta;
@@ -22545,7 +22649,7 @@ var init_api = __esm({
22545
22649
  });
22546
22650
 
22547
22651
  // src/data/config-as-code/config-parser.ts
22548
- import * as fs16 from "fs";
22652
+ import * as fs15 from "fs";
22549
22653
  import * as path20 from "path";
22550
22654
  import * as yaml10 from "js-yaml";
22551
22655
  function readEntityDir(folder, dir) {
@@ -22554,7 +22658,7 @@ function readEntityDir(folder, dir) {
22554
22658
  }
22555
22659
  if (!isDirectory(dir)) return [];
22556
22660
  const out = [];
22557
- for (const file of fs16.readdirSync(dir).sort()) {
22661
+ for (const file of fs15.readdirSync(dir).sort()) {
22558
22662
  if (!file.endsWith(".yaml")) continue;
22559
22663
  const abs = path20.join(dir, file);
22560
22664
  const bytes = readFileNoFollow(folder, abs);
@@ -22941,7 +23045,7 @@ __export(push_exports, {
22941
23045
  shouldWarnIgnoredPreviewLabel: () => shouldWarnIgnoredPreviewLabel,
22942
23046
  syncAfterPush: () => syncAfterPush
22943
23047
  });
22944
- import * as fs17 from "fs";
23048
+ import * as fs16 from "fs";
22945
23049
  import * as path22 from "path";
22946
23050
  import * as yaml11 from "js-yaml";
22947
23051
  function parseArgs5(args2) {
@@ -23050,13 +23154,14 @@ function printLocalFileChanges(delta) {
23050
23154
  emit(delta.changed, "~");
23051
23155
  }
23052
23156
  async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, remoteConfig) {
23157
+ requireRealHubFolder(hubFolder, false);
23053
23158
  const agentsDir = path22.join(hubFolder, "agents");
23054
23159
  let agentsWithIds = [];
23055
- if (fs17.existsSync(agentsDir)) {
23056
- const yamlFiles = fs17.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
23160
+ if (fs16.existsSync(agentsDir)) {
23161
+ const yamlFiles = fs16.readdirSync(agentsDir).filter((f) => f.endsWith(".yaml"));
23057
23162
  for (const file of yamlFiles) {
23163
+ const content = readRealFileOrThrow(hubFolder, path22.join(agentsDir, file)) ?? "";
23058
23164
  try {
23059
- const content = fs17.readFileSync(path22.join(agentsDir, file), "utf-8");
23060
23165
  const agent = yaml11.load(content);
23061
23166
  if (agent?.id && agent.name) {
23062
23167
  agentsWithIds.push({ id: agent.id, name: agent.name });
@@ -23069,7 +23174,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
23069
23174
  if (agentsWithIds.length === 0) {
23070
23175
  const yamlPath = resolveHubYamlPath(hubFolder);
23071
23176
  if (!yamlPath) return;
23072
- const yamlContent = fs17.readFileSync(yamlPath, "utf-8");
23177
+ const yamlContent = readRealFileOrThrow(hubFolder, yamlPath) ?? "";
23073
23178
  const config = yaml11.load(yamlContent);
23074
23179
  agentsWithIds = (config.agents || []).filter((a) => !!a.id && !!a.name);
23075
23180
  }
@@ -23101,18 +23206,18 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
23101
23206
  }
23102
23207
  }
23103
23208
  if (renames.length === 0) return;
23104
- if (!fs17.existsSync(agentsDir)) return;
23105
- for (const file of fs17.readdirSync(agentsDir)) {
23209
+ if (!fs16.existsSync(agentsDir)) return;
23210
+ for (const file of fs16.readdirSync(agentsDir)) {
23106
23211
  if (file.startsWith("__rename_temp_") && (file.endsWith(".md") || file.endsWith(".yaml"))) {
23107
23212
  console.warn(` Warning: removing orphaned temp file agents/${file}`);
23108
- fs17.unlinkSync(path22.join(agentsDir, file));
23213
+ fs16.unlinkSync(path22.join(agentsDir, file));
23109
23214
  }
23110
23215
  }
23111
23216
  const renameFileIfExists = (dir, oldName, newName) => {
23112
23217
  const oldPath = path22.join(dir, oldName);
23113
23218
  const newPath = path22.join(dir, newName);
23114
- if (!fs17.existsSync(oldPath)) return false;
23115
- fs17.renameSync(oldPath, newPath);
23219
+ if (!fs16.existsSync(oldPath)) return false;
23220
+ fs16.renameSync(oldPath, newPath);
23116
23221
  return true;
23117
23222
  };
23118
23223
  const oldSlugs = new Set(renames.map((r) => r.oldSlug));
@@ -23145,9 +23250,9 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
23145
23250
  }
23146
23251
  } else {
23147
23252
  for (const { oldSlug, newSlug } of renames) {
23148
- const hasOldFile = extensions.some((ext) => fs17.existsSync(path22.join(agentsDir, `${oldSlug}${ext}`)));
23253
+ const hasOldFile = extensions.some((ext) => fs16.existsSync(path22.join(agentsDir, `${oldSlug}${ext}`)));
23149
23254
  if (!hasOldFile) continue;
23150
- const hasNewFile = extensions.some((ext) => fs17.existsSync(path22.join(agentsDir, `${newSlug}${ext}`)));
23255
+ const hasNewFile = extensions.some((ext) => fs16.existsSync(path22.join(agentsDir, `${newSlug}${ext}`)));
23151
23256
  if (hasNewFile) {
23152
23257
  console.warn(` Warning: skipping rename agents/${oldSlug}.* \u2192 agents/${newSlug}.* (target already exists)`);
23153
23258
  continue;
@@ -23162,7 +23267,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
23162
23267
  if (completedRenames.length === 0) return;
23163
23268
  const mainYamlPath = resolveHubYamlPath(hubFolder);
23164
23269
  if (mainYamlPath) {
23165
- const mainYamlContent = fs17.readFileSync(mainYamlPath, "utf-8");
23270
+ const mainYamlContent = readRealFileOrThrow(hubFolder, mainYamlPath) ?? "";
23166
23271
  const substitutionMap = /* @__PURE__ */ new Map();
23167
23272
  for (const { oldSlug, newSlug } of completedRenames) {
23168
23273
  substitutionMap.set(`agents/${oldSlug}.md`, `agents/${newSlug}.md`);
@@ -23173,7 +23278,7 @@ async function autoRenameAgentFiles(client, hubId, hubFolder, organizationId, re
23173
23278
  (_match, prefix, pathMatch) => `${prefix}${substitutionMap.get(pathMatch) ?? pathMatch}`
23174
23279
  );
23175
23280
  if (updatedYaml !== mainYamlContent) {
23176
- fs17.writeFileSync(mainYamlPath, updatedYaml, "utf-8");
23281
+ writeFileNoFollow(hubFolder, mainYamlPath, Buffer.from(updatedYaml, "utf-8"));
23177
23282
  console.log(` Updated instructions paths in ${path22.basename(mainYamlPath)}`);
23178
23283
  }
23179
23284
  }
@@ -23267,11 +23372,13 @@ New hub: "${newHub.hubName}" (${hubType})`);
23267
23372
  return;
23268
23373
  }
23269
23374
  }
23375
+ requireRealHubFolder(newHub.hubFolder, false);
23270
23376
  const hubYamlPath = resolveHubYamlPath(newHub.hubFolder);
23271
23377
  if (!hubYamlPath) {
23272
23378
  console.error(`hub.yaml not found in ${newHub.hubFolder}. Cannot proceed with hub creation.`);
23273
23379
  process.exit(1);
23274
23380
  }
23381
+ const content = readRealFileOrThrow(newHub.hubFolder, hubYamlPath) ?? "";
23275
23382
  console.log("Creating hub...");
23276
23383
  const createResult = await client.createHub(opts.organizationId, {
23277
23384
  hubName: newHub.hubName,
@@ -23285,7 +23392,6 @@ New hub: "${newHub.hubName}" (${hubType})`);
23285
23392
  process.exit(1);
23286
23393
  }
23287
23394
  console.log(`Hub created: ${createdHub.hub_name} (${hubId})`);
23288
- const content = fs17.readFileSync(hubYamlPath, "utf-8");
23289
23395
  const hasVersion = content.match(/^version:\s/m);
23290
23396
  let updated;
23291
23397
  if (hasVersion) {
@@ -23301,7 +23407,7 @@ hub_id: "${hubId}"
23301
23407
  hub_environment: preview
23302
23408
  ${content}`;
23303
23409
  }
23304
- fs17.writeFileSync(hubYamlPath, updated, "utf-8");
23410
+ writeFileNoFollow(newHub.hubFolder, hubYamlPath, Buffer.from(updated, "utf-8"));
23305
23411
  seedScopeIfEmpty("hubs", hubId);
23306
23412
  await pushSingleHub(client, hubId, newHub.hubFolder, opts.autoConfirm, opts.organizationId, { skipAgentRename: true });
23307
23413
  }
@@ -23347,7 +23453,7 @@ async function pushCommand(args2) {
23347
23453
  const client = new ApiClient({ apiUrl: config.api_url, accessToken });
23348
23454
  const workspaceDir = resolveWorkspaceDir();
23349
23455
  const wsLabel = hubsDirLabel(gitRoot);
23350
- if (!fs17.existsSync(workspaceDir)) {
23456
+ if (!fs16.existsSync(workspaceDir)) {
23351
23457
  console.error(`No ${wsLabel}/ directory found. Run \`wayai pull\` first or create hub files in ${wsLabel}/<hub>/hub.yaml.`);
23352
23458
  process.exit(1);
23353
23459
  }
@@ -23391,6 +23497,7 @@ var init_push = __esm({
23391
23497
  init_repo_config();
23392
23498
  init_worktree_scope();
23393
23499
  init_subtree_routing();
23500
+ init_fs_safety();
23394
23501
  LOCAL_CHANGE_LIST_CAP = 20;
23395
23502
  }
23396
23503
  });
@@ -23403,7 +23510,7 @@ __export(pull_exports, {
23403
23510
  resolveHubTarget: () => resolveHubTarget,
23404
23511
  writeProductionMirror: () => writeProductionMirror2
23405
23512
  });
23406
- import * as fs18 from "fs";
23513
+ import * as fs17 from "fs";
23407
23514
  import * as path23 from "path";
23408
23515
  function parseArgs6(args2) {
23409
23516
  return { autoConfirm: args2.includes("--yes") || args2.includes("-y") };
@@ -23470,7 +23577,7 @@ async function pullCommand(args2) {
23470
23577
  payload.preview_label,
23471
23578
  payload.branch_name
23472
23579
  );
23473
- fs18.mkdirSync(path23.dirname(hubFolder), { recursive: true });
23580
+ fs17.mkdirSync(path23.dirname(hubFolder), { recursive: true });
23474
23581
  console.log("Writing hub configuration...");
23475
23582
  await materializeHubFolder(hubFolder, payload);
23476
23583
  const finalFolder = autoRenameHubFolder(hubFolder, payload.hub.name, payload.hub_environment, payload.hub_id, payload.preview_label, payload.branch_name);
@@ -23499,7 +23606,8 @@ async function pullCommand(args2) {
23499
23606
  }
23500
23607
  action = "apply";
23501
23608
  }
23502
- } catch {
23609
+ } catch (err) {
23610
+ if (isWorkspaceRefusal(err)) throw err;
23503
23611
  action = "overwrite";
23504
23612
  }
23505
23613
  if (action === "overwrite") console.log("Writing hub configuration...");
@@ -23524,7 +23632,7 @@ async function pullCommand(args2) {
23524
23632
  }
23525
23633
  async function writeProductionMirror2(workspaceDir, prodPayload) {
23526
23634
  const folder = resolveHubFolder(workspaceDir, prodPayload.hub_id, prodPayload.hub.name, "production", null, null);
23527
- fs18.mkdirSync(path23.dirname(folder), { recursive: true });
23635
+ fs17.mkdirSync(path23.dirname(folder), { recursive: true });
23528
23636
  await materializeHubFolder(folder, prodPayload, { seedAgentContext: false });
23529
23637
  const finalFolder = autoRenameHubFolder(folder, prodPayload.hub.name, "production", prodPayload.hub_id, null, null);
23530
23638
  prependMirrorMarker(finalFolder, prodPayload.hub_id);
@@ -23542,11 +23650,11 @@ async function mirrorLinkedProduction2(client, workspaceDir, productionHubId, or
23542
23650
  function prependMirrorMarker(hubFolder, productionHubId) {
23543
23651
  const hubYaml = path23.join(hubFolder, "hub.yaml");
23544
23652
  try {
23545
- const content = fs18.readFileSync(hubYaml, "utf-8");
23653
+ const content = fs17.readFileSync(hubYaml, "utf-8");
23546
23654
  if (content.startsWith(MIRROR_MARKER_PREFIX2)) return;
23547
23655
  const marker = `${MIRROR_MARKER_PREFIX2} ${productionHubId}. Edits are ignored; push is blocked. Edit the linked preview hub instead.
23548
23656
  `;
23549
- fs18.writeFileSync(hubYaml, marker + content, "utf-8");
23657
+ fs17.writeFileSync(hubYaml, marker + content, "utf-8");
23550
23658
  } catch {
23551
23659
  }
23552
23660
  }
@@ -23574,6 +23682,7 @@ var init_pull = __esm({
23574
23682
  init_utils();
23575
23683
  init_resource_files();
23576
23684
  init_hub_materializer();
23685
+ init_fs_safety();
23577
23686
  init_push();
23578
23687
  init_workspace();
23579
23688
  init_layout();
@@ -23590,7 +23699,7 @@ __export(create_exports, {
23590
23699
  createCommand: () => createCommand
23591
23700
  });
23592
23701
  import * as path24 from "path";
23593
- import * as fs19 from "fs";
23702
+ import * as fs18 from "fs";
23594
23703
  function parseArgs7(args2) {
23595
23704
  let autoConfirm = false;
23596
23705
  let folderSelector;
@@ -23617,7 +23726,7 @@ async function createCommand(args2) {
23617
23726
  const gitRoot = findGitRoot();
23618
23727
  const wsLabel = hubsDirLabel(gitRoot);
23619
23728
  if (gitRoot) warnLayoutOnce(gitRoot);
23620
- if (!fs19.existsSync(workspaceDir)) {
23729
+ if (!fs18.existsSync(workspaceDir)) {
23621
23730
  console.error(`No ${wsLabel}/ directory found. Create hub files in ${wsLabel}/<hub>/hub.yaml with \`hub: { name: ... }\` first.`);
23622
23731
  process.exit(1);
23623
23732
  }
@@ -23751,7 +23860,7 @@ var replicate_exports = {};
23751
23860
  __export(replicate_exports, {
23752
23861
  replicateCommand: () => replicateCommand
23753
23862
  });
23754
- import * as fs20 from "fs";
23863
+ import * as fs19 from "fs";
23755
23864
  import * as path25 from "path";
23756
23865
  function parseArgs9(args2) {
23757
23866
  let label;
@@ -23798,8 +23907,8 @@ async function replicateCommand(args2) {
23798
23907
  payload.preview_label,
23799
23908
  payload.branch_name
23800
23909
  );
23801
- const folderPreExisted = fs20.existsSync(hubFolder);
23802
- fs20.mkdirSync(path25.dirname(hubFolder), { recursive: true });
23910
+ const folderPreExisted = fs19.existsSync(hubFolder);
23911
+ fs19.mkdirSync(path25.dirname(hubFolder), { recursive: true });
23803
23912
  const delta = await materializeHubFolder(hubFolder, payload);
23804
23913
  hubFolder = autoRenameHubFolder(
23805
23914
  hubFolder,
@@ -24226,7 +24335,7 @@ __export(migrate_exports, {
24226
24335
  migrateCommand: () => migrateCommand
24227
24336
  });
24228
24337
  import { execFileSync as execFileSync3 } from "child_process";
24229
- import * as fs21 from "fs";
24338
+ import * as fs20 from "fs";
24230
24339
  import * as path29 from "path";
24231
24340
  function isTracked(gitRoot, p) {
24232
24341
  try {
@@ -24240,7 +24349,7 @@ function isTracked(gitRoot, p) {
24240
24349
  }
24241
24350
  }
24242
24351
  function moveDir(gitRoot, from, to) {
24243
- fs21.mkdirSync(path29.dirname(to), { recursive: true });
24352
+ fs20.mkdirSync(path29.dirname(to), { recursive: true });
24244
24353
  if (isTracked(gitRoot, from)) {
24245
24354
  try {
24246
24355
  execFileSync3("git", ["mv", path29.relative(gitRoot, from), path29.relative(gitRoot, to)], {
@@ -24251,7 +24360,7 @@ function moveDir(gitRoot, from, to) {
24251
24360
  } catch {
24252
24361
  }
24253
24362
  }
24254
- fs21.renameSync(from, to);
24363
+ fs20.renameSync(from, to);
24255
24364
  return "fs";
24256
24365
  }
24257
24366
  async function migrateCommand(_args) {
@@ -24266,8 +24375,11 @@ async function migrateCommand(_args) {
24266
24375
  const legacyOrg = path29.join(gitRoot, WAYAI_LAYOUT.legacy.orgAtRoot);
24267
24376
  const newHubs = path29.join(newWs, WAYAI_LAYOUT.hubsSubdir);
24268
24377
  const newOrg = path29.join(newWs, WAYAI_LAYOUT.orgSubdir);
24269
- const hasLegacyWs = isDirectory(legacyWs);
24270
- const hasLegacyOrg = isDirectory(legacyOrg);
24378
+ requireRealSubdirNoSymlink(gitRoot, newHubs, false);
24379
+ requireRealSubdirNoSymlink(gitRoot, newOrg, false);
24380
+ readRealFileOrThrow(gitRoot, workspaceManifestPath(gitRoot));
24381
+ const hasLegacyWs = isRealDirectory(legacyWs);
24382
+ const hasLegacyOrg = isRealDirectory(legacyOrg);
24271
24383
  const orgPlan = planOrgMigration(gitRoot);
24272
24384
  if (orgPlan.kind === "refuse") {
24273
24385
  console.error(orgPlan.message);
@@ -24331,6 +24443,7 @@ var init_migrate = __esm({
24331
24443
  "use strict";
24332
24444
  init_workspace();
24333
24445
  init_layout();
24446
+ init_fs_safety();
24334
24447
  init_repo_config();
24335
24448
  init_workspace_manifest();
24336
24449
  }
@@ -24414,12 +24527,12 @@ var send_message_exports = {};
24414
24527
  __export(send_message_exports, {
24415
24528
  sendMessageCommand: () => sendMessageCommand
24416
24529
  });
24417
- import * as fs22 from "fs";
24530
+ import * as fs21 from "fs";
24418
24531
  import * as path30 from "path";
24419
24532
  function statAttachment(filePath) {
24420
24533
  let stat2;
24421
24534
  try {
24422
- stat2 = fs22.statSync(filePath);
24535
+ stat2 = fs21.statSync(filePath);
24423
24536
  } catch {
24424
24537
  console.error(`Error: file not found: ${filePath}`);
24425
24538
  process.exit(1);
@@ -24435,7 +24548,7 @@ function readAttachment(filePath, size) {
24435
24548
  const ext = path30.extname(fileName).replace(/^\./, "");
24436
24549
  return {
24437
24550
  file_name: fileName,
24438
- file_binary: fs22.readFileSync(filePath).toString("base64"),
24551
+ file_binary: fs21.readFileSync(filePath).toString("base64"),
24439
24552
  file_size: size,
24440
24553
  ...ext && { file_extension: ext }
24441
24554
  };
@@ -26634,7 +26747,7 @@ var eval_capture_exports = {};
26634
26747
  __export(eval_capture_exports, {
26635
26748
  evalCaptureCommand: () => evalCaptureCommand
26636
26749
  });
26637
- import * as fs23 from "fs";
26750
+ import * as fs22 from "fs";
26638
26751
  import * as path31 from "path";
26639
26752
  import * as yaml12 from "js-yaml";
26640
26753
  function isValidSetName(name) {
@@ -26708,7 +26821,17 @@ async function evalCaptureCommand(args2) {
26708
26821
  console.error(`Resolved path "${path31.relative(hubFolder, targetPath)}" escapes evals/. Aborting.`);
26709
26822
  process.exit(1);
26710
26823
  }
26711
- if (fs23.existsSync(targetPath)) {
26824
+ if (!ensureRealSubdirNoSymlink(hubFolder, targetDir, false)) {
26825
+ console.error(`${path31.relative(hubFolder, targetDir)} is reached through a symlink. Aborting.`);
26826
+ process.exit(1);
26827
+ }
26828
+ let targetTaken = true;
26829
+ try {
26830
+ fs22.lstatSync(targetPath);
26831
+ } catch {
26832
+ targetTaken = false;
26833
+ }
26834
+ if (targetTaken) {
26712
26835
  console.error(`File already exists: ${path31.relative(hubFolder, targetPath)}. Use --name to choose a different name.`);
26713
26836
  process.exit(1);
26714
26837
  }
@@ -26741,8 +26864,13 @@ async function evalCaptureCommand(args2) {
26741
26864
  ...captured.evaluator_instructions ? { evaluator_instructions: captured.evaluator_instructions } : {}
26742
26865
  };
26743
26866
  const yamlObj = buildEvalYamlObject(evalEntry, slug);
26744
- fs23.mkdirSync(targetDir, { recursive: true });
26745
- fs23.writeFileSync(targetPath, yaml12.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8");
26867
+ const outcome = createFileNoFollow(hubFolder, targetPath, Buffer.from(yaml12.dump(yamlObj, YAML_DUMP_OPTIONS), "utf-8"));
26868
+ if (outcome !== "created") {
26869
+ console.error(
26870
+ `Could not write ${path31.relative(hubFolder, targetPath)} (${outcome === "exists" ? "it appeared meanwhile" : "its folder is reached through a symlink"}). The scenario was created on the platform as ${captured.eval_id} \u2014 run \`wayai pull\` to fetch it.`
26871
+ );
26872
+ process.exit(1);
26873
+ }
26746
26874
  const relPath = path31.relative(process.cwd(), targetPath);
26747
26875
  console.log(`
26748
26876
  Wrote ${relPath}`);
@@ -26757,6 +26885,7 @@ var init_eval_capture = __esm({
26757
26885
  init_workspace();
26758
26886
  init_utils();
26759
26887
  init_yaml_writer();
26888
+ init_fs_safety();
26760
26889
  }
26761
26890
  });
26762
26891
 
@@ -28114,23 +28243,21 @@ var init_set_connection_credential = __esm({
28114
28243
  });
28115
28244
 
28116
28245
  // src/lib/org-workspace.ts
28117
- import * as fs24 from "fs";
28246
+ import * as fs23 from "fs";
28118
28247
  import * as path32 from "path";
28119
28248
  import * as yaml13 from "js-yaml";
28120
28249
  function getOrgDir(gitRoot) {
28121
28250
  return resolveLayout(gitRoot).orgDir;
28122
28251
  }
28123
28252
  function orgManifestExists(orgDir) {
28124
- return fs24.existsSync(path32.join(orgDir, ORG_MANIFEST_NAME));
28253
+ return fs23.existsSync(path32.join(orgDir, ORG_MANIFEST_NAME));
28125
28254
  }
28126
28255
  function parseOrgResources(orgDir) {
28127
- const manifestPath = path32.join(orgDir, ORG_MANIFEST_NAME);
28128
- let manifest = {};
28129
- if (fs24.existsSync(manifestPath)) {
28130
- manifest = yaml13.load(fs24.readFileSync(manifestPath, "utf-8")) ?? {};
28131
- }
28132
- const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
28133
28256
  const resourcesDir = path32.join(orgDir, "resources");
28257
+ requireRealSubdirNoSymlink(orgDir, resourcesDir, false);
28258
+ const manifestText = readRealFileOrThrow(orgDir, path32.join(orgDir, ORG_MANIFEST_NAME));
28259
+ const manifest = (manifestText !== null ? yaml13.load(manifestText) : null) ?? {};
28260
+ const rawResources = Array.isArray(manifest.resources) ? manifest.resources : [];
28134
28261
  const resources = rawResources.map((res) => {
28135
28262
  const resource = { name: res.name };
28136
28263
  if (res.id) resource.id = res.id;
@@ -28143,7 +28270,8 @@ function parseOrgResources(orgDir) {
28143
28270
  if (Array.isArray(res.tags)) resource.tags = res.tags;
28144
28271
  if (Array.isArray(res.folders)) resource.folders = res.folders;
28145
28272
  const resDir = path32.join(resourcesDir, slugify(resource.name));
28146
- if (fs24.existsSync(resDir)) {
28273
+ requireRealSubdirNoSymlink(orgDir, resDir, false);
28274
+ if (fs23.existsSync(resDir)) {
28147
28275
  const files = scanResourceFiles(resDir, "");
28148
28276
  if (files.length > 0) resource.files = files;
28149
28277
  }
@@ -28152,28 +28280,29 @@ function parseOrgResources(orgDir) {
28152
28280
  return { version: 1, resources };
28153
28281
  }
28154
28282
  function writeOrgResources(orgDir, payload) {
28155
- fs24.mkdirSync(orgDir, { recursive: true });
28283
+ fs23.mkdirSync(orgDir, { recursive: true });
28284
+ const resourcesDir = path32.join(orgDir, "resources");
28285
+ requireRealSubdirNoSymlink(orgDir, resourcesDir, false);
28156
28286
  const resources = payload.resources ?? [];
28157
28287
  const manifestResources = resources.map((r) => {
28158
28288
  const { files: _files, ...rest } = r;
28159
28289
  return rest;
28160
28290
  });
28161
- fs24.writeFileSync(
28291
+ writeFileNoFollow(
28292
+ orgDir,
28162
28293
  path32.join(orgDir, ORG_MANIFEST_NAME),
28163
- yaml13.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS),
28164
- "utf-8"
28294
+ Buffer.from(yaml13.dump({ version: 1, resources: manifestResources }, YAML_DUMP_OPTIONS), "utf-8")
28165
28295
  );
28166
- const resourcesDir = path32.join(orgDir, "resources");
28167
28296
  const currentSlugs = /* @__PURE__ */ new Set();
28168
28297
  for (const resource of resources) {
28169
28298
  const resSlug = slugify(resource.name);
28170
28299
  currentSlugs.add(resSlug);
28171
28300
  writeResourceFileTree(path32.join(resourcesDir, resSlug), resource.files || [], orgDir);
28172
28301
  }
28173
- if (fs24.existsSync(resourcesDir)) {
28174
- for (const entry of fs24.readdirSync(resourcesDir, { withFileTypes: true })) {
28302
+ if (fs23.existsSync(resourcesDir)) {
28303
+ for (const entry of fs23.readdirSync(resourcesDir, { withFileTypes: true })) {
28175
28304
  if (entry.isDirectory() && !currentSlugs.has(entry.name)) {
28176
- fs24.rmSync(path32.join(resourcesDir, entry.name), { recursive: true, force: true });
28305
+ fs23.rmSync(path32.join(resourcesDir, entry.name), { recursive: true, force: true });
28177
28306
  }
28178
28307
  }
28179
28308
  }
@@ -28193,6 +28322,7 @@ var init_org_workspace = __esm({
28193
28322
  "use strict";
28194
28323
  init_utils();
28195
28324
  init_layout();
28325
+ init_fs_safety();
28196
28326
  init_resource_files();
28197
28327
  init_yaml_writer();
28198
28328
  ORG_MANIFEST_NAME = "resources.yaml";
@@ -28478,7 +28608,7 @@ var init_report_edit_args = __esm({
28478
28608
  });
28479
28609
 
28480
28610
  // src/lib/file-map.ts
28481
- import * as fs25 from "fs";
28611
+ import * as fs24 from "fs";
28482
28612
  import * as path33 from "path";
28483
28613
  function isSafeRelPath(rel) {
28484
28614
  if (rel.length === 0 || rel.length > 300) return false;
@@ -28495,8 +28625,8 @@ function writeFileMap(targetDir, files) {
28495
28625
  throw new Error(`Refusing to write unsafe path: ${rel}`);
28496
28626
  }
28497
28627
  const abs = path33.join(targetDir, rel);
28498
- fs25.mkdirSync(path33.dirname(abs), { recursive: true });
28499
- fs25.writeFileSync(abs, body, "utf-8");
28628
+ fs24.mkdirSync(path33.dirname(abs), { recursive: true });
28629
+ fs24.writeFileSync(abs, body, "utf-8");
28500
28630
  written.push(rel);
28501
28631
  }
28502
28632
  return written;
@@ -28512,7 +28642,7 @@ var admin_exports = {};
28512
28642
  __export(admin_exports, {
28513
28643
  adminCommand: () => adminCommand
28514
28644
  });
28515
- import * as fs26 from "fs";
28645
+ import * as fs25 from "fs";
28516
28646
  import * as path34 from "path";
28517
28647
  async function adminCommand(args2) {
28518
28648
  const [group, ...afterGroup] = args2;
@@ -28828,7 +28958,7 @@ async function runArchiveRead(positional, flagArgs) {
28828
28958
  exitOnApiError(err);
28829
28959
  throw err;
28830
28960
  }
28831
- fs26.writeFileSync(outPath, zip);
28961
+ fs25.writeFileSync(outPath, zip);
28832
28962
  console.log(`Wrote ${zip.byteLength} bytes to ${outPath}`);
28833
28963
  return;
28834
28964
  }
@@ -28970,7 +29100,7 @@ async function runSkillInstall(positional) {
28970
29100
  throw err;
28971
29101
  }
28972
29102
  const root = findGitRoot() ?? process.cwd();
28973
- const present = HARNESS_SKILL_DIRS.filter((dir) => fs26.existsSync(path34.join(root, dir)));
29103
+ const present = HARNESS_SKILL_DIRS.filter((dir) => fs25.existsSync(path34.join(root, dir)));
28974
29104
  const targets = present.length > 0 ? present : HARNESS_SKILL_DIRS;
28975
29105
  const fileCount = Object.keys(res.files).length;
28976
29106
  const relDirs = targets.map((harness) => {
@@ -30528,7 +30658,7 @@ var init_actions = __esm({
30528
30658
 
30529
30659
  // src/data/commands/attachments.ts
30530
30660
  import { Command as Command2 } from "commander";
30531
- import { readFileSync as readFileSync20 } from "fs";
30661
+ import { readFileSync as readFileSync17 } from "fs";
30532
30662
  function findAttachmentByFilename(attachments, filename) {
30533
30663
  return attachments.find((a) => a.key.endsWith(`/${filename}`)) ?? null;
30534
30664
  }
@@ -30569,7 +30699,7 @@ function buildAttachmentsCommand() {
30569
30699
  printOutput(data, outputFormat(this));
30570
30700
  return;
30571
30701
  }
30572
- const body = readFileSync20(opts.file);
30702
+ const body = readFileSync17(opts.file);
30573
30703
  await client.upload(uploadPathFrom(data?.upload_url), body, opts.contentType);
30574
30704
  printOutput({ ...data, uploaded: true }, outputFormat(this));
30575
30705
  });
@@ -30969,7 +31099,7 @@ var init_import = __esm({
30969
31099
 
30970
31100
  // src/data/commands/providers.ts
30971
31101
  import { Command as Command5 } from "commander";
30972
- import { writeFileSync as writeFileSync14 } from "fs";
31102
+ import { writeFileSync as writeFileSync9 } from "fs";
30973
31103
  function providerSegment(provider) {
30974
31104
  if (!VALID_PROVIDERS.includes(provider)) {
30975
31105
  throw expected(`Unknown provider ${JSON.stringify(provider)}. Expected one of: ${VALID_PROVIDERS_HELP}.`);
@@ -31002,7 +31132,7 @@ function buildBasesProvidersCommand() {
31002
31132
  );
31003
31133
  if (opts.to) {
31004
31134
  try {
31005
- writeFileSync14(opts.to, JSON.stringify(data, null, 2));
31135
+ writeFileSync9(opts.to, JSON.stringify(data, null, 2));
31006
31136
  } catch (e) {
31007
31137
  throw expected(`--to ${opts.to}: ${e instanceof Error ? e.message : "could not be written"}`);
31008
31138
  }
@@ -31049,8 +31179,8 @@ var init_providers = __esm({
31049
31179
 
31050
31180
  // src/data/commands/report.ts
31051
31181
  import { Command as Command6 } from "commander";
31052
- import { readFileSync as readFileSync21 } from "fs";
31053
- import { dirname as dirname12, join as join30 } from "path";
31182
+ import { readFileSync as readFileSync18 } from "fs";
31183
+ import { dirname as dirname13, join as join30 } from "path";
31054
31184
  import { fileURLToPath as fileURLToPath2 } from "url";
31055
31185
  function resolveCliVersion() {
31056
31186
  for (const candidate of [
@@ -31058,7 +31188,7 @@ function resolveCliVersion() {
31058
31188
  join30(here, "..", "..", "..", "package.json")
31059
31189
  ]) {
31060
31190
  try {
31061
- const version = JSON.parse(readFileSync21(candidate, "utf-8")).version;
31191
+ const version = JSON.parse(readFileSync18(candidate, "utf-8")).version;
31062
31192
  if (typeof version === "string" && version) return version;
31063
31193
  } catch {
31064
31194
  }
@@ -31254,13 +31384,13 @@ var init_report2 = __esm({
31254
31384
  init_terminal_output();
31255
31385
  init_workspace();
31256
31386
  init_skill_version();
31257
- here = dirname12(fileURLToPath2(import.meta.url));
31387
+ here = dirname13(fileURLToPath2(import.meta.url));
31258
31388
  }
31259
31389
  });
31260
31390
 
31261
31391
  // src/data/commands/credentials.ts
31262
31392
  import { Command as Command7 } from "commander";
31263
- import { readFileSync as readFileSync22 } from "fs";
31393
+ import { readFileSync as readFileSync19 } from "fs";
31264
31394
  function withValueSourceOptions(cmd, what) {
31265
31395
  return cmd.option(
31266
31396
  "--file <path>",
@@ -31273,7 +31403,7 @@ async function resolveValue(opts, label) {
31273
31403
  throw expected("--file cannot be combined with --value-stdin or --value-prompt \u2014 pass one.");
31274
31404
  }
31275
31405
  try {
31276
- return readFileSync22(opts.file).toString("base64");
31406
+ return readFileSync19(opts.file).toString("base64");
31277
31407
  } catch (e) {
31278
31408
  throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
31279
31409
  }
@@ -31422,7 +31552,7 @@ var init_credentials = __esm({
31422
31552
 
31423
31553
  // src/data/commands/sql.ts
31424
31554
  import { Command as Command8 } from "commander";
31425
- import { readFileSync as readFileSync23 } from "fs";
31555
+ import { readFileSync as readFileSync20 } from "fs";
31426
31556
  function buildBasesSqlCommand() {
31427
31557
  return withBaseOption(new Command8("sql")).description("Execute a read-only SQL query against base data").argument("[query]", "SQL query (SELECT only)").option("--file <path>", "Read SQL from a file instead of the argument").option(
31428
31558
  "--param <kv...>",
@@ -31432,7 +31562,7 @@ function buildBasesSqlCommand() {
31432
31562
  let query;
31433
31563
  if (opts.file) {
31434
31564
  try {
31435
- query = readFileSync23(opts.file, "utf-8").trim();
31565
+ query = readFileSync20(opts.file, "utf-8").trim();
31436
31566
  } catch (e) {
31437
31567
  throw expected(`--file ${opts.file}: ${e instanceof Error ? e.message : "could not be read"}`);
31438
31568
  }
@@ -32087,8 +32217,8 @@ var init_file_types = __esm({
32087
32217
 
32088
32218
  // src/data/commands/files.ts
32089
32219
  import { Command as Command12 } from "commander";
32090
- import { readFileSync as readFileSync24, writeFileSync as writeFileSync15 } from "fs";
32091
- import { basename as basename18 } from "path";
32220
+ import { readFileSync as readFileSync21, writeFileSync as writeFileSync10 } from "fs";
32221
+ import { basename as basename20 } from "path";
32092
32222
  function renderFileDiff(fileType, filePath, from, to, d) {
32093
32223
  console.log(sanitizeTerminalText(`${fileType}/${filePath}: v${from} \u2192 v${to}`));
32094
32224
  const md = d.metadata_delta;
@@ -32120,7 +32250,7 @@ function renderFileDiff(fileType, filePath, from, to, d) {
32120
32250
  }
32121
32251
  function downloadTarget(remotePath, to) {
32122
32252
  if (to) return to;
32123
- const derived = basename18(remotePath);
32253
+ const derived = basename20(remotePath);
32124
32254
  if (derived === "" || derived === "." || derived === "..") {
32125
32255
  throw expected(
32126
32256
  `Cannot derive a local filename from "${remotePath}" \u2014 pass --to <local> to name it.`
@@ -32136,7 +32266,7 @@ function buildFilesCommand() {
32136
32266
  "Upload a local file to a path (e.g. wayai files put reports q3/summary.pdf --file ./summary.pdf)"
32137
32267
  ).requiredOption("--file <local>", "Local file to upload").option("--content-type <type>", "MIME type", "application/octet-stream").action(async function(fileType, filePath, opts) {
32138
32268
  const base = pathSegment(requireBase(this), "--base");
32139
- const body = readFileSync24(opts.file);
32269
+ const body = readFileSync21(opts.file);
32140
32270
  const client = await createDataClient();
32141
32271
  printOutput(
32142
32272
  await client.upload(
@@ -32167,7 +32297,7 @@ function buildFilesCommand() {
32167
32297
  const { bytes } = await client.download(
32168
32298
  `/v1/${base}/files/${pathSegment(fileType, "file_type")}/${encoded}${versionQs ? `?${versionQs}` : ""}`
32169
32299
  );
32170
- writeFileSync15(out, bytes);
32300
+ writeFileSync10(out, bytes);
32171
32301
  console.log(`Downloaded to ${out}`);
32172
32302
  });
32173
32303
  files.command("history <file_type> <path>").description("List the content versions of a file (newest first)").option("--limit <n>", "Max versions to return").option("--offset <n>", "Pagination offset").action(async function(fileType, filePath, opts) {
@@ -33185,9 +33315,9 @@ init_errors2();
33185
33315
  init_mask_secrets();
33186
33316
  init_utils();
33187
33317
  init_registry();
33188
- import { readFileSync as readFileSync25 } from "fs";
33318
+ import { readFileSync as readFileSync22 } from "fs";
33189
33319
  import { fileURLToPath as fileURLToPath3 } from "url";
33190
- import { dirname as dirname13, join as join31 } from "path";
33320
+ import { dirname as dirname14, join as join31 } from "path";
33191
33321
 
33192
33322
  // src/lib/version-refresh.ts
33193
33323
  init_version_cache();
@@ -33342,8 +33472,8 @@ Run \`wayai admin skill install\` to update.`);
33342
33472
  }
33343
33473
 
33344
33474
  // src/index.ts
33345
- var __dirname = dirname13(fileURLToPath3(import.meta.url));
33346
- var pkg = JSON.parse(readFileSync25(join31(__dirname, "..", "package.json"), "utf-8"));
33475
+ var __dirname = dirname14(fileURLToPath3(import.meta.url));
33476
+ var pkg = JSON.parse(readFileSync22(join31(__dirname, "..", "package.json"), "utf-8"));
33347
33477
  var [, , command, ...args] = process.argv;
33348
33478
  var isBackgroundRefresh = command === REFRESH_COMMAND;
33349
33479
  if (!isBackgroundRefresh) initSentry(command, pkg.version);