@allocator-one/harness-cli 1.2.1 → 1.3.0
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.
|
@@ -1477,12 +1477,12 @@ function resolveConfig(env = process.env, credentialsPath, repo) {
|
|
|
1477
1477
|
};
|
|
1478
1478
|
}
|
|
1479
1479
|
function resolveCacheDir(env = process.env) {
|
|
1480
|
-
return absolute(env["HARNESS_CACHE_DIR"]) ?? join4(
|
|
1480
|
+
return absolute(env["HARNESS_CACHE_DIR"]) ?? join4(resolveCacheHome(env), "harness", "attachments");
|
|
1481
1481
|
}
|
|
1482
1482
|
function absolute(value) {
|
|
1483
1483
|
return value !== void 0 && value !== "" && isAbsolute3(value) ? value : null;
|
|
1484
1484
|
}
|
|
1485
|
-
function
|
|
1485
|
+
function resolveCacheHome(env = process.env) {
|
|
1486
1486
|
return absolute(env["XDG_CACHE_HOME"]) ?? join4(homedir3() || tmpdir(), ".cache");
|
|
1487
1487
|
}
|
|
1488
1488
|
function normalize(url) {
|
|
@@ -1595,6 +1595,7 @@ export {
|
|
|
1595
1595
|
trustHost,
|
|
1596
1596
|
DEFAULT_BASE_URL,
|
|
1597
1597
|
resolveConfig,
|
|
1598
|
+
resolveCacheHome,
|
|
1598
1599
|
validateBaseUrl,
|
|
1599
1600
|
loginHelp
|
|
1600
1601
|
};
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
CLI_VERSION,
|
|
3
4
|
Client,
|
|
4
5
|
CommandError,
|
|
5
6
|
DEFAULT_BASE_URL,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
readCredentials,
|
|
44
45
|
ready,
|
|
45
46
|
requireIdentifiers,
|
|
47
|
+
resolveCacheHome,
|
|
46
48
|
resolveConfig,
|
|
47
49
|
search,
|
|
48
50
|
show,
|
|
@@ -57,7 +59,7 @@ import {
|
|
|
57
59
|
version,
|
|
58
60
|
workflowStates,
|
|
59
61
|
writeCredentials
|
|
60
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-3WR6YFU6.js";
|
|
61
63
|
|
|
62
64
|
// src/cli.ts
|
|
63
65
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -370,6 +372,426 @@ function defaultOpenBrowser(url) {
|
|
|
370
372
|
}
|
|
371
373
|
}
|
|
372
374
|
|
|
375
|
+
// src/core/upgrade.ts
|
|
376
|
+
import { spawn, spawnSync } from "child_process";
|
|
377
|
+
import {
|
|
378
|
+
accessSync as accessSync2,
|
|
379
|
+
constants,
|
|
380
|
+
existsSync,
|
|
381
|
+
linkSync,
|
|
382
|
+
mkdirSync,
|
|
383
|
+
readFileSync,
|
|
384
|
+
realpathSync,
|
|
385
|
+
renameSync,
|
|
386
|
+
rmSync,
|
|
387
|
+
statSync,
|
|
388
|
+
writeFileSync
|
|
389
|
+
} from "fs";
|
|
390
|
+
import { basename, dirname, join as join2, parse, sep } from "path";
|
|
391
|
+
import { fileURLToPath } from "url";
|
|
392
|
+
var PACKAGE_NAME = "@allocator-one/harness-cli";
|
|
393
|
+
var UPDATE_CHECK_COMMAND = "__update-check";
|
|
394
|
+
var REEXEC_SENTINEL = "HARNESS_UPGRADED";
|
|
395
|
+
var DEFAULT_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
|
|
396
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
397
|
+
var REGISTRY_TIMEOUT_MS = 5e3;
|
|
398
|
+
var NPM_ROOT_TIMEOUT_MS = 15e3;
|
|
399
|
+
var INSTALL_TIMEOUT_MS = 12e4;
|
|
400
|
+
var LOCK_STALE_MS = NPM_ROOT_TIMEOUT_MS + INSTALL_TIMEOUT_MS + 6e4;
|
|
401
|
+
function detectInstall(from = fileURLToPath(import.meta.url)) {
|
|
402
|
+
const unknown = { kind: "unknown", packageRoot: null, nodeModules: null };
|
|
403
|
+
if (from.split(sep).includes("_npx")) return { ...unknown, kind: "npx" };
|
|
404
|
+
const packageRoot = findPackageRoot(from);
|
|
405
|
+
if (packageRoot === null) return unknown;
|
|
406
|
+
if (existsSync(join2(packageRoot, ".git"))) {
|
|
407
|
+
return { kind: "checkout", packageRoot, nodeModules: null };
|
|
408
|
+
}
|
|
409
|
+
const nodeModules = enclosingNodeModules(packageRoot);
|
|
410
|
+
if (nodeModules === null) return { ...unknown, packageRoot };
|
|
411
|
+
const kind = existsSync(join2(nodeModules, ".bin")) ? "local" : "global";
|
|
412
|
+
return { kind, packageRoot, nodeModules };
|
|
413
|
+
}
|
|
414
|
+
function findPackageRoot(from) {
|
|
415
|
+
let dir = dirname(from);
|
|
416
|
+
const { root } = parse(dir);
|
|
417
|
+
for (; ; ) {
|
|
418
|
+
if (packageNameAt(join2(dir, "package.json")) === PACKAGE_NAME) return dir;
|
|
419
|
+
if (dir === root) return null;
|
|
420
|
+
dir = dirname(dir);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function packageNameAt(path) {
|
|
424
|
+
try {
|
|
425
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
426
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
427
|
+
const name = parsed["name"];
|
|
428
|
+
return typeof name === "string" ? name : null;
|
|
429
|
+
} catch {
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
function versionAt(packageRoot) {
|
|
434
|
+
try {
|
|
435
|
+
const parsed = JSON.parse(readFileSync(join2(packageRoot, "package.json"), "utf8"));
|
|
436
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
437
|
+
const version2 = parsed["version"];
|
|
438
|
+
return typeof version2 === "string" ? version2 : null;
|
|
439
|
+
} catch {
|
|
440
|
+
return null;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
function enclosingNodeModules(packageRoot) {
|
|
444
|
+
let dir = dirname(packageRoot);
|
|
445
|
+
const { root } = parse(dir);
|
|
446
|
+
for (; ; ) {
|
|
447
|
+
if (basename(dir) === "node_modules") return dir;
|
|
448
|
+
if (dir === root) return null;
|
|
449
|
+
dir = dirname(dir);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
var SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
453
|
+
function parseVersion(value) {
|
|
454
|
+
const match = SEMVER.exec(value.trim());
|
|
455
|
+
if (match === null) return null;
|
|
456
|
+
return {
|
|
457
|
+
parts: [Number(match[1]), Number(match[2]), Number(match[3])],
|
|
458
|
+
pre: match[4] ?? null
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
function isNewer(candidate, current) {
|
|
462
|
+
const a = parseVersion(candidate);
|
|
463
|
+
const b = parseVersion(current);
|
|
464
|
+
if (a === null || b === null) return false;
|
|
465
|
+
for (let index = 0; index < 3; index++) {
|
|
466
|
+
if (a.parts[index] !== b.parts[index]) return a.parts[index] > b.parts[index];
|
|
467
|
+
}
|
|
468
|
+
if (a.pre === b.pre) return false;
|
|
469
|
+
if (a.pre === null) return true;
|
|
470
|
+
if (b.pre === null) return false;
|
|
471
|
+
return comparePrerelease(a.pre, b.pre) > 0;
|
|
472
|
+
}
|
|
473
|
+
function comparePrerelease(left, right) {
|
|
474
|
+
const a = left.split(".");
|
|
475
|
+
const b = right.split(".");
|
|
476
|
+
for (let index = 0; index < Math.max(a.length, b.length); index++) {
|
|
477
|
+
const x = a[index];
|
|
478
|
+
const y = b[index];
|
|
479
|
+
if (x === void 0) return -1;
|
|
480
|
+
if (y === void 0) return 1;
|
|
481
|
+
const xNumeric = /^\d+$/.test(x);
|
|
482
|
+
const yNumeric = /^\d+$/.test(y);
|
|
483
|
+
if (xNumeric && yNumeric) {
|
|
484
|
+
if (Number(x) !== Number(y)) return Number(x) < Number(y) ? -1 : 1;
|
|
485
|
+
} else if (xNumeric !== yNumeric) {
|
|
486
|
+
return xNumeric ? -1 : 1;
|
|
487
|
+
} else if (x !== y) {
|
|
488
|
+
return x < y ? -1 : 1;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return 0;
|
|
492
|
+
}
|
|
493
|
+
function stampPath(env = process.env) {
|
|
494
|
+
return join2(resolveCacheHome(env), "harness", "update-check.json");
|
|
495
|
+
}
|
|
496
|
+
function readStamp(env = process.env) {
|
|
497
|
+
try {
|
|
498
|
+
const parsed = JSON.parse(readFileSync(stampPath(env), "utf8"));
|
|
499
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
500
|
+
const record = parsed;
|
|
501
|
+
const checkedAt = record["checkedAt"];
|
|
502
|
+
if (typeof checkedAt !== "number" || !Number.isFinite(checkedAt)) return null;
|
|
503
|
+
const latest = record["latest"];
|
|
504
|
+
return { checkedAt, latest: typeof latest === "string" ? latest : null };
|
|
505
|
+
} catch {
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function writeStamp(stamp, env = process.env) {
|
|
510
|
+
const path = stampPath(env);
|
|
511
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
512
|
+
const latest = stamp.latest ?? readStamp(env)?.latest ?? null;
|
|
513
|
+
try {
|
|
514
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
515
|
+
writeFileSync(temporary, JSON.stringify({ ...stamp, latest }) + "\n");
|
|
516
|
+
renameSync(temporary, path);
|
|
517
|
+
} catch {
|
|
518
|
+
try {
|
|
519
|
+
rmSync(temporary, { force: true });
|
|
520
|
+
} catch {
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function updateCheck(options = {}) {
|
|
525
|
+
const env = options.env ?? process.env;
|
|
526
|
+
const now = options.now ?? Date.now();
|
|
527
|
+
if (!checksEnabled(env)) return null;
|
|
528
|
+
const install = options.install ?? detectInstall();
|
|
529
|
+
if (install.kind !== "global" && install.kind !== "local") return null;
|
|
530
|
+
const stamp = readStamp(env);
|
|
531
|
+
if (stamp === null || now - stamp.checkedAt >= CHECK_INTERVAL_MS) {
|
|
532
|
+
writeStamp({ checkedAt: now, latest: stamp?.latest ?? null }, env);
|
|
533
|
+
(options.refresh ?? spawnRefresh)();
|
|
534
|
+
}
|
|
535
|
+
if (stamp !== null && stamp.latest !== null && isNewer(stamp.latest, CLI_VERSION)) {
|
|
536
|
+
return `harness: ${stamp.latest} is available (you have ${CLI_VERSION}) \u2014 ${upgradeHint(install)}
|
|
537
|
+
`;
|
|
538
|
+
}
|
|
539
|
+
return null;
|
|
540
|
+
}
|
|
541
|
+
function upgradeHint(install) {
|
|
542
|
+
return install.kind === "local" ? `npm i ${PACKAGE_NAME}@latest` : `npm i -g ${PACKAGE_NAME}@latest`;
|
|
543
|
+
}
|
|
544
|
+
function spawnRefresh() {
|
|
545
|
+
const self = process.argv[1];
|
|
546
|
+
if (self === void 0 || self === "") return;
|
|
547
|
+
try {
|
|
548
|
+
const child = spawn(process.execPath, [self, UPDATE_CHECK_COMMAND], {
|
|
549
|
+
detached: true,
|
|
550
|
+
stdio: "ignore"
|
|
551
|
+
});
|
|
552
|
+
child.on("error", () => {
|
|
553
|
+
});
|
|
554
|
+
child.unref();
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function performUpdateCheck(env = process.env, now = Date.now()) {
|
|
559
|
+
if (!checksEnabled(env)) return;
|
|
560
|
+
writeStamp({ checkedAt: now, latest: await fetchLatestVersion(env) }, env);
|
|
561
|
+
}
|
|
562
|
+
async function fetchLatestVersion(env) {
|
|
563
|
+
const configured = env["HARNESS_REGISTRY_URL"];
|
|
564
|
+
const url = configured !== void 0 && configured.trim() !== "" ? configured.trim() : DEFAULT_REGISTRY_URL;
|
|
565
|
+
try {
|
|
566
|
+
const response = await fetch(url, {
|
|
567
|
+
headers: { accept: "application/json" },
|
|
568
|
+
signal: AbortSignal.timeout(REGISTRY_TIMEOUT_MS)
|
|
569
|
+
});
|
|
570
|
+
if (!response.ok) return null;
|
|
571
|
+
const parsed = await response.json();
|
|
572
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
573
|
+
const version2 = parsed["version"];
|
|
574
|
+
if (typeof version2 !== "string" || parseVersion(version2) === null) return null;
|
|
575
|
+
return version2;
|
|
576
|
+
} catch {
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
function selfHeal(options = {}) {
|
|
581
|
+
const env = options.env ?? process.env;
|
|
582
|
+
const argv2 = options.argv ?? process.argv;
|
|
583
|
+
if (truthy(env[REEXEC_SENTINEL])) {
|
|
584
|
+
return { kind: "skipped", reason: "already upgraded once for this command" };
|
|
585
|
+
}
|
|
586
|
+
if (!autoUpgradeEnabled(env)) return { kind: "skipped", reason: "automatic upgrades are off" };
|
|
587
|
+
if (argv2[2] === "mcp") return { kind: "skipped", reason: "an MCP session must not be replaced mid-stream" };
|
|
588
|
+
const install = options.install ?? detectInstall();
|
|
589
|
+
if (install.kind !== "global" || install.packageRoot === null) {
|
|
590
|
+
return { kind: "skipped", reason: describeInstall(install.kind) };
|
|
591
|
+
}
|
|
592
|
+
const packageRoot = install.packageRoot;
|
|
593
|
+
const installed = versionAt(packageRoot);
|
|
594
|
+
if (installed !== null && isNewer(installed, CLI_VERSION)) {
|
|
595
|
+
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
596
|
+
}
|
|
597
|
+
const lock = takeLock(env);
|
|
598
|
+
if (lock === null) return { kind: "skipped", reason: "another harness process is already upgrading" };
|
|
599
|
+
try {
|
|
600
|
+
const stillHeld = () => readHolder(lock.path)?.pid === lock.pid;
|
|
601
|
+
const result = (options.runInstall ?? npmInstallGlobal)(install, stillHeld);
|
|
602
|
+
if (!result.ok) return { kind: "failed", reason: result.detail };
|
|
603
|
+
const after = versionAt(packageRoot);
|
|
604
|
+
if (after !== null && after === installed) {
|
|
605
|
+
return { kind: "failed", reason: `still ${after} after upgrading \u2014 no newer release is published` };
|
|
606
|
+
}
|
|
607
|
+
if (after !== null && installed !== null && !isNewer(after, installed)) {
|
|
608
|
+
return { kind: "failed", reason: `npm left ${after} installed, which is not newer than ${installed}` };
|
|
609
|
+
}
|
|
610
|
+
} finally {
|
|
611
|
+
releaseLock(lock);
|
|
612
|
+
}
|
|
613
|
+
return { kind: "upgraded", code: reexec(options, env, argv2) };
|
|
614
|
+
}
|
|
615
|
+
function reexec(options, env, argv2) {
|
|
616
|
+
return (options.reexec ?? reexecSelf)({ ...env, [REEXEC_SENTINEL]: "1" }, argv2);
|
|
617
|
+
}
|
|
618
|
+
function describeInstall(kind) {
|
|
619
|
+
switch (kind) {
|
|
620
|
+
case "checkout":
|
|
621
|
+
return "this is a git checkout, not an npm install";
|
|
622
|
+
case "local":
|
|
623
|
+
return "this copy is a project dependency, not a global install";
|
|
624
|
+
case "npx":
|
|
625
|
+
return "npx runs a throwaway copy";
|
|
626
|
+
default:
|
|
627
|
+
return "cannot tell how this CLI was installed";
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function npmInstallGlobal(install, stillHeld) {
|
|
631
|
+
const globalRoot = npmGlobalRoot();
|
|
632
|
+
if (globalRoot === null) return { ok: false, detail: "npm did not say where its global root is" };
|
|
633
|
+
if (globalRoot.includes("***")) {
|
|
634
|
+
return { ok: false, detail: "npm redacted its own global root, so this copy's location cannot be confirmed" };
|
|
635
|
+
}
|
|
636
|
+
if (install.nodeModules === null || !samePath(globalRoot, install.nodeModules)) {
|
|
637
|
+
return {
|
|
638
|
+
ok: false,
|
|
639
|
+
detail: `this copy lives in ${install.nodeModules ?? "an unrecognized place"}, not npm's global root (${globalRoot})`
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
try {
|
|
643
|
+
accessSync2(globalRoot, constants.W_OK);
|
|
644
|
+
} catch {
|
|
645
|
+
return { ok: false, detail: `${globalRoot} is not writable \u2014 upgrade it yourself` };
|
|
646
|
+
}
|
|
647
|
+
if (!stillHeld()) {
|
|
648
|
+
return { ok: false, detail: "lost the upgrade lock while preparing \u2014 another process took over" };
|
|
649
|
+
}
|
|
650
|
+
process.stderr.write(`harness: upgrading ${PACKAGE_NAME} \u2026
|
|
651
|
+
`);
|
|
652
|
+
const args = [
|
|
653
|
+
"install",
|
|
654
|
+
"--global",
|
|
655
|
+
"--ignore-scripts",
|
|
656
|
+
"--no-audit",
|
|
657
|
+
"--no-fund",
|
|
658
|
+
"--no-update-notifier",
|
|
659
|
+
"--loglevel=error",
|
|
660
|
+
`${PACKAGE_NAME}@latest`
|
|
661
|
+
];
|
|
662
|
+
const result = spawnSync(npmCommand(), args, {
|
|
663
|
+
// npm's diagnostics belong on stderr with ours; stdout stays the
|
|
664
|
+
// command's own, so its summary is dropped rather than piped into jq.
|
|
665
|
+
stdio: ["ignore", "ignore", "inherit"],
|
|
666
|
+
timeout: INSTALL_TIMEOUT_MS,
|
|
667
|
+
...npmSpawnOptions()
|
|
668
|
+
});
|
|
669
|
+
if (result.error !== void 0) return { ok: false, detail: result.error.message };
|
|
670
|
+
if (result.status !== 0) {
|
|
671
|
+
return { ok: false, detail: `npm install exited ${result.status ?? `on ${String(result.signal)}`}` };
|
|
672
|
+
}
|
|
673
|
+
return { ok: true, detail: "" };
|
|
674
|
+
}
|
|
675
|
+
function npmGlobalRoot() {
|
|
676
|
+
try {
|
|
677
|
+
const result = spawnSync(npmCommand(), ["root", "--global"], {
|
|
678
|
+
encoding: "utf8",
|
|
679
|
+
timeout: NPM_ROOT_TIMEOUT_MS,
|
|
680
|
+
...npmSpawnOptions()
|
|
681
|
+
});
|
|
682
|
+
if (result.status !== 0 || typeof result.stdout !== "string") return null;
|
|
683
|
+
const path = result.stdout.trim();
|
|
684
|
+
return path === "" ? null : path;
|
|
685
|
+
} catch {
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
function npmCommand() {
|
|
690
|
+
return process.platform === "win32" ? "npm.cmd" : "npm";
|
|
691
|
+
}
|
|
692
|
+
function npmSpawnOptions() {
|
|
693
|
+
return process.platform === "win32" ? { shell: true } : {};
|
|
694
|
+
}
|
|
695
|
+
function samePath(left, right) {
|
|
696
|
+
const resolve = (path) => {
|
|
697
|
+
try {
|
|
698
|
+
return realpathSync(path);
|
|
699
|
+
} catch {
|
|
700
|
+
return path;
|
|
701
|
+
}
|
|
702
|
+
};
|
|
703
|
+
return resolve(left) === resolve(right);
|
|
704
|
+
}
|
|
705
|
+
function reexecSelf(env, argv2) {
|
|
706
|
+
const self = argv2[1];
|
|
707
|
+
if (self === void 0) return 1;
|
|
708
|
+
const result = spawnSync(process.execPath, [self, ...argv2.slice(2)], {
|
|
709
|
+
stdio: "inherit",
|
|
710
|
+
env
|
|
711
|
+
});
|
|
712
|
+
if (result.error !== void 0) {
|
|
713
|
+
process.stderr.write(`harness: upgraded, but re-running failed: ${result.error.message}
|
|
714
|
+
`);
|
|
715
|
+
return 1;
|
|
716
|
+
}
|
|
717
|
+
return result.status ?? 1;
|
|
718
|
+
}
|
|
719
|
+
function takeLock(env) {
|
|
720
|
+
const path = join2(resolveCacheHome(env), "harness", "upgrade.lock");
|
|
721
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
722
|
+
try {
|
|
723
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
724
|
+
writeFileSync(temporary, `${process.pid}
|
|
725
|
+
`);
|
|
726
|
+
} catch {
|
|
727
|
+
return null;
|
|
728
|
+
}
|
|
729
|
+
try {
|
|
730
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
731
|
+
try {
|
|
732
|
+
linkSync(temporary, path);
|
|
733
|
+
return { path, pid: process.pid };
|
|
734
|
+
} catch {
|
|
735
|
+
const holder = readHolder(path);
|
|
736
|
+
if (holder === null) continue;
|
|
737
|
+
const aged = Date.now() - holder.mtimeMs > LOCK_STALE_MS;
|
|
738
|
+
const abandoned = holder.pid === null ? aged : !running(holder.pid) || aged;
|
|
739
|
+
if (!abandoned) return null;
|
|
740
|
+
try {
|
|
741
|
+
rmSync(path, { force: true });
|
|
742
|
+
} catch {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
return null;
|
|
748
|
+
} finally {
|
|
749
|
+
try {
|
|
750
|
+
rmSync(temporary, { force: true });
|
|
751
|
+
} catch {
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function releaseLock(lock) {
|
|
756
|
+
try {
|
|
757
|
+
if (readHolder(lock.path)?.pid !== lock.pid) return;
|
|
758
|
+
rmSync(lock.path, { force: true });
|
|
759
|
+
} catch {
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function readHolder(path) {
|
|
763
|
+
try {
|
|
764
|
+
const mtimeMs = statSync(path).mtimeMs;
|
|
765
|
+
const pid = Number.parseInt(readFileSync(path, "utf8").trim(), 10);
|
|
766
|
+
return { pid: Number.isInteger(pid) && pid > 0 ? pid : null, mtimeMs };
|
|
767
|
+
} catch {
|
|
768
|
+
return null;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
function running(pid) {
|
|
772
|
+
if (pid === null) return false;
|
|
773
|
+
try {
|
|
774
|
+
process.kill(pid, 0);
|
|
775
|
+
return true;
|
|
776
|
+
} catch (error) {
|
|
777
|
+
return error.code === "EPERM";
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
function truthy(value) {
|
|
781
|
+
if (value === void 0) return false;
|
|
782
|
+
const normalized = value.trim().toLowerCase();
|
|
783
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false" && normalized !== "no";
|
|
784
|
+
}
|
|
785
|
+
function checksEnabled(env) {
|
|
786
|
+
return !truthy(env["CI"]) && !truthy(env["HARNESS_NO_UPDATE_CHECK"]);
|
|
787
|
+
}
|
|
788
|
+
function autoUpgradeEnabled(env) {
|
|
789
|
+
if (!checksEnabled(env)) return false;
|
|
790
|
+
const explicit = env["HARNESS_AUTO_UPGRADE"];
|
|
791
|
+
if (explicit !== void 0 && explicit.trim() !== "") return truthy(explicit);
|
|
792
|
+
return true;
|
|
793
|
+
}
|
|
794
|
+
|
|
373
795
|
// src/cli.ts
|
|
374
796
|
function promptLine(question) {
|
|
375
797
|
const readline = createInterface2({ input: process.stdin, output: process.stderr });
|
|
@@ -474,7 +896,10 @@ async function fail(error, hostNotice) {
|
|
|
474
896
|
return exitWith(3);
|
|
475
897
|
}
|
|
476
898
|
if (error instanceof UpgradeRequiredError) {
|
|
477
|
-
|
|
899
|
+
const healed = selfHeal();
|
|
900
|
+
if (healed.kind === "upgraded") return exitWith(healed.code);
|
|
901
|
+
const why = healed.kind === "failed" ? ` (tried to upgrade: ${clean(healed.reason)})` : "";
|
|
902
|
+
process.stderr.write(hostNotice + `harness: ${clean(error.message)} (HTTP 426)${why}
|
|
478
903
|
`);
|
|
479
904
|
return exitWith(1);
|
|
480
905
|
}
|
|
@@ -698,7 +1123,7 @@ program.command("upload").description("upload a file; prints markdown to referen
|
|
|
698
1123
|
});
|
|
699
1124
|
});
|
|
700
1125
|
program.command("mcp").description("run the MCP server over stdio (for agents)").action(async () => {
|
|
701
|
-
const { startMcpServer } = await import("./server-
|
|
1126
|
+
const { startMcpServer } = await import("./server-TK2KFAOQ.js");
|
|
702
1127
|
await startMcpServer();
|
|
703
1128
|
});
|
|
704
1129
|
program.command("login").description("sign in via the browser (infra.one session)").option("--url <url>", "host to sign in to").option("--no-browser", "print the URL and prompt for a pasted token instead").option("--timeout <ms>", "how long to wait for the browser callback", integer).option("--json", "emit JSON").action(async (opts) => {
|
|
@@ -749,6 +1174,15 @@ async function finishAuth(result, json2) {
|
|
|
749
1174
|
}
|
|
750
1175
|
var argv = process.argv.slice();
|
|
751
1176
|
if (argv[2] === "--version" || argv[2] === "-V") argv[2] = "version";
|
|
1177
|
+
if (argv[2] === UPDATE_CHECK_COMMAND) {
|
|
1178
|
+
await performUpdateCheck();
|
|
1179
|
+
await exitWith(0);
|
|
1180
|
+
}
|
|
1181
|
+
try {
|
|
1182
|
+
const notice = updateCheck();
|
|
1183
|
+
if (notice !== null) process.stderr.write(notice);
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
752
1186
|
if (argv.length <= 2) {
|
|
753
1187
|
program.outputHelp({ error: true });
|
|
754
1188
|
await exitWith(2);
|