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.
Files changed (56) hide show
  1. package/bundle/server/dist/adapters/agent-health.js +28 -1
  2. package/bundle/server/dist/cli-env.js +16 -0
  3. package/bundle/server/dist/cli-env.test.js +14 -1
  4. package/bundle/server/dist/db/index.js +2 -1
  5. package/bundle/server/dist/queue/result-qa.js +1 -1
  6. package/bundle/server/dist/runners/claude.js +6 -87
  7. package/bundle/server/dist/runners/codex-args.js +59 -0
  8. package/bundle/server/dist/runners/codex-args.test.js +72 -0
  9. package/bundle/server/dist/runners/codex-jsonl.js +150 -0
  10. package/bundle/server/dist/runners/codex-jsonl.test.js +36 -0
  11. package/bundle/server/dist/runners/codex-resume.test.js +19 -0
  12. package/bundle/server/dist/runners/codex.js +46 -0
  13. package/bundle/server/dist/runners/models-codex.test.js +14 -0
  14. package/bundle/server/dist/runners/models.js +53 -1
  15. package/bundle/server/dist/runners/persist.js +4 -1
  16. package/bundle/server/dist/runners/qa.js +17 -1
  17. package/bundle/server/dist/runners/spawn-cli.js +90 -0
  18. package/bundle/server/package.json +2 -2
  19. package/bundle/server/static-ui/assets/index-DTcianAH.js +79 -0
  20. package/bundle/server/static-ui/index.html +1 -1
  21. package/bundle/shared/dist/agents.d.ts +18 -17
  22. package/bundle/shared/dist/agents.js +1 -0
  23. package/bundle/shared/dist/index.d.ts +83 -83
  24. package/bundle/shared/dist/runtime.d.ts +1 -1
  25. package/bundle/shared/dist/runtime.js +1 -1
  26. package/bundle/shared/package.json +1 -1
  27. package/dist/doctor.js +18 -0
  28. package/dist/index.js +14 -2
  29. package/dist/install.d.ts +8 -0
  30. package/dist/install.js +72 -0
  31. package/dist/managed/activate.d.ts +2 -0
  32. package/dist/managed/activate.js +49 -0
  33. package/dist/managed/activate.test.d.ts +1 -0
  34. package/dist/managed/activate.test.js +34 -0
  35. package/dist/managed/index.d.ts +8 -0
  36. package/dist/managed/index.js +8 -0
  37. package/dist/managed/install-kind.d.ts +5 -0
  38. package/dist/managed/install-kind.js +16 -0
  39. package/dist/managed/launcher.d.ts +1 -0
  40. package/dist/managed/launcher.js +19 -0
  41. package/dist/managed/npm-prefix-install.d.ts +16 -0
  42. package/dist/managed/npm-prefix-install.js +38 -0
  43. package/dist/managed/paths.d.ts +11 -0
  44. package/dist/managed/paths.js +45 -0
  45. package/dist/managed/semver.d.ts +2 -0
  46. package/dist/managed/semver.js +12 -0
  47. package/dist/managed/update-state.d.ts +7 -0
  48. package/dist/managed/update-state.js +22 -0
  49. package/dist/managed/updater.d.ts +21 -0
  50. package/dist/managed/updater.js +107 -0
  51. package/dist/update-check.d.ts +1 -0
  52. package/dist/update-check.js +25 -37
  53. package/dist/upgrade.d.ts +1 -0
  54. package/dist/upgrade.js +55 -6
  55. package/package.json +1 -1
  56. package/bundle/server/static-ui/assets/index-B_7S7xJb.js +0 -79
@@ -1,3 +1,3 @@
1
1
  import { z } from "zod";
2
- export declare const Runtime: z.ZodEnum<["claude_code", "cursor_local"]>;
2
+ export declare const Runtime: z.ZodEnum<["claude_code", "cursor_local", "codex_local"]>;
3
3
  export type Runtime = z.infer<typeof Runtime>;
@@ -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"]);
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-dealer/shared",
3
- "version": "0.1.13",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
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 upgrade [--to VERSION] [--yes]
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>;
@@ -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,2 @@
1
+ export declare function activateVersion(version: string): void;
2
+ export declare function pruneOldVersions(keep: number): void;
@@ -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,5 @@
1
+ export type InstallKind = "managed" | "npm-global" | "unknown";
2
+ export declare function detectInstallKind(options?: {
3
+ whichPath?: string;
4
+ npmGlobalPrefix?: string;
5
+ }): InstallKind;
@@ -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,2 @@
1
+ /** Semver compare: positive if a > b. */
2
+ export declare function compareSemver(a: string, b: string): number;
@@ -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,7 @@
1
+ export interface UpdateState {
2
+ checkedAt: string;
3
+ latest: string | null;
4
+ pendingVersion: string | null;
5
+ }
6
+ export declare function readUpdateState(): UpdateState | null;
7
+ export declare function writeUpdateState(state: UpdateState): void;
@@ -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
+ }
@@ -1,5 +1,6 @@
1
1
  type UpdateCheckResult = {
2
2
  upgraded: boolean;
3
3
  };
4
+ /** npm-global / unknown path — keep TTY prompt behavior. Managed uses hooks instead. */
4
5
  export declare function maybeCheckForUpdateOnRun(): Promise<UpdateCheckResult>;
5
6
  export {};