opendevbrowser 0.0.25 → 0.0.27

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 (75) hide show
  1. package/README.md +7 -10
  2. package/dist/browser/canvas-manager.d.ts.map +1 -1
  3. package/dist/canvas/document-store.d.ts.map +1 -1
  4. package/dist/canvas/types.d.ts +3 -0
  5. package/dist/canvas/types.d.ts.map +1 -1
  6. package/dist/{chunk-7U63PZ4W.js → chunk-MWBDO2L5.js} +461 -189
  7. package/dist/chunk-MWBDO2L5.js.map +1 -0
  8. package/dist/{chunk-Z6ENAZUN.js → chunk-V5DJUSPV.js} +899 -115
  9. package/dist/chunk-V5DJUSPV.js.map +1 -0
  10. package/dist/cli/args.d.ts.map +1 -1
  11. package/dist/cli/commands/daemon.d.ts +27 -0
  12. package/dist/cli/commands/daemon.d.ts.map +1 -1
  13. package/dist/cli/commands/devtools/console-poll.d.ts.map +1 -1
  14. package/dist/cli/commands/devtools/network-poll.d.ts.map +1 -1
  15. package/dist/cli/commands/serve.d.ts +10 -13
  16. package/dist/cli/commands/serve.d.ts.map +1 -1
  17. package/dist/cli/commands/status.d.ts.map +1 -1
  18. package/dist/cli/commands/update.d.ts.map +1 -1
  19. package/dist/cli/daemon-autostart.d.ts +6 -2
  20. package/dist/cli/daemon-autostart.d.ts.map +1 -1
  21. package/dist/cli/daemon-client.d.ts.map +1 -1
  22. package/dist/cli/daemon-status-policy.d.ts +6 -0
  23. package/dist/cli/daemon-status-policy.d.ts.map +1 -0
  24. package/dist/cli/daemon-status.d.ts +1 -0
  25. package/dist/cli/daemon-status.d.ts.map +1 -1
  26. package/dist/cli/daemon.d.ts +5 -0
  27. package/dist/cli/daemon.d.ts.map +1 -1
  28. package/dist/cli/index.js +566 -154
  29. package/dist/cli/index.js.map +1 -1
  30. package/dist/cli/utils/http.d.ts.map +1 -1
  31. package/dist/cli/utils/parse.d.ts.map +1 -1
  32. package/dist/daemon-fingerprint.json +3 -0
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +109 -28
  35. package/dist/index.js.map +1 -1
  36. package/dist/inspiredesign/brief-expansion.d.ts +3 -0
  37. package/dist/inspiredesign/brief-expansion.d.ts.map +1 -1
  38. package/dist/{providers/inspiredesign-capture-mode.d.ts → inspiredesign/capture-mode.d.ts} +2 -2
  39. package/dist/inspiredesign/capture-mode.d.ts.map +1 -0
  40. package/dist/{providers/inspiredesign-capture.d.ts → inspiredesign/capture.d.ts} +3 -3
  41. package/dist/inspiredesign/capture.d.ts.map +1 -0
  42. package/dist/{providers/inspiredesign-contract.d.ts → inspiredesign/contract.d.ts} +18 -5
  43. package/dist/inspiredesign/contract.d.ts.map +1 -0
  44. package/dist/inspiredesign/handoff.d.ts +1 -11
  45. package/dist/inspiredesign/handoff.d.ts.map +1 -1
  46. package/dist/inspiredesign/reference-pattern-board.d.ts +73 -0
  47. package/dist/inspiredesign/reference-pattern-board.d.ts.map +1 -0
  48. package/dist/opendevbrowser.d.ts.map +1 -1
  49. package/dist/opendevbrowser.js +109 -28
  50. package/dist/opendevbrowser.js.map +1 -1
  51. package/dist/providers/renderer.d.ts +1 -1
  52. package/dist/providers/renderer.d.ts.map +1 -1
  53. package/dist/providers/workflows.d.ts +7 -5
  54. package/dist/providers/workflows.d.ts.map +1 -1
  55. package/dist/{providers-CYEJZVXB.js → providers-TR3DUJZV.js} +2 -2
  56. package/dist/public-surface/generated-manifest.d.ts +3 -3
  57. package/dist/public-surface/generated-manifest.d.ts.map +1 -1
  58. package/dist/public-surface/source.d.ts +5 -4
  59. package/dist/public-surface/source.d.ts.map +1 -1
  60. package/dist/relay/protocol.d.ts +14 -2
  61. package/dist/relay/protocol.d.ts.map +1 -1
  62. package/dist/tools/index.d.ts.map +1 -1
  63. package/dist/tools/status.d.ts.map +1 -1
  64. package/extension/dist/canvas/canvas-runtime.js +13 -6
  65. package/extension/dist/services/ConnectionManager.js +8 -4
  66. package/extension/manifest.json +1 -1
  67. package/package.json +1 -1
  68. package/skills/opendevbrowser-best-practices/assets/templates/skill-runtime-pack-matrix.json +1 -1
  69. package/skills/opendevbrowser-design-agent/assets/templates/inspiredesign-advanced-brief.v1.json +67 -31
  70. package/dist/chunk-7U63PZ4W.js.map +0 -1
  71. package/dist/chunk-Z6ENAZUN.js.map +0 -1
  72. package/dist/providers/inspiredesign-capture-mode.d.ts.map +0 -1
  73. package/dist/providers/inspiredesign-capture.d.ts.map +0 -1
  74. package/dist/providers/inspiredesign-contract.d.ts.map +0 -1
  75. /package/dist/{providers-CYEJZVXB.js.map → providers-TR3DUJZV.js.map} +0 -0
package/dist/cli/index.js CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  CLI_COMMANDS,
4
4
  CLI_COMMAND_HELP_DETAILS,
5
5
  DEFAULT_CLICK_TRANSPORT_TIMEOUT_MS,
6
+ DEFAULT_DAEMON_STATUS_FETCH_OPTIONS,
6
7
  DEFAULT_DIALOG_TRANSPORT_TIMEOUT_MS,
7
8
  DEFAULT_REVIEW_TRANSPORT_TIMEOUT_MS,
8
9
  DEFAULT_SCREENSHOT_TRANSPORT_TIMEOUT_MS,
