agent-dealer 0.1.13 → 0.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.
- package/bundle/server/dist/adapters/agent-health.js +28 -1
- package/bundle/server/dist/cli-env.js +16 -0
- package/bundle/server/dist/cli-env.test.js +14 -1
- package/bundle/server/dist/db/index.js +2 -1
- package/bundle/server/dist/queue/result-qa.js +1 -1
- package/bundle/server/dist/runners/claude.js +6 -87
- package/bundle/server/dist/runners/codex-args.js +59 -0
- package/bundle/server/dist/runners/codex-args.test.js +72 -0
- package/bundle/server/dist/runners/codex-jsonl.js +150 -0
- package/bundle/server/dist/runners/codex-jsonl.test.js +36 -0
- package/bundle/server/dist/runners/codex-resume.test.js +19 -0
- package/bundle/server/dist/runners/codex.js +46 -0
- package/bundle/server/dist/runners/models-codex.test.js +14 -0
- package/bundle/server/dist/runners/models.js +53 -1
- package/bundle/server/dist/runners/persist.js +4 -1
- package/bundle/server/dist/runners/qa.js +17 -1
- package/bundle/server/dist/runners/spawn-cli.js +90 -0
- package/bundle/server/package.json +2 -2
- package/bundle/server/static-ui/assets/index-DTcianAH.js +79 -0
- package/bundle/server/static-ui/index.html +1 -1
- package/bundle/shared/dist/agents.d.ts +18 -17
- package/bundle/shared/dist/agents.js +1 -0
- package/bundle/shared/dist/index.d.ts +83 -83
- package/bundle/shared/dist/runtime.d.ts +1 -1
- package/bundle/shared/dist/runtime.js +1 -1
- package/bundle/shared/package.json +1 -1
- package/dist/doctor.js +18 -0
- package/dist/index.js +14 -2
- package/dist/install.d.ts +8 -0
- package/dist/install.js +72 -0
- package/dist/managed/activate.d.ts +2 -0
- package/dist/managed/activate.js +49 -0
- package/dist/managed/activate.test.d.ts +1 -0
- package/dist/managed/activate.test.js +34 -0
- package/dist/managed/index.d.ts +8 -0
- package/dist/managed/index.js +8 -0
- package/dist/managed/install-kind.d.ts +5 -0
- package/dist/managed/install-kind.js +16 -0
- package/dist/managed/launcher.d.ts +1 -0
- package/dist/managed/launcher.js +19 -0
- package/dist/managed/npm-prefix-install.d.ts +16 -0
- package/dist/managed/npm-prefix-install.js +38 -0
- package/dist/managed/paths.d.ts +11 -0
- package/dist/managed/paths.js +45 -0
- package/dist/managed/semver.d.ts +2 -0
- package/dist/managed/semver.js +12 -0
- package/dist/managed/update-state.d.ts +7 -0
- package/dist/managed/update-state.js +22 -0
- package/dist/managed/updater.d.ts +21 -0
- package/dist/managed/updater.js +107 -0
- package/dist/update-check.d.ts +1 -0
- package/dist/update-check.js +25 -37
- package/dist/upgrade.d.ts +1 -0
- package/dist/upgrade.js +55 -6
- package/package.json +1 -1
- package/bundle/server/static-ui/assets/index-B_7S7xJb.js +0 -79
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
export const Runtime = z.enum(["claude_code", "cursor_local"]);
|
|
2
|
+
export const Runtime = z.enum(["claude_code", "cursor_local", "codex_local"]);
|
package/dist/doctor.js
CHANGED
|
@@ -4,6 +4,8 @@ import { claudeAvailable } from "./cli-check.js";
|
|
|
4
4
|
import { loadProdEnvFile, prodEnvFilePath, prodHomeDir, resolveBundledListenPort, shortenHome, } from "./env.js";
|
|
5
5
|
import { probeAgentDealer } from "./ports.js";
|
|
6
6
|
import { resolveServerEntry, resolveUiDist } from "./paths.js";
|
|
7
|
+
import { detectInstallKind, localBinLauncherPath, readCurrentManagedVersion, readUpdateState, resolveCurrentVersionDir, } from "./managed/index.js";
|
|
8
|
+
import { getVersion } from "./version.js";
|
|
7
9
|
export async function runDoctor() {
|
|
8
10
|
let failed = false;
|
|
9
11
|
const major = Number(process.versions.node.split(".")[0]);
|
|
@@ -53,6 +55,22 @@ export async function runDoctor() {
|
|
|
53
55
|
if (fs.existsSync(home)) {
|
|
54
56
|
console.log(`✓ data ${shortenHome(home)}`);
|
|
55
57
|
}
|
|
58
|
+
console.log(`Package version ${getVersion()}`);
|
|
59
|
+
const kind = detectInstallKind();
|
|
60
|
+
console.log(`Install kind: ${kind}`);
|
|
61
|
+
if (kind === "managed") {
|
|
62
|
+
const current = readCurrentManagedVersion();
|
|
63
|
+
const currentDir = resolveCurrentVersionDir();
|
|
64
|
+
console.log(`Managed current: ${current ?? "(unknown)"} (${currentDir ?? "missing"})`);
|
|
65
|
+
console.log(`Launcher: ${localBinLauncherPath()}`);
|
|
66
|
+
const pending = readUpdateState()?.pendingVersion;
|
|
67
|
+
if (pending) {
|
|
68
|
+
console.log(`Pending managed version: ${pending} (activates on next start/doctor/upgrade)`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
console.log("Tip: agent-dealer install # managed CLI + auto-updates (data unchanged)");
|
|
73
|
+
}
|
|
56
74
|
const port = resolveBundledListenPort();
|
|
57
75
|
const probe = await probeAgentDealer("127.0.0.1", port);
|
|
58
76
|
if (probe.up) {
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,7 @@ import { runStop } from "./stop.js";
|
|
|
6
6
|
import { maybeCheckForUpdateOnRun } from "./update-check.js";
|
|
7
7
|
import { getVersion } from "./version.js";
|
|
8
8
|
import { runUpgrade, printUpgradeHelp } from "./upgrade.js";
|
|
9
|
+
import { runInstall, printInstallHelp } from "./install.js";
|
|
9
10
|
function printUsage() {
|
|
10
11
|
console.log(`Usage:
|
|
11
12
|
agent-dealer setup [--home DIR] [--force]
|
|
@@ -13,7 +14,8 @@ function printUsage() {
|
|
|
13
14
|
agent-dealer stop
|
|
14
15
|
agent-dealer status
|
|
15
16
|
agent-dealer doctor
|
|
16
|
-
agent-dealer
|
|
17
|
+
agent-dealer install [--to VERSION] [--migrate-cli] [--purge-global]
|
|
18
|
+
agent-dealer upgrade [--to VERSION] [--yes] [--check]
|
|
17
19
|
agent-dealer --version
|
|
18
20
|
|
|
19
21
|
Human control plane for agent execution.`);
|
|
@@ -92,6 +94,13 @@ export async function runCli(argv) {
|
|
|
92
94
|
return 0;
|
|
93
95
|
return runDoctor();
|
|
94
96
|
}
|
|
97
|
+
if (cmd === "install") {
|
|
98
|
+
if (args.includes("--help")) {
|
|
99
|
+
printInstallHelp();
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
return runInstall(args.slice(1));
|
|
103
|
+
}
|
|
95
104
|
if (cmd === "upgrade") {
|
|
96
105
|
if (args.includes("--help")) {
|
|
97
106
|
printUpgradeHelp();
|
|
@@ -99,17 +108,20 @@ export async function runCli(argv) {
|
|
|
99
108
|
}
|
|
100
109
|
let toVersion;
|
|
101
110
|
let yes = false;
|
|
111
|
+
let check = false;
|
|
102
112
|
for (let i = 1; i < args.length; i += 1) {
|
|
103
113
|
if (args[i] === "--to")
|
|
104
114
|
toVersion = args[++i];
|
|
105
115
|
else if (args[i] === "--yes")
|
|
106
116
|
yes = true;
|
|
117
|
+
else if (args[i] === "--check")
|
|
118
|
+
check = true;
|
|
107
119
|
else {
|
|
108
120
|
console.error(`Unknown upgrade option: ${args[i]}`);
|
|
109
121
|
return 1;
|
|
110
122
|
}
|
|
111
123
|
}
|
|
112
|
-
return runUpgrade({ toVersion, yes });
|
|
124
|
+
return runUpgrade({ toVersion, yes, check });
|
|
113
125
|
}
|
|
114
126
|
console.error(`Unknown command: ${cmd}`);
|
|
115
127
|
printUsage();
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { installCliVersionToPrefix } from "./managed/index.js";
|
|
2
|
+
export type InstallDeps = {
|
|
3
|
+
installVersion?: typeof installCliVersionToPrefix;
|
|
4
|
+
fetchLatest?: () => Promise<string | null>;
|
|
5
|
+
purgeGlobal?: () => Promise<number>;
|
|
6
|
+
};
|
|
7
|
+
export declare function printInstallHelp(): void;
|
|
8
|
+
export declare function runInstall(args: string[], deps?: InstallDeps): Promise<number>;
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { activateVersion, cliEntryInVersionDir, fetchLatestVersion, installCliVersionToPrefix, localBinLauncherPath, PACKAGE_NAME, versionDir, } from "./managed/index.js";
|
|
6
|
+
function parseArgs(args) {
|
|
7
|
+
let to;
|
|
8
|
+
let migrateCli = false;
|
|
9
|
+
let purgeGlobal = false;
|
|
10
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
11
|
+
const a = args[i];
|
|
12
|
+
if (a === "--to")
|
|
13
|
+
to = args[++i];
|
|
14
|
+
else if (a === "--migrate-cli")
|
|
15
|
+
migrateCli = true;
|
|
16
|
+
else if (a === "--purge-global")
|
|
17
|
+
purgeGlobal = true;
|
|
18
|
+
}
|
|
19
|
+
return { to, migrateCli, purgeGlobal };
|
|
20
|
+
}
|
|
21
|
+
export function printInstallHelp() {
|
|
22
|
+
console.log(`Usage:
|
|
23
|
+
agent-dealer install [--to VERSION] [--migrate-cli] [--purge-global]
|
|
24
|
+
|
|
25
|
+
Installs agent-dealer into ~/.agent-dealer/versions and writes ~/.local/bin/agent-dealer.
|
|
26
|
+
Existing config, queue data, and logs are left unchanged.`);
|
|
27
|
+
}
|
|
28
|
+
export async function runInstall(args, deps = {}) {
|
|
29
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
30
|
+
printInstallHelp();
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
const { to, migrateCli, purgeGlobal } = parseArgs(args);
|
|
34
|
+
const fetchLatest = deps.fetchLatest ?? fetchLatestVersion;
|
|
35
|
+
const installVersion = deps.installVersion ?? installCliVersionToPrefix;
|
|
36
|
+
const version = to ?? (await fetchLatest());
|
|
37
|
+
if (!version) {
|
|
38
|
+
console.error("Could not resolve latest version from npm.");
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (!fs.existsSync(cliEntryInVersionDir(versionDir(version)))) {
|
|
42
|
+
const result = await installVersion(version);
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
console.error(`Managed install failed: ${result.error}`);
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
activateVersion(version);
|
|
49
|
+
const home = process.env.AGENT_DEALER_HOME?.trim() || path.join(os.homedir(), ".agent-dealer");
|
|
50
|
+
console.log(`Installed ${PACKAGE_NAME}@${version} to ${home} (data home unchanged).`);
|
|
51
|
+
console.log(`Launcher: ${localBinLauncherPath()}`);
|
|
52
|
+
console.log('If command not found: export PATH="$HOME/.local/bin:$PATH"');
|
|
53
|
+
if (migrateCli) {
|
|
54
|
+
console.log("Prefer ~/.local/bin ahead of any npm global agent-dealer on PATH.");
|
|
55
|
+
}
|
|
56
|
+
console.log("Next: agent-dealer doctor && agent-dealer start --daemon --open");
|
|
57
|
+
if (purgeGlobal) {
|
|
58
|
+
const code = deps.purgeGlobal
|
|
59
|
+
? await deps.purgeGlobal()
|
|
60
|
+
: await new Promise((resolve) => {
|
|
61
|
+
const child = spawn("npm", ["uninstall", "-g", PACKAGE_NAME], {
|
|
62
|
+
stdio: "inherit",
|
|
63
|
+
shell: process.platform === "win32",
|
|
64
|
+
});
|
|
65
|
+
child.on("exit", (c) => resolve(c ?? 1));
|
|
66
|
+
});
|
|
67
|
+
if (code !== 0) {
|
|
68
|
+
console.warn("Warning: could not uninstall npm global package (managed install is still active).");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return 0;
|
|
72
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { writeLocalBinLauncher } from "./launcher.js";
|
|
4
|
+
import { cliEntryInVersionDir, currentLinkPath, resolveCurrentVersionDir, versionDir, versionsDir, } from "./paths.js";
|
|
5
|
+
import { compareSemver } from "./semver.js";
|
|
6
|
+
export function activateVersion(version) {
|
|
7
|
+
const target = versionDir(version);
|
|
8
|
+
const entry = cliEntryInVersionDir(target);
|
|
9
|
+
if (!fs.existsSync(entry)) {
|
|
10
|
+
throw new Error(`Cannot activate ${version}: missing ${entry}`);
|
|
11
|
+
}
|
|
12
|
+
const link = currentLinkPath();
|
|
13
|
+
fs.mkdirSync(path.dirname(link), { recursive: true });
|
|
14
|
+
try {
|
|
15
|
+
const st = fs.lstatSync(link);
|
|
16
|
+
if (st.isSymbolicLink() || st.isFile()) {
|
|
17
|
+
fs.unlinkSync(link);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
fs.rmSync(link, { recursive: true, force: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// missing
|
|
25
|
+
}
|
|
26
|
+
fs.symlinkSync(target, link, "dir");
|
|
27
|
+
writeLocalBinLauncher();
|
|
28
|
+
pruneOldVersions(3);
|
|
29
|
+
}
|
|
30
|
+
export function pruneOldVersions(keep) {
|
|
31
|
+
const root = versionsDir();
|
|
32
|
+
if (!fs.existsSync(root))
|
|
33
|
+
return;
|
|
34
|
+
const currentReal = resolveCurrentVersionDir();
|
|
35
|
+
const names = fs
|
|
36
|
+
.readdirSync(root, { withFileTypes: true })
|
|
37
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith("."))
|
|
38
|
+
.map((d) => d.name)
|
|
39
|
+
.sort((a, b) => compareSemver(b, a));
|
|
40
|
+
const retained = names.slice(0, Math.max(keep, 0));
|
|
41
|
+
for (const name of names) {
|
|
42
|
+
const full = path.join(root, name);
|
|
43
|
+
if (retained.includes(name))
|
|
44
|
+
continue;
|
|
45
|
+
if (currentReal && fs.existsSync(full) && fs.realpathSync(full) === currentReal)
|
|
46
|
+
continue;
|
|
47
|
+
fs.rmSync(full, { recursive: true, force: true });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
import { activateVersion } from "./activate.js";
|
|
7
|
+
import { detectInstallKind } from "./install-kind.js";
|
|
8
|
+
import { currentLinkPath, localBinLauncherPath, versionDir } from "./paths.js";
|
|
9
|
+
function seedVersion(ver) {
|
|
10
|
+
const dir = versionDir(ver);
|
|
11
|
+
const bin = path.join(dir, "node_modules", "agent-dealer", "dist", "bin.js");
|
|
12
|
+
fs.mkdirSync(path.dirname(bin), { recursive: true });
|
|
13
|
+
fs.writeFileSync(bin, "ok\n");
|
|
14
|
+
}
|
|
15
|
+
test("managed activate preserves sibling data and writes launcher", () => {
|
|
16
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "adlr-act-"));
|
|
17
|
+
const localBin = path.join(tmp, "local-bin");
|
|
18
|
+
process.env.AGENT_DEALER_HOME = tmp;
|
|
19
|
+
process.env.AGENT_DEALER_LOCAL_BIN = localBin;
|
|
20
|
+
try {
|
|
21
|
+
fs.writeFileSync(path.join(tmp, ".env"), "KEEP=1\n");
|
|
22
|
+
seedVersion("0.1.0");
|
|
23
|
+
activateVersion("0.1.0");
|
|
24
|
+
assert.equal(fs.realpathSync(currentLinkPath()), fs.realpathSync(versionDir("0.1.0")));
|
|
25
|
+
assert.equal(fs.readFileSync(path.join(tmp, ".env"), "utf8"), "KEEP=1\n");
|
|
26
|
+
assert.ok(fs.existsSync(localBinLauncherPath()));
|
|
27
|
+
assert.equal(detectInstallKind(), "managed");
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
delete process.env.AGENT_DEALER_HOME;
|
|
31
|
+
delete process.env.AGENT_DEALER_LOCAL_BIN;
|
|
32
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { agentDealerHome, cliEntryInVersionDir, currentLinkPath, localBinDir, localBinLauncherPath, partialVersionDir, resolveCurrentVersionDir, updateStatePath, versionDir, versionsDir, } from "./paths.js";
|
|
2
|
+
export { detectInstallKind, type InstallKind } from "./install-kind.js";
|
|
3
|
+
export { activateVersion, pruneOldVersions } from "./activate.js";
|
|
4
|
+
export { writeLocalBinLauncher } from "./launcher.js";
|
|
5
|
+
export { installCliVersionToPrefix, PACKAGE_NAME } from "./npm-prefix-install.js";
|
|
6
|
+
export { compareSemver } from "./semver.js";
|
|
7
|
+
export { readUpdateState, writeUpdateState, type UpdateState } from "./update-state.js";
|
|
8
|
+
export { ensurePendingDownload, fetchLatestVersion, isAutoupdaterDisabled, maybeActivatePendingVersion, readCurrentManagedVersion, runManagedCliEntryHooks, scheduleBackgroundUpdateCheck, } from "./updater.js";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { agentDealerHome, cliEntryInVersionDir, currentLinkPath, localBinDir, localBinLauncherPath, partialVersionDir, resolveCurrentVersionDir, updateStatePath, versionDir, versionsDir, } from "./paths.js";
|
|
2
|
+
export { detectInstallKind } from "./install-kind.js";
|
|
3
|
+
export { activateVersion, pruneOldVersions } from "./activate.js";
|
|
4
|
+
export { writeLocalBinLauncher } from "./launcher.js";
|
|
5
|
+
export { installCliVersionToPrefix, PACKAGE_NAME } from "./npm-prefix-install.js";
|
|
6
|
+
export { compareSemver } from "./semver.js";
|
|
7
|
+
export { readUpdateState, writeUpdateState } from "./update-state.js";
|
|
8
|
+
export { ensurePendingDownload, fetchLatestVersion, isAutoupdaterDisabled, maybeActivatePendingVersion, readCurrentManagedVersion, runManagedCliEntryHooks, scheduleBackgroundUpdateCheck, } from "./updater.js";
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { resolveCurrentVersionDir } from "./paths.js";
|
|
2
|
+
export function detectInstallKind(options) {
|
|
3
|
+
if (resolveCurrentVersionDir())
|
|
4
|
+
return "managed";
|
|
5
|
+
const whichPath = options?.whichPath;
|
|
6
|
+
const prefix = options?.npmGlobalPrefix;
|
|
7
|
+
if (whichPath && prefix) {
|
|
8
|
+
const normalizedWhich = whichPath.replace(/\\/g, "/");
|
|
9
|
+
const normalizedPrefix = prefix.replace(/\\/g, "/").replace(/\/$/, "");
|
|
10
|
+
if (normalizedWhich === normalizedPrefix ||
|
|
11
|
+
normalizedWhich.startsWith(`${normalizedPrefix}/`)) {
|
|
12
|
+
return "npm-global";
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return "unknown";
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function writeLocalBinLauncher(): void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { localBinDir, localBinLauncherPath } from "./paths.js";
|
|
3
|
+
const LAUNCHER_BODY = `#!/usr/bin/env bash
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
HOME_DIR="\${AGENT_DEALER_HOME:-$HOME/.agent-dealer}"
|
|
6
|
+
CURRENT="$HOME_DIR/current"
|
|
7
|
+
BIN="$CURRENT/node_modules/agent-dealer/dist/bin.js"
|
|
8
|
+
if [ ! -f "$BIN" ]; then
|
|
9
|
+
echo "agent-dealer: managed install broken (missing $BIN). Re-run: agent-dealer install" >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
exec node "$BIN" "$@"
|
|
13
|
+
`;
|
|
14
|
+
export function writeLocalBinLauncher() {
|
|
15
|
+
fs.mkdirSync(localBinDir(), { recursive: true });
|
|
16
|
+
const target = localBinLauncherPath();
|
|
17
|
+
fs.writeFileSync(target, LAUNCHER_BODY, { mode: 0o755 });
|
|
18
|
+
fs.chmodSync(target, 0o755);
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME = "agent-dealer";
|
|
2
|
+
export type NpmSpawn = (args: string[], options: {
|
|
3
|
+
cwd?: string;
|
|
4
|
+
}) => Promise<{
|
|
5
|
+
code: number;
|
|
6
|
+
stderr: string;
|
|
7
|
+
}>;
|
|
8
|
+
export declare function installCliVersionToPrefix(version: string, options?: {
|
|
9
|
+
npmSpawn?: NpmSpawn;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
ok: true;
|
|
12
|
+
dir: string;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
error: string;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { cliEntryInVersionDir, partialVersionDir, versionDir, versionsDir } from "./paths.js";
|
|
4
|
+
export const PACKAGE_NAME = "agent-dealer";
|
|
5
|
+
const defaultNpmSpawn = (args, options) => new Promise((resolve) => {
|
|
6
|
+
const child = spawn("npm", args, {
|
|
7
|
+
cwd: options.cwd,
|
|
8
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
9
|
+
shell: process.platform === "win32",
|
|
10
|
+
});
|
|
11
|
+
let stderr = "";
|
|
12
|
+
child.stderr?.on("data", (chunk) => {
|
|
13
|
+
stderr += chunk.toString();
|
|
14
|
+
});
|
|
15
|
+
child.on("error", (err) => resolve({ code: 1, stderr: err.message }));
|
|
16
|
+
child.on("exit", (code) => resolve({ code: code ?? 1, stderr }));
|
|
17
|
+
});
|
|
18
|
+
export async function installCliVersionToPrefix(version, options = {}) {
|
|
19
|
+
const npmSpawn = options.npmSpawn ?? defaultNpmSpawn;
|
|
20
|
+
const partial = partialVersionDir(version);
|
|
21
|
+
const finalDir = versionDir(version);
|
|
22
|
+
fs.mkdirSync(versionsDir(), { recursive: true });
|
|
23
|
+
fs.rmSync(partial, { recursive: true, force: true });
|
|
24
|
+
fs.mkdirSync(partial, { recursive: true });
|
|
25
|
+
const result = await npmSpawn(["install", "--prefix", partial, `${PACKAGE_NAME}@${version}`], {});
|
|
26
|
+
if (result.code !== 0) {
|
|
27
|
+
fs.rmSync(partial, { recursive: true, force: true });
|
|
28
|
+
return { ok: false, error: result.stderr.trim() || `npm install failed (exit ${result.code})` };
|
|
29
|
+
}
|
|
30
|
+
const entry = cliEntryInVersionDir(partial);
|
|
31
|
+
if (!fs.existsSync(entry)) {
|
|
32
|
+
fs.rmSync(partial, { recursive: true, force: true });
|
|
33
|
+
return { ok: false, error: `Install succeeded but missing CLI entry: ${entry}` };
|
|
34
|
+
}
|
|
35
|
+
fs.rmSync(finalDir, { recursive: true, force: true });
|
|
36
|
+
fs.renameSync(partial, finalDir);
|
|
37
|
+
return { ok: true, dir: finalDir };
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function agentDealerHome(): string;
|
|
2
|
+
export declare function versionsDir(): string;
|
|
3
|
+
export declare function versionDir(version: string): string;
|
|
4
|
+
export declare function partialVersionDir(version: string): string;
|
|
5
|
+
export declare function currentLinkPath(): string;
|
|
6
|
+
export declare function updateStatePath(): string;
|
|
7
|
+
/** Override with AGENT_DEALER_LOCAL_BIN for tests. */
|
|
8
|
+
export declare function localBinDir(): string;
|
|
9
|
+
export declare function localBinLauncherPath(): string;
|
|
10
|
+
export declare function cliEntryInVersionDir(dir: string): string;
|
|
11
|
+
export declare function resolveCurrentVersionDir(): string | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
export function agentDealerHome() {
|
|
5
|
+
return process.env.AGENT_DEALER_HOME?.trim() || path.join(os.homedir(), ".agent-dealer");
|
|
6
|
+
}
|
|
7
|
+
export function versionsDir() {
|
|
8
|
+
return path.join(agentDealerHome(), "versions");
|
|
9
|
+
}
|
|
10
|
+
export function versionDir(version) {
|
|
11
|
+
return path.join(versionsDir(), version);
|
|
12
|
+
}
|
|
13
|
+
export function partialVersionDir(version) {
|
|
14
|
+
return path.join(versionsDir(), `.partial-${version}`);
|
|
15
|
+
}
|
|
16
|
+
export function currentLinkPath() {
|
|
17
|
+
return path.join(agentDealerHome(), "current");
|
|
18
|
+
}
|
|
19
|
+
export function updateStatePath() {
|
|
20
|
+
return path.join(agentDealerHome(), "update-state.json");
|
|
21
|
+
}
|
|
22
|
+
/** Override with AGENT_DEALER_LOCAL_BIN for tests. */
|
|
23
|
+
export function localBinDir() {
|
|
24
|
+
const override = process.env.AGENT_DEALER_LOCAL_BIN?.trim();
|
|
25
|
+
if (override)
|
|
26
|
+
return override;
|
|
27
|
+
return path.join(os.homedir(), ".local", "bin");
|
|
28
|
+
}
|
|
29
|
+
export function localBinLauncherPath() {
|
|
30
|
+
return path.join(localBinDir(), "agent-dealer");
|
|
31
|
+
}
|
|
32
|
+
export function cliEntryInVersionDir(dir) {
|
|
33
|
+
return path.join(dir, "node_modules", "agent-dealer", "dist", "bin.js");
|
|
34
|
+
}
|
|
35
|
+
export function resolveCurrentVersionDir() {
|
|
36
|
+
const link = currentLinkPath();
|
|
37
|
+
try {
|
|
38
|
+
if (!fs.existsSync(link))
|
|
39
|
+
return null;
|
|
40
|
+
return fs.realpathSync(link);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Semver compare: positive if a > b. */
|
|
2
|
+
export function compareSemver(a, b) {
|
|
3
|
+
const parse = (value) => value.replace(/^v/, "").split(".").map((part) => Number.parseInt(part, 10) || 0);
|
|
4
|
+
const left = parse(a);
|
|
5
|
+
const right = parse(b);
|
|
6
|
+
for (let i = 0; i < Math.max(left.length, right.length); i += 1) {
|
|
7
|
+
const diff = (left[i] ?? 0) - (right[i] ?? 0);
|
|
8
|
+
if (diff !== 0)
|
|
9
|
+
return diff > 0 ? 1 : -1;
|
|
10
|
+
}
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { updateStatePath } from "./paths.js";
|
|
4
|
+
export function readUpdateState() {
|
|
5
|
+
try {
|
|
6
|
+
const raw = fs.readFileSync(updateStatePath(), "utf8");
|
|
7
|
+
const parsed = JSON.parse(raw);
|
|
8
|
+
return {
|
|
9
|
+
checkedAt: parsed.checkedAt,
|
|
10
|
+
latest: parsed.latest ?? null,
|
|
11
|
+
pendingVersion: parsed.pendingVersion ?? null,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function writeUpdateState(state) {
|
|
19
|
+
const file = updateStatePath();
|
|
20
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
21
|
+
fs.writeFileSync(file, `${JSON.stringify(state, null, 2)}\n`, "utf8");
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { installCliVersionToPrefix } from "./npm-prefix-install.js";
|
|
2
|
+
export declare function isAutoupdaterDisabled(): boolean;
|
|
3
|
+
export declare function fetchLatestVersion(): Promise<string | null>;
|
|
4
|
+
export declare function readCurrentManagedVersion(): string | null;
|
|
5
|
+
export declare function maybeActivatePendingVersion(): {
|
|
6
|
+
activated: string | null;
|
|
7
|
+
};
|
|
8
|
+
export declare function ensurePendingDownload(latest: string, options?: {
|
|
9
|
+
installVersion?: typeof installCliVersionToPrefix;
|
|
10
|
+
}): Promise<void>;
|
|
11
|
+
export declare function scheduleBackgroundUpdateCheck(options?: {
|
|
12
|
+
fetchLatest?: () => Promise<string | null>;
|
|
13
|
+
installVersion?: typeof installCliVersionToPrefix;
|
|
14
|
+
}): void;
|
|
15
|
+
export declare function runManagedCliEntryHooks(options: {
|
|
16
|
+
allowActivate: boolean;
|
|
17
|
+
fetchLatest?: () => Promise<string | null>;
|
|
18
|
+
installVersion?: typeof installCliVersionToPrefix;
|
|
19
|
+
}): {
|
|
20
|
+
activated: string | null;
|
|
21
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { activateVersion } from "./activate.js";
|
|
4
|
+
import { detectInstallKind } from "./install-kind.js";
|
|
5
|
+
import { installCliVersionToPrefix } from "./npm-prefix-install.js";
|
|
6
|
+
import { cliEntryInVersionDir, resolveCurrentVersionDir, versionDir } from "./paths.js";
|
|
7
|
+
import { compareSemver } from "./semver.js";
|
|
8
|
+
import { readUpdateState, writeUpdateState } from "./update-state.js";
|
|
9
|
+
import { fetchLatestPublishedVersion } from "../npm-registry.js";
|
|
10
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
11
|
+
const PACKAGE_NAME = "agent-dealer";
|
|
12
|
+
export function isAutoupdaterDisabled() {
|
|
13
|
+
const v = process.env.AGENT_DEALER_DISABLE_AUTOUPDATER?.trim().toLowerCase();
|
|
14
|
+
return v === "1" || v === "true";
|
|
15
|
+
}
|
|
16
|
+
export async function fetchLatestVersion() {
|
|
17
|
+
const v = await fetchLatestPublishedVersion(PACKAGE_NAME);
|
|
18
|
+
return v ?? null;
|
|
19
|
+
}
|
|
20
|
+
export function readCurrentManagedVersion() {
|
|
21
|
+
const real = resolveCurrentVersionDir();
|
|
22
|
+
if (!real)
|
|
23
|
+
return null;
|
|
24
|
+
return path.basename(real);
|
|
25
|
+
}
|
|
26
|
+
export function maybeActivatePendingVersion() {
|
|
27
|
+
if (isAutoupdaterDisabled())
|
|
28
|
+
return { activated: null };
|
|
29
|
+
if (detectInstallKind() !== "managed")
|
|
30
|
+
return { activated: null };
|
|
31
|
+
const state = readUpdateState();
|
|
32
|
+
const pending = state?.pendingVersion;
|
|
33
|
+
if (!pending)
|
|
34
|
+
return { activated: null };
|
|
35
|
+
const dir = versionDir(pending);
|
|
36
|
+
if (!fs.existsSync(cliEntryInVersionDir(dir)))
|
|
37
|
+
return { activated: null };
|
|
38
|
+
activateVersion(pending);
|
|
39
|
+
writeUpdateState({
|
|
40
|
+
checkedAt: state?.checkedAt ?? new Date().toISOString(),
|
|
41
|
+
latest: state?.latest ?? pending,
|
|
42
|
+
pendingVersion: null,
|
|
43
|
+
});
|
|
44
|
+
return { activated: pending };
|
|
45
|
+
}
|
|
46
|
+
export async function ensurePendingDownload(latest, options = {}) {
|
|
47
|
+
if (isAutoupdaterDisabled())
|
|
48
|
+
return;
|
|
49
|
+
const dir = versionDir(latest);
|
|
50
|
+
if (fs.existsSync(cliEntryInVersionDir(dir))) {
|
|
51
|
+
const prev = readUpdateState();
|
|
52
|
+
writeUpdateState({
|
|
53
|
+
checkedAt: prev?.checkedAt ?? new Date().toISOString(),
|
|
54
|
+
latest,
|
|
55
|
+
pendingVersion: latest,
|
|
56
|
+
});
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
const install = options.installVersion ?? installCliVersionToPrefix;
|
|
60
|
+
const result = await install(latest);
|
|
61
|
+
if (!result.ok)
|
|
62
|
+
return;
|
|
63
|
+
const prev = readUpdateState();
|
|
64
|
+
writeUpdateState({
|
|
65
|
+
checkedAt: prev?.checkedAt ?? new Date().toISOString(),
|
|
66
|
+
latest,
|
|
67
|
+
pendingVersion: latest,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
export function scheduleBackgroundUpdateCheck(options = {}) {
|
|
71
|
+
if (isAutoupdaterDisabled() || detectInstallKind() !== "managed")
|
|
72
|
+
return;
|
|
73
|
+
const state = readUpdateState();
|
|
74
|
+
if (state?.checkedAt) {
|
|
75
|
+
const age = Date.now() - Date.parse(state.checkedAt);
|
|
76
|
+
if (Number.isFinite(age) && age >= 0 && age < CACHE_TTL_MS)
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const fetchLatest = options.fetchLatest ?? fetchLatestVersion;
|
|
80
|
+
void (async () => {
|
|
81
|
+
try {
|
|
82
|
+
const latest = await fetchLatest();
|
|
83
|
+
writeUpdateState({
|
|
84
|
+
checkedAt: new Date().toISOString(),
|
|
85
|
+
latest,
|
|
86
|
+
pendingVersion: readUpdateState()?.pendingVersion ?? null,
|
|
87
|
+
});
|
|
88
|
+
if (!latest)
|
|
89
|
+
return;
|
|
90
|
+
const current = readCurrentManagedVersion();
|
|
91
|
+
if (current && compareSemver(latest, current) <= 0)
|
|
92
|
+
return;
|
|
93
|
+
await ensurePendingDownload(latest, { installVersion: options.installVersion });
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// background only
|
|
97
|
+
}
|
|
98
|
+
})();
|
|
99
|
+
}
|
|
100
|
+
export function runManagedCliEntryHooks(options) {
|
|
101
|
+
const activated = options.allowActivate ? maybeActivatePendingVersion().activated : null;
|
|
102
|
+
scheduleBackgroundUpdateCheck({
|
|
103
|
+
fetchLatest: options.fetchLatest,
|
|
104
|
+
installVersion: options.installVersion,
|
|
105
|
+
});
|
|
106
|
+
return { activated };
|
|
107
|
+
}
|
package/dist/update-check.d.ts
CHANGED