runwork 0.25.0 → 0.25.2

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
@@ -1020,26 +1020,36 @@ function buildHelperValue(execPath, scriptPath) {
1020
1020
  return `!"${normalised}" git-credential-helper`;
1021
1021
  }
1022
1022
  async function configureGitCredentials(remoteUrl) {
1023
- const origin = new URL(remoteUrl).origin;
1023
+ let origin;
1024
+ try {
1025
+ origin = new URL(remoteUrl).origin;
1026
+ } catch {
1027
+ const message = `"${remoteUrl}" is not a valid URL.`;
1028
+ console.warn(`Note: ${message}`);
1029
+ return { ok: false, reason: "invalid-url", message };
1030
+ }
1024
1031
  const key = `credential.${origin}.helper`;
1025
1032
  const helperValue = buildHelperValue(process.execPath, process.argv[1]);
1026
1033
  try {
1027
1034
  try {
1028
1035
  execFileSync("git", ["config", "--global", "--unset-all", key], { stdio: "pipe" });
1029
- } catch (unsetErr) {
1030
- if (unsetErr?.code === "ENOENT")
1031
- throw unsetErr;
1032
- }
1036
+ } catch {}
1033
1037
  execFileSync("git", ["config", "--global", "--add", key, ""], { stdio: "pipe" });
1034
1038
  execFileSync("git", ["config", "--global", "--add", key, helperValue], { stdio: "pipe" });
1039
+ return { ok: true };
1035
1040
  } catch (err) {
1036
1041
  const code = err?.code;
1037
1042
  if (code === "ENOENT") {
1038
- console.warn("Note: git is not installed. Skipping git credential helper setup.");
1039
- console.warn("Install git before running `runwork init`, `clone`, `dev`, or `deploy`.");
1040
- return;
1043
+ const message2 = "git is not installed. Install git before running `runwork init`, `clone`, `dev`, or `deploy`.";
1044
+ console.warn(`Note: ${message2}`);
1045
+ return { ok: false, reason: "missing", message: message2 };
1041
1046
  }
1042
- throw err;
1047
+ const detail = err instanceof Error ? err.message.split(`
1048
+ `)[0] : String(err);
1049
+ const xcodeHint = process.platform === "darwin" ? " On macOS this usually means the Xcode Command Line Tools are missing; run `xcode-select --install`." : "";
1050
+ const message = `git is installed but could not be run (${detail}).${xcodeHint}`;
1051
+ console.warn(`Note: ${message}`);
1052
+ return { ok: false, reason: "unusable", message };
1043
1053
  }
1044
1054
  }
