agentloom 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -13
- package/dist/cli.js +17 -8
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +14 -0
- package/dist/commands/mcp.js +2 -9
- package/dist/commands/skills.js +6 -27
- package/dist/commands/sync.d.ts +1 -0
- package/dist/commands/sync.js +82 -11
- package/dist/commands/upgrade.d.ts +2 -0
- package/dist/commands/upgrade.js +19 -0
- package/dist/core/argv.js +3 -7
- package/dist/core/copy.d.ts +2 -0
- package/dist/core/copy.js +32 -3
- package/dist/core/lockfile.js +34 -2
- package/dist/core/manage-agents-bootstrap.js +1 -0
- package/dist/core/migration.d.ts +28 -0
- package/dist/core/migration.js +809 -0
- package/dist/core/provider-paths.d.ts +16 -0
- package/dist/core/provider-paths.js +154 -0
- package/dist/core/router.d.ts +1 -1
- package/dist/core/router.js +2 -0
- package/dist/core/skills.js +2 -16
- package/dist/core/version-notifier.d.ts +11 -1
- package/dist/core/version-notifier.js +179 -44
- package/dist/sync/index.js +27 -104
- package/dist/types.d.ts +2 -1
- package/dist/types.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Provider, ScopePaths } from "../types.js";
|
|
2
|
+
export declare function getProviderAgentsDir(paths: ScopePaths, provider: Provider): string;
|
|
3
|
+
export declare function getProviderCommandsDir(paths: ScopePaths, provider: Provider): string;
|
|
4
|
+
export declare function getProviderSkillsPaths(paths: ScopePaths, providers: Provider[]): string[];
|
|
5
|
+
export declare function getCursorMcpPath(paths: ScopePaths): string;
|
|
6
|
+
export declare function getClaudeMcpPath(paths: ScopePaths): string;
|
|
7
|
+
export declare function getClaudeSettingsPath(paths: ScopePaths): string;
|
|
8
|
+
export declare function getOpenCodeConfigPath(paths: ScopePaths): string;
|
|
9
|
+
export declare function getGeminiSettingsPath(paths: ScopePaths): string;
|
|
10
|
+
export declare function getCopilotMcpPath(paths: ScopePaths): string;
|
|
11
|
+
export declare function getCodexRootDir(paths: ScopePaths): string;
|
|
12
|
+
export declare function getCodexConfigPath(paths: ScopePaths): string;
|
|
13
|
+
export declare function getCodexAgentsDir(paths: ScopePaths): string;
|
|
14
|
+
export declare function getCodexPromptsDir(paths: ScopePaths): string;
|
|
15
|
+
export declare function getPiMcpPath(paths: ScopePaths): string;
|
|
16
|
+
export declare function getVsCodeSettingsPath(homeDir: string): string;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export function getProviderAgentsDir(paths, provider) {
|
|
4
|
+
const workspaceRoot = paths.workspaceRoot;
|
|
5
|
+
const home = paths.homeDir;
|
|
6
|
+
switch (provider) {
|
|
7
|
+
case "cursor":
|
|
8
|
+
return paths.scope === "local"
|
|
9
|
+
? path.join(workspaceRoot, ".cursor", "agents")
|
|
10
|
+
: path.join(home, ".cursor", "agents");
|
|
11
|
+
case "claude":
|
|
12
|
+
return paths.scope === "local"
|
|
13
|
+
? path.join(workspaceRoot, ".claude", "agents")
|
|
14
|
+
: path.join(home, ".claude", "agents");
|
|
15
|
+
case "codex":
|
|
16
|
+
return path.join(getCodexRootDir(paths), "agents");
|
|
17
|
+
case "opencode":
|
|
18
|
+
return paths.scope === "local"
|
|
19
|
+
? path.join(workspaceRoot, ".opencode", "agents")
|
|
20
|
+
: path.join(home, ".config", "opencode", "agents");
|
|
21
|
+
case "gemini":
|
|
22
|
+
return paths.scope === "local"
|
|
23
|
+
? path.join(workspaceRoot, ".gemini", "agents")
|
|
24
|
+
: path.join(home, ".gemini", "agents");
|
|
25
|
+
case "copilot":
|
|
26
|
+
return paths.scope === "local"
|
|
27
|
+
? path.join(workspaceRoot, ".github", "agents")
|
|
28
|
+
: path.join(home, ".vscode", "chatmodes");
|
|
29
|
+
case "pi":
|
|
30
|
+
return paths.scope === "local"
|
|
31
|
+
? path.join(workspaceRoot, ".pi", "agents")
|
|
32
|
+
: path.join(home, ".pi", "agent", "agents");
|
|
33
|
+
default:
|
|
34
|
+
return path.join(workspaceRoot, ".agents", "unknown");
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function getProviderCommandsDir(paths, provider) {
|
|
38
|
+
const workspaceRoot = paths.workspaceRoot;
|
|
39
|
+
const home = paths.homeDir;
|
|
40
|
+
switch (provider) {
|
|
41
|
+
case "cursor":
|
|
42
|
+
return paths.scope === "local"
|
|
43
|
+
? path.join(workspaceRoot, ".cursor", "commands")
|
|
44
|
+
: path.join(home, ".cursor", "commands");
|
|
45
|
+
case "claude":
|
|
46
|
+
return paths.scope === "local"
|
|
47
|
+
? path.join(workspaceRoot, ".claude", "commands")
|
|
48
|
+
: path.join(home, ".claude", "commands");
|
|
49
|
+
case "codex":
|
|
50
|
+
return getCodexPromptsDir(paths);
|
|
51
|
+
case "opencode":
|
|
52
|
+
return paths.scope === "local"
|
|
53
|
+
? path.join(workspaceRoot, ".opencode", "commands")
|
|
54
|
+
: path.join(home, ".config", "opencode", "commands");
|
|
55
|
+
case "gemini":
|
|
56
|
+
return paths.scope === "local"
|
|
57
|
+
? path.join(workspaceRoot, ".gemini", "commands")
|
|
58
|
+
: path.join(home, ".gemini", "commands");
|
|
59
|
+
case "copilot":
|
|
60
|
+
return paths.scope === "local"
|
|
61
|
+
? path.join(workspaceRoot, ".github", "prompts")
|
|
62
|
+
: path.join(home, ".github", "prompts");
|
|
63
|
+
case "pi":
|
|
64
|
+
return paths.scope === "local"
|
|
65
|
+
? path.join(workspaceRoot, ".pi", "prompts")
|
|
66
|
+
: path.join(home, ".pi", "agent", "prompts");
|
|
67
|
+
default:
|
|
68
|
+
return path.join(workspaceRoot, ".agents", "unknown", "commands");
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export function getProviderSkillsPaths(paths, providers) {
|
|
72
|
+
const targets = new Set();
|
|
73
|
+
const hasClaudeStyleProvider = providers.includes("claude") || providers.includes("copilot");
|
|
74
|
+
if (hasClaudeStyleProvider) {
|
|
75
|
+
targets.add(paths.scope === "local"
|
|
76
|
+
? path.join(paths.workspaceRoot, ".claude", "skills")
|
|
77
|
+
: path.join(paths.homeDir, ".claude", "skills"));
|
|
78
|
+
}
|
|
79
|
+
if (providers.includes("cursor")) {
|
|
80
|
+
targets.add(paths.scope === "local"
|
|
81
|
+
? path.join(paths.workspaceRoot, ".cursor", "skills")
|
|
82
|
+
: path.join(paths.homeDir, ".cursor", "skills"));
|
|
83
|
+
}
|
|
84
|
+
if (providers.includes("pi")) {
|
|
85
|
+
targets.add(paths.scope === "local"
|
|
86
|
+
? path.join(paths.workspaceRoot, ".pi", "skills")
|
|
87
|
+
: path.join(paths.homeDir, ".pi", "agent", "skills"));
|
|
88
|
+
}
|
|
89
|
+
return [...targets];
|
|
90
|
+
}
|
|
91
|
+
export function getCursorMcpPath(paths) {
|
|
92
|
+
return paths.scope === "local"
|
|
93
|
+
? path.join(paths.workspaceRoot, ".cursor", "mcp.json")
|
|
94
|
+
: path.join(paths.homeDir, ".cursor", "mcp.json");
|
|
95
|
+
}
|
|
96
|
+
export function getClaudeMcpPath(paths) {
|
|
97
|
+
return paths.scope === "local"
|
|
98
|
+
? path.join(paths.workspaceRoot, ".mcp.json")
|
|
99
|
+
: path.join(paths.homeDir, ".mcp.json");
|
|
100
|
+
}
|
|
101
|
+
export function getClaudeSettingsPath(paths) {
|
|
102
|
+
return paths.scope === "local"
|
|
103
|
+
? path.join(paths.workspaceRoot, ".claude", "settings.json")
|
|
104
|
+
: path.join(paths.homeDir, ".claude.json");
|
|
105
|
+
}
|
|
106
|
+
export function getOpenCodeConfigPath(paths) {
|
|
107
|
+
return paths.scope === "local"
|
|
108
|
+
? path.join(paths.workspaceRoot, ".opencode", "opencode.json")
|
|
109
|
+
: path.join(paths.homeDir, ".config", "opencode", "opencode.json");
|
|
110
|
+
}
|
|
111
|
+
export function getGeminiSettingsPath(paths) {
|
|
112
|
+
return paths.scope === "local"
|
|
113
|
+
? path.join(paths.workspaceRoot, ".gemini", "settings.json")
|
|
114
|
+
: path.join(paths.homeDir, ".gemini", "settings.json");
|
|
115
|
+
}
|
|
116
|
+
export function getCopilotMcpPath(paths) {
|
|
117
|
+
return paths.scope === "local"
|
|
118
|
+
? path.join(paths.workspaceRoot, ".vscode", "mcp.json")
|
|
119
|
+
: path.join(paths.homeDir, ".vscode", "mcp.json");
|
|
120
|
+
}
|
|
121
|
+
export function getCodexRootDir(paths) {
|
|
122
|
+
return paths.scope === "local"
|
|
123
|
+
? path.join(paths.workspaceRoot, ".codex")
|
|
124
|
+
: path.join(paths.homeDir, ".codex");
|
|
125
|
+
}
|
|
126
|
+
export function getCodexConfigPath(paths) {
|
|
127
|
+
return path.join(getCodexRootDir(paths), "config.toml");
|
|
128
|
+
}
|
|
129
|
+
export function getCodexAgentsDir(paths) {
|
|
130
|
+
return path.join(getCodexRootDir(paths), "agents");
|
|
131
|
+
}
|
|
132
|
+
export function getCodexPromptsDir(paths) {
|
|
133
|
+
return path.join(paths.homeDir, ".codex", "prompts");
|
|
134
|
+
}
|
|
135
|
+
export function getPiMcpPath(paths) {
|
|
136
|
+
return paths.scope === "local"
|
|
137
|
+
? path.join(paths.workspaceRoot, ".pi", "mcp.json")
|
|
138
|
+
: path.join(paths.homeDir, ".pi", "agent", "mcp.json");
|
|
139
|
+
}
|
|
140
|
+
export function getVsCodeSettingsPath(homeDir) {
|
|
141
|
+
switch (os.platform()) {
|
|
142
|
+
case "darwin":
|
|
143
|
+
return path.join(homeDir, "Library", "Application Support", "Code", "User", "settings.json");
|
|
144
|
+
case "win32": {
|
|
145
|
+
const appData = process.env.APPDATA;
|
|
146
|
+
if (!appData) {
|
|
147
|
+
return path.join(homeDir, "AppData", "Roaming", "Code", "User", "settings.json");
|
|
148
|
+
}
|
|
149
|
+
return path.join(appData, "Code", "User", "settings.json");
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
return path.join(homeDir, ".config", "Code", "User", "settings.json");
|
|
153
|
+
}
|
|
154
|
+
}
|
package/dist/core/router.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { EntityType } from "../types.js";
|
|
2
|
-
export type AggregateVerb = "add" | "find" | "update" | "sync" | "delete";
|
|
2
|
+
export type AggregateVerb = "add" | "find" | "update" | "upgrade" | "sync" | "delete" | "init";
|
|
3
3
|
export type EntityVerb = "add" | "list" | "delete" | "find" | "update" | "sync";
|
|
4
4
|
export type McpServerVerb = "add" | "list" | "delete";
|
|
5
5
|
export type CommandRoute = {
|
package/dist/core/router.js
CHANGED
package/dist/core/skills.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import matter from "gray-matter";
|
|
4
4
|
import { ensureDir, slugify } from "./fs.js";
|
|
5
|
+
import { getProviderSkillsPaths } from "./provider-paths.js";
|
|
5
6
|
export const ROOT_SKILL_ARTIFACT_DIRS = [
|
|
6
7
|
"references",
|
|
7
8
|
"assets",
|
|
@@ -123,7 +124,7 @@ export function skillContentMatchesTarget(skill, targetDir) {
|
|
|
123
124
|
return rootSkillArtifactsEqual(skill.sourcePath, targetDir);
|
|
124
125
|
}
|
|
125
126
|
export function applySkillProviderSideEffects(options) {
|
|
126
|
-
const pathsToSymlink =
|
|
127
|
+
const pathsToSymlink = getProviderSkillsPaths(options.paths, options.providers);
|
|
127
128
|
if (pathsToSymlink.length === 0)
|
|
128
129
|
return;
|
|
129
130
|
const canonicalSkillsDir = options.paths.skillsDir;
|
|
@@ -139,21 +140,6 @@ export function applySkillProviderSideEffects(options) {
|
|
|
139
140
|
});
|
|
140
141
|
}
|
|
141
142
|
}
|
|
142
|
-
function resolveProviderSkillsPaths(paths, providers) {
|
|
143
|
-
const targets = new Set();
|
|
144
|
-
const hasClaudeStyleProvider = providers.includes("claude") || providers.includes("copilot");
|
|
145
|
-
if (hasClaudeStyleProvider) {
|
|
146
|
-
targets.add(paths.scope === "local"
|
|
147
|
-
? path.join(paths.workspaceRoot, ".claude", "skills")
|
|
148
|
-
: path.join(paths.homeDir, ".claude", "skills"));
|
|
149
|
-
}
|
|
150
|
-
if (providers.includes("cursor")) {
|
|
151
|
-
targets.add(paths.scope === "local"
|
|
152
|
-
? path.join(paths.workspaceRoot, ".cursor", "skills")
|
|
153
|
-
: path.join(paths.homeDir, ".cursor", "skills"));
|
|
154
|
-
}
|
|
155
|
-
return [...targets];
|
|
156
|
-
}
|
|
157
143
|
function enforceProviderSkillsSymlink(options) {
|
|
158
144
|
const resolvedCanonical = realPathOrResolved(options.canonicalSkillsDir);
|
|
159
145
|
const targetDir = options.targetSkillsDir;
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
type MaybeNotifyOptions = {
|
|
2
2
|
command: string;
|
|
3
|
+
argv: string[];
|
|
3
4
|
packageName?: string;
|
|
4
5
|
currentVersion: string;
|
|
5
6
|
};
|
|
7
|
+
export type UpgradeResult = "updated" | "already-latest" | "failed" | "unavailable";
|
|
6
8
|
export declare function maybeNotifyVersionUpdate(options: MaybeNotifyOptions): Promise<void>;
|
|
9
|
+
export declare function upgradeToLatest(options: {
|
|
10
|
+
currentVersion: string;
|
|
11
|
+
packageName?: string;
|
|
12
|
+
}): Promise<UpgradeResult>;
|
|
7
13
|
export declare function isNewerVersion(candidate: string, current: string): boolean;
|
|
8
|
-
export declare function promptAndUpdate(current: string, latest: string
|
|
14
|
+
export declare function promptAndUpdate(current: string, latest: string, options?: {
|
|
15
|
+
packageName?: string;
|
|
16
|
+
rerunArgs?: string[];
|
|
17
|
+
}): Promise<"updated" | "already-latest" | "failed">;
|
|
18
|
+
export declare function maybeConfirmAutoUpgrade(current: string, latest: string): Promise<boolean>;
|
|
9
19
|
export {};
|
|
@@ -6,50 +6,73 @@ import path from "node:path";
|
|
|
6
6
|
import { confirm, isCancel } from "@clack/prompts";
|
|
7
7
|
import { ensureDir, writeJsonAtomic } from "./fs.js";
|
|
8
8
|
const UPDATE_CACHE_PATH = path.join(os.homedir(), ".agents", ".agentloom-version-cache.json");
|
|
9
|
-
const CHECK_INTERVAL_MS =
|
|
9
|
+
const CHECK_INTERVAL_MS = 2 * 60 * 60 * 1000;
|
|
10
10
|
const REQUEST_TIMEOUT_MS = 1800;
|
|
11
|
+
const DISABLE_UPDATE_ENV = "AGENTLOOM_DISABLE_UPDATE_NOTIFIER";
|
|
12
|
+
const SKIP_COMMANDS = new Set([
|
|
13
|
+
"help",
|
|
14
|
+
"--help",
|
|
15
|
+
"-h",
|
|
16
|
+
"version",
|
|
17
|
+
"--version",
|
|
18
|
+
"-v",
|
|
19
|
+
"upgrade",
|
|
20
|
+
]);
|
|
11
21
|
export async function maybeNotifyVersionUpdate(options) {
|
|
12
|
-
if (process.env
|
|
22
|
+
if (process.env[DISABLE_UPDATE_ENV] === "1")
|
|
13
23
|
return;
|
|
14
|
-
|
|
24
|
+
const loweredCommand = options.command.trim().toLowerCase();
|
|
25
|
+
if (SKIP_COMMANDS.has(loweredCommand))
|
|
15
26
|
return;
|
|
16
|
-
const loweredCommand = options.command.toLowerCase();
|
|
17
|
-
if (loweredCommand === "help" ||
|
|
18
|
-
loweredCommand === "--help" ||
|
|
19
|
-
loweredCommand === "-h" ||
|
|
20
|
-
loweredCommand === "version" ||
|
|
21
|
-
loweredCommand === "--version" ||
|
|
22
|
-
loweredCommand === "-v") {
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
27
|
const packageName = options.packageName ?? "agentloom";
|
|
26
28
|
const cache = readVersionCache();
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
cache
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
const latest = await resolveLatestVersion({
|
|
30
|
+
packageName,
|
|
31
|
+
cache,
|
|
32
|
+
forceFetch: false,
|
|
33
|
+
});
|
|
34
|
+
if (!latest)
|
|
35
|
+
return;
|
|
36
|
+
if (!isNewerVersion(latest, options.currentVersion))
|
|
37
|
+
return;
|
|
34
38
|
const now = Date.now();
|
|
35
|
-
|
|
36
|
-
if (lastChecked && now - lastChecked < CHECK_INTERVAL_MS) {
|
|
39
|
+
if (!shouldAttemptAutoUpgrade(cache, latest, now)) {
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
39
|
-
const
|
|
40
|
-
if (!
|
|
41
|
-
cache
|
|
42
|
+
const approved = await maybeConfirmAutoUpgrade(options.currentVersion, latest);
|
|
43
|
+
if (!approved) {
|
|
44
|
+
recordUpgradeAttempt(cache, latest, now);
|
|
42
45
|
writeVersionCache(cache);
|
|
43
46
|
return;
|
|
44
47
|
}
|
|
45
|
-
|
|
46
|
-
cache
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
// Persist attempt before upgrade/rerun, because rerun success paths can exit the process.
|
|
49
|
+
recordUpgradeAttempt(cache, latest, now);
|
|
50
|
+
writeVersionCache(cache);
|
|
51
|
+
await promptAndUpdate(options.currentVersion, latest, {
|
|
52
|
+
packageName,
|
|
53
|
+
rerunArgs: options.argv,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
export async function upgradeToLatest(options) {
|
|
57
|
+
const packageName = options.packageName ?? "agentloom";
|
|
58
|
+
const cache = readVersionCache();
|
|
59
|
+
const latest = await resolveLatestVersion({
|
|
60
|
+
packageName,
|
|
61
|
+
cache,
|
|
62
|
+
forceFetch: true,
|
|
63
|
+
});
|
|
64
|
+
if (!latest)
|
|
65
|
+
return "unavailable";
|
|
66
|
+
if (!isNewerVersion(latest, options.currentVersion)) {
|
|
67
|
+
return "already-latest";
|
|
51
68
|
}
|
|
69
|
+
const result = await promptAndUpdate(options.currentVersion, latest, {
|
|
70
|
+
packageName,
|
|
71
|
+
});
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
recordUpgradeAttempt(cache, latest, now);
|
|
52
74
|
writeVersionCache(cache);
|
|
75
|
+
return result;
|
|
53
76
|
}
|
|
54
77
|
export function isNewerVersion(candidate, current) {
|
|
55
78
|
const next = parseSemver(candidate);
|
|
@@ -111,24 +134,54 @@ function fetchLatestVersion(packageName) {
|
|
|
111
134
|
req.on("error", () => resolve(null));
|
|
112
135
|
});
|
|
113
136
|
}
|
|
114
|
-
export async function promptAndUpdate(current, latest) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
137
|
+
export async function promptAndUpdate(current, latest, options = {}) {
|
|
138
|
+
if (!isNewerVersion(latest, current))
|
|
139
|
+
return "already-latest";
|
|
140
|
+
const packageName = options.packageName ?? "agentloom";
|
|
141
|
+
const rerunArgs = options.rerunArgs ?? [];
|
|
142
|
+
if (rerunArgs.length > 0 && isLikelyNpxExecution()) {
|
|
143
|
+
if (!canRunPackageViaNpx(packageName, latest)) {
|
|
144
|
+
console.error("\nAutomatic npx upgrade is currently unavailable; continuing with current version.\n");
|
|
145
|
+
return "failed";
|
|
146
|
+
}
|
|
147
|
+
console.log(`\nUpdate available: ${current} → ${latest}. Re-running with npx ${packageName}@${latest}...\n`);
|
|
148
|
+
const rerun = spawnSync("npx", ["--yes", `${packageName}@${latest}`, ...rerunArgs], {
|
|
149
|
+
stdio: "inherit",
|
|
150
|
+
env: buildChildEnv(),
|
|
151
|
+
});
|
|
152
|
+
if (rerun.error || rerun.status === null) {
|
|
153
|
+
console.error("\nAutomatic npx rerun failed after upgrade. Please run your command again.\n");
|
|
154
|
+
process.exit(1);
|
|
155
|
+
return "failed"; // unreachable, but satisfies type checker
|
|
156
|
+
}
|
|
157
|
+
process.exit(rerun.status);
|
|
158
|
+
return rerun.status === 0 ? "updated" : "failed"; // unreachable, but satisfies type checker
|
|
159
|
+
}
|
|
160
|
+
console.log(`\nUpdating ${packageName} ${current} → ${latest}...\n`);
|
|
161
|
+
const install = spawnSync("npm", ["i", "-g", `${packageName}@${latest}`], {
|
|
162
|
+
stdio: "inherit",
|
|
118
163
|
});
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
164
|
+
if (install.status !== 0) {
|
|
165
|
+
console.error("\nAutomatic upgrade failed. Run `agentloom upgrade` to retry.\n");
|
|
166
|
+
return "failed";
|
|
167
|
+
}
|
|
168
|
+
if (rerunArgs.length === 0) {
|
|
169
|
+
console.log(`\nUpdated to ${latest}.\n`);
|
|
170
|
+
return "updated";
|
|
171
|
+
}
|
|
172
|
+
console.log(`\nUpdated to ${latest}. Re-running your command...\n`);
|
|
173
|
+
const invocation = buildRerunInvocation(rerunArgs);
|
|
174
|
+
const rerun = spawnSync(invocation.command, invocation.args, {
|
|
123
175
|
stdio: "inherit",
|
|
176
|
+
env: buildChildEnv(),
|
|
124
177
|
});
|
|
125
|
-
if (
|
|
126
|
-
console.
|
|
127
|
-
process.exit(
|
|
128
|
-
return "
|
|
178
|
+
if (rerun.error || rerun.status === null) {
|
|
179
|
+
console.error("\nAutomatic rerun failed after upgrade. Please run your command again.\n");
|
|
180
|
+
process.exit(1);
|
|
181
|
+
return "failed"; // unreachable, but satisfies type checker
|
|
129
182
|
}
|
|
130
|
-
|
|
131
|
-
return "
|
|
183
|
+
process.exit(rerun.status);
|
|
184
|
+
return "updated"; // unreachable, but satisfies type checker
|
|
132
185
|
}
|
|
133
186
|
function readVersionCache() {
|
|
134
187
|
try {
|
|
@@ -158,3 +211,85 @@ function parseTime(value) {
|
|
|
158
211
|
return null;
|
|
159
212
|
return parsed;
|
|
160
213
|
}
|
|
214
|
+
async function resolveLatestVersion(options) {
|
|
215
|
+
const now = Date.now();
|
|
216
|
+
const lastChecked = parseTime(options.cache.lastCheckedAt);
|
|
217
|
+
const shouldFetch = options.forceFetch ||
|
|
218
|
+
!lastChecked ||
|
|
219
|
+
now - lastChecked >= CHECK_INTERVAL_MS;
|
|
220
|
+
if (!shouldFetch) {
|
|
221
|
+
return options.cache.latestVersion ?? null;
|
|
222
|
+
}
|
|
223
|
+
const fetched = await fetchLatestVersion(options.packageName);
|
|
224
|
+
options.cache.lastCheckedAt = new Date(now).toISOString();
|
|
225
|
+
if (fetched) {
|
|
226
|
+
options.cache.latestVersion = fetched;
|
|
227
|
+
}
|
|
228
|
+
writeVersionCache(options.cache);
|
|
229
|
+
if (options.forceFetch) {
|
|
230
|
+
return fetched ?? null;
|
|
231
|
+
}
|
|
232
|
+
return fetched ?? options.cache.latestVersion ?? null;
|
|
233
|
+
}
|
|
234
|
+
function shouldAttemptAutoUpgrade(cache, latest, now) {
|
|
235
|
+
const lastAttemptVersion = cache.lastUpgradeAttemptVersion ?? cache.lastAutoUpgradeVersion;
|
|
236
|
+
if (lastAttemptVersion !== latest)
|
|
237
|
+
return true;
|
|
238
|
+
const lastAttempt = parseTime(cache.lastUpgradeAttemptAt ?? cache.lastAutoUpgradeAt);
|
|
239
|
+
if (!lastAttempt)
|
|
240
|
+
return true;
|
|
241
|
+
return now - lastAttempt >= CHECK_INTERVAL_MS;
|
|
242
|
+
}
|
|
243
|
+
function isLikelyNpxExecution() {
|
|
244
|
+
const npmCommand = process.env.npm_command?.trim().toLowerCase();
|
|
245
|
+
if (npmCommand === "exec")
|
|
246
|
+
return true;
|
|
247
|
+
const argv1 = process.argv[1];
|
|
248
|
+
if (typeof argv1 !== "string" || argv1.length === 0)
|
|
249
|
+
return false;
|
|
250
|
+
return argv1.includes(`${path.sep}_npx${path.sep}`);
|
|
251
|
+
}
|
|
252
|
+
function buildChildEnv() {
|
|
253
|
+
return {
|
|
254
|
+
...process.env,
|
|
255
|
+
[DISABLE_UPDATE_ENV]: "1",
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export async function maybeConfirmAutoUpgrade(current, latest) {
|
|
259
|
+
if (!isInteractiveTty())
|
|
260
|
+
return true;
|
|
261
|
+
const approved = await confirm({
|
|
262
|
+
message: `Update available: ${current} → ${latest}. Upgrade now and re-run your command?`,
|
|
263
|
+
initialValue: true,
|
|
264
|
+
});
|
|
265
|
+
if (isCancel(approved) || approved !== true)
|
|
266
|
+
return false;
|
|
267
|
+
return true;
|
|
268
|
+
}
|
|
269
|
+
function isInteractiveTty() {
|
|
270
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && process.stderr.isTTY);
|
|
271
|
+
}
|
|
272
|
+
function recordUpgradeAttempt(cache, latest, now) {
|
|
273
|
+
cache.lastUpgradeAttemptVersion = latest;
|
|
274
|
+
cache.lastUpgradeAttemptAt = new Date(now).toISOString();
|
|
275
|
+
}
|
|
276
|
+
function canRunPackageViaNpx(packageName, version) {
|
|
277
|
+
const probe = spawnSync("npx", ["--yes", `${packageName}@${version}`, "--version"], {
|
|
278
|
+
stdio: "ignore",
|
|
279
|
+
env: buildChildEnv(),
|
|
280
|
+
});
|
|
281
|
+
return !probe.error && probe.status === 0;
|
|
282
|
+
}
|
|
283
|
+
function buildRerunInvocation(rerunArgs) {
|
|
284
|
+
const scriptPath = process.argv[1];
|
|
285
|
+
if (typeof scriptPath === "string" && scriptPath.trim().length > 0) {
|
|
286
|
+
return {
|
|
287
|
+
command: process.execPath,
|
|
288
|
+
args: [...process.execArgv, scriptPath, ...rerunArgs],
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
command: "agentloom",
|
|
293
|
+
args: rerunArgs,
|
|
294
|
+
};
|
|
295
|
+
}
|