agentloom 0.1.9 → 0.1.10

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.
@@ -59,21 +59,28 @@ async function runEntityAwareUpdate(options) {
59
59
  console.log("No lock entries matched the requested source filter.");
60
60
  return;
61
61
  }
62
+ console.log(`Checking ${entries.length} lock entr${entries.length === 1 ? "y" : "ies"} for updates...`);
62
63
  let updated = 0;
63
64
  let skipped = 0;
64
- for (const entry of entries) {
65
+ for (const [index, entry] of entries.entries()) {
66
+ const progressPrefix = formatUpdateProgressPrefix(index, entries.length);
67
+ const entryLabel = formatLockEntryLabel(entry);
65
68
  if (!entryIncludesTarget(entry, options.target)) {
69
+ console.log(`${progressPrefix} Skipping ${entryLabel} (does not track ${options.target}).`);
66
70
  skipped += 1;
67
71
  continue;
68
72
  }
73
+ console.log(`${progressPrefix} Checking ${entryLabel}...`);
69
74
  const probe = prepareSource({
70
75
  source: entry.source,
71
76
  ref: entry.requestedRef,
72
77
  subdir: entry.subdir,
73
78
  });
74
- const hasNewCommit = probe.resolvedCommit !== entry.resolvedCommit;
79
+ const latestCommit = probe.resolvedCommit;
80
+ const hasNewCommit = latestCommit !== entry.resolvedCommit;
75
81
  probe.cleanup();
76
82
  if (!hasNewCommit) {
83
+ console.log(`${progressPrefix} Up to date at ${formatShortCommit(entry.resolvedCommit)}.`);
77
84
  skipped += 1;
78
85
  continue;
79
86
  }
@@ -83,9 +90,11 @@ async function runEntityAwareUpdate(options) {
83
90
  !updatePlan.importMcp &&
84
91
  !updatePlan.importRules &&
85
92
  !updatePlan.importSkills) {
93
+ console.log(`${progressPrefix} Skipping ${entryLabel} (no tracked entities selected for update).`);
86
94
  skipped += 1;
87
95
  continue;
88
96
  }
97
+ console.log(`${progressPrefix} Updating ${entryLabel} (${formatShortCommit(entry.resolvedCommit)} -> ${formatShortCommit(latestCommit)})...`);
89
98
  try {
90
99
  const importOptions = {
91
100
  source: entry.source,
@@ -142,6 +151,7 @@ async function runEntityAwareUpdate(options) {
142
151
  rawSource: entry.source,
143
152
  summary,
144
153
  });
154
+ console.log(`${progressPrefix} Updated ${entryLabel} to ${formatShortCommit(summary.resolvedCommit)}.`);
145
155
  updated += 1;
146
156
  }
147
157
  catch (err) {
@@ -163,6 +173,25 @@ async function runEntityAwareUpdate(options) {
163
173
  });
164
174
  }
165
175
  }
176
+ function formatUpdateProgressPrefix(index, total) {
177
+ return `[${index + 1}/${total}]`;
178
+ }
179
+ function formatLockEntryLabel(entry) {
180
+ const base = entry.source;
181
+ const refSuffix = typeof entry.requestedRef === "string" && entry.requestedRef.length > 0
182
+ ? `@${entry.requestedRef}`
183
+ : "";
184
+ const subdirSuffix = typeof entry.subdir === "string" && entry.subdir.length > 0
185
+ ? ` (${entry.subdir})`
186
+ : "";
187
+ return `${base}${refSuffix}${subdirSuffix}`;
188
+ }
189
+ function formatShortCommit(commit) {
190
+ const normalized = commit.trim();
191
+ if (normalized.length <= 12)
192
+ return normalized;
193
+ return normalized.slice(0, 12);
194
+ }
166
195
  function buildEntryUpdatePlan(entry, target) {
167
196
  const includeAgents = shouldUpdateEntity(entry, "agent", target);
168
197
  const includeCommands = shouldUpdateEntity(entry, "command", target);
@@ -914,6 +914,15 @@ async function migrateMcp(options, summary) {
914
914
  writeCanonicalMcp(options.paths, merged);
915
915
  }
916
916
  }
