@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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "sdkVersion": "0.3.281",
3
- "sha256": "7c22c97149f00383b45b4f18cc9e08de81b99dbdddc067c76e9b888b359cd456",
4
- "sourceCommit": "441fed6eb19d2a0e0c86f5926bbcb516900114fa"
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 BASH_DENY = [
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
- if (d.behavior === "allow") await beforeWrite(name, d.updatedInput);
11640
- if (d.behavior === "allow" && gate && name === "Bash") {
11641
- return { hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask", permissionDecisionReason: "Commands on this machine are confirmed by the user." } };
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.6",
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": {