runwork 0.25.1 → 0.25.3

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.
Files changed (2) hide show
  1. package/dist/index.js +683 -233
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -61,7 +61,42 @@ import {
61
61
  createInflate,
62
62
  createInflateRaw
63
63
  } from "node:zlib";
64
- function pickTransport() {
64
+ function firstEnv(names) {
65
+ for (const variable of names) {
66
+ const value = process.env[variable];
67
+ if (value && value.trim() !== "")
68
+ return { variable, value: value.trim() };
69
+ }
70
+ return null;
71
+ }
72
+ function isProxyExempt(hostname) {
73
+ const raw = process.env.NO_PROXY ?? process.env.no_proxy ?? "";
74
+ const host = hostname.toLowerCase();
75
+ for (const entry of raw.split(",")) {
76
+ const rule = entry.trim().toLowerCase().replace(/:\d+$/, "");
77
+ if (!rule)
78
+ continue;
79
+ if (rule === "*")
80
+ return true;
81
+ const bare = rule.startsWith(".") ? rule.slice(1) : rule;
82
+ if (host === bare || host.endsWith(`.${bare}`))
83
+ return true;
84
+ }
85
+ return false;
86
+ }
87
+ function proxyForUrl(url) {
88
+ let parsed;
89
+ try {
90
+ parsed = new URL2(url);
91
+ } catch {
92
+ return null;
93
+ }
94
+ if (isProxyExempt(parsed.hostname))
95
+ return null;
96
+ const schemeVars = parsed.protocol === "http:" ? ["HTTP_PROXY", "http_proxy"] : ["HTTPS_PROXY", "https_proxy"];
97
+ return firstEnv([...schemeVars, "ALL_PROXY", "all_proxy"]);
98
+ }
99
+ function pickTransport(url) {
65
100
  if (transportOverride === "curl")
66
101
  return "curl";
67
102
  if (transportOverride === "node")
@@ -69,10 +104,14 @@ function pickTransport() {
69
104
  const env = (process.env.RUNWORK_HTTP_TRANSPORT || "").toLowerCase().trim();
70
105
  if (env === "curl")
71
106
  return "curl";
107
+ if (env === "node")
108
+ return "node";
109
+ if (url && proxyForUrl(url))
110
+ return "curl";
72
111
  return "node";
73
112
  }
74
113
  async function httpFetch(url, init = {}) {
75
- const transport = pickTransport();
114
+ const transport = pickTransport(url);
76
115
  if (transport === "curl")
77
116
  return curlFetch(url, init);
78
117
  return doRequest(url, init, 0);
@@ -1020,26 +1059,36 @@ function buildHelperValue(execPath, scriptPath) {
1020
1059
  return `!"${normalised}" git-credential-helper`;
1021
1060
  }
1022
1061
  async function configureGitCredentials(remoteUrl) {
1023
- const origin = new URL(remoteUrl).origin;
1062
+ let origin;
1063
+ try {
1064
+ origin = new URL(remoteUrl).origin;
1065
+ } catch {
1066
+ const message = `"${remoteUrl}" is not a valid URL.`;
1067
+ console.warn(`Note: ${message}`);
1068
+ return { ok: false, reason: "invalid-url", message };
1069
+ }
1024
1070
  const key = `credential.${origin}.helper`;
1025
1071
  const helperValue = buildHelperValue(process.execPath, process.argv[1]);
1026
1072
  try {
1027
1073
  try {
1028
1074
  execFileSync("git", ["config", "--global", "--unset-all", key], { stdio: "pipe" });
1029
- } catch (unsetErr) {
1030
- if (unsetErr?.code === "ENOENT")
1031
- throw unsetErr;
1032
- }
1075
+ } catch {}
1033
1076
  execFileSync("git", ["config", "--global", "--add", key, ""], { stdio: "pipe" });
1034
1077
  execFileSync("git", ["config", "--global", "--add", key, helperValue], { stdio: "pipe" });
1078
+ return { ok: true };
1035
1079
  } catch (err) {
1036
1080
  const code = err?.code;
1037
1081
  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;
1082
+ const message2 = "git is not installed. Install git before running `runwork init`, `clone`, `dev`, or `deploy`.";
1083
+ console.warn(`Note: ${message2}`);
1084
+ return { ok: false, reason: "missing", message: message2 };
1041
1085
  }
1042
- throw err;
1086
+ const detail = err instanceof Error ? err.message.split(`
1087
+ `)[0] : String(err);
1088
+ const xcodeHint = process.platform === "darwin" ? " On macOS this usually means the Xcode Command Line Tools are missing; run `xcode-select --install`." : "";
1089
+ const message = `git is installed but could not be run (${detail}).${xcodeHint}`;
1090
+ console.warn(`Note: ${message}`);
1091
+ return { ok: false, reason: "unusable", message };
1043
1092
  }
1044
1093
  }
1045
1094
  function lookupCredentialHelper(origin) {
@@ -1097,13 +1146,13 @@ async function ensureGitCredentialHelper(baseUrl) {
1097
1146
  try {
1098
1147
  origin = new URL(baseUrl).origin;
1099
1148
  } catch {
1100
- return;
1149
+ return { ok: false, reason: "invalid-url", message: `"${baseUrl}" is not a valid URL.` };
1101
1150
  }
1102
1151
  const lookup = lookupCredentialHelper(origin);
1103
1152
  if (lookup.status === "registered" && helperBinaryStatus(lookup.value).ok && lookup.hasReset) {
1104
- return;
1153
+ return { ok: true };
1105
1154
  }
1106
- await configureGitCredentials(baseUrl);
1155
+ return configureGitCredentials(baseUrl);
1107
1156
  }
1108
1157
  async function removeGitCredentials(baseUrl) {
1109
1158
  const origin = new URL(baseUrl).origin;
@@ -7973,7 +8022,7 @@ function createKeyboardListener() {
7973
8022
  }
7974
8023
 
7975
8024
  // src/generated/version.ts
7976
- var VERSION = "0.25.1";
8025
+ var VERSION = "0.25.3";
7977
8026
 
7978
8027
  // src/commands/dev.ts
7979
8028
  var exports_dev = {};
@@ -8902,6 +8951,32 @@ var init_dev = __esm(() => {
8902
8951
  devCommand.addCommand(devAttachCommand);
8903
8952
  });
8904
8953
 
8954
+ // src/agents/detection-probes.ts
8955
+ function powershellQuote(value) {
8956
+ return `'${value.replace(/'/g, "''")}'`;
8957
+ }
8958
+ function isValidBundleId(id) {
8959
+ return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(id);
8960
+ }
8961
+ function macosBundleIdProbeScript(id) {
8962
+ return `p=$(mdfind "kMDItemCFBundleIdentifier == '${id}'" 2>/dev/null | head -1); ` + `if [ -n "$p" ]; then exit 0; fi; ` + `for a in /Applications/*.app "$HOME"/Applications/*.app; do ` + `[ -e "$a" ] || continue; ` + `if [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$a/Contents/Info.plist" 2>/dev/null)" = "${id}" ]; then exit 0; fi; ` + `done; exit 1`;
8963
+ }
8964
+ function appxPackageProbeScript(pkg) {
8965
+ return `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
8966
+ }
8967
+ function startAppProbeScript(pattern) {
8968
+ const p = powershellQuote(pattern);
8969
+ return `$a = Get-StartApps -ErrorAction SilentlyContinue | Where-Object { $_.Name -like ${p} -or $_.AppID -like ${p} } | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
8970
+ }
8971
+ function isCommandNotFoundExit(code) {
8972
+ return typeof code === "number" && COMMAND_NOT_FOUND_EXIT_CODES.includes(code);
8973
+ }
8974
+ var PATH_REFRESH_FAILED_MARKER = "__runwork_path_refresh_failed__", WINDOWS_PATH_REFRESH, COMMAND_NOT_FOUND_EXIT_CODES;
8975
+ var init_detection_probes = __esm(() => {
8976
+ WINDOWS_PATH_REFRESH = "try { $env:Path = [Environment]::GetEnvironmentVariable('Path','Machine') + ';' + " + "[Environment]::GetEnvironmentVariable('Path','User') + ';' + $env:Path } " + `catch { Write-Output '${PATH_REFRESH_FAILED_MARKER}' }; `;
8977
+ COMMAND_NOT_FOUND_EXIT_CODES = [127, 9009];
8978
+ });
8979
+
8905
8980
  // src/utils/which.ts
8906
8981
  import { platform } from "os";
8907
8982
  import { isAbsolute } from "path";
@@ -8922,11 +8997,25 @@ function whichAllLines(name) {
8922
8997
  return [];
8923
8998
  }
8924
8999
  }
8925
- function isBinaryRunnable(binary) {
9000
+ function classifyProbeError(err) {
9001
+ const e = err;
9002
+ if (!e || typeof e !== "object")
9003
+ return "spawn-error";
9004
+ if (e.signal || e.code === "ETIMEDOUT")
9005
+ return "timeout";
9006
+ if (e.code === "ENOENT")
9007
+ return "not-found";
9008
+ if (isCommandNotFoundExit(e.status))
9009
+ return "not-found";
9010
+ if (typeof e.status === "number")
9011
+ return "exit-nonzero";
9012
+ return "spawn-error";
9013
+ }
9014
+ function probeBinaryRunnable(binary) {
8926
9015
  const cached = runnableCache.get(binary);
8927
9016
  if (cached !== undefined)
8928
9017
  return cached;
8929
- let ok = false;
9018
+ let probe;
8930
9019
  try {
8931
9020
  const spec = toSpawnSpec(binary, ["--version"]);
8932
9021
  execFileSync(spec.command, spec.args, {
@@ -8934,12 +9023,16 @@ function isBinaryRunnable(binary) {
8934
9023
  timeout: RUNNABLE_PROBE_TIMEOUT_MS,
8935
9024
  ...spec.windowsVerbatimArguments ? { windowsVerbatimArguments: true } : {}
8936
9025
  });
8937
- ok = true;
8938
- } catch {
8939
- ok = false;
9026
+ probe = { runnable: true, reason: "ran" };
9027
+ } catch (err) {
9028
+ probe = { runnable: false, reason: classifyProbeError(err) };
8940
9029
  }
8941
- runnableCache.set(binary, ok);
8942
- return ok;
9030
+ if (probe.reason !== "timeout")
9031
+ runnableCache.set(binary, probe);
9032
+ return probe;
9033
+ }
9034
+ function isBinaryRunnable(binary) {
9035
+ return probeBinaryRunnable(binary).runnable;
8943
9036
  }
8944
9037
  function toSpawnSpec(binary, args) {
8945
9038
  if (isAbsolute(binary)) {
@@ -8987,6 +9080,7 @@ function isAppRunning(appName) {
8987
9080
  var RUNNABLE_PROBE_TIMEOUT_MS = 1e4, runnableCache;
8988
9081
  var init_which = __esm(() => {
8989
9082
  init_subprocess();
9083
+ init_detection_probes();
8990
9084
  runnableCache = new Map;
8991
9085
  });
8992
9086
 
@@ -9105,7 +9199,10 @@ var init_resolve = __esm(() => {
9105
9199
 
9106
9200
  // ../../shared/skill/skill-canonical.ts
9107
9201
  function toSkillSlug(value) {
9108
- return value.trim().replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9202
+ return transliterateLatin(value.trim()).replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
9203
+ }
9204
+ function transliterateLatin(value) {
9205
+ return value.replace(/[ıßøØłŁđĐæÆœŒþÞðÐ]/g, (ch) => NON_DECOMPOSABLE_LATIN[ch] ?? ch).normalize("NFD").replace(/[\u0300-\u036f]/g, "");
9109
9206
  }
9110
9207
  function isStructuredYamlValue(value) {
9111
9208
  if (value.includes(`
@@ -9150,11 +9247,13 @@ function parseSkillMd(content) {
9150
9247
  const lines = content.split(`
9151
9248
  `);
9152
9249
  if (lines[0]?.trim() !== "---") {
9153
- const rawName = lines[0]?.replace(/^#\s*/, "").trim() || "Untitled Skill";
9250
+ const firstLine = lines[0]?.trim() ?? "";
9251
+ const isHeading = /^#+\s/.test(firstLine);
9252
+ const rawName = firstLine.replace(/^#+\s*/, "").trim() || "Untitled Skill";
9154
9253
  return {
9155
9254
  frontmatter: {
9156
9255
  name: toSkillSlug(rawName) || "untitled-skill",
9157
- description: ""
9256
+ description: isHeading ? rawName : ""
9158
9257
  },
9159
9258
  orderedKeys: [],
9160
9259
  body: content,
@@ -9241,6 +9340,27 @@ function buildSkillMd(parts) {
9241
9340
  return lines.join(`
9242
9341
  `);
9243
9342
  }
9343
+ var NON_DECOMPOSABLE_LATIN;
9344
+ var init_skill_canonical = __esm(() => {
9345
+ NON_DECOMPOSABLE_LATIN = {
9346
+ "ı": "i",
9347
+ "ß": "ss",
9348
+ "ø": "o",
9349
+ "Ø": "O",
9350
+ "ł": "l",
9351
+ "Ł": "L",
9352
+ "đ": "d",
9353
+ "Đ": "D",
9354
+ "æ": "ae",
9355
+ "Æ": "AE",
9356
+ "œ": "oe",
9357
+ "Œ": "OE",
9358
+ "þ": "th",
9359
+ "Þ": "TH",
9360
+ "ð": "d",
9361
+ "Ð": "D"
9362
+ };
9363
+ });
9244
9364
 
9245
9365
  // src/agents/registry-data.ts
9246
9366
  function chatgptConnectorSteps(confirmLead) {
@@ -9784,7 +9904,7 @@ var init_registry_data = __esm(() => {
9784
9904
  name: "GitHub Copilot CLI",
9785
9905
  description: "GitHub Copilot in the terminal",
9786
9906
  category: "cli",
9787
- detection: { method: "binary", target: "gh" },
9907
+ detection: { method: "binary", target: "copilot" },
9788
9908
  logo: "vscode",
9789
9909
  skillsPaths: { global: ".copilot/skills", project: ".github/skills" },
9790
9910
  mcpConfigPath: ".copilot/mcp-config.json",
@@ -9805,7 +9925,7 @@ var init_registry_data = __esm(() => {
9805
9925
  { slug: "droid", name: "Droid", aliases: ["Droid (Factory AI)"], description: "Factory AI's coding agent", category: "cli", detection: { method: "binary", target: "droid" }, skillsPaths: { global: ".factory/skills", project: ".factory/skills" } },
9806
9926
  { slug: "firebender", name: "Firebender", description: "AI coding agent", category: "cli", detection: { method: "binary", target: "firebender" }, skillsPaths: { global: ".firebender/skills", project: ".firebender/skills" } },
9807
9927
  { slug: "goose", name: "Goose", description: "AI coding agent by Block", category: "cli", detection: { method: "binary", target: "goose" }, skillsPaths: { global: ".config/goose/skills", project: ".goose/skills" } },
9808
- { slug: "hermes", name: "Hermes", aliases: ["hermes-agent"], description: "AI coding agent", category: "cli", detection: { method: "binary", target: "hermes" }, skillsPaths: { global: ".hermes/skills", project: ".hermes/skills" } },
9928
+ { slug: "hermes", name: "Hermes", aliases: ["hermes-agent"], description: "AI coding agent", category: "cli", detection: { method: "binary", target: "hermes" }, launch: { cli: "hermes" }, skillsPaths: { global: ".hermes/skills", project: ".hermes/skills" } },
9809
9929
  { slug: "iflow", name: "iFlow CLI", aliases: ["iflow-cli"], description: "AI coding agent", category: "cli", detection: { method: "binary", target: "iflow" }, skillsPaths: { global: ".iflow/skills", project: ".iflow/skills" } },
9810
9930
  { slug: "junie", name: "Junie", description: "JetBrains AI coding agent", category: "ide", detection: { method: "binary", target: "junie" }, skillsPaths: { global: ".junie/skills", project: ".junie/skills" } },
9811
9931
  { slug: "kilocode", name: "Kilo Code", aliases: ["kilo"], description: "AI coding agent", category: "extension", detection: { method: "binary", target: "kilocode" }, skillsPaths: { global: ".kilocode/skills", project: ".kilocode/skills" } },
@@ -10879,6 +10999,7 @@ function buildSkillMd2(skill) {
10879
10999
  }
10880
11000
  var RUNWORK_MCP_PREFIX = "Runwork: ", RUNWORK_MCP_PREFIX_LEGACY = "runwork-", RUNWORK_WORKSPACE_MCP_NAME = "Runwork", RUNWORK_PLUGIN_MARKETPLACE = "runwork", toSlug;
10881
11001
  var init_types = __esm(() => {
11002
+ init_skill_canonical();
10882
11003
  toSlug = toSkillSlug;
10883
11004
  });
10884
11005
 
@@ -10997,11 +11118,33 @@ function readJsonConfig(filePath) {
10997
11118
  return {};
10998
11119
  try {
10999
11120
  return JSON.parse(readFileSync23(filePath, "utf-8"));
11000
- } catch {
11121
+ } catch (err) {
11122
+ console.warn(` [config] ${filePath} is not valid JSON: ${err instanceof Error ? err.message : err}`);
11001
11123
  return {};
11002
11124
  }
11003
11125
  }
11126
+ function existingContentIsUnparseable(filePath) {
11127
+ if (!existsSync28(filePath))
11128
+ return { bad: false };
11129
+ let raw;
11130
+ try {
11131
+ raw = readFileSync23(filePath, "utf-8");
11132
+ } catch {
11133
+ return { bad: false };
11134
+ }
11135
+ if (raw.trim() === "")
11136
+ return { bad: false };
11137
+ try {
11138
+ JSON.parse(raw);
11139
+ return { bad: false };
11140
+ } catch (cause) {
11141
+ return { bad: true, cause };
11142
+ }
11143
+ }
11004
11144
  function writeJsonConfig(filePath, config) {
11145
+ const { bad, cause } = existingContentIsUnparseable(filePath);
11146
+ if (bad)
11147
+ throw new ConfigParseError(filePath, cause);
11005
11148
  mkdirSync14(dirname7(filePath), { recursive: true });
11006
11149
  writeFileSync14(filePath, JSON.stringify(config, null, 2) + `
11007
11150
  `);
@@ -11045,9 +11188,20 @@ function mergeJsonMcpServers(filePath, servers, topKey) {
11045
11188
  writeJsonConfig(filePath, config);
11046
11189
  return true;
11047
11190
  }
11191
+ var ConfigParseError;
11048
11192
  var init_json_config = __esm(() => {
11049
11193
  init_types();
11050
11194
  init_hash();
11195
+ ConfigParseError = class ConfigParseError extends Error {
11196
+ filePath;
11197
+ cause;
11198
+ constructor(filePath, cause) {
11199
+ super(`${filePath} is not valid JSON, so Runwork left it untouched. ` + `Fix the file (or move it aside) and run the command again.`);
11200
+ this.filePath = filePath;
11201
+ this.cause = cause;
11202
+ this.name = "ConfigParseError";
11203
+ }
11204
+ };
11051
11205
  });
11052
11206
 
11053
11207
  // src/agents/utils/skill-removal.ts
@@ -11602,24 +11756,6 @@ function vlog(...args) {
11602
11756
  }
11603
11757
  var verbose = false;
11604
11758
 
11605
- // src/agents/detection-probes.ts
11606
- function powershellQuote(value) {
11607
- return `'${value.replace(/'/g, "''")}'`;
11608
- }
11609
- function isValidBundleId(id) {
11610
- return /^[A-Za-z0-9][A-Za-z0-9.-]*$/.test(id);
11611
- }
11612
- function macosBundleIdProbeScript(id) {
11613
- return `p=$(mdfind "kMDItemCFBundleIdentifier == '${id}'" 2>/dev/null | head -1); ` + `if [ -n "$p" ]; then exit 0; fi; ` + `for a in /Applications/*.app "$HOME"/Applications/*.app; do ` + `[ -e "$a" ] || continue; ` + `if [ "$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIdentifier' "$a/Contents/Info.plist" 2>/dev/null)" = "${id}" ]; then exit 0; fi; ` + `done; exit 1`;
11614
- }
11615
- function appxPackageProbeScript(pkg) {
11616
- return `$p = Get-AppxPackage -Name ${powershellQuote(pkg)} -ErrorAction SilentlyContinue; if ($null -ne $p) { exit 0 } exit 1`;
11617
- }
11618
- function startAppProbeScript(pattern) {
11619
- const p = powershellQuote(pattern);
11620
- return `$a = Get-StartApps -ErrorAction SilentlyContinue | Where-Object { $_.Name -like ${p} -or $_.AppID -like ${p} } | Select-Object -First 1; if ($null -ne $a) { exit 0 } exit 1`;
11621
- }
11622
-
11623
11759
  // src/agents/detection.ts
11624
11760
  import { execFile } from "child_process";
11625
11761
  import { existsSync as existsSync31 } from "fs";
@@ -11725,6 +11861,7 @@ var init_detection = __esm(() => {
11725
11861
  init_which();
11726
11862
  init_registry_data();
11727
11863
  init_registry();
11864
+ init_detection_probes();
11728
11865
  NOT_DETECTED = { detected: false };
11729
11866
  });
11730
11867
 
@@ -11949,8 +12086,7 @@ ${instructions}`;
11949
12086
  }
11950
12087
  if (!hadFile && !config.modelPreference && !config.permissionRules)
11951
12088
  return;
11952
- mkdirSync16(join26(settingsPath, ".."), { recursive: true });
11953
- writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
12089
+ writeJsonConfig(settingsPath, settings);
11954
12090
  }
11955
12091
  async readManagedBlock(_scope) {
11956
12092
  return;
@@ -11997,7 +12133,7 @@ ${instructions}`;
11997
12133
  delete settings.enabledPlugins[key];
11998
12134
  }
11999
12135
  }
12000
- writeFileSync16(settingsPath, JSON.stringify(settings, null, 2));
12136
+ writeJsonConfig(settingsPath, settings);
12001
12137
  } catch {}
12002
12138
  }
12003
12139
  const pluginDir = this.getPluginDir();
@@ -12889,7 +13025,7 @@ var init_claude_desktop = __esm(() => {
12889
13025
  prefs.ccdScheduledTasksEnabled = false;
12890
13026
  }
12891
13027
  }
12892
- writeFileSync18(configPath, JSON.stringify(desktopConfig, null, 2));
13028
+ writeJsonConfig(configPath, desktopConfig);
12893
13029
  }
12894
13030
  async cleanup(_scope, _manifest) {
12895
13031
  removeRunworkMcpServers(getMcpConfigPath(), "mcpServers");
@@ -14431,7 +14567,7 @@ var init_cline = __esm(() => {
14431
14567
  }
14432
14568
  }
14433
14569
  mkdirSync22(join32(globalStatePath, ".."), { recursive: true });
14434
- writeFileSync22(globalStatePath, JSON.stringify(state, null, 2));
14570
+ writeJsonConfig(globalStatePath, state);
14435
14571
  }
14436
14572
  async removeSkills(skillFilenames, scope) {
14437
14573
  if (scope !== "project")
@@ -14544,7 +14680,7 @@ var init_gemini = __esm(() => {
14544
14680
  settings.tools.exclude = config.permissionRules.deny;
14545
14681
  }
14546
14682
  mkdirSync23(join33(settingsPath, ".."), { recursive: true });
14547
- writeFileSync23(settingsPath, JSON.stringify(settings, null, 2));
14683
+ writeJsonConfig(settingsPath, settings);
14548
14684
  }
14549
14685
  async readUsageStats(lastSyncAt) {
14550
14686
  try {
@@ -14738,7 +14874,7 @@ var init_gemini = __esm(() => {
14738
14874
  });
14739
14875
 
14740
14876
  // src/agents/generic-adapter.ts
14741
- import { existsSync as existsSync40, mkdirSync as mkdirSync24, rmSync as rmSync12, writeFileSync as writeFileSync24 } from "fs";
14877
+ import { mkdirSync as mkdirSync24, writeFileSync as writeFileSync24 } from "fs";
14742
14878
  import { join as join34 } from "path";
14743
14879
  import { homedir as homedir16 } from "os";
14744
14880
  var GenericAgentAdapter;
@@ -14749,6 +14885,7 @@ var init_generic_adapter = __esm(() => {
14749
14885
  init_instruction_hint();
14750
14886
  init_registry();
14751
14887
  init_detection();
14888
+ init_trash();
14752
14889
  GenericAgentAdapter = class GenericAgentAdapter extends RegistryDetectedAdapter {
14753
14890
  name;
14754
14891
  slug;
@@ -14795,12 +14932,7 @@ var init_generic_adapter = __esm(() => {
14795
14932
  return 0;
14796
14933
  for (const skill of skills) {
14797
14934
  if (skill.name !== skill.filename) {
14798
- const oldDir = join34(baseDir, skill.name);
14799
- if (existsSync40(oldDir)) {
14800
- try {
14801
- rmSync12(oldDir, { recursive: true, force: true });
14802
- } catch {}
14803
- }
14935
+ moveToTrash(join34(baseDir, skill.name), `skill renamed to ${skill.filename}`);
14804
14936
  }
14805
14937
  const skillDir = join34(baseDir, skill.filename);
14806
14938
  mkdirSync24(skillDir, { recursive: true });
@@ -15557,10 +15689,12 @@ function selectAnalyst(requestedSlug) {
15557
15689
  const resolved = resolve3(requested);
15558
15690
  if (!resolved)
15559
15691
  return { candidates: [], broken };
15560
- if (isBinaryRunnable(resolved.command)) {
15692
+ const probe = probeBinaryRunnable(resolved.command);
15693
+ if (probe.runnable) {
15561
15694
  return { chosen: resolved, candidates: [resolved], broken };
15562
15695
  }
15563
- broken.push(requested.binary);
15696
+ if (probe.reason !== "timeout")
15697
+ broken.push(requested.binary);
15564
15698
  return { candidates: [], broken };
15565
15699
  }
15566
15700
  const candidates = [];
@@ -15568,9 +15702,10 @@ function selectAnalyst(requestedSlug) {
15568
15702
  const resolved = resolve3(a);
15569
15703
  if (!resolved)
15570
15704
  continue;
15571
- if (isBinaryRunnable(resolved.command))
15705
+ const probe = probeBinaryRunnable(resolved.command);
15706
+ if (probe.runnable)
15572
15707
  candidates.push(resolved);
15573
- else
15708
+ else if (probe.reason !== "timeout")
15574
15709
  broken.push(a.binary);
15575
15710
  }
15576
15711
  return { chosen: candidates[0], candidates, broken };
@@ -19825,6 +19960,7 @@ var infoCommand = new Command12("info").description("Show app context, registrie
19825
19960
  init_store();
19826
19961
  init_client();
19827
19962
  init_resolve();
19963
+ init_skill_canonical();
19828
19964
  import { Command as Command13 } from "commander";
19829
19965
  import { readFileSync as readFileSync22, existsSync as existsSync25 } from "fs";
19830
19966
  function truncate(text2, max) {
@@ -19899,7 +20035,7 @@ function buildSkillPushPayload(fileContent, nameArg) {
19899
20035
  const name = toSkillSlug(nameArg || "") || docName;
19900
20036
  if (!name)
19901
20037
  return null;
19902
- const description = parsed.hadFrontmatter ? parsed.frontmatter.description : "";
20038
+ const description = parsed.frontmatter.description;
19903
20039
  const extra = {};
19904
20040
  for (const key of parsed.orderedKeys) {
19905
20041
  if (key === "name" || key === "description")
@@ -22571,8 +22707,8 @@ init_resolve();
22571
22707
  init_prompt();
22572
22708
  await init_detect();
22573
22709
  import { Command as Command27 } from "commander";
22574
- import { join as join48 } from "path";
22575
- import { homedir as homedir27 } from "os";
22710
+ import { join as join49 } from "path";
22711
+ import { homedir as homedir28 } from "os";
22576
22712
 
22577
22713
  // src/commands/sync.ts
22578
22714
  init_store();
@@ -22583,9 +22719,9 @@ await __promiseAll([
22583
22719
  init_codex()
22584
22720
  ]);
22585
22721
  import { Command as Command26 } from "commander";
22586
- import { readFileSync as readFileSync40, existsSync as existsSync51 } from "fs";
22587
- import { join as join47 } from "path";
22588
- import { homedir as homedir26 } from "os";
22722
+ import { readFileSync as readFileSync41, existsSync as existsSync52 } from "fs";
22723
+ import { join as join48 } from "path";
22724
+ import { homedir as homedir27 } from "os";
22589
22725
 
22590
22726
  // src/commands/mcp-entries.ts
22591
22727
  init_types();
@@ -23444,6 +23580,91 @@ function sameStringSet(a, b) {
23444
23580
  return true;
23445
23581
  }
23446
23582
 
23583
+ // src/utils/sync-lock.ts
23584
+ import { existsSync as existsSync51, mkdirSync as mkdirSync28, readFileSync as readFileSync40, unlinkSync as unlinkSync8, writeFileSync as writeFileSync30 } from "fs";
23585
+ import { join as join47 } from "path";
23586
+ import { homedir as homedir26 } from "os";
23587
+ var LOCK_PATH = join47(homedir26(), ".runwork", "sync.lock");
23588
+ var STALE_LOCK_MS = 5 * 60 * 1000;
23589
+ var DEFAULT_WAIT_MS = 30000;
23590
+ var exitHandlerRegistered = false;
23591
+ function ensureExitHandler() {
23592
+ if (exitHandlerRegistered)
23593
+ return;
23594
+ exitHandlerRegistered = true;
23595
+ process.once("exit", releaseSyncLock);
23596
+ }
23597
+ function isProcessAlive(pid) {
23598
+ try {
23599
+ process.kill(pid, 0);
23600
+ return true;
23601
+ } catch (err) {
23602
+ return err?.code === "EPERM";
23603
+ }
23604
+ }
23605
+ function readLock() {
23606
+ try {
23607
+ return JSON.parse(readFileSync40(LOCK_PATH, "utf-8"));
23608
+ } catch {
23609
+ return null;
23610
+ }
23611
+ }
23612
+ function writeLockExclusive() {
23613
+ try {
23614
+ if (!existsSync51(join47(homedir26(), ".runwork"))) {
23615
+ mkdirSync28(join47(homedir26(), ".runwork"), { recursive: true });
23616
+ }
23617
+ writeFileSync30(LOCK_PATH, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), {
23618
+ flag: "wx"
23619
+ });
23620
+ return true;
23621
+ } catch {
23622
+ return false;
23623
+ }
23624
+ }
23625
+ function tryAcquireOnce() {
23626
+ if (writeLockExclusive()) {
23627
+ ensureExitHandler();
23628
+ return true;
23629
+ }
23630
+ const existing = readLock();
23631
+ const stale = !existing || Date.now() - existing.startedAt > STALE_LOCK_MS || !isProcessAlive(existing.pid);
23632
+ if (!stale)
23633
+ return false;
23634
+ try {
23635
+ unlinkSync8(LOCK_PATH);
23636
+ } catch {}
23637
+ if (writeLockExclusive()) {
23638
+ ensureExitHandler();
23639
+ return true;
23640
+ }
23641
+ return false;
23642
+ }
23643
+ function sleep2(ms) {
23644
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
23645
+ }
23646
+ async function acquireSyncLock(waitMs = DEFAULT_WAIT_MS) {
23647
+ const deadline = Date.now() + waitMs;
23648
+ let delay = 250;
23649
+ for (;; ) {
23650
+ if (tryAcquireOnce())
23651
+ return true;
23652
+ const remaining = deadline - Date.now();
23653
+ if (remaining <= 0)
23654
+ return false;
23655
+ await sleep2(Math.min(delay, remaining));
23656
+ delay = Math.min(delay * 2, 5000);
23657
+ }
23658
+ }
23659
+ function releaseSyncLock() {
23660
+ const existing = readLock();
23661
+ if (existing?.pid === process.pid) {
23662
+ try {
23663
+ unlinkSync8(LOCK_PATH);
23664
+ } catch {}
23665
+ }
23666
+ }
23667
+
23447
23668
  // src/commands/sync.ts
23448
23669
  async function printAdoptionHint(credentials, workspaceId) {
23449
23670
  if (!workspaceId)
@@ -23460,10 +23681,10 @@ Tip: ${hint.title}`);
23460
23681
  } catch {}
23461
23682
  }
23462
23683
  function loadSetupState(filePath) {
23463
- if (!existsSync51(filePath))
23684
+ if (!existsSync52(filePath))
23464
23685
  return null;
23465
23686
  try {
23466
- return JSON.parse(readFileSync40(filePath, "utf-8"));
23687
+ return JSON.parse(readFileSync41(filePath, "utf-8"));
23467
23688
  } catch {
23468
23689
  return null;
23469
23690
  }
@@ -23484,15 +23705,15 @@ function readLocalSkills(state) {
23484
23705
  if (!baseDir)
23485
23706
  continue;
23486
23707
  for (const skillName of state.skills) {
23487
- const skillMdPath = join47(baseDir, skillName, "SKILL.md");
23488
- if (existsSync51(skillMdPath)) {
23489
- results.push({ name: skillName, content: readFileSync40(skillMdPath, "utf-8") });
23708
+ const skillMdPath = join48(baseDir, skillName, "SKILL.md");
23709
+ if (existsSync52(skillMdPath)) {
23710
+ results.push({ name: skillName, content: readFileSync41(skillMdPath, "utf-8") });
23490
23711
  continue;
23491
23712
  }
23492
23713
  const filename = skillName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
23493
- const flatPath = join47(baseDir, `${filename}.md`);
23494
- if (existsSync51(flatPath)) {
23495
- results.push({ name: skillName, content: readFileSync40(flatPath, "utf-8") });
23714
+ const flatPath = join48(baseDir, `${filename}.md`);
23715
+ if (existsSync52(flatPath)) {
23716
+ results.push({ name: skillName, content: readFileSync41(flatPath, "utf-8") });
23496
23717
  }
23497
23718
  }
23498
23719
  if (results.length > 0)
@@ -23546,6 +23767,19 @@ function ensureWorkspacePointer(state, statePath2, credentials) {
23546
23767
  return true;
23547
23768
  }
23548
23769
  async function syncFromState(state, statePath2, credentials, opts) {
23770
+ const acquired = await acquireSyncLock();
23771
+ if (!acquired) {
23772
+ console.log(" Another sync appears to be in progress on this machine; proceeding anyway.");
23773
+ await runSyncFromState(state, statePath2, credentials, opts);
23774
+ return;
23775
+ }
23776
+ try {
23777
+ await runSyncFromState(state, statePath2, credentials, opts);
23778
+ } finally {
23779
+ releaseSyncLock();
23780
+ }
23781
+ }
23782
+ async function runSyncFromState(state, statePath2, credentials, opts) {
23549
23783
  const client = new ApiClient(credentials);
23550
23784
  setVerbose(!!opts.verbose);
23551
23785
  if (!ensureWorkspacePointer(state, statePath2, credentials)) {
@@ -23733,9 +23967,9 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23733
23967
  persona: state.persona
23734
23968
  });
23735
23969
  let projectAppSkillFilter = null;
23736
- if (existsSync51(".runwork.json")) {
23970
+ if (existsSync52(".runwork.json")) {
23737
23971
  try {
23738
- const config = JSON.parse(readFileSync40(".runwork.json", "utf-8"));
23972
+ const config = JSON.parse(readFileSync41(".runwork.json", "utf-8"));
23739
23973
  if (config.appName) {
23740
23974
  projectAppSkillFilter = config.appName.toLowerCase().replace(/[^a-z0-9]+/g, "-");
23741
23975
  }
@@ -23760,9 +23994,15 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23760
23994
  };
23761
23995
  const mcpFailedAdapters = new Set;
23762
23996
  const skillFailedAdapters = new Set;
23997
+ const instructionFailedAdapters = new Set;
23998
+ const configFailedAdapters = new Set;
23999
+ const hookFailedAdapters = new Set;
24000
+ const agentOutcomes = {};
24001
+ const syncStartedAt = new Date().toISOString();
23763
24002
  for (const adapter2 of adapters) {
23764
24003
  if (isConnectOnlyAgent(getRegistryAgent(adapter2.slug))) {
23765
24004
  vlog(` [${adapter2.name}] Connect-only agent: no local files to sync`);
24005
+ agentOutcomes[adapter2.slug] = { at: syncStartedAt, ok: true, skipped: true };
23766
24006
  summary.adaptersProcessed++;
23767
24007
  continue;
23768
24008
  }
@@ -23824,15 +24064,32 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23824
24064
  await adapter2.writeInstructionHint(instructionHint, scope);
23825
24065
  summary.instructionHintWrites++;
23826
24066
  vlog(` [${adapter2.name}] Updated instruction hints (${scope})`);
23827
- if (adapter2.writeBuiltInHooks) {
24067
+ } catch (err) {
24068
+ adapterFailedAnyScope = true;
24069
+ instructionFailedAdapters.add(adapter2.slug);
24070
+ console.warn(` [${adapter2.name}] Failed instructions (${scope}): ${err instanceof Error ? err.message : err}`);
24071
+ }
24072
+ if (adapter2.writeBuiltInHooks) {
24073
+ try {
23828
24074
  await adapter2.writeBuiltInHooks(scope);
23829
24075
  summary.hookInstallCalls++;
24076
+ } catch (err) {
24077
+ adapterFailedAnyScope = true;
24078
+ hookFailedAdapters.add(adapter2.slug);
24079
+ console.warn(` [${adapter2.name}] Failed hooks (${scope}): ${err instanceof Error ? err.message : err}`);
23830
24080
  }
23831
- } catch (err) {
23832
- adapterFailedAnyScope = true;
23833
- console.warn(` [${adapter2.name}] Failed (${scope}): ${err instanceof Error ? err.message : err}`);
23834
24081
  }
23835
24082
  }
24083
+ const failedDimensions = [];
24084
+ if (skillFailedAdapters.has(adapter2.slug))
24085
+ failedDimensions.push("skills");
24086
+ if (mcpFailedAdapters.has(adapter2.slug))
24087
+ failedDimensions.push("mcp");
24088
+ if (instructionFailedAdapters.has(adapter2.slug))
24089
+ failedDimensions.push("instructions");
24090
+ if (hookFailedAdapters.has(adapter2.slug))
24091
+ failedDimensions.push("hooks");
24092
+ agentOutcomes[adapter2.slug] = failedDimensions.length > 0 ? { at: syncStartedAt, ok: false, failed: failedDimensions } : { at: syncStartedAt, ok: true };
23836
24093
  summary.adaptersProcessed++;
23837
24094
  if (adapterFailedAnyScope)
23838
24095
  summary.adaptersFailed++;
@@ -23863,7 +24120,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23863
24120
  await adapter2.writeTeamInstructions(fullInstructions, scope);
23864
24121
  teamInstructionsApplied = true;
23865
24122
  vlog(` [${adapter2.name}] Updated team instructions (${scope})`);
23866
- } catch {}
24123
+ } catch (err) {
24124
+ instructionFailedAdapters.add(adapter2.slug);
24125
+ console.warn(` [${adapter2.name}] Failed team instructions (${scope}): ${err instanceof Error ? err.message : err}`);
24126
+ }
23867
24127
  }
23868
24128
  }
23869
24129
  if (agentConfigs) {
@@ -23884,7 +24144,10 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23884
24144
  agentConfigsApplied++;
23885
24145
  const configKeys = Object.keys(configWithoutInstructions).join(", ");
23886
24146
  vlog(` [${adapter2.name}] Updated agent config: ${configKeys} (${scope})`);
23887
- } catch {}
24147
+ } catch (err) {
24148
+ configFailedAdapters.add(adapter2.slug);
24149
+ console.warn(` [${adapter2.name}] Failed agent config (${scope}): ${err instanceof Error ? err.message : err}`);
24150
+ }
23888
24151
  }
23889
24152
  }
23890
24153
  }
@@ -23966,13 +24229,16 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23966
24229
  }
23967
24230
  if (team)
23968
24231
  agentConfigsApplied++;
23969
- } catch {}
24232
+ } catch (err) {
24233
+ configFailedAdapters.add(adapter2.slug);
24234
+ console.warn(` [${adapter2.name}] Failed agent config: ${err instanceof Error ? err.message : err}`);
24235
+ }
23970
24236
  }
23971
24237
  state.agentDefaultsVersion = AGENT_DEFAULTS_SCHEMA_VERSION;
23972
24238
  }
23973
24239
  for (const adapter2 of adapters) {
23974
24240
  if (adapter2 instanceof CodexAdapter) {
23975
- const runworkDir = join47(homedir26(), ".runwork");
24241
+ const runworkDir = join48(homedir27(), ".runwork");
23976
24242
  const result = adapter2.registerDesktopWorkspace(runworkDir, "Runwork");
23977
24243
  if (result === "written") {
23978
24244
  vlog(` [${adapter2.name}] Registered workspace in Codex desktop app`);
@@ -23982,6 +24248,21 @@ This account is not a member of "${state.workspaceName || state.workspaceId}".`)
23982
24248
  }
23983
24249
  const prevMcpNames = state.mcpServers ?? [];
23984
24250
  state.lastSyncAt = new Date().toISOString();
24251
+ const lateFailures = [
24252
+ [configFailedAdapters, "config"],
24253
+ [instructionFailedAdapters, "instructions"],
24254
+ [hookFailedAdapters, "hooks"]
24255
+ ];
24256
+ for (const [slugs, dimension] of lateFailures) {
24257
+ for (const slug of slugs) {
24258
+ const existing = agentOutcomes[slug];
24259
+ if (existing?.failed?.includes(dimension))
24260
+ continue;
24261
+ const failed = [...existing?.failed ?? [], dimension];
24262
+ agentOutcomes[slug] = { at: existing?.at ?? syncStartedAt, ok: false, failed };
24263
+ }
24264
+ }
24265
+ state.lastSyncAgents = agentOutcomes;
23985
24266
  state.mcpServers = mcpEntries.map((e) => e.name);
23986
24267
  state.skills = remoteSkills.map((s) => s.name);
23987
24268
  state.skillFilenames = [
@@ -24114,8 +24395,8 @@ var syncCommand = new Command26("sync").description("Sync skills bidirectionally
24114
24395
  verbose: !!opts.verbose,
24115
24396
  redetect: !!opts.redetect
24116
24397
  };
24117
- const projectStatePath = join47(process.cwd(), ".runwork", "setup.json");
24118
- const userStatePath = join47(homedir26(), ".runwork", "setup.json");
24398
+ const projectStatePath = join48(process.cwd(), ".runwork", "setup.json");
24399
+ const userStatePath = join48(homedir27(), ".runwork", "setup.json");
24119
24400
  const projectState = loadSetupState(projectStatePath);
24120
24401
  const userState = loadSetupState(userStatePath);
24121
24402
  if (!projectState && !userState) {
@@ -24184,7 +24465,7 @@ function toSkillFilename(name) {
24184
24465
  return name.toLowerCase().replace(/[^a-z0-9]+/g, "-");
24185
24466
  }
24186
24467
  function loadSetupStateForScope(scope) {
24187
- const path4 = scope === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
24468
+ const path4 = scope === "project" ? join49(process.cwd(), ".runwork", "setup.json") : join49(homedir28(), ".runwork", "setup.json");
24188
24469
  return readJsonOrNull(path4);
24189
24470
  }
24190
24471
  async function parkAndTeardownWorkspace(previous, scopes) {
@@ -24353,8 +24634,8 @@ Re-run without --dry-run to sync workspace data.`);
24353
24634
  }
24354
24635
  persistDefaultWorkspace(workspaceId, workspaceName);
24355
24636
  for (const s of scopes) {
24356
- const dir = s === "project" ? ".runwork" : join48(homedir27(), ".runwork");
24357
- writeJsonAtomic(join48(dir, "setup.json"), state);
24637
+ const dir = s === "project" ? ".runwork" : join49(homedir28(), ".runwork");
24638
+ writeJsonAtomic(join49(dir, "setup.json"), state);
24358
24639
  }
24359
24640
  if (restored)
24360
24641
  clearParkedState(workspaceId);
@@ -24362,7 +24643,7 @@ Re-run without --dry-run to sync workspace data.`);
24362
24643
  Syncing workspace data...
24363
24644
  `);
24364
24645
  for (const s of scopes) {
24365
- const statePath2 = s === "project" ? join48(process.cwd(), ".runwork", "setup.json") : join48(homedir27(), ".runwork", "setup.json");
24646
+ const statePath2 = s === "project" ? join49(process.cwd(), ".runwork", "setup.json") : join49(homedir28(), ".runwork", "setup.json");
24366
24647
  await syncFromState(state, statePath2, credentials, {
24367
24648
  dryRun: false,
24368
24649
  pullOnly: true,
@@ -24382,16 +24663,16 @@ init_client();
24382
24663
  import { Command as Command28 } from "commander";
24383
24664
 
24384
24665
  // src/utils/setup-state.ts
24385
- import { existsSync as existsSync52, readFileSync as readFileSync41 } from "fs";
24386
- import { join as join49 } from "path";
24387
- import { homedir as homedir28 } from "os";
24666
+ import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
24667
+ import { join as join50 } from "path";
24668
+ import { homedir as homedir29 } from "os";
24388
24669
  function loadSetupState2() {
24389
- const projectPath = join49(process.cwd(), ".runwork", "setup.json");
24390
- const userPath = join49(homedir28(), ".runwork", "setup.json");
24670
+ const projectPath = join50(process.cwd(), ".runwork", "setup.json");
24671
+ const userPath = join50(homedir29(), ".runwork", "setup.json");
24391
24672
  for (const p of [projectPath, userPath]) {
24392
- if (existsSync52(p)) {
24673
+ if (existsSync53(p)) {
24393
24674
  try {
24394
- return JSON.parse(readFileSync41(p, "utf-8"));
24675
+ return JSON.parse(readFileSync42(p, "utf-8"));
24395
24676
  } catch {
24396
24677
  continue;
24397
24678
  }
@@ -24450,14 +24731,14 @@ init_client();
24450
24731
  init_types();
24451
24732
  await init_detect();
24452
24733
  import { Command as Command29 } from "commander";
24453
- import { existsSync as existsSync53, readFileSync as readFileSync42 } from "fs";
24454
- import { resolve as resolve3, join as join50 } from "path";
24455
- import { homedir as homedir29 } from "os";
24734
+ import { existsSync as existsSync54, readFileSync as readFileSync43 } from "fs";
24735
+ import { resolve as resolve3, join as join51 } from "path";
24736
+ import { homedir as homedir30 } from "os";
24456
24737
  function loadSetupState3(filePath) {
24457
- if (!existsSync53(filePath))
24738
+ if (!existsSync54(filePath))
24458
24739
  return null;
24459
24740
  try {
24460
- return JSON.parse(readFileSync42(filePath, "utf-8"));
24741
+ return JSON.parse(readFileSync43(filePath, "utf-8"));
24461
24742
  } catch {
24462
24743
  return null;
24463
24744
  }
@@ -24473,8 +24754,8 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
24473
24754
  process.exit(1);
24474
24755
  }
24475
24756
  const credentials = requireAuth();
24476
- const projectStatePath = join50(process.cwd(), ".runwork", "setup.json");
24477
- const userStatePath = join50(homedir29(), ".runwork", "setup.json");
24757
+ const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
24758
+ const userStatePath = join51(homedir30(), ".runwork", "setup.json");
24478
24759
  const state = loadSetupState3(projectStatePath) ?? loadSetupState3(userStatePath);
24479
24760
  if (!state) {
24480
24761
  console.error("No setup state found. Run `runwork setup` first.");
@@ -24564,23 +24845,143 @@ var buildPluginCommand = new Command29("build-plugin").description("Build an ins
24564
24845
 
24565
24846
  // src/commands/uninstall.ts
24566
24847
  init_prompt();
24848
+ init_subprocess();
24567
24849
  await init_detect();
24568
24850
  import { Command as Command30 } from "commander";
24569
- import { existsSync as existsSync54, readFileSync as readFileSync43, rmSync as rmSync13, unlinkSync as unlinkSync8 } from "fs";
24570
- import { join as join51 } from "path";
24571
- import { homedir as homedir30 } from "os";
24851
+ import { existsSync as existsSync55, readFileSync as readFileSync44, writeFileSync as writeFileSync32, readdirSync as readdirSync16, rmSync as rmSync12, unlinkSync as unlinkSync9, lstatSync, readlinkSync } from "fs";
24852
+ import { join as join52, resolve as resolve4, relative as relative5, isAbsolute as isAbsolute5 } from "path";
24853
+ import { homedir as homedir31 } from "os";
24572
24854
  function loadSetupState4(filePath) {
24573
- if (!existsSync54(filePath))
24855
+ if (!existsSync55(filePath))
24574
24856
  return null;
24575
24857
  try {
24576
- return JSON.parse(readFileSync43(filePath, "utf-8"));
24858
+ return JSON.parse(readFileSync44(filePath, "utf-8"));
24577
24859
  } catch {
24578
24860
  return null;
24579
24861
  }
24580
24862
  }
24863
+ var PRESERVED_ENTRIES = ["bin", "apps", "trash"];
24864
+ function removeRunworkState(stateDir, opts) {
24865
+ const result = { removed: [], preserved: [], errors: [] };
24866
+ if (!existsSync55(stateDir))
24867
+ return result;
24868
+ const preserve = new Set(PRESERVED_ENTRIES);
24869
+ if (opts.keepAuth)
24870
+ preserve.add(".credentials");
24871
+ let entries;
24872
+ try {
24873
+ entries = readdirSync16(stateDir);
24874
+ } catch (err) {
24875
+ result.errors.push(`${stateDir}: ${err instanceof Error ? err.message : err}`);
24876
+ return result;
24877
+ }
24878
+ for (const entry of entries) {
24879
+ const target = join52(stateDir, entry);
24880
+ if (preserve.has(entry)) {
24881
+ if (existsSync55(target))
24882
+ result.preserved.push(target);
24883
+ continue;
24884
+ }
24885
+ try {
24886
+ rmSync12(target, { recursive: true, force: true });
24887
+ result.removed.push(target);
24888
+ } catch (err) {
24889
+ result.errors.push(`${target}: ${err instanceof Error ? err.message : err}`);
24890
+ }
24891
+ }
24892
+ return result;
24893
+ }
24894
+ var SHELL_PROFILES = [".zshrc", ".zprofile", ".bashrc", ".bash_profile", ".profile"];
24895
+ var BIN_DIR_PATTERN = /\.runwork[\\/]bin/;
24896
+ function stripRunworkPathLines(file) {
24897
+ if (!existsSync55(file))
24898
+ return false;
24899
+ let content;
24900
+ try {
24901
+ content = readFileSync44(file, "utf-8");
24902
+ } catch {
24903
+ return false;
24904
+ }
24905
+ if (!content.includes("Added by Runwork"))
24906
+ return false;
24907
+ const lines = content.split(`
24908
+ `);
24909
+ const kept = [];
24910
+ for (let i = 0;i < lines.length; i++) {
24911
+ if (/^\s*#\s*Added by Runwork\b/.test(lines[i])) {
24912
+ if (i + 1 < lines.length && BIN_DIR_PATTERN.test(lines[i + 1]))
24913
+ i++;
24914
+ continue;
24915
+ }
24916
+ kept.push(lines[i]);
24917
+ }
24918
+ const next = kept.join(`
24919
+ `);
24920
+ if (next === content)
24921
+ return false;
24922
+ try {
24923
+ writeFileSync32(file, next);
24924
+ return true;
24925
+ } catch {
24926
+ return false;
24927
+ }
24928
+ }
24929
+ function cleanShellProfilePathEntries() {
24930
+ return SHELL_PROFILES.map((name) => join52(homedir31(), name)).filter(stripRunworkPathLines);
24931
+ }
24932
+ function cleanPowerShellProfilePathEntries() {
24933
+ if (process.platform !== "win32")
24934
+ return [];
24935
+ const touched = [];
24936
+ const seen = new Set;
24937
+ for (const host of ["powershell", "pwsh"]) {
24938
+ let profilePath;
24939
+ try {
24940
+ profilePath = execFileSync(host, ["-NoProfile", "-Command", "$PROFILE.CurrentUserCurrentHost"], {
24941
+ encoding: "utf-8",
24942
+ stdio: "pipe"
24943
+ }).trim();
24944
+ } catch {
24945
+ continue;
24946
+ }
24947
+ if (!profilePath || seen.has(profilePath))
24948
+ continue;
24949
+ seen.add(profilePath);
24950
+ if (stripRunworkPathLines(profilePath))
24951
+ touched.push(profilePath);
24952
+ }
24953
+ return touched;
24954
+ }
24955
+ function removeRunworkSymlink(linkPath) {
24956
+ let stat;
24957
+ try {
24958
+ stat = lstatSync(linkPath);
24959
+ } catch {
24960
+ return false;
24961
+ }
24962
+ if (!stat.isSymbolicLink())
24963
+ return false;
24964
+ let target;
24965
+ try {
24966
+ target = resolve4(linkPath, "..", readlinkSync(linkPath));
24967
+ } catch {
24968
+ return false;
24969
+ }
24970
+ const ours = resolve4(join52(homedir31(), ".runwork", "bin"));
24971
+ const rel = relative5(ours, resolve4(target));
24972
+ const insideOurs = rel === "" || !rel.startsWith("..") && !isAbsolute5(rel);
24973
+ if (!insideOurs)
24974
+ return false;
24975
+ try {
24976
+ unlinkSync9(linkPath);
24977
+ return true;
24978
+ } catch {
24979
+ return false;
24980
+ }
24981
+ }
24581
24982
  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) => {
24582
- const projectStatePath = join51(process.cwd(), ".runwork", "setup.json");
24583
- const userStatePath = join51(homedir30(), ".runwork", "setup.json");
24983
+ const projectStatePath = join52(process.cwd(), ".runwork", "setup.json");
24984
+ const userStatePath = join52(homedir31(), ".runwork", "setup.json");
24584
24985
  const projectState = loadSetupState4(projectStatePath);
24585
24986
  const userState = loadSetupState4(userStatePath);
24586
24987
  if (!projectState && !userState) {
@@ -24618,6 +25019,13 @@ This will remove all Runwork configuration from your local agents:
24618
25019
  } else {
24619
25020
  console.log(" - Setup state and auth credentials (~/.runwork/)");
24620
25021
  }
25022
+ console.log(" - The PATH line we added to your shell profiles");
25023
+ console.log(" - Symlinks that point at our own binary");
25024
+ console.log(`
25025
+ What will be KEPT:`);
25026
+ console.log(" - ~/.runwork/bin the CLI binary itself");
25027
+ console.log(" - ~/.runwork/apps your own app source");
25028
+ console.log(" - ~/.runwork/trash recoverable copies of removed files");
24621
25029
  console.log("");
24622
25030
  if (!opts.yes) {
24623
25031
  const confirmed = await promptConfirm("Proceed with uninstall?");
@@ -24636,7 +25044,7 @@ This will remove all Runwork configuration from your local agents:
24636
25044
  for (const { state, label } of entries) {
24637
25045
  const scopes = state.scope === "both" ? ["project", "user"] : [state.scope];
24638
25046
  const manifest = {
24639
- skillFilenames: state.skillFilenames ?? state.skills.map((s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-")),
25047
+ skillFilenames: state.skillFilenames ?? (state.skills ?? []).map((s) => s.toLowerCase().replace(/[^a-z0-9]+/g, "-")),
24640
25048
  mcpServerNames: state.mcpServers ?? []
24641
25049
  };
24642
25050
  for (const slug of state.configuredAgents) {
@@ -24660,28 +25068,51 @@ This will remove all Runwork configuration from your local agents:
24660
25068
  }
24661
25069
  }
24662
25070
  }
24663
- const stateDir = label === "project" ? join51(process.cwd(), ".runwork") : join51(homedir30(), ".runwork");
24664
- if (opts.keepAuth && label === "user") {
24665
- const setupFile = join51(stateDir, "setup.json");
24666
- if (existsSync54(setupFile)) {
24667
- try {
24668
- unlinkSync8(setupFile);
24669
- console.log(` Removed ${setupFile} (kept credentials)`);
24670
- } catch (err) {
24671
- console.warn(` Failed to remove ${setupFile}: ${err instanceof Error ? err.message : err}`);
24672
- errors++;
24673
- }
25071
+ const stateDir = label === "project" ? join52(process.cwd(), ".runwork") : join52(homedir31(), ".runwork");
25072
+ if (existsSync55(stateDir)) {
25073
+ const outcome = removeRunworkState(stateDir, {
25074
+ keepAuth: Boolean(opts.keepAuth) && label === "user"
25075
+ });
25076
+ if (outcome.removed.length > 0) {
25077
+ console.log(` Removed Runwork state from ${stateDir}`);
24674
25078
  }
24675
- } else if (existsSync54(stateDir)) {
24676
- try {
24677
- rmSync13(stateDir, { recursive: true, force: true });
24678
- console.log(` Removed ${stateDir}`);
24679
- } catch (err) {
24680
- console.warn(` Failed to remove ${stateDir}: ${err instanceof Error ? err.message : err}`);
25079
+ for (const problem of outcome.errors) {
25080
+ console.warn(` Failed to remove ${problem}`);
24681
25081
  errors++;
24682
25082
  }
25083
+ if (outcome.preserved.length > 0) {
25084
+ console.log("");
25085
+ console.log(" Kept (not Runwork's to delete):");
25086
+ for (const kept of outcome.preserved)
25087
+ console.log(` - ${kept}`);
25088
+ }
24683
25089
  }
24684
25090
  }
25091
+ const touchedProfiles = [
25092
+ ...cleanShellProfilePathEntries(),
25093
+ ...cleanPowerShellProfilePathEntries()
25094
+ ];
25095
+ if (touchedProfiles.length > 0) {
25096
+ console.log("");
25097
+ console.log(" Removed the Runwork PATH line from:");
25098
+ for (const file of touchedProfiles)
25099
+ console.log(` - ${file}`);
25100
+ }
25101
+ const removedLinks = [
25102
+ join52(homedir31(), ".local", "bin", "runwork"),
25103
+ "/usr/local/bin/runwork"
25104
+ ].filter(removeRunworkSymlink);
25105
+ if (removedLinks.length > 0) {
25106
+ console.log("");
25107
+ console.log(" Removed symlinks:");
25108
+ for (const link of removedLinks)
25109
+ console.log(` - ${link}`);
25110
+ }
25111
+ if (process.platform === "win32") {
25112
+ console.log("");
25113
+ console.log(" Still on your PATH (remove by hand if you want it gone):");
25114
+ console.log(` ${join52(homedir31(), ".runwork", "bin")} in your user PATH`);
25115
+ }
24685
25116
  console.log("");
24686
25117
  if (errors > 0) {
24687
25118
  console.log(`Uninstall completed with ${errors} warning${errors > 1 ? "s" : ""}. ${cleanedAgents} agent${cleanedAgents > 1 ? "s" : ""} cleaned.`);
@@ -24806,7 +25237,7 @@ var membersCommand = new Command32("members").description("List workspace member
24806
25237
  init_store();
24807
25238
  init_client();
24808
25239
  import { Command as Command33 } from "commander";
24809
- import { readFileSync as readFileSync44 } from "fs";
25240
+ import { readFileSync as readFileSync45 } from "fs";
24810
25241
  function normalizeApiPath(rawPath, baseUrl) {
24811
25242
  if (/^https?:\/\//i.test(rawPath)) {
24812
25243
  const target = new URL(rawPath);
@@ -24839,7 +25270,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
24839
25270
  let curlStr = opts.curl;
24840
25271
  if (opts.curlFile) {
24841
25272
  try {
24842
- curlStr = readFileSync44(opts.curlFile, "utf-8");
25273
+ curlStr = readFileSync45(opts.curlFile, "utf-8");
24843
25274
  } catch (err) {
24844
25275
  console.error(`Could not read --curl-file: ${err instanceof Error ? err.message : err}`);
24845
25276
  process.exit(1);
@@ -24859,7 +25290,7 @@ to be read or pasted manually. Prefer a dedicated command when one exists
24859
25290
  let raw = opts.body;
24860
25291
  if (raw.startsWith("@")) {
24861
25292
  try {
24862
- raw = readFileSync44(raw.slice(1), "utf-8");
25293
+ raw = readFileSync45(raw.slice(1), "utf-8");
24863
25294
  } catch (err) {
24864
25295
  console.error(`Could not read body file: ${err instanceof Error ? err.message : err}`);
24865
25296
  process.exit(1);
@@ -24905,6 +25336,7 @@ import { Command as Command34 } from "commander";
24905
25336
 
24906
25337
  // src/health/checks.ts
24907
25338
  init_subprocess();
25339
+ init_http();
24908
25340
  init_store();
24909
25341
  init_client();
24910
25342
  init_http();
@@ -24912,9 +25344,9 @@ init_preflight();
24912
25344
  init_credentials();
24913
25345
  await init_detect();
24914
25346
  import { parse as parse2 } from "smol-toml";
24915
- import { existsSync as existsSync55, readFileSync as readFileSync45 } from "fs";
24916
- import { join as join52, sep as sep4 } from "path";
24917
- import { homedir as homedir31, platform as osPlatform2, arch as osArch } from "os";
25347
+ import { existsSync as existsSync56, readFileSync as readFileSync46 } from "fs";
25348
+ import { join as join53, sep as sep4 } from "path";
25349
+ import { homedir as homedir32, platform as osPlatform2, arch as osArch } from "os";
24918
25350
  var BASE_URL2 = process.env.RUNWORK_DOWNLOAD_BASE_URL || "https://runwork.ai";
24919
25351
  var LATEST_JSON_URL2 = `${BASE_URL2}/cli/latest.json`;
24920
25352
  function detectPlatform() {
@@ -24942,10 +25374,10 @@ function buildContext() {
24942
25374
  const credentials = getCredentials();
24943
25375
  const client = credentials ? new ApiClient(credentials) : null;
24944
25376
  let config = null;
24945
- const configPath = join52(process.cwd(), ".runwork.json");
24946
- if (existsSync55(configPath)) {
25377
+ const configPath = join53(process.cwd(), ".runwork.json");
25378
+ if (existsSync56(configPath)) {
24947
25379
  try {
24948
- config = JSON.parse(readFileSync45(configPath, "utf-8"));
25380
+ config = JSON.parse(readFileSync46(configPath, "utf-8"));
24949
25381
  } catch {}
24950
25382
  }
24951
25383
  return { credentials, client, config, cwd: process.cwd() };
@@ -25046,9 +25478,9 @@ async function checkCliArtifactReachable() {
25046
25478
  }
25047
25479
  async function checkCliInstallLocation() {
25048
25480
  const isWindows2 = osPlatform2() === "win32";
25049
- const home = homedir31();
25050
- const canonicalDir = join52(home, ".runwork", "bin");
25051
- const canonicalBinary = isWindows2 ? join52(canonicalDir, "runwork.exe") : join52(canonicalDir, "runwork");
25481
+ const home = homedir32();
25482
+ const canonicalDir = join53(home, ".runwork", "bin");
25483
+ const canonicalBinary = isWindows2 ? join53(canonicalDir, "runwork.exe") : join53(canonicalDir, "runwork");
25052
25484
  const candidates = [process.execPath, process.argv[1] || ""].filter(Boolean);
25053
25485
  const runsFromCanonical = candidates.some((p) => normalizePath(p) === normalizePath(canonicalBinary));
25054
25486
  if (runsFromCanonical) {
@@ -25058,7 +25490,7 @@ async function checkCliInstallLocation() {
25058
25490
  message: `canonical (${canonicalBinary})`
25059
25491
  };
25060
25492
  }
25061
- if (existsSync55(canonicalBinary)) {
25493
+ if (existsSync56(canonicalBinary)) {
25062
25494
  return {
25063
25495
  name: "cli-install-location",
25064
25496
  status: "warn",
@@ -25106,9 +25538,20 @@ async function checkAuthAndNetwork(ctx) {
25106
25538
  network: { name: "network", status: "pass", message: `API reachable (${elapsed}ms)` }
25107
25539
  };
25108
25540
  }
25541
+ const proxy = proxyForUrl(ctx.credentials.baseUrl || BASE_URL2);
25542
+ const network = proxy ? {
25543
+ name: "network",
25544
+ status: "fail",
25545
+ message: `API unreachable via ${proxy.variable} (${proxy.value}): ${message}`,
25546
+ details: [
25547
+ `A proxy is configured through ${proxy.variable}.`,
25548
+ "Runwork routes requests through `curl` when a proxy is set, so curl must be installed and able to reach the proxy.",
25549
+ "If this host should bypass the proxy, add it to NO_PROXY."
25550
+ ]
25551
+ } : { name: "network", status: "fail", message: `API unreachable: ${message}` };
25109
25552
  return {
25110
25553
  auth: { name: "auth", status: "skip", message: "could not verify (network error)" },
25111
- network: { name: "network", status: "fail", message: `API unreachable: ${message}` }
25554
+ network
25112
25555
  };
25113
25556
  }
25114
25557
  }
@@ -25180,8 +25623,8 @@ async function checkGitCredentialHelper(ctx) {
25180
25623
  };
25181
25624
  }
25182
25625
  async function checkProjectConfig(ctx) {
25183
- const configPath = join52(ctx.cwd, ".runwork.json");
25184
- if (!existsSync55(configPath)) {
25626
+ const configPath = join53(ctx.cwd, ".runwork.json");
25627
+ if (!existsSync56(configPath)) {
25185
25628
  if (!ctx.credentials) {
25186
25629
  return { name: "project-config", status: "skip", message: "no project (not logged in)" };
25187
25630
  }
@@ -25243,7 +25686,7 @@ async function checkGitRemote(ctx) {
25243
25686
  if (!ctx.config) {
25244
25687
  return { name: "git-remote", status: "skip", message: "skipped (no project)" };
25245
25688
  }
25246
- if (!existsSync55(join52(ctx.cwd, ".git"))) {
25689
+ if (!existsSync56(join53(ctx.cwd, ".git"))) {
25247
25690
  return {
25248
25691
  name: "git-remote",
25249
25692
  status: "fail",
@@ -25297,12 +25740,12 @@ async function checkDeployFreshness(ctx) {
25297
25740
  return { name: "deploy-freshness", status: "skip", message: "local HEAD unknown" };
25298
25741
  }
25299
25742
  function loadSetupState5() {
25300
- const projectPath = join52(process.cwd(), ".runwork", "setup.json");
25301
- const userPath = join52(homedir31(), ".runwork", "setup.json");
25743
+ const projectPath = join53(process.cwd(), ".runwork", "setup.json");
25744
+ const userPath = join53(homedir32(), ".runwork", "setup.json");
25302
25745
  for (const p of [projectPath, userPath]) {
25303
- if (existsSync55(p)) {
25746
+ if (existsSync56(p)) {
25304
25747
  try {
25305
- return JSON.parse(readFileSync45(p, "utf-8"));
25748
+ return JSON.parse(readFileSync46(p, "utf-8"));
25306
25749
  } catch {
25307
25750
  continue;
25308
25751
  }
@@ -25317,13 +25760,13 @@ async function checkCodexNetwork() {
25317
25760
  if (!state || !state.configuredAgents.includes("codex")) {
25318
25761
  return { name, status: "skip", message: "Codex not configured for Runwork" };
25319
25762
  }
25320
- const configPath = join52(homedir31(), ".codex", "config.toml");
25321
- if (!existsSync55(configPath)) {
25763
+ const configPath = join53(homedir32(), ".codex", "config.toml");
25764
+ if (!existsSync56(configPath)) {
25322
25765
  return { name, status: "skip", message: "no Codex config found" };
25323
25766
  }
25324
25767
  let parsed;
25325
25768
  try {
25326
- parsed = parse2(readFileSync45(configPath, "utf-8"));
25769
+ parsed = parse2(readFileSync46(configPath, "utf-8"));
25327
25770
  } catch {
25328
25771
  return { name, status: "warn", message: "could not parse ~/.codex/config.toml" };
25329
25772
  }
@@ -25376,19 +25819,19 @@ async function checkCodexDesktopProject() {
25376
25819
  if (!usesCodex) {
25377
25820
  return { name, status: "skip", message: "Codex not configured for Runwork" };
25378
25821
  }
25379
- const statePath2 = join52(homedir31(), ".codex", ".codex-global-state.json");
25380
- if (!existsSync55(statePath2)) {
25822
+ const statePath2 = join53(homedir32(), ".codex", ".codex-global-state.json");
25823
+ if (!existsSync56(statePath2)) {
25381
25824
  return { name, status: "skip", message: "Codex desktop app not detected" };
25382
25825
  }
25383
25826
  let savedRoots = [];
25384
25827
  try {
25385
- const parsed = JSON.parse(readFileSync45(statePath2, "utf-8"));
25828
+ const parsed = JSON.parse(readFileSync46(statePath2, "utf-8"));
25386
25829
  const roots = parsed["electron-saved-workspace-roots"];
25387
25830
  savedRoots = Array.isArray(roots) ? roots.filter((r) => typeof r === "string") : [];
25388
25831
  } catch {
25389
25832
  return { name, status: "warn", message: "could not read Codex desktop state" };
25390
25833
  }
25391
- const runworkDir = join52(homedir31(), ".runwork");
25834
+ const runworkDir = join53(homedir32(), ".runwork");
25392
25835
  if (savedRoots.includes(runworkDir)) {
25393
25836
  return { name, status: "pass", message: "Runwork project added to Codex desktop sidebar" };
25394
25837
  }
@@ -25441,9 +25884,9 @@ async function checkAgentSetup() {
25441
25884
  if (!adapter2 || !adapter2.supportsMcpScope("user"))
25442
25885
  continue;
25443
25886
  const mcpConfigPath = getMcpConfigPath2(slug, "user");
25444
- if (mcpConfigPath && existsSync55(mcpConfigPath)) {
25887
+ if (mcpConfigPath && existsSync56(mcpConfigPath)) {
25445
25888
  try {
25446
- const content = readFileSync45(mcpConfigPath, "utf-8");
25889
+ const content = readFileSync46(mcpConfigPath, "utf-8");
25447
25890
  const missingMcp = state.mcpServers.filter((name) => !content.includes(name));
25448
25891
  if (missingMcp.length > 0) {
25449
25892
  details.push(`${missingMcp.length} MCP server(s) missing from ${slug} config`);
@@ -25465,15 +25908,22 @@ async function checkAgentSetup() {
25465
25908
  const skillsDir = getSkillsDir(slug, "user");
25466
25909
  if (!skillsDir)
25467
25910
  continue;
25911
+ const adapter2 = getAdapterBySlug(slug);
25912
+ const mcpCoversAppSkills = !!adapter2?.mcpProvidesSkills && state.mcpServers.length > 0;
25913
+ const isCoveredByMcp = (name) => mcpCoversAppSkills && state.skillHashes?.[name]?.source === "app";
25468
25914
  const missingSkills = state.skills.filter((name) => {
25469
- const skillPath = join52(skillsDir, name, "SKILL.md");
25470
- return !existsSync55(skillPath);
25915
+ if (isCoveredByMcp(name))
25916
+ return false;
25917
+ const skillPath = join53(skillsDir, name, "SKILL.md");
25918
+ return !existsSync56(skillPath);
25471
25919
  });
25472
25920
  if (missingSkills.length > 0) {
25473
25921
  details.push(`${missingSkills.length} skill(s) missing from ${slug}`);
25474
25922
  upgrade("warn");
25475
25923
  } else if (state.skills.length > 0) {
25476
- details.push(`${state.skills.length} skill(s) installed`);
25924
+ const mcpCoveredCount = state.skills.filter(isCoveredByMcp).length;
25925
+ const onDiskCount = state.skills.length - mcpCoveredCount;
25926
+ details.push(mcpCoveredCount > 0 ? `${state.skills.length} skill(s) installed (${onDiskCount} on disk, ${mcpCoveredCount} via MCP)` : `${state.skills.length} skill(s) installed`);
25477
25927
  }
25478
25928
  skillsChecked = true;
25479
25929
  break;
@@ -25492,28 +25942,28 @@ async function checkAgentSetup() {
25492
25942
  };
25493
25943
  }
25494
25944
  function getMcpConfigPath2(slug, scope) {
25495
- const home = homedir31();
25945
+ const home = homedir32();
25496
25946
  switch (slug) {
25497
25947
  case "claude-code":
25498
- return scope === "project" ? join52(process.cwd(), ".mcp.json") : join52(home, ".claude", "settings.json");
25948
+ return scope === "project" ? join53(process.cwd(), ".mcp.json") : join53(home, ".claude", "settings.json");
25499
25949
  case "cursor":
25500
- return scope === "project" ? join52(process.cwd(), ".cursor", "mcp.json") : join52(home, ".cursor", "mcp.json");
25950
+ return scope === "project" ? join53(process.cwd(), ".cursor", "mcp.json") : join53(home, ".cursor", "mcp.json");
25501
25951
  case "windsurf":
25502
- return scope === "project" ? join52(process.cwd(), ".windsurf", "mcp.json") : join52(home, ".windsurf", "mcp.json");
25952
+ return scope === "project" ? join53(process.cwd(), ".windsurf", "mcp.json") : join53(home, ".windsurf", "mcp.json");
25503
25953
  case "codex":
25504
25954
  case "codex-app":
25505
- return scope === "user" ? join52(home, ".codex", "config.toml") : null;
25955
+ return scope === "user" ? join53(home, ".codex", "config.toml") : null;
25506
25956
  case "gemini":
25507
- return scope === "user" ? join52(home, ".gemini", "settings.json") : null;
25957
+ return scope === "user" ? join53(home, ".gemini", "settings.json") : null;
25508
25958
  default:
25509
25959
  return null;
25510
25960
  }
25511
25961
  }
25512
25962
  async function checkWorkspacePointers() {
25513
- const userStatePath = join52(homedir31(), ".runwork", "setup.json");
25514
- const state = existsSync55(userStatePath) ? (() => {
25963
+ const userStatePath = join53(homedir32(), ".runwork", "setup.json");
25964
+ const state = existsSync56(userStatePath) ? (() => {
25515
25965
  try {
25516
- return JSON.parse(readFileSync45(userStatePath, "utf-8"));
25966
+ return JSON.parse(readFileSync46(userStatePath, "utf-8"));
25517
25967
  } catch {
25518
25968
  return null;
25519
25969
  }
@@ -25543,15 +25993,15 @@ async function checkWorkspacePointers() {
25543
25993
  };
25544
25994
  }
25545
25995
  function getSkillsDir(slug, scope) {
25546
- const home = homedir31();
25996
+ const home = homedir32();
25547
25997
  switch (slug) {
25548
25998
  case "claude-code":
25549
- return scope === "project" ? join52(process.cwd(), ".claude", "skills") : join52(home, ".claude", "skills");
25999
+ return scope === "project" ? join53(process.cwd(), ".claude", "skills") : join53(home, ".claude", "skills");
25550
26000
  case "codex":
25551
26001
  case "codex-app":
25552
- return scope === "project" ? join52(process.cwd(), ".agents", "skills") : join52(home, ".agents", "skills");
26002
+ return scope === "project" ? join53(process.cwd(), ".agents", "skills") : join53(home, ".agents", "skills");
25553
26003
  case "gemini":
25554
- return scope === "project" ? join52(process.cwd(), ".gemini", "skills") : join52(home, ".gemini", "skills");
26004
+ return scope === "project" ? join53(process.cwd(), ".gemini", "skills") : join53(home, ".gemini", "skills");
25555
26005
  default:
25556
26006
  return null;
25557
26007
  }
@@ -25604,18 +26054,18 @@ async function runAllChecks(options) {
25604
26054
  // src/health/fix.ts
25605
26055
  init_credentials();
25606
26056
  init_remote();
25607
- import { existsSync as existsSync56 } from "fs";
25608
- import { join as join53 } from "path";
26057
+ import { existsSync as existsSync57 } from "fs";
26058
+ import { join as join54 } from "path";
25609
26059
  async function applyDoctorFixes(ctx, failingNames) {
25610
26060
  const failing = new Set(failingNames);
25611
26061
  const outcomes = [];
25612
26062
  if (failing.has("git-credential-helper")) {
25613
26063
  if (ctx.credentials?.baseUrl) {
25614
- await ensureGitCredentialHelper(ctx.credentials.baseUrl);
26064
+ const result = await ensureGitCredentialHelper(ctx.credentials.baseUrl);
25615
26065
  outcomes.push({
25616
26066
  name: "git-credential-helper",
25617
- applied: true,
25618
- message: "registered the runwork git credential helper"
26067
+ applied: result.ok,
26068
+ message: result.ok ? "registered the runwork git credential helper" : result.message
25619
26069
  });
25620
26070
  } else {
25621
26071
  outcomes.push({
@@ -25632,7 +26082,7 @@ async function applyDoctorFixes(ctx, failingNames) {
25632
26082
  applied: false,
25633
26083
  message: "no project config -- run inside an app directory"
25634
26084
  });
25635
- } else if (!existsSync56(join53(ctx.cwd, ".git"))) {
26085
+ } else if (!existsSync57(join54(ctx.cwd, ".git"))) {
25636
26086
  outcomes.push({
25637
26087
  name: "git-remote",
25638
26088
  applied: false,
@@ -25651,10 +26101,10 @@ async function applyDoctorFixes(ctx, failingNames) {
25651
26101
  }
25652
26102
 
25653
26103
  // src/agents/runtime-detection.ts
25654
- import { existsSync as existsSync57, readFileSync as readFileSync46, statSync as statSync10, readdirSync as readdirSync16 } from "fs";
25655
- import { homedir as homedir32 } from "os";
25656
- import { join as join54 } from "path";
25657
- var RUNWORK_SESSIONS_DIR = join54(homedir32(), ".runwork", "sessions");
26104
+ import { existsSync as existsSync58, readFileSync as readFileSync47, statSync as statSync10, readdirSync as readdirSync17 } from "fs";
26105
+ import { homedir as homedir33 } from "os";
26106
+ import { join as join55 } from "path";
26107
+ var RUNWORK_SESSIONS_DIR = join55(homedir33(), ".runwork", "sessions");
25658
26108
  function detectCurrentAgent() {
25659
26109
  const claudeCodeSessionId = process.env.CLAUDE_CODE_SESSION_ID;
25660
26110
  if (claudeCodeSessionId) {
@@ -25717,11 +26167,11 @@ function detectCurrentAgent() {
25717
26167
  return null;
25718
26168
  }
25719
26169
  function readHookSessionInfo(sessionId) {
25720
- const path4 = join54(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
25721
- if (!existsSync57(path4))
26170
+ const path4 = join55(RUNWORK_SESSIONS_DIR, `${sessionId}.json`);
26171
+ if (!existsSync58(path4))
25722
26172
  return null;
25723
26173
  try {
25724
- const raw = readFileSync46(path4, "utf8");
26174
+ const raw = readFileSync47(path4, "utf8");
25725
26175
  const parsed = JSON.parse(raw);
25726
26176
  return parsed;
25727
26177
  } catch {
@@ -25729,37 +26179,37 @@ function readHookSessionInfo(sessionId) {
25729
26179
  }
25730
26180
  }
25731
26181
  function findClaudeCodeSessionFile(sessionId) {
25732
- const root = join54(homedir32(), ".claude", "projects");
25733
- if (!existsSync57(root))
26182
+ const root = join55(homedir33(), ".claude", "projects");
26183
+ if (!existsSync58(root))
25734
26184
  return null;
25735
26185
  let projectDirs;
25736
26186
  try {
25737
- projectDirs = readdirSync16(root);
26187
+ projectDirs = readdirSync17(root);
25738
26188
  } catch {
25739
26189
  return null;
25740
26190
  }
25741
26191
  for (const dir of projectDirs) {
25742
- const candidate = join54(root, dir, `${sessionId}.jsonl`);
25743
- if (existsSync57(candidate))
26192
+ const candidate = join55(root, dir, `${sessionId}.jsonl`);
26193
+ if (existsSync58(candidate))
25744
26194
  return candidate;
25745
26195
  }
25746
26196
  return null;
25747
26197
  }
25748
26198
  function findCodexRolloutFile(threadId) {
25749
- const root = join54(homedir32(), ".codex", "sessions");
25750
- if (!existsSync57(root))
26199
+ const root = join55(homedir33(), ".codex", "sessions");
26200
+ if (!existsSync58(root))
25751
26201
  return null;
25752
26202
  const stack = [root];
25753
26203
  while (stack.length > 0) {
25754
26204
  const dir = stack.pop();
25755
26205
  let entries;
25756
26206
  try {
25757
- entries = readdirSync16(dir);
26207
+ entries = readdirSync17(dir);
25758
26208
  } catch {
25759
26209
  continue;
25760
26210
  }
25761
26211
  for (const entry of entries) {
25762
- const full = join54(dir, entry);
26212
+ const full = join55(dir, entry);
25763
26213
  let s;
25764
26214
  try {
25765
26215
  s = statSync10(full);
@@ -25776,28 +26226,28 @@ function findCodexRolloutFile(threadId) {
25776
26226
  return null;
25777
26227
  }
25778
26228
  function findNewestClaudeCodeSession() {
25779
- const root = join54(homedir32(), ".claude", "projects");
25780
- if (!existsSync57(root))
26229
+ const root = join55(homedir33(), ".claude", "projects");
26230
+ if (!existsSync58(root))
25781
26231
  return null;
25782
26232
  let projectDirs;
25783
26233
  try {
25784
- projectDirs = readdirSync16(root);
26234
+ projectDirs = readdirSync17(root);
25785
26235
  } catch {
25786
26236
  return null;
25787
26237
  }
25788
26238
  let best = null;
25789
26239
  for (const dir of projectDirs) {
25790
- const projectPath = join54(root, dir);
26240
+ const projectPath = join55(root, dir);
25791
26241
  let files;
25792
26242
  try {
25793
- files = readdirSync16(projectPath);
26243
+ files = readdirSync17(projectPath);
25794
26244
  } catch {
25795
26245
  continue;
25796
26246
  }
25797
26247
  for (const file of files) {
25798
26248
  if (!file.endsWith(".jsonl"))
25799
26249
  continue;
25800
- const full = join54(projectPath, file);
26250
+ const full = join55(projectPath, file);
25801
26251
  try {
25802
26252
  const s = statSync10(full);
25803
26253
  if (!best || s.mtimeMs > best.mtime) {
@@ -25815,8 +26265,8 @@ function findNewestClaudeCodeSession() {
25815
26265
  return best ? { sessionId: best.sessionId, path: best.path } : null;
25816
26266
  }
25817
26267
  function findNewestCodexRollout() {
25818
- const root = join54(homedir32(), ".codex", "sessions");
25819
- if (!existsSync57(root))
26268
+ const root = join55(homedir33(), ".codex", "sessions");
26269
+ if (!existsSync58(root))
25820
26270
  return null;
25821
26271
  const stack = [root];
25822
26272
  let best = null;
@@ -25824,12 +26274,12 @@ function findNewestCodexRollout() {
25824
26274
  const dir = stack.pop();
25825
26275
  let entries;
25826
26276
  try {
25827
- entries = readdirSync16(dir);
26277
+ entries = readdirSync17(dir);
25828
26278
  } catch {
25829
26279
  continue;
25830
26280
  }
25831
26281
  for (const entry of entries) {
25832
- const full = join54(dir, entry);
26282
+ const full = join55(dir, entry);
25833
26283
  let s;
25834
26284
  try {
25835
26285
  s = statSync10(full);
@@ -26061,8 +26511,8 @@ init_store();
26061
26511
  init_client();
26062
26512
  init_resolve();
26063
26513
  import { Command as Command35 } from "commander";
26064
- import { readFileSync as readFileSync47, writeFileSync as writeFileSync31, existsSync as existsSync58, mkdtempSync as mkdtempSync4 } from "fs";
26065
- import { join as join55 } from "path";
26514
+ import { readFileSync as readFileSync48, writeFileSync as writeFileSync33, existsSync as existsSync59, mkdtempSync as mkdtempSync4 } from "fs";
26515
+ import { join as join56 } from "path";
26066
26516
  import { tmpdir as tmpdir4 } from "os";
26067
26517
  import { createHash as createHash6 } from "crypto";
26068
26518
 
@@ -26184,14 +26634,14 @@ function resolveLocalSessionShare(opts, conversation) {
26184
26634
  process.exit(1);
26185
26635
  }
26186
26636
  const title = opts.title ?? conversation.title ?? conversation.project;
26187
- const markdown = renderTranscriptMarkdown(readFileSync47(conversation.transcriptPath, "utf8"), family, title);
26637
+ const markdown = renderTranscriptMarkdown(readFileSync48(conversation.transcriptPath, "utf8"), family, title);
26188
26638
  if (!markdown) {
26189
26639
  console.error("Error: this conversation has no shareable content.");
26190
26640
  process.exit(1);
26191
26641
  }
26192
- const tempDir = mkdtempSync4(join55(tmpdir4(), "runwork-share-"));
26193
- const transcriptFile = join55(tempDir, "transcript.md");
26194
- writeFileSync31(transcriptFile, markdown);
26642
+ const tempDir = mkdtempSync4(join56(tmpdir4(), "runwork-share-"));
26643
+ const transcriptFile = join56(tempDir, "transcript.md");
26644
+ writeFileSync33(transcriptFile, markdown);
26195
26645
  opts.transcriptFile = transcriptFile;
26196
26646
  opts.nativeFile = opts.nativeFile ?? conversation.transcriptPath;
26197
26647
  opts.sourceAgent = opts.sourceAgent ?? conversation.agentSlug;
@@ -26228,7 +26678,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26228
26678
  console.error("Error: --transcript-file is required. Pass the path to the LLM-emitted markdown transcript.");
26229
26679
  process.exit(1);
26230
26680
  }
26231
- if (!existsSync58(opts.transcriptFile)) {
26681
+ if (!existsSync59(opts.transcriptFile)) {
26232
26682
  console.error(`Error: transcript file does not exist: ${opts.transcriptFile}`);
26233
26683
  process.exit(1);
26234
26684
  }
@@ -26249,7 +26699,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26249
26699
  const credentials = requireAuth();
26250
26700
  const client = new ApiClient(credentials);
26251
26701
  const { workspaceId } = await resolveWorkspace2(client, { workspace: opts.workspace });
26252
- const transcriptContent = readFileSync47(opts.transcriptFile, "utf8");
26702
+ const transcriptContent = readFileSync48(opts.transcriptFile, "utf8");
26253
26703
  const bundles = [
26254
26704
  {
26255
26705
  format: "transcript",
@@ -26262,19 +26712,19 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26262
26712
  const sourceAgent = opts.sourceAgent ?? detected?.slug ?? "generic";
26263
26713
  let nativeFilePath = null;
26264
26714
  if (opts.nativeFile) {
26265
- if (!existsSync58(opts.nativeFile)) {
26715
+ if (!existsSync59(opts.nativeFile)) {
26266
26716
  console.error(`Error: --native-file path does not exist: ${opts.nativeFile}`);
26267
26717
  process.exit(1);
26268
26718
  }
26269
26719
  nativeFilePath = opts.nativeFile;
26270
- } else if (detected?.sessionFilePath && existsSync58(detected.sessionFilePath)) {
26720
+ } else if (detected?.sessionFilePath && existsSync59(detected.sessionFilePath)) {
26271
26721
  nativeFilePath = detected.sessionFilePath;
26272
26722
  }
26273
26723
  if (nativeFilePath) {
26274
26724
  const nativeFormat = nativeBundleFormatForAgent(sourceAgent);
26275
26725
  if (nativeFormat) {
26276
26726
  try {
26277
- const content = readFileSync47(nativeFilePath, "utf8");
26727
+ const content = readFileSync48(nativeFilePath, "utf8");
26278
26728
  bundles.push({
26279
26729
  format: nativeFormat,
26280
26730
  content,
@@ -26290,7 +26740,7 @@ async function runShareConvo(opts, command, isPersonalAlias = false) {
26290
26740
  let metadata = {};
26291
26741
  if (opts.metadataFile) {
26292
26742
  try {
26293
- metadata = JSON.parse(readFileSync47(opts.metadataFile, "utf8"));
26743
+ metadata = JSON.parse(readFileSync48(opts.metadataFile, "utf8"));
26294
26744
  } catch (err) {
26295
26745
  console.error(`Error: --metadata-file is not valid JSON: ${err instanceof Error ? err.message : err}`);
26296
26746
  process.exit(1);
@@ -26399,9 +26849,9 @@ init_client();
26399
26849
  init_resolve();
26400
26850
  init_registry_data();
26401
26851
  import { Command as Command38 } from "commander";
26402
- import { writeFileSync as writeFileSync32, mkdirSync as mkdirSync28, realpathSync } from "fs";
26403
- import { homedir as homedir33 } from "os";
26404
- import { join as join56 } from "path";
26852
+ import { writeFileSync as writeFileSync34, mkdirSync as mkdirSync29, realpathSync } from "fs";
26853
+ import { homedir as homedir34 } from "os";
26854
+ import { join as join57 } from "path";
26405
26855
  import { spawn as spawn5 } from "child_process";
26406
26856
  init_registry();
26407
26857
  init_which();
@@ -26440,10 +26890,10 @@ function extractCodexUuid(rolloutContent) {
26440
26890
  }
26441
26891
  function placeClaudeJsonl(uuid, content, recipientCwd) {
26442
26892
  const encoded = encodeClaudeCodeCwd(recipientCwd);
26443
- const projectDir = join56(homedir33(), ".claude", "projects", encoded);
26444
- mkdirSync28(projectDir, { recursive: true });
26445
- const placedAt = join56(projectDir, `${uuid}.jsonl`);
26446
- writeFileSync32(placedAt, content);
26893
+ const projectDir = join57(homedir34(), ".claude", "projects", encoded);
26894
+ mkdirSync29(projectDir, { recursive: true });
26895
+ const placedAt = join57(projectDir, `${uuid}.jsonl`);
26896
+ writeFileSync34(placedAt, content);
26447
26897
  return { placedAt, runFromCwd: recipientCwd };
26448
26898
  }
26449
26899
  function placeCodexRollout(uuid, content) {
@@ -26451,11 +26901,11 @@ function placeCodexRollout(uuid, content) {
26451
26901
  const yyyy = String(now.getUTCFullYear());
26452
26902
  const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
26453
26903
  const dd = String(now.getUTCDate()).padStart(2, "0");
26454
- const dir = join56(homedir33(), ".codex", "sessions", yyyy, mm, dd);
26455
- mkdirSync28(dir, { recursive: true });
26904
+ const dir = join57(homedir34(), ".codex", "sessions", yyyy, mm, dd);
26905
+ mkdirSync29(dir, { recursive: true });
26456
26906
  const ts = now.toISOString().replace(/\.\d+Z$/, "").replace(/:/g, "-");
26457
- const placedAt = join56(dir, `rollout-${ts}-${uuid}.jsonl`);
26458
- writeFileSync32(placedAt, content);
26907
+ const placedAt = join57(dir, `rollout-${ts}-${uuid}.jsonl`);
26908
+ writeFileSync34(placedAt, content);
26459
26909
  return { placedAt };
26460
26910
  }
26461
26911
  function pickTargetAgent(opts, sourceAgent) {