@scalequality/cli 0.4.25 → 0.4.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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "sdkVersion": "0.3.281",
3
- "sha256": "c4e5e374b92cb3ee3c9a0c197613d4f57edfc87d920a3b3583f80477389a72d6",
4
- "sourceCommit": "f0e1250e989d1546b868486f528e19ac0e6c47d7"
3
+ "sha256": "17180f5819c1bc15eb3673f2b233c0ed2bfec058b6e5c61f664c1e31a814d8a5",
4
+ "sourceCommit": "2d39889d75ad2d495e1d185212f15d0113581f52"
5
5
  }
package/dist/connect.cjs CHANGED
@@ -28,9 +28,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
28
28
  ));
29
29
 
30
30
  // src/main/workspace-connect.ts
31
- var import_fs11 = require("fs");
32
- var import_os4 = require("os");
33
- var import_path17 = require("path");
31
+ var import_fs12 = require("fs");
32
+ var import_os5 = require("os");
33
+ var import_path18 = require("path");
34
34
  var import_url = require("url");
35
35
 
36
36
  // src/application/services/workspaceSandbox/importers.ts
@@ -1125,7 +1125,8 @@ var WRITE_TOOLS = { Write: "file_path", Edit: "file_path", MultiEdit: "file_path
1125
1125
  var IMAGE_TOOL = "generate_image";
1126
1126
  var WEB_SEARCH_TOOL = "web_search";
1127
1127
  var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp"]);
1128
- var WRITE_TOOL_PATHS = { ...WRITE_TOOLS, [`mcp__${"scalequality"}__${IMAGE_TOOL}`]: "file_path" };
1128
+ var SCREENSHOT_TOOL = "screenshot_app";
1129
+ var WRITE_TOOL_PATHS = { ...WRITE_TOOLS, [`mcp__${"scalequality"}__${IMAGE_TOOL}`]: "file_path", [`mcp__${"scalequality"}__${SCREENSHOT_TOOL}`]: "file_path" };
1129
1130
  var HARMLESS = /* @__PURE__ */ new Set(["TodoWrite", "BashOutput", "KillShell", "TaskStop", "TaskOutput", "Skill"]);
1130
1131
  var SUBAGENT_TOOLS = /* @__PURE__ */ new Set(["Agent", "Task"]);
1131
1132
  var SQ_READ_ONLY = /* @__PURE__ */ new Set([
@@ -1260,6 +1261,16 @@ async function imageToolDecision(input, ctx) {
1260
1261
  if (decided.behavior !== "allow") return decided;
1261
1262
  return { behavior: "allow", updatedInput: { ...input, file_path: (0, import_path3.resolve)(ctx.root, raw.trim()) } };
1262
1263
  }
1264
+ async function screenshotToolDecision(input, ctx) {
1265
+ const url = typeof input.url === "string" ? input.url.trim() : "";
1266
+ if (!/^http:\/\/(localhost|127\.0\.0\.1)(:\d{2,5})?(\/[^\s]*)?$/i.test(url)) return { behavior: "deny", message: "screenshot_app opens only the app you started here: http://localhost:<port>/... (never another site)." };
1267
+ const raw = input.file_path;
1268
+ if (typeof raw !== "string" || !raw.trim()) return { behavior: "deny", message: "screenshot_app needs file_path: where to save the .png in the folder (for example screenshots/hud.png)." };
1269
+ if ((0, import_path3.extname)(raw).toLowerCase() !== ".png") return { behavior: "deny", message: "screenshot_app saves .png files: give file_path that extension." };
1270
+ const decided = await decideToolUse("Write", { file_path: raw }, { ...ctx, plan: false, planDir: void 0 });
1271
+ if (decided.behavior !== "allow") return decided;
1272
+ return { behavior: "allow", updatedInput: { ...input, url, file_path: (0, import_path3.resolve)(ctx.root, raw.trim()) } };
1273
+ }
1263
1274
  async function decideToolUse(toolName, input, ctx) {
1264
1275
  if (toolName.startsWith("mcp__") && ctx.orgMcpTools?.all.has(toolName)) {
1265
1276
  if (ctx.plan && !ctx.orgMcpTools.readOnly.has(toolName)) return { behavior: "deny", message: PLAN_ONLY_READS };
@@ -1269,6 +1280,7 @@ async function decideToolUse(toolName, input, ctx) {
1269
1280
  if (!toolName.startsWith(SQ_MCP_PREFIX)) return { behavior: "deny", message: "Only ScaleQuality tools and the MCP servers your organization approved are available in this workspace." };
1270
1281
  if (ctx.plan && !SQ_READ_ONLY.has(toolName.slice(SQ_MCP_PREFIX.length))) return { behavior: "deny", message: PLAN_ONLY_READS };
1271
1282
  if (toolName === SQ_MCP_PREFIX + IMAGE_TOOL) return imageToolDecision(input, ctx);
1283
+ if (toolName === SQ_MCP_PREFIX + SCREENSHOT_TOOL) return screenshotToolDecision(input, ctx);
1272
1284
  return { behavior: "allow", updatedInput: input };
1273
1285
  }
1274
1286
  if (HARMLESS.has(toolName)) return { behavior: "allow", updatedInput: input };
@@ -4917,11 +4929,11 @@ async function addFolder(credentials2, api, path, home) {
4917
4929
  }
4918
4930
 
4919
4931
  // src/application/services/workspaceSandbox/WorkspaceEngine.ts
4920
- var import_child_process3 = require("child_process");
4921
- var import_promises14 = require("fs/promises");
4922
- var import_fs9 = require("fs");
4932
+ var import_child_process4 = require("child_process");
4933
+ var import_promises15 = require("fs/promises");
4934
+ var import_fs10 = require("fs");
4923
4935
  var import_crypto8 = require("crypto");
4924
- var import_path15 = require("path");
4936
+ var import_path16 = require("path");
4925
4937
 
4926
4938
  // src/application/services/execution/LanguageAdapter.ts
4927
4939
  var import_async_hooks = require("async_hooks");
@@ -5041,6 +5053,57 @@ var ApprovalBroker = class {
5041
5053
  }
5042
5054
  };
5043
5055
 
5056
+ // src/application/services/workspaceSandbox/appScreenshot.ts
5057
+ var import_child_process3 = require("child_process");
5058
+ var import_fs7 = require("fs");
5059
+ var import_promises9 = require("fs/promises");
5060
+ var import_os4 = require("os");
5061
+ var import_path9 = require("path");
5062
+ var LOCAL_APP_URL = /^http:\/\/(localhost|127\.0\.0\.1)(:\d{2,5})?(\/[^\s]*)?$/i;
5063
+ function chromeCandidates(env = process.env, platform2 = process.platform) {
5064
+ const own = env.SQ_CHROME_PATH ? [env.SQ_CHROME_PATH] : [];
5065
+ if (platform2 === "darwin") return [
5066
+ ...own,
5067
+ "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
5068
+ "/Applications/Chromium.app/Contents/MacOS/Chromium",
5069
+ "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
5070
+ "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser"
5071
+ ];
5072
+ if (platform2 === "win32") {
5073
+ const roots = [env.PROGRAMFILES, env["PROGRAMFILES(X86)"], env.LOCALAPPDATA].filter((r) => !!r);
5074
+ return [...own, ...roots.flatMap((r) => [(0, import_path9.join)(r, "Google", "Chrome", "Application", "chrome.exe"), (0, import_path9.join)(r, "Microsoft", "Edge", "Application", "msedge.exe")])];
5075
+ }
5076
+ return [...own, "/usr/bin/chromium", "/usr/bin/chromium-browser", "/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/snap/bin/chromium"];
5077
+ }
5078
+ function findChrome(candidates = chromeCandidates()) {
5079
+ return candidates.find((path) => (0, import_fs7.existsSync)(path)) ?? null;
5080
+ }
5081
+ async function screenshotPage(chrome, url, target, size) {
5082
+ const profile = await (0, import_promises9.mkdtemp)((0, import_path9.join)((0, import_os4.tmpdir)(), "sq-shot-"));
5083
+ try {
5084
+ await new Promise((resolve8, reject) => {
5085
+ (0, import_child_process3.execFile)(chrome, [
5086
+ "--headless=new",
5087
+ "--disable-gpu",
5088
+ "--no-sandbox",
5089
+ "--disable-dev-shm-usage",
5090
+ "--hide-scrollbars",
5091
+ "--no-first-run",
5092
+ "--no-default-browser-check",
5093
+ `--user-data-dir=${profile}`,
5094
+ `--window-size=${size.width},${size.height}`,
5095
+ "--virtual-time-budget=8000",
5096
+ `--screenshot=${target}`,
5097
+ url
5098
+ ], { timeout: 45e3, windowsHide: true }, (error) => error ? reject(error) : resolve8());
5099
+ });
5100
+ const shot = await (0, import_promises9.stat)(target).catch(() => null);
5101
+ if (!shot || !shot.size) throw new Error("no screenshot was written");
5102
+ } finally {
5103
+ await (0, import_promises9.rm)(profile, { recursive: true, force: true }).catch(() => void 0);
5104
+ }
5105
+ }
5106
+
5044
5107
  // src/application/services/workspaceSandbox/engineMessages.ts
5045
5108
  var ENGINE_MESSAGES = {
5046
5109
  // Start and preparation.
@@ -9515,6 +9578,20 @@ function buildScaleQualityServer(sdk, host) {
9515
9578
  async (args) => host.openPullRequest(String(args.title ?? ""), String(args.body ?? ""), typeof args.repoFullName === "string" ? args.repoFullName : void 0)
9516
9579
  )
9517
9580
  );
9581
+ if (host.screenshotApp) {
9582
+ const shoot = host.screenshotApp.bind(host);
9583
+ tools.push(sdk.tool(
9584
+ SCREENSHOT_TOOL,
9585
+ "Take a screenshot of the app you started on this machine (a dev server on http://localhost:<port>) and save it as a PNG in the workspace; then Read the PNG to see it and check your work, and show it to the user as a Markdown image when it helps. Use it after building or changing a screen, instead of guessing how it looks.",
9586
+ {
9587
+ url: external_exports.string().min(1).max(500).describe("The app address, http://localhost:<port>/<path>."),
9588
+ file_path: external_exports.string().min(1).max(500).describe("Where to save it in the workspace, ending in .png (for example screenshots/hud.png)."),
9589
+ width: external_exports.number().int().min(200).max(2560).optional().describe("Viewport width, default 1280."),
9590
+ height: external_exports.number().int().min(200).max(2560).optional().describe("Viewport height, default 800.")
9591
+ },
9592
+ async (args) => shoot({ url: String(args.url ?? ""), filePath: String(args.file_path ?? ""), ...typeof args.width === "number" ? { width: args.width } : {}, ...typeof args.height === "number" ? { height: args.height } : {} })
9593
+ ));
9594
+ }
9518
9595
  if (host.searchWeb) {
9519
9596
  const search = host.searchWeb.bind(host);
9520
9597
  tools.push(sdk.tool(
@@ -9546,24 +9623,24 @@ function buildScaleQualityServer(sdk, host) {
9546
9623
  }
9547
9624
 
9548
9625
  // src/application/services/workspaceSandbox/projectConventions.ts
9549
- var import_promises9 = require("fs/promises");
9550
- var import_path9 = require("path");
9626
+ var import_promises10 = require("fs/promises");
9627
+ var import_path10 = require("path");
9551
9628
  var CONVENTION_FILES = ["CLAUDE.md", "AGENTS.md", ".claude/CLAUDE.md"];
9552
9629
  var CONVENTION_CAPS = { perFileBytes: 24 * 1024, totalBytes: 64 * 1024 };
9553
9630
  async function readConventions(repos, caps = CONVENTION_CAPS) {
9554
9631
  const out2 = [];
9555
9632
  let total = 0;
9556
9633
  for (const repo2 of repos) {
9557
- const realRoot = await (0, import_promises9.realpath)(repo2.root).catch(() => null);
9634
+ const realRoot = await (0, import_promises10.realpath)(repo2.root).catch(() => null);
9558
9635
  if (!realRoot) continue;
9559
9636
  for (const name of CONVENTION_FILES) {
9560
9637
  if (total >= caps.totalBytes) return out2;
9561
- const abs = (0, import_path9.join)(repo2.root, name);
9562
- const st = await (0, import_promises9.lstat)(abs).catch(() => null);
9638
+ const abs = (0, import_path10.join)(repo2.root, name);
9639
+ const st = await (0, import_promises10.lstat)(abs).catch(() => null);
9563
9640
  if (!st?.isFile()) continue;
9564
- const real = await (0, import_promises9.realpath)(abs).catch(() => null);
9565
- if (!real || (0, import_path9.relative)(realRoot, real).startsWith("..") || (0, import_path9.relative)(realRoot, real).split(import_path9.sep).includes("..")) continue;
9566
- const buf = await (0, import_promises9.readFile)(abs).catch(() => null);
9641
+ const real = await (0, import_promises10.realpath)(abs).catch(() => null);
9642
+ if (!real || (0, import_path10.relative)(realRoot, real).startsWith("..") || (0, import_path10.relative)(realRoot, real).split(import_path10.sep).includes("..")) continue;
9643
+ const buf = await (0, import_promises10.readFile)(abs).catch(() => null);
9567
9644
  if (!buf || buf.includes(0)) continue;
9568
9645
  const room = Math.min(caps.perFileBytes, caps.totalBytes - total);
9569
9646
  let text3 = buf.toString("utf8");
@@ -9589,7 +9666,7 @@ ${f.text.replace(/<\/project_conventions>/g, "")}
9589
9666
  }
9590
9667
 
9591
9668
  // src/application/services/workspaceSandbox/sdkEventMapper.ts
9592
- var import_path10 = require("path");
9669
+ var import_path11 = require("path");
9593
9670
  var TERMINAL_TAIL_BYTES = 64 * 1024;
9594
9671
  var FILE_CHANGING = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "Write", "NotebookEdit", "Bash", "Agent", "Task"]);
9595
9672
  var SUBAGENT_NAMES = /* @__PURE__ */ new Set(["Agent", "Task"]);
@@ -9619,6 +9696,7 @@ var SQ_TOOL_LABELS = {
9619
9696
  measure_change: { kind: "measure", label: "Measuring the change" },
9620
9697
  generate_image: { kind: "tool", label: "Creating an image" },
9621
9698
  web_search: { kind: "search", label: "Searching the web" },
9699
+ screenshot_app: { kind: "tool", label: "Taking a screenshot of the app" },
9622
9700
  open_pull_request: { kind: "tool", label: "Opening the pull request" }
9623
9701
  };
9624
9702
  function clip(s, n) {
@@ -9973,8 +10051,8 @@ function describeTool(name, input, root) {
9973
10051
  const s = (k) => typeof input[k] === "string" ? input[k] : "";
9974
10052
  const rel = (p) => {
9975
10053
  if (!p) return "";
9976
- if (!(0, import_path10.isAbsolute)(p)) return p;
9977
- const r = (0, import_path10.relative)(root, p);
10054
+ if (!(0, import_path11.isAbsolute)(p)) return p;
10055
+ const r = (0, import_path11.relative)(root, p);
9978
10056
  return r && !r.startsWith("..") ? r : p;
9979
10057
  };
9980
10058
  switch (name) {
@@ -9988,10 +10066,10 @@ function describeTool(name, input, root) {
9988
10066
  return { kind: "search", label: `Searching for "${clip(s("pattern"), 80)}"`, detail: s("pattern"), code: "STEP_SEARCH", params: { pattern: clip(s("pattern"), 80) } };
9989
10067
  case "Edit":
9990
10068
  case "MultiEdit":
9991
- if (/[\\/]plans[\\/][^\\/]+\.md$/.test(s("file_path")) && (0, import_path10.isAbsolute)(rel(s("file_path")))) return { kind: "think", label: "Writing the plan", code: "STEP_PLAN_WRITE" };
10069
+ if (/[\\/]plans[\\/][^\\/]+\.md$/.test(s("file_path")) && (0, import_path11.isAbsolute)(rel(s("file_path")))) return { kind: "think", label: "Writing the plan", code: "STEP_PLAN_WRITE" };
9992
10070
  return { kind: "edit", label: `Editing ${rel(s("file_path"))}`, detail: rel(s("file_path")), code: "STEP_EDIT", params: { path: rel(s("file_path")) } };
9993
10071
  case "Write":
9994
- if (/[\\/]plans[\\/][^\\/]+\.md$/.test(s("file_path")) && (0, import_path10.isAbsolute)(rel(s("file_path")))) return { kind: "think", label: "Writing the plan", code: "STEP_PLAN_WRITE" };
10072
+ if (/[\\/]plans[\\/][^\\/]+\.md$/.test(s("file_path")) && (0, import_path11.isAbsolute)(rel(s("file_path")))) return { kind: "think", label: "Writing the plan", code: "STEP_PLAN_WRITE" };
9995
10073
  return { kind: "edit", label: `Writing ${rel(s("file_path"))}`, detail: rel(s("file_path")), code: "STEP_WRITE", params: { path: rel(s("file_path")) } };
9996
10074
  case "NotebookEdit":
9997
10075
  return { kind: "edit", label: `Editing ${rel(s("notebook_path"))}`, detail: rel(s("notebook_path")), code: "STEP_EDIT", params: { path: rel(s("notebook_path")) } };
@@ -10389,16 +10467,17 @@ function buildSystemAppend(c) {
10389
10467
  "- End with at most one question or one suggested next step, never a menu of options. When there is an obvious next step, say it in one line.",
10390
10468
  "- To show the user an image (a screenshot, a design, a reference board, a chart), write it as a Markdown image with its path in the workspace, for example `![Login screen](docs/login.png)`: the screen displays it in the conversation, and the user can open it larger. An https image address works the same way. Never say you cannot show an image; read it yourself first when you need to know what it shows.",
10391
10469
  "- To show the user a page you built (a prototype, a screen, a mockup, a chart, a small game, an HTML report), save it as an .html file in the workspace and write it as a Markdown image with that path, for example `![Battle HUD](prototype/hud.html)`: the screen runs the page in the conversation, with its code one click away. Stylesheets, scripts and images it links from the workspace go with it; scripts from a CDN load only from cdn.jsdelivr.net, cdnjs.cloudflare.com, unpkg.com or cdn.tailwindcss.com, and it calls no other address. Never say the conversation cannot show a page or a preview, and never offer to open it on the user's computer instead.",
10392
- ...c.local ? ["- To show the user an app you started on this machine (a web dev server: Vite, Next, a game build served over http), run it in the background and write its address as a Markdown image, for example `![Battle HUD](http://localhost:5173)`: the screen runs the app in the conversation, since the user's browser is on this same machine. Never say you cannot show the running app."] : [],
10470
+ c.local ? "- To show the user an app you started on this machine (a web dev server: Vite, Next, a game build served over http), run it in the background and write its address as a Markdown image, for example `![Battle HUD](http://localhost:5173)`: the screen runs the app in the conversation, since the user's browser is on this same machine. Never say you cannot show the running app." : "- To show the user an app you started in the workspace (a web dev server: Vite, Next, a game build served over http, on a port from 1024 up), run it in the background and write its address as a Markdown image, for example `![Battle HUD](http://localhost:5173)`: the screen runs the app in the conversation through ScaleQuality, live (hot reload included). The page there has no cookies or browser storage of its own. Never say you cannot show the running app.",
10471
+ "- After you build or change a screen of an app you started, call screenshot_app with its http://localhost address and Read the PNG to check it looks right before you say it is done.",
10393
10472
  ...c.webSearch ? ["- To look something up on the web (current documentation, a version, an error message, an API), call web_search; read a page in full with WebFetch. Never say you cannot search the web."] : [],
10394
10473
  ...c.imageGeneration ? ["- To create a new image (a character sheet, concept art, a sprite, an icon, a mockup, a reference board), call generate_image with a detailed prompt and where to save it, then show it from that path. Never say you cannot create images, and never answer an image request with only a prompt for another tool."] : []
10395
10474
  ].join("\n");
10396
10475
  }
10397
10476
 
10398
10477
  // src/application/services/workspaceSandbox/testCommand.ts
10399
- var import_fs7 = require("fs");
10400
- var import_promises10 = require("fs/promises");
10401
- var import_path11 = require("path");
10478
+ var import_fs8 = require("fs");
10479
+ var import_promises11 = require("fs/promises");
10480
+ var import_path12 = require("path");
10402
10481
  function commandForFramework(framework, dir, pkgTestScript) {
10403
10482
  switch (framework) {
10404
10483
  case "jest":
@@ -10410,13 +10489,13 @@ function commandForFramework(framework, dir, pkgTestScript) {
10410
10489
  case "go-test":
10411
10490
  return "go test ./...";
10412
10491
  case "junit":
10413
- return (0, import_fs7.existsSync)((0, import_path11.join)(dir, "gradlew")) ? "./gradlew test" : (0, import_fs7.existsSync)((0, import_path11.join)(dir, "build.gradle")) || (0, import_fs7.existsSync)((0, import_path11.join)(dir, "build.gradle.kts")) ? "gradle test" : "mvn -q test";
10492
+ return (0, import_fs8.existsSync)((0, import_path12.join)(dir, "gradlew")) ? "./gradlew test" : (0, import_fs8.existsSync)((0, import_path12.join)(dir, "build.gradle")) || (0, import_fs8.existsSync)((0, import_path12.join)(dir, "build.gradle.kts")) ? "gradle test" : "mvn -q test";
10414
10493
  case "xunit":
10415
10494
  case "nunit":
10416
10495
  case "mstest":
10417
10496
  return "dotnet test";
10418
10497
  case "phpunit":
10419
- return (0, import_fs7.existsSync)((0, import_path11.join)(dir, "vendor", "bin", "phpunit")) ? "vendor/bin/phpunit" : "phpunit";
10498
+ return (0, import_fs8.existsSync)((0, import_path12.join)(dir, "vendor", "bin", "phpunit")) ? "vendor/bin/phpunit" : "phpunit";
10420
10499
  case "rspec":
10421
10500
  return "bundle exec rspec";
10422
10501
  case "cargo-test":
@@ -10424,7 +10503,7 @@ function commandForFramework(framework, dir, pkgTestScript) {
10424
10503
  case "exunit":
10425
10504
  return "mix test";
10426
10505
  case "dart-test":
10427
- return (0, import_fs7.existsSync)((0, import_path11.join)(dir, "pubspec.yaml")) && /flutter:/.test(safeRead((0, import_path11.join)(dir, "pubspec.yaml"))) ? "flutter test" : "dart test";
10506
+ return (0, import_fs8.existsSync)((0, import_path12.join)(dir, "pubspec.yaml")) && /flutter:/.test(safeRead((0, import_path12.join)(dir, "pubspec.yaml"))) ? "flutter test" : "dart test";
10428
10507
  case "xctest":
10429
10508
  return "swift test";
10430
10509
  case "scalatest":
@@ -10437,13 +10516,13 @@ function commandForFramework(framework, dir, pkgTestScript) {
10437
10516
  }
10438
10517
  function safeRead(p) {
10439
10518
  try {
10440
- return (0, import_fs7.readFileSync)(p, "utf8");
10519
+ return (0, import_fs8.readFileSync)(p, "utf8");
10441
10520
  } catch {
10442
10521
  return "";
10443
10522
  }
10444
10523
  }
10445
10524
  async function hasTestScript(dir) {
10446
- const raw = await (0, import_promises10.readFile)((0, import_path11.join)(dir, "package.json"), "utf8").catch(() => null);
10525
+ const raw = await (0, import_promises11.readFile)((0, import_path12.join)(dir, "package.json"), "utf8").catch(() => null);
10447
10526
  if (!raw) return false;
10448
10527
  try {
10449
10528
  const script = JSON.parse(raw).scripts?.test;
@@ -10453,7 +10532,7 @@ async function hasTestScript(dir) {
10453
10532
  }
10454
10533
  }
10455
10534
  async function detectTestCommandFromManifests(dir) {
10456
- const has = (f) => (0, import_fs7.existsSync)((0, import_path11.join)(dir, f));
10535
+ const has = (f) => (0, import_fs8.existsSync)((0, import_path12.join)(dir, f));
10457
10536
  const pkgScript = await hasTestScript(dir);
10458
10537
  if (pkgScript) return { command: "npm test --silent", framework: "npm-script", source: "manifest" };
10459
10538
  const checks = [
@@ -10478,9 +10557,9 @@ async function detectTestCommandFromManifests(dir) {
10478
10557
 
10479
10558
  // src/application/services/workspaceSandbox/transcript.ts
10480
10559
  var import_crypto7 = require("crypto");
10481
- var import_fs8 = require("fs");
10482
- var import_promises11 = require("fs/promises");
10483
- var import_path12 = require("path");
10560
+ var import_fs9 = require("fs");
10561
+ var import_promises12 = require("fs/promises");
10562
+ var import_path13 = require("path");
10484
10563
  var import_zlib2 = require("zlib");
10485
10564
  var TRANSCRIPT_CAPS = { maxRawBytes: 64 * 1024 * 1024, maxCompressedBytes: 4 * 1024 * 1024 };
10486
10565
  function engineProjectDir(cwd) {
@@ -10489,10 +10568,10 @@ function engineProjectDir(cwd) {
10489
10568
  var SESSION_ID = /^[A-Za-z0-9-]{8,80}$/;
10490
10569
  async function findTranscript(configDir, sessionId) {
10491
10570
  if (!SESSION_ID.test(sessionId)) return null;
10492
- const projects = (0, import_path12.join)(configDir, "projects");
10493
- for (const dir of await (0, import_promises11.readdir)(projects).catch(() => [])) {
10494
- const file = (0, import_path12.join)(projects, dir, `${sessionId}.jsonl`);
10495
- const st = await (0, import_promises11.stat)(file).catch(() => null);
10571
+ const projects = (0, import_path13.join)(configDir, "projects");
10572
+ for (const dir of await (0, import_promises12.readdir)(projects).catch(() => [])) {
10573
+ const file = (0, import_path13.join)(projects, dir, `${sessionId}.jsonl`);
10574
+ const st = await (0, import_promises12.stat)(file).catch(() => null);
10496
10575
  if (st?.isFile()) return file;
10497
10576
  }
10498
10577
  return null;
@@ -10500,9 +10579,9 @@ async function findTranscript(configDir, sessionId) {
10500
10579
  async function packTranscript(configDir, sessionId, redactor, caps = TRANSCRIPT_CAPS) {
10501
10580
  const file = await findTranscript(configDir, sessionId);
10502
10581
  if (!file) return null;
10503
- const st = await (0, import_promises11.stat)(file);
10582
+ const st = await (0, import_promises12.stat)(file);
10504
10583
  if (st.size > caps.maxRawBytes) return { tooLarge: true, bytes: st.size };
10505
- const raw = await (0, import_promises11.readFile)(file, "utf8");
10584
+ const raw = await (0, import_promises12.readFile)(file, "utf8");
10506
10585
  let removed = 0;
10507
10586
  const lines2 = [];
10508
10587
  for (const line of raw.split("\n")) {
@@ -10537,24 +10616,24 @@ async function restoreTranscript(configDir, cwd, t) {
10537
10616
  return false;
10538
10617
  }
10539
10618
  if (t.sha256 && (0, import_crypto7.createHash)("sha256").update(text3).digest("hex") !== t.sha256) return false;
10540
- const dir = (0, import_path12.join)(configDir, "projects", engineProjectDir(cwd));
10541
- await (0, import_promises11.mkdir)(dir, { recursive: true, mode: 448 });
10542
- const target = (0, import_path12.join)(dir, `${t.sdkSessionId}.jsonl`);
10619
+ const dir = (0, import_path13.join)(configDir, "projects", engineProjectDir(cwd));
10620
+ await (0, import_promises12.mkdir)(dir, { recursive: true, mode: 448 });
10621
+ const target = (0, import_path13.join)(dir, `${t.sdkSessionId}.jsonl`);
10543
10622
  const partial = `${target}.${process.pid}.partial`;
10544
10623
  try {
10545
- await (0, import_promises11.writeFile)(partial, text3, { mode: 384 });
10546
- await (0, import_promises11.chmod)(partial, 384).catch(() => void 0);
10547
- await (0, import_promises11.rename)(partial, target);
10624
+ await (0, import_promises12.writeFile)(partial, text3, { mode: 384 });
10625
+ await (0, import_promises12.chmod)(partial, 384).catch(() => void 0);
10626
+ await (0, import_promises12.rename)(partial, target);
10548
10627
  } catch {
10549
- await (0, import_promises11.rm)(partial, { force: true }).catch(() => void 0);
10628
+ await (0, import_promises12.rm)(partial, { force: true }).catch(() => void 0);
10550
10629
  return false;
10551
10630
  }
10552
- return (0, import_fs8.existsSync)(target);
10631
+ return (0, import_fs9.existsSync)(target);
10553
10632
  }
10554
10633
 
10555
10634
  // src/application/services/workspaceSandbox/workspaceFiles.ts
10556
- var import_promises12 = require("fs/promises");
10557
- var import_path13 = require("path");
10635
+ var import_promises13 = require("fs/promises");
10636
+ var import_path14 = require("path");
10558
10637
  var FILE_CAPS = { maxEntries: 500, maxReadBytes: 256 * 1024, maxFileBytes: 20 * 1024 * 1024, maxLines: 5e3, maxImageBytes: 6 * 1024 * 1024 };
10559
10638
  var FileRequestError = class extends Error {
10560
10639
  constructor(code) {
@@ -10568,36 +10647,36 @@ async function fenced(root, rel, denied) {
10568
10647
  if (clean.split(/[\\/]/).includes("..")) throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10569
10648
  const abs = await resolveInside(root, clean || ".");
10570
10649
  if (!abs) throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10571
- const realRoot = await (0, import_promises12.realpath)(root).catch(() => (0, import_path13.resolve)(root));
10572
- const r = (0, import_path13.relative)(realRoot, abs);
10573
- if (r.split(import_path13.sep).includes(".git")) throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10650
+ const realRoot = await (0, import_promises13.realpath)(root).catch(() => (0, import_path14.resolve)(root));
10651
+ const r = (0, import_path14.relative)(realRoot, abs);
10652
+ if (r.split(import_path14.sep).includes(".git")) throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10574
10653
  for (const d of denied) {
10575
- const realDenied = await (0, import_promises12.realpath)(d).catch(() => (0, import_path13.resolve)(d));
10576
- if (abs === realDenied || abs.startsWith(realDenied + import_path13.sep)) throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10654
+ const realDenied = await (0, import_promises13.realpath)(d).catch(() => (0, import_path14.resolve)(d));
10655
+ if (abs === realDenied || abs.startsWith(realDenied + import_path14.sep)) throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10577
10656
  }
10578
- return { abs, rel: r.split(import_path13.sep).join("/") };
10657
+ return { abs, rel: r.split(import_path14.sep).join("/") };
10579
10658
  }
10580
10659
  async function listFiles(root, rel, denied = []) {
10581
10660
  const { abs, rel: clean } = await fenced(root, rel, denied);
10582
- const st = await (0, import_promises12.stat)(abs).catch(() => null);
10661
+ const st = await (0, import_promises13.stat)(abs).catch(() => null);
10583
10662
  if (!st) throw new FileRequestError("NOT_FOUND");
10584
10663
  if (!st.isDirectory()) throw new FileRequestError("NOT_A_DIRECTORY");
10585
- const deniedReal = await Promise.all(denied.map((d) => (0, import_promises12.realpath)(d).catch(() => (0, import_path13.resolve)(d))));
10586
- const dirents = await (0, import_promises12.readdir)(abs, { withFileTypes: true });
10664
+ const deniedReal = await Promise.all(denied.map((d) => (0, import_promises13.realpath)(d).catch(() => (0, import_path14.resolve)(d))));
10665
+ const dirents = await (0, import_promises13.readdir)(abs, { withFileTypes: true });
10587
10666
  const entries = [];
10588
10667
  for (const d of dirents) {
10589
10668
  if (d.name === ".git") continue;
10590
- const child = (0, import_path13.join)(abs, d.name);
10591
- if (deniedReal.some((x) => child === x || child.startsWith(x + import_path13.sep))) continue;
10669
+ const child = (0, import_path14.join)(abs, d.name);
10670
+ if (deniedReal.some((x) => child === x || child.startsWith(x + import_path14.sep))) continue;
10592
10671
  let type = d.isDirectory() ? "dir" : d.isFile() ? "file" : null;
10593
10672
  let size;
10594
10673
  if (d.isSymbolicLink()) {
10595
- const inside3 = await resolveInside(root, (0, import_path13.relative)(root, child)).catch(() => null);
10596
- const target = inside3 ? await (0, import_promises12.stat)(child).catch(() => null) : null;
10674
+ const inside3 = await resolveInside(root, (0, import_path14.relative)(root, child)).catch(() => null);
10675
+ const target = inside3 ? await (0, import_promises13.stat)(child).catch(() => null) : null;
10597
10676
  type = target?.isDirectory() ? "dir" : target?.isFile() ? "file" : null;
10598
10677
  size = target?.isFile() ? target.size : void 0;
10599
10678
  } else if (type === "file") {
10600
- size = (await (0, import_promises12.stat)(child).catch(() => null))?.size;
10679
+ size = (await (0, import_promises13.stat)(child).catch(() => null))?.size;
10601
10680
  }
10602
10681
  if (!type) continue;
10603
10682
  entries.push({ name: d.name, path: clean ? `${clean}/${d.name}` : d.name, type, ...size !== void 0 ? { size } : {} });
@@ -10607,11 +10686,11 @@ async function listFiles(root, rel, denied = []) {
10607
10686
  }
10608
10687
  async function readTextFile(root, rel, range = {}, denied = []) {
10609
10688
  const { abs, rel: clean } = await fenced(root, rel, denied);
10610
- const st = await (0, import_promises12.stat)(abs).catch(() => null);
10689
+ const st = await (0, import_promises13.stat)(abs).catch(() => null);
10611
10690
  if (!st) throw new FileRequestError("NOT_FOUND");
10612
10691
  if (!st.isFile()) throw new FileRequestError("NOT_A_FILE");
10613
10692
  if (st.size > FILE_CAPS.maxFileBytes) throw new FileRequestError("FILE_TOO_LARGE");
10614
- const fh = await (0, import_promises12.open)(abs, "r");
10693
+ const fh = await (0, import_promises13.open)(abs, "r");
10615
10694
  let buf;
10616
10695
  try {
10617
10696
  buf = Buffer.alloc(st.size);
@@ -10650,23 +10729,23 @@ function imageMime(buf) {
10650
10729
  }
10651
10730
  async function insideRelative(path, bases) {
10652
10731
  const clean = (path ?? "").trim().replace(/^file:\/\//i, "");
10653
- if (!(0, import_path13.isAbsolute)(clean)) return clean.replace(/^\.\//, "");
10732
+ if (!(0, import_path14.isAbsolute)(clean)) return clean.replace(/^\.\//, "");
10654
10733
  for (const base of bases) {
10655
- for (const root of [(0, import_path13.resolve)(base), await (0, import_promises12.realpath)(base).catch(() => (0, import_path13.resolve)(base))]) {
10656
- const rel = (0, import_path13.relative)(root, (0, import_path13.resolve)(clean));
10734
+ for (const root of [(0, import_path14.resolve)(base), await (0, import_promises13.realpath)(base).catch(() => (0, import_path14.resolve)(base))]) {
10735
+ const rel = (0, import_path14.relative)(root, (0, import_path14.resolve)(clean));
10657
10736
  if (!rel) return "";
10658
- if (rel !== ".." && !rel.startsWith(`..${import_path13.sep}`) && !(0, import_path13.isAbsolute)(rel)) return rel.split(import_path13.sep).join("/");
10737
+ if (rel !== ".." && !rel.startsWith(`..${import_path14.sep}`) && !(0, import_path14.isAbsolute)(rel)) return rel.split(import_path14.sep).join("/");
10659
10738
  }
10660
10739
  }
10661
10740
  throw new FileRequestError("PATH_OUTSIDE_WORKSPACE");
10662
10741
  }
10663
10742
  async function readWholeFile(root, rel, denied = []) {
10664
10743
  const { abs, rel: clean } = await fenced(root, rel, denied);
10665
- const st = await (0, import_promises12.stat)(abs).catch(() => null);
10744
+ const st = await (0, import_promises13.stat)(abs).catch(() => null);
10666
10745
  if (!st) throw new FileRequestError("NOT_FOUND");
10667
10746
  if (!st.isFile()) throw new FileRequestError("NOT_A_FILE");
10668
10747
  if (st.size > FILE_CAPS.maxImageBytes) throw new FileRequestError("FILE_TOO_LARGE");
10669
- const fh = await (0, import_promises12.open)(abs, "r");
10748
+ const fh = await (0, import_promises13.open)(abs, "r");
10670
10749
  try {
10671
10750
  const buf = Buffer.alloc(st.size);
10672
10751
  await fh.read(buf, 0, st.size, 0);
@@ -10677,11 +10756,11 @@ async function readWholeFile(root, rel, denied = []) {
10677
10756
  }
10678
10757
  async function readImageFile(root, rel, denied = []) {
10679
10758
  const { abs, rel: clean } = await fenced(root, rel, denied);
10680
- const st = await (0, import_promises12.stat)(abs).catch(() => null);
10759
+ const st = await (0, import_promises13.stat)(abs).catch(() => null);
10681
10760
  if (!st) throw new FileRequestError("NOT_FOUND");
10682
10761
  if (!st.isFile()) throw new FileRequestError("NOT_A_FILE");
10683
10762
  if (st.size > FILE_CAPS.maxImageBytes) throw new FileRequestError("FILE_TOO_LARGE");
10684
- const fh = await (0, import_promises12.open)(abs, "r");
10763
+ const fh = await (0, import_promises13.open)(abs, "r");
10685
10764
  let buf;
10686
10765
  try {
10687
10766
  buf = Buffer.alloc(st.size);
@@ -10761,8 +10840,8 @@ async function callOrgTool(host, server, tool, args) {
10761
10840
  }
10762
10841
 
10763
10842
  // src/application/services/workspaceSandbox/workspaceSkills.ts
10764
- var import_promises13 = require("fs/promises");
10765
- var import_path14 = require("path");
10843
+ var import_promises14 = require("fs/promises");
10844
+ var import_path15 = require("path");
10766
10845
  var SKILLS_PLUGIN = "sq";
10767
10846
  var BUILTIN_SKILLS = ["code-review", "simplify", "debug", "verify"];
10768
10847
  var SKILL_CAPS = { fileBytes: 256 * 1024, skillBytes: 2 * 1024 * 1024, skillFiles: 200, totalBytes: 16 * 1024 * 1024, skills: 200 };
@@ -10806,29 +10885,29 @@ ${lines2.join("\n")}
10806
10885
  ${neutralize(body)}`;
10807
10886
  }
10808
10887
  async function regularFile(abs, realRoot) {
10809
- const st = await (0, import_promises13.lstat)(abs).catch(() => null);
10888
+ const st = await (0, import_promises14.lstat)(abs).catch(() => null);
10810
10889
  if (!st?.isFile()) return false;
10811
- const real = await (0, import_promises13.realpath)(abs).catch(() => null);
10812
- return !!real && !(0, import_path14.relative)(realRoot, real).split(import_path14.sep).includes("..");
10890
+ const real = await (0, import_promises14.realpath)(abs).catch(() => null);
10891
+ return !!real && !(0, import_path15.relative)(realRoot, real).split(import_path15.sep).includes("..");
10813
10892
  }
10814
10893
  async function copySkillDir(src, dest, realRoot, budget) {
10815
10894
  let files = 0, bytes = 0;
10816
10895
  const walk = async (rel) => {
10817
- const entries = await (0, import_promises13.readdir)((0, import_path14.join)(src, rel), { withFileTypes: true }).catch(() => []);
10896
+ const entries = await (0, import_promises14.readdir)((0, import_path15.join)(src, rel), { withFileTypes: true }).catch(() => []);
10818
10897
  for (const e of entries) {
10819
10898
  if (files >= SKILL_CAPS.skillFiles || bytes >= SKILL_CAPS.skillBytes) return;
10820
- const r = rel ? (0, import_path14.join)(rel, e.name) : e.name;
10899
+ const r = rel ? (0, import_path15.join)(rel, e.name) : e.name;
10821
10900
  if (e.isDirectory()) {
10822
10901
  if (!SKIP_DIRS.has(e.name)) await walk(r);
10823
10902
  continue;
10824
10903
  }
10825
10904
  if (!e.isFile() || r === "SKILL.md") continue;
10826
- const abs = (0, import_path14.join)(src, r);
10905
+ const abs = (0, import_path15.join)(src, r);
10827
10906
  if (!await regularFile(abs, realRoot)) continue;
10828
- const buf = await (0, import_promises13.readFile)(abs).catch(() => null);
10907
+ const buf = await (0, import_promises14.readFile)(abs).catch(() => null);
10829
10908
  if (!buf || buf.length > SKILL_CAPS.fileBytes || bytes + buf.length > SKILL_CAPS.skillBytes || budget.bytes < buf.length) continue;
10830
- await (0, import_promises13.mkdir)((0, import_path14.join)(dest, rel), { recursive: true });
10831
- await (0, import_promises13.writeFile)((0, import_path14.join)(dest, r), buf, { mode: 384 });
10909
+ await (0, import_promises14.mkdir)((0, import_path15.join)(dest, rel), { recursive: true });
10910
+ await (0, import_promises14.writeFile)((0, import_path15.join)(dest, r), buf, { mode: 384 });
10832
10911
  files++;
10833
10912
  bytes += buf.length;
10834
10913
  budget.bytes -= buf.length;
@@ -10837,9 +10916,9 @@ async function copySkillDir(src, dest, realRoot, budget) {
10837
10916
  await walk("");
10838
10917
  }
10839
10918
  async function buildSkillsPlugin(opts) {
10840
- await (0, import_promises13.rm)(opts.dir, { recursive: true, force: true });
10841
- await (0, import_promises13.mkdir)((0, import_path14.join)(opts.dir, ".claude-plugin"), { recursive: true, mode: 448 });
10842
- await (0, import_promises13.writeFile)((0, import_path14.join)(opts.dir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: SKILLS_PLUGIN, description: "Skills and commands of this ScaleQuality workspace" }));
10919
+ await (0, import_promises14.rm)(opts.dir, { recursive: true, force: true });
10920
+ await (0, import_promises14.mkdir)((0, import_path15.join)(opts.dir, ".claude-plugin"), { recursive: true, mode: 448 });
10921
+ await (0, import_promises14.writeFile)((0, import_path15.join)(opts.dir, ".claude-plugin", "plugin.json"), JSON.stringify({ name: SKILLS_PLUGIN, description: "Skills and commands of this ScaleQuality workspace" }));
10843
10922
  const out2 = [];
10844
10923
  const taken = new Set(BUILTIN_SKILLS);
10845
10924
  const budget = { bytes: SKILL_CAPS.totalBytes };
@@ -10850,31 +10929,31 @@ async function buildSkillsPlugin(opts) {
10850
10929
  const meta = { description: s.description ?? "", ...s.argumentHint ? { "argument-hint": s.argumentHint } : {} };
10851
10930
  const text3 = rebuild(meta, name, s.body);
10852
10931
  if (Buffer.byteLength(text3) > SKILL_CAPS.fileBytes) continue;
10853
- await (0, import_promises13.mkdir)((0, import_path14.join)(opts.dir, "skills", name), { recursive: true });
10854
- await (0, import_promises13.writeFile)((0, import_path14.join)(opts.dir, "skills", name, "SKILL.md"), text3, { mode: 384 });
10932
+ await (0, import_promises14.mkdir)((0, import_path15.join)(opts.dir, "skills", name), { recursive: true });
10933
+ await (0, import_promises14.writeFile)((0, import_path15.join)(opts.dir, "skills", name, "SKILL.md"), text3, { mode: 384 });
10855
10934
  budget.bytes -= Buffer.byteLength(text3);
10856
10935
  taken.add(name);
10857
10936
  out2.push({ name, description: meta.description.slice(0, 300), kind: "skill", source: "organization", ...s.argumentHint ? { argumentHint: s.argumentHint.slice(0, 120) } : {} });
10858
10937
  }
10859
10938
  for (const repo2 of opts.repos) {
10860
- const realRoot = await (0, import_promises13.realpath)(repo2.root).catch(() => null);
10939
+ const realRoot = await (0, import_promises14.realpath)(repo2.root).catch(() => null);
10861
10940
  if (!realRoot) continue;
10862
- const skillsDir = (0, import_path14.join)(repo2.root, ".claude", "skills");
10863
- for (const e of (await (0, import_promises13.readdir)(skillsDir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name.localeCompare(b.name))) {
10941
+ const skillsDir = (0, import_path15.join)(repo2.root, ".claude", "skills");
10942
+ for (const e of (await (0, import_promises14.readdir)(skillsDir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name.localeCompare(b.name))) {
10864
10943
  if (!room() || !e.isDirectory()) continue;
10865
- const file = (0, import_path14.join)(skillsDir, e.name, "SKILL.md");
10944
+ const file = (0, import_path15.join)(skillsDir, e.name, "SKILL.md");
10866
10945
  if (!await regularFile(file, realRoot)) continue;
10867
- const buf = await (0, import_promises13.readFile)(file).catch(() => null);
10946
+ const buf = await (0, import_promises14.readFile)(file).catch(() => null);
10868
10947
  if (!buf || buf.length > SKILL_CAPS.fileBytes || buf.includes(0)) continue;
10869
10948
  const { meta, body } = parseSkillFile(buf.toString("utf8"));
10870
10949
  const name = (meta.name || e.name).toLowerCase();
10871
10950
  if (!NAME.test(name) || taken.has(name)) continue;
10872
- const dest = (0, import_path14.join)(opts.dir, "skills", name);
10873
- await (0, import_promises13.mkdir)(dest, { recursive: true });
10951
+ const dest = (0, import_path15.join)(opts.dir, "skills", name);
10952
+ await (0, import_promises14.mkdir)(dest, { recursive: true });
10874
10953
  const text3 = rebuild(meta, name, body);
10875
- await (0, import_promises13.writeFile)((0, import_path14.join)(dest, "SKILL.md"), text3, { mode: 384 });
10954
+ await (0, import_promises14.writeFile)((0, import_path15.join)(dest, "SKILL.md"), text3, { mode: 384 });
10876
10955
  budget.bytes -= Buffer.byteLength(text3);
10877
- await copySkillDir((0, import_path14.join)(skillsDir, e.name), dest, realRoot, budget);
10956
+ await copySkillDir((0, import_path15.join)(skillsDir, e.name), dest, realRoot, budget);
10878
10957
  taken.add(name);
10879
10958
  out2.push({
10880
10959
  name,
@@ -10885,12 +10964,12 @@ async function buildSkillsPlugin(opts) {
10885
10964
  ...meta["argument-hint"] ? { argumentHint: meta["argument-hint"].slice(0, 120) } : {}
10886
10965
  });
10887
10966
  }
10888
- const commandsDir = (0, import_path14.join)(repo2.root, ".claude", "commands");
10889
- for (const e of (await (0, import_promises13.readdir)(commandsDir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name.localeCompare(b.name))) {
10967
+ const commandsDir = (0, import_path15.join)(repo2.root, ".claude", "commands");
10968
+ for (const e of (await (0, import_promises14.readdir)(commandsDir, { withFileTypes: true }).catch(() => [])).sort((a, b) => a.name.localeCompare(b.name))) {
10890
10969
  if (!room() || !e.isFile() || !e.name.endsWith(".md")) continue;
10891
- const file = (0, import_path14.join)(commandsDir, e.name);
10970
+ const file = (0, import_path15.join)(commandsDir, e.name);
10892
10971
  if (!await regularFile(file, realRoot)) continue;
10893
- const buf = await (0, import_promises13.readFile)(file).catch(() => null);
10972
+ const buf = await (0, import_promises14.readFile)(file).catch(() => null);
10894
10973
  if (!buf || buf.length > SKILL_CAPS.fileBytes || buf.includes(0)) continue;
10895
10974
  const name = e.name.slice(0, -3).toLowerCase();
10896
10975
  if (!NAME.test(name) || taken.has(name)) continue;
@@ -10901,8 +10980,8 @@ ${lines2.join("\n")}
10901
10980
  ---
10902
10981
 
10903
10982
  ` : ""}${neutralize(body)}`;
10904
- await (0, import_promises13.mkdir)((0, import_path14.join)(opts.dir, "commands"), { recursive: true });
10905
- await (0, import_promises13.writeFile)((0, import_path14.join)(opts.dir, "commands", `${name}.md`), text3, { mode: 384 });
10983
+ await (0, import_promises14.mkdir)((0, import_path15.join)(opts.dir, "commands"), { recursive: true });
10984
+ await (0, import_promises14.writeFile)((0, import_path15.join)(opts.dir, "commands", `${name}.md`), text3, { mode: 384 });
10906
10985
  budget.bytes -= Buffer.byteLength(text3);
10907
10986
  taken.add(name);
10908
10987
  out2.push({
@@ -10941,19 +11020,19 @@ ${lines2.join("\n")}
10941
11020
  ${neutralize(body)}`;
10942
11021
  }
10943
11022
  async function buildOrganizationPlugins(opts) {
10944
- await (0, import_promises13.rm)(opts.dir, { recursive: true, force: true });
11023
+ await (0, import_promises14.rm)(opts.dir, { recursive: true, force: true });
10945
11024
  const entries = [], dirs = [];
10946
11025
  for (const p of opts.plugins) {
10947
11026
  const plugin = p.name.toLowerCase();
10948
11027
  if (!/^[a-z0-9][a-z0-9-]{0,39}$/.test(plugin) || plugin === SKILLS_PLUGIN || dirs.some((d) => d.endsWith(`/${plugin}`))) continue;
10949
- const root = (0, import_path14.join)(opts.dir, plugin);
10950
- await (0, import_promises13.mkdir)((0, import_path14.join)(root, ".claude-plugin"), { recursive: true, mode: 448 });
10951
- await (0, import_promises13.writeFile)((0, import_path14.join)(root, ".claude-plugin", "plugin.json"), JSON.stringify({ name: plugin }));
11028
+ const root = (0, import_path15.join)(opts.dir, plugin);
11029
+ await (0, import_promises14.mkdir)((0, import_path15.join)(root, ".claude-plugin"), { recursive: true, mode: 448 });
11030
+ await (0, import_promises14.writeFile)((0, import_path15.join)(root, ".claude-plugin", "plugin.json"), JSON.stringify({ name: plugin }));
10952
11031
  const write = async (rel, text3) => {
10953
- const abs = (0, import_path14.join)(root, rel);
10954
- if ((0, import_path14.relative)(root, abs).split(import_path14.sep).includes("..")) return;
10955
- await (0, import_promises13.mkdir)((0, import_path14.join)(abs, ".."), { recursive: true });
10956
- await (0, import_promises13.writeFile)(abs, text3, { mode: 384 });
11032
+ const abs = (0, import_path15.join)(root, rel);
11033
+ if ((0, import_path15.relative)(root, abs).split(import_path15.sep).includes("..")) return;
11034
+ await (0, import_promises14.mkdir)((0, import_path15.join)(abs, ".."), { recursive: true });
11035
+ await (0, import_promises14.writeFile)(abs, text3, { mode: 384 });
10957
11036
  };
10958
11037
  for (const f of p.files) {
10959
11038
  if (typeof f?.path !== "string" || typeof f.content !== "string" || f.path.split("/").some((x) => x === ".." || x === "")) continue;
@@ -11061,7 +11140,7 @@ function turnOffering(raw, loaded) {
11061
11140
  mcpCatalog: loaded.catalog ? { ...loaded.catalog, servers: loaded.catalog.servers.filter((sv) => !connectors.has(sv.server.toLowerCase())) } : null,
11062
11141
  mcpServers: Object.fromEntries(Object.entries(loaded.servers).filter(([key]) => !connectors.has(key.slice(ORG_MCP_PREFIX.length).toLowerCase()))),
11063
11142
  skillEntries: loaded.skillEntries.filter((e) => !(e.source === "plugin" && e.plugin && plugins.has(e.plugin))),
11064
- pluginDirs: loaded.pluginDirs.filter((d) => !plugins.has((0, import_path15.basename)(d).toLowerCase()))
11143
+ pluginDirs: loaded.pluginDirs.filter((d) => !plugins.has((0, import_path16.basename)(d).toLowerCase()))
11065
11144
  };
11066
11145
  }
11067
11146
  function sameModel(a, b) {
@@ -11388,7 +11467,7 @@ var WorkspaceEngine = class {
11388
11467
  }
11389
11468
  await this.backgroundOutputDir();
11390
11469
  if (boot.plugins?.length) {
11391
- this.orgPlugins = await buildOrganizationPlugins({ dir: (0, import_path15.join)(this.deps.configDir, "sq-org-plugins"), plugins: boot.plugins }).catch((e) => {
11470
+ this.orgPlugins = await buildOrganizationPlugins({ dir: (0, import_path16.join)(this.deps.configDir, "sq-org-plugins"), plugins: boot.plugins }).catch((e) => {
11392
11471
  this.deps.log.warn("organization plugins unavailable", { error: this.redactor.text(e.message) });
11393
11472
  return { entries: [], dirs: [] };
11394
11473
  });
@@ -11415,8 +11494,8 @@ var WorkspaceEngine = class {
11415
11494
  }
11416
11495
  /** Where a background command writes its output: inside the config dir, which Read may read. */
11417
11496
  async backgroundOutputDir() {
11418
- await (0, import_promises14.mkdir)((0, import_path15.join)(this.deps.configDir, "tmp"), { recursive: true, mode: 448 }).catch(() => void 0);
11419
- await (0, import_promises14.mkdir)((0, import_path15.join)(this.deps.configDir, "plans"), { recursive: true, mode: 448 }).catch(() => void 0);
11497
+ await (0, import_promises15.mkdir)((0, import_path16.join)(this.deps.configDir, "tmp"), { recursive: true, mode: 448 }).catch(() => void 0);
11498
+ await (0, import_promises15.mkdir)((0, import_path16.join)(this.deps.configDir, "plans"), { recursive: true, mode: 448 }).catch(() => void 0);
11420
11499
  }
11421
11500
  /** Runs until shutdown. */
11422
11501
  async run() {
@@ -11445,7 +11524,7 @@ var WorkspaceEngine = class {
11445
11524
  const repo2 = {
11446
11525
  ...r,
11447
11526
  tracker: r.tracker ?? gitTracker(r.root, r.prepared.baseRevision, r.prefix),
11448
- measurer: this.deps.createMeasurer && !this.local ? this.deps.createMeasurer(r.repoFullName ?? (0, import_path15.basename)(r.root), r.root, this.baseStore(r.repoFullName), r.repoFullName) : null,
11527
+ measurer: this.deps.createMeasurer && !this.local ? this.deps.createMeasurer(r.repoFullName ?? (0, import_path16.basename)(r.root), r.root, this.baseStore(r.repoFullName), r.repoFullName) : null,
11449
11528
  lastDiff: null
11450
11529
  };
11451
11530
  this.repos.set(r.root, repo2);
@@ -11474,14 +11553,14 @@ var WorkspaceEngine = class {
11474
11553
  const short = folderName(lastSegment(repoFullName));
11475
11554
  const full = folderName(repoFullName.split("/").filter(Boolean).join("__"));
11476
11555
  const clash = this.scope.repos.some((r) => r.repoFullName !== repoFullName && folderName(lastSegment(r.repoFullName)) === short);
11477
- const privateDirs = (this.deps.privateDirs ?? []).map((d) => (0, import_path15.resolve)(d));
11556
+ const privateDirs = (this.deps.privateDirs ?? []).map((d) => (0, import_path16.resolve)(d));
11478
11557
  const taken = (name2) => {
11479
- const dir = (0, import_path15.resolve)(this.deps.root, name2);
11480
- return this.repos.has(dir) || privateDirs.includes(dir) || (0, import_fs9.existsSync)(dir);
11558
+ const dir = (0, import_path16.resolve)(this.deps.root, name2);
11559
+ return this.repos.has(dir) || privateDirs.includes(dir) || (0, import_fs10.existsSync)(dir);
11481
11560
  };
11482
11561
  let name = clash || taken(short) ? full : short;
11483
11562
  for (let n = 2; taken(name); n++) name = `${full}-${n}`;
11484
- return (0, import_path15.join)(this.deps.root, name);
11563
+ return (0, import_path16.join)(this.deps.root, name);
11485
11564
  }
11486
11565
  /** Clones one repository into its folder and registers it. The token is dropped either way. */
11487
11566
  async cloneInto(access, onStep) {
@@ -11491,7 +11570,7 @@ var WorkspaceEngine = class {
11491
11570
  try {
11492
11571
  prepared = await this.deps.clone(access, dir, saved, onStep);
11493
11572
  } catch (e) {
11494
- await (0, import_promises14.rm)(dir, { recursive: true, force: true }).catch(() => void 0);
11573
+ await (0, import_promises15.rm)(dir, { recursive: true, force: true }).catch(() => void 0);
11495
11574
  throw e;
11496
11575
  } finally {
11497
11576
  access.token = "";
@@ -11528,7 +11607,7 @@ var WorkspaceEngine = class {
11528
11607
  }
11529
11608
  if (open2.length === 1) return { repo: open2[0] };
11530
11609
  if (!open2.length) return { error: "No repository is open in this workspace. Call list_repositories, then open_repository." };
11531
- return { error: `Several repositories are open (${open2.map((o) => o.repoFullName ?? (0, import_path15.basename)(o.root)).join(", ")}). Pass repoFullName.` };
11610
+ return { error: `Several repositories are open (${open2.map((o) => o.repoFullName ?? (0, import_path16.basename)(o.root)).join(", ")}). Pass repoFullName.` };
11532
11611
  }
11533
11612
  notInScope(repo2, target) {
11534
11613
  const loose = repo2.localId === LOOSE_REPO_ID;
@@ -11980,7 +12059,7 @@ ${String(item.content)}` : String(item.content);
11980
12059
  webSearch: !!this.boot?.webSearch && !!this.deps.transport.searchWeb
11981
12060
  });
11982
12061
  const conventions = await readConventions([...this.repos.values()].filter((r) => !r.dormant).flatMap((r) => {
11983
- const label = r.repoFullName ?? r.localId ?? (0, import_path15.basename)(r.root);
12062
+ const label = r.repoFullName ?? r.localId ?? (0, import_path16.basename)(r.root);
11984
12063
  return r.fence && r.fence !== r.root ? [{ label, root: r.root }, { label: `${label} (${r.prefix})`, root: r.fence }] : [{ label, root: r.root }];
11985
12064
  })).catch(() => []);
11986
12065
  return base + conventionsSection(conventions);
@@ -12044,7 +12123,7 @@ ${String(item.content)}` : String(item.content);
12044
12123
  return {
12045
12124
  registry: this.registry,
12046
12125
  currentBranch: async () => (await git(["rev-parse", "--abbrev-ref", "HEAD"], { cwd: this.deps.root, timeoutMs: 3e3 })).trim() || null,
12047
- installedLocally: (name) => [this.deps.root, ...[...this.repos.values()].map((r) => r.root)].some((dir) => (0, import_fs9.existsSync)((0, import_path15.join)(dir, "node_modules", name, "package.json")) || (0, import_fs9.existsSync)((0, import_path15.join)(dir, "node_modules", ".bin", name)))
12126
+ installedLocally: (name) => [this.deps.root, ...[...this.repos.values()].map((r) => r.root)].some((dir) => (0, import_fs10.existsSync)((0, import_path16.join)(dir, "node_modules", name, "package.json")) || (0, import_fs10.existsSync)((0, import_path16.join)(dir, "node_modules", ".bin", name)))
12048
12127
  };
12049
12128
  }
12050
12129
  /**
@@ -12151,7 +12230,7 @@ ${String(item.content)}` : String(item.content);
12151
12230
  for (const repo2 of this.repos.values()) {
12152
12231
  const tree = cp.trees.get(repo2.root);
12153
12232
  if (!tree) {
12154
- if (!repo2.dormant) untouched.push(repo2.repoFullName ?? repo2.localId ?? (0, import_path15.basename)(repo2.root));
12233
+ if (!repo2.dormant) untouched.push(repo2.repoFullName ?? repo2.localId ?? (0, import_path16.basename)(repo2.root));
12155
12234
  continue;
12156
12235
  }
12157
12236
  try {
@@ -12187,7 +12266,7 @@ ${String(item.content)}` : String(item.content);
12187
12266
  return;
12188
12267
  }
12189
12268
  const repo2 = picked.repo;
12190
- const label = repo2.repoFullName ?? repo2.localId ?? (0, import_path15.basename)(repo2.root);
12269
+ const label = repo2.repoFullName ?? repo2.localId ?? (0, import_path16.basename)(repo2.root);
12191
12270
  const cwd = repo2.fence ?? repo2.root;
12192
12271
  const detected = (this.deps.detectTests ? await this.deps.detectTests(cwd).catch(() => null) : null) ?? await detectTestCommandFromManifests(cwd).catch(() => null);
12193
12272
  if (!detected) {
@@ -12229,7 +12308,7 @@ ${String(item.content)}` : String(item.content);
12229
12308
  let output = "";
12230
12309
  let pending = "";
12231
12310
  let sent = 0;
12232
- const child = (0, import_child_process3.spawn)(
12311
+ const child = (0, import_child_process4.spawn)(
12233
12312
  process.platform === "win32" ? "cmd.exe" : "sh",
12234
12313
  process.platform === "win32" ? ["/d", "/s", "/c", command] : ["-c", command],
12235
12314
  { cwd, env, stdio: ["ignore", "pipe", "pipe"], windowsHide: true, detached: process.platform !== "win32" }
@@ -12393,7 +12472,7 @@ ${json}
12393
12472
  }
12394
12473
  /** The plugin with the repositories' and the organization's skills, rebuilt; the list goes to the screen when it changed. */
12395
12474
  async refreshSkills() {
12396
- const repos = [...this.repos.values()].filter((r) => !r.dormant).map((r) => ({ label: r.repoFullName ?? r.localId ?? (0, import_path15.basename)(r.root), root: r.fence ?? r.root }));
12475
+ const repos = [...this.repos.values()].filter((r) => !r.dormant).map((r) => ({ label: r.repoFullName ?? r.localId ?? (0, import_path16.basename)(r.root), root: r.fence ?? r.root }));
12397
12476
  try {
12398
12477
  this.skillEntries = [...await buildSkillsPlugin({ dir: this.skillsDir(), repos, organization: this.boot?.skills ?? [] }), ...this.orgPlugins.entries];
12399
12478
  } catch (e) {
@@ -12406,7 +12485,7 @@ ${json}
12406
12485
  this.emit({ type: "skills", data: { skills: this.skillEntries } });
12407
12486
  }
12408
12487
  skillsDir() {
12409
- return (0, import_path15.join)(this.deps.configDir, "sq-plugin");
12488
+ return (0, import_path16.join)(this.deps.configDir, "sq-plugin");
12410
12489
  }
12411
12490
  /**
12412
12491
  * The organization's MCP servers (their tools, from the API): one in-process server each, forwarding calls to
@@ -12454,7 +12533,7 @@ ${json}
12454
12533
  }
12455
12534
  }
12456
12535
  async runEnvSetup(repo2, command, secrets) {
12457
- const label = repo2.repoFullName ?? (0, import_path15.basename)(repo2.root);
12536
+ const label = repo2.repoFullName ?? (0, import_path16.basename)(repo2.root);
12458
12537
  const step = this.ownStep("setup", "command", `Preparing the environment of ${label}`, "STEP_ENV_SETUP", { repoFullName: label }, this.redactor.text(command));
12459
12538
  const started = Date.now();
12460
12539
  const { exitCode, output } = await this.runStreaming(command, repo2.fence ?? repo2.root, step.id, this.envAbort.signal, secrets);
@@ -12923,7 +13002,7 @@ ${text3}`;
12923
13002
  deniedRoots: this.deps.privateDirs,
12924
13003
  local: this.local,
12925
13004
  plan: t.planMode,
12926
- planDir: (0, import_path15.join)(this.deps.configDir, "plans"),
13005
+ planDir: (0, import_path16.join)(this.deps.configDir, "plans"),
12927
13006
  orgMcpTools: orgMcpToolNames(org ? t.off.mcpCatalog : null)
12928
13007
  },
12929
13008
  plan: t.planMode,
@@ -13223,7 +13302,7 @@ ${patch}`;
13223
13302
  this.pendingCheckpoints.delete(repo2.localId);
13224
13303
  repo2.dormant = !repo2.unsaved;
13225
13304
  }
13226
- const stateDir = this.deps.stateDir ?? (0, import_path15.join)(this.deps.scratch, `folder-${(0, import_crypto8.createHash)("sha256").update(local.root).digest("hex").slice(0, 16)}`);
13305
+ const stateDir = this.deps.stateDir ?? (0, import_path16.join)(this.deps.scratch, `folder-${(0, import_crypto8.createHash)("sha256").update(local.root).digest("hex").slice(0, 16)}`);
13227
13306
  const tracker = new LooseFileTracker({ root: local.root, stateDir, excluded: () => local.repos.map((r) => r.root) });
13228
13307
  const tracked = local.gitAvailable && await tracker.init().then(() => true, (e) => {
13229
13308
  this.deps.log.warn("loose files not tracked", { error: e.message });
@@ -13275,7 +13354,7 @@ ${patch}`;
13275
13354
  let owner = null;
13276
13355
  for (const r of this.repos.values()) {
13277
13356
  if (r.localId === LOOSE_REPO_ID) continue;
13278
- if ((abs === r.root || abs.startsWith(r.root + import_path15.sep)) && (!owner || r.root.length > owner.root.length)) owner = r;
13357
+ if ((abs === r.root || abs.startsWith(r.root + import_path16.sep)) && (!owner || r.root.length > owner.root.length)) owner = r;
13279
13358
  }
13280
13359
  if (owner) {
13281
13360
  if (owner.dormant) await this.wake(owner, true);
@@ -13314,7 +13393,7 @@ ${patch}`;
13314
13393
  if (!f || f.kind === "repo") return void 0;
13315
13394
  if (f.kind === "subfolder") return { kind: "subfolder", repoRoot: f.repoRoot ?? "", prefix: f.prefix };
13316
13395
  const repos = [...this.repos.values()].filter((r) => r.localId && r.localId !== LOOSE_REPO_ID).map((r) => ({
13317
- path: (0, import_path15.relative)(f.root, r.root).split(import_path15.sep).join("/"),
13396
+ path: (0, import_path16.relative)(f.root, r.root).split(import_path16.sep).join("/"),
13318
13397
  id: r.repoFullName ?? r.localId,
13319
13398
  repoFullName: r.repoFullName,
13320
13399
  projectId: r.repoFullName ? this.scope.repos.find((x) => x.repoFullName === r.repoFullName)?.projectId ?? null : null,
@@ -13370,13 +13449,36 @@ ${patch}`;
13370
13449
  // Offered only when the API creates images for this session (an older API never gets the call).
13371
13450
  ...this.boot?.imageGeneration && this.deps.transport.generateImage ? { generateImage: (a) => this.generateImage(a) } : {},
13372
13451
  // Offered only when the API searches for this session (an older API never gets the call).
13373
- ...this.boot?.webSearch && this.deps.transport.searchWeb ? { searchWeb: (q) => this.searchWeb(q) } : {}
13452
+ ...this.boot?.webSearch && this.deps.transport.searchWeb ? { searchWeb: (q) => this.searchWeb(q) } : {},
13453
+ screenshotApp: (a) => this.screenshotApp(a)
13374
13454
  };
13375
13455
  }
13376
13456
  /**
13377
13457
  * web_search (Erik, 20/10/2026: Code owes nothing to Claude Code): the API answers the query with a research model,
13378
13458
  * metered to the session's owner. What comes back is the web's text: the model reads it as data, never instructions.
13379
13459
  */
13460
+ /**
13461
+ * screenshot_app: a headless Chrome or Chromium (the workspace image's, or the person's own) draws the app the model
13462
+ * started and saves the PNG where the model asked (the policy fenced the path and the address); the model reads it
13463
+ * to check its work. No browser here is said plainly; nothing is retried.
13464
+ */
13465
+ async screenshotApp(args) {
13466
+ if (!LOCAL_APP_URL.test(args.url)) return text("screenshot_app opens only http://localhost:<port>/...", true);
13467
+ const target = await resolveInside(this.deps.root, args.filePath).catch(() => null);
13468
+ if (!target) return text(`screenshot_app saves only inside the workspace (${this.deps.root}). Nothing was saved.`, true);
13469
+ const chrome = findChrome();
13470
+ if (!chrome) return text("No Chrome or Chromium is installed here, so no screenshot was taken. The user sees the app in the conversation when you write ![App](its address); say so in one sentence.", true);
13471
+ try {
13472
+ await (0, import_promises15.mkdir)((0, import_path16.dirname)(target), { recursive: true });
13473
+ await screenshotPage(chrome, args.url, target, { width: args.width ?? 1280, height: args.height ?? 800 });
13474
+ } catch (e) {
13475
+ this.deps.log.warn("app screenshot failed", { error: this.redactor.text(e.message) });
13476
+ return text(`The screenshot of ${args.url} failed: is the app running there? Start it in the background and try again.`, true);
13477
+ }
13478
+ this.scheduleDiff();
13479
+ const rel = (0, import_path16.relative)(await (0, import_promises15.realpath)(this.deps.root).catch(() => this.deps.root), target).split(import_path16.sep).join("/");
13480
+ return text(`Saved the screenshot to ${rel}. Read it to see the app, and show it as ![short description](${rel}) when it helps the user.`);
13481
+ }
13380
13482
  async searchWeb(query) {
13381
13483
  const transport = this.deps.transport;
13382
13484
  if (!transport.searchWeb) return text("Web search is not available in this session. Tell the user plainly.", true);
@@ -13412,7 +13514,7 @@ ${sources}` : ""}`);
13412
13514
  try {
13413
13515
  const image = await readImageFile(root, await insideRelative(args.editPath, [root]), this.deps.privateDirs ?? []);
13414
13516
  if (!["image/png", "image/jpeg", "image/webp"].includes(image.mime)) return text(`${args.editPath} is not a PNG, JPEG or WebP image, so it cannot be changed. Nothing was created.`, true);
13415
- reference = { name: (0, import_path15.basename)(image.path), mimeType: image.mime, sizeBytes: image.size, dataBase64: image.data };
13517
+ reference = { name: (0, import_path16.basename)(image.path), mimeType: image.mime, sizeBytes: image.size, dataBase64: image.data };
13416
13518
  } catch (e) {
13417
13519
  return text(`Could not read ${args.editPath} to change it (${e instanceof FileRequestError ? e.code : "UNREADABLE"}). Nothing was created.`, true);
13418
13520
  }
@@ -13427,10 +13529,10 @@ ${sources}` : ""}`);
13427
13529
  if ("refused" in created) return text(`The image could not be created (${created.refused}). Tell the user plainly in one sentence; do not claim an image was made and do not offer a prompt for another tool instead.`, true);
13428
13530
  const bytes = Buffer.from(created.dataBase64, "base64");
13429
13531
  if (!bytes.length || !imageMime(bytes)) return text("The image model returned something that is not an image. Nothing was saved.", true);
13430
- await (0, import_promises14.mkdir)((0, import_path15.dirname)(target), { recursive: true });
13431
- await (0, import_promises14.writeFile)(target, bytes);
13532
+ await (0, import_promises15.mkdir)((0, import_path16.dirname)(target), { recursive: true });
13533
+ await (0, import_promises15.writeFile)(target, bytes);
13432
13534
  this.scheduleDiff();
13433
- const rel = (0, import_path15.relative)(await (0, import_promises14.realpath)(root).catch(() => root), target).split(import_path15.sep).join("/");
13535
+ const rel = (0, import_path16.relative)(await (0, import_promises15.realpath)(root).catch(() => root), target).split(import_path16.sep).join("/");
13434
13536
  return text(`Saved the image to ${rel}. Show it to the user in your answer as ![short description](${rel}) and say in one or two sentences what you made.`);
13435
13537
  }
13436
13538
  listRepositories() {
@@ -13509,7 +13611,7 @@ ${JSON.stringify(data, null, 1)}`);
13509
13611
  const repo2 = picked.repo;
13510
13612
  const refused = this.notInScope(repo2);
13511
13613
  if (refused) {
13512
- say2("REPOSITORY_NOT_IN_SCOPE", { repoFullName: repo2.repoFullName ?? (0, import_path15.basename)(repo2.root) });
13614
+ say2("REPOSITORY_NOT_IN_SCOPE", { repoFullName: repo2.repoFullName ?? (0, import_path16.basename)(repo2.root) });
13513
13615
  return text(refused, true);
13514
13616
  }
13515
13617
  if (!repo2.measurer) {
@@ -13530,7 +13632,7 @@ ${JSON.stringify(data, null, 1)}`);
13530
13632
  return text("The measurement did not finish in time. Say it was not measured; do not estimate.", true);
13531
13633
  }
13532
13634
  if ("empty" in r) {
13533
- say2("NOTHING_TO_MEASURE", { repoFullName: repo2.repoFullName ?? (0, import_path15.basename)(repo2.root) });
13635
+ say2("NOTHING_TO_MEASURE", { repoFullName: repo2.repoFullName ?? (0, import_path16.basename)(repo2.root) });
13534
13636
  return text(`There is no change in ${repo2.repoFullName} to measure.`);
13535
13637
  }
13536
13638
  this.emit({ type: "measurement", data: { ...r.data, ...repo2.repoFullName ? { repoFullName: repo2.repoFullName } : {} } });
@@ -13724,7 +13826,7 @@ var scrubAvailable = null;
13724
13826
  function subprocessScrubAvailable() {
13725
13827
  if (scrubAvailable !== null) return scrubAvailable;
13726
13828
  if (process.platform !== "linux") return scrubAvailable = true;
13727
- const r = (0, import_child_process3.spawnSync)("bwrap", ["--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc", "true"], { timeout: 5e3, stdio: "ignore" });
13829
+ const r = (0, import_child_process4.spawnSync)("bwrap", ["--ro-bind", "/", "/", "--dev", "/dev", "--proc", "/proc", "true"], { timeout: 5e3, stdio: "ignore" });
13728
13830
  return scrubAvailable = !r.error && r.status === 0;
13729
13831
  }
13730
13832
  function buildEngineEnv(boot, configDir, model, opts = {}) {
@@ -13752,7 +13854,7 @@ function buildEngineEnv(boot, configDir, model, opts = {}) {
13752
13854
  Object.assign(env, {
13753
13855
  CLAUDE_CONFIG_DIR: configDir,
13754
13856
  // Background command output lands here, inside the config dir the policy lets Read read.
13755
- CLAUDE_CODE_TMPDIR: (0, import_path15.join)(configDir, "tmp"),
13857
+ CLAUDE_CODE_TMPDIR: (0, import_path16.join)(configDir, "tmp"),
13756
13858
  CLAUDE_AGENT_SDK_CLIENT_APP: opts.local ? "scalequality-cli-connect/1.0" : "scalequality-workspace/1.0",
13757
13859
  DISABLE_TELEMETRY: "1",
13758
13860
  DISABLE_ERROR_REPORTING: "1",
@@ -13788,7 +13890,7 @@ function buildQueryOptions(o) {
13788
13890
  const dir = o.policy.planDir;
13789
13891
  if (!file || !dir || !file.endsWith(".md")) return "";
13790
13892
  const abs = await resolveInside(dir, file);
13791
- return abs ? (0, import_promises14.readFile)(abs, "utf8").catch(() => "") : "";
13893
+ return abs ? (0, import_promises15.readFile)(abs, "utf8").catch(() => "") : "";
13792
13894
  };
13793
13895
  const preToolUse = async (input) => {
13794
13896
  if (input.hook_event_name !== "PreToolUse") return {};
@@ -13846,7 +13948,7 @@ function buildQueryOptions(o) {
13846
13948
  const changes = !o.plan && toolName in WRITE_TOOL_PATHS;
13847
13949
  const changePath = () => {
13848
13950
  const field = WRITE_TOOL_PATHS[toolName];
13849
- return typeof d.updatedInput[field] === "string" ? (0, import_path15.relative)(o.root, d.updatedInput[field]) || String(d.updatedInput[field]) : "";
13951
+ return typeof d.updatedInput[field] === "string" ? (0, import_path16.relative)(o.root, d.updatedInput[field]) || String(d.updatedInput[field]) : "";
13850
13952
  };
13851
13953
  if (changes && protections.toolApproval) {
13852
13954
  const path = changePath(), askEdit = o.protection?.approver?.askEdit;
@@ -13923,15 +14025,15 @@ function timedGate(gate, took) {
13923
14025
  }
13924
14026
  async function hasLocalTranscript(configDir, sessionId) {
13925
14027
  if (!/^[A-Za-z0-9-]{8,80}$/.test(sessionId)) return false;
13926
- const projects = (0, import_path15.join)(configDir, "projects");
13927
- const dirs = await (0, import_promises14.readdir)(projects).catch(() => []);
13928
- return dirs.some((d) => (0, import_fs9.existsSync)((0, import_path15.join)(projects, d, `${sessionId}.jsonl`)));
14028
+ const projects = (0, import_path16.join)(configDir, "projects");
14029
+ const dirs = await (0, import_promises15.readdir)(projects).catch(() => []);
14030
+ return dirs.some((d) => (0, import_fs10.existsSync)((0, import_path16.join)(projects, d, `${sessionId}.jsonl`)));
13929
14031
  }
13930
14032
 
13931
14033
  // src/application/services/workspaceSandbox/selfUpdate.ts
13932
14034
  var import_crypto9 = require("crypto");
13933
- var import_fs10 = require("fs");
13934
- var import_path16 = require("path");
14035
+ var import_fs11 = require("fs");
14036
+ var import_path17 = require("path");
13935
14037
  var REGISTRY_LATEST = "https://registry.npmjs.org/@scalequality%2fcli/latest";
13936
14038
  var UPDATE_FIRST_CHECK_MS = 2 * 6e4;
13937
14039
  var UPDATE_INTERVAL_MS = 6 * 36e5;
@@ -13940,10 +14042,10 @@ var UPDATE_RETRY_MS = 15 * 6e4;
13940
14042
  var CONFIRM_AFTER_MS = 9e4;
13941
14043
  var RESTART_EXIT_CODE = 75;
13942
14044
  var MAX_START_ATTEMPTS = 3;
13943
- var cliDir = (home) => (0, import_path16.join)(sqHome(home), "cli");
13944
- var launcherPath = (home) => (0, import_path16.join)(cliDir(home), "launcher.mjs");
13945
- var currentPath = (home) => (0, import_path16.join)(cliDir(home), "current.json");
13946
- var autoUpdateOffPath = (home) => (0, import_path16.join)(cliDir(home), "auto-update-off");
14045
+ var cliDir = (home) => (0, import_path17.join)(sqHome(home), "cli");
14046
+ var launcherPath = (home) => (0, import_path17.join)(cliDir(home), "launcher.mjs");
14047
+ var currentPath = (home) => (0, import_path17.join)(cliDir(home), "current.json");
14048
+ var autoUpdateOffPath = (home) => (0, import_path17.join)(cliDir(home), "auto-update-off");
13947
14049
  var VERSION = /^(\d{1,5})\.(\d{1,5})\.(\d{1,5})$/;
13948
14050
  function isNewer(candidate, current) {
13949
14051
  const a = VERSION.exec(candidate), b = VERSION.exec(current);
@@ -13956,18 +14058,18 @@ function isNewer(candidate, current) {
13956
14058
  }
13957
14059
  function readJson2(file) {
13958
14060
  try {
13959
- const v = JSON.parse((0, import_fs10.readFileSync)(file, "utf8"));
14061
+ const v = JSON.parse((0, import_fs11.readFileSync)(file, "utf8"));
13960
14062
  return v && typeof v === "object" ? v : null;
13961
14063
  } catch {
13962
14064
  return null;
13963
14065
  }
13964
14066
  }
13965
14067
  function writeJsonAtomic(file, value) {
13966
- (0, import_fs10.mkdirSync)((0, import_path16.dirname)(file), { recursive: true, mode: 448 });
14068
+ (0, import_fs11.mkdirSync)((0, import_path17.dirname)(file), { recursive: true, mode: 448 });
13967
14069
  const tmp = `${file}.${process.pid}.tmp`;
13968
- (0, import_fs10.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}
14070
+ (0, import_fs11.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}
13969
14071
  `, { mode: 384 });
13970
- (0, import_fs10.renameSync)(tmp, file);
14072
+ (0, import_fs11.renameSync)(tmp, file);
13971
14073
  }
13972
14074
  var installed = (v) => {
13973
14075
  const r = v;
@@ -14023,25 +14125,30 @@ function installLauncher(home, copy) {
14023
14125
  writeJsonAtomic(currentPath(home), { version: copy.version, bin: copy.bin, previous: null, pending: false, attempts: 0, rejected: current?.rejected ?? [] });
14024
14126
  const file = launcherPath(home);
14025
14127
  const tmp = `${file}.${process.pid}.tmp`;
14026
- (0, import_fs10.writeFileSync)(tmp, launcherSource(copy.bin), { mode: 420 });
14027
- (0, import_fs10.renameSync)(tmp, file);
14128
+ (0, import_fs11.writeFileSync)(tmp, launcherSource(copy.bin), { mode: 420 });
14129
+ (0, import_fs11.renameSync)(tmp, file);
14028
14130
  return file;
14029
14131
  }
14030
14132
  function confirmCurrent(home, version) {
14031
14133
  const current = readCurrent(home);
14032
14134
  if (!current || current.version !== version || !current.pending) return false;
14033
14135
  writeJsonAtomic(currentPath(home), { ...current, pending: false, attempts: 0, previous: null });
14034
- removeStableCopies(home, [version]);
14136
+ removeStableCopies(home, [version, ...current.previous ? [current.previous.version] : []]);
14035
14137
  return true;
14036
14138
  }
14139
+ function behindCurrent(home, version) {
14140
+ const current = readCurrent(home);
14141
+ if (!current || current.pending || current.version === version || !isNewer(current.version, version) || !(0, import_fs11.existsSync)(current.bin)) return null;
14142
+ return current.version;
14143
+ }
14037
14144
  function autoUpdateEnabled(home, env) {
14038
- return env.SCALEQUALITY_NO_AUTO_UPDATE !== "1" && !(0, import_fs10.existsSync)(autoUpdateOffPath(home));
14145
+ return env.SCALEQUALITY_NO_AUTO_UPDATE !== "1" && !(0, import_fs11.existsSync)(autoUpdateOffPath(home));
14039
14146
  }
14040
14147
  function setAutoUpdate(home, on) {
14041
- if (on) (0, import_fs10.rmSync)(autoUpdateOffPath(home), { force: true });
14148
+ if (on) (0, import_fs11.rmSync)(autoUpdateOffPath(home), { force: true });
14042
14149
  else {
14043
- (0, import_fs10.mkdirSync)(cliDir(home), { recursive: true, mode: 448 });
14044
- (0, import_fs10.writeFileSync)(autoUpdateOffPath(home), `${(/* @__PURE__ */ new Date()).toISOString()}
14150
+ (0, import_fs11.mkdirSync)(cliDir(home), { recursive: true, mode: 448 });
14151
+ (0, import_fs11.writeFileSync)(autoUpdateOffPath(home), `${(/* @__PURE__ */ new Date()).toISOString()}
14045
14152
  `, { mode: 384 });
14046
14153
  }
14047
14154
  }
@@ -14055,20 +14162,20 @@ async function latestVersion(fetchImpl = fetch) {
14055
14162
  return null;
14056
14163
  }
14057
14164
  }
14058
- function npmCli(nodePath, exists = import_fs10.existsSync, resolve8 = realOrNull, pathEnv = process.env.PATH ?? "") {
14059
- const dir = (0, import_path16.dirname)(nodePath);
14165
+ function npmCli(nodePath, exists = import_fs11.existsSync, resolve8 = realOrNull, pathEnv = process.env.PATH ?? "") {
14166
+ const dir = (0, import_path17.dirname)(nodePath);
14060
14167
  const script = (p) => p && /npm-cli\.js$/.test(p) && exists(p) ? p : null;
14061
14168
  const linked = (p) => exists(p) ? script(resolve8(p)) : null;
14062
14169
  const layouts = [
14063
- (0, import_path16.join)(dir, "node_modules", "npm", "bin", "npm-cli.js"),
14064
- (0, import_path16.join)(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
14065
- (0, import_path16.join)(dir, "..", "libexec", "lib", "node_modules", "npm", "bin", "npm-cli.js")
14170
+ (0, import_path17.join)(dir, "node_modules", "npm", "bin", "npm-cli.js"),
14171
+ (0, import_path17.join)(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
14172
+ (0, import_path17.join)(dir, "..", "libexec", "lib", "node_modules", "npm", "bin", "npm-cli.js")
14066
14173
  ];
14067
- return linked((0, import_path16.join)(dir, "npm")) ?? layouts.map(script).find(Boolean) ?? pathEnv.split(import_path16.delimiter).filter(Boolean).map((d) => linked((0, import_path16.join)(d, "npm"))).find(Boolean) ?? null;
14174
+ return linked((0, import_path17.join)(dir, "npm")) ?? layouts.map(script).find(Boolean) ?? pathEnv.split(import_path17.delimiter).filter(Boolean).map((d) => linked((0, import_path17.join)(d, "npm"))).find(Boolean) ?? null;
14068
14175
  }
14069
14176
  function realOrNull(p) {
14070
14177
  try {
14071
- return (0, import_fs10.realpathSync)(p);
14178
+ return (0, import_fs11.realpathSync)(p);
14072
14179
  } catch {
14073
14180
  return null;
14074
14181
  }
@@ -14088,11 +14195,11 @@ function npmReason(output) {
14088
14195
  async function installVersion(o) {
14089
14196
  const npm = o.npm === void 0 ? npmCli(o.nodePath) : o.npm;
14090
14197
  if (!npm) throw new UpdateError("NPM_NOT_FOUND", `npm was not found next to ${o.nodePath}.`);
14091
- const staging = (0, import_path16.join)(cliDir(o.home), `.update-${o.version}`);
14092
- (0, import_fs10.rmSync)(staging, { recursive: true, force: true });
14093
- (0, import_fs10.mkdirSync)(staging, { recursive: true, mode: 448 });
14198
+ const staging = (0, import_path17.join)(cliDir(o.home), `.update-${o.version}`);
14199
+ (0, import_fs11.rmSync)(staging, { recursive: true, force: true });
14200
+ (0, import_fs11.mkdirSync)(staging, { recursive: true, mode: 448 });
14094
14201
  try {
14095
- (0, import_fs10.writeFileSync)((0, import_path16.join)(staging, "package.json"), '{ "private": true }\n');
14202
+ (0, import_fs11.writeFileSync)((0, import_path17.join)(staging, "package.json"), '{ "private": true }\n');
14096
14203
  const r = await o.run(o.nodePath, [
14097
14204
  npm,
14098
14205
  "install",
@@ -14107,12 +14214,12 @@ async function installVersion(o) {
14107
14214
  `@scalequality/cli@${o.version}`
14108
14215
  ]);
14109
14216
  if (r.code !== 0) throw new UpdateError("NPM_INSTALL_FAILED", `npm could not install @scalequality/cli@${o.version}: ${npmReason(`${r.stderr || r.stdout}`) || r.code}`);
14110
- const root = (0, import_path16.join)(staging, "node_modules", "@scalequality", "cli");
14111
- const pkg = readJson2((0, import_path16.join)(root, "package.json"));
14217
+ const root = (0, import_path17.join)(staging, "node_modules", "@scalequality", "cli");
14218
+ const pkg = readJson2((0, import_path17.join)(root, "package.json"));
14112
14219
  if (pkg?.name !== "@scalequality/cli" || pkg?.version !== o.version) throw new UpdateError("WRONG_PACKAGE", `npm installed ${String(pkg?.name)}@${String(pkg?.version)} instead of ${o.version}.`);
14113
- const build = readJson2((0, import_path16.join)(root, "dist", "connect.build.json"));
14114
- const bundle = (0, import_path16.join)(root, "dist", "connect.cjs");
14115
- const sha = (0, import_fs10.existsSync)(bundle) ? (0, import_crypto9.createHash)("sha256").update((0, import_fs10.readFileSync)(bundle)).digest("hex") : null;
14220
+ const build = readJson2((0, import_path17.join)(root, "dist", "connect.build.json"));
14221
+ const bundle = (0, import_path17.join)(root, "dist", "connect.cjs");
14222
+ const sha = (0, import_fs11.existsSync)(bundle) ? (0, import_crypto9.createHash)("sha256").update((0, import_fs11.readFileSync)(bundle)).digest("hex") : null;
14116
14223
  if (!sha || typeof build?.sha256 !== "string" || build.sha256 !== sha) throw new UpdateError("BUNDLE_MISMATCH", `The engine of ${o.version} does not match the sha256 it declares.`);
14117
14224
  let copy;
14118
14225
  try {
@@ -14122,7 +14229,7 @@ async function installVersion(o) {
14122
14229
  }
14123
14230
  return { version: copy.version, bin: copy.bin };
14124
14231
  } finally {
14125
- (0, import_fs10.rmSync)(staging, { recursive: true, force: true });
14232
+ (0, import_fs11.rmSync)(staging, { recursive: true, force: true });
14126
14233
  }
14127
14234
  }
14128
14235
  function stageVersion(home, next, running) {
@@ -14154,7 +14261,7 @@ async function checkForUpdate(d) {
14154
14261
  }
14155
14262
 
14156
14263
  // src/main/workspace-connect.ts
14157
- var import_child_process4 = require("child_process");
14264
+ var import_child_process5 = require("child_process");
14158
14265
  var importEsm = new Function("specifier", "return import(specifier)");
14159
14266
  async function loadSdk() {
14160
14267
  const resolved = require.resolve("@anthropic-ai/claude-agent-sdk");
@@ -14168,10 +14275,10 @@ var say = (line = "") => {
14168
14275
  };
14169
14276
  var cliVersion = process.env.SCALEQUALITY_CLI_VERSION || "dev";
14170
14277
  var userAgent = (mode) => `scalequality-cli/${cliVersion} (${mode}; node ${process.versions.node}; ${process.platform})`;
14171
- var HOME = (0, import_os4.homedir)();
14172
- var SQ_HOME = (0, import_path17.join)(HOME, ".scalequality");
14173
- var ENGINE_HOME = (0, import_path17.join)(SQ_HOME, "workspace");
14174
- var credentials = new CredentialStore((0, import_path17.join)(SQ_HOME, "credentials.json"));
14278
+ var HOME = (0, import_os5.homedir)();
14279
+ var SQ_HOME = (0, import_path18.join)(HOME, ".scalequality");
14280
+ var ENGINE_HOME = (0, import_path18.join)(SQ_HOME, "workspace");
14281
+ var credentials = new CredentialStore((0, import_path18.join)(SQ_HOME, "credentials.json"));
14175
14282
  function startFailure(e, api) {
14176
14283
  if (e instanceof NotLocalSessionError) return 'This code belongs to a cloud workspace session. In the AI Workspace, choose "Use a folder on your computer" to get a code for this command.';
14177
14284
  if (e instanceof SessionGoneError) {
@@ -14184,16 +14291,16 @@ function startFailure(e, api) {
14184
14291
  return "ScaleQuality could not start this session. Try again in a moment, or get a new code from the AI Workspace.";
14185
14292
  }
14186
14293
  function engineDirs() {
14187
- const configDir = (0, import_path17.join)(ENGINE_HOME, "claude-home");
14188
- const scratch = (0, import_path17.join)(ENGINE_HOME, "tmp");
14189
- (0, import_fs11.mkdirSync)(configDir, { recursive: true, mode: 448 });
14190
- (0, import_fs11.mkdirSync)(scratch, { recursive: true, mode: 448 });
14294
+ const configDir = (0, import_path18.join)(ENGINE_HOME, "claude-home");
14295
+ const scratch = (0, import_path18.join)(ENGINE_HOME, "tmp");
14296
+ (0, import_fs12.mkdirSync)(configDir, { recursive: true, mode: 448 });
14297
+ (0, import_fs12.mkdirSync)(scratch, { recursive: true, mode: 448 });
14191
14298
  return { configDir, scratch };
14192
14299
  }
14193
14300
  function createLocalEngine(o) {
14194
14301
  const { configDir, scratch } = engineDirs();
14195
- const sessions = (0, import_path17.join)(ENGINE_HOME, "sessions");
14196
- const stateDir = (0, import_path17.join)(sessions, o.sessionId);
14302
+ const sessions = (0, import_path18.join)(ENGINE_HOME, "sessions");
14303
+ const stateDir = (0, import_path18.join)(sessions, o.sessionId);
14197
14304
  void pruneSessionStates(sessions, stateDir).catch(() => void 0);
14198
14305
  const log = {
14199
14306
  info: (msg, ctx) => {
@@ -14340,7 +14447,7 @@ function chooseApi(explicit) {
14340
14447
  const saved = credentials.apis();
14341
14448
  return saved.length === 1 ? saved[0] : DEFAULT_API;
14342
14449
  }
14343
- var osLabel = () => `${(0, import_os4.platform)()} ${(0, import_os4.release)()}`.slice(0, 100);
14450
+ var osLabel = () => `${(0, import_os5.platform)()} ${(0, import_os5.release)()}`.slice(0, 100);
14344
14451
  function usageError(command, message) {
14345
14452
  say(style.red(message));
14346
14453
  say();
@@ -14350,7 +14457,7 @@ function usageError(command, message) {
14350
14457
  function serviceEnv() {
14351
14458
  let user = process.env.USER || process.env.USERNAME || "";
14352
14459
  try {
14353
- user = (0, import_os4.userInfo)().username || user;
14460
+ user = (0, import_os5.userInfo)().username || user;
14354
14461
  } catch {
14355
14462
  }
14356
14463
  return {
@@ -14404,7 +14511,7 @@ async function loginMain(api, name, thenUp, verbose) {
14404
14511
  const client = new MachineClient(api, { userAgent: userAgent("login") });
14405
14512
  let auth;
14406
14513
  try {
14407
- auth = await client.authorize({ name: name ?? ((0, import_os4.hostname)() || "Computer").slice(0, 100), os: osLabel(), cliVersion });
14514
+ auth = await client.authorize({ name: name ?? ((0, import_os5.hostname)() || "Computer").slice(0, 100), os: osLabel(), cliVersion });
14408
14515
  } catch (e) {
14409
14516
  say(style.red(e instanceof MachineApiError && e.status === null ? `Could not reach ScaleQuality at ${api}.` : "ScaleQuality could not start the login. Try again in a moment."));
14410
14517
  process.exit(1);
@@ -14433,7 +14540,7 @@ async function loginMain(api, name, thenUp, verbose) {
14433
14540
  machineId: token.machineId,
14434
14541
  machineToken: token.machineToken,
14435
14542
  orgId: token.orgId,
14436
- name: name ?? ((0, import_os4.hostname)() || "Computer").slice(0, 100),
14543
+ name: name ?? ((0, import_os5.hostname)() || "Computer").slice(0, 100),
14437
14544
  folders: previous?.folders ?? [],
14438
14545
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
14439
14546
  });
@@ -14493,7 +14600,7 @@ async function upMain(api, verbose, service) {
14493
14600
  let shuttingDown = false;
14494
14601
  let agent;
14495
14602
  const startSession = ({ sessionId, secret, root }) => {
14496
- const label = (0, import_path17.basename)(root);
14603
+ const label = (0, import_path18.basename)(root);
14497
14604
  const prefix = style.dim(`[${label}] `);
14498
14605
  const consoleLog = new ConsoleLog(style);
14499
14606
  let resolveDone = () => void 0;
@@ -14547,7 +14654,7 @@ async function upMain(api, verbose, service) {
14547
14654
  agent.stop();
14548
14655
  releaseLock(HOME, api);
14549
14656
  if (process.platform === "win32") {
14550
- (0, import_child_process4.spawn)(process.execPath, [launcherPath(HOME), "up", "--api", api, "--service"], { detached: true, stdio: "ignore", windowsHide: true, env: process.env }).unref();
14657
+ (0, import_child_process5.spawn)(process.execPath, [launcherPath(HOME), "up", "--api", api, "--service"], { detached: true, stdio: "ignore", windowsHide: true, env: process.env }).unref();
14551
14658
  process.exit(0);
14552
14659
  }
14553
14660
  process.exit(RESTART_EXIT_CODE);
@@ -14579,7 +14686,7 @@ async function upMain(api, verbose, service) {
14579
14686
  function startSelfUpdate(api, busy, restart) {
14580
14687
  const confirm = setTimeout(() => {
14581
14688
  try {
14582
- if (confirmCurrent(HOME, cliVersion)) say(`Version ${cliVersion} started well; the previous copy was removed.`);
14689
+ if (confirmCurrent(HOME, cliVersion)) say(`Version ${cliVersion} started well.`);
14583
14690
  } catch (e) {
14584
14691
  say(`[warn] version ${cliVersion} not confirmed: ${e.message}`);
14585
14692
  }
@@ -14610,11 +14717,22 @@ function startSelfUpdate(api, busy, restart) {
14610
14717
  };
14611
14718
  setTimeout(() => void check(), UPDATE_FIRST_CHECK_MS).unref();
14612
14719
  setInterval(() => void check(), UPDATE_INTERVAL_MS).unref();
14720
+ if (process.env.SCALEQUALITY_LAUNCHER === "1") setInterval(() => {
14721
+ try {
14722
+ const next = !busy() ? behindCurrent(HOME, cliVersion) : null;
14723
+ if (next) {
14724
+ say(`This computer moved to version ${next}; restarting on it.`);
14725
+ restart();
14726
+ }
14727
+ } catch (e) {
14728
+ say(`[warn] version check failed: ${e.message}`);
14729
+ }
14730
+ }, UPDATE_IDLE_POLL_MS).unref();
14613
14731
  say(style.dim(autoUpdateEnabled(HOME, process.env) ? ` Updates install by themselves while no session runs (scalequality service auto-update off${apiFlag(api)} to stop).` : " Automatic updates are off."));
14614
14732
  }
14615
14733
  async function addMain(api, path) {
14616
14734
  try {
14617
- const real = await addFolder(credentials, api, (0, import_path17.resolve)(path ?? process.cwd()), HOME);
14735
+ const real = await addFolder(credentials, api, (0, import_path18.resolve)(path ?? process.cwd()), HOME);
14618
14736
  say(style.green(`Added ${real}.`));
14619
14737
  const credential = credentials.get(api);
14620
14738
  const client = new MachineClient(api, { token: credential.machineToken, userAgent: userAgent("add") });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scalequality/cli",
3
- "version": "0.4.25",
3
+ "version": "0.4.27",
4
4
  "description": "ScaleQuality CLI. Connect your computer to the ScaleQuality AI Workspace (`scalequality login`), run its coding engine in any folder of your computer, and import your Claude Code and Codex conversations.",
5
5
  "type": "module",
6
6
  "bin": {