@scalequality/cli 0.4.13 → 0.4.14
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 +309 -23
- 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": "d3ca465f1db41c45109f40d765ff928454585df4214acaab11ec84c8124ba5ec",
|
|
4
|
+
"sourceCommit": "ce4ddc0a595f557925ecce922fe14b938fcceba4"
|
|
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
|
|
31
|
+
var import_fs11 = require("fs");
|
|
32
32
|
var import_os4 = require("os");
|
|
33
|
-
var
|
|
33
|
+
var import_path17 = require("path");
|
|
34
34
|
var import_url = require("url");
|
|
35
35
|
|
|
36
36
|
// src/application/services/workspaceSandbox/importers.ts
|
|
@@ -3405,7 +3405,7 @@ var MACHINE_USAGE = {
|
|
|
3405
3405
|
"ScaleQuality and deletes its credential."
|
|
3406
3406
|
].join("\n"),
|
|
3407
3407
|
service: [
|
|
3408
|
-
"Usage: scalequality service <install|start|uninstall|status|logs> [--api URL] [--lines N]",
|
|
3408
|
+
"Usage: scalequality service <install|start|uninstall|status|logs|auto-update on|off> [--api URL] [--lines N]",
|
|
3409
3409
|
"",
|
|
3410
3410
|
"The background service that keeps this computer connected.",
|
|
3411
3411
|
"",
|
|
@@ -3414,18 +3414,21 @@ var MACHINE_USAGE = {
|
|
|
3414
3414
|
' uninstall Stop and remove it. The computer stays paired; "scalequality',
|
|
3415
3415
|
' logout" disconnects it.',
|
|
3416
3416
|
" status Whether it is installed and running, and where it logs.",
|
|
3417
|
-
" logs The last lines of its log (--lines N, default 100)."
|
|
3417
|
+
" logs The last lines of its log (--lines N, default 100).",
|
|
3418
|
+
" auto-update on|off",
|
|
3419
|
+
" Whether the service installs new versions of this CLI by itself",
|
|
3420
|
+
" (on by default; only while no session runs)."
|
|
3418
3421
|
].join("\n")
|
|
3419
3422
|
};
|
|
3420
3423
|
var MACHINE_COMMANDS = ["login", "up", "add", "logout", "service"];
|
|
3421
|
-
var SERVICE_ACTIONS = ["install", "start", "uninstall", "status", "logs"];
|
|
3424
|
+
var SERVICE_ACTIONS = ["install", "start", "uninstall", "status", "logs", "auto-update"];
|
|
3422
3425
|
function isMachineCommand(c) {
|
|
3423
3426
|
return !!c && MACHINE_COMMANDS.includes(c);
|
|
3424
3427
|
}
|
|
3425
3428
|
function parseMachineArgs(argv) {
|
|
3426
3429
|
const [command, ...rest] = argv;
|
|
3427
3430
|
if (!isMachineCommand(command)) return { ok: false, help: false, error: `Unknown command: ${command}` };
|
|
3428
|
-
const out2 = { command, api: null, name: null, path: null, up: true, verbose: false, service: false, action: null, lines: 100 };
|
|
3431
|
+
const out2 = { command, api: null, name: null, path: null, up: true, verbose: false, service: false, action: null, lines: 100, toggle: null };
|
|
3429
3432
|
for (let i = 0; i < rest.length; i++) {
|
|
3430
3433
|
const a = rest[i];
|
|
3431
3434
|
const value = () => {
|
|
@@ -3478,9 +3481,14 @@ function parseMachineArgs(argv) {
|
|
|
3478
3481
|
out2.action = a;
|
|
3479
3482
|
continue;
|
|
3480
3483
|
}
|
|
3484
|
+
if (command === "service" && out2.action === "auto-update" && !out2.toggle && (a === "on" || a === "off")) {
|
|
3485
|
+
out2.toggle = a;
|
|
3486
|
+
continue;
|
|
3487
|
+
}
|
|
3481
3488
|
return { ok: false, help: false, error: `Unexpected argument ${a}.` };
|
|
3482
3489
|
}
|
|
3483
|
-
if (command === "service" && !out2.action) return { ok: false, help: false, error: "Choose install, start, uninstall, status or
|
|
3490
|
+
if (command === "service" && !out2.action) return { ok: false, help: false, error: "Choose install, start, uninstall, status, logs or auto-update." };
|
|
3491
|
+
if (command === "service" && out2.action === "auto-update" && !out2.toggle) return { ok: false, help: false, error: "Say auto-update on or auto-update off." };
|
|
3484
3492
|
return { ok: true, args: out2 };
|
|
3485
3493
|
}
|
|
3486
3494
|
|
|
@@ -3684,9 +3692,10 @@ function installStableCopy(o) {
|
|
|
3684
3692
|
}
|
|
3685
3693
|
function removeStableCopies(home, keep = null) {
|
|
3686
3694
|
const base = (0, import_path6.join)(sqHome(home), "cli");
|
|
3695
|
+
const kept = new Set(keep === null ? [] : typeof keep === "string" ? [keep] : keep);
|
|
3687
3696
|
const removed = [];
|
|
3688
3697
|
for (const name of (0, import_fs5.existsSync)(base) ? (0, import_fs5.readdirSync)(base) : []) {
|
|
3689
|
-
if (name
|
|
3698
|
+
if (kept.has(name) || !/^\.?[0-9A-Za-z.+-]{1,60}$/.test(name)) continue;
|
|
3690
3699
|
const full = (0, import_path6.join)(base, name);
|
|
3691
3700
|
if (!(0, import_fs5.lstatSync)(full).isDirectory()) continue;
|
|
3692
3701
|
(0, import_fs5.rmSync)(full, { recursive: true, force: true });
|
|
@@ -3891,7 +3900,7 @@ function releaseLock(home, api, pid = process.pid) {
|
|
|
3891
3900
|
}
|
|
3892
3901
|
function binFromDefinition(text3) {
|
|
3893
3902
|
if (!text3) return null;
|
|
3894
|
-
const m = /([^"<>\s]*[\\/]@scalequality[\\/]cli[\\/]bin[\\/]scalequality\.mjs)/.exec(text3);
|
|
3903
|
+
const m = /([^"<>\s]*[\\/]@scalequality[\\/]cli[\\/]bin[\\/]scalequality\.mjs)/.exec(text3) ?? /([^"<>\s]*[\\/]\.scalequality[\\/]cli[\\/]launcher\.mjs)/.exec(text3);
|
|
3895
3904
|
return m ? m[1] : null;
|
|
3896
3905
|
}
|
|
3897
3906
|
async function serviceStatus(env, api) {
|
|
@@ -13571,7 +13580,226 @@ async function hasLocalTranscript(configDir, sessionId) {
|
|
|
13571
13580
|
return dirs.some((d) => (0, import_fs9.existsSync)((0, import_path15.join)(projects, d, `${sessionId}.jsonl`)));
|
|
13572
13581
|
}
|
|
13573
13582
|
|
|
13583
|
+
// src/application/services/workspaceSandbox/selfUpdate.ts
|
|
13584
|
+
var import_crypto8 = require("crypto");
|
|
13585
|
+
var import_fs10 = require("fs");
|
|
13586
|
+
var import_path16 = require("path");
|
|
13587
|
+
var REGISTRY_LATEST = "https://registry.npmjs.org/@scalequality%2fcli/latest";
|
|
13588
|
+
var UPDATE_FIRST_CHECK_MS = 2 * 6e4;
|
|
13589
|
+
var UPDATE_INTERVAL_MS = 6 * 36e5;
|
|
13590
|
+
var UPDATE_IDLE_POLL_MS = 6e4;
|
|
13591
|
+
var CONFIRM_AFTER_MS = 9e4;
|
|
13592
|
+
var RESTART_EXIT_CODE = 75;
|
|
13593
|
+
var MAX_START_ATTEMPTS = 3;
|
|
13594
|
+
var cliDir = (home) => (0, import_path16.join)(sqHome(home), "cli");
|
|
13595
|
+
var launcherPath = (home) => (0, import_path16.join)(cliDir(home), "launcher.mjs");
|
|
13596
|
+
var currentPath = (home) => (0, import_path16.join)(cliDir(home), "current.json");
|
|
13597
|
+
var autoUpdateOffPath = (home) => (0, import_path16.join)(cliDir(home), "auto-update-off");
|
|
13598
|
+
var VERSION = /^(\d{1,5})\.(\d{1,5})\.(\d{1,5})$/;
|
|
13599
|
+
function isNewer(candidate, current) {
|
|
13600
|
+
const a = VERSION.exec(candidate), b = VERSION.exec(current);
|
|
13601
|
+
if (!a || !b) return false;
|
|
13602
|
+
for (let i = 1; i <= 3; i++) {
|
|
13603
|
+
const d = Number(a[i]) - Number(b[i]);
|
|
13604
|
+
if (d !== 0) return d > 0;
|
|
13605
|
+
}
|
|
13606
|
+
return false;
|
|
13607
|
+
}
|
|
13608
|
+
function readJson2(file) {
|
|
13609
|
+
try {
|
|
13610
|
+
const v = JSON.parse((0, import_fs10.readFileSync)(file, "utf8"));
|
|
13611
|
+
return v && typeof v === "object" ? v : null;
|
|
13612
|
+
} catch {
|
|
13613
|
+
return null;
|
|
13614
|
+
}
|
|
13615
|
+
}
|
|
13616
|
+
function writeJsonAtomic(file, value) {
|
|
13617
|
+
(0, import_fs10.mkdirSync)((0, import_path16.dirname)(file), { recursive: true, mode: 448 });
|
|
13618
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
13619
|
+
(0, import_fs10.writeFileSync)(tmp, `${JSON.stringify(value, null, 2)}
|
|
13620
|
+
`, { mode: 384 });
|
|
13621
|
+
(0, import_fs10.renameSync)(tmp, file);
|
|
13622
|
+
}
|
|
13623
|
+
var installed = (v) => {
|
|
13624
|
+
const r = v;
|
|
13625
|
+
return r && typeof r.version === "string" && typeof r.bin === "string" ? { version: r.version, bin: r.bin } : null;
|
|
13626
|
+
};
|
|
13627
|
+
function readCurrent(home) {
|
|
13628
|
+
const raw = readJson2(currentPath(home));
|
|
13629
|
+
const self = installed(raw);
|
|
13630
|
+
if (!raw || !self) return null;
|
|
13631
|
+
return {
|
|
13632
|
+
...self,
|
|
13633
|
+
previous: installed(raw.previous),
|
|
13634
|
+
pending: raw.pending === true,
|
|
13635
|
+
attempts: typeof raw.attempts === "number" ? raw.attempts : 0,
|
|
13636
|
+
rejected: Array.isArray(raw.rejected) ? raw.rejected.filter((x) => typeof x === "string").slice(-20) : []
|
|
13637
|
+
};
|
|
13638
|
+
}
|
|
13639
|
+
function launcherSource(fallbackBin) {
|
|
13640
|
+
return `// ScaleQuality CLI launcher: the background service starts this file, and it runs the version named in
|
|
13641
|
+
// current.json beside it. A new version that fails to start ${MAX_START_ATTEMPTS} times goes back to the previous one.
|
|
13642
|
+
// Written by the ScaleQuality CLI; it is replaced on the next "scalequality login".
|
|
13643
|
+
import { existsSync, readFileSync, renameSync, writeFileSync } from 'node:fs'
|
|
13644
|
+
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
13645
|
+
|
|
13646
|
+
const file = fileURLToPath(new URL('./current.json', import.meta.url))
|
|
13647
|
+
const fallback = ${JSON.stringify(fallbackBin)}
|
|
13648
|
+
let current = null
|
|
13649
|
+
try { current = JSON.parse(readFileSync(file, 'utf8')) } catch { current = null }
|
|
13650
|
+
const save = (value) => {
|
|
13651
|
+
try { writeFileSync(file + '.launcher.tmp', JSON.stringify(value, null, 2) + '\\n', { mode: 0o600 }); renameSync(file + '.launcher.tmp', file) } catch {}
|
|
13652
|
+
}
|
|
13653
|
+
if (current && current.pending === true) {
|
|
13654
|
+
const attempts = (Number(current.attempts) || 0) + 1
|
|
13655
|
+
const previous = current.previous
|
|
13656
|
+
if (attempts > ${MAX_START_ATTEMPTS} && previous && typeof previous.bin === 'string' && existsSync(previous.bin)) {
|
|
13657
|
+
const rejected = [...(Array.isArray(current.rejected) ? current.rejected : []), current.version].slice(-20)
|
|
13658
|
+
current = { version: previous.version, bin: previous.bin, previous: null, pending: false, attempts: 0, rejected }
|
|
13659
|
+
} else current = { ...current, attempts }
|
|
13660
|
+
save(current)
|
|
13661
|
+
}
|
|
13662
|
+
const bin = current && typeof current.bin === 'string' && existsSync(current.bin) ? current.bin : fallback
|
|
13663
|
+
if (!existsSync(bin)) {
|
|
13664
|
+
process.stderr.write('The ScaleQuality CLI copy this service runs is missing. Run: npx @scalequality/cli@latest login\\n')
|
|
13665
|
+
process.exit(1)
|
|
13666
|
+
}
|
|
13667
|
+
process.argv[1] = bin
|
|
13668
|
+
process.env.SCALEQUALITY_LAUNCHER = '1'
|
|
13669
|
+
await import(pathToFileURL(bin).href)
|
|
13670
|
+
`;
|
|
13671
|
+
}
|
|
13672
|
+
function installLauncher(home, copy) {
|
|
13673
|
+
const current = readCurrent(home);
|
|
13674
|
+
writeJsonAtomic(currentPath(home), { version: copy.version, bin: copy.bin, previous: null, pending: false, attempts: 0, rejected: current?.rejected ?? [] });
|
|
13675
|
+
const file = launcherPath(home);
|
|
13676
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
13677
|
+
(0, import_fs10.writeFileSync)(tmp, launcherSource(copy.bin), { mode: 420 });
|
|
13678
|
+
(0, import_fs10.renameSync)(tmp, file);
|
|
13679
|
+
return file;
|
|
13680
|
+
}
|
|
13681
|
+
function confirmCurrent(home, version) {
|
|
13682
|
+
const current = readCurrent(home);
|
|
13683
|
+
if (!current || current.version !== version || !current.pending) return false;
|
|
13684
|
+
writeJsonAtomic(currentPath(home), { ...current, pending: false, attempts: 0, previous: null });
|
|
13685
|
+
removeStableCopies(home, [version]);
|
|
13686
|
+
return true;
|
|
13687
|
+
}
|
|
13688
|
+
function autoUpdateEnabled(home, env) {
|
|
13689
|
+
return env.SCALEQUALITY_NO_AUTO_UPDATE !== "1" && !(0, import_fs10.existsSync)(autoUpdateOffPath(home));
|
|
13690
|
+
}
|
|
13691
|
+
function setAutoUpdate(home, on) {
|
|
13692
|
+
if (on) (0, import_fs10.rmSync)(autoUpdateOffPath(home), { force: true });
|
|
13693
|
+
else {
|
|
13694
|
+
(0, import_fs10.mkdirSync)(cliDir(home), { recursive: true, mode: 448 });
|
|
13695
|
+
(0, import_fs10.writeFileSync)(autoUpdateOffPath(home), `${(/* @__PURE__ */ new Date()).toISOString()}
|
|
13696
|
+
`, { mode: 384 });
|
|
13697
|
+
}
|
|
13698
|
+
}
|
|
13699
|
+
async function latestVersion(fetchImpl = fetch) {
|
|
13700
|
+
try {
|
|
13701
|
+
const res = await fetchImpl(REGISTRY_LATEST, { headers: { accept: "application/json" }, signal: AbortSignal.timeout(15e3) });
|
|
13702
|
+
if (!res.ok) return null;
|
|
13703
|
+
const body = await res.json();
|
|
13704
|
+
return body?.name === "@scalequality/cli" && typeof body.version === "string" && VERSION.test(body.version) ? body.version : null;
|
|
13705
|
+
} catch {
|
|
13706
|
+
return null;
|
|
13707
|
+
}
|
|
13708
|
+
}
|
|
13709
|
+
function npmCli(nodePath, exists = import_fs10.existsSync, resolve8 = realOrNull, pathEnv = process.env.PATH ?? "") {
|
|
13710
|
+
const dir = (0, import_path16.dirname)(nodePath);
|
|
13711
|
+
const script = (p) => p && /npm-cli\.js$/.test(p) && exists(p) ? p : null;
|
|
13712
|
+
const linked = (p) => exists(p) ? script(resolve8(p)) : null;
|
|
13713
|
+
const layouts = [
|
|
13714
|
+
(0, import_path16.join)(dir, "node_modules", "npm", "bin", "npm-cli.js"),
|
|
13715
|
+
(0, import_path16.join)(dir, "..", "lib", "node_modules", "npm", "bin", "npm-cli.js"),
|
|
13716
|
+
(0, import_path16.join)(dir, "..", "libexec", "lib", "node_modules", "npm", "bin", "npm-cli.js")
|
|
13717
|
+
];
|
|
13718
|
+
return linked((0, import_path16.join)(dir, "npm")) ?? layouts.map(script).find(Boolean) ?? pathEnv.split(import_path16.delimiter).filter(Boolean).map((d) => linked((0, import_path16.join)(d, "npm"))).find(Boolean) ?? null;
|
|
13719
|
+
}
|
|
13720
|
+
function realOrNull(p) {
|
|
13721
|
+
try {
|
|
13722
|
+
return (0, import_fs10.realpathSync)(p);
|
|
13723
|
+
} catch {
|
|
13724
|
+
return null;
|
|
13725
|
+
}
|
|
13726
|
+
}
|
|
13727
|
+
var UpdateError = class extends Error {
|
|
13728
|
+
constructor(code, message) {
|
|
13729
|
+
super(message);
|
|
13730
|
+
this.code = code;
|
|
13731
|
+
}
|
|
13732
|
+
code;
|
|
13733
|
+
};
|
|
13734
|
+
async function installVersion(o) {
|
|
13735
|
+
const npm = o.npm === void 0 ? npmCli(o.nodePath) : o.npm;
|
|
13736
|
+
if (!npm) throw new UpdateError("NPM_NOT_FOUND", `npm was not found next to ${o.nodePath}.`);
|
|
13737
|
+
const staging = (0, import_path16.join)(cliDir(o.home), `.update-${o.version}`);
|
|
13738
|
+
(0, import_fs10.rmSync)(staging, { recursive: true, force: true });
|
|
13739
|
+
(0, import_fs10.mkdirSync)(staging, { recursive: true, mode: 448 });
|
|
13740
|
+
try {
|
|
13741
|
+
(0, import_fs10.writeFileSync)((0, import_path16.join)(staging, "package.json"), '{ "private": true }\n');
|
|
13742
|
+
const r = await o.run(o.nodePath, [
|
|
13743
|
+
npm,
|
|
13744
|
+
"install",
|
|
13745
|
+
"--prefix",
|
|
13746
|
+
staging,
|
|
13747
|
+
"--no-audit",
|
|
13748
|
+
"--no-fund",
|
|
13749
|
+
"--ignore-scripts",
|
|
13750
|
+
"--omit=dev",
|
|
13751
|
+
"--registry=https://registry.npmjs.org/",
|
|
13752
|
+
`@scalequality/cli@${o.version}`
|
|
13753
|
+
]);
|
|
13754
|
+
if (r.code !== 0) throw new UpdateError("NPM_INSTALL_FAILED", `npm could not install @scalequality/cli@${o.version}: ${`${r.stderr || r.stdout}`.trim().split("\n").slice(-1)[0]?.slice(0, 200) ?? r.code}`);
|
|
13755
|
+
const root = (0, import_path16.join)(staging, "node_modules", "@scalequality", "cli");
|
|
13756
|
+
const pkg = readJson2((0, import_path16.join)(root, "package.json"));
|
|
13757
|
+
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}.`);
|
|
13758
|
+
const build = readJson2((0, import_path16.join)(root, "dist", "connect.build.json"));
|
|
13759
|
+
const bundle = (0, import_path16.join)(root, "dist", "connect.cjs");
|
|
13760
|
+
const sha = (0, import_fs10.existsSync)(bundle) ? (0, import_crypto8.createHash)("sha256").update((0, import_fs10.readFileSync)(bundle)).digest("hex") : null;
|
|
13761
|
+
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.`);
|
|
13762
|
+
let copy;
|
|
13763
|
+
try {
|
|
13764
|
+
copy = installStableCopy({ packageRoot: root, home: o.home });
|
|
13765
|
+
} catch (e) {
|
|
13766
|
+
throw new UpdateError("COPY_FAILED", e.message);
|
|
13767
|
+
}
|
|
13768
|
+
return { version: copy.version, bin: copy.bin };
|
|
13769
|
+
} finally {
|
|
13770
|
+
(0, import_fs10.rmSync)(staging, { recursive: true, force: true });
|
|
13771
|
+
}
|
|
13772
|
+
}
|
|
13773
|
+
function stageVersion(home, next, running) {
|
|
13774
|
+
const current = readCurrent(home);
|
|
13775
|
+
writeJsonAtomic(currentPath(home), { version: next.version, bin: next.bin, previous: running, pending: true, attempts: 0, rejected: current?.rejected ?? [] });
|
|
13776
|
+
}
|
|
13777
|
+
async function checkForUpdate(d) {
|
|
13778
|
+
if (!autoUpdateEnabled(d.home, d.env) || !d.launched()) return "NONE";
|
|
13779
|
+
const latest = await latestVersion(d.fetchImpl);
|
|
13780
|
+
if (!latest || !isNewer(latest, d.version)) return "NONE";
|
|
13781
|
+
if (readCurrent(d.home)?.rejected?.includes(latest)) return "NONE";
|
|
13782
|
+
const sleep2 = d.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
13783
|
+
while (d.busy()) await sleep2(UPDATE_IDLE_POLL_MS);
|
|
13784
|
+
const running = readCurrent(d.home);
|
|
13785
|
+
if (!running || running.version !== d.version) return "NONE";
|
|
13786
|
+
d.say(`A new version of the ScaleQuality CLI is available (${latest}); installing it beside ${d.version}.`);
|
|
13787
|
+
let next;
|
|
13788
|
+
try {
|
|
13789
|
+
next = await installVersion({ version: latest, home: d.home, nodePath: d.nodePath, run: d.run, npm: d.npm });
|
|
13790
|
+
} catch (e) {
|
|
13791
|
+
d.say(`The update to ${latest} was not installed (${e.message}); ${d.version} keeps running.`);
|
|
13792
|
+
return "SKIPPED";
|
|
13793
|
+
}
|
|
13794
|
+
while (d.busy()) await sleep2(UPDATE_IDLE_POLL_MS);
|
|
13795
|
+
stageVersion(d.home, next, { version: running.version, bin: running.bin });
|
|
13796
|
+
d.say(`Restarting on ${latest}. If it does not start, ${d.version} comes back on its own.`);
|
|
13797
|
+
d.restart();
|
|
13798
|
+
return "RESTARTING";
|
|
13799
|
+
}
|
|
13800
|
+
|
|
13574
13801
|
// src/main/workspace-connect.ts
|
|
13802
|
+
var import_child_process4 = require("child_process");
|
|
13575
13803
|
var importEsm = new Function("specifier", "return import(specifier)");
|
|
13576
13804
|
async function loadSdk() {
|
|
13577
13805
|
const resolved = require.resolve("@anthropic-ai/claude-agent-sdk");
|
|
@@ -13586,9 +13814,9 @@ var say = (line = "") => {
|
|
|
13586
13814
|
var cliVersion = process.env.SCALEQUALITY_CLI_VERSION || "dev";
|
|
13587
13815
|
var userAgent = (mode) => `scalequality-cli/${cliVersion} (${mode}; node ${process.versions.node}; ${process.platform})`;
|
|
13588
13816
|
var HOME = (0, import_os4.homedir)();
|
|
13589
|
-
var SQ_HOME = (0,
|
|
13590
|
-
var ENGINE_HOME = (0,
|
|
13591
|
-
var credentials = new CredentialStore((0,
|
|
13817
|
+
var SQ_HOME = (0, import_path17.join)(HOME, ".scalequality");
|
|
13818
|
+
var ENGINE_HOME = (0, import_path17.join)(SQ_HOME, "workspace");
|
|
13819
|
+
var credentials = new CredentialStore((0, import_path17.join)(SQ_HOME, "credentials.json"));
|
|
13592
13820
|
function startFailure(e, api) {
|
|
13593
13821
|
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.';
|
|
13594
13822
|
if (e instanceof SessionGoneError) {
|
|
@@ -13601,16 +13829,16 @@ function startFailure(e, api) {
|
|
|
13601
13829
|
return "ScaleQuality could not start this session. Try again in a moment, or get a new code from the AI Workspace.";
|
|
13602
13830
|
}
|
|
13603
13831
|
function engineDirs() {
|
|
13604
|
-
const configDir = (0,
|
|
13605
|
-
const scratch = (0,
|
|
13606
|
-
(0,
|
|
13607
|
-
(0,
|
|
13832
|
+
const configDir = (0, import_path17.join)(ENGINE_HOME, "claude-home");
|
|
13833
|
+
const scratch = (0, import_path17.join)(ENGINE_HOME, "tmp");
|
|
13834
|
+
(0, import_fs11.mkdirSync)(configDir, { recursive: true, mode: 448 });
|
|
13835
|
+
(0, import_fs11.mkdirSync)(scratch, { recursive: true, mode: 448 });
|
|
13608
13836
|
return { configDir, scratch };
|
|
13609
13837
|
}
|
|
13610
13838
|
function createLocalEngine(o) {
|
|
13611
13839
|
const { configDir, scratch } = engineDirs();
|
|
13612
|
-
const sessions = (0,
|
|
13613
|
-
const stateDir = (0,
|
|
13840
|
+
const sessions = (0, import_path17.join)(ENGINE_HOME, "sessions");
|
|
13841
|
+
const stateDir = (0, import_path17.join)(sessions, o.sessionId);
|
|
13614
13842
|
void pruneSessionStates(sessions, stateDir).catch(() => void 0);
|
|
13615
13843
|
const log = {
|
|
13616
13844
|
info: (msg, ctx) => {
|
|
@@ -13797,7 +14025,14 @@ async function setUpService(api) {
|
|
|
13797
14025
|
say(style.yellow(e.message));
|
|
13798
14026
|
return false;
|
|
13799
14027
|
}
|
|
13800
|
-
|
|
14028
|
+
let launcher;
|
|
14029
|
+
try {
|
|
14030
|
+
launcher = installLauncher(HOME, { version, bin });
|
|
14031
|
+
} catch (e) {
|
|
14032
|
+
say(style.yellow(`The launcher of the background service could not be written: ${e.message}`));
|
|
14033
|
+
return false;
|
|
14034
|
+
}
|
|
14035
|
+
const result = await installService(env, { bin: launcher, api });
|
|
13801
14036
|
if (!result.ok) {
|
|
13802
14037
|
say(style.yellow(result.message));
|
|
13803
14038
|
return false;
|
|
@@ -13903,7 +14138,7 @@ async function upMain(api, verbose, service) {
|
|
|
13903
14138
|
let shuttingDown = false;
|
|
13904
14139
|
let agent;
|
|
13905
14140
|
const startSession = ({ sessionId, secret, root }) => {
|
|
13906
|
-
const label = (0,
|
|
14141
|
+
const label = (0, import_path17.basename)(root);
|
|
13907
14142
|
const prefix = style.dim(`[${label}] `);
|
|
13908
14143
|
const consoleLog = new ConsoleLog(style);
|
|
13909
14144
|
let resolveDone = () => void 0;
|
|
@@ -13953,6 +14188,15 @@ async function upMain(api, verbose, service) {
|
|
|
13953
14188
|
process.on("unhandledRejection", (e) => {
|
|
13954
14189
|
if (verbose || service) say(style.dim(`[warn] unhandled rejection ${e?.message}`));
|
|
13955
14190
|
});
|
|
14191
|
+
if (service && process.env.SCALEQUALITY_LAUNCHER === "1") startSelfUpdate(api, () => agent.sessions.size > 0, () => {
|
|
14192
|
+
agent.stop();
|
|
14193
|
+
releaseLock(HOME, api);
|
|
14194
|
+
if (process.platform === "win32") {
|
|
14195
|
+
(0, import_child_process4.spawn)(process.execPath, [launcherPath(HOME), "up", "--api", api, "--service"], { detached: true, stdio: "ignore", windowsHide: true, env: process.env }).unref();
|
|
14196
|
+
process.exit(0);
|
|
14197
|
+
}
|
|
14198
|
+
process.exit(RESTART_EXIT_CODE);
|
|
14199
|
+
});
|
|
13956
14200
|
say(style.bold(`ScaleQuality: keeping "${credential.name}" connected to ${api}${service ? " (background service)" : ""}`));
|
|
13957
14201
|
say(` Folders: ${credential.folders.length ? credential.folders.join(", ") : "none yet (scalequality add <folder>)"}`);
|
|
13958
14202
|
say(style.dim(" Commands that sessions want to run are approved in the browser."));
|
|
@@ -13977,9 +14221,44 @@ async function upMain(api, verbose, service) {
|
|
|
13977
14221
|
throw e;
|
|
13978
14222
|
}
|
|
13979
14223
|
}
|
|
14224
|
+
function startSelfUpdate(api, busy, restart) {
|
|
14225
|
+
const confirm = setTimeout(() => {
|
|
14226
|
+
try {
|
|
14227
|
+
if (confirmCurrent(HOME, cliVersion)) say(`Version ${cliVersion} started well; the previous copy was removed.`);
|
|
14228
|
+
} catch (e) {
|
|
14229
|
+
say(`[warn] version ${cliVersion} not confirmed: ${e.message}`);
|
|
14230
|
+
}
|
|
14231
|
+
}, CONFIRM_AFTER_MS);
|
|
14232
|
+
confirm.unref();
|
|
14233
|
+
let checking = false;
|
|
14234
|
+
const check = async () => {
|
|
14235
|
+
if (checking) return;
|
|
14236
|
+
checking = true;
|
|
14237
|
+
try {
|
|
14238
|
+
await checkForUpdate({
|
|
14239
|
+
home: HOME,
|
|
14240
|
+
env: process.env,
|
|
14241
|
+
version: cliVersion,
|
|
14242
|
+
nodePath: process.execPath,
|
|
14243
|
+
run: runCommand,
|
|
14244
|
+
busy,
|
|
14245
|
+
say,
|
|
14246
|
+
launched: () => process.env.SCALEQUALITY_LAUNCHER === "1",
|
|
14247
|
+
restart
|
|
14248
|
+
});
|
|
14249
|
+
} catch (e) {
|
|
14250
|
+
say(`[warn] update check failed: ${e.message}`);
|
|
14251
|
+
} finally {
|
|
14252
|
+
checking = false;
|
|
14253
|
+
}
|
|
14254
|
+
};
|
|
14255
|
+
setTimeout(() => void check(), UPDATE_FIRST_CHECK_MS).unref();
|
|
14256
|
+
setInterval(() => void check(), UPDATE_INTERVAL_MS).unref();
|
|
14257
|
+
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."));
|
|
14258
|
+
}
|
|
13980
14259
|
async function addMain(api, path) {
|
|
13981
14260
|
try {
|
|
13982
|
-
const real = await addFolder(credentials, api, (0,
|
|
14261
|
+
const real = await addFolder(credentials, api, (0, import_path17.resolve)(path ?? process.cwd()), HOME);
|
|
13983
14262
|
say(style.green(`Added ${real}.`));
|
|
13984
14263
|
const credential = credentials.get(api);
|
|
13985
14264
|
const client = new MachineClient(api, { token: credential.machineToken, userAgent: userAgent("add") });
|
|
@@ -14022,8 +14301,13 @@ async function logoutMain(api) {
|
|
|
14022
14301
|
credentials.remove(api);
|
|
14023
14302
|
say(`Deleted the local credential for ${api}.`);
|
|
14024
14303
|
}
|
|
14025
|
-
async function serviceMain(action, api, lines2) {
|
|
14304
|
+
async function serviceMain(action, api, lines2, toggle = null) {
|
|
14026
14305
|
const env = serviceEnv();
|
|
14306
|
+
if (action === "auto-update") {
|
|
14307
|
+
setAutoUpdate(HOME, toggle === "on");
|
|
14308
|
+
say(toggle === "on" ? "Automatic updates are on: the service installs new versions by itself while no session runs." : "Automatic updates are off. Update by hand with: npx @scalequality/cli@latest login");
|
|
14309
|
+
return;
|
|
14310
|
+
}
|
|
14027
14311
|
if (action === "install" || action === "start") {
|
|
14028
14312
|
if (!credentials.get(api)) {
|
|
14029
14313
|
say(style.red(`This computer is not connected to ${api}. Run scalequality login${apiFlag(api)} first.`));
|
|
@@ -14048,6 +14332,8 @@ async function serviceMain(action, api, lines2) {
|
|
|
14048
14332
|
`Running ${s.running ? `yes${s.pid ? ` (process ${s.pid})` : ""}` : "no"}`,
|
|
14049
14333
|
...s.definition ? [`Definition ${s.definition}`] : [],
|
|
14050
14334
|
...s.bin ? [`Runs ${s.bin}`] : [],
|
|
14335
|
+
...readCurrent(HOME) ? [`Version ${readCurrent(HOME).version}${readCurrent(HOME).pending ? " (just updated, confirming)" : ""}`] : [],
|
|
14336
|
+
`Automatic updates ${autoUpdateEnabled(HOME, process.env) ? "on" : "off"}`,
|
|
14051
14337
|
`Log ${s.logFile}`
|
|
14052
14338
|
].join("\n") + "\n");
|
|
14053
14339
|
return;
|
|
@@ -14087,7 +14373,7 @@ async function main() {
|
|
|
14087
14373
|
if (args.command === "login") await loginMain(api, args.name, args.up, args.verbose);
|
|
14088
14374
|
else if (args.command === "up") await upMain(api, args.verbose, args.service || process.env.SCALEQUALITY_SERVICE === "1");
|
|
14089
14375
|
else if (args.command === "add") await addMain(api, args.path);
|
|
14090
|
-
else if (args.command === "service") await serviceMain(args.action, api, args.lines);
|
|
14376
|
+
else if (args.command === "service") await serviceMain(args.action, api, args.lines, args.toggle);
|
|
14091
14377
|
else await logoutMain(api);
|
|
14092
14378
|
}
|
|
14093
14379
|
main().catch((e) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalequality/cli",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
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": {
|