@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.
- package/dist/connect.build.json +2 -2
- package/dist/connect.cjs +321 -203
- package/package.json +1 -1
package/dist/connect.build.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sdkVersion": "0.3.281",
|
|
3
|
-
"sha256": "
|
|
4
|
-
"sourceCommit": "
|
|
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
|
|
32
|
-
var
|
|
33
|
-
var
|
|
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
|
|
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
|
|
4921
|
-
var
|
|
4922
|
-
var
|
|
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
|
|
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
|
|
9550
|
-
var
|
|
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,
|
|
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,
|
|
9562
|
-
const st = await (0,
|
|
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,
|
|
9565
|
-
if (!real || (0,
|
|
9566
|
-
const buf = await (0,
|
|
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
|
|
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,
|
|
9977
|
-
const r = (0,
|
|
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,
|
|
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,
|
|
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 ``: 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 ``: 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
|
-
|
|
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 ``: 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 ``: 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
|
|
10400
|
-
var
|
|
10401
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
10482
|
-
var
|
|
10483
|
-
var
|
|
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,
|
|
10493
|
-
for (const dir of await (0,
|
|
10494
|
-
const file = (0,
|
|
10495
|
-
const st = await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
10541
|
-
await (0,
|
|
10542
|
-
const target = (0,
|
|
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,
|
|
10546
|
-
await (0,
|
|
10547
|
-
await (0,
|
|
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,
|
|
10628
|
+
await (0, import_promises12.rm)(partial, { force: true }).catch(() => void 0);
|
|
10550
10629
|
return false;
|
|
10551
10630
|
}
|
|
10552
|
-
return (0,
|
|
10631
|
+
return (0, import_fs9.existsSync)(target);
|
|
10553
10632
|
}
|
|
10554
10633
|
|
|
10555
10634
|
// src/application/services/workspaceSandbox/workspaceFiles.ts
|
|
10556
|
-
var
|
|
10557
|
-
var
|
|
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,
|
|
10572
|
-
const r = (0,
|
|
10573
|
-
if (r.split(
|
|
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,
|
|
10576
|
-
if (abs === realDenied || abs.startsWith(realDenied +
|
|
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(
|
|
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,
|
|
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,
|
|
10586
|
-
const dirents = await (0,
|
|
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,
|
|
10591
|
-
if (deniedReal.some((x) => child === x || child.startsWith(x +
|
|
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,
|
|
10596
|
-
const target = inside3 ? await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
10732
|
+
if (!(0, import_path14.isAbsolute)(clean)) return clean.replace(/^\.\//, "");
|
|
10654
10733
|
for (const base of bases) {
|
|
10655
|
-
for (const root of [(0,
|
|
10656
|
-
const rel = (0,
|
|
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(`..${
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
10765
|
-
var
|
|
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,
|
|
10888
|
+
const st = await (0, import_promises14.lstat)(abs).catch(() => null);
|
|
10810
10889
|
if (!st?.isFile()) return false;
|
|
10811
|
-
const real = await (0,
|
|
10812
|
-
return !!real && !(0,
|
|
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,
|
|
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,
|
|
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,
|
|
10905
|
+
const abs = (0, import_path15.join)(src, r);
|
|
10827
10906
|
if (!await regularFile(abs, realRoot)) continue;
|
|
10828
|
-
const buf = await (0,
|
|
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,
|
|
10831
|
-
await (0,
|
|
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,
|
|
10841
|
-
await (0,
|
|
10842
|
-
await (0,
|
|
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,
|
|
10854
|
-
await (0,
|
|
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,
|
|
10939
|
+
const realRoot = await (0, import_promises14.realpath)(repo2.root).catch(() => null);
|
|
10861
10940
|
if (!realRoot) continue;
|
|
10862
|
-
const skillsDir = (0,
|
|
10863
|
-
for (const e of (await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
10873
|
-
await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
10889
|
-
for (const e of (await (0,
|
|
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,
|
|
10970
|
+
const file = (0, import_path15.join)(commandsDir, e.name);
|
|
10892
10971
|
if (!await regularFile(file, realRoot)) continue;
|
|
10893
|
-
const buf = await (0,
|
|
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,
|
|
10905
|
-
await (0,
|
|
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,
|
|
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,
|
|
10950
|
-
await (0,
|
|
10951
|
-
await (0,
|
|
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,
|
|
10954
|
-
if ((0,
|
|
10955
|
-
await (0,
|
|
10956
|
-
await (0,
|
|
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,
|
|
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,
|
|
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,
|
|
11419
|
-
await (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,
|
|
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,
|
|
11556
|
+
const privateDirs = (this.deps.privateDirs ?? []).map((d) => (0, import_path16.resolve)(d));
|
|
11478
11557
|
const taken = (name2) => {
|
|
11479
|
-
const dir = (0,
|
|
11480
|
-
return this.repos.has(dir) || privateDirs.includes(dir) || (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 +
|
|
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,
|
|
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 ; 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  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,
|
|
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,
|
|
13431
|
-
await (0,
|
|
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,
|
|
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  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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
13927
|
-
const dirs = await (0,
|
|
13928
|
-
return dirs.some((d) => (0,
|
|
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
|
|
13934
|
-
var
|
|
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,
|
|
13944
|
-
var launcherPath = (home) => (0,
|
|
13945
|
-
var currentPath = (home) => (0,
|
|
13946
|
-
var autoUpdateOffPath = (home) => (0,
|
|
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,
|
|
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,
|
|
14068
|
+
(0, import_fs11.mkdirSync)((0, import_path17.dirname)(file), { recursive: true, mode: 448 });
|
|
13967
14069
|
const tmp = `${file}.${process.pid}.tmp`;
|
|
13968
|
-
(0,
|
|
14070
|
+
(0, import_fs11.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}
|
|
13969
14071
|
`, { mode: 384 });
|
|
13970
|
-
(0,
|
|
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,
|
|
14027
|
-
(0,
|
|
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,
|
|
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,
|
|
14148
|
+
if (on) (0, import_fs11.rmSync)(autoUpdateOffPath(home), { force: true });
|
|
14042
14149
|
else {
|
|
14043
|
-
(0,
|
|
14044
|
-
(0,
|
|
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 =
|
|
14059
|
-
const dir = (0,
|
|
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,
|
|
14064
|
-
(0,
|
|
14065
|
-
(0,
|
|
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,
|
|
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,
|
|
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,
|
|
14092
|
-
(0,
|
|
14093
|
-
(0,
|
|
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,
|
|
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,
|
|
14111
|
-
const pkg = readJson2((0,
|
|
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,
|
|
14114
|
-
const bundle = (0,
|
|
14115
|
-
const sha = (0,
|
|
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,
|
|
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
|
|
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,
|
|
14172
|
-
var SQ_HOME = (0,
|
|
14173
|
-
var ENGINE_HOME = (0,
|
|
14174
|
-
var credentials = new CredentialStore((0,
|
|
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,
|
|
14188
|
-
const scratch = (0,
|
|
14189
|
-
(0,
|
|
14190
|
-
(0,
|
|
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,
|
|
14196
|
-
const stateDir = (0,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
|
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,
|
|
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.
|
|
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": {
|