agentloom 0.1.2 → 0.1.3

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 CHANGED
@@ -1,15 +1,16 @@
1
1
  # agentloom
2
2
 
3
- `agentloom` is a unified CLI for managing agent definitions and MCP configuration across multiple AI coding tools.
3
+ `agentloom` is a unified CLI for managing agent definitions and MCP configuration across multiple AI coding tools — Cursor, Claude, Copilot, Codex, OpenCode, Gemini, and Pi.
4
4
 
5
- It uses `.agents` as the canonical source of truth and syncs provider-native files for:
5
+ For monorepo-level documentation and architecture context, see the [root README](../../README.md).
6
6
 
7
- - Cursor
8
- - Claude
9
- - Codex
10
- - OpenCode
11
- - Gemini
12
- - Copilot
7
+ ## Getting started
8
+
9
+ ```bash
10
+ npx agentloom init
11
+ ```
12
+
13
+ That's all you need. Agentloom picks up your existing provider configs, migrates them into a unified `.agents/` directory, and syncs everything back out to all your tools. From here on, manage your agents, commands, skills, and MCP servers in one place and run `agentloom sync` whenever you make changes.
13
14
 
14
15
  ## Install
15
16
 
package/dist/cli.js CHANGED
@@ -5,6 +5,7 @@ import { runAddCommand } from "./commands/add.js";
5
5
  import { runCommandCommand } from "./commands/command.js";
6
6
  import { runDeleteCommand } from "./commands/delete.js";
7
7
  import { runFindCommand } from "./commands/find.js";
8
+ import { runInitCommand } from "./commands/init.js";
8
9
  import { runMcpCommand } from "./commands/mcp.js";
9
10
  import { runSkillCommand } from "./commands/skills.js";
10
11
  import { runSyncCommand } from "./commands/sync.js";
@@ -81,6 +82,9 @@ async function runRoutedCommand(route, parsed, cwd, command) {
81
82
  case "delete":
82
83
  await runDeleteCommand(parsed, cwd);
83
84
  return;
85
+ case "init":
86
+ await runInitCommand(parsed, cwd);
87
+ return;
84
88
  default:
85
89
  throw new Error(formatUnknownCommandError(command));
86
90
  }
@@ -0,0 +1,2 @@
1
+ import type { ParsedArgs } from "minimist";
2
+ export declare function runInitCommand(argv: ParsedArgs, cwd: string): Promise<void>;
@@ -0,0 +1,14 @@
1
+ import { getInitHelpText } from "../core/copy.js";
2
+ import { runScopedSyncCommand } from "./sync.js";
3
+ export async function runInitCommand(argv, cwd) {
4
+ if (argv.help) {
5
+ console.log(getInitHelpText());
6
+ return;
7
+ }
8
+ await runScopedSyncCommand({
9
+ argv,
10
+ cwd,
11
+ target: "all",
12
+ skipSync: Boolean(argv["no-sync"]),
13
+ });
14
+ }
@@ -1,3 +1,4 @@
1
+ import { ALL_PROVIDERS } from "../types.js";
1
2
  import { getStringArrayFlag, parseProvidersFlag } from "../core/argv.js";
2
3
  import { formatUsageError, getMcpAddHelpText, getMcpDeleteHelpText, getMcpHelpText, getMcpListHelpText, getMcpServerHelpText, } from "../core/copy.js";
3
4
  import { readCanonicalMcp, writeCanonicalMcp } from "../core/mcp.js";
@@ -244,15 +245,7 @@ function runMcpAdd(paths, argv, name) {
244
245
  }
