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.
@@ -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
+ }
@@ -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 = {
@@ -2,8 +2,10 @@ const AGGREGATE_VERBS = new Set([
2
2
  "add",
3
3
  "find",
4
4
  "update",
5
+ "upgrade",
5
6
  "sync",
6
7
  "delete",
8
+ "init",
7
9
  ]);
8
10
  const ENTITY_NOUNS = new Set(["agent", "command", "mcp", "skill"]);
9
11
  const ENTITY_VERBS = new Set([
@@ -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 = resolveProviderSkillsPaths(options.paths, options.providers);
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): Promise<"updated" | "declined" | "failed">;
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 = 12 * 60 * 60 * 1000;
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.AGENTLOOM_DISABLE_UPDATE_NOTIFIER === "1")
22
+ if (process.env[DISABLE_UPDATE_ENV] === "1")
13
23
  return;
14
- if (!process.stdout.isTTY || !process.stderr.isTTY)
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
- if (cache.latestVersion &&
28
- isNewerVersion(cache.latestVersion, options.currentVersion) &&
29
- cache.lastNotifiedVersion !== cache.latestVersion) {
30
- await promptAndUpdate(options.currentVersion, cache.latestVersion);
31
- cache.lastNotifiedVersion = cache.latestVersion;
32
- writeVersionCache(cache);
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
- const lastChecked = parseTime(cache.lastCheckedAt);
36
- if (lastChecked && now - lastChecked < CHECK_INTERVAL_MS) {
39
+ if (!shouldAttemptAutoUpgrade(cache, latest, now)) {
37
40
  return;
38
41
  }
39
- const latest = await fetchLatestVersion(packageName);
40
- if (!latest) {
41
- cache.lastCheckedAt = new Date(now).toISOString();
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
- cache.lastCheckedAt = new Date(now).toISOString();
46
- cache.latestVersion = latest;
47
- if (isNewerVersion(latest, options.currentVersion) &&
48
- cache.lastNotifiedVersion !== latest) {
49
- await promptAndUpdate(options.currentVersion, latest);
50
- cache.lastNotifiedVersion = latest;
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
- const accepted = await confirm({
116
- message: `Update available: ${current} → ${latest}. Update now?`,
117
- initialValue: true,
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 (isCancel(accepted) || !accepted)
120
- return "declined";
121
- console.log(`\nUpdating agentloom to ${latest}...\n`);
122
- const result = spawnSync("npm", ["i", "-g", "agentloom"], {
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 (result.status === 0) {
126
- console.log(`\nUpdated to ${latest}. Please re-run your command.\n`);
127
- process.exit(0);
128
- return "updated"; // unreachable, but satisfies type checker
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
- console.error(`\nUpdate failed. You can update manually: npm i -g agentloom\n`);
131
- return "failed";
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
+ }