@@ -19,6 +20,8 @@ import {
19
20
  VALID_FLAGS,
20
21
  buildAnnotateResult,
21
22
  callDaemon,
23
+ createDaemonStopHeaders,
24
+ createDisconnectedError,
22
25
  createOpenDevBrowserCore,
23
26
  createUsageError,
24
27
  extractExtension,
@@ -28,9 +31,9 @@ import {
28
31
  formatErrorPayload,
29
32
  generateSecureToken,
30
33
  getChromeUserDataRoots,
31
- getCurrentDaemonFingerprint,
32
34
  getExtensionPath,
33
35
  getProfileDirs,
36
+ isCurrentDaemonFingerprint,
34
37
  loadGlobalConfig,
35
38
  onboarding_metadata_default,
36
39
  readDaemonMetadata,
@@ -38,7 +41,7 @@ import {
38
41
  resolveExitCode,
39
42
  startDaemon,
40
43
  toCliError
41
- } from "../chunk-7U63PZ4W.js";
44
+ } from "../chunk-MWBDO2L5.js";
42
45
  import "../chunk-STGGGVYT.js";
43
46
  import {
44
47
  createNoOpSkillRemovalResult,
@@ -71,7 +74,7 @@ import {
71
74
  setDefaultLogSink,
72
75
  stderrSink,
73
76
  summarizePrimaryProviderIssue
74
- } from "../chunk-Z6ENAZUN.js";
77
+ } from "../chunk-V5DJUSPV.js";
75
78
  import "../chunk-FUSXMW3G.js";
76
79
 
77
80
  // src/cli/args.ts
@@ -140,6 +143,17 @@ function parseTransport(args) {
140
143
  }
141
144
  var VALID_FLAG_SET = new Set(VALID_FLAGS);
142
145
  var VALID_EQUALS_FLAG_SET = new Set(VALID_EQUALS_FLAGS);
146
+ var SIGNED_VALUE_FLAG_SET = /* @__PURE__ */ new Set(["--dy"]);
147
+ var SIGNED_INTEGER_VALUE = /^-\d+$/;
148
+ function shouldSkipValueToken(flag, value) {
149
+ if (!VALID_EQUALS_FLAG_SET.has(flag) || value === void 0) {
150
+ return false;
151
+ }
152
+ if (!value.startsWith("-")) {
153
+ return true;
154
+ }
155
+ return SIGNED_VALUE_FLAG_SET.has(flag) && SIGNED_INTEGER_VALUE.test(value);
156
+ }
143
157
  function parseArgs(argv) {
144
158
  let args = expandShortFlags(argv.slice(2));
145
159
  let commandOverride = null;
@@ -234,7 +248,8 @@ function parseArgs(argv) {
234
248
  } else if (noPrompt) {
235
249
  mode = "global";
236
250
  }
237
- for (const arg of args) {
251
+ for (let index = 0; index < args.length; index += 1) {
252
+ const arg = args[index] ?? "";
238
253
  if (arg.startsWith("--") && !VALID_FLAG_SET.has(arg)) {
239
254
  if (arg.includes("=")) {
240
255
  const baseFlag = arg.split("=", 2)[0] ?? "";
@@ -247,6 +262,9 @@ function parseArgs(argv) {
247
262
  if (arg.startsWith("-") && !arg.startsWith("--") && !SHORT_FLAGS[arg]) {
248
263
  throw createUsageError(`Unknown flag: ${arg}`);
249
264
  }
265
+ if (shouldSkipValueToken(arg, args[index + 1])) {
266
+ index += 1;
267
+ }
250
268
  }
251
269
  return {
252
270
  command: commandOverride ?? "install",
@@ -926,45 +944,292 @@ function installLocal(withConfig = false) {
926
944
  import * as fs4 from "fs";
927
945
  import * as path2 from "path";
928
946
  import * as os2 from "os";
947
+ import { randomUUID } from "crypto";
929
948
  var PLUGIN_NAME = "opendevbrowser";
949
+ var CACHE_MANIFEST = "package.json";
950
+ var CACHE_LOCKFILE = "package-lock.json";
951
+ var CACHE_UPDATE_LOCK = ".opendevbrowser-update.lock";
952
+ var CACHE_LOCK_STALE_MS = 30 * 60 * 1e3;
953
+ var DEPENDENCY_SECTIONS = [
954
+ "dependencies",
955
+ "devDependencies",
956
+ "optionalDependencies",
957
+ "peerDependencies"
958
+ ];
930
959
  function getCacheDir() {
931
960
  return process.env.OPENCODE_CACHE_DIR || path2.join(os2.homedir(), ".cache", "opencode");
932
961
  }
933
- function rmdir(dirPath) {
934
- const cacheDir = getCacheDir();
962
+ function assertCacheChild(targetPath, cacheDir) {
935
963
  const resolvedCache = path2.resolve(cacheDir);
936
- const resolvedPath = path2.resolve(dirPath);
937
- if (!resolvedPath.startsWith(resolvedCache + path2.sep) || resolvedPath === resolvedCache) {
938
- throw new Error(`Security: refusing to delete path outside cache directory: ${dirPath}`);
964
+ const resolvedPath = path2.resolve(targetPath);
965
+ const relativePath = path2.relative(resolvedCache, resolvedPath);
966
+ if (!relativePath || relativePath.startsWith("..") || path2.isAbsolute(relativePath)) {
967
+ throw new Error(`Security: refusing to modify path outside cache directory: ${targetPath}`);
968
+ }
969
+ }
970
+ function isMissingPathError(error) {
971
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
972
+ }
973
+ function isExistingPathError(error) {
974
+ return error instanceof Error && "code" in error && error.code === "EEXIST";
975
+ }
976
+ function assertNoSymlinkCachePath(targetPath, cacheDir) {
977
+ assertCacheChild(targetPath, cacheDir);
978
+ const resolvedCache = path2.resolve(cacheDir);
979
+ const relativeSegments = path2.relative(resolvedCache, path2.resolve(targetPath)).split(path2.sep).filter(Boolean);
980
+ for (const candidate of [resolvedCache, ...relativeSegments.map((_, index) => path2.join(resolvedCache, ...relativeSegments.slice(0, index + 1)))]) {
981
+ try {
982
+ if (fs4.lstatSync(candidate).isSymbolicLink()) {
983
+ throw new Error(`Security: refusing to modify symlinked cache path: ${candidate}`);
984
+ }
985
+ } catch (error) {
986
+ if (isMissingPathError(error)) {
987
+ continue;
988
+ }
989
+ throw error;
990
+ }
991
+ }
992
+ }
993
+ function cacheDirectoryExists(cacheDir) {
994
+ try {
995
+ const stat = fs4.lstatSync(cacheDir);
996
+ if (stat.isSymbolicLink()) {
997
+ throw new Error(`Security: refusing to modify symlinked cache path: ${cacheDir}`);
998
+ }
999
+ if (!stat.isDirectory()) {
1000
+ throw new Error(`${cacheDir} must be a directory`);
1001
+ }
1002
+ return true;
1003
+ } catch (error) {
1004
+ if (isMissingPathError(error)) {
1005
+ return false;
1006
+ }
1007
+ throw error;
1008
+ }
1009
+ }
1010
+ function removePathIfExists(targetPath, cacheDir) {
1011
+ assertNoSymlinkCachePath(targetPath, cacheDir);
1012
+ if (!fs4.existsSync(targetPath)) {
1013
+ return false;
1014
+ }
1015
+ fs4.rmSync(targetPath, { recursive: true, force: true });
1016
+ return true;
1017
+ }
1018
+ function cacheFileExists(targetPath, cacheDir) {
1019
+ assertNoSymlinkCachePath(targetPath, cacheDir);
1020
+ try {
1021
+ const stat = fs4.lstatSync(targetPath);
1022
+ if (stat.isSymbolicLink()) {
1023
+ throw new Error(`Security: refusing to modify symlinked cache manifest: ${targetPath}`);
1024
+ }
1025
+ return stat.isFile();
1026
+ } catch (error) {
1027
+ if (isMissingPathError(error)) {
1028
+ return false;
1029
+ }
1030
+ throw error;
1031
+ }
1032
+ }
1033
+ function writeManifestAtomic(manifestPath, cacheDir, manifest) {
1034
+ const tempPath = `${manifestPath}.${process.pid}.tmp`;
1035
+ assertNoSymlinkCachePath(tempPath, cacheDir);
1036
+ let fd = null;
1037
+ try {
1038
+ fd = fs4.openSync(tempPath, "wx");
1039
+ fs4.writeFileSync(fd, `${JSON.stringify(manifest, null, 2)}
1040
+ `, "utf8");
1041
+ fs4.closeSync(fd);
1042
+ fd = null;
1043
+ fs4.renameSync(tempPath, manifestPath);
1044
+ } catch (error) {
1045
+ if (fd !== null) {
1046
+ fs4.closeSync(fd);
1047
+ }
1048
+ fs4.rmSync(tempPath, { force: true });
1049
+ throw error;
939
1050
  }
940
- if (fs4.existsSync(dirPath)) {
941
- fs4.rmSync(dirPath, { recursive: true, force: true });
1051
+ }
1052
+ function withCacheMutationLock(cacheDir, action) {
1053
+ const lockPath = path2.join(cacheDir, CACHE_UPDATE_LOCK);
1054
+ assertNoSymlinkCachePath(lockPath, cacheDir);
1055
+ let lock = null;
1056
+ try {
1057
+ lock = openCacheMutationLock(lockPath);
1058
+ return action();
1059
+ } catch (error) {
1060
+ if (isExistingPathError(error)) {
1061
+ throw new Error("another update is already running for this OpenCode cache");
1062
+ }
1063
+ throw error;
1064
+ } finally {
1065
+ if (lock !== null) {
1066
+ fs4.closeSync(lock.fd);
1067
+ removeOwnedCacheMutationLock(lockPath, lock.token);
1068
+ }
942
1069
  }
943
1070
  }
1071
+ function openCacheMutationLock(lockPath) {
1072
+ try {
1073
+ return writeCacheMutationLock(lockPath);
1074
+ } catch (error) {
1075
+ if (!isExistingPathError(error) || !isStaleCacheMutationLock(lockPath)) {
1076
+ throw error;
1077
+ }
1078
+ removeStaleCacheMutationLock(lockPath);
1079
+ return writeCacheMutationLock(lockPath);
1080
+ }
1081
+ }
1082
+ function writeCacheMutationLock(lockPath) {
1083
+ const fd = fs4.openSync(lockPath, "wx");
1084
+ const token = randomUUID();
1085
+ try {
1086
+ const lock = { pid: process.pid, createdAt: Date.now(), token };
1087
+ fs4.writeFileSync(fd, `${JSON.stringify(lock)}
1088
+ `, "utf8");
1089
+ return { fd, token };
1090
+ } catch (error) {
1091
+ fs4.closeSync(fd);
1092
+ fs4.rmSync(lockPath, { force: true });
1093
+ throw error;
1094
+ }
1095
+ }
1096
+ function isStaleCacheMutationLock(lockPath) {
1097
+ const lock = readCacheMutationLock(lockPath);
1098
+ if (!lock) {
1099
+ return isLegacyCacheMutationLockStale(lockPath);
1100
+ }
1101
+ if (Date.now() - lock.createdAt < CACHE_LOCK_STALE_MS) {
1102
+ return false;
1103
+ }
1104
+ return !isProcessRunning(lock.pid);
1105
+ }
1106
+ function isLegacyCacheMutationLockStale(lockPath) {
1107
+ try {
1108
+ const pid = readLegacyCacheMutationLockPid(lockPath);
1109
+ if (pid !== null && isProcessRunning(pid)) {
1110
+ return false;
1111
+ }
1112
+ return Date.now() - fs4.statSync(lockPath).mtimeMs >= CACHE_LOCK_STALE_MS;
1113
+ } catch {
1114
+ return false;
1115
+ }
1116
+ }
1117
+ function readLegacyCacheMutationLockPid(lockPath) {
1118
+ const raw = fs4.readFileSync(lockPath, "utf8").trim();
1119
+ if (!/^\d+$/.test(raw)) {
1120
+ return null;
1121
+ }
1122
+ const pid = Number(raw);
1123
+ return Number.isSafeInteger(pid) && pid > 0 ? pid : null;
1124
+ }
1125
+ function readCacheMutationLock(lockPath) {
1126
+ try {
1127
+ const parsed = JSON.parse(fs4.readFileSync(lockPath, "utf8"));
1128
+ const { pid, createdAt, token } = parsed;
1129
+ const hasToken = token === void 0 || typeof token === "string" && token.length > 0;
1130
+ if (typeof pid === "number" && typeof createdAt === "number" && hasToken && Number.isInteger(pid) && Number.isFinite(createdAt)) {
1131
+ return token === void 0 ? { pid, createdAt } : { pid, createdAt, token };
1132
+ }
1133
+ } catch {
1134
+ return null;
1135
+ }
1136
+ return null;
1137
+ }
1138
+ function isProcessRunning(pid) {
1139
+ try {
1140
+ process.kill(pid, 0);
1141
+ return true;
1142
+ } catch (error) {
1143
+ return error instanceof Error && "code" in error && error.code === "EPERM";
1144
+ }
1145
+ }
1146
+ function createExistingLockError() {
1147
+ const error = new Error("cache update lock changed before stale cleanup");
1148
+ error.code = "EEXIST";
1149
+ return error;
1150
+ }
1151
+ function removeStaleCacheMutationLock(lockPath) {
1152
+ const content = fs4.readFileSync(lockPath, "utf8");
1153
+ if (!isStaleCacheMutationLock(lockPath) || fs4.readFileSync(lockPath, "utf8") !== content) {
1154
+ throw createExistingLockError();
1155
+ }
1156
+ fs4.rmSync(lockPath, { force: true });
1157
+ }
1158
+ function removeOwnedCacheMutationLock(lockPath, token) {
1159
+ const lock = readCacheMutationLock(lockPath);
1160
+ if (lock?.token === token) {
1161
+ fs4.rmSync(lockPath, { force: true });
1162
+ }
1163
+ }
1164
+ function preflightCacheMutationPaths(cacheDir, paths) {
1165
+ for (const targetPath of paths) {
1166
+ assertNoSymlinkCachePath(targetPath, cacheDir);
1167
+ }
1168
+ }
1169
+ function isJsonObject(value) {
1170
+ return value !== null && typeof value === "object" && !Array.isArray(value);
1171
+ }
1172
+ function removeDependencyPin(manifest, section) {
1173
+ const dependencies = manifest[section];
1174
+ if (!isJsonObject(dependencies) || !(PLUGIN_NAME in dependencies)) {
1175
+ return false;
1176
+ }
1177
+ delete dependencies[PLUGIN_NAME];
1178
+ if (Object.keys(dependencies).length === 0) {
1179
+ delete manifest[section];
1180
+ }
1181
+ return true;
1182
+ }
1183
+ function removeManifestPin(cacheDir) {
1184
+ const manifestPath = path2.join(cacheDir, CACHE_MANIFEST);
1185
+ if (!cacheFileExists(manifestPath, cacheDir)) {
1186
+ return false;
1187
+ }
1188
+ const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf8"));
1189
+ if (!isJsonObject(manifest)) {
1190
+ throw new Error(`${CACHE_MANIFEST} must contain a JSON object`);
1191
+ }
1192
+ const removed = DEPENDENCY_SECTIONS.map((section) => removeDependencyPin(manifest, section)).some(Boolean);
1193
+ if (removed) {
1194
+ writeManifestAtomic(manifestPath, cacheDir, manifest);
1195
+ }
1196
+ return removed;
1197
+ }
944
1198
  function runUpdate() {
945
1199
  const cacheDir = getCacheDir();
946
1200
  const nodeModulesDir = path2.join(cacheDir, "node_modules");
947
1201
  const pluginCacheDir = path2.join(nodeModulesDir, PLUGIN_NAME);
1202
+ const lockfilePath = path2.join(cacheDir, CACHE_LOCKFILE);
948
1203
  try {
949
- if (!fs4.existsSync(pluginCacheDir)) {
950
- if (fs4.existsSync(nodeModulesDir)) {
951
- rmdir(nodeModulesDir);
952
- return {
953
- success: true,
954
- message: "Cleared OpenCode plugin cache. The latest version will be installed on next run.",
955
- cleared: true
956
- };
957
- }
1204
+ if (!cacheDirectoryExists(cacheDir)) {
1205
+ return {
1206
+ success: true,
1207
+ message: "No cached plugin found. OpenCode will install the latest version on next run.",
1208
+ cleared: false
1209
+ };
1210
+ }
1211
+ preflightCacheMutationPaths(cacheDir, [
1212
+ path2.join(cacheDir, CACHE_MANIFEST),
1213
+ pluginCacheDir,
1214
+ lockfilePath,
1215
+ path2.join(cacheDir, CACHE_UPDATE_LOCK)
1216
+ ]);
1217
+ const cleared = withCacheMutationLock(cacheDir, () => {
1218
+ const manifestPinRemoved = removeManifestPin(cacheDir);
1219
+ const packageRemoved = removePathIfExists(pluginCacheDir, cacheDir);
1220
+ const lockfileRemoved = removePathIfExists(lockfilePath, cacheDir);
1221
+ return packageRemoved || manifestPinRemoved || lockfileRemoved;
1222
+ });
1223
+ if (!cleared) {
958
1224
  return {
959
1225
  success: true,
960
1226
  message: "No cached plugin found. OpenCode will install the latest version on next run.",
961
1227
  cleared: false
962
1228
  };
963
1229
  }
964
- rmdir(pluginCacheDir);
965
1230
  return {
966
1231
  success: true,
967
- message: "Cache cleared. OpenCode will install the latest version on next run.",
1232
+ message: "Cache repaired. OpenCode will install the latest version on next run.",
968
1233
  cleared: true
969
1234
  };
970
1235
  } catch (error) {
@@ -1053,7 +1318,24 @@ function findInstalledConfigs() {
1053
1318
  import { spawnSync } from "child_process";
1054
1319
 
1055
1320
  // src/cli/utils/parse.ts
1321
+ var SIGNED_INTEGER_PATTERN = /^-?\d+$/;
1322
+ var UNSIGNED_INTEGER_PATTERN = /^\d+$/;
1323
+ var SIGNED_DECIMAL_PATTERN = /^-?(?:\d+|\d+\.\d+|\.\d+)$/;
1324
+ var UNSIGNED_DECIMAL_PATTERN = /^(?:\d+|\d+\.\d+|\.\d+)$/;
1325
+ function allowsNegative(options) {
1326
+ return typeof options.min !== "number" || options.min < 0;
1327
+ }
1328
+ function decimalPattern(options) {
1329
+ const signed = allowsNegative(options);
1330
+ if (options.integer ?? true) {
1331
+ return signed ? SIGNED_INTEGER_PATTERN : UNSIGNED_INTEGER_PATTERN;
1332
+ }
1333
+ return signed ? SIGNED_DECIMAL_PATTERN : UNSIGNED_DECIMAL_PATTERN;
1334
+ }
1056
1335
  function parseNumberFlag(value, flag, options = {}) {
1336
+ if (value.trim() === "" || value !== value.trim() || !decimalPattern(options).test(value)) {
1337
+ throw createUsageError(`Invalid ${flag}: ${value}`);
1338
+ }
1057
1339
  const parsed = Number(value);
1058
1340
  if (!Number.isFinite(parsed)) {
1059
1341
  throw createUsageError(`Invalid ${flag}: ${value}`);
@@ -1526,7 +1808,15 @@ async function runNativeCommand(args) {
1526
1808
  // src/cli/commands/serve.ts
1527
1809
  var daemonHandle = null;
1528
1810
  var PS_MAX_BUFFER = 8 * 1024 * 1024;
1811
+ var DAEMON_SHUTDOWN_POLL_ATTEMPTS = 10;
1812
+ var DAEMON_SHUTDOWN_POLL_DELAY_MS = 100;
1813
+ var DAEMON_SHUTDOWN_STATUS_TIMEOUT_MS = 250;
1814
+ var DAEMON_STOP_TIMEOUT_MS = 1e3;
1815
+ var MIN_PORT = 1;
1816
+ var MAX_PORT = 65535;
1529
1817
  var SERVE_COMMAND_PATTERN = /(?:^|\s)(?:\S*[\\/])?(?:opendevbrowser|dist[\\/]+cli[\\/]+index\.js)(?=\s|$).*?\bserve\b/;
1818
+ var SERVE_PORT_SPLIT_PATTERN = /(?:^|\s)--port\s+(\d+)(?=\s|$)/;
1819
+ var SERVE_PORT_EQUALS_PATTERN = /(?:^|\s)--port=(\d+)(?=\s|$)/;
1530
1820
  var SERVE_STOP_PATTERN = /(?:^|\s)--stop(?:\s|$)/;
1531
1821
  var CURRENT_UID = typeof process.getuid === "function" ? process.getuid() : null;
1532
1822
  var CURRENT_EXECUTABLE = process.execPath;
@@ -1545,29 +1835,6 @@ async function resolveExistingDaemon(port, tokens) {
1545
1835
  function isPositivePid(value) {
1546
1836
  return typeof value === "number" && Number.isInteger(value) && value > 0;
1547
1837
  }
1548
- function rememberStalePid(staleDaemonPids, pid) {
1549
- if (isPositivePid(pid)) {
1550
- staleDaemonPids.add(pid);
1551
- }
1552
- }
1553
- async function stopDaemonOnPort(port, token) {
1554
- try {
1555
- const response = await fetchWithTimeout(`http://127.0.0.1:${port}/stop`, {
1556
- method: "POST",
1557
- headers: { Authorization: `Bearer ${token}` }
1558
- });
1559
- return response.ok;
1560
- } catch {
1561
- return false;
1562
- }
1563
- }
1564
- async function stopStaleDaemon(port, daemon, staleDaemonPids) {
1565
- rememberStalePid(staleDaemonPids, daemon.status.pid);
1566
- const stopped = await stopDaemonOnPort(port, daemon.token);
1567
- if (!stopped && isPositivePid(daemon.status.pid)) {
1568
- terminateProcess(daemon.status.pid);
1569
- }
1570
- }
1571
1838
  function parseServeArgs(rawArgs) {
1572
1839
  const parsed = { stop: false };
1573
1840
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -1581,7 +1848,7 @@ function parseServeArgs(rawArgs) {
1581
1848
  if (!value) {
1582
1849
  throw createUsageError("Missing value for --port");
1583
1850
  }
1584
- parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
1851
+ parsed.port = parseNumberFlag(value, "--port", { min: MIN_PORT, max: MAX_PORT });
1585
1852
  i += 1;
1586
1853
  continue;
1587
1854
  }
@@ -1590,7 +1857,7 @@ function parseServeArgs(rawArgs) {
1590
1857
  if (!value) {
1591
1858
  throw createUsageError("Missing value for --port");
1592
1859
  }
1593
- parsed.port = parseNumberFlag(value, "--port", { min: 1, max: 65535 });
1860
+ parsed.port = parseNumberFlag(value, "--port", { min: MIN_PORT, max: MAX_PORT });
1594
1861
  continue;
1595
1862
  }
1596
1863
  if (arg === "--token") {
@@ -1638,6 +1905,14 @@ function parseServeProcessSnapshot(line) {
1638
1905
  command
1639
1906
  };
1640
1907
  }
1908
+ function parseServeCommandPort(command) {
1909
+ const rawPort = command.match(SERVE_PORT_EQUALS_PATTERN)?.[1] ?? command.match(SERVE_PORT_SPLIT_PATTERN)?.[1];
1910
+ if (!rawPort) {
1911
+ return null;
1912
+ }
1913
+ const port = Number.parseInt(rawPort, 10);
1914
+ return Number.isInteger(port) && port >= MIN_PORT && port <= MAX_PORT ? port : null;
1915
+ }
1641
1916
  function listServeProcessSnapshots() {
1642
1917
  const result = spawnSync("ps", ["-axww", "-o", "pid=,uid=,command="], {
1643
1918
  encoding: "utf-8",
@@ -1660,6 +1935,9 @@ function isCurrentExecutableServeProcess(snapshot) {
1660
1935
  }
1661
1936
  return !SERVE_STOP_PATTERN.test(snapshot.command);
1662
1937
  }
1938
+ function isRequestedPortServeProcess(snapshot, requestedPort) {
1939
+ return isCurrentExecutableServeProcess(snapshot) && parseServeCommandPort(snapshot.command) === requestedPort;
1940
+ }
1663
1941
  function terminateProcess(pid) {
1664
1942
  if (!Number.isInteger(pid) || pid <= 0 || pid === process.pid || pid === process.ppid) {
1665
1943
  return false;
@@ -1675,9 +1953,9 @@ function terminateProcess(pid) {
1675
1953
  }
1676
1954
  return true;
1677
1955
  }
1678
- function cleanupCompetingServeProcesses(keepPid) {
1956
+ function cleanupCompetingServeProcesses(requestedPort, keepPid) {
1679
1957
  const candidates = listServeProcessSnapshots().filter((snapshot) => {
1680
- if (!isCurrentExecutableServeProcess(snapshot)) {
1958
+ if (!isRequestedPortServeProcess(snapshot, requestedPort)) {
1681
1959
  return false;
1682
1960
  }
1683
1961
  if (snapshot.pid === process.pid || snapshot.pid === process.ppid) {
@@ -1699,6 +1977,85 @@ function cleanupCompetingServeProcesses(keepPid) {
1699
1977
  }
1700
1978
  return clearedPids;
1701
1979
  }
1980
+ function terminateServeProcessByPid(pid) {
1981
+ if (!isPositivePid(pid)) {
1982
+ return false;
1983
+ }
1984
+ const snapshot = listServeProcessSnapshots().find((item) => item.pid === pid);
1985
+ return snapshot ? isCurrentExecutableServeProcess(snapshot) && terminateProcess(pid) : false;
1986
+ }
1987
+ function buildStaleStopMessage(metadata) {
1988
+ const pid = isPositivePid(metadata.pid) ? ` pid=${metadata.pid}` : "";
1989
+ return `Daemon rejected stale stop request for 127.0.0.1:${metadata.port}${pid}. Run \`opendevbrowser status --daemon\` to inspect the active daemon, then restart from the current install if needed.`;
1990
+ }
1991
+ function buildProtectedMismatchMessage(port, status) {
1992
+ return `Daemon on 127.0.0.1:${port} pid=${status.pid} is protected by a different opendevbrowser build. Run \`opendevbrowser status --daemon\` to inspect it, then restart from the current install.`;
1993
+ }
1994
+ async function waitForDaemonShutdown(port, token) {
1995
+ for (let attempt = 0; attempt < DAEMON_SHUTDOWN_POLL_ATTEMPTS; attempt += 1) {
1996
+ const status = await fetchDaemonStatus(port, token, { timeoutMs: DAEMON_SHUTDOWN_STATUS_TIMEOUT_MS });
1997
+ if (!status?.ok) {
1998
+ return true;
1999
+ }
2000
+ await new Promise((resolve5) => setTimeout(resolve5, DAEMON_SHUTDOWN_POLL_DELAY_MS));
2001
+ }
2002
+ return false;
2003
+ }
2004
+ async function stopMismatchedDaemon(port, daemon) {
2005
+ let response;
2006
+ try {
2007
+ response = await fetchWithTimeout(`http://127.0.0.1:${port}/stop`, {
2008
+ method: "POST",
2009
+ headers: createDaemonStopHeaders(daemon.token, "serve.upgrade")
2010
+ }, DAEMON_STOP_TIMEOUT_MS);
2011
+ } catch (error) {
2012
+ const status = await fetchDaemonStatus(port, daemon.token, {
2013
+ timeoutMs: DAEMON_SHUTDOWN_STATUS_TIMEOUT_MS
2014
+ });
2015
+ if (!status?.ok) {
2016
+ return null;
2017
+ }
2018
+ const message = error instanceof Error ? error.message : String(error);
2019
+ return `Failed to stop mismatched daemon on 127.0.0.1:${port}: ${message}.`;
2020
+ }
2021
+ if (response.status === 409) {
2022
+ return buildProtectedMismatchMessage(port, daemon.status);
2023
+ }
2024
+ if (!response.ok) {
2025
+ return `Failed to stop mismatched daemon on 127.0.0.1:${port}: stop returned ${response.status}.`;
2026
+ }
2027
+ if (await waitForDaemonShutdown(port, daemon.token)) {
2028
+ return null;
2029
+ }
2030
+ if (terminateServeProcessByPid(daemon.status.pid)) {
2031
+ return null;
2032
+ }
2033
+ return `Timed out waiting for mismatched daemon on 127.0.0.1:${port} to stop.`;
2034
+ }
2035
+ async function prepareExistingDaemon(port, daemon) {
2036
+ if (isCurrentDaemonFingerprint(daemon.status.fingerprint)) {
2037
+ return null;
2038
+ }
2039
+ return await stopMismatchedDaemon(port, daemon);
2040
+ }
2041
+ function buildAlreadyRunningResult(port, status, fallbackRelayPort, clearedCount) {
2042
+ const relayPort = status.relay.port ?? fallbackRelayPort;
2043
+ const staleNote = clearedCount > 0 ? `
2044
+ Cleared ${clearedCount} stale daemon process${clearedCount === 1 ? "" : "es"}.` : "";
2045
+ return {
2046
+ success: true,
2047
+ message: `Daemon already running on 127.0.0.1:${port} (pid=${status.pid}, relay ${relayPort}).${staleNote}`,
2048
+ data: {
2049
+ port,
2050
+ pid: status.pid,
2051
+ relayPort,
2052
+ alreadyRunning: true,
2053
+ staleDaemonsCleared: clearedCount,
2054
+ relay: status.relay
2055
+ },
2056
+ exitCode: null
2057
+ };
2058
+ }
1702
2059
  async function runServe(args) {
1703
2060
  const serveArgs = parseServeArgs(args.rawArgs);
1704
2061
  if (serveArgs.stop) {
@@ -1714,8 +2071,11 @@ async function runServe(args) {
1714
2071
  try {
1715
2072
  const response = await fetchWithTimeout(`http://127.0.0.1:${metadata2.port}/stop`, {
1716
2073
  method: "POST",
1717
- headers: { Authorization: `Bearer ${metadata2.token}` }
2074
+ headers: createDaemonStopHeaders(metadata2.token, "serve.stop")
1718
2075
  });
2076
+ if (response.status === 409) {
2077
+ return { success: false, message: buildStaleStopMessage(metadata2), exitCode: EXIT_EXECUTION };
2078
+ }
1719
2079
  if (!response.ok) {
1720
2080
  throw new Error(`Stop failed (${response.status})`);
1721
2081
  }
@@ -1730,34 +2090,23 @@ async function runServe(args) {
1730
2090
  const metadata = readDaemonMetadata();
1731
2091
  const metadataToken = metadata?.port === requestedPort ? metadata.token : void 0;
1732
2092
  const tokenCandidates = resolveTokenCandidates(serveArgs.token, metadataToken, config.daemonToken);
1733
- const currentFingerprint = getCurrentDaemonFingerprint();
1734
2093
  const existingDaemon = await resolveExistingDaemon(requestedPort, tokenCandidates);
1735
- const staleDaemonPids = new Set(cleanupCompetingServeProcesses(existingDaemon?.status.pid));
2094
+ const staleDaemonPids = /* @__PURE__ */ new Set();
1736
2095
  const staleCleared = () => staleDaemonPids.size;
1737
- let replacedStaleFingerprint = false;
1738
2096
  if (existingDaemon) {
1739
- const fingerprintMatches = existingDaemon.status.fingerprint === currentFingerprint;
1740
- if (fingerprintMatches) {
1741
- const relayPort = existingDaemon.status.relay.port ?? config.relayPort;
1742
- const clearedCount2 = staleCleared();
1743
- const staleNote2 = clearedCount2 > 0 ? `
1744
- Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.` : "";
1745
- return {
1746
- success: true,
1747
- message: `Daemon already running on 127.0.0.1:${requestedPort} (pid=${existingDaemon.status.pid}, relay ${relayPort}).${staleNote2}`,
1748
- data: {
1749
- port: requestedPort,
1750
- pid: existingDaemon.status.pid,
1751
- relayPort,
1752
- alreadyRunning: true,
1753
- staleDaemonsCleared: clearedCount2,
1754
- relay: existingDaemon.status.relay
1755
- },
1756
- exitCode: null
1757
- };
2097
+ const mismatchMessage = await prepareExistingDaemon(requestedPort, existingDaemon);
2098
+ if (mismatchMessage) {
2099
+ return { success: false, message: mismatchMessage, exitCode: EXIT_EXECUTION };
2100
+ }
2101
+ if (isCurrentDaemonFingerprint(existingDaemon.status.fingerprint)) {
2102
+ for (const pid of cleanupCompetingServeProcesses(requestedPort, existingDaemon.status.pid)) {
2103
+ staleDaemonPids.add(pid);
2104
+ }
2105
+ return buildAlreadyRunningResult(requestedPort, existingDaemon.status, config.relayPort, staleCleared());
1758
2106
  }
1759
- await stopStaleDaemon(requestedPort, existingDaemon, staleDaemonPids);
1760
- replacedStaleFingerprint = true;
2107
+ }
2108
+ for (const pid of cleanupCompetingServeProcesses(requestedPort)) {
2109
+ staleDaemonPids.add(pid);
1761
2110
  }
1762
2111
  let nativeStatus = getNativeStatusSnapshot();
1763
2112
  let nativeMessage = null;
@@ -1800,35 +2149,17 @@ Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.
1800
2149
  }
1801
2150
  const runningDaemon = await resolveExistingDaemon(requestedPort, tokenCandidates);
1802
2151
  if (runningDaemon) {
1803
- const fingerprintMatches = runningDaemon.status.fingerprint === currentFingerprint;
1804
- if (fingerprintMatches) {
1805
- const relayPort = runningDaemon.status.relay.port ?? config.relayPort;
1806
- const clearedCount2 = staleCleared();
1807
- const staleNote2 = clearedCount2 > 0 ? `
1808
- Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.` : "";
1809
- return {
1810
- success: true,
1811
- message: `Daemon already running on 127.0.0.1:${requestedPort} (pid=${runningDaemon.status.pid}, relay ${relayPort}).${staleNote2}`,
1812
- data: {
1813
- port: requestedPort,
1814
- pid: runningDaemon.status.pid,
1815
- relayPort,
1816
- alreadyRunning: true,
1817
- staleDaemonsCleared: clearedCount2,
1818
- relay: runningDaemon.status.relay
1819
- },
1820
- exitCode: null
1821
- };
2152
+ const mismatchMessage = await prepareExistingDaemon(requestedPort, runningDaemon);
2153
+ if (mismatchMessage) {
2154
+ return { success: false, message: mismatchMessage, exitCode: EXIT_EXECUTION };
1822
2155
  }
1823
- await stopStaleDaemon(requestedPort, runningDaemon, staleDaemonPids);
1824
- replacedStaleFingerprint = true;
1825
- if (attempt === 0) {
1826
- continue;
2156
+ if (isCurrentDaemonFingerprint(runningDaemon.status.fingerprint)) {
2157
+ return buildAlreadyRunningResult(requestedPort, runningDaemon.status, config.relayPort, staleCleared());
1827
2158
  }
1828
2159
  }
1829
2160
  if (attempt === 0) {
1830
2161
  let clearedNewPid = false;
1831
- for (const pid of cleanupCompetingServeProcesses()) {
2162
+ for (const pid of cleanupCompetingServeProcesses(requestedPort)) {
1832
2163
  const previousSize = staleDaemonPids.size;
1833
2164
  staleDaemonPids.add(pid);
1834
2165
  if (staleDaemonPids.size > previousSize) {
@@ -1863,9 +2194,8 @@ Cleared ${clearedCount2} stale daemon process${clearedCount2 === 1 ? "" : "es"}.
1863
2194
  const clearedCount = staleCleared();
1864
2195
  const staleNote = clearedCount > 0 ? `
1865
2196
  Cleared ${clearedCount} stale daemon process${clearedCount === 1 ? "" : "es"}.` : "";
1866
- const fingerprintNote = replacedStaleFingerprint ? "\nReplaced stale daemon fingerprint." : "";
1867
2197
  const message = nativeMessage ? `${baseMessage}
1868
- ${nativeMessage}${fingerprintNote}${staleNote}` : `${baseMessage}${fingerprintNote}${staleNote}`;
2198
+ ${nativeMessage}${staleNote}` : `${baseMessage}${staleNote}`;
1869
2199
  return {
1870
2200
  success: true,
1871
2201
  message,
@@ -1876,9 +2206,9 @@ ${nativeMessage}${fingerprintNote}${staleNote}` : `${baseMessage}${fingerprintNo
1876
2206
 
1877
2207
  // src/cli/daemon-autostart.ts
1878
2208
  import { execFileSync as execFileSync2 } from "child_process";
1879
- import { existsSync as existsSync5, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync4 } from "fs";
2209
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, statSync as statSync2, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
1880
2210
  import { homedir as homedir4, tmpdir } from "os";
1881
- import { dirname as dirname3, isAbsolute, join as join5, relative, resolve as resolve3 } from "path";
2211
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join5, relative as relative2, resolve as resolve3 } from "path";
1882
2212
  import { fileURLToPath as fileURLToPath2 } from "url";
1883
2213
  var MAC_LABEL = "com.opendevbrowser.daemon";
1884
2214
  var WIN_TASK_NAME = "OpenDevBrowser Daemon";
@@ -1891,7 +2221,8 @@ var defaultDeps = () => ({
1891
2221
  homedir: homedir4,
1892
2222
  existsSync: existsSync5,
1893
2223
  mkdirSync: mkdirSync2,
1894
- writeFileSync: writeFileSync4,
2224
+ statSync: statSync2,
2225
+ writeFileSync: writeFileSync5,
1895
2226
  unlinkSync: unlinkSync2,
1896
2227
  execFileSync: execFileSync2,
1897
2228
  transientEntrypointRoots: void 0
@@ -1900,6 +2231,17 @@ var NPX_CACHE_SEGMENT_PATTERN = /[\\/]_npx(?:[\\/]|$)/;
1900
2231
  var formatCommand = (programArguments) => {
1901
2232
  return programArguments.map((value) => `"${value}"`).join(" ");
1902
2233
  };
2234
+ var resolveMacWorkingDirectory = (home) => {
2235
+ return join5(home, ".cache", "opendevbrowser");
2236
+ };
2237
+ var pathIsDirectory = (path5, deps) => {
2238
+ if (!deps.existsSync(path5)) return false;
2239
+ try {
2240
+ return deps.statSync(path5).isDirectory();
2241
+ } catch {
2242
+ return false;
2243
+ }
2244
+ };
1903
2245
  var resolveCliPathFromModule = (moduleUrl, exists) => {
1904
2246
  const modulePath = fileURLToPath2(moduleUrl);
1905
2247
  const candidate = resolve3(dirname3(modulePath), "..", "index.js");
@@ -1915,8 +2257,8 @@ var normalizeComparisonPath = (value, platform) => {
1915
2257
  var isPathInsideRoot = (candidate, root, platform) => {
1916
2258
  const normalizedCandidate = normalizeComparisonPath(candidate, platform);
1917
2259
  const normalizedRoot = normalizeComparisonPath(root, platform);
1918
- const relation = relative(normalizedRoot, normalizedCandidate);
1919
- return relation === "" || !relation.startsWith("..") && !isAbsolute(relation);
2260
+ const relation = relative2(normalizedRoot, normalizedCandidate);
2261
+ return relation === "" || !relation.startsWith("..") && !isAbsolute2(relation);
1920
2262
  };
1921
2263
  var getTransientEntrypointRoots = (deps) => {
1922
2264
  const configuredRoots = deps.transientEntrypointRoots;
@@ -1962,18 +2304,20 @@ var resolveCliEntrypoint = (deps = {}) => {
1962
2304
  var getLaunchAgentPath = (home = homedir4()) => {
1963
2305
  return join5(home, "Library", "LaunchAgents", `${MAC_LABEL}.plist`);
1964
2306
  };
2307
+ var escapePlistString = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
1965
2308
  var buildLaunchAgentPlist = (entrypoint, options = {}) => {
1966
2309
  const label = options.label ?? MAC_LABEL;
1967
2310
  const stdoutPath = options.stdoutPath ?? join5(homedir4(), "Library", "Logs", "opendevbrowser-daemon.log");
1968
2311
  const stderrPath = options.stderrPath ?? join5(homedir4(), "Library", "Logs", "opendevbrowser-daemon.err.log");
1969
- const programArgs = [entrypoint.nodePath, ...entrypoint.args].map((value) => ` <string>${value}</string>`).join("\n");
2312
+ const workingDirectory = options.workingDirectory ?? resolveMacWorkingDirectory(homedir4());
2313
+ const programArgs = [entrypoint.nodePath, ...entrypoint.args].map((value) => ` <string>${escapePlistString(value)}</string>`).join("\n");
1970
2314
  return [
1971
2315
  '<?xml version="1.0" encoding="UTF-8"?>',
1972
2316
  '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
1973
2317
  '<plist version="1.0">',
1974
2318
  "<dict>",
1975
2319
  ` <key>Label</key>`,
1976
- ` <string>${label}</string>`,
2320
+ ` <string>${escapePlistString(label)}</string>`,
1977
2321
  " <key>ProgramArguments</key>",
1978
2322
  " <array>",
1979
2323
  programArgs,
@@ -1982,10 +2326,12 @@ var buildLaunchAgentPlist = (entrypoint, options = {}) => {
1982
2326
  " <true/>",
1983
2327
  " <key>KeepAlive</key>",
1984
2328
  " <true/>",
2329
+ " <key>WorkingDirectory</key>",
2330
+ ` <string>${escapePlistString(workingDirectory)}</string>`,
1985
2331
  " <key>StandardOutPath</key>",
1986
- ` <string>${stdoutPath}</string>`,
2332
+ ` <string>${escapePlistString(stdoutPath)}</string>`,
1987
2333
  " <key>StandardErrorPath</key>",
1988
- ` <string>${stderrPath}</string>`,
2334
+ ` <string>${escapePlistString(stderrPath)}</string>`,
1989
2335
  "</dict>",
1990
2336
  "</plist>",
1991
2337
  ""
@@ -2077,6 +2423,7 @@ var readMacLaunchAgentProgramArguments = (plistPath, deps) => {
2077
2423
  const text = deps.execFileSync("plutil", ["-convert", "json", "-o", "-", plistPath], { encoding: "utf-8" });
2078
2424
  const parsed = JSON.parse(text);
2079
2425
  const programArguments = parsed?.ProgramArguments;
2426
+ const workingDirectory = typeof parsed?.WorkingDirectory === "string" ? parsed.WorkingDirectory : void 0;
2080
2427
  if (!Array.isArray(programArguments) || programArguments.length < 2 || programArguments.some((value) => typeof value !== "string")) {
2081
2428
  return { ok: false, reason: "missing_program_arguments" };
2082
2429
  }
@@ -2084,7 +2431,8 @@ var readMacLaunchAgentProgramArguments = (plistPath, deps) => {
2084
2431
  return {
2085
2432
  ok: true,
2086
2433
  command: formatCommand(commandArgs),
2087
- programArguments: commandArgs
2434
+ programArguments: commandArgs,
2435
+ ...workingDirectory ? { workingDirectory } : {}
2088
2436
  };
2089
2437
  } catch {
2090
2438
  return { ok: false, reason: "malformed_plist" };
@@ -2100,11 +2448,13 @@ var classifyMacAutostartStatus = (entrypoint, location, deps) => {
2100
2448
  });
2101
2449
  }
2102
2450
  const parsed = readMacLaunchAgentProgramArguments(location, deps);
2451
+ const expectedWorkingDirectory = resolveMacWorkingDirectory(deps.homedir());
2103
2452
  if (!parsed.ok) {
2104
2453
  return createMacAutostartStatus(entrypoint, location, {
2105
2454
  installed: true,
2106
2455
  health: "malformed",
2107
2456
  needsRepair: true,
2457
+ expectedWorkingDirectory,
2108
2458
  reason: parsed.reason
2109
2459
  });
2110
2460
  }
@@ -2119,15 +2469,41 @@ var classifyMacAutostartStatus = (entrypoint, location, deps) => {
2119
2469
  health: "needs_repair",
2120
2470
  needsRepair: true,
2121
2471
  command: parsed.command,
2472
+ workingDirectory: parsed.workingDirectory,
2473
+ expectedWorkingDirectory,
2122
2474
  reason: actualStatus.reason
2123
2475
  });
2124
2476
  }
2477
+ if (parsed.workingDirectory !== expectedWorkingDirectory) {
2478
+ return createMacAutostartStatus(entrypoint, location, {
2479
+ installed: true,
2480
+ health: "needs_repair",
2481
+ needsRepair: true,
2482
+ command: parsed.command,
2483
+ workingDirectory: parsed.workingDirectory,
2484
+ expectedWorkingDirectory,
2485
+ reason: "working_directory_mismatch"
2486
+ });
2487
+ }
2488
+ if (!pathIsDirectory(expectedWorkingDirectory, deps)) {
2489
+ return createMacAutostartStatus(entrypoint, location, {
2490
+ installed: true,
2491
+ health: "needs_repair",
2492
+ needsRepair: true,
2493
+ command: parsed.command,
2494
+ workingDirectory: parsed.workingDirectory,
2495
+ expectedWorkingDirectory,
2496
+ reason: "working_directory_mismatch"
2497
+ });
2498
+ }
2125
2499
  if (entrypoint.isTransient) {
2126
2500
  return createMacAutostartStatus(entrypoint, location, {
2127
2501
  installed: true,
2128
2502
  health: "healthy",
2129
2503
  needsRepair: false,
2130
- command: parsed.command
2504
+ command: parsed.command,
2505
+ workingDirectory: parsed.workingDirectory,
2506
+ expectedWorkingDirectory
2131
2507
  });
2132
2508
  }
2133
2509
  const mismatchReason = classifyExpectedProgramArguments(expectedArgs, actualArgs, deps);
@@ -2137,6 +2513,8 @@ var classifyMacAutostartStatus = (entrypoint, location, deps) => {
2137
2513
  health: "needs_repair",
2138
2514
  needsRepair: true,
2139
2515
  command: parsed.command,
2516
+ workingDirectory: parsed.workingDirectory,
2517
+ expectedWorkingDirectory,
2140
2518
  reason: mismatchReason
2141
2519
  });
2142
2520
  }
@@ -2144,7 +2522,9 @@ var classifyMacAutostartStatus = (entrypoint, location, deps) => {
2144
2522
  installed: true,
2145
2523
  health: "healthy",
2146
2524
  needsRepair: false,
2147
- command: parsed.command
2525
+ command: parsed.command,
2526
+ workingDirectory: parsed.workingDirectory,
2527
+ expectedWorkingDirectory
2148
2528
  });
2149
2529
  };
2150
2530
  var runCommand = (exec, command, args, ignoreFailure = false) => {
@@ -2172,12 +2552,14 @@ var installMacAutostart = (deps = {}) => {
2172
2552
  const plistPath = getLaunchAgentPath(home);
2173
2553
  const stdoutPath = join5(home, "Library", "Logs", "opendevbrowser-daemon.log");
2174
2554
  const stderrPath = join5(home, "Library", "Logs", "opendevbrowser-daemon.err.log");
2555
+ const workingDirectory = resolveMacWorkingDirectory(home);
2175
2556
  const logsDir = dirname3(stdoutPath);
2176
2557
  resolved.mkdirSync(dirname3(plistPath), { recursive: true });
2177
2558
  resolved.mkdirSync(logsDir, { recursive: true });
2559
+ resolved.mkdirSync(workingDirectory, { recursive: true });
2178
2560
  resolved.writeFileSync(
2179
2561
  plistPath,
2180
- buildLaunchAgentPlist(entrypoint, { stdoutPath, stderrPath }),
2562
+ buildLaunchAgentPlist(entrypoint, { stdoutPath, stderrPath, workingDirectory }),
2181
2563
  { encoding: "utf-8" }
2182
2564
  );
2183
2565
  const uid = resolved.uid;
@@ -2189,7 +2571,9 @@ var installMacAutostart = (deps = {}) => {
2189
2571
  installed: true,
2190
2572
  health: "healthy",
2191
2573
  needsRepair: false,
2192
- command: entrypoint.command
2574
+ command: entrypoint.command,
2575
+ workingDirectory,
2576
+ expectedWorkingDirectory: workingDirectory
2193
2577
  });
2194
2578
  };
2195
2579
  var uninstallMacAutostart = (deps = {}) => {
@@ -2433,17 +2817,40 @@ var parseDaemonArgs = (rawArgs) => {
2433
2817
  var stopDaemonIfRunning = async () => {
2434
2818
  const metadata = readDaemonMetadata();
2435
2819
  if (!metadata) {
2436
- return false;
2820
+ return { outcome: "not_running" };
2437
2821
  }
2438
2822
  try {
2439
2823
  const response = await fetchWithTimeout(`http://127.0.0.1:${metadata.port}/stop`, {
2440
2824
  method: "POST",
2441
- headers: { Authorization: `Bearer ${metadata.token}` }
2825
+ headers: createDaemonStopHeaders(metadata.token, "daemon.uninstall")
2442
2826
  });
2443
- return response.ok;
2444
- } catch {
2827
+ if (response.status === 409) {
2828
+ return { outcome: "fingerprint_rejected", pid: metadata.pid, port: metadata.port };
2829
+ }
2830
+ return response.ok ? { outcome: "stopped", pid: metadata.pid, port: metadata.port } : { outcome: "failed", pid: metadata.pid, port: metadata.port, status: response.status };
2831
+ } catch (error) {
2832
+ return {
2833
+ outcome: "failed",
2834
+ pid: metadata.pid,
2835
+ port: metadata.port,
2836
+ error: error instanceof Error ? error.message : String(error)
2837
+ };
2838
+ }
2839
+ };
2840
+ var buildStopFailureMessage = (stop) => {
2841
+ const target = stop.port ? `127.0.0.1:${stop.port}` : "recorded daemon";
2842
+ const pid = stop.pid ? ` pid=${stop.pid}` : "";
2843
+ if (stop.outcome === "fingerprint_rejected") {
2844
+ return `Daemon autostart removed, but the running daemon at ${target}${pid} rejected the stop request as stale. Run \`opendevbrowser status --daemon\` to inspect it and restart from the current install if needed.`;
2845
+ }
2846
+ const reason = stop.error ?? (stop.status ? `HTTP ${stop.status}` : "unknown error");
2847
+ return `Daemon autostart removed, but stopping ${target}${pid} failed (${reason}).`;
2848
+ };
2849
+ var shouldFailUninstallStop = (stop) => {
2850
+ if (stop.outcome === "stopped" || stop.outcome === "not_running") {
2445
2851
  return false;
2446
2852
  }
2853
+ return true;
2447
2854
  };
2448
2855
  var formatReason = (reason) => {
2449
2856
  return reason ? reason.replace(/_/g, " ") : "unknown reason";
@@ -2518,7 +2925,15 @@ async function runDaemonCommand(args) {
2518
2925
  exitCode: EXIT_EXECUTION
2519
2926
  };
2520
2927
  }
2521
- await stopDaemonIfRunning();
2928
+ const stop = await stopDaemonIfRunning();
2929
+ if (shouldFailUninstallStop(stop)) {
2930
+ return {
2931
+ success: false,
2932
+ message: buildStopFailureMessage(stop),
2933
+ data: { ...result, stop },
2934
+ exitCode: EXIT_EXECUTION
2935
+ };
2936
+ }
2522
2937
  return {
2523
2938
  success: true,
2524
2939
  message: `Daemon autostart removed (${result.platform}).`,
@@ -2526,7 +2941,7 @@ async function runDaemonCommand(args) {
2526
2941
  };
2527
2942
  }
2528
2943
  const autostart = getAutostartStatus();
2529
- const daemonStatus = await fetchDaemonStatusFromMetadata();
2944
+ const daemonStatus = await fetchDaemonStatusFromMetadata(void 0, DEFAULT_DAEMON_STATUS_FETCH_OPTIONS);
2530
2945
  const running = Boolean(daemonStatus);
2531
2946
  const message = buildStatusMessage(autostart, running);
2532
2947
  const data = {
@@ -2750,7 +3165,7 @@ function formatAutostartReconciliationMessage(result) {
2750
3165
  }
2751
3166
 
2752
3167
  // src/cli/commands/run.ts
2753
- import { readFileSync as readFileSync2 } from "fs";
3168
+ import { readFileSync as readFileSync3 } from "fs";
2754
3169
 
2755
3170
  // src/cli/output.ts
2756
3171
  var normalizeExitCode = (code) => {
@@ -2914,7 +3329,7 @@ async function runScriptCommand(args) {
2914
3329
  const outputOptions = { format: args.outputFormat, quiet: args.quiet };
2915
3330
  let scriptRaw = "";
2916
3331
  if (runArgs.scriptPath) {
2917
- scriptRaw = readFileSync2(runArgs.scriptPath, "utf-8");
3332
+ scriptRaw = readFileSync3(runArgs.scriptPath, "utf-8");
2918
3333
  } else if (!process.stdin.isTTY) {
2919
3334
  scriptRaw = await readScriptFromStdin();
2920
3335
  } else {
@@ -3574,11 +3989,6 @@ async function runSessionStatus(args) {
3574
3989
  }
3575
3990
 
3576
3991
  // src/cli/commands/status.ts
3577
- var DAEMON_STATUS_READ_OPTIONS = {
3578
- timeoutMs: 5e3,
3579
- retryAttempts: 5,
3580
- retryDelayMs: 250
3581
- };
3582
3992
  var parseStatusArgs2 = (rawArgs) => {
3583
3993
  const parsed = { daemon: false };
3584
3994
  for (let i = 0; i < rawArgs.length; i += 1) {
@@ -3619,21 +4029,23 @@ async function runStatus(args) {
3619
4029
  exitCode: assessment.exitCode ?? void 0
3620
4030
  };
3621
4031
  }
3622
- const daemonStatus = await fetchDaemonStatusFromMetadata(void 0, DAEMON_STATUS_READ_OPTIONS);
4032
+ const daemonStatus = await fetchDaemonStatusFromMetadata(void 0, DEFAULT_DAEMON_STATUS_FETCH_OPTIONS);
3623
4033
  if (!daemonStatus) {
3624
- throw createUsageError("Daemon not running. Start with `opendevbrowser serve`.");
4034
+ throw createDisconnectedError("Daemon not running. Start with `opendevbrowser serve`.");
3625
4035
  }
3626
4036
  const nativeStatus = getNativeStatusSnapshot();
3627
4037
  const nativeAssessment = assessNativeStatus(nativeStatus);
4038
+ const fingerprintLine = daemonStatus.fingerprintCurrent === false ? "Daemon fingerprint: mismatch with current build" : "Daemon fingerprint: current";
3628
4039
  const baseLines = [
3629
4040
  `Daemon OK (pid=${daemonStatus.pid})`,
4041
+ fingerprintLine,
3630
4042
  `Relay: port=${daemonStatus.relay.port ?? "n/a"} ext=${daemonStatus.relay.extensionConnected ? "on" : "off"} handshake=${daemonStatus.relay.extensionHandshakeComplete ? "on" : "off"} cdp=${daemonStatus.relay.cdpConnected ? "on" : "off"} annotate=${daemonStatus.relay.annotationConnected ? "on" : "off"} ops=${daemonStatus.relay.opsConnected ? "on" : "off"} canvas=${daemonStatus.relay.canvasConnected ? "on" : "off"} pairing=${daemonStatus.relay.pairingRequired ? "on" : "off"} health=${daemonStatus.relay.health?.reason ?? "n/a"}`,
3631
4043
  `Native: ${nativeAssessment.summary}`,
3632
4044
  daemonStatus.relay.lastHandshakeError ? `Relay last handshake error: ${daemonStatus.relay.lastHandshakeError.code} (${daemonStatus.relay.lastHandshakeError.message})` : "Relay last handshake error: none",
3633
4045
  "Legend: ext=extension websocket, handshake=extension handshake, cdp=active /cdp client, annotate=annotation channel, ops=ops clients, canvas=canvas clients, pairing=token required, health=relay status"
3634
4046
  ];
3635
4047
  if (!nativeAssessment.success) {
3636
- baseLines.splice(3, 0, `Native detail: ${nativeAssessment.message}`);
4048
+ baseLines.splice(4, 0, `Native detail: ${nativeAssessment.message}`);
3637
4049
  }
3638
4050
  const baseMessage = baseLines.join("\n");
3639
4051
  const message = daemon || args.outputFormat !== "text" ? baseMessage : [
@@ -3850,12 +4262,12 @@ function parseSnapshotArgs(rawArgs) {
3850
4262
  if (arg === "--max-chars") {
3851
4263
  const value = rawArgs[i + 1];
3852
4264
  if (!value) throw createUsageError("Missing value for --max-chars");
3853
- parsed.maxChars = Number(value);
4265
+ parsed.maxChars = parseNumberFlag(value, "--max-chars", { min: 1 });
3854
4266
  i += 1;
3855
4267
  continue;
3856
4268
  }
3857
4269
  if (arg?.startsWith("--max-chars=")) {
3858
- parsed.maxChars = Number(arg.split("=", 2)[1]);
4270
+ parsed.maxChars = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--max-chars", { min: 1 });
3859
4271
  continue;
3860
4272
  }
3861
4273
  if (arg === "--cursor") {
@@ -4114,7 +4526,7 @@ async function runAnnotate(args) {
4114
4526
  }
4115
4527
 
4116
4528
  // src/cli/commands/canvas.ts
4117
- import { readFileSync as readFileSync3 } from "fs";
4529
+ import { readFileSync as readFileSync4 } from "fs";
4118
4530
  var DEFAULT_FEEDBACK_STREAM_TIMEOUT_MS = 3e4;
4119
4531
  var isRecord = (value) => {
4120
4532
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -4315,7 +4727,7 @@ var resolveCanvasParams = (canvasArgs) => {
4315
4727
  if (hasParamsFile) {
4316
4728
  let raw = "";
4317
4729
  try {
4318
- raw = readFileSync3(canvasArgs.paramsFile ?? "", "utf8");
4730
+ raw = readFileSync4(canvasArgs.paramsFile ?? "", "utf8");
4319
4731
  } catch (error) {
4320
4732
  const message = error instanceof Error ? error.message : "Unable to read file";
4321
4733
  throw createUsageError(`Invalid --params-file: ${message}`);
@@ -4384,7 +4796,7 @@ async function runCanvas(args) {
4384
4796
  }
4385
4797
 
4386
4798
  // src/cli/commands/rpc.ts
4387
- import { readFileSync as readFileSync4 } from "fs";
4799
+ import { readFileSync as readFileSync5 } from "fs";
4388
4800
  var requireValue4 = (value, flag) => {
4389
4801
  if (!value) throw createUsageError(`Missing value for ${flag}`);
4390
4802
  return value;
@@ -4464,7 +4876,7 @@ var resolveRpcParams = (rpcArgs) => {
4464
4876
  if (hasParamsFile) {
4465
4877
  let raw = "";
4466
4878
  try {
4467
- raw = readFileSync4(rpcArgs.paramsFile ?? "", "utf8");
4879
+ raw = readFileSync5(rpcArgs.paramsFile ?? "", "utf8");
4468
4880
  } catch (error) {
4469
4881
  const message = error instanceof Error ? error.message : "Unable to read file";
4470
4882
  throw createUsageError(`Invalid --params-file: ${message}`);
@@ -4895,12 +5307,12 @@ function parseScrollArgs(rawArgs) {
4895
5307
  if (arg === "--dy") {
4896
5308
  const value = rawArgs[i + 1];
4897
5309
  if (!value) throw createUsageError("Missing value for --dy");
4898
- parsed.dy = Number(value);
5310
+ parsed.dy = parseNumberFlag(value, "--dy");
4899
5311
  i += 1;
4900
5312
  continue;
4901
5313
  }
4902
5314
  if (arg?.startsWith("--dy=")) {
4903
- parsed.dy = Number(arg.split("=", 2)[1]);
5315
+ parsed.dy = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--dy");
4904
5316
  continue;
4905
5317
  }
4906
5318
  }
@@ -5396,12 +5808,12 @@ function parseDomHtmlArgs(rawArgs) {
5396
5808
  if (arg === "--max-chars") {
5397
5809
  const value = rawArgs[i + 1];
5398
5810
  if (!value) throw createUsageError("Missing value for --max-chars");
5399
- parsed.maxChars = Number(value);
5811
+ parsed.maxChars = parseNumberFlag(value, "--max-chars", { min: 1 });
5400
5812
  i += 1;
5401
5813
  continue;
5402
5814
  }
5403
5815
  if (arg?.startsWith("--max-chars=")) {
5404
- parsed.maxChars = Number(arg.split("=", 2)[1]);
5816
+ parsed.maxChars = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--max-chars", { min: 1 });
5405
5817
  continue;
5406
5818
  }
5407
5819
  }
@@ -5451,12 +5863,12 @@ function parseDomTextArgs(rawArgs) {
5451
5863
  if (arg === "--max-chars") {
5452
5864
  const value = rawArgs[i + 1];
5453
5865
  if (!value) throw createUsageError("Missing value for --max-chars");
5454
- parsed.maxChars = Number(value);
5866
+ parsed.maxChars = parseNumberFlag(value, "--max-chars", { min: 1 });
5455
5867
  i += 1;
5456
5868
  continue;
5457
5869
  }
5458
5870
  if (arg?.startsWith("--max-chars=")) {
5459
- parsed.maxChars = Number(arg.split("=", 2)[1]);
5871
+ parsed.maxChars = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--max-chars", { min: 1 });
5460
5872
  continue;
5461
5873
  }
5462
5874
  }
@@ -5971,23 +6383,23 @@ function parseConsolePollArgs(rawArgs) {
5971
6383
  if (arg === "--since-seq") {
5972
6384
  const value = rawArgs[i + 1];
5973
6385
  if (!value) throw createUsageError("Missing value for --since-seq");
5974
- parsed.sinceSeq = Number(value);
6386
+ parsed.sinceSeq = parseNumberFlag(value, "--since-seq", { min: 0 });
5975
6387
  i += 1;
5976
6388
  continue;
5977
6389
  }
5978
6390
  if (arg?.startsWith("--since-seq=")) {
5979
- parsed.sinceSeq = Number(arg.split("=", 2)[1]);
6391
+ parsed.sinceSeq = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--since-seq", { min: 0 });
5980
6392
  continue;
5981
6393
  }
5982
6394
  if (arg === "--max") {
5983
6395
  const value = rawArgs[i + 1];
5984
6396
  if (!value) throw createUsageError("Missing value for --max");
5985
- parsed.max = Number(value);
6397
+ parsed.max = parseNumberFlag(value, "--max", { min: 1 });
5986
6398
  i += 1;
5987
6399
  continue;
5988
6400
  }
5989
6401
  if (arg?.startsWith("--max=")) {
5990
- parsed.max = Number(arg.split("=", 2)[1]);
6402
+ parsed.max = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--max", { min: 1 });
5991
6403
  continue;
5992
6404
  }
5993
6405
  }
@@ -6019,23 +6431,23 @@ function parseNetworkPollArgs(rawArgs) {
6019
6431
  if (arg === "--since-seq") {
6020
6432
  const value = rawArgs[i + 1];
6021
6433
  if (!value) throw createUsageError("Missing value for --since-seq");
6022
- parsed.sinceSeq = Number(value);
6434
+ parsed.sinceSeq = parseNumberFlag(value, "--since-seq", { min: 0 });
6023
6435
  i += 1;
6024
6436
  continue;
6025
6437
  }
6026
6438
  if (arg?.startsWith("--since-seq=")) {
6027
- parsed.sinceSeq = Number(arg.split("=", 2)[1]);
6439
+ parsed.sinceSeq = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--since-seq", { min: 0 });
6028
6440
  continue;
6029
6441
  }
6030
6442
  if (arg === "--max") {
6031
6443
  const value = rawArgs[i + 1];
6032
6444
  if (!value) throw createUsageError("Missing value for --max");
6033
- parsed.max = Number(value);
6445
+ parsed.max = parseNumberFlag(value, "--max", { min: 1 });
6034
6446
  i += 1;
6035
6447
  continue;
6036
6448
  }
6037
6449
  if (arg?.startsWith("--max=")) {
6038
- parsed.max = Number(arg.split("=", 2)[1]);
6450
+ parsed.max = parseNumberFlag(arg.split("=", 2)[1] ?? "", "--max", { min: 1 });
6039
6451
  continue;
6040
6452
  }
6041
6453
  }
@@ -6247,7 +6659,7 @@ async function runDesktopAccessibilitySnapshot(args) {
6247
6659
  }
6248
6660
 
6249
6661
  // src/cli/commands/session/cookie-import.ts
6250
- import { readFileSync as readFileSync5 } from "fs";
6662
+ import { readFileSync as readFileSync6 } from "fs";
6251
6663
  var requireValue5 = (value, flag) => {
6252
6664
  if (!value) {
6253
6665
  throw createUsageError(`Missing value for ${flag}`);
@@ -6362,7 +6774,7 @@ var resolveCookies = (parsed) => {
6362
6774
  }
6363
6775
  let raw = "";
6364
6776
  try {
6365
- raw = readFileSync5(parsed.cookiesFile ?? "", "utf8");
6777
+ raw = readFileSync6(parsed.cookiesFile ?? "", "utf8");
6366
6778
  } catch (error) {
6367
6779
  const message = error instanceof Error ? error.message : "Unable to read file";
6368
6780
  throw createUsageError(`Invalid --cookies-file: ${message}`);
@@ -7415,7 +7827,7 @@ async function runInspiredesignCommand(args) {
7415
7827
  // package.json
7416
7828
  var package_default = {
7417
7829
  name: "opendevbrowser",
7418
- version: "0.0.25",
7830
+ version: "0.0.27",
7419
7831
  description: "Browser automation runtime with snapshot-refs-actions, browser replay screencasts, public read-only desktop observation, and browser-scoped computer-use orchestration",
7420
7832
  type: "module",
7421
7833
  main: "dist/index.js",
@@ -7749,7 +8161,7 @@ async function main() {
7749
8161
  });
7750
8162
  registerCommand({
7751
8163
  name: "update",
7752
- description: "Clear cached plugin and refresh managed skill packs",
8164
+ description: "Repair cached plugin pins and refresh managed skill packs",
7753
8165
  run: () => buildUpdateCommandResult(args, runUpdate(), {
7754
8166
  resolveUpdateSkillModes,
7755
8167
  hasInstalledConfig,