245
246
  else {
246
247
  const providerMap = {};
247
- const allProviders = [
248
- "cursor",
249
- "claude",
250
- "codex",
251
- "opencode",
252
- "gemini",
253
- "copilot",
254
- ];
255
- for (const provider of allProviders) {
248
+ for (const provider of ALL_PROVIDERS) {
256
249
  providerMap[provider] = providers.includes(provider)
257
250
  ? { ...baseConfig }
258
251
  : false;
@@ -1,12 +1,11 @@
1
1
  import path from "node:path";
2
- import { parseProvidersFlag } from "../core/argv.js";
3
2
  import { formatUsageError } from "../core/copy.js";
4
- import { applySkillProviderSideEffects, parseSkillsDir, } from "../core/skills.js";
5
- import { formatSyncSummary, resolveProvidersForSync, syncFromCanonical, } from "../sync/index.js";
3
+ import { parseSkillsDir } from "../core/skills.js";
6
4
  import { runScopedAddCommand } from "./add.js";
7
5
  import { runScopedDeleteCommand } from "./delete.js";
8
- import { getNonInteractiveMode, resolvePathsForCommand, } from "./entity-utils.js";
6
+ import { resolvePathsForCommand } from "./entity-utils.js";
9
7
  import { runScopedFindCommand } from "./find.js";
8
+ import { runScopedSyncCommand } from "./sync.js";
10
9
  import { runScopedUpdateCommand } from "./update.js";
11
10
  export async function runSkillCommand(argv, cwd) {
12
11
  const action = argv._[1];
@@ -79,29 +78,9 @@ export async function runSkillCommand(argv, cwd) {
79
78
  });
80
79
  return;
81
80
  }
82
- const paths = await resolvePathsForCommand(argv, cwd);
83
- const nonInteractive = getNonInteractiveMode(argv);
84
- const explicitProviders = parseProvidersFlag(argv.providers);
85
- const providers = await resolveProvidersForSync({
86
- paths,
87
- explicitProviders,
88
- nonInteractive,
89
- });
90
- applySkillProviderSideEffects({
91
- paths,
92
- providers,
93
- dryRun: Boolean(argv["dry-run"]),
94
- warn(message) {
95
- console.warn(`Warning: ${message}`);
96
- },
97
- });
98
- const summary = await syncFromCanonical({
99
- paths,
100
- providers,
101
- yes: Boolean(argv.yes),
102
- nonInteractive,
103
- dryRun: Boolean(argv["dry-run"]),
81
+ await runScopedSyncCommand({
82
+ argv,
83
+ cwd,
104
84
  target: "skill",
105
85
  });
106
- console.log(formatSyncSummary(summary, paths.agentsRoot));
107
86
  }
@@ -5,4 +5,5 @@ export declare function runScopedSyncCommand(options: {
5
5
  argv: ParsedArgs;
6
6
  cwd: string;
7
7
  target: EntityType | "all";
8
+ skipSync?: boolean;
8
9
  }): Promise<void>;
@@ -1,7 +1,11 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
1
4
  import { parseProvidersFlag } from "../core/argv.js";
2
5
  import { getSyncHelpText } from "../core/copy.js";
6
+ import { formatMigrationSummary, initializeCanonicalLayout, migrateProviderStateToCanonical, MigrationConflictError, } from "../core/migration.js";
3
7
  import { getNonInteractiveMode, resolvePathsForCommand, } from "./entity-utils.js";
4
- import { formatSyncSummary, syncFromCanonical } from "../sync/index.js";
8
+ import { formatSyncSummary, resolveProvidersForSync, syncFromCanonical, } from "../sync/index.js";
5
9
  export async function runSyncCommand(argv, cwd) {
6
10
  if (argv.help) {
7
11
  console.log(getSyncHelpText());
@@ -14,14 +18,81 @@ export async function runSyncCommand(argv, cwd) {
14
18
  });
15
19
  }
16
20
  export async function runScopedSyncCommand(options) {
17
- const paths = await resolvePathsForCommand(options.argv, options.cwd);
18
- const summary = await syncFromCanonical({
19
- paths,
20
- providers: parseProvidersFlag(options.argv.providers),
21
- yes: Boolean(options.argv.yes),
22
- nonInteractive: getNonInteractiveMode(options.argv),
23
- dryRun: Boolean(options.argv["dry-run"]),
24
- target: options.target,
25
- });
26
- console.log(formatSyncSummary(summary, paths.agentsRoot));
21
+ const nonInteractive = getNonInteractiveMode(options.argv);
22
+ let cleanupDryRunPaths;
23
+ try {
24
+ const paths = await resolvePathsForCommand(options.argv, options.cwd);
25
+ const explicitProviders = parseProvidersFlag(options.argv.providers);
26
+ const providers = await resolveProvidersForSync({
27
+ paths,
28
+ explicitProviders,
29
+ nonInteractive,
30
+ });
31
+ const dryRun = Boolean(options.argv["dry-run"]);
32
+ const effectivePaths = dryRun
33
+ ? createDryRunCanonicalPaths(paths)
34
+ : { paths, cleanup: undefined };
35
+ cleanupDryRunPaths = effectivePaths.cleanup;
36
+ initializeCanonicalLayout(effectivePaths.paths, providers);
37
+ const migrationSummary = await migrateProviderStateToCanonical({
38
+ paths: effectivePaths.paths,
39
+ providers,
40
+ target: options.target,
41
+ yes: Boolean(options.argv.yes),
42
+ nonInteractive,
43
+ dryRun,
44
+ materializeCanonical: dryRun,
45
+ });
46
+ console.log(formatMigrationSummary(migrationSummary));
47
+ if (options.skipSync) {
48
+ return;
49
+ }
50
+ const syncSummary = await syncFromCanonical({
51
+ paths: effectivePaths.paths,
52
+ providers,
53
+ yes: Boolean(options.argv.yes),
54
+ nonInteractive,
55
+ dryRun,
56
+ target: options.target,
57
+ });
58
+ console.log("");
59
+ console.log(formatSyncSummary(syncSummary, paths.agentsRoot));
60
+ }
61
+ catch (error) {
62
+ if (error instanceof MigrationConflictError) {
63
+ console.error(error.message);
64
+ process.exit(2);
65
+ }
66
+ throw error;
67
+ }
68
+ finally {
69
+ cleanupDryRunPaths?.();
70
+ }
71
+ }
72
+ function createDryRunCanonicalPaths(paths) {
73
+ const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "agentloom-dry-run-"));
74
+ const tempAgentsRoot = path.join(tempRoot, ".agents");
75
+ if (fs.existsSync(paths.agentsRoot) &&
76
+ fs.statSync(paths.agentsRoot).isDirectory()) {
77
+ fs.cpSync(paths.agentsRoot, tempAgentsRoot, {
78
+ recursive: true,
79
+ force: true,
80
+ });
81
+ }
82
+ return {
83
+ paths: {
84
+ ...paths,
85
+ agentsRoot: tempAgentsRoot,
86
+ agentsDir: path.join(tempAgentsRoot, "agents"),
87
+ commandsDir: path.join(tempAgentsRoot, "commands"),
88
+ skillsDir: path.join(tempAgentsRoot, "skills"),
89
+ mcpPath: path.join(tempAgentsRoot, "mcp.json"),
90
+ lockPath: path.join(tempAgentsRoot, "agents.lock.json"),
91
+ settingsPath: path.join(tempAgentsRoot, "settings.local.json"),
92
+ manifestPath: path.join(tempAgentsRoot, ".sync-manifest.json"),
93
+ },
94
+ cleanup() {
95
+ fs.rmSync(tempRoot, { recursive: true, force: true });
96
+ },
97
+ };
27
98
  }
package/dist/core/argv.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import minimist from "minimist";
2
+ import { ALL_PROVIDERS } from "../types.js";
2
3
  export function parseArgs(argv) {
3
4
  const parsed = minimist(argv, {
4
5
  boolean: ["global", "local", "yes", "no-sync", "dry-run", "json", "help"],
@@ -50,16 +51,11 @@ export function parseProvidersFlag(input) {
50
51
  .map((item) => item.toLowerCase());
51
52
  const validProviders = [];
52
53
  for (const provider of parsed) {
53
- if (provider === "cursor" ||
54
- provider === "claude" ||
55
- provider === "codex" ||
56
- provider === "opencode" ||
57
- provider === "gemini" ||
58
- provider === "copilot") {
54
+ if (ALL_PROVIDERS.includes(provider)) {
59
55
  validProviders.push(provider);
60
56
  continue;
61
57
  }
62
- throw new Error(`Unknown provider: ${provider}. Expected one of: cursor, claude, codex, opencode, gemini, copilot.`);
58
+ throw new Error(`Unknown provider: ${provider}. Expected one of: ${ALL_PROVIDERS.join(", ")}.`);
63
59
  }
64
60
  return [...new Set(validProviders)];
65
61
  }
@@ -8,6 +8,7 @@ export declare function getFindHelpText(): string;
8
8
  export declare function getAddHelpText(): string;
9
9
  export declare function getUpdateHelpText(): string;
10
10
  export declare function getSyncHelpText(): string;
11
+ export declare function getInitHelpText(): string;
11
12
  export declare function getCommandHelpText(): string;
12
13
  export declare function getCommandAddHelpText(): string;
13
14
  export declare function getCommandListHelpText(): string;
package/dist/core/copy.js CHANGED
@@ -1,4 +1,5 @@
1
- const PROVIDERS_CSV = "cursor,claude,codex,opencode,gemini,copilot";
1
+ import { ALL_PROVIDERS } from "../types.js";
2
+ const PROVIDERS_CSV = ALL_PROVIDERS.join(",");
2
3
  export function getRootHelpText() {
3
4
  return `agentloom - unified canonical agent package manager
4
5
 
@@ -8,9 +9,10 @@ Usage:
8
9
 
9
10
  Aggregate commands:
10
11
  add <source> Import agents/commands/mcp/skills from a source
12
+ init Bootstrap canonical files, migrate providers, then sync
11
13
  find <query> Search remote + local entities
12
14
  update [source] Refresh lockfile-managed imports
13
- sync Generate provider-specific outputs
15
+ sync Migrate provider configs then generate provider outputs
14
16
  delete <source|name> Delete imported entities by source or name
15
17
 
16
18
  Entity commands:
@@ -112,7 +114,7 @@ Example:
112
114
  `;
113
115
  }
114
116
  export function getSyncHelpText() {
115
- return `Generate provider-specific files from canonical .agents data.
117
+ return `Migrate provider configs into canonical .agents data, then generate provider-specific outputs.
116
118
 
117
119
  Usage:
118
120
  agentloom sync [options]
@@ -125,6 +127,20 @@ Options:
125
127
  --dry-run Show file changes without writing
126
128
  `;
127
129
  }
130
+ export function getInitHelpText() {
131
+ return `Bootstrap canonical .agents files, migrate provider configs into canonical state, and sync providers.
132
+
133
+ Usage:
134
+ agentloom init [options]
135
+
136
+ Options:
137
+ --local | --global Choose canonical scope (interactive prompts when omitted)
138
+ --providers <csv> Limit providers (${PROVIDERS_CSV})
139
+ --yes Skip interactive conflict prompts and fail fast in non-interactive flows
140
+ --no-sync Skip sync after canonical bootstrap + migration
141
+ --dry-run Show planned migration/sync changes without writing
142
+ `;
143
+ }
128
144
  export function getCommandHelpText() {
129
145
  return `Manage canonical command entities.
130
146
 
@@ -0,0 +1,28 @@
1
+ import type { EntityType, Provider, ScopePaths } from "../types.js";
2
+ export interface MigrationOptions {
3
+ paths: ScopePaths;
4
+ providers: Provider[];
5
+ target: EntityType | "all";
6
+ yes?: boolean;
7
+ nonInteractive?: boolean;
8
+ dryRun?: boolean;
9
+ materializeCanonical?: boolean;
10
+ }
11
+ export interface MigrationEntitySummary {
12
+ detected: number;
13
+ imported: number;
14
+ conflicts: number;
15
+ skipped: number;
16
+ }
17
+ export interface MigrationSummary {
18
+ providers: Provider[];
19
+ target: EntityType | "all";
20
+ entities: Record<EntityType, MigrationEntitySummary>;
21
+ }
22
+ export declare class MigrationConflictError extends Error {
23
+ constructor(message: string);
24
+ }
25
+ export declare function createEmptyMigrationSummary(providers: Provider[], target: EntityType | "all"): MigrationSummary;
26
+ export declare function initializeCanonicalLayout(paths: ScopePaths, providers?: Provider[]): void;
27
+ export declare function migrateProviderStateToCanonical(options: MigrationOptions): Promise<MigrationSummary>;
28
+ export declare function formatMigrationSummary(summary: MigrationSummary): string;