@scalequality/cli 0.3.2 → 0.3.3
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/README.md +7 -0
- package/dist/connect.build.json +2 -2
- package/dist/connect.cjs +388 -61
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,6 +35,13 @@ While `up` runs, the AI Workspace can:
|
|
|
35
35
|
- start a session in one of your folders (at most 3 at once);
|
|
36
36
|
- add a folder you pick there (it is checked here: it must exist, be inside
|
|
37
37
|
your home folder and not be the home folder);
|
|
38
|
+
- show you your folders to pick one, only while the folder picker is open in
|
|
39
|
+
the browser: the subfolders of one folder of your home at a time (no files,
|
|
40
|
+
no hidden or system folders, links only while they stay inside the home) and
|
|
41
|
+
suggestions (git repositories under `~/GIT`, `~/code`, `~/projects`,
|
|
42
|
+
`~/Developer` and similar folders, 3 levels down, plus the folders of your
|
|
43
|
+
Claude Code and Codex conversations). Only folder names, their paths and the
|
|
44
|
+
`origin` remote of repositories are sent, without any user or password;
|
|
38
45
|
- list your Claude Code (`~/.claude/projects`) and Codex (`~/.codex/sessions`)
|
|
39
46
|
conversations, and import the ones you choose. Secrets (cloud keys, provider
|
|
40
47
|
tokens, private keys, JWTs, connection strings, `password=`-style values) are
|
package/dist/connect.build.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"sdkVersion": "0.3.281",
|
|
3
|
-
"sha256": "
|
|
4
|
-
"sourceCommit": "
|
|
3
|
+
"sha256": "08314a95264b9909caa6a812f0bba154465f35fb82a3ffb541fdaeb5c3b95ee5",
|
|
4
|
+
"sourceCommit": "982edf3dc5d3c2e1473b4f13d0695caaed4eeaed"
|
|
5
5
|
}
|
package/dist/connect.cjs
CHANGED
|
@@ -30,7 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
30
30
|
// src/main/workspace-connect.ts
|
|
31
31
|
var import_fs5 = require("fs");
|
|
32
32
|
var import_os2 = require("os");
|
|
33
|
-
var
|
|
33
|
+
var import_path9 = require("path");
|
|
34
34
|
var import_url = require("url");
|
|
35
35
|
|
|
36
36
|
// src/application/services/workspaceSandbox/reasoning.ts
|
|
@@ -376,11 +376,11 @@ function routeLabel(path) {
|
|
|
376
376
|
return path.split("?")[0];
|
|
377
377
|
}
|
|
378
378
|
function sleep(ms, signal) {
|
|
379
|
-
return new Promise((
|
|
380
|
-
const t = setTimeout(
|
|
379
|
+
return new Promise((resolve7) => {
|
|
380
|
+
const t = setTimeout(resolve7, ms);
|
|
381
381
|
signal?.addEventListener("abort", () => {
|
|
382
382
|
clearTimeout(t);
|
|
383
|
-
|
|
383
|
+
resolve7();
|
|
384
384
|
}, { once: true });
|
|
385
385
|
});
|
|
386
386
|
}
|
|
@@ -478,7 +478,7 @@ var RULES = [
|
|
|
478
478
|
{ kind: "PRIVATE_KEY", re: /-----BEGIN [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----[\s\S]*?(?:-----END [A-Z0-9 ]*PRIVATE KEY(?: BLOCK)?-----|$)/g },
|
|
479
479
|
{ kind: "CONNECTION_STRING", re: /\b([a-z][a-z0-9+.-]{1,30}:\/\/)([^\s:@/'"]{1,200}):([^\s@/'"]{1,300})@/gi, replace: (_m, scheme, user) => `${scheme}${user}:${R("PASSWORD")}@` },
|
|
480
480
|
{ kind: "AWS_ACCESS_KEY", re: /\b(?:AKIA|ASIA|AGPA|AIDA|AROA|ANPA|ANVA|AIPA|ABIA|ACCA)[A-Z0-9]{16}\b/g },
|
|
481
|
-
{ kind: "AWS_SECRET_KEY", re: /\b(aws_?secret_?access_?key|aws_?secret|secretAccessKey)(["']?\s*[:=]\s*["']?)([A-Za-z0-9/+=]{40})\b/gi, replace: (_m, k,
|
|
481
|
+
{ kind: "AWS_SECRET_KEY", re: /\b(aws_?secret_?access_?key|aws_?secret|secretAccessKey)(["']?\s*[:=]\s*["']?)([A-Za-z0-9/+=]{40})\b/gi, replace: (_m, k, sep4) => `${k}${sep4}${R("AWS_SECRET_KEY")}` },
|
|
482
482
|
{ kind: "ANTHROPIC_KEY", re: /\bsk-ant-[a-z]{2,10}\d{0,3}-[A-Za-z0-9_-]{20,}/g },
|
|
483
483
|
{ kind: "OPENAI_KEY", re: /\bsk-(?:proj-|svcacct-|admin-|None-)?[A-Za-z0-9_-]{20,}/g },
|
|
484
484
|
{ kind: "GITHUB_TOKEN", re: /\b(?:gh[pousr]_[A-Za-z0-9]{30,255}|github_pat_[A-Za-z0-9_]{22,255})\b/g },
|
|
@@ -494,7 +494,7 @@ var RULES = [
|
|
|
494
494
|
kind: "ASSIGNED_SECRET",
|
|
495
495
|
// password=..., "api_key": "...", SECRET_TOKEN: ..., --token ... (key names that say "secret").
|
|
496
496
|
re: /\b([A-Za-z0-9_.-]*(?:passw(?:or)?d|pwd|secret|token|api[_-]?key|apikey|access[_-]?key|private[_-]?key|client[_-]?secret|credential|auth[_-]?key)[A-Za-z0-9_-]*)(["']?\s*(?:=|:|\s)\s*)(["']?)([^\s"'`,;)}\]]{8,500})\3/gi,
|
|
497
|
-
replace: (_m, key,
|
|
497
|
+
replace: (_m, key, sep4, quote, value) => looksLikeSecretValue(value) ? `${key}${sep4}${quote}${R("SECRET")}${quote}` : null
|
|
498
498
|
}
|
|
499
499
|
];
|
|
500
500
|
function scrubSecrets(input) {
|
|
@@ -773,6 +773,37 @@ async function countImports(sources) {
|
|
|
773
773
|
}
|
|
774
774
|
return counts;
|
|
775
775
|
}
|
|
776
|
+
async function conversationFolders(sources, deadline) {
|
|
777
|
+
const claude = [];
|
|
778
|
+
for (const project of await subdirs((0, import_path.join)(sources.claudeDir, "projects"))) {
|
|
779
|
+
if (Date.now() > deadline) break;
|
|
780
|
+
const newest = (await regularFiles(project, (n) => n.endsWith(".jsonl"))).sort((a, b) => b.mtime - a.mtime)[0];
|
|
781
|
+
if (newest) claude.push({ ...newest, source: "CLAUDE_CODE" });
|
|
782
|
+
}
|
|
783
|
+
const codex = Date.now() > deadline ? [] : (await codexFiles(sources.codexDir)).map((f) => ({ ...f, source: "CODEX" }));
|
|
784
|
+
const files = [...claude, ...codex].sort((a, b) => b.mtime - a.mtime).slice(0, MAX_FILES);
|
|
785
|
+
const out = [];
|
|
786
|
+
const seen = /* @__PURE__ */ new Set();
|
|
787
|
+
for (const f of files) {
|
|
788
|
+
if (Date.now() > deadline) break;
|
|
789
|
+
let n = 0;
|
|
790
|
+
try {
|
|
791
|
+
for await (const r of lines(f.path)) {
|
|
792
|
+
if (++n > 50) break;
|
|
793
|
+
const cwd = f.source === "CLAUDE_CODE" ? r.cwd : r.type === "session_meta" && r.payload && typeof r.payload === "object" && !(r.payload.source && typeof r.payload.source === "object" && r.payload.source.subagent) ? r.payload.cwd : void 0;
|
|
794
|
+
if (typeof cwd === "string" && cwd) {
|
|
795
|
+
if (!seen.has(cwd)) {
|
|
796
|
+
seen.add(cwd);
|
|
797
|
+
out.push(cwd);
|
|
798
|
+
}
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
} catch {
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
return out;
|
|
806
|
+
}
|
|
776
807
|
async function findConversation(sources, source, externalId) {
|
|
777
808
|
if (!isExternalId(externalId)) return null;
|
|
778
809
|
if (source === "CLAUDE_CODE") {
|
|
@@ -850,8 +881,8 @@ function scrubRecord(record) {
|
|
|
850
881
|
async function writeScrubbedTranscript(source, target) {
|
|
851
882
|
const out = (0, import_fs.createWriteStream)(target, { mode: 384, flags: "wx" });
|
|
852
883
|
let removed = 0;
|
|
853
|
-
const done = new Promise((
|
|
854
|
-
out.on("finish",
|
|
884
|
+
const done = new Promise((resolve7, reject) => {
|
|
885
|
+
out.on("finish", resolve7);
|
|
855
886
|
out.on("error", reject);
|
|
856
887
|
});
|
|
857
888
|
const rl = (0, import_readline.createInterface)({ input: (0, import_fs.createReadStream)(source, { encoding: "utf8" }), crlfDelay: Infinity });
|
|
@@ -1325,13 +1356,13 @@ ${indent}`);
|
|
|
1325
1356
|
function terminalCommandPrompt(o) {
|
|
1326
1357
|
const bold = (s) => o.color ? `\x1B[1m${s}\x1B[22m` : s;
|
|
1327
1358
|
const dim = (s) => o.color ? `\x1B[2m${s}\x1B[22m` : s;
|
|
1328
|
-
return (q, signal) => new Promise((
|
|
1359
|
+
return (q, signal) => new Promise((resolve7) => {
|
|
1329
1360
|
if (!o.input.isTTY) {
|
|
1330
|
-
|
|
1361
|
+
resolve7(null);
|
|
1331
1362
|
return;
|
|
1332
1363
|
}
|
|
1333
1364
|
if (signal?.aborted) {
|
|
1334
|
-
|
|
1365
|
+
resolve7(null);
|
|
1335
1366
|
return;
|
|
1336
1367
|
}
|
|
1337
1368
|
const rl = (0, import_readline2.createInterface)({ input: o.input, output: o.output, terminal: true });
|
|
@@ -1341,7 +1372,7 @@ function terminalCommandPrompt(o) {
|
|
|
1341
1372
|
done = true;
|
|
1342
1373
|
signal?.removeEventListener("abort", onAbort);
|
|
1343
1374
|
rl.close();
|
|
1344
|
-
|
|
1375
|
+
resolve7(a);
|
|
1345
1376
|
};
|
|
1346
1377
|
const onAbort = () => {
|
|
1347
1378
|
o.output.write(`
|
|
@@ -1637,8 +1668,297 @@ function parseMachineArgs(argv) {
|
|
|
1637
1668
|
|
|
1638
1669
|
// src/application/services/workspaceSandbox/machineCli.ts
|
|
1639
1670
|
var import_fs3 = require("fs");
|
|
1671
|
+
var import_promises5 = require("fs/promises");
|
|
1672
|
+
var import_path5 = require("path");
|
|
1673
|
+
|
|
1674
|
+
// src/application/services/workspaceSandbox/machineFolders.ts
|
|
1640
1675
|
var import_promises4 = require("fs/promises");
|
|
1641
1676
|
var import_path4 = require("path");
|
|
1677
|
+
var FOLDER_LIST_LIMIT = 300;
|
|
1678
|
+
var FOLDER_SUGGEST_LIMIT = 200;
|
|
1679
|
+
var FOLDER_LIST_BUDGET_MS = 4e3;
|
|
1680
|
+
var FOLDER_SUGGEST_BUDGET_MS = 3e3;
|
|
1681
|
+
var MAX_DIRENTS = 5e3;
|
|
1682
|
+
var CHILD_PROBE = 256;
|
|
1683
|
+
var SUGGEST_DEPTH = 3;
|
|
1684
|
+
var MAX_REMOTE = 1024;
|
|
1685
|
+
var MAX_ANSWER_BYTES = 900 * 1024;
|
|
1686
|
+
var CONCURRENCY = 24;
|
|
1687
|
+
var DEV_ROOTS = ["GIT", "git", "code", "Code", "projects", "Projects", "dev", "src", "workspace", "repos", "Developer", "Documents/GitHub", "go/src"];
|
|
1688
|
+
var SKIP_ANYWHERE = /* @__PURE__ */ new Set(["node_modules", "bower_components", "jspm_packages", "__pycache__", "Caches", "CachedData", "DerivedData", "Pods"]);
|
|
1689
|
+
var SKIP_AT_HOME = /* @__PURE__ */ new Set([
|
|
1690
|
+
"Library",
|
|
1691
|
+
"Applications",
|
|
1692
|
+
"System",
|
|
1693
|
+
"Volumes",
|
|
1694
|
+
"AppData",
|
|
1695
|
+
"Application Data",
|
|
1696
|
+
"Local Settings",
|
|
1697
|
+
"Cookies",
|
|
1698
|
+
"NetHood",
|
|
1699
|
+
"PrintHood",
|
|
1700
|
+
"Recent",
|
|
1701
|
+
"SendTo",
|
|
1702
|
+
"Start Menu",
|
|
1703
|
+
"Templates",
|
|
1704
|
+
"snap",
|
|
1705
|
+
"Trash"
|
|
1706
|
+
]);
|
|
1707
|
+
var FolderBrowseError = class extends Error {
|
|
1708
|
+
constructor(code) {
|
|
1709
|
+
super(code);
|
|
1710
|
+
this.code = code;
|
|
1711
|
+
this.name = "FolderBrowseError";
|
|
1712
|
+
}
|
|
1713
|
+
code;
|
|
1714
|
+
};
|
|
1715
|
+
var hidden = (name) => name.startsWith(".") || name.startsWith("$");
|
|
1716
|
+
var skipped = (name, atHome) => hidden(name) || SKIP_ANYWHERE.has(name) || atHome && SKIP_AT_HOME.has(name);
|
|
1717
|
+
function insideOrHome(path, home) {
|
|
1718
|
+
const problem = folderPathProblem(path, home);
|
|
1719
|
+
return problem === null || problem === "PATH_IS_HOME";
|
|
1720
|
+
}
|
|
1721
|
+
function logical(home, real) {
|
|
1722
|
+
if (home.logical === home.real) return real;
|
|
1723
|
+
const rel = (0, import_path4.relative)(home.real, real);
|
|
1724
|
+
return rel ? (0, import_path4.join)(home.logical, rel) : home.logical;
|
|
1725
|
+
}
|
|
1726
|
+
async function realInside(home, path) {
|
|
1727
|
+
const real = await (0, import_promises4.realpath)(path).catch(() => null);
|
|
1728
|
+
if (!real || !insideOrHome(real, home.real)) return null;
|
|
1729
|
+
return real;
|
|
1730
|
+
}
|
|
1731
|
+
async function resolveBrowsePath(raw, home) {
|
|
1732
|
+
if (raw !== void 0 && raw !== null && typeof raw !== "string") throw new FolderBrowseError("INVALID_PATH");
|
|
1733
|
+
const text2 = (raw ?? "").trim();
|
|
1734
|
+
if (text2.length > MAX_PATH_LENGTH) throw new FolderBrowseError("INVALID_PATH");
|
|
1735
|
+
if (/[\u0000-\u001f\u007f]/.test(text2)) throw new FolderBrowseError("INVALID_PATH");
|
|
1736
|
+
let target;
|
|
1737
|
+
if (!text2 || text2 === "~") target = home.logical;
|
|
1738
|
+
else {
|
|
1739
|
+
const rest = text2.startsWith("~/") || text2.startsWith("~\\") ? text2.slice(2) : text2;
|
|
1740
|
+
if (rest.split(/[\\/]+/).some((part) => part === ".." || part === ".")) throw new FolderBrowseError("INVALID_PATH");
|
|
1741
|
+
target = (0, import_path4.isAbsolute)(rest) && rest === text2 ? rest : (0, import_path4.resolve)(home.logical, rest);
|
|
1742
|
+
if (!insideOrHome(target, home.logical)) throw new FolderBrowseError("PATH_OUTSIDE_HOME");
|
|
1743
|
+
}
|
|
1744
|
+
const st = await (0, import_promises4.stat)(target).catch(() => null);
|
|
1745
|
+
if (!st?.isDirectory()) throw new FolderBrowseError("FOLDER_NOT_FOUND");
|
|
1746
|
+
const real = await realInside(home, target);
|
|
1747
|
+
if (!real) throw new FolderBrowseError("PATH_OUTSIDE_HOME");
|
|
1748
|
+
return real;
|
|
1749
|
+
}
|
|
1750
|
+
async function originRemote(repo2) {
|
|
1751
|
+
try {
|
|
1752
|
+
const dotGit = (0, import_path4.join)(repo2, ".git");
|
|
1753
|
+
const st = await (0, import_promises4.lstat)(dotGit);
|
|
1754
|
+
let gitDir = dotGit;
|
|
1755
|
+
if (st.isFile()) {
|
|
1756
|
+
const pointer = /^gitdir:\s*(.+)\s*$/m.exec(await (0, import_promises4.readFile)(dotGit, "utf8"))?.[1];
|
|
1757
|
+
if (!pointer) return null;
|
|
1758
|
+
gitDir = (0, import_path4.resolve)(repo2, pointer.trim());
|
|
1759
|
+
const common = (await (0, import_promises4.readFile)((0, import_path4.join)(gitDir, "commondir"), "utf8").catch(() => "")).trim();
|
|
1760
|
+
if (common) gitDir = (0, import_path4.resolve)(gitDir, common);
|
|
1761
|
+
} else if (!st.isDirectory()) return null;
|
|
1762
|
+
const config = await (0, import_promises4.readFile)((0, import_path4.join)(gitDir, "config"), "utf8");
|
|
1763
|
+
let inOrigin = false;
|
|
1764
|
+
for (const line of config.split(/\r?\n/)) {
|
|
1765
|
+
const section = /^\s*\[\s*([^\]]+?)\s*\]/.exec(line);
|
|
1766
|
+
if (section) {
|
|
1767
|
+
inOrigin = /^remote\s+"origin"$/.test(section[1]);
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
if (!inOrigin) continue;
|
|
1771
|
+
const url = /^\s*url\s*=\s*(.+?)\s*$/.exec(line)?.[1];
|
|
1772
|
+
if (url) return stripRemoteCredentials(url.replace(/^"(.*)"$/, "$1")).slice(0, MAX_REMOTE) || null;
|
|
1773
|
+
}
|
|
1774
|
+
return null;
|
|
1775
|
+
} catch {
|
|
1776
|
+
return null;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
async function isGitRepo(dir) {
|
|
1780
|
+
const st = await (0, import_promises4.lstat)((0, import_path4.join)(dir, ".git")).catch(() => null);
|
|
1781
|
+
return !!st && (st.isDirectory() || st.isFile());
|
|
1782
|
+
}
|
|
1783
|
+
async function hasVisibleChild(dir) {
|
|
1784
|
+
let handle;
|
|
1785
|
+
try {
|
|
1786
|
+
handle = await (0, import_promises4.opendir)(dir);
|
|
1787
|
+
} catch {
|
|
1788
|
+
return false;
|
|
1789
|
+
}
|
|
1790
|
+
let seen = 0;
|
|
1791
|
+
try {
|
|
1792
|
+
for await (const d of handle) {
|
|
1793
|
+
if (++seen > CHILD_PROBE) return false;
|
|
1794
|
+
if ((d.isDirectory() || d.isSymbolicLink()) && !skipped(d.name, false)) return true;
|
|
1795
|
+
}
|
|
1796
|
+
return false;
|
|
1797
|
+
} catch {
|
|
1798
|
+
return false;
|
|
1799
|
+
} finally {
|
|
1800
|
+
await handle.close().catch(() => void 0);
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
async function subfolderNames(dir, atHome) {
|
|
1804
|
+
const names = [];
|
|
1805
|
+
let handle;
|
|
1806
|
+
try {
|
|
1807
|
+
handle = await (0, import_promises4.opendir)(dir);
|
|
1808
|
+
} catch {
|
|
1809
|
+
return { names, cut: false };
|
|
1810
|
+
}
|
|
1811
|
+
let seen = 0, cut = false;
|
|
1812
|
+
try {
|
|
1813
|
+
for await (const d of handle) {
|
|
1814
|
+
if (++seen > MAX_DIRENTS) {
|
|
1815
|
+
cut = true;
|
|
1816
|
+
break;
|
|
1817
|
+
}
|
|
1818
|
+
if (skipped(d.name, atHome)) continue;
|
|
1819
|
+
if (d.isDirectory()) names.push({ name: d.name, link: false });
|
|
1820
|
+
else if (d.isSymbolicLink()) names.push({ name: d.name, link: true });
|
|
1821
|
+
}
|
|
1822
|
+
} catch {
|
|
1823
|
+
} finally {
|
|
1824
|
+
await handle.close().catch(() => void 0);
|
|
1825
|
+
}
|
|
1826
|
+
return { names, cut };
|
|
1827
|
+
}
|
|
1828
|
+
async function eachUntil(items, deadline, work) {
|
|
1829
|
+
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
1830
|
+
if (Date.now() > deadline) return false;
|
|
1831
|
+
await Promise.all(items.slice(i, i + CONCURRENCY).map((item) => work(item).catch(() => void 0)));
|
|
1832
|
+
}
|
|
1833
|
+
return true;
|
|
1834
|
+
}
|
|
1835
|
+
var byGitThenName = (a, b) => (a.isGitRepo === b.isGitRepo ? 0 : a.isGitRepo ? -1 : 1) || a.name.localeCompare(b.name, void 0, { sensitivity: "base", numeric: true });
|
|
1836
|
+
function fit(answer) {
|
|
1837
|
+
while (answer.entries.length && Buffer.byteLength(JSON.stringify(answer)) > MAX_ANSWER_BYTES) {
|
|
1838
|
+
answer.entries.splice(Math.max(1, Math.floor(answer.entries.length * 0.9)));
|
|
1839
|
+
answer.truncated = true;
|
|
1840
|
+
}
|
|
1841
|
+
return answer;
|
|
1842
|
+
}
|
|
1843
|
+
async function homeOf(home) {
|
|
1844
|
+
return { logical: home, real: await (0, import_promises4.realpath)(home).catch(() => home) };
|
|
1845
|
+
}
|
|
1846
|
+
async function listFolders(homePath, rawPath, opts = {}) {
|
|
1847
|
+
const deadline = Date.now() + (opts.budgetMs ?? FOLDER_LIST_BUDGET_MS);
|
|
1848
|
+
const limit = opts.limit ?? FOLDER_LIST_LIMIT;
|
|
1849
|
+
const home = await homeOf(homePath);
|
|
1850
|
+
const real = await resolveBrowsePath(rawPath, home);
|
|
1851
|
+
const atHome = real === home.real;
|
|
1852
|
+
const { names, cut } = await subfolderNames(real, atHome);
|
|
1853
|
+
let truncated = cut;
|
|
1854
|
+
const found = [];
|
|
1855
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1856
|
+
truncated = !await eachUntil(names, deadline, async ({ name, link }) => {
|
|
1857
|
+
let target = (0, import_path4.join)(real, name);
|
|
1858
|
+
if (link) {
|
|
1859
|
+
const st = await (0, import_promises4.stat)(target).catch(() => null);
|
|
1860
|
+
if (!st?.isDirectory()) return;
|
|
1861
|
+
const inside2 = await realInside(home, target);
|
|
1862
|
+
if (!inside2 || inside2 === home.real) return;
|
|
1863
|
+
target = inside2;
|
|
1864
|
+
}
|
|
1865
|
+
if (seen.has(target)) return;
|
|
1866
|
+
seen.add(target);
|
|
1867
|
+
found.push({ name, real: target, isGitRepo: await isGitRepo(target) });
|
|
1868
|
+
}) || truncated;
|
|
1869
|
+
found.sort(byGitThenName);
|
|
1870
|
+
if (found.length > limit) truncated = true;
|
|
1871
|
+
const shown = found.slice(0, limit);
|
|
1872
|
+
const entries = shown.map(() => null);
|
|
1873
|
+
truncated = !await eachUntil(shown.map((f, i) => ({ f, i })), deadline, async ({ f, i }) => {
|
|
1874
|
+
const [remoteUrl, hasChildren] = await Promise.all([f.isGitRepo ? originRemote(f.real) : Promise.resolve(null), hasVisibleChild(f.real)]);
|
|
1875
|
+
entries[i] = { name: f.name, path: logical(home, f.real), isGitRepo: f.isGitRepo, remoteUrl, hasChildren };
|
|
1876
|
+
}) || truncated;
|
|
1877
|
+
const path = logical(home, real);
|
|
1878
|
+
const rel = (0, import_path4.relative)(home.logical, path);
|
|
1879
|
+
const parentReal = atHome ? null : (0, import_path4.resolve)(real, "..");
|
|
1880
|
+
const parent = parentReal && insideOrHome(parentReal, home.real) ? logical(home, parentReal) : null;
|
|
1881
|
+
return fit({ home: "~", homePath: home.logical, path, relative: rel.split(import_path4.sep).join("/"), parent, entries: entries.filter((e) => !!e), truncated });
|
|
1882
|
+
}
|
|
1883
|
+
async function suggestFolders(homePath, sources, opts = {}) {
|
|
1884
|
+
const started = Date.now();
|
|
1885
|
+
const deadline = started + (opts.budgetMs ?? FOLDER_SUGGEST_BUDGET_MS);
|
|
1886
|
+
const limit = opts.limit ?? FOLDER_SUGGEST_LIMIT;
|
|
1887
|
+
const home = await homeOf(homePath);
|
|
1888
|
+
const out = [];
|
|
1889
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1890
|
+
let truncated = false;
|
|
1891
|
+
const add = (real, source) => {
|
|
1892
|
+
if (seen.has(real) || real === home.real) return;
|
|
1893
|
+
if (out.length >= limit) {
|
|
1894
|
+
truncated = true;
|
|
1895
|
+
return;
|
|
1896
|
+
}
|
|
1897
|
+
seen.add(real);
|
|
1898
|
+
out.push({ real, source });
|
|
1899
|
+
};
|
|
1900
|
+
const convoDeadline = started + Math.floor((deadline - started) / 2);
|
|
1901
|
+
const folders = await (opts.conversations ?? ((d) => conversationFolders(sources, d)))(convoDeadline).catch(() => []);
|
|
1902
|
+
for (const folder of folders) {
|
|
1903
|
+
if (Date.now() > deadline) {
|
|
1904
|
+
truncated = true;
|
|
1905
|
+
break;
|
|
1906
|
+
}
|
|
1907
|
+
if (typeof folder !== "string" || folderPathProblem(folder, home.logical) && folderPathProblem(folder, home.real)) continue;
|
|
1908
|
+
let dir = await realInside(home, folder);
|
|
1909
|
+
while (dir && dir !== home.real) {
|
|
1910
|
+
if (await isGitRepo(dir)) {
|
|
1911
|
+
add(dir, "CONVERSATION");
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
const up = (0, import_path4.resolve)(dir, "..");
|
|
1915
|
+
if (up === dir) break;
|
|
1916
|
+
dir = up;
|
|
1917
|
+
}
|
|
1918
|
+
}
|
|
1919
|
+
const roots = [];
|
|
1920
|
+
for (const root of DEV_ROOTS) {
|
|
1921
|
+
const real = await realInside(home, (0, import_path4.join)(home.logical, root));
|
|
1922
|
+
const st = real ? await (0, import_promises4.stat)(real).catch(() => null) : null;
|
|
1923
|
+
if (real && real !== home.real && st?.isDirectory() && !roots.includes(real)) roots.push(real);
|
|
1924
|
+
}
|
|
1925
|
+
const devFound = [];
|
|
1926
|
+
let level = [];
|
|
1927
|
+
for (const root of roots) {
|
|
1928
|
+
if (await isGitRepo(root)) devFound.push(root);
|
|
1929
|
+
else level.push(root);
|
|
1930
|
+
}
|
|
1931
|
+
for (let depth = 1; depth <= SUGGEST_DEPTH && level.length && !truncated; depth++) {
|
|
1932
|
+
const next = [];
|
|
1933
|
+
const finished = await eachUntil(level, deadline, async (dir) => {
|
|
1934
|
+
const { names } = await subfolderNames(dir, false);
|
|
1935
|
+
for (const { name, link } of names) {
|
|
1936
|
+
const child = (0, import_path4.join)(dir, name);
|
|
1937
|
+
const real = link ? await realInside(home, child) : child;
|
|
1938
|
+
if (!real || real === home.real) continue;
|
|
1939
|
+
if (await isGitRepo(real)) devFound.push(real);
|
|
1940
|
+
else if (depth < SUGGEST_DEPTH) next.push(real);
|
|
1941
|
+
}
|
|
1942
|
+
});
|
|
1943
|
+
if (!finished) truncated = true;
|
|
1944
|
+
if (devFound.length + out.length >= limit * 2) {
|
|
1945
|
+
if (next.length) truncated = true;
|
|
1946
|
+
break;
|
|
1947
|
+
}
|
|
1948
|
+
level = next;
|
|
1949
|
+
}
|
|
1950
|
+
devFound.sort((a, b) => (0, import_path4.relative)(home.real, a).localeCompare((0, import_path4.relative)(home.real, b), void 0, { sensitivity: "base", numeric: true }));
|
|
1951
|
+
for (const real of devFound) add(real, "DEV_ROOT");
|
|
1952
|
+
const entries = out.map(() => null);
|
|
1953
|
+
const done = await eachUntil(out.map((f, i) => ({ f, i })), deadline + 1e3, async ({ f, i }) => {
|
|
1954
|
+
const [remoteUrl, hasChildren] = await Promise.all([originRemote(f.real), hasVisibleChild(f.real)]);
|
|
1955
|
+
const path = logical(home, f.real);
|
|
1956
|
+
entries[i] = { name: path.split(/[\\/]/).pop() || path, path, isGitRepo: true, remoteUrl, hasChildren, source: f.source };
|
|
1957
|
+
});
|
|
1958
|
+
return fit({ home: "~", homePath: home.logical, entries: entries.filter((e) => !!e), truncated: truncated || !done });
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1961
|
+
// src/application/services/workspaceSandbox/machineCli.ts
|
|
1642
1962
|
var CredentialStore = class {
|
|
1643
1963
|
constructor(file) {
|
|
1644
1964
|
this.file = file;
|
|
@@ -1672,7 +1992,7 @@ var CredentialStore = class {
|
|
|
1672
1992
|
}
|
|
1673
1993
|
}
|
|
1674
1994
|
write(data) {
|
|
1675
|
-
(0, import_fs3.mkdirSync)((0,
|
|
1995
|
+
(0, import_fs3.mkdirSync)((0, import_path5.dirname)(this.file), { recursive: true, mode: 448 });
|
|
1676
1996
|
const tmp = `${this.file}.${process.pid}.tmp`;
|
|
1677
1997
|
(0, import_fs3.writeFileSync)(tmp, `${JSON.stringify(data, null, 2)}
|
|
1678
1998
|
`, { mode: 384 });
|
|
@@ -1802,10 +2122,10 @@ var FOLDER_MESSAGES = {
|
|
|
1802
2122
|
async function checkFolder(path, home) {
|
|
1803
2123
|
const shape = folderPathProblem(path, home);
|
|
1804
2124
|
if (shape) throw new FolderError(shape, FOLDER_MESSAGES[shape] ?? "That folder cannot be connected.");
|
|
1805
|
-
const st = await (0,
|
|
2125
|
+
const st = await (0, import_promises5.stat)(path).catch(() => null);
|
|
1806
2126
|
if (!st?.isDirectory()) throw new FolderError("FOLDER_NOT_FOUND", FOLDER_MESSAGES.FOLDER_NOT_FOUND);
|
|
1807
|
-
const real = await (0,
|
|
1808
|
-
const realHome = await (0,
|
|
2127
|
+
const real = await (0, import_promises5.realpath)(path);
|
|
2128
|
+
const realHome = await (0, import_promises5.realpath)(home).catch(() => home);
|
|
1809
2129
|
const problem = folderPathProblem(real, realHome);
|
|
1810
2130
|
if (problem) throw new FolderError(problem, FOLDER_MESSAGES[problem] ?? "That folder cannot be connected.");
|
|
1811
2131
|
return real;
|
|
@@ -1813,7 +2133,7 @@ async function checkFolder(path, home) {
|
|
|
1813
2133
|
async function folderRemote(path) {
|
|
1814
2134
|
try {
|
|
1815
2135
|
const top = (await git(["rev-parse", "--show-toplevel"], { cwd: path })).trim();
|
|
1816
|
-
const realTop = await (0,
|
|
2136
|
+
const realTop = await (0, import_promises5.realpath)(top).catch(() => top);
|
|
1817
2137
|
if (realTop !== path) return null;
|
|
1818
2138
|
const url = (await git(["config", "--get", "remote.origin.url"], { cwd: path })).trim();
|
|
1819
2139
|
return url ? stripRemoteCredentials(url) : null;
|
|
@@ -1834,28 +2154,28 @@ function claudeProjectDir(cwd) {
|
|
|
1834
2154
|
}
|
|
1835
2155
|
async function copyClaudeTranscript(sources, externalId, root, engineConfigDir) {
|
|
1836
2156
|
if (!isExternalId(externalId)) return false;
|
|
1837
|
-
const projects = (0,
|
|
2157
|
+
const projects = (0, import_path5.join)(sources.claudeDir, "projects");
|
|
1838
2158
|
let original = null;
|
|
1839
|
-
for (const dir of await (0,
|
|
1840
|
-
const candidate = (0,
|
|
1841
|
-
const st = await (0,
|
|
2159
|
+
for (const dir of await (0, import_promises5.readdir)(projects).catch(() => [])) {
|
|
2160
|
+
const candidate = (0, import_path5.join)(projects, dir, `${externalId}.jsonl`);
|
|
2161
|
+
const st = await (0, import_promises5.lstat)(candidate).catch(() => null);
|
|
1842
2162
|
if (st?.isFile()) {
|
|
1843
2163
|
original = candidate;
|
|
1844
2164
|
break;
|
|
1845
2165
|
}
|
|
1846
2166
|
}
|
|
1847
2167
|
if (!original) return false;
|
|
1848
|
-
const targetDir = (0,
|
|
1849
|
-
const target = (0,
|
|
2168
|
+
const targetDir = (0, import_path5.join)(engineConfigDir, "projects", claudeProjectDir(root));
|
|
2169
|
+
const target = (0, import_path5.join)(targetDir, `${externalId}.jsonl`);
|
|
1850
2170
|
if ((0, import_fs3.existsSync)(target)) return true;
|
|
1851
|
-
await (0,
|
|
2171
|
+
await (0, import_promises5.mkdir)(targetDir, { recursive: true, mode: 448 });
|
|
1852
2172
|
const partial = `${target}.${process.pid}.partial`;
|
|
1853
2173
|
try {
|
|
1854
2174
|
await writeScrubbedTranscript(original, partial);
|
|
1855
|
-
await (0,
|
|
1856
|
-
await (0,
|
|
2175
|
+
await (0, import_promises5.chmod)(partial, 384).catch(() => void 0);
|
|
2176
|
+
await (0, import_promises5.rename)(partial, target);
|
|
1857
2177
|
} catch {
|
|
1858
|
-
await (0,
|
|
2178
|
+
await (0, import_promises5.rm)(partial, { force: true }).catch(() => void 0);
|
|
1859
2179
|
return false;
|
|
1860
2180
|
}
|
|
1861
2181
|
return true;
|
|
@@ -1975,6 +2295,13 @@ var MachineAgent = class {
|
|
|
1975
2295
|
case "scan_imports":
|
|
1976
2296
|
answer = { ok: true, result: { items: await scanImports(this.deps.sources) } };
|
|
1977
2297
|
break;
|
|
2298
|
+
// The folder picker of the browser: only folder names, paths and remotes leave this computer, and only when asked.
|
|
2299
|
+
case "list_folders":
|
|
2300
|
+
answer = { ok: true, result: await listFolders(this.deps.home, p.path) };
|
|
2301
|
+
break;
|
|
2302
|
+
case "suggest_folders":
|
|
2303
|
+
answer = { ok: true, result: await suggestFolders(this.deps.home, this.deps.sources) };
|
|
2304
|
+
break;
|
|
1978
2305
|
case "upload_imports":
|
|
1979
2306
|
answer = await this.upload(p);
|
|
1980
2307
|
break;
|
|
@@ -1982,7 +2309,7 @@ var MachineAgent = class {
|
|
|
1982
2309
|
answer = { ok: false, error: { code: "UNKNOWN_COMMAND" } };
|
|
1983
2310
|
}
|
|
1984
2311
|
} catch (e) {
|
|
1985
|
-
answer = { ok: false, error: { code: e instanceof FolderError || e instanceof LocalWorkspaceError ? e.code : "MACHINE_COMMAND_FAILED" } };
|
|
2312
|
+
answer = { ok: false, error: { code: e instanceof FolderError || e instanceof LocalWorkspaceError || e instanceof FolderBrowseError ? e.code : "MACHINE_COMMAND_FAILED" } };
|
|
1986
2313
|
}
|
|
1987
2314
|
await this.deps.client.reply(c.id, answer).catch(() => void 0);
|
|
1988
2315
|
}
|
|
@@ -2086,9 +2413,9 @@ function serialPrompt(ask) {
|
|
|
2086
2413
|
}
|
|
2087
2414
|
|
|
2088
2415
|
// src/application/services/workspaceSandbox/WorkspaceEngine.ts
|
|
2089
|
-
var
|
|
2416
|
+
var import_promises7 = require("fs/promises");
|
|
2090
2417
|
var import_fs4 = require("fs");
|
|
2091
|
-
var
|
|
2418
|
+
var import_path8 = require("path");
|
|
2092
2419
|
|
|
2093
2420
|
// src/application/services/execution/LanguageAdapter.ts
|
|
2094
2421
|
var import_async_hooks = require("async_hooks");
|
|
@@ -2177,15 +2504,15 @@ var ApprovalBroker = class {
|
|
|
2177
2504
|
return Promise.resolve(ready);
|
|
2178
2505
|
}
|
|
2179
2506
|
if (signal?.aborted) return Promise.resolve(null);
|
|
2180
|
-
return new Promise((
|
|
2507
|
+
return new Promise((resolve7) => {
|
|
2181
2508
|
const onAbort = () => {
|
|
2182
2509
|
this.waiting.delete(approvalId);
|
|
2183
|
-
|
|
2510
|
+
resolve7(null);
|
|
2184
2511
|
};
|
|
2185
2512
|
signal?.addEventListener("abort", onAbort, { once: true });
|
|
2186
2513
|
this.waiting.set(approvalId, (d) => {
|
|
2187
2514
|
signal?.removeEventListener("abort", onAbort);
|
|
2188
|
-
|
|
2515
|
+
resolve7(d);
|
|
2189
2516
|
});
|
|
2190
2517
|
});
|
|
2191
2518
|
}
|
|
@@ -6356,8 +6683,8 @@ var coerce = {
|
|
|
6356
6683
|
var NEVER = INVALID;
|
|
6357
6684
|
|
|
6358
6685
|
// src/application/services/workspaceSandbox/toolPolicy.ts
|
|
6359
|
-
var
|
|
6360
|
-
var
|
|
6686
|
+
var import_promises6 = require("fs/promises");
|
|
6687
|
+
var import_path6 = require("path");
|
|
6361
6688
|
var SQ_MCP_SERVER = "scalequality";
|
|
6362
6689
|
var SQ_MCP_PREFIX = `mcp__${SQ_MCP_SERVER}__`;
|
|
6363
6690
|
var DENIED_TOOLS = ["WebFetch", "WebSearch", "Task", "Agent", "RemoteTrigger", "CronCreate", "CronDelete", "CronList", "ScheduleWakeup", "PushNotification", "EnterWorktree", "ExitWorktree", "Artifact", "Workflow", "SendFeedback", "ClaudeDesign", "Projects"];
|
|
@@ -6395,7 +6722,7 @@ function bashDenial(command, opts = {}) {
|
|
|
6395
6722
|
return null;
|
|
6396
6723
|
}
|
|
6397
6724
|
function inside(root, abs) {
|
|
6398
|
-
return abs === root || abs.startsWith(root +
|
|
6725
|
+
return abs === root || abs.startsWith(root + import_path6.sep);
|
|
6399
6726
|
}
|
|
6400
6727
|
async function decideToolUse(toolName, input, ctx) {
|
|
6401
6728
|
if (toolName.startsWith("mcp__")) {
|
|
@@ -6412,13 +6739,13 @@ async function decideToolUse(toolName, input, ctx) {
|
|
|
6412
6739
|
const abs = await resolveInside(ctx.root, raw);
|
|
6413
6740
|
let denied = false;
|
|
6414
6741
|
for (const d of ctx.deniedRoots ?? []) {
|
|
6415
|
-
const realDenied = await (0,
|
|
6742
|
+
const realDenied = await (0, import_promises6.realpath)(d).catch(() => (0, import_path6.resolve)(d));
|
|
6416
6743
|
if (abs && inside(realDenied, abs)) denied = true;
|
|
6417
6744
|
}
|
|
6418
6745
|
if (abs && !denied) {
|
|
6419
6746
|
if (toolName in WRITE_TOOLS) {
|
|
6420
|
-
const realRoot = await (0,
|
|
6421
|
-
const rel = (0,
|
|
6747
|
+
const realRoot = await (0, import_promises6.realpath)(ctx.root).catch(() => (0, import_path6.resolve)(ctx.root));
|
|
6748
|
+
const rel = (0, import_path6.relative)(realRoot, abs).split(import_path6.sep);
|
|
6422
6749
|
if (rel.includes(".git")) return { behavior: "deny", message: "Files under .git cannot be written from the workspace." };
|
|
6423
6750
|
}
|
|
6424
6751
|
return { behavior: "allow", updatedInput: input };
|
|
@@ -6426,7 +6753,7 @@ async function decideToolUse(toolName, input, ctx) {
|
|
|
6426
6753
|
if (toolName in READ_TOOLS) {
|
|
6427
6754
|
for (const extra of ctx.extraReadRoots ?? []) {
|
|
6428
6755
|
const e = await resolveInside(extra, raw);
|
|
6429
|
-
const realExtra = await (0,
|
|
6756
|
+
const realExtra = await (0, import_promises6.realpath)(extra).catch(() => (0, import_path6.resolve)(extra));
|
|
6430
6757
|
if (e && inside(realExtra, e)) return { behavior: "allow", updatedInput: input };
|
|
6431
6758
|
}
|
|
6432
6759
|
}
|
|
@@ -6562,7 +6889,7 @@ function buildScaleQualityServer(sdk, host) {
|
|
|
6562
6889
|
}
|
|
6563
6890
|
|
|
6564
6891
|
// src/application/services/workspaceSandbox/sdkEventMapper.ts
|
|
6565
|
-
var
|
|
6892
|
+
var import_path7 = require("path");
|
|
6566
6893
|
var TERMINAL_TAIL_BYTES = 64 * 1024;
|
|
6567
6894
|
var FILE_CHANGING = /* @__PURE__ */ new Set(["Edit", "MultiEdit", "Write", "NotebookEdit", "Bash"]);
|
|
6568
6895
|
var SQ_TOOL_LABELS = {
|
|
@@ -6774,8 +7101,8 @@ function describeTool(name, input, root) {
|
|
|
6774
7101
|
const s = (k) => typeof input[k] === "string" ? input[k] : "";
|
|
6775
7102
|
const rel = (p) => {
|
|
6776
7103
|
if (!p) return "";
|
|
6777
|
-
if (!(0,
|
|
6778
|
-
const r = (0,
|
|
7104
|
+
if (!(0, import_path7.isAbsolute)(p)) return p;
|
|
7105
|
+
const r = (0, import_path7.relative)(root, p);
|
|
6779
7106
|
return r && !r.startsWith("..") ? r : p;
|
|
6780
7107
|
};
|
|
6781
7108
|
switch (name) {
|
|
@@ -7108,7 +7435,7 @@ var WorkspaceEngine = class {
|
|
|
7108
7435
|
register(r) {
|
|
7109
7436
|
const repo2 = {
|
|
7110
7437
|
...r,
|
|
7111
|
-
measurer: this.deps.createMeasurer && !this.local ? this.deps.createMeasurer(r.repoFullName ?? (0,
|
|
7438
|
+
measurer: this.deps.createMeasurer && !this.local ? this.deps.createMeasurer(r.repoFullName ?? (0, import_path8.basename)(r.root), r.root) : null,
|
|
7112
7439
|
lastDiff: null
|
|
7113
7440
|
};
|
|
7114
7441
|
this.repos.set(r.root, repo2);
|
|
@@ -7123,14 +7450,14 @@ var WorkspaceEngine = class {
|
|
|
7123
7450
|
const short = folderName(lastSegment(repoFullName));
|
|
7124
7451
|
const full = folderName(repoFullName.split("/").filter(Boolean).join("__"));
|
|
7125
7452
|
const clash = this.scope.repos.some((r) => r.repoFullName !== repoFullName && folderName(lastSegment(r.repoFullName)) === short);
|
|
7126
|
-
const privateDirs = (this.deps.privateDirs ?? []).map((d) => (0,
|
|
7453
|
+
const privateDirs = (this.deps.privateDirs ?? []).map((d) => (0, import_path8.resolve)(d));
|
|
7127
7454
|
const taken = (name2) => {
|
|
7128
|
-
const dir = (0,
|
|
7455
|
+
const dir = (0, import_path8.resolve)(this.deps.root, name2);
|
|
7129
7456
|
return this.repos.has(dir) || privateDirs.includes(dir) || (0, import_fs4.existsSync)(dir);
|
|
7130
7457
|
};
|
|
7131
7458
|
let name = clash || taken(short) ? full : short;
|
|
7132
7459
|
for (let n = 2; taken(name); n++) name = `${full}-${n}`;
|
|
7133
|
-
return (0,
|
|
7460
|
+
return (0, import_path8.join)(this.deps.root, name);
|
|
7134
7461
|
}
|
|
7135
7462
|
/** Clones one repository into its folder and registers it. The token is dropped either way. */
|
|
7136
7463
|
async cloneInto(access, onStep) {
|
|
@@ -7140,7 +7467,7 @@ var WorkspaceEngine = class {
|
|
|
7140
7467
|
try {
|
|
7141
7468
|
prepared = await this.deps.clone(access, dir, saved, onStep);
|
|
7142
7469
|
} catch (e) {
|
|
7143
|
-
await (0,
|
|
7470
|
+
await (0, import_promises7.rm)(dir, { recursive: true, force: true }).catch(() => void 0);
|
|
7144
7471
|
throw e;
|
|
7145
7472
|
} finally {
|
|
7146
7473
|
access.token = "";
|
|
@@ -7179,7 +7506,7 @@ var WorkspaceEngine = class {
|
|
|
7179
7506
|
}
|
|
7180
7507
|
if (open.length === 1) return { repo: open[0] };
|
|
7181
7508
|
if (!open.length) return { error: "No repository is open in this workspace. Call list_repositories, then open_repository." };
|
|
7182
|
-
return { error: `Several repositories are open (${open.map((o) => o.repoFullName ?? (0,
|
|
7509
|
+
return { error: `Several repositories are open (${open.map((o) => o.repoFullName ?? (0, import_path8.basename)(o.root)).join(", ")}). Pass repoFullName.` };
|
|
7183
7510
|
}
|
|
7184
7511
|
notInScope(repo2, target) {
|
|
7185
7512
|
if (this.inScope(target ?? repo2.repoFullName)) return null;
|
|
@@ -7684,8 +8011,8 @@ _Measured on an earlier version of this change._`);
|
|
|
7684
8011
|
});
|
|
7685
8012
|
const opened = out?.pullRequest ?? out;
|
|
7686
8013
|
const url = typeof opened?.url === "string" ? opened.url : "";
|
|
7687
|
-
const
|
|
7688
|
-
return text(url ? `Pull request opened on ${target}: ${url} (title: "${editedTitle}").${
|
|
8014
|
+
const skipped2 = final.skipped.length ? ` Not included: ${final.skipped.map((s) => `${s.path} (${s.reason})`).join(", ")}.` : "";
|
|
8015
|
+
return text(url ? `Pull request opened on ${target}: ${url} (title: "${editedTitle}").${skipped2}` : `The pull request request was accepted.${skipped2}`);
|
|
7689
8016
|
} catch (e) {
|
|
7690
8017
|
const status = e instanceof SessionGoneError || e instanceof TransportError ? e.status : null;
|
|
7691
8018
|
if (e instanceof TransportError && e.code === REPOSITORY_NOT_IN_SCOPE) {
|
|
@@ -7861,9 +8188,9 @@ function buildQueryOptions(o) {
|
|
|
7861
8188
|
}
|
|
7862
8189
|
async function hasLocalTranscript(configDir, sessionId) {
|
|
7863
8190
|
if (!/^[A-Za-z0-9-]{8,80}$/.test(sessionId)) return false;
|
|
7864
|
-
const projects = (0,
|
|
7865
|
-
const dirs = await (0,
|
|
7866
|
-
return dirs.some((d) => (0, import_fs4.existsSync)((0,
|
|
8191
|
+
const projects = (0, import_path8.join)(configDir, "projects");
|
|
8192
|
+
const dirs = await (0, import_promises7.readdir)(projects).catch(() => []);
|
|
8193
|
+
return dirs.some((d) => (0, import_fs4.existsSync)((0, import_path8.join)(projects, d, `${sessionId}.jsonl`)));
|
|
7867
8194
|
}
|
|
7868
8195
|
|
|
7869
8196
|
// src/main/workspace-connect.ts
|
|
@@ -7879,9 +8206,9 @@ var say = (line = "") => err.write(`${line}
|
|
|
7879
8206
|
var cliVersion = process.env.SCALEQUALITY_CLI_VERSION || "dev";
|
|
7880
8207
|
var userAgent = (mode) => `scalequality-cli/${cliVersion} (${mode}; node ${process.versions.node}; ${process.platform})`;
|
|
7881
8208
|
var HOME = (0, import_os2.homedir)();
|
|
7882
|
-
var SQ_HOME = (0,
|
|
7883
|
-
var ENGINE_HOME = (0,
|
|
7884
|
-
var credentials = new CredentialStore((0,
|
|
8209
|
+
var SQ_HOME = (0, import_path9.join)(HOME, ".scalequality");
|
|
8210
|
+
var ENGINE_HOME = (0, import_path9.join)(SQ_HOME, "workspace");
|
|
8211
|
+
var credentials = new CredentialStore((0, import_path9.join)(SQ_HOME, "credentials.json"));
|
|
7885
8212
|
var NotLocalSessionError = class extends Error {
|
|
7886
8213
|
};
|
|
7887
8214
|
function startFailure(e, api) {
|
|
@@ -7896,8 +8223,8 @@ function startFailure(e, api) {
|
|
|
7896
8223
|
return "ScaleQuality could not start this session. Try again in a moment, or get a new code from the AI Workspace.";
|
|
7897
8224
|
}
|
|
7898
8225
|
function engineDirs() {
|
|
7899
|
-
const configDir = (0,
|
|
7900
|
-
const scratch = (0,
|
|
8226
|
+
const configDir = (0, import_path9.join)(ENGINE_HOME, "claude-home");
|
|
8227
|
+
const scratch = (0, import_path9.join)(ENGINE_HOME, "tmp");
|
|
7901
8228
|
(0, import_fs5.mkdirSync)(configDir, { recursive: true, mode: 448 });
|
|
7902
8229
|
(0, import_fs5.mkdirSync)(scratch, { recursive: true, mode: 448 });
|
|
7903
8230
|
return { configDir, scratch };
|
|
@@ -8129,7 +8456,7 @@ async function upMain(api, verbose) {
|
|
|
8129
8456
|
onInterrupt: () => void shutdown()
|
|
8130
8457
|
}));
|
|
8131
8458
|
const startSession = ({ sessionId, secret, root }) => {
|
|
8132
|
-
const label = (0,
|
|
8459
|
+
const label = (0, import_path9.basename)(root);
|
|
8133
8460
|
const prefix = style.dim(`[${label}] `);
|
|
8134
8461
|
const consoleLog = new ConsoleLog(style);
|
|
8135
8462
|
let resolveDone = () => void 0;
|
|
@@ -8192,7 +8519,7 @@ async function upMain(api, verbose) {
|
|
|
8192
8519
|
}
|
|
8193
8520
|
async function addMain(api, path) {
|
|
8194
8521
|
try {
|
|
8195
|
-
const real = await addFolder(credentials, api, (0,
|
|
8522
|
+
const real = await addFolder(credentials, api, (0, import_path9.resolve)(path ?? process.cwd()), HOME);
|
|
8196
8523
|
say(style.green(`Added ${real}.`));
|
|
8197
8524
|
const credential = credentials.get(api);
|
|
8198
8525
|
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.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "ScaleQuality CLI. Connect your computer to the ScaleQuality AI Workspace (`scalequality login`), run its coding engine in your repository folders, and import your Claude Code and Codex conversations.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|