@scalequality/cli 0.4.6 → 0.4.7
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 +41 -11
- 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": "03afc2ab7b4193895bb197e5d3594b5733ff18868f79e0b26ea9d3f08ae3dec7",
|
|
4
|
+
"sourceCommit": "3975ff27d27caf9615cc9ff288a2cbeab1bced2b"
|
|
5
5
|
}
|
package/dist/connect.cjs
CHANGED
|
@@ -1960,9 +1960,11 @@ var SQ_READ_ONLY = /* @__PURE__ */ new Set([
|
|
|
1960
1960
|
"measure_change"
|
|
1961
1961
|
]);
|
|
1962
1962
|
var BASH_MAX_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
1963
|
-
var
|
|
1963
|
+
var CLOUD_BASH_DENY = [
|
|
1964
1964
|
{ re: /\bgit\b[^\n;&|]*\s(push|send-pack|send-email|request-pull)\b/, why: "Publishing from the workspace is not allowed. Use the open_pull_request tool; it asks the user for approval." },
|
|
1965
|
-
{ re: /\bgit\b[^\n;&|]*\sremote\b/, why: "Git remotes are managed by ScaleQuality and cannot be read or changed from the workspace." }
|
|
1965
|
+
{ re: /\bgit\b[^\n;&|]*\sremote\b/, why: "Git remotes are managed by ScaleQuality and cannot be read or changed from the workspace." }
|
|
1966
|
+
];
|
|
1967
|
+
var BASH_DENY = [
|
|
1966
1968
|
{ re: /\bgit\b[^\n;&|]*\scredential\b/, why: "Git credentials are not available in the workspace." },
|
|
1967
1969
|
{ re: /\bgit\b[^\n;&|]*\sconfig\b[^\n;&|]*(credential|insteadof|pushinsteadof|extraheader|\bremote\.|\burl\.)/, why: "Git credential and remote settings cannot be changed from the workspace." },
|
|
1968
1970
|
{ re: /credential[._-]?helper/, why: "Git credential helpers cannot be configured in the workspace." },
|
|
@@ -1984,6 +1986,9 @@ function normalizeCommand(command) {
|
|
|
1984
1986
|
function bashDenial(command, opts = {}) {
|
|
1985
1987
|
const n = normalizeCommand(command);
|
|
1986
1988
|
for (const { re, why } of BASH_DENY) if (re.test(n) || re.test(command)) return why;
|
|
1989
|
+
if (!opts.local) {
|
|
1990
|
+
for (const { re, why } of CLOUD_BASH_DENY) if (re.test(n) || re.test(command)) return why;
|
|
1991
|
+
}
|
|
1987
1992
|
if (opts.local) {
|
|
1988
1993
|
for (const { re, why } of LOCAL_BASH_DENY) if (re.test(n) || re.test(command)) return why;
|
|
1989
1994
|
}
|
|
@@ -2363,7 +2368,7 @@ function httpCommandApprovalApi(o) {
|
|
|
2363
2368
|
const r = await post("", body);
|
|
2364
2369
|
const approvalId = typeof r?.approvalId === "string" ? r.approvalId : "";
|
|
2365
2370
|
if (!approvalId) throw new CommandApprovalError(502, "INVALID_RESPONSE");
|
|
2366
|
-
return { approvalId };
|
|
2371
|
+
return { approvalId, ...r?.approved === true ? { approved: true } : {} };
|
|
2367
2372
|
},
|
|
2368
2373
|
async cancel(approvalId, reason) {
|
|
2369
2374
|
await post(`/${encodeURIComponent(approvalId)}/cancel`, { reason });
|
|
@@ -2389,17 +2394,24 @@ var BrowserCommandGate = class {
|
|
|
2389
2394
|
this.chain = next.catch(() => void 0);
|
|
2390
2395
|
return next;
|
|
2391
2396
|
}
|
|
2397
|
+
checkEdit(path, opts = {}) {
|
|
2398
|
+
const next = this.chain.then(() => this.ask(`edit ${path}`, { signal: opts.signal, edit: { path, ...opts.preview ? { preview: opts.preview } : {} } }));
|
|
2399
|
+
this.chain = next.catch(() => void 0);
|
|
2400
|
+
return next;
|
|
2401
|
+
}
|
|
2392
2402
|
async ask(command, opts) {
|
|
2393
|
-
if (matchingRule(command, this.allRules())) return { allow: true };
|
|
2403
|
+
if (!opts.edit && matchingRule(command, this.allRules())) return { allow: true };
|
|
2394
2404
|
if (opts.signal?.aborted) return { allow: false, message: STOPPED };
|
|
2395
2405
|
let approvalId;
|
|
2406
|
+
let approved = false;
|
|
2396
2407
|
try {
|
|
2397
|
-
({ approvalId } = await this.o.api.request({
|
|
2408
|
+
({ approvalId, approved = false } = await this.o.api.request({
|
|
2398
2409
|
command,
|
|
2399
2410
|
cwd: this.o.root,
|
|
2400
2411
|
...opts.description ? { description: opts.description.slice(0, 1e3) } : {},
|
|
2401
|
-
reasons: approvalReasons(command),
|
|
2402
|
-
suggestedRule: suggestRule(command)
|
|
2412
|
+
reasons: opts.edit ? [] : approvalReasons(command),
|
|
2413
|
+
suggestedRule: opts.edit ? null : suggestRule(command),
|
|
2414
|
+
...opts.edit ? { edit: { path: opts.edit.path, ...opts.edit.preview ? { preview: opts.edit.preview.slice(0, 4e3) } : {} } } : {}
|
|
2403
2415
|
}));
|
|
2404
2416
|
} catch (e) {
|
|
2405
2417
|
this.o.log?.(`command approval could not be requested: ${e.message}`);
|
|
@@ -2409,6 +2421,7 @@ var BrowserCommandGate = class {
|
|
|
2409
2421
|
message: code === "COMMAND_NOT_ALLOWED" ? "This command is not allowed in the ScaleQuality workspace. It did not run; do not try to run it another way." : "The command could not be sent to the user for approval, so it did not run. Tell the user; do not try to run it another way."
|
|
2410
2422
|
};
|
|
2411
2423
|
}
|
|
2424
|
+
if (approved) return { allow: true };
|
|
2412
2425
|
this.o.onWaiting?.(command);
|
|
2413
2426
|
const timeoutMs = this.o.timeoutMs ?? COMMAND_APPROVAL_TIMEOUT_MS;
|
|
2414
2427
|
const timer = new AbortController();
|
|
@@ -2422,7 +2435,7 @@ var BrowserCommandGate = class {
|
|
|
2422
2435
|
return reason === "STOPPED" ? { allow: false, message: STOPPED } : { allow: false, message: `The user did not answer the approval for this command within ${Math.round(timeoutMs / 6e4)} minutes, so it did not run. Ask the user before trying it again.` };
|
|
2423
2436
|
}
|
|
2424
2437
|
if (decision.decision === "REJECT") {
|
|
2425
|
-
return { allow: false, message: "The user denied this command in the browser. It did not run. Do not run the same command again unless the user asks; continue another way or ask the user." };
|
|
2438
|
+
return opts.edit ? { allow: false, message: `The user declined this change to ${opts.edit.path} in the browser. The file was not changed. Ask the user what to do differently.` } : { allow: false, message: "The user denied this command in the browser. It did not run. Do not run the same command again unless the user asks; continue another way or ask the user." };
|
|
2426
2439
|
}
|
|
2427
2440
|
if (decision.remember) {
|
|
2428
2441
|
if (this.o.onRemember) this.o.onRemember(decision.remember);
|
|
@@ -9677,6 +9690,16 @@ function findRunId(value) {
|
|
|
9677
9690
|
};
|
|
9678
9691
|
return walk(value, 0);
|
|
9679
9692
|
}
|
|
9693
|
+
function editPreview(tool, input) {
|
|
9694
|
+
const lines2 = (text3, mark) => typeof text3 === "string" ? text3.split("\n").slice(0, 40).map((line) => `${mark}${line}`) : [];
|
|
9695
|
+
const out2 = [];
|
|
9696
|
+
if (tool === "Write") out2.push(...lines2(input.content, "+"));
|
|
9697
|
+
else if (tool === "Edit") out2.push(...lines2(input.old_string, "-"), ...lines2(input.new_string, "+"));
|
|
9698
|
+
else if (tool === "MultiEdit" && Array.isArray(input.edits)) {
|
|
9699
|
+
for (const e of input.edits.slice(0, 5)) out2.push(...lines2(e?.old_string, "-"), ...lines2(e?.new_string, "+"), "");
|
|
9700
|
+
} else if (tool === "NotebookEdit") out2.push(...lines2(input.new_source, "+"));
|
|
9701
|
+
return out2.join("\n").slice(0, 4e3);
|
|
9702
|
+
}
|
|
9680
9703
|
function turnOffering(raw, loaded) {
|
|
9681
9704
|
const o = raw && typeof raw === "object" ? raw : {};
|
|
9682
9705
|
const names = (v) => new Set((Array.isArray(v) ? v : []).filter((x) => typeof x === "string").map((x) => x.toLowerCase()));
|
|
@@ -11636,9 +11659,10 @@ function buildQueryOptions(o) {
|
|
|
11636
11659
|
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: "Only ScaleQuality tools are available in this workspace." } };
|
|
11637
11660
|
}
|
|
11638
11661
|
const d = await decideToolUse(name, input.tool_input ?? {}, o.policy);
|
|
11639
|
-
|
|
11640
|
-
if (d.behavior === "allow" &&
|
|
11641
|
-
|
|
11662
|
+
const gated = d.behavior === "allow" && !!gate && (name === "Bash" || !!gate.checkEdit && !o.plan && name in WRITE_TOOL_PATHS);
|
|
11663
|
+
if (d.behavior === "allow" && !gated) await beforeWrite(name, d.updatedInput);
|
|
11664
|
+
if (gated) {
|
|
11665
|
+
return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: "Commands and changes on this machine follow the person's permission mode." } };
|
|
11642
11666
|
}
|
|
11643
11667
|
return d.behavior === "allow" ? { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "allow", updatedInput: d.updatedInput } } : { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: d.message } };
|
|
11644
11668
|
};
|
|
@@ -11655,6 +11679,12 @@ function buildQueryOptions(o) {
|
|
|
11655
11679
|
}
|
|
11656
11680
|
const d = await decideToolUse(toolName, input, o.policy);
|
|
11657
11681
|
if (d.behavior !== "allow") return { behavior: "deny", message: d.message };
|
|
11682
|
+
if (gate?.checkEdit && !o.plan && toolName in WRITE_TOOL_PATHS) {
|
|
11683
|
+
const field = WRITE_TOOL_PATHS[toolName];
|
|
11684
|
+
const path = typeof d.updatedInput[field] === "string" ? (0, import_path15.relative)(o.root, d.updatedInput[field]) || String(d.updatedInput[field]) : "";
|
|
11685
|
+
const r = await gate.checkEdit(path, { preview: editPreview(toolName, d.updatedInput), signal: opts?.signal ?? o.abortController.signal });
|
|
11686
|
+
if (!r.allow) return { behavior: "deny", message: r.message };
|
|
11687
|
+
}
|
|
11658
11688
|
await beforeWrite(toolName, d.updatedInput);
|
|
11659
11689
|
if (gate && toolName === "Bash") {
|
|
11660
11690
|
const r = await gate.check(String(d.updatedInput.command ?? ""), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalequality/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.7",
|
|
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": {
|