1045
1055
  function lookupCredentialHelper(origin) {
@@ -1097,13 +1107,13 @@ async function ensureGitCredentialHelper(baseUrl) {
1097
1107
  try {
1098
1108
  origin = new URL(baseUrl).origin;
1099
1109
  } catch {
1100
- return;
1110
+ return { ok: false, reason: "invalid-url", message: `"${baseUrl}" is not a valid URL.` };
1101
1111
  }
1102
1112
  const lookup = lookupCredentialHelper(origin);
1103
1113
  if (lookup.status === "registered" && helperBinaryStatus(lookup.value).ok && lookup.hasReset) {
1104
- return;
1114
+ return { ok: true };
1105
1115
  }
1106
- await configureGitCredentials(baseUrl);
1116
+ return configureGitCredentials(baseUrl);
1107
1117
  }
1108
1118
  async function removeGitCredentials(baseUrl) {
1109
1119
  const origin = new URL(baseUrl).origin;
@@ -1380,8 +1390,154 @@ var init_identity = __esm(() => {
1380
1390
  init_subprocess();
1381
1391
  });
1382
1392
 
1393
+ // src/utils/ignore-matcher.ts
1394
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1395
+ import { basename, join as join4 } from "path";
1396
+ function defaultIgnoreSets() {
1397
+ return {
1398
+ dirs: new Set(DEFAULT_DIR_NAMES),
1399
+ files: new Set(DEFAULT_FILE_NAMES),
1400
+ extensions: new Set(DEFAULT_EXTENSIONS)
1401
+ };
1402
+ }
1403
+ function parseGitignoreContent(content) {
1404
+ const dirs = new Set;
1405
+ const files = new Set;
1406
+ for (const rawLine of content.split(`
1407
+ `)) {
1408
+ const line = rawLine.trim();
1409
+ if (!line || line.startsWith("#"))
1410
+ continue;
1411
+ if (line.startsWith("!"))
1412
+ continue;
1413
+ if (GLOB_CHARS.test(line))
1414
+ continue;
1415
+ let pattern = line;
1416
+ const isDirOnly = pattern.endsWith("/");
1417
+ if (isDirOnly)
1418
+ pattern = pattern.slice(0, -1);
1419
+ if (pattern.startsWith("/"))
1420
+ pattern = pattern.slice(1);
1421
+ if (!pattern)
1422
+ continue;
1423
+ if (pattern.includes("/"))
1424
+ continue;
1425
+ if (isDirOnly) {
1426
+ dirs.add(pattern);
1427
+ } else {
1428
+ dirs.add(pattern);
1429
+ files.add(pattern);
1430
+ }
1431
+ }
1432
+ return { dirs, files };
1433
+ }
1434
+ function loadGitignoreFromDir(dir) {
1435
+ const path = join4(dir, ".gitignore");
1436
+ if (!existsSync4(path))
1437
+ return { dirs: new Set, files: new Set };
1438
+ try {
1439
+ return parseGitignoreContent(readFileSync4(path, "utf-8"));
1440
+ } catch {
1441
+ return { dirs: new Set, files: new Set };
1442
+ }
1443
+ }
1444
+ function buildIgnoreSets(dir) {
1445
+ const sets = defaultIgnoreSets();
1446
+ const parsed = loadGitignoreFromDir(dir);
1447
+ for (const name of parsed.dirs)
1448
+ sets.dirs.add(name);
1449
+ for (const name of parsed.files)
1450
+ sets.files.add(name);
1451
+ return sets;
1452
+ }
1453
+ function isPathIgnored(filePath, sets) {
1454
+ const name = basename(filePath);
1455
+ if (sets.dirs.has(name))
1456
+ return true;
1457
+ if (sets.files.has(name))
1458
+ return true;
1459
+ const dotIndex = name.lastIndexOf(".");
1460
+ if (dotIndex >= 0) {
1461
+ const ext = name.slice(dotIndex);
1462
+ if (sets.extensions.has(ext))
1463
+ return true;
1464
+ }
1465
+ return false;
1466
+ }
1467
+ function isRelPathIgnored(relPath, sets) {
1468
+ const segments = relPath.split("/");
1469
+ for (let i = 0;i < segments.length - 1; i++) {
1470
+ if (sets.dirs.has(segments[i]))
1471
+ return true;
1472
+ }
1473
+ return isPathIgnored(relPath, sets);
1474
+ }
1475
+ var ALWAYS_IGNORED_DIRS, DEFAULT_DIR_NAMES, DEFAULT_FILE_NAMES, DEFAULT_EXTENSIONS, GLOB_CHARS;
1476
+ var init_ignore_matcher = __esm(() => {
1477
+ ALWAYS_IGNORED_DIRS = [
1478
+ ".git",
1479
+ "node_modules",
1480
+ ".runwork",
1481
+ ".bun-cache",
1482
+ ".npm-cache",
1483
+ ".pnpm-store",
1484
+ ".turbo",
1485
+ ".vite",
1486
+ ".cache",
1487
+ "coverage",
1488
+ "dist"
1489
+ ];
1490
+ DEFAULT_DIR_NAMES = ALWAYS_IGNORED_DIRS;
1491
+ DEFAULT_FILE_NAMES = [
1492
+ ".dev.vars",
1493
+ ".env"
1494
+ ];
1495
+ DEFAULT_EXTENSIONS = [
1496
+ ".log"
1497
+ ];
1498
+ GLOB_CHARS = /[*?\[\]]/;
1499
+ });
1500
+
1501
+ // src/git/repo-config.ts
1502
+ function hardenRepoForRestrictedFs(cwd) {
1503
+ for (const [key, value] of HARDENING) {
1504
+ try {
1505
+ execFileSync("git", ["config", key, value], { cwd, stdio: "pipe" });
1506
+ } catch {}
1507
+ }
1508
+ }
1509
+ function buildInitialGitignore() {
1510
+ const dirs = ALWAYS_IGNORED_DIRS.filter((dir) => dir !== ".git").map((dir) => `${dir}/`);
1511
+ return [
1512
+ "# Dependencies, caches and build output",
1513
+ ...dirs,
1514
+ "",
1515
+ "# Local secrets. Never commit these.",
1516
+ ".dev.vars",
1517
+ ".dev.vars*",
1518
+ ".env",
1519
+ ".env.*",
1520
+ "",
1521
+ "# Logs and tool state",
1522
+ "*.log",
1523
+ "*.tsbuildinfo",
1524
+ ".eslintcache",
1525
+ ""
1526
+ ].join(`
1527
+ `);
1528
+ }
1529
+ var HARDENING;
1530
+ var init_repo_config = __esm(() => {
1531
+ init_subprocess();
1532
+ init_ignore_matcher();
1533
+ HARDENING = [
1534
+ ["gc.auto", "0"],
1535
+ ["maintenance.auto", "false"]
1536
+ ];
1537
+ });
1538
+
1383
1539
  // src/git/preflight.ts
1384
- import { existsSync as existsSync4 } from "fs";
1540
+ import { existsSync as existsSync5 } from "fs";
1385
1541
  import { win32 as winPath } from "path";
1386
1542
  import { homedir as homedir3 } from "os";
1387
1543
  function tryRun(bin) {
@@ -1403,9 +1559,9 @@ function whereGit() {
1403
1559
  const out = buf.toString("utf-8");
1404
1560
  const lines = out.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
1405
1561
  const exe = lines.find((line) => /\.exe$/i.test(line));
1406
- if (exe && existsSync4(exe))
1562
+ if (exe && existsSync5(exe))
1407
1563
  return exe;
1408
- const fallback = lines.find((line) => existsSync4(line));
1564
+ const fallback = lines.find((line) => existsSync5(line));
1409
1565
  return fallback ?? null;
1410
1566
  } catch {
1411
1567
  return null;
@@ -1430,7 +1586,7 @@ function registryGit() {
1430
1586
  continue;
1431
1587
  const installRoot = match[1].trim();
1432
1588
  const gitExe = winPath.join(installRoot, "cmd", "git.exe");
1433
- if (existsSync4(gitExe))
1589
+ if (existsSync5(gitExe))
1434
1590
  return gitExe;
1435
1591
  } catch {}
1436
1592
  }
@@ -1498,7 +1654,7 @@ function probeGit() {
1498
1654
  }
1499
1655
  }
1500
1656
  for (const candidate of canonicalGitCandidates()) {
1501
- if (!existsSync4(candidate))
1657
+ if (!existsSync5(candidate))
1502
1658
  continue;
1503
1659
  const verify = tryRun(candidate);
1504
1660
  if (verify.ok) {
@@ -1636,106 +1792,6 @@ async function resolveApp(client, nameOrId, workspaceId) {
1636
1792
  process.exit(1);
1637
1793
  }
1638
1794
 
1639
- // src/utils/ignore-matcher.ts
1640
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
1641
- import { basename, join as join4 } from "path";
1642
- function defaultIgnoreSets() {
1643
- return {
1644
- dirs: new Set(DEFAULT_DIR_NAMES),
1645
- files: new Set(DEFAULT_FILE_NAMES),
1646
- extensions: new Set(DEFAULT_EXTENSIONS)
1647
- };
1648
- }
1649
- function parseGitignoreContent(content) {
1650
- const dirs = new Set;
1651
- const files = new Set;
1652
- for (const rawLine of content.split(`
1653
- `)) {
1654
- const line = rawLine.trim();
1655
- if (!line || line.startsWith("#"))
1656
- continue;
1657
- if (line.startsWith("!"))
1658
- continue;
1659
- if (GLOB_CHARS.test(line))
1660
- continue;
1661
- let pattern = line;
1662
- const isDirOnly = pattern.endsWith("/");
1663
- if (isDirOnly)
1664
- pattern = pattern.slice(0, -1);
1665
- if (pattern.startsWith("/"))
1666
- pattern = pattern.slice(1);
1667
- if (!pattern)
1668
- continue;
1669
- if (pattern.includes("/"))
1670
- continue;
1671
- if (isDirOnly) {
1672
- dirs.add(pattern);
1673
- } else {
1674
- dirs.add(pattern);
1675
- files.add(pattern);
1676
- }
1677
- }
1678
- return { dirs, files };
1679
- }
1680
- function loadGitignoreFromDir(dir) {
1681
- const path = join4(dir, ".gitignore");
1682
- if (!existsSync5(path))
1683
- return { dirs: new Set, files: new Set };
1684
- try {
1685
- return parseGitignoreContent(readFileSync4(path, "utf-8"));
1686
- } catch {
1687
- return { dirs: new Set, files: new Set };
1688
- }
1689
- }
1690
- function buildIgnoreSets(dir) {
1691
- const sets = defaultIgnoreSets();
1692
- const parsed = loadGitignoreFromDir(dir);
1693
- for (const name of parsed.dirs)
1694
- sets.dirs.add(name);
1695
- for (const name of parsed.files)
1696
- sets.files.add(name);
1697
- return sets;
1698
- }
1699
- function isPathIgnored(filePath, sets) {
1700
- const name = basename(filePath);
1701
- if (sets.dirs.has(name))
1702
- return true;
1703
- if (sets.files.has(name))
1704
- return true;
1705
- const dotIndex = name.lastIndexOf(".");
1706
- if (dotIndex >= 0) {
1707
- const ext = name.slice(dotIndex);
1708
- if (sets.extensions.has(ext))
1709
- return true;
1710
- }
1711
- return false;
1712
- }
1713
- function isRelPathIgnored(relPath, sets) {
1714
- const segments = relPath.split("/");
1715
- for (let i = 0;i < segments.length - 1; i++) {
1716
- if (sets.dirs.has(segments[i]))
1717
- return true;
1718
- }
1719
- return isPathIgnored(relPath, sets);
1720
- }
1721
- var DEFAULT_DIR_NAMES, DEFAULT_FILE_NAMES, DEFAULT_EXTENSIONS, GLOB_CHARS;
1722
- var init_ignore_matcher = __esm(() => {
1723
- DEFAULT_DIR_NAMES = [
1724
- ".git",
1725
- "node_modules",
1726
- ".runwork",
1727
- ".bun-cache"
1728
- ];
1729
- DEFAULT_FILE_NAMES = [
1730
- ".dev.vars",
1731
- ".env"
1732
- ];
1733
- DEFAULT_EXTENSIONS = [
1734
- ".log"
1735
- ];
1736
- GLOB_CHARS = /[*?\[\]]/;
1737
- });
1738
-
1739
1795
  // src/template/manifest.ts
1740
1796
  import { createHash } from "crypto";
1741
1797
  import { readFileSync as readFileSync5, writeFileSync as writeFileSync2, existsSync as existsSync6, readdirSync, mkdirSync as mkdirSync2 } from "fs";
@@ -1782,17 +1838,39 @@ async function saveManifest(dir, manifest) {
1782
1838
  }
1783
1839
  writeFileSync2(join5(manifestDir, "template-manifest.json"), JSON.stringify(manifest, null, 2));
1784
1840
  }
1841
+ function loadManifestSync(dir) {
1842
+ const manifestPath = join5(dir, ".runwork", "template-manifest.json");
1843
+ if (!existsSync6(manifestPath))
1844
+ return null;
1845
+ try {
1846
+ return normalizeManifest(JSON.parse(readFileSync5(manifestPath, "utf-8")));
1847
+ } catch {
1848
+ return null;
1849
+ }
1850
+ }
1851
+ function isPristineTemplateFile(dir, relPath, manifest) {
1852
+ const expectedHash = manifest?.files[relPath];
1853
+ if (!expectedHash)
1854
+ return false;
1855
+ try {
1856
+ return sha256(readFileSync5(join5(dir, relPath))) === expectedHash;
1857
+ } catch {
1858
+ return false;
1859
+ }
1860
+ }
1861
+ function normalizeManifest(manifest) {
1862
+ const files = {};
1863
+ for (const [relPath, hash] of Object.entries(manifest.files)) {
1864
+ files[relPath.split("\\").join("/")] = hash;
1865
+ }
1866
+ return { ...manifest, files };
1867
+ }
1785
1868
  async function loadManifest(dir) {
1786
1869
  const manifestPath = join5(dir, ".runwork", "template-manifest.json");
1787
1870
  if (!existsSync6(manifestPath))
1788
1871
  return null;
1789
1872
  try {
1790
- const manifest = JSON.parse(readFileSync5(manifestPath, "utf-8"));
1791
- const files = {};
1792
- for (const [relPath, hash] of Object.entries(manifest.files)) {
1793
- files[relPath.split("\\").join("/")] = hash;
1794
- }
1795
- return { ...manifest, files };
1873
+ return normalizeManifest(JSON.parse(readFileSync5(manifestPath, "utf-8")));
1796
1874
  } catch {
1797
1875
  return null;
1798
1876
  }
@@ -2251,10 +2329,7 @@ async function execInit(client, appName, workspace, options = {}, creds) {
2251
2329
  execFileSync("git", ["remote", "set-url", "runwork", remoteUrl], { cwd: dir, stdio: "pipe" });
2252
2330
  }
2253
2331
  if (!existsSync7(join8(dir, ".gitignore"))) {
2254
- writeFileSync4(join8(dir, ".gitignore"), `node_modules/
2255
- .runwork/
2256
- .dev.vars
2257
- `);
2332
+ writeFileSync4(join8(dir, ".gitignore"), buildInitialGitignore());
2258
2333
  }
2259
2334
  execFileSync("git", ["add", "--", ".runwork.json", ".gitignore"], { cwd: dir, stdio: "pipe" });
2260
2335
  try {
@@ -2335,6 +2410,7 @@ var init_init = __esm(() => {
2335
2410
  init_store();
2336
2411
  init_client();
2337
2412
  init_identity();
2413
+ init_repo_config();
2338
2414
  init_preflight();
2339
2415
  init_prompt();
2340
2416
  init_manifest();
@@ -2396,23 +2472,6 @@ var init_remote = __esm(() => {
2396
2472
  init_subprocess();
2397
2473
  });
2398
2474
 
2399
- // src/git/repo-config.ts
2400
- function hardenRepoForRestrictedFs(cwd) {
2401
- for (const [key, value] of HARDENING) {
2402
- try {
2403
- execFileSync("git", ["config", key, value], { cwd, stdio: "pipe" });
2404
- } catch {}
2405
- }
2406
- }
2407
- var HARDENING;
2408
- var init_repo_config = __esm(() => {
2409
- init_subprocess();
2410
- HARDENING = [
2411
- ["gc.auto", "0"],
2412
- ["maintenance.auto", "false"]
2413
- ];
2414
- });
2415
-
2416
2475
  // src/git/classify-sync-error.ts
2417
2476
  function classifySyncError(raw) {
2418
2477
  if (!raw)
@@ -2420,6 +2479,9 @@ function classifySyncError(raw) {
2420
2479
  const s = raw.toLowerCase();
2421
2480
  if (raw === STASH_CONFLICT)
2422
2481
  return "conflict";
2482
+ if (s.includes("[remote rejected]") || s.includes("[rejected]") || s.includes("pre-receive hook declined") || s.includes("push declined")) {
2483
+ return "rejected";
2484
+ }
2423
2485
  if (s.includes("does not appear to be a git repository") || s.includes("'runwork' does not appear") || s.includes("no such remote") || s.includes("remote") && s.includes("not found")) {
2424
2486
  return "no-remote";
2425
2487
  }
@@ -2467,6 +2529,19 @@ function diagnoseSyncError(raw) {
2467
2529
  "Confirm outbound access to runwork.ai, then run `runwork dev` again"
2468
2530
  ]
2469
2531
  };
2532
+ case "rejected":
2533
+ return {
2534
+ reason,
2535
+ message: "the server refused the push",
2536
+ diagnosis: `The runwork remote rejected this push, so nothing was updated on the server. It said:
2537
+ ${(raw ?? "").trim()}`,
2538
+ suggestions: [
2539
+ 'Read the "remote:" lines above: they name the exact reason and the fix',
2540
+ "If an oversized file was rejected, remove it from the commit and add it to .gitignore",
2541
+ "If the history is reported as damaged, contact support to rebuild it",
2542
+ "Retrying without changing anything will fail the same way"
2543
+ ]
2544
+ };
2470
2545
  case "conflict":
2471
2546
  return {
2472
2547
  reason,
@@ -2942,26 +3017,61 @@ function hasTrackedChanges(cwd) {
2942
3017
  }
2943
3018
  }
2944
3019
  function removeConflictingUntrackedFiles(cwd) {
3020
+ const kept = [];
2945
3021
  try {
2946
3022
  const remoteFiles = execFileSync("git", ["ls-tree", "-r", "--name-only", "runwork/main"], {
2947
3023
  cwd,
2948
3024
  encoding: "utf-8"
2949
3025
  }).trim().split(`
2950
3026
  `);
2951
- const untrackedOutput = execFileSync("git", ["ls-files", "--others", "--exclude-standard"], {
3027
+ const untrackedOutput = execFileSync("git", ["-c", "core.quotePath=false", "ls-files", "--others", "--exclude-standard"], {
2952
3028
  cwd,
2953
3029
  encoding: "utf-8"
2954
3030
  }).trim();
2955
3031
  const untracked = new Set(untrackedOutput.split(`
2956
3032
  `).filter(Boolean));
3033
+ const manifest = loadManifestSync(cwd);
2957
3034
  for (const file of remoteFiles) {
2958
- if (untracked.has(file)) {
3035
+ if (!untracked.has(file))
3036
+ continue;
3037
+ if (matchesRemote(cwd, file) || isPristineTemplateFile(cwd, file, manifest)) {
2959
3038
  try {
2960
3039
  unlinkSync2(join11(cwd, file));
2961
3040
  } catch {}
3041
+ } else {
3042
+ kept.push(file);
2962
3043
  }
2963
3044
  }
2964
3045
  } catch {}
3046
+ return { kept };
3047
+ }
3048
+ function matchesRemote(cwd, file) {
3049
+ try {
3050
+ const remoteOid = execFileSync("git", ["rev-parse", `runwork/main:${file}`], {
3051
+ cwd,
3052
+ encoding: "utf-8",
3053
+ stdio: ["ignore", "pipe", "ignore"]
3054
+ }).trim();
3055
+ const localOid = execFileSync("git", ["hash-object", "--", file], {
3056
+ cwd,
3057
+ encoding: "utf-8",
3058
+ stdio: ["ignore", "pipe", "ignore"]
3059
+ }).trim();
3060
+ return Boolean(remoteOid) && remoteOid === localOid;
3061
+ } catch {
3062
+ return false;
3063
+ }
3064
+ }
3065
+ function conflictedPaths(cwd) {
3066
+ try {
3067
+ return execFileSync("git", ["-c", "core.quotePath=false", "diff", "--name-only", "--diff-filter=U"], {
3068
+ cwd,
3069
+ encoding: "utf-8"
3070
+ }).trim().split(`
3071
+ `).filter(Boolean);
3072
+ } catch {
3073
+ return [];
3074
+ }
2965
3075
  }
2966
3076
  function extractGitError(err) {
2967
3077
  if (err && typeof err === "object") {
@@ -2996,9 +3106,11 @@ function syncWithRemote(cwd) {
2996
3106
  }
2997
3107
  let status = "synced";
2998
3108
  let syncError;
3109
+ let keptUntracked = [];
3110
+ let remoteOverwrote = [];
2999
3111
  try {
3000
3112
  execFileSync("git", ["fetch", "runwork", "main"], { cwd, stdio: "pipe" });
3001
- removeConflictingUntrackedFiles(cwd);
3113
+ keptUntracked = removeConflictingUntrackedFiles(cwd).kept;
3002
3114
  try {
3003
3115
  execFileSync("git", ["rebase", "runwork/main"], { cwd, stdio: "pipe" });
3004
3116
  } catch (rebaseErr) {
@@ -3009,6 +3121,7 @@ function syncWithRemote(cwd) {
3009
3121
  execFileSync("git", ["merge", "runwork/main", "--allow-unrelated-histories", "--no-edit"], { cwd, stdio: "pipe" });
3010
3122
  status = "merged";
3011
3123
  } catch {
3124
+ remoteOverwrote = conflictedPaths(cwd);
3012
3125
  try {
3013
3126
  execFileSync("git", ["merge", "--abort"], { cwd, stdio: "pipe" });
3014
3127
  } catch {}
@@ -3019,6 +3132,7 @@ function syncWithRemote(cwd) {
3019
3132
  try {
3020
3133
  execFileSync("git", ["merge", "--abort"], { cwd, stdio: "pipe" });
3021
3134
  } catch {}
3135
+ remoteOverwrote = [];
3022
3136
  status = "sync-failed";
3023
3137
  syncError = extractGitError(mergeErr);
3024
3138
  }
@@ -3032,21 +3146,25 @@ function syncWithRemote(cwd) {
3032
3146
  try {
3033
3147
  execFileSync("git", ["stash", "pop"], { cwd, stdio: "pipe" });
3034
3148
  } catch {
3035
- return { status, pushed: false, error: "stash-conflict" };
3149
+ return { status, pushed: false, error: "stash-conflict", keptUntracked, remoteOverwrote };
3036
3150
  }
3037
3151
  }
3038
3152
  if (status === "sync-failed") {
3039
- return { status, pushed: false, error: syncError };
3153
+ return { status, pushed: false, error: syncError, keptUntracked, remoteOverwrote };
3040
3154
  }
3041
3155
  let pushed = false;
3156
+ let pushError;
3042
3157
  try {
3043
3158
  execFileSync("git", ["push", "runwork", "HEAD:main"], { cwd, stdio: "pipe" });
3044
3159
  pushed = true;
3045
- } catch {}
3046
- return { status, pushed };
3160
+ } catch (pushErr) {
3161
+ pushError = extractGitError(pushErr);
3162
+ }
3163
+ return { status, pushed, pushError, keptUntracked, remoteOverwrote };
3047
3164
  }
3048
3165
  var init_sync = __esm(() => {
3049
3166
  init_subprocess();
3167
+ init_manifest();
3050
3168
  });
3051
3169
 
3052
3170
  // src/git/critical-files.ts
@@ -4982,6 +5100,31 @@ export interface ClientErrorReport {
4982
5100
  level?: 'error' | 'warning' | 'info';
4983
5101
  category?: string;
4984
5102
  }
5103
+ /**
5104
+ * Decide whether to send the permissive \`Permissions-Policy\` header for an app
5105
+ * that is being embedded in an iframe.
5106
+ *
5107
+ * A document's own Permissions-Policy can only restrict, never grant: the grant
5108
+ * comes from the embedding page's policy plus the iframe \`allow\` attribute. This
5109
+ * header's job is therefore to stop the app self-restricting the features the
5110
+ * platform preview delegates to it, and this predicate decides who is trusted
5111
+ * enough to be handed that.
5112
+ *
5113
+ * Hostnames are parsed and compared exactly, never substring-matched against the
5114
+ * raw header. The difference is not cosmetic: \`origin.endsWith('runwork.ai')\`
5115
+ * also matches \`https://notrunwork.ai\`, and \`origin.includes(domain)\` matches
5116
+ * \`https://evil-acme.com.attacker.net\` against a tenant's \`acme.com\`, which would
5117
+ * hand an attacker's page camera, microphone, geolocation and screen capture over
5118
+ * an embedded app.
5119
+ *
5120
+ * @param origin The \`Origin\` header, or the \`Referer\` as a fallback. Origin is a
5121
+ * bare origin and Referer is a full URL; both parse the same way.
5122
+ * Empty means direct (non-embedded) access.
5123
+ * @param allowedOrigins Comma-separated exact hostnames from \`ALLOWED_ORIGINS\`,
5124
+ * injected by the platform as the workspace's active custom
5125
+ * domains. Matched exactly; subdomains are deliberately excluded.
5126
+ */
5127
+ export declare function shouldGrantIframePermissions(origin: string, allowedOrigins?: string): boolean;
4985
5128
  /**
4986
5129
  * Mount all core platform routes on the Hono app
4987
5130
  * Called from index.ts before user-defined routes
@@ -7840,7 +7983,7 @@ function createKeyboardListener() {
7840
7983
  }
7841
7984
 
7842
7985
  // src/generated/version.ts
7843
- var VERSION = "0.25.0";
7986
+ var VERSION = "0.25.2";
7844
7987
 
7845
7988
  // src/commands/dev.ts
7846
7989
  var exports_dev = {};
@@ -8068,6 +8211,36 @@ async function execDev(options) {
8068
8211
  }
8069
8212
  }
8070
8213
  }
8214
+ if (syncResult.remoteOverwrote && syncResult.remoteOverwrote.length > 0) {
8215
+ if (useJson) {
8216
+ jsonLine({ event: "sync_remote_overwrote", files: syncResult.remoteOverwrote, timestamp: ts() });
8217
+ } else {
8218
+ console.warn(yellow(` Sync resolved conflicts in the remote's favour; local changes were replaced in: ${syncResult.remoteOverwrote.join(", ")}`));
8219
+ console.warn(dim(" Recover them with `git reflog` / `git diff ORIG_HEAD` if that was wrong."));
8220
+ }
8221
+ }
8222
+ if (syncResult.keptUntracked && syncResult.keptUntracked.length > 0) {
8223
+ if (useJson) {
8224
+ jsonLine({ event: "sync_kept_untracked", files: syncResult.keptUntracked, timestamp: ts() });
8225
+ } else {
8226
+ console.warn(yellow(` Kept your untracked file(s) over the remote's copy: ${syncResult.keptUntracked.join(", ")}`));
8227
+ console.warn(dim(" Commit or remove them so the sync can reconcile that path."));
8228
+ }
8229
+ }
8230
+ if (!syncResult.pushed && syncResult.pushError) {
8231
+ const diag = diagnoseSyncError(syncResult.pushError);
8232
+ if (useJson) {
8233
+ jsonLine({
8234
+ event: "error",
8235
+ phase: "push",
8236
+ timestamp: ts(),
8237
+ error: { reason: diag.reason, message: diag.message, diagnosis: diag.diagnosis, suggestions: diag.suggestions }
8238
+ });
8239
+ } else {
8240
+ console.warn(yellow(` Push failed: ${diag.message}`));
8241
+ console.warn(dim(` ${diag.diagnosis}`));
8242
+ }
8243
+ }
8071
8244
  if (useJson) {
8072
8245
  jsonLine({ event: "startup", phase: "sync", status: syncResult.status, pushed: syncResult.pushed, timestamp: ts() });
8073
8246
  if (syncResult.status === "sync-failed") {
@@ -8942,7 +9115,10 @@ var init_resolve = __esm(() => {
8942
9115
 
8943
9116
  // ../../shared/skill/skill-canonical.ts
8944
9117
  function toSkillSlug(value) {
8945
- return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9118
+ return transliterateLatin(value.trim()).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9119
+ }
9120
+ function transliterateLatin(value) {
9121
+ return value.replace(/[ıßøØłŁđĐæÆœŒþÞðÐ]/g, (ch) => NON_DECOMPOSABLE_LATIN[ch] ?? ch).normalize("NFD").replace(/[\u0300-\u036f]/g, "");
8946
9122
  }
8947
9123
  function isStructuredYamlValue(value) {
8948
9124
  if (value.includes(`
@@ -8987,11 +9163,13 @@ function parseSkillMd(content) {
8987
9163
  const lines = content.split(`
8988
9164
  `);
8989
9165
  if (lines[0]?.trim() !== "---") {
8990
- const rawName = lines[0]?.replace(/^#\s*/, "").trim() || "Untitled Skill";
9166
+ const firstLine = lines[0]?.trim() ?? "";
9167
+ const isHeading = /^#+\s/.test(firstLine);
9168
+ const rawName = firstLine.replace(/^#+\s*/, "").trim() || "Untitled Skill";
8991
9169
  return {
8992
9170
  frontmatter: {
8993
9171
  name: toSkillSlug(rawName) || "untitled-skill",
8994
- description: ""
9172
+ description: isHeading ? rawName : ""
8995
9173
  },
8996
9174
  orderedKeys: [],
8997
9175
  body: content,
@@ -9078,6 +9256,27 @@ function buildSkillMd(parts) {
9078
9256
  return lines.join(`
9079
9257
  `);
9080
9258
  }
9259
+ var NON_DECOMPOSABLE_LATIN;
9260
+ var init_skill_canonical = __esm(() => {
9261
+ NON_DECOMPOSABLE_LATIN = {
9262
+ "ı": "i",
9263
+ "ß": "ss",
9264
+ "ø": "o",
9265
+ "Ø": "O",
9266
+ "ł": "l",
9267
+ "Ł": "L",
9268
+ "đ": "d",
9269
+ "Đ": "D",
9270
+ "æ": "ae",
9271
+ "Æ": "AE",
9272
+ "œ": "oe",
9273
+ "Œ": "OE",
9274
+ "þ": "th",
9275
+ "Þ": "TH",
9276
+ "ð": "d",
9277
+ "Ð": "D"
9278
+ };
9279
+ });
9081
9280
 
9082
9281
  // src/agents/registry-data.ts
9083
9282
  function chatgptConnectorSteps(confirmLead) {
@@ -10716,6 +10915,7 @@ function buildSkillMd2(skill) {
10716
10915
  }
10717
10916
  var RUNWORK_MCP_PREFIX = "Runwork: ", RUNWORK_MCP_PREFIX_LEGACY = "runwork-", RUNWORK_WORKSPACE_MCP_NAME = "Runwork", RUNWORK_PLUGIN_MARKETPLACE = "runwork", toSlug;
10718
10917
  var init_types = __esm(() => {
10918
+ init_skill_canonical();
10719
10919
  toSlug = toSkillSlug;
10720
10920
  });
10721
10921
 
@@ -17964,12 +18164,28 @@ var deployCommand = new Command5("deploy").description("Deploy the current app t
17964
18164
  console.error(` - ${s}`);
17965
18165
  process.exit(1);
17966
18166
  }
18167
+ if (syncResult.remoteOverwrote && syncResult.remoteOverwrote.length > 0) {
18168
+ const files = syncResult.remoteOverwrote.join(", ");
18169
+ if (useJson) {
18170
+ jsonLine({ event: "sync_remote_overwrote", files: syncResult.remoteOverwrote, timestamp: new Date().toISOString() });
18171
+ } else {
18172
+ console.warn(`Sync resolved conflicts in the remote's favour; local changes were replaced in: ${files}`);
18173
+ console.warn(" Recover them with `git reflog` / `git diff ORIG_HEAD` before deploying again if that was wrong.");
18174
+ }
18175
+ }
18176
+ if (syncResult.keptUntracked && syncResult.keptUntracked.length > 0 && !useJson) {
18177
+ console.warn(`Kept your untracked file(s) over the remote's copy: ${syncResult.keptUntracked.join(", ")}`);
18178
+ }
17967
18179
  if (!syncResult.pushed) {
18180
+ const diag = diagnoseSyncError(syncResult.pushError ?? syncResult.error);
17968
18181
  if (useJson) {
17969
- jsonOut(buildErrorResponse("deploy", "Failed to push before deployment", "Local commits were synced but could not be pushed to the remote.", ["Check your network connection", "Run runwork dev to retry syncing", "Then retry runwork deploy"]));
18182
+ jsonOut(buildErrorResponse("deploy", `Failed to push before deployment: ${diag.message}`, diag.diagnosis, [...diag.suggestions, "Then retry runwork deploy"]));
17970
18183
  process.exit(1);
17971
18184
  }
17972
- console.error("Push failed. Check your connection and retry `runwork deploy`.");
18185
+ console.error(`Push failed: ${diag.message}`);
18186
+ console.error(diag.diagnosis);
18187
+ for (const suggestion of diag.suggestions)
18188
+ console.error(` - ${suggestion}`);
17973
18189
  process.exit(1);
17974
18190
  }
17975
18191
  if (!useJson)
@@ -19646,6 +19862,7 @@ var infoCommand = new Command12("info").description("Show app context, registrie
19646
19862
  init_store();
19647
19863
  init_client();
19648
19864
  init_resolve();
19865
+ init_skill_canonical();
19649
19866
  import { Command as Command13 } from "commander";
19650
19867
  import { readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
19651
19868
  function truncate(text2, max) {
@@ -19720,7 +19937,7 @@ function buildSkillPushPayload(fileContent, nameArg) {
19720
19937
  const name = toSkillSlug(nameArg || "") || docName;
19721
19938
  if (!name)
19722
19939
  return null;
19723
- const description = parsed.hadFrontmatter ? parsed.frontmatter.description : "";
19940
+ const description = parsed.frontmatter.description;
19724
19941
  const extra = {};
19725
19942
  for (const key of parsed.orderedKeys) {
19726
19943
  if (key === "name" || key === "description")
@@ -22392,8 +22609,8 @@ init_resolve();
22392
22609
  init_prompt();
22393
22610
  await init_detect();
22394
22611
  import { Command as Command27 } from "commander";
22395
- import { join as join48 } from "path";
22396
- import { homedir as homedir27 } from "os";
22612
+ import { join as join49 } from "path";
22613
+ import { homedir as homedir28 } from "os";
22397
22614
 
22398
22615
  // src/commands/sync.ts
22399
22616
  init_store();
@@ -22404,9 +22621,9 @@ await __promiseAll([
22404
22621
  init_codex()
22405
22622
  ]);
22406
22623
  import { Command as Command26 } from "commander";
22407
- import { readFileSync as readFileSync40, existsSync as existsSync51 } from "fs";
22408
- import { join as join47 } from "path";
22409
- import { homedir as homedir26 } from "os";
22624
+ import { readFileSync as readFileSync41, existsSync as existsSync52 } from "fs";
22625
+ import { join as join48 } from "path";
22626
+ import { homedir as homedir27 } from "os";
22410
22627
 
22411
22628
  // src/commands/mcp-entries.ts
22412
22629
  init_types();
@@ -23265,6 +23482,91 @@ function sameStringSet(a, b) {
23265
23482
  return true;
23266
23483
  }
23267
23484
 
23485
+ // src/utils/sync-lock.ts
23486
+ import { existsSync as existsSync51, mkdirSync as mkdirSync28, readFileSync as readFileSync40, unlinkSync as unlinkSync8, writeFileSync as writeFileSync30 } from "fs";
23487
+ import { join as join47 } from "path";
23488
+ import { homedir as homedir26 } from "os";
23489
+ var LOCK_PATH = join47(homedir26(), ".runwork", "sync.lock");
23490
+ var STALE_LOCK_MS = 5 * 60 * 1000;
23491
+ var DEFAULT_WAIT_MS = 30000;
23492
+ var exitHandlerRegistered = false;
23493
+ function ensureExitHandler() {
23494
+ if (exitHandlerRegistered)
23495
+ return;
23496
+ exitHandlerRegistered = true;
23497
+ process.once("exit", releaseSyncLock);
23498
+ }
23499
+ function isProcessAlive(pid) {
23500
+ try {
23501
+ process.kill(pid, 0);
23502
+ return true;
23503
+ } catch (err) {
23504
+ return err?.code === "EPERM";
23505
+ }
23506
+ }
23507
+ function readLock() {
23508
+ try {
23509
+ return JSON.parse(readFileSync40(LOCK_PATH, "utf-8"));
23510
+ } catch {
23511
+ return null;
23512
+ }
23513
+ }
23514
+ function writeLockExclusive() {
23515
+ try {
23516
+ if (!existsSync51(join47(homedir26(), ".runwork"))) {
23517
+ mkdirSync28(join47(homedir26(), ".runwork"), { recursive: true });
23518
+ }
23519
+ writeFileSync30(LOCK_PATH, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), {
23520
+ flag: "wx"
23521
+ });
23522
+ return true;
23523
+ } catch {
23524
+ return false;
23525
+ }
23526
+ }
23527
+ function tryAcquireOnce() {
23528
+ if (writeLockExclusive()) {
23529
+ ensureExitHandler();
23530
+ return true;
23531
+ }
23532
+ const existing = readLock();
23533
+ const stale = !existing || Date.now() - existing.startedAt > STALE_LOCK_MS || !isProcessAlive(existing.pid);
23534
+ if (!stale)
23535
+ return false;
23536
+ try {
23537
+ unlinkSync8(LOCK_PATH);
23538
+ } catch {}
23539
+ if (writeLockExclusive()) {
23540
+ ensureExitHandler();
23541
+ return true;
23542
+ }
23543
+ return false;
23544
+ }
23545
+ function sleep2(ms) {
23546
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
23547
+ }
23548
+ async function acquireSyncLock(waitMs = DEFAULT_WAIT_MS) {
23549
+ const deadline = Date.now() + waitMs;
23550
+ let delay = 250;
23551
+ for (;; ) {
23552
+ if (tryAcquireOnce())
23553
+ return true;
23554
+ const remaining = deadline - Date.now();
23555
+ if (remaining <= 0)
23556
+ return false;
23557
+ await sleep2(Math.min(delay, remaining));
23558
+ delay = Math.min(delay * 2, 5000);
23559
+ }
23560
+ }
23561
+ function releaseSyncLock() {
23562
+ const existing = readLock();
23563
+ if (existing?.pid === process.pid) {
23564
+ try {
23565
+ unlinkSync8(LOCK_PATH);
23566
+ } catch {}
23567
+ }
23568
+ }
23569
+
23268
23570
  // src/commands/sync.ts
23269
23571
  async function printAdoptionHint(credentials, workspaceId) {
23270
23572
  if (!workspaceId)
@@ -23281,10 +23583,10 @@ Tip: ${hint.title}`);
23281
23583
  } catch {}
23282
23584
  }
23283
23585
  function loadSetupState(filePath) {
23284
- if (!existsSync51(filePath))
23586
+ if (!existsSync52(filePath))
23285
23587
  return null;
23286
23588
  try {
23287
- return JSON.parse(readFileSync40(filePath, "utf-8"));
23589
+ return JSON.parse(readFileSync41(filePath, "utf-8"));
23288
23590
  } catch {
23289
23591
  return null;
23290
23592
  }
@@ -23305,15 +23607,15 @@ function readLocalSkills(state) {
23305
23607
  if (!baseDir)
23306
23608
  continue;
23307
23609
  for (const skillName of state.skills) {
23308
- const skillMdPath = join47(baseDir, skillName, "SKILL.md");
23309
- if (existsSync51(skillMdPath)) {
23310
- results.push({ name: skillName, content: readFileSync40(skillMdPath, "utf-8") });
23610
+ const skillMdPath = join48(baseDir, skillName, "SKILL.md");
23611
+ if (existsSync52(skillMdPath)) {
23612
+ results.push({ name: skillName, content: readFileSync41(skillMdPath, "utf-8") });
23311
23613
  continue;
23312
23614
  }
23313
23615
  const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
23314
- const flatPath = join47(baseDir, `${filename}.md`);
23315
- if (existsSync51(flatPath)) {
23316
- results.push({ name: skillName, content: readFileSync40(flatPath, "utf-8") });
23616
+ const flatPath = join48(baseDir, `${filename}.md`);
23617
+ if (existsSync52(flatPath)) {
23618
+ results.push({ name: skillName, content: readFileSync41(flatPath, "utf-8") });
23317
23619
  }
23318
23620
  }
23319
23621
  if (results.length > 0)
@@ -23367,6 +23669,19 @@ function ensureWorkspacePointer(state, statePath2, credentials) {
23367
23669
  return true;
23368
23670
  }
23369
23671
  async function syncFromState(state, statePath2, credentials, opts) {
23672
+ const acquired = await acquireSyncLock();
23673
+ if (!acquired) {
23674
+ console.log(" Another sync appears to be in progress on this machine; proceeding anyway.");
23675
+ await runSyncFromState(state, statePath2, credentials, opts);
23676
+ return;
23677
+ }
23678
+ try {
23679
+ await runSyncFromState(state, statePath2, credentials, opts);
23680
+ } finally {
23681
+ releaseSyncLock();
23682
+ }
23683
+ }
23684
+ async function runSyncFromState(state, statePath2, credentials, opts) {
23370
23685
  const client = new ApiClient(credentials);
23371
23686
  setVerbose(!!opts.verbose);
23372
23687
  if (!ensureWorkspacePointer(state, statePath2, credentials)) {
@@ -23554,9 +23869,9 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23554
23869
  persona: state.persona
23555
23870
  });
23556
23871
  let projectAppSkillFilter = null;
23557
- if (existsSync51(".runwork.json")) {
23872
+ if (existsSync52(".runwork.json")) {
23558
23873
  try {
23559
- const config = JSON.parse(readFileSync40(".runwork.json", "utf-8"));
23874
+ const config = JSON.parse(readFileSync41(".runwork.json", "utf-8"));
23560
23875
  if (config.appName) {
23561
23876
  projectAppSkillFilter = config.appName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
23562
23877
  }
@@ -23793,7 +24108,7 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23793
24108
  }
23794
24109
  for (const adapter2 of adapters) {
23795
24110
  if (adapter2 instanceof CodexAdapter) {
23796
- const runworkDir = join47(homedir26(), ".runwork");
24111
+ const runworkDir = join48(homedir27(), ".runwork");
23797
24112
  const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
23798
24113
  if (result === "written") {
23799
24114
  vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
@@ -23935,8 +24250,8 @@ var syncCommand = new Command26("sync").description("Sync skills bidirectionally
23935
24250
  verbose: !!opts.verbose,
23936
24251
  redetect: !!opts.redetect
23937
24252
  };
23938
- const projectStatePath = join47(process.cwd(), ".runwork", "setup.json");
23939
- const userStatePath = join47(homedir26(), ".runwork", "setup.json");
24253
+ const projectStatePath = join48(process.cwd(), ".runwork", "setup.json");
24254
+ const userStatePath = join48(homedir27(), ".runwork", "setup.json");
23940
24255
  const projectState = loadSetupState(projectStatePath);
23941
24256
  const userState = loadSetupState(userStatePath);
23942
24257
  if (!projectState && !userState) {
@@ -24005,7 +24320,7 @@ function toSkillFilename(name) {
24005
24320
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
24006
24321
  }
24007
24322
  function loadSetupStateForScope(scope) {
24008
- const path4 = scope === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
24323
+ const path4 = scope === "project" ? join49(process.cwd(), ".runwork", "setup.json") : join49(homedir28(), ".runwork", "setup.json");
24009
24324
  return readJsonOrNull(path4);
24010
24325
  }
24011
24326
  async function parkAndTeardownWorkspace(previous, scopes) {
@@ -24174,8 +24489,8 @@ Re-run without --dry-run to sync workspace data.`);
24174
24489
  }
24175
24490
  persistDefaultWorkspace(workspaceId, workspaceName);
24176
24491
  for (const s of scopes) {
24177
- const dir = s === "project" ? ".runwork" : join48(homedir27(), ".runwork");
24178
- writeJsonAtomic(join48(dir, "setup.json"), state);
24492
+ const dir = s === "project" ? ".runwork" : join49(homedir28(), ".runwork");
24493
+ writeJsonAtomic(join49(dir, "setup.json"), state);
24179
24494
  }
24180
24495
  if (restored)
24181
24496
  clearParkedState(workspaceId);
@@ -24183,7 +24498,7 @@ Re-run without --dry-run to sync workspace data.`);
24183
24498
  Syncing workspace data...
24184
24499
  `);
24185
24500
  for (const s of scopes) {
24186
- const statePath2 = s === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
24501
+ const statePath2 = s === "project" ? join49(process.cwd(), ".runwork", "setup.json") : join49(homedir28(), ".runwork", "setup.json");
24187
24502
  await syncFromState(state, statePath2, credentials, {
24188
24503
  dryRun: false,
24189
24504
  pullOnly: true,
@@ -24203,16 +24518,16 @@ init_client();
24203
24518
  import { Command as Command28 } from "commander";
24204
24519
 
24205
24520
  // src/utils/setup-state.ts
24206
- import { existsSync as existsSync52, readFileSync as readFileSync41 } from "fs";
24207
- import { join as join49 } from "path";
24208
- import { homedir as homedir28 } from "os";
24521
+ import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
24522
+ import { join as join50 } from "path";
24523
+ import { homedir as homedir29 } from "os";
24209
24524
  function loadSetupState2() {
24210
- const projectPath = join49(process.cwd(), ".runwork", "setup.json");
24211
- const userPath = join49(homedir28(), ".runwork", "setup.json");
24525
+ const projectPath = join50(process.cwd(), ".runwork", "setup.json");
24526
+ const userPath = join50(homedir29(), ".runwork", "setup.json");
24212
24527
  for (const p of [projectPath, userPath]) {
24213
- if (existsSync52(p)) {
24528
+ if (existsSync53(p)) {
24214
24529
  try {
24215
- return JSON.parse(readFileSync41(p, "utf-8"));
24530
+ return JSON.parse(readFileSync42(p, "utf-8"));
24216
24531
  } catch {
24217
24532
  continue;
24218
24533
  }
@@ -24271,14 +24586,14 @@ init_client();
24271
24586
  init_types();
24272
24587
  await init_detect();
24273
24588
  import { Command as Command29 } from "commander";
24274
- import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
24275
- import { resolve as resolve3, join as join50 } from "path";
24276
- import { homedir as homedir29 } from "os";
24589
+ import { existsSync as existsSync54, readFileSync as readFileSync43 } from "fs";
24590
+ import { resolve as resolve3, join as join51 } from "path";
24591
+ import { homedir as homedir30 } from "os";
24277
24592
  function loadSetupState3(filePath) {
24278
- if (!existsSync53(filePath))
24593
+ if (!existsSync54(filePath))
24279
24594
  return null;
24280
24595
  try {
24281
- return JSON.parse(readFileSync42(filePath, "utf-8"));
24596
+ return JSON.parse(readFileSync43(filePath, "utf-8"));
24282
24597
  } catch {
24283
24598
  return null;
24284
24599
  }
@@ -24294,8 +24609,8 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
24294
24609
  process.exit(1);
24295
24610
  }
24296
24611
  const credentials = requireAuth();
24297
- const projectStatePath = join50(process.cwd(), ".runwork", "setup.json");
24298
- const userStatePath = join50(homedir29(), ".runwork", "setup.json");
24612
+ const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
24613
+ const userStatePath = join51(homedir30(), ".runwork", "setup.json");
24299
24614
  const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
24300
24615
  if (!state) {
24301
24616
  console.error("No setup state found. Run `runwork setup` first.");
@@ -24387,21 +24702,21 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
24387
24702
  init_prompt();
24388
24703
  await init_detect();
24389
24704
  import { Command as Command30 } from "commander";
24390
- import { existsSync as existsSync54, readFileSync as readFileSync43, rmSync as rmSync13, unlinkSync as unlinkSync8 } from "fs";
24391
- import { join as join51 } from "path";
24392
- import { homedir as homedir30 } from "os";
24705
+ import { existsSync as existsSync55, readFileSync as readFileSync44, rmSync as rmSync13, unlinkSync as unlinkSync9 } from "fs";
24706
+ import { join as join52 } from "path";
24707
+ import { homedir as homedir31 } from "os";
24393
24708
  function loadSetupState4(filePath) {
24394
- if (!existsSync54(filePath))
24709
+ if (!existsSync55(filePath))
24395
24710
  return null;
24396
24711
  try {
24397
- return JSON.parse(readFileSync43(filePath, "utf-8"));
24712
+ return JSON.parse(readFileSync44(filePath, "utf-8"));
24398
24713
  } catch {
24399
24714
  return null;
24400
24715
  }
24401
24716
  }
24402
24717
  var uninstallCommand = new Command30("uninstall").description("Remove all Runwork configuration from local agents (MCP servers, skills, instructions)").option("-y, --yes", "Skip confirmation prompt").option("--keep-auth", "Keep authentication credentials (only remove agent configs)").action(async (opts) => {
24403
- const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
24404
- const userStatePath = join51(homedir30(), ".runwork", "setup.json");
24718
+ const projectStatePath = join52(process.cwd(), ".runwork", "setup.json");
24719
+ const userStatePath = join52(homedir31(), ".runwork", "setup.json");
24405
24720
  const projectState = loadSetupState4(projectStatePath);
24406
24721
  const userState = loadSetupState4(userStatePath);
24407
24722
  if (!projectState && !userState) {
@@ -24481,19 +24796,19 @@ This will remove all Runwork configuration from your local agents:
24481
24796
  }
24482
24797
  }
24483
24798
  }
24484
- const stateDir = label === "project" ? join51(process.cwd(), ".runwork") : join51(homedir30(), ".runwork");
24799
+ const stateDir = label === "project" ? join52(process.cwd(), ".runwork") : join52(homedir31(), ".runwork");
24485
24800
  if (opts.keepAuth && label === "user") {
24486
- const setupFile = join51(stateDir, "setup.json");
24487
- if (existsSync54(setupFile)) {
24801
+ const setupFile = join52(stateDir, "setup.json");
24802
+ if (existsSync55(setupFile)) {
24488
24803
  try {
24489
- unlinkSync8(setupFile);
24804
+ unlinkSync9(setupFile);
24490
24805
  console.log(` Removed ${setupFile} (kept credentials)`);
24491
24806
  } catch (err) {
24492
24807
  console.warn(` Failed to remove ${setupFile}: ${err instanceof Error ? err.message : err}`);
24493
24808
  errors++;
24494
24809
  }
24495
24810
  }
24496
- } else if (existsSync54(stateDir)) {
24811
+ } else if (existsSync55(stateDir)) {
24497
24812
  try {
24498
24813
  rmSync13(stateDir, { recursive: true, force: true });
24499
24814
  console.log(` Removed ${stateDir}`);
@@ -24627,7 +24942,7 @@ var membersCommand = new Command32("members").description("List workspace member
24627
24942
  init_store();
24628
24943
  init_client();
24629
24944
  import { Command as Command33 } from "commander";
24630
- import { readFileSync as readFileSync44 } from "fs";
24945
+ import { readFileSync as readFileSync45 } from "fs";
24631
24946
  function normalizeApiPath(rawPath, baseUrl) {
24632
24947
  if (/^https?:\/\//i.test(rawPath)) {
24633
24948
  const target = new URL(rawPath);
@@ -24660,7 +24975,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
24660
24975
  let curlStr = opts.curl;
24661
24976
  if (opts.curlFile) {
24662
24977
  try {
24663
- curlStr = readFileSync44(opts.curlFile, "utf-8");
24978
+ curlStr = readFileSync45(opts.curlFile, "utf-8");
24664
24979
  } catch (err) {
24665
24980
  console.error(`Could not read --curl-file: ${err instanceof Error ? err.message : err}`);
24666
24981
  process.exit(1);
@@ -24680,7 +24995,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
24680
24995
  let raw = opts.body;
24681
24996
  if (raw.startsWith("@")) {
24682
24997
  try {
24683
- raw = readFileSync44(raw.slice(1), "utf-8");
24998
+ raw = readFileSync45(raw.slice(1), "utf-8");
24684
24999
  } catch (err) {
24685
25000
  console.error(`Could not read body file: ${err instanceof Error ? err.message : err}`);
24686
25001
  process.exit(1);
@@ -24733,9 +25048,9 @@ init_preflight();
24733
25048
  init_credentials();
24734
25049
  await init_detect();
24735
25050
  import { parse as parse2 } from "smol-toml";
24736
- import { existsSync as existsSync55, readFileSync as readFileSync45 } from "fs";
24737
- import { join as join52, sep as sep4 } from "path";
24738
- import { homedir as homedir31, platform as osPlatform2, arch as osArch } from "os";
25051
+ import { existsSync as existsSync56, readFileSync as readFileSync46 } from "fs";
25052
+ import { join as join53, sep as sep4 } from "path";
25053
+ import { homedir as homedir32, platform as osPlatform2, arch as osArch } from "os";
24739
25054
  var BASE_URL2 = process.env.RUNWORK_DOWNLOAD_BASE_URL || "https://runwork.ai";
24740
25055
  var LATEST_JSON_URL2 = `${BASE_URL2}/cli/latest.json`;
24741
25056
  function detectPlatform() {
@@ -24763,10 +25078,10 @@ function buildContext() {
24763
25078
  const credentials = getCredentials();
24764
25079
  const client = credentials ? new ApiClient(credentials) : null;
24765
25080
  let config = null;
24766
- const configPath = join52(process.cwd(), ".runwork.json");
24767
- if (existsSync55(configPath)) {
25081
+ const configPath = join53(process.cwd(), ".runwork.json");
25082
+ if (existsSync56(configPath)) {
24768
25083
  try {
24769
- config = JSON.parse(readFileSync45(configPath, "utf-8"));
25084
+ config = JSON.parse(readFileSync46(configPath, "utf-8"));
24770
25085
  } catch {}
24771
25086
  }
24772
25087
  return { credentials, client, config, cwd: process.cwd() };
@@ -24867,9 +25182,9 @@ async function checkCliArtifactReachable() {
24867
25182
  }
24868
25183
  async function checkCliInstallLocation() {
24869
25184
  const isWindows2 = osPlatform2() === "win32";
24870
- const home = homedir31();
24871
- const canonicalDir = join52(home, ".runwork", "bin");
24872
- const canonicalBinary = isWindows2 ? join52(canonicalDir, "runwork.exe") : join52(canonicalDir, "runwork");
25185
+ const home = homedir32();
25186
+ const canonicalDir = join53(home, ".runwork", "bin");
25187
+ const canonicalBinary = isWindows2 ? join53(canonicalDir, "runwork.exe") : join53(canonicalDir, "runwork");
24873
25188
  const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
24874
25189
  const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
24875
25190
  if (runsFromCanonical) {
@@ -24879,7 +25194,7 @@ async function checkCliInstallLocation() {
24879
25194
  message: `canonical (${canonicalBinary})`
24880
25195
  };
24881
25196
  }
24882
- if (existsSync55(canonicalBinary)) {
25197
+ if (existsSync56(canonicalBinary)) {
24883
25198
  return {
24884
25199
  name: "cli-install-location",
24885
25200
  status: "warn",
@@ -25001,8 +25316,8 @@ async function checkGitCredentialHelper(ctx) {
25001
25316
  };
25002
25317
  }
25003
25318
  async function checkProjectConfig(ctx) {
25004
- const configPath = join52(ctx.cwd, ".runwork.json");
25005
- if (!existsSync55(configPath)) {
25319
+ const configPath = join53(ctx.cwd, ".runwork.json");
25320
+ if (!existsSync56(configPath)) {
25006
25321
  if (!ctx.credentials) {
25007
25322
  return { name: "project-config", status: "skip", message: "no project (not logged in)" };
25008
25323
  }
@@ -25064,7 +25379,7 @@ async function checkGitRemote(ctx) {
25064
25379
  if (!ctx.config) {
25065
25380
  return { name: "git-remote", status: "skip", message: "skipped (no project)" };
25066
25381
  }
25067
- if (!existsSync55(join52(ctx.cwd, ".git"))) {
25382
+ if (!existsSync56(join53(ctx.cwd, ".git"))) {
25068
25383
  return {
25069
25384
  name: "git-remote",
25070
25385
  status: "fail",
@@ -25118,12 +25433,12 @@ async function checkDeployFreshness(ctx) {
25118
25433
  return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
25119
25434
  }
25120
25435
  function loadSetupState5() {
25121
- const projectPath = join52(process.cwd(), ".runwork", "setup.json");
25122
- const userPath = join52(homedir31(), ".runwork", "setup.json");
25436
+ const projectPath = join53(process.cwd(), ".runwork", "setup.json");
25437
+ const userPath = join53(homedir32(), ".runwork", "setup.json");
25123
25438
  for (const p of [projectPath, userPath]) {
25124
- if (existsSync55(p)) {
25439
+ if (existsSync56(p)) {
25125
25440
  try {
25126
- return JSON.parse(readFileSync45(p, "utf-8"));
25441
+ return JSON.parse(readFileSync46(p, "utf-8"));
25127
25442
  } catch {
25128
25443
  continue;
25129
25444
  }
@@ -25138,13 +25453,13 @@ async function checkCodexNetwork() {
25138
25453
  if (!state || !state.configuredAgents.includes("codex")) {
25139
25454
  return { name, status: "skip", message: "Codex not configured for Runwork" };
25140
25455
  }
25141
- const configPath = join52(homedir31(), ".codex", "config.toml");
25142
- if (!existsSync55(configPath)) {
25456
+ const configPath = join53(homedir32(), ".codex", "config.toml");
25457
+ if (!existsSync56(configPath)) {
25143
25458
  return { name, status: "skip", message: "no Codex config found" };
25144
25459
  }
25145
25460
  let parsed;
25146
25461
  try {
25147
- parsed = parse2(readFileSync45(configPath, "utf-8"));
25462
+ parsed = parse2(readFileSync46(configPath, "utf-8"));
25148
25463
  } catch {
25149
25464
  return { name, status: "warn", message: "could not parse ~/.codex/config.toml" };
25150
25465
  }
@@ -25197,19 +25512,19 @@ async function checkCodexDesktopProject() {
25197
25512
  if (!usesCodex) {
25198
25513
  return { name, status: "skip", message: "Codex not configured for Runwork" };
25199
25514
  }
25200
- const statePath2 = join52(homedir31(), ".codex", ".codex-global-state.json");
25201
- if (!existsSync55(statePath2)) {
25515
+ const statePath2 = join53(homedir32(), ".codex", ".codex-global-state.json");
25516
+ if (!existsSync56(statePath2)) {
25202
25517
  return { name, status: "skip", message: "Codex desktop app not detected" };
25203
25518
  }
25204
25519
  let savedRoots = [];
25205
25520
  try {
25206
- const parsed = JSON.parse(readFileSync45(statePath2, "utf-8"));
25521
+ const parsed = JSON.parse(readFileSync46(statePath2, "utf-8"));
25207
25522
  const roots = parsed["electron-saved-workspace-roots"];
25208
25523
  savedRoots = Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
25209
25524
  } catch {
25210
25525
  return { name, status: "warn", message: "could not read Codex desktop state" };
25211
25526
  }
25212
- const runworkDir = join52(homedir31(), ".runwork");
25527
+ const runworkDir = join53(homedir32(), ".runwork");
25213
25528
  if (savedRoots.includes(runworkDir)) {
25214
25529
  return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
25215
25530
  }
@@ -25262,9 +25577,9 @@ async function checkAgentSetup() {
25262
25577
  if (!adapter2 || !adapter2.supportsMcpScope("user"))
25263
25578
  continue;
25264
25579
  const mcpConfigPath = getMcpConfigPath2(slug, "user");
25265
- if (mcpConfigPath && existsSync55(mcpConfigPath)) {
25580
+ if (mcpConfigPath && existsSync56(mcpConfigPath)) {
25266
25581
  try {
25267
- const content = readFileSync45(mcpConfigPath, "utf-8");
25582
+ const content = readFileSync46(mcpConfigPath, "utf-8");
25268
25583
  const missingMcp = state.mcpServers.filter((name) => !content.includes(name));
25269
25584
  if (missingMcp.length > 0) {
25270
25585
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
@@ -25286,15 +25601,22 @@ async function checkAgentSetup() {
25286
25601
  const skillsDir = getSkillsDir(slug, "user");
25287
25602
  if (!skillsDir)
25288
25603
  continue;
25604
+ const adapter2 = getAdapterBySlug(slug);
25605
+ const mcpCoversAppSkills = !!adapter2?.mcpProvidesSkills && state.mcpServers.length > 0;
25606
+ const isCoveredByMcp = (name) => mcpCoversAppSkills && state.skillHashes?.[name]?.source === "app";
25289
25607
  const missingSkills = state.skills.filter((name) => {
25290
- const skillPath = join52(skillsDir, name, "SKILL.md");
25291
- return !existsSync55(skillPath);
25608
+ if (isCoveredByMcp(name))
25609
+ return false;
25610
+ const skillPath = join53(skillsDir, name, "SKILL.md");
25611
+ return !existsSync56(skillPath);
25292
25612
  });
25293
25613
  if (missingSkills.length > 0) {
25294
25614
  details.push(`${missingSkills.length} skill(s) missing from ${slug}`);
25295
25615
  upgrade("warn");
25296
25616
  } else if (state.skills.length > 0) {
25297
- details.push(`${state.skills.length} skill(s) installed`);
25617
+ const mcpCoveredCount = state.skills.filter(isCoveredByMcp).length;
25618
+ const onDiskCount = state.skills.length - mcpCoveredCount;
25619
+ details.push(mcpCoveredCount > 0 ? `${state.skills.length} skill(s) installed (${onDiskCount} on disk, ${mcpCoveredCount} via MCP)` : `${state.skills.length} skill(s) installed`);
25298
25620
  }
25299
25621
  skillsChecked = true;
25300
25622
  break;
@@ -25313,28 +25635,28 @@ async function checkAgentSetup() {
25313
25635
  };
25314
25636
  }
25315
25637
  function getMcpConfigPath2(slug, scope) {
25316
- const home = homedir31();
25638
+ const home = homedir32();
25317
25639
  switch (slug) {
25318
25640
  case "claude-code":
25319
- return scope === "project" ? join52(process.cwd(), ".mcp.json") : join52(home, ".claude", "settings.json");
25641
+ return scope === "project" ? join53(process.cwd(), ".mcp.json") : join53(home, ".claude", "settings.json");
25320
25642
  case "cursor":
25321
- return scope === "project" ? join52(process.cwd(), ".cursor", "mcp.json") : join52(home, ".cursor", "mcp.json");
25643
+ return scope === "project" ? join53(process.cwd(), ".cursor", "mcp.json") : join53(home, ".cursor", "mcp.json");
25322
25644
  case "windsurf":
25323
- return scope === "project" ? join52(process.cwd(), ".windsurf", "mcp.json") : join52(home, ".windsurf", "mcp.json");
25645
+ return scope === "project" ? join53(process.cwd(), ".windsurf", "mcp.json") : join53(home, ".windsurf", "mcp.json");
25324
25646
  case "codex":
25325
25647
  case "codex-app":
25326
- return scope === "user" ? join52(home, ".codex", "config.toml") : null;
25648
+ return scope === "user" ? join53(home, ".codex", "config.toml") : null;
25327
25649
  case "gemini":
25328
- return scope === "user" ? join52(home, ".gemini", "settings.json") : null;
25650
+ return scope === "user" ? join53(home, ".gemini", "settings.json") : null;
25329
25651
  default:
25330
25652
  return null;
25331
25653
  }
25332
25654
  }
25333
25655
  async function checkWorkspacePointers() {
25334
- const userStatePath = join52(homedir31(), ".runwork", "setup.json");
25335
- const state = existsSync55(userStatePath) ? (() => {
25656
+ const userStatePath = join53(homedir32(), ".runwork", "setup.json");
25657
+ const state = existsSync56(userStatePath) ? (() => {
25336
25658
  try {
25337
- return JSON.parse(readFileSync45(userStatePath, "utf-8"));
25659
+ return JSON.parse(readFileSync46(userStatePath, "utf-8"));
25338
25660
  } catch {
25339
25661
  return null;
25340
25662
  }
@@ -25364,15 +25686,15 @@ async function checkWorkspacePointers() {
25364
25686
  };
25365
25687
  }
25366
25688
  function getSkillsDir(slug, scope) {
25367
- const home = homedir31();
25689
+ const home = homedir32();
25368
25690
  switch (slug) {
25369
25691
  case "claude-code":
25370
- return scope === "project" ? join52(process.cwd(), ".claude", "skills") : join52(home, ".claude", "skills");
25692
+ return scope === "project" ? join53(process.cwd(), ".claude", "skills") : join53(home, ".claude", "skills");
25371
25693
  case "codex":
25372
25694
  case "codex-app":
25373
- return scope === "project" ? join52(process.cwd(), ".agents", "skills") : join52(home, ".agents", "skills");
25695
+ return scope === "project" ? join53(process.cwd(), ".agents", "skills") : join53(home, ".agents", "skills");
25374
25696
  case "gemini":
25375
- return scope === "project" ? join52(process.cwd(), ".gemini", "skills") : join52(home, ".gemini", "skills");
25697
+ return scope === "project" ? join53(process.cwd(), ".gemini", "skills") : join53(home, ".gemini", "skills");
25376
25698
  default:
25377
25699
  return null;
25378
25700
  }
@@ -25425,18 +25747,18 @@ async function runAllChecks(options) {
25425
25747
  // src/health/fix.ts
25426
25748
  init_credentials();
25427
25749
  init_remote();
25428
- import { existsSync as existsSync56 } from "fs";
25429
- import { join as join53 } from "path";
25750
+ import { existsSync as existsSync57 } from "fs";
25751
+ import { join as join54 } from "path";
25430
25752
  async function applyDoctorFixes(ctx, failingNames) {
25431
25753
  const failing = new Set(failingNames);
25432
25754
  const outcomes = [];
25433
25755
  if (failing.has("git-credential-helper")) {
25434
25756
  if (ctx.credentials?.baseUrl) {
25435
- await ensureGitCredentialHelper(ctx.credentials.baseUrl);
25757
+ const result = await ensureGitCredentialHelper(ctx.credentials.baseUrl);
25436
25758
  outcomes.push({
25437
25759
  name: "git-credential-helper",
25438
- applied: true,
25439
- message: "registered the runwork git credential helper"
25760
+ applied: result.ok,
25761
+ message: result.ok ? "registered the runwork git credential helper" : result.message
25440
25762
  });
25441
25763
  } else {
25442
25764
  outcomes.push({
@@ -25453,7 +25775,7 @@ async function applyDoctorFixes(ctx, failingNames) {
25453
25775
  applied: false,
25454
25776
  message: "no project config -- run inside an app directory"
25455
25777
  });
25456
- } else if (!existsSync56(join53(ctx.cwd, ".git"))) {
25778
+ } else if (!existsSync57(join54(ctx.cwd, ".git"))) {
25457
25779
  outcomes.push({
25458
25780
  name: "git-remote",
25459
25781
  applied: false,
@@ -25472,10 +25794,10 @@ async function applyDoctorFixes(ctx, failingNames) {
25472
25794
  }
25473
25795
 
25474
25796
  // src/agents/runtime-detection.ts
25475
- import { existsSync as existsSync57, readFileSync as readFileSync46, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
25476
- import { homedir as homedir32 } from "os";
25477
- import { join as join54 } from "path";
25478
- var RUNWORK_SESSIONS_DIR = join54(homedir32(), ".runwork", "sessions");
25797
+ import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
25798
+ import { homedir as homedir33 } from "os";
25799
+ import { join as join55 } from "path";
25800
+ var RUNWORK_SESSIONS_DIR = join55(homedir33(), ".runwork", "sessions");
25479
25801
  function detectCurrentAgent() {
25480
25802
  const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
25481
25803
  if (claudeCodeSessionId) {
@@ -25538,11 +25860,11 @@ function detectCurrentAgent() {
25538
25860
  return null;
25539
25861
  }
25540
25862
  function readHookSessionInfo(sessionId) {
25541
- const path4 = join54(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
25542
- if (!existsSync57(path4))
25863
+ const path4 = join55(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
25864
+ if (!existsSync58(path4))
25543
25865
  return null;
25544
25866
  try {
25545
- const raw = readFileSync46(path4, "utf8");
25867
+ const raw = readFileSync47(path4, "utf8");
25546
25868
  const parsed = JSON.parse(raw);
25547
25869
  return parsed;
25548
25870
  } catch {
@@ -25550,8 +25872,8 @@ function readHookSessionInfo(sessionId) {
25550
25872
  }
25551
25873
  }
25552
25874
  function findClaudeCodeSessionFile(sessionId) {
25553
- const root = join54(homedir32(), ".claude", "projects");
25554
- if (!existsSync57(root))
25875
+ const root = join55(homedir33(), ".claude", "projects");
25876
+ if (!existsSync58(root))
25555
25877
  return null;
25556
25878
  let projectDirs;
25557
25879
  try {
@@ -25560,15 +25882,15 @@ function findClaudeCodeSessionFile(sessionId) {
25560
25882
  return null;
25561
25883
  }
25562
25884
  for (const dir of projectDirs) {
25563
- const candidate = join54(root, dir, `${sessionId}.jsonl`);
25564
- if (existsSync57(candidate))
25885
+ const candidate = join55(root, dir, `${sessionId}.jsonl`);
25886
+ if (existsSync58(candidate))
25565
25887
  return candidate;
25566
25888
  }
25567
25889
  return null;
25568
25890
  }
25569
25891
  function findCodexRolloutFile(threadId) {
25570
- const root = join54(homedir32(), ".codex", "sessions");
25571
- if (!existsSync57(root))
25892
+ const root = join55(homedir33(), ".codex", "sessions");
25893
+ if (!existsSync58(root))
25572
25894
  return null;
25573
25895
  const stack = [root];
25574
25896
  while (stack.length > 0) {
@@ -25580,7 +25902,7 @@ function findCodexRolloutFile(threadId) {
25580
25902
  continue;
25581
25903
  }
25582
25904
  for (const entry of entries) {
25583
- const full = join54(dir, entry);
25905
+ const full = join55(dir, entry);
25584
25906
  let s;
25585
25907
  try {
25586
25908
  s = statSync10(full);
@@ -25597,8 +25919,8 @@ function findCodexRolloutFile(threadId) {
25597
25919
  return null;
25598
25920
  }
25599
25921
  function findNewestClaudeCodeSession() {
25600
- const root = join54(homedir32(), ".claude", "projects");
25601
- if (!existsSync57(root))
25922
+ const root = join55(homedir33(), ".claude", "projects");
25923
+ if (!existsSync58(root))
25602
25924
  return null;
25603
25925
  let projectDirs;
25604
25926
  try {
@@ -25608,7 +25930,7 @@ function findNewestClaudeCodeSession() {
25608
25930
  }
25609
25931
  let best = null;
25610
25932
  for (const dir of projectDirs) {
25611
- const projectPath = join54(root, dir);
25933
+ const projectPath = join55(root, dir);
25612
25934
  let files;
25613
25935
  try {
25614
25936
  files = readdirSync16(projectPath);
@@ -25618,7 +25940,7 @@ function findNewestClaudeCodeSession() {
25618
25940
  for (const file of files) {
25619
25941
  if (!file.endsWith(".jsonl"))
25620
25942
  continue;
25621
- const full = join54(projectPath, file);
25943
+ const full = join55(projectPath, file);
25622
25944
  try {
25623
25945
  const s = statSync10(full);
25624
25946
  if (!best || s.mtimeMs > best.mtime) {
@@ -25636,8 +25958,8 @@ function findNewestClaudeCodeSession() {
25636
25958
  return best ? { sessionId: best.sessionId, path: best.path } : null;
25637
25959
  }
25638
25960
  function findNewestCodexRollout() {
25639
- const root = join54(homedir32(), ".codex", "sessions");
25640
- if (!existsSync57(root))
25961
+ const root = join55(homedir33(), ".codex", "sessions");
25962
+ if (!existsSync58(root))
25641
25963
  return null;
25642
25964
  const stack = [root];
25643
25965
  let best = null;
@@ -25650,7 +25972,7 @@ function findNewestCodexRollout() {
25650
25972
  continue;
25651
25973
  }
25652
25974
  for (const entry of entries) {
25653
- const full = join54(dir, entry);
25975
+ const full = join55(dir, entry);
25654
25976
  let s;
25655
25977
  try {
25656
25978
  s = statSync10(full);
@@ -25882,8 +26204,8 @@ init_store();
25882
26204
  init_client();
25883
26205
  init_resolve();
25884
26206
  import { Command as Command35 } from "commander";
25885
- import { readFileSync as readFileSync47, writeFileSync as writeFileSync31, existsSync as existsSync58, mkdtempSync as mkdtempSync4 } from "fs";
25886
- import { join as join55 } from "path";
26207
+ import { readFileSync as readFileSync48, writeFileSync as writeFileSync32, existsSync as existsSync59, mkdtempSync as mkdtempSync4 } from "fs";
26208
+ import { join as join56 } from "path";
25887
26209
  import { tmpdir as tmpdir4 } from "os";
25888
26210
  import { createHash as createHash6 } from "crypto";
25889
26211
 
@@ -26005,14 +26327,14 @@ function resolveLocalSessionShare(opts, conversation) {
26005
26327
  process.exit(1);
26006
26328
  }
26007
26329
  const title = opts.title ?? conversation.title ?? conversation.project;
26008
- const markdown = renderTranscriptMarkdown(readFileSync47(conversation.transcriptPath, "utf8"), family, title);
26330
+ const markdown = renderTranscriptMarkdown(readFileSync48(conversation.transcriptPath, "utf8"), family, title);
26009
26331
  if (!markdown) {
26010
26332
  console.error("Error: this conversation has no shareable content.");
26011
26333
  process.exit(1);
26012
26334
  }
26013
- const tempDir = mkdtempSync4(join55(tmpdir4(), "runwork-share-"));
26014
- const transcriptFile = join55(tempDir, "transcript.md");
26015
- writeFileSync31(transcriptFile, markdown);
26335
+ const tempDir = mkdtempSync4(join56(tmpdir4(), "runwork-share-"));
26336
+ const transcriptFile = join56(tempDir, "transcript.md");
26337
+ writeFileSync32(transcriptFile, markdown);
26016
26338
  opts.transcriptFile = transcriptFile;
26017
26339
  opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
26018
26340
  opts.sourceAgent = opts.sourceAgent ?? conversation.agentSlug;
@@ -26049,7 +26371,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26049
26371
  console.error("Error: --transcript-file is required. Pass the path to the LLM-emitted markdown transcript.");
26050
26372
  process.exit(1);
26051
26373
  }
26052
- if (!existsSync58(opts.transcriptFile)) {
26374
+ if (!existsSync59(opts.transcriptFile)) {
26053
26375
  console.error(`Error: transcript file does not exist: ${opts.transcriptFile}`);
26054
26376
  process.exit(1);
26055
26377
  }
@@ -26070,7 +26392,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26070
26392
  const credentials = requireAuth();
26071
26393
  const client = new ApiClient(credentials);
26072
26394
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
26073
- const transcriptContent = readFileSync47(opts.transcriptFile, "utf8");
26395
+ const transcriptContent = readFileSync48(opts.transcriptFile, "utf8");
26074
26396
  const bundles = [
26075
26397
  {
26076
26398
  format: "transcript",
@@ -26083,19 +26405,19 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26083
26405
  const sourceAgent = opts.sourceAgent ?? detected?.slug ?? "generic";
26084
26406
  let nativeFilePath = null;
26085
26407
  if (opts.nativeFile) {
26086
- if (!existsSync58(opts.nativeFile)) {
26408
+ if (!existsSync59(opts.nativeFile)) {
26087
26409
  console.error(`Error: --native-file path does not exist: ${opts.nativeFile}`);
26088
26410
  process.exit(1);
26089
26411
  }
26090
26412
  nativeFilePath = opts.nativeFile;
26091
- } else if (detected?.sessionFilePath && existsSync58(detected.sessionFilePath)) {
26413
+ } else if (detected?.sessionFilePath && existsSync59(detected.sessionFilePath)) {
26092
26414
  nativeFilePath = detected.sessionFilePath;
26093
26415
  }
26094
26416
  if (nativeFilePath) {
26095
26417
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
26096
26418
  if (nativeFormat) {
26097
26419
  try {
26098
- const content = readFileSync47(nativeFilePath, "utf8");
26420
+ const content = readFileSync48(nativeFilePath, "utf8");
26099
26421
  bundles.push({
26100
26422
  format: nativeFormat,
26101
26423
  content,
@@ -26111,7 +26433,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26111
26433
  let metadata = {};
26112
26434
  if (opts.metadataFile) {
26113
26435
  try {
26114
- metadata = JSON.parse(readFileSync47(opts.metadataFile, "utf8"));
26436
+ metadata = JSON.parse(readFileSync48(opts.metadataFile, "utf8"));
26115
26437
  } catch (err) {
26116
26438
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
26117
26439
  process.exit(1);
@@ -26220,9 +26542,9 @@ init_client();
26220
26542
  init_resolve();
26221
26543
  init_registry_data();
26222
26544
  import { Command as Command38 } from "commander";
26223
- import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync28, realpathSync } from "fs";
26224
- import { homedir as homedir33 } from "os";
26225
- import { join as join56 } from "path";
26545
+ import { writeFileSync as writeFileSync33, mkdirSync as mkdirSync29, realpathSync } from "fs";
26546
+ import { homedir as homedir34 } from "os";
26547
+ import { join as join57 } from "path";
26226
26548
  import { spawn as spawn5 } from "child_process";
26227
26549
  init_registry();
26228
26550
  init_which();
@@ -26261,10 +26583,10 @@ function extractCodexUuid(rolloutContent) {
26261
26583
  }
26262
26584
  function placeClaudeJsonl(uuid, content, recipientCwd) {
26263
26585
  const encoded = encodeClaudeCodeCwd(recipientCwd);
26264
- const projectDir = join56(homedir33(), ".claude", "projects", encoded);
26265
- mkdirSync28(projectDir, { recursive: true });
26266
- const placedAt = join56(projectDir, `${uuid}.jsonl`);
26267
- writeFileSync32(placedAt, content);
26586
+ const projectDir = join57(homedir34(), ".claude", "projects", encoded);
26587
+ mkdirSync29(projectDir, { recursive: true });
26588
+ const placedAt = join57(projectDir, `${uuid}.jsonl`);
26589
+ writeFileSync33(placedAt, content);
26268
26590
  return { placedAt, runFromCwd: recipientCwd };
26269
26591
  }
26270
26592
  function placeCodexRollout(uuid, content) {
@@ -26272,11 +26594,11 @@ function placeCodexRollout(uuid, content) {
26272
26594
  const yyyy = String(now.getUTCFullYear());
26273
26595
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
26274
26596
  const dd = String(now.getUTCDate()).padStart(2, "0");
26275
- const dir = join56(homedir33(), ".codex", "sessions", yyyy, mm, dd);
26276
- mkdirSync28(dir, { recursive: true });
26597
+ const dir = join57(homedir34(), ".codex", "sessions", yyyy, mm, dd);
26598
+ mkdirSync29(dir, { recursive: true });
26277
26599
  const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
26278
- const placedAt = join56(dir, `rollout-${ts}-${uuid}.jsonl`);
26279
- writeFileSync32(placedAt, content);
26600
+ const placedAt = join57(dir, `rollout-${ts}-${uuid}.jsonl`);
26601
+ writeFileSync33(placedAt, content);
26280
26602
  return { placedAt };
26281
26603
  }
26282
26604
  function pickTargetAgent(opts, sourceAgent) {