917
+ const MANAGED_MCP_CANONICAL_KEYS_BY_PROVIDER = {
918
+ cursor: ["url", "command", "args", "env"],
919
+ claude: ["type", "url", "command", "args", "env"],
920
+ codex: ["url", "command", "args", "env"],
921
+ opencode: ["url", "command", "args", "env"],
922
+ gemini: ["url", "command", "args", "env"],
923
+ copilot: ["type", "url", "command", "args", "env", "tools"],
924
+ pi: ["url", "command", "args", "env"],
925
+ };
917
926
  function collectProviderMcpServers(paths, providers) {
918
927
  let detected = 0;
919
928
  const servers = new Map();
@@ -930,13 +939,13 @@ function collectProviderMcpServers(paths, providers) {
930
939
  }
931
940
  function readProviderMcp(paths, provider) {
932
941
  if (provider === "cursor") {
933
- return readJsonMcpServers(getCursorMcpPath(paths));
942
+ return readJsonMcpServers(getCursorMcpPath(paths), provider);
934
943
  }
935
944
  if (provider === "claude") {
936
- return readJsonMcpServers(getClaudeMcpPath(paths));
945
+ return readJsonMcpServers(getClaudeMcpPath(paths), provider);
937
946
  }
938
947
  if (provider === "copilot") {
939
- return readJsonMcpServers(getCopilotMcpPath(paths));
948
+ return readJsonMcpServers(getCopilotMcpPath(paths), provider);
940
949
  }
941
950
  if (provider === "opencode") {
942
951
  return readOpenCodeMcp(getOpenCodeConfigPath(paths));
@@ -948,16 +957,16 @@ function readProviderMcp(paths, provider) {
948
957
  return readCodexMcp(getCodexConfigPath(paths));
949
958
  }
950
959
  if (provider === "pi") {
951
- return readJsonMcpServers(getPiMcpPath(paths));
960
+ return readJsonMcpServers(getPiMcpPath(paths), provider);
952
961
  }
953
962
  return {};
954
963
  }
955
- function readJsonMcpServers(filePath) {
964
+ function readJsonMcpServers(filePath, provider) {
956
965
  const parsed = readJsonIfExists(filePath);
957
966
  if (!parsed || !isObject(parsed.mcpServers)) {
958
967
  return {};
959
968
  }
960
- return normalizeMcpServerRecord(parsed.mcpServers);
969
+ return normalizeManagedMcpServerRecord(parsed.mcpServers, MANAGED_MCP_CANONICAL_KEYS_BY_PROVIDER[provider]);
961
970
  }
962
971
  function readOpenCodeMcp(filePath) {
963
972
  const parsed = readJsonIfExists(filePath);
@@ -1013,17 +1022,31 @@ function readCodexMcp(filePath) {
1013
1022
  const parsed = raw.trim() ? TOML.parse(raw) : {};
1014
1023
  if (!isObject(parsed.mcp_servers))
1015
1024
  return {};
1016
- return normalizeMcpServerRecord(parsed.mcp_servers);
1025
+ return normalizeManagedMcpServerRecord(parsed.mcp_servers, MANAGED_MCP_CANONICAL_KEYS_BY_PROVIDER.codex);
1017
1026
  }
1018
- function normalizeMcpServerRecord(raw) {
1027
+ function normalizeManagedMcpServerRecord(raw, allowedKeys) {
1019
1028
  const servers = {};
1020
1029
  for (const [name, config] of Object.entries(raw)) {
1021
- if (!isObject(config))
1030
+ const normalized = pickManagedMcpFields(config, allowedKeys);
1031
+ if (!normalized)
1022
1032
  continue;
1023
- servers[name] = cloneRecord(config);
1033
+ servers[name] = normalized;
1024
1034
  }
1025
1035
  return servers;
1026
1036
  }
1037
+ function pickManagedMcpFields(config, allowedKeys) {
1038
+ if (!isObject(config)) {
1039
+ return null;
1040
+ }
1041
+ const next = {};
1042
+ for (const key of allowedKeys) {
1043
+ const value = config[key];
1044
+ if (value !== undefined) {
1045
+ next[key] = cloneRecord(value);
1046
+ }
1047
+ }
1048
+ return Object.keys(next).length > 0 ? next : null;
1049
+ }
1027
1050
  function normalizeCanonicalServer(server) {
1028
1051
  if (!server) {
1029
1052
  return {
@@ -361,8 +361,10 @@ function syncProviderMcp(options) {
361
361
  const resolved = resolveMcpForProvider(options.mcp, provider);
362
362
  if (provider === "cursor") {
363
363
  const outputPath = getCursorMcpPath(options.paths);
364
+ const existing = readJsonIfExists(outputPath) ?? {};
364
365
  const payload = {
365
- mcpServers: mapMcpServers(resolved, ["url", "command", "args", "env"]),
366
+ ...existing,
367
+ mcpServers: mergeManagedMcpServerEntries(existing.mcpServers, mapMcpServers(resolved, ["url", "command", "args", "env"]), ["url", "command", "args", "env"]),
366
368
  };
367
369
  maybeWriteJson(outputPath, payload, options.dryRun);
368
370
  options.generated.add(outputPath);
@@ -381,19 +383,24 @@ function syncProviderMcp(options) {
381
383
  continue;
382
384
  }
383
385
  const mcpPath = getClaudeMcpPath(options.paths);
384
- const claudeServers = mapMcpServers(resolved, [
386
+ const existingMcp = readJsonIfExists(mcpPath) ?? {};
387
+ const managedClaudeServers = mapMcpServers(resolved, [
385
388
  "type",
386
389
  "url",
387
390
  "command",
388
391
  "args",
389
392
  "env",
390
393
  ]);
391
- for (const [serverName, config] of Object.entries(claudeServers)) {
394
+ for (const [serverName, config] of Object.entries(managedClaudeServers)) {
392
395
  if (!("type" in config) && typeof config.url === "string") {
393
396
  config.type = "http";
394
397
  }
395
398
  }
396
- maybeWriteJson(mcpPath, { mcpServers: claudeServers }, options.dryRun);
399
+ const claudeServers = mergeManagedMcpServerEntries(existingMcp.mcpServers, managedClaudeServers, ["type", "url", "command", "args", "env"]);
400
+ maybeWriteJson(mcpPath, {
401
+ ...existingMcp,
402
+ mcpServers: claudeServers,
403
+ }, options.dryRun);
397
404
  options.generated.add(mcpPath);
398
405
  settings.enabledMcpjsonServers = Object.keys(claudeServers).sort();
399
406
  maybeWriteJson(settingsPath, settings, options.dryRun);
@@ -403,28 +410,34 @@ function syncProviderMcp(options) {
403
410
  if (provider === "opencode") {
404
411
  const outputPath = getOpenCodeConfigPath(options.paths);
405
412
  const existing = readJsonIfExists(outputPath) ?? {};
406
- const mcp = {};
413
+ const managedMcp = {};
407
414
  for (const [serverName, config] of Object.entries(resolved)) {
408
415
  if (typeof config.url === "string") {
409
- mcp[serverName] = {
416
+ managedMcp[serverName] = {
410
417
  type: "remote",
411
418
  url: config.url,
412
419
  };
413
420
  }
414
421
  else {
415
- mcp[serverName] = {
422
+ managedMcp[serverName] = {
416
423
  type: "local",
417
424
  command: config.command,
418
425
  args: Array.isArray(config.args) ? config.args : undefined,
419
426
  };
420
427
  }
421
428
  if (isObject(config.env)) {
422
- mcp[serverName].environment = config.env;
429
+ managedMcp[serverName].environment = config.env;
423
430
  }
424
431
  }
425
432
  const payload = {
426
433
  ...existing,
427
- mcp,
434
+ mcp: mergeManagedMcpServerEntries(existing.mcp, managedMcp, [
435
+ "type",
436
+ "url",
437
+ "command",
438
+ "args",
439
+ "environment",
440
+ ]),
428
441
  };
429
442
  maybeWriteJson(outputPath, payload, options.dryRun);
430
443
  options.generated.add(outputPath);
@@ -453,7 +466,7 @@ function syncProviderMcp(options) {
453
466
  const payload = {
454
467
  ...existing,
455
468
  experimental,
456
- mcpServers,
469
+ mcpServers: mergeManagedMcpServerEntries(existing.mcpServers, mcpServers, ["httpUrl", "command", "args", "env"]),
457
470
  };
458
471
  maybeWriteJson(outputPath, payload, options.dryRun);
459
472
  options.generated.add(outputPath);
@@ -461,6 +474,8 @@ function syncProviderMcp(options) {
461
474
  }
462
475
  if (provider === "copilot") {
463
476
  const profileMcpPath = getCopilotMcpPath(options.paths);
477
+ const managedKeys = ["type", "url", "command", "args", "env", "tools"];
478
+ const existingProfileMcp = readJsonIfExists(profileMcpPath) ?? {};
464
479
  const copilotServers = mapMcpServers(resolved, [
465
480
  "type",
466
481
  "url",
@@ -477,20 +492,25 @@ function syncProviderMcp(options) {
477
492
  config.type = config.url ? "http" : "local";
478
493
  }
479
494
  }
480
- maybeWriteJson(profileMcpPath, { mcpServers: copilotServers }, options.dryRun);
495
+ maybeWriteJson(profileMcpPath, {
496
+ ...existingProfileMcp,
497
+ mcpServers: mergeManagedMcpServerEntries(existingProfileMcp.mcpServers, copilotServers, managedKeys),
498
+ }, options.dryRun);
481
499
  options.generated.add(profileMcpPath);
482
500
  if (options.paths.scope === "global") {
483
501
  const settingsPath = getVsCodeSettingsPath(options.paths.homeDir);
484
502
  const settings = readJsonIfExists(settingsPath) ?? {};
485
- settings["mcp.servers"] = copilotServers;
503
+ settings["mcp.servers"] = mergeManagedMcpServerEntries(settings["mcp.servers"], copilotServers, managedKeys);
486
504
  maybeWriteJson(settingsPath, settings, options.dryRun);
487
505
  options.generated.add(settingsPath);
488
506
  }
489
507
  }
490
508
  if (provider === "pi") {
491
509
  const outputPath = getPiMcpPath(options.paths);
510
+ const existing = readJsonIfExists(outputPath) ?? {};
492
511
  const payload = {
493
- mcpServers: mapMcpServers(resolved, ["url", "command", "args", "env"]),
512
+ ...existing,
513
+ mcpServers: mergeManagedMcpServerEntries(existing.mcpServers, mapMcpServers(resolved, ["url", "command", "args", "env"]), ["url", "command", "args", "env"]),
494
514
  };
495
515
  maybeWriteJson(outputPath, payload, options.dryRun);
496
516
  options.generated.add(outputPath);
@@ -813,22 +833,19 @@ function syncCodex(options) {
813
833
  let nextServers = [...trackedServers];
814
834
  if (options.includeMcp) {
815
835
  const previousServers = new Set(trackedServers);
836
+ const managedCodexMcpServers = mapMcpServers(options.resolvedMcp, [
837
+ "url",
838
+ "command",
839
+ "args",
840
+ "env",
841
+ ]);
816
842
  for (const oldServer of previousServers) {
817
843
  if (!Object.prototype.hasOwnProperty.call(options.resolvedMcp, oldServer)) {
818
844
  delete mcpServers[oldServer];
819
845
  }
820
846
  }
821
- for (const [serverName, config] of Object.entries(options.resolvedMcp)) {
822
- const mapped = {};
823
- if (typeof config.url === "string")
824
- mapped.url = config.url;
825
- if (typeof config.command === "string")
826
- mapped.command = config.command;
827
- if (Array.isArray(config.args))
828
- mapped.args = config.args;
829
- if (isObject(config.env))
830
- mapped.env = config.env;
831
- mcpServers[serverName] = mapped;
847
+ for (const [serverName, config] of Object.entries(managedCodexMcpServers)) {
848
+ mcpServers[serverName] = mergeManagedMcpServerEntry(mcpServers[serverName], config, ["url", "command", "args", "env"]);
832
849
  }
833
850
  parsed.mcp_servers = mcpServers;
834
851
  nextServers = Object.keys(options.resolvedMcp).sort();
@@ -895,6 +912,28 @@ function mapMcpServers(servers, allowedKeys) {
895
912
  }
896
913
  return mapped;
897
914
  }
915
+ function mergeManagedMcpServerEntries(existingServers, nextServers, managedKeys) {
916
+ const merged = {};
917
+ for (const [serverName, config] of Object.entries(nextServers)) {
918
+ merged[serverName] = mergeManagedMcpServerEntry(isObject(existingServers) ? existingServers[serverName] : undefined, config, managedKeys);
919
+ }
920
+ return merged;
921
+ }
922
+ function mergeManagedMcpServerEntry(existingConfig, nextConfig, managedKeys) {
923
+ const merged = isObject(existingConfig) ? cloneSyncValue(existingConfig) : {};
924
+ for (const key of managedKeys) {
925
+ delete merged[key];
926
+ }
927
+ for (const [key, value] of Object.entries(nextConfig)) {
928
+ if (value !== undefined) {
929
+ merged[key] = cloneSyncValue(value);
930
+ }
931
+ }
932
+ return merged;
933
+ }
934
+ function cloneSyncValue(value) {
935
+ return JSON.parse(JSON.stringify(value));
936
+ }
898
937
  function maybeWriteJson(filePath, payload, dryRun) {
899
938
  if (dryRun)
900
939
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentloom",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "description": "Unified agent and MCP sync CLI for multi-provider AI tooling",
5
5
  "type": "module",
6
6
  "bin": {