allagents 1.4.9 → 1.4.11

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 (2) hide show
  1. package/dist/index.js +301 -42
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -15959,9 +15959,37 @@ var init_plugin_path = __esm(() => {
15959
15959
  });
15960
15960
 
15961
15961
  // src/core/plugin.ts
15962
+ var exports_plugin = {};
15963
+ __export(exports_plugin, {
15964
+ updatePlugin: () => updatePlugin,
15965
+ updateCachedPlugins: () => updateCachedPlugins,
15966
+ seedFetchCache: () => seedFetchCache,
15967
+ resetFetchCache: () => resetFetchCache,
15968
+ listCachedPlugins: () => listCachedPlugins,
15969
+ getPluginName: () => getPluginName,
15970
+ getPluginCacheDir: () => getPluginCacheDir,
15971
+ fetchPlugin: () => fetchPlugin
15972
+ });
15962
15973
  import { mkdir, readdir, stat } from "node:fs/promises";
15963
15974
  import { existsSync as existsSync2 } from "node:fs";
15964
15975
  import { basename, dirname, join as join3, resolve as resolve3 } from "node:path";
15976
+ function resetFetchCache() {
15977
+ fetchCache.clear();
15978
+ }
15979
+ function seedFetchCache(url, path, branch) {
15980
+ const parsed = parseGitHubUrl(url);
15981
+ if (!parsed)
15982
+ return;
15983
+ const { owner, repo } = parsed;
15984
+ const cachePath = getPluginCachePath(owner, repo, branch ?? parsed.branch);
15985
+ if (fetchCache.has(cachePath))
15986
+ return;
15987
+ fetchCache.set(cachePath, Promise.resolve({
15988
+ success: true,
15989
+ action: "skipped",
15990
+ cachePath: path
15991
+ }));
15992
+ }
15965
15993
  async function fetchPlugin(url, options2 = {}, deps = {}) {
15966
15994
  const { offline = false, branch } = options2;
15967
15995
  const validation = validatePluginSource(url);
@@ -15984,17 +16012,13 @@ async function fetchPlugin(url, options2 = {}, deps = {}) {
15984
16012
  }
15985
16013
  const { owner, repo } = parsed;
15986
16014
  const cachePath = getPluginCachePath(owner, repo, branch);
15987
- const existing = inflight.get(cachePath);
15988
- if (existing) {
15989
- return existing;
16015
+ const cached = fetchCache.get(cachePath);
16016
+ if (cached) {
16017
+ return cached;
15990
16018
  }
15991
16019
  const promise = doFetchPlugin(cachePath, owner, repo, offline, branch, deps);
15992
- inflight.set(cachePath, promise);
15993
- try {
15994
- return await promise;
15995
- } finally {
15996
- inflight.delete(cachePath);
15997
- }
16020
+ fetchCache.set(cachePath, promise);
16021
+ return promise;
15998
16022
  }
15999
16023
  async function doFetchPlugin(cachePath, owner, repo, offline, branch, deps) {
16000
16024
  const {
@@ -16014,8 +16038,10 @@ async function doFetchPlugin(cachePath, owner, repo, offline, branch, deps) {
16014
16038
  const repoUrl = gitHubUrl(owner, repo);
16015
16039
  if (isCached) {
16016
16040
  try {
16041
+ const pullStart = performance.now();
16017
16042
  await pullFn(cachePath);
16018
- return { success: true, action: "updated", cachePath };
16043
+ const pullMs = Math.round(performance.now() - pullStart);
16044
+ return { success: true, action: "updated", cachePath, durationMs: pullMs };
16019
16045
  } catch {
16020
16046
  return { success: true, action: "skipped", cachePath };
16021
16047
  }
@@ -16023,11 +16049,14 @@ async function doFetchPlugin(cachePath, owner, repo, offline, branch, deps) {
16023
16049
  try {
16024
16050
  const parentDir = dirname(cachePath);
16025
16051
  await mkdirFn(parentDir, { recursive: true });
16052
+ const cloneStart = performance.now();
16026
16053
  await cloneToFn(repoUrl, cachePath, branch);
16054
+ const cloneMs = Math.round(performance.now() - cloneStart);
16027
16055
  return {
16028
16056
  success: true,
16029
16057
  action: "fetched",
16030
- cachePath
16058
+ cachePath,
16059
+ durationMs: cloneMs
16031
16060
  };
16032
16061
  } catch (error) {
16033
16062
  if (error instanceof GitCloneError) {
@@ -16058,6 +16087,57 @@ async function doFetchPlugin(cachePath, owner, repo, offline, branch, deps) {
16058
16087
  };
16059
16088
  }
16060
16089
  }
16090
+ function getPluginCacheDir() {
16091
+ return resolve3(getHomeDir(), ".allagents", "plugins", "marketplaces");
16092
+ }
16093
+ async function listCachedPlugins() {
16094
+ const cacheDir = getPluginCacheDir();
16095
+ if (!existsSync2(cacheDir)) {
16096
+ return [];
16097
+ }
16098
+ const entries = await readdir(cacheDir, { withFileTypes: true });
16099
+ const plugins = [];
16100
+ for (const entry of entries) {
16101
+ if (entry.isDirectory()) {
16102
+ const pluginPath = join3(cacheDir, entry.name);
16103
+ const stats = await stat(pluginPath);
16104
+ plugins.push({
16105
+ name: entry.name,
16106
+ path: pluginPath,
16107
+ lastModified: stats.mtime
16108
+ });
16109
+ }
16110
+ }
16111
+ plugins.sort((a, b) => a.name.localeCompare(b.name));
16112
+ return plugins;
16113
+ }
16114
+ async function updateCachedPlugins(name) {
16115
+ const plugins = await listCachedPlugins();
16116
+ const results = [];
16117
+ const toUpdate = name ? plugins.filter((p) => p.name === name) : plugins;
16118
+ if (name && toUpdate.length === 0) {
16119
+ return [
16120
+ {
16121
+ name,
16122
+ success: false,
16123
+ error: `Plugin not found in cache: ${name}`
16124
+ }
16125
+ ];
16126
+ }
16127
+ for (const plugin of toUpdate) {
16128
+ try {
16129
+ await pull(plugin.path);
16130
+ results.push({ name: plugin.name, success: true });
16131
+ } catch (error) {
16132
+ results.push({
16133
+ name: plugin.name,
16134
+ success: false,
16135
+ error: error instanceof Error ? error.message : "Unknown error"
16136
+ });
16137
+ }
16138
+ }
16139
+ return results;
16140
+ }
16061
16141
  function getPluginName(pluginPath) {
16062
16142
  return basename(pluginPath);
16063
16143
  }
@@ -16135,12 +16215,12 @@ async function updatePlugin(pluginSpec, deps) {
16135
16215
  ...fetchResult.error && { error: fetchResult.error }
16136
16216
  };
16137
16217
  }
16138
- var inflight;
16218
+ var fetchCache;
16139
16219
  var init_plugin = __esm(() => {
16140
16220
  init_plugin_path();
16141
16221
  init_constants();
16142
16222
  init_git();
16143
- inflight = new Map;
16223
+ fetchCache = new Map;
16144
16224
  });
16145
16225
 
16146
16226
  // node_modules/braces/lib/utils.js
@@ -28252,6 +28332,21 @@ async function updateRepositories(changes, workspacePath = process.cwd()) {
28252
28332
  };
28253
28333
  }
28254
28334
  }
28335
+ async function setRepositories(repositories, workspacePath = process.cwd()) {
28336
+ const configPath = join11(workspacePath, CONFIG_DIR, WORKSPACE_CONFIG_FILE);
28337
+ try {
28338
+ const content = await readFile7(configPath, "utf-8");
28339
+ const config = load(content);
28340
+ config.repositories = repositories;
28341
+ await writeFile4(configPath, dump(config, { lineWidth: -1 }), "utf-8");
28342
+ return { success: true };
28343
+ } catch (error) {
28344
+ return {
28345
+ success: false,
28346
+ error: error instanceof Error ? error.message : String(error)
28347
+ };
28348
+ }
28349
+ }
28255
28350
  var DEFAULT_PROJECT_CLIENTS;
28256
28351
  var init_workspace_modify = __esm(() => {
28257
28352
  init_js_yaml();
@@ -28729,6 +28824,7 @@ function reconcileVscodeWorkspaceFolders(workspacePath, codeWorkspaceFolders, la
28729
28824
  const currentAbsPaths = new Set(currentReposByAbsPath.keys());
28730
28825
  const added = [];
28731
28826
  const removed = [];
28827
+ const renamed2 = [];
28732
28828
  const updatedRepos = [];
28733
28829
  for (const [absPath, repo] of currentReposByAbsPath) {
28734
28830
  const inLastSync = lastSyncedSet.has(absPath);
@@ -28736,7 +28832,19 @@ function reconcileVscodeWorkspaceFolders(workspacePath, codeWorkspaceFolders, la
28736
28832
  if (inLastSync && !inCodeWorkspace) {
28737
28833
  removed.push(repo.path);
28738
28834
  } else {
28739
- updatedRepos.push(repo);
28835
+ const folderName = codeWorkspaceNames.get(absPath);
28836
+ if (folderName !== repo.name) {
28837
+ const updatedRepo = { ...repo };
28838
+ if (folderName === undefined) {
28839
+ delete updatedRepo.name;
28840
+ } else {
28841
+ updatedRepo.name = folderName;
28842
+ }
28843
+ updatedRepos.push(updatedRepo);
28844
+ renamed2.push(repo.path);
28845
+ } else {
28846
+ updatedRepos.push(repo);
28847
+ }
28740
28848
  }
28741
28849
  }
28742
28850
  for (const absPath of codeWorkspaceAbsPaths) {
@@ -28752,7 +28860,7 @@ function reconcileVscodeWorkspaceFolders(workspacePath, codeWorkspaceFolders, la
28752
28860
  updatedRepos.push(newRepo);
28753
28861
  }
28754
28862
  }
28755
- return { updatedRepos, added, removed };
28863
+ return { updatedRepos, added, removed, renamed: renamed2 };
28756
28864
  }
28757
28865
  var DEFAULT_SETTINGS;
28758
28866
  var init_vscode_workspace = __esm(() => {
@@ -29736,6 +29844,80 @@ var init_native = __esm(() => {
29736
29844
  init_registry();
29737
29845
  });
29738
29846
 
29847
+ // src/utils/stopwatch.ts
29848
+ class Stopwatch {
29849
+ entries = [];
29850
+ active = new Map;
29851
+ globalStart;
29852
+ constructor() {
29853
+ this.globalStart = performance.now();
29854
+ }
29855
+ start(label) {
29856
+ this.active.set(label, performance.now());
29857
+ }
29858
+ stop(label, detail) {
29859
+ const startTime = this.active.get(label);
29860
+ if (startTime === undefined) {
29861
+ return 0;
29862
+ }
29863
+ const durationMs = performance.now() - startTime;
29864
+ this.active.delete(label);
29865
+ this.entries.push({ label, durationMs, ...detail !== undefined && { detail } });
29866
+ return durationMs;
29867
+ }
29868
+ async measure(label, fn, detail) {
29869
+ this.start(label);
29870
+ try {
29871
+ const result = await fn();
29872
+ this.stop(label, detail);
29873
+ return result;
29874
+ } catch (error) {
29875
+ this.stop(label, detail ? `${detail} (failed)` : "(failed)");
29876
+ throw error;
29877
+ }
29878
+ }
29879
+ getEntries() {
29880
+ return [...this.entries];
29881
+ }
29882
+ getTotalMs() {
29883
+ return performance.now() - this.globalStart;
29884
+ }
29885
+ formatReport() {
29886
+ const lines = [];
29887
+ const totalMs = this.getTotalMs();
29888
+ lines.push(`Sync timing report (total: ${formatMs(totalMs)})`);
29889
+ lines.push("─".repeat(60));
29890
+ for (const entry of this.entries) {
29891
+ const pct = totalMs > 0 ? (entry.durationMs / totalMs * 100).toFixed(1) : "0.0";
29892
+ const detail = entry.detail ? ` [${entry.detail}]` : "";
29893
+ lines.push(` ${padEnd(entry.label, 40)} ${padStart2(formatMs(entry.durationMs), 8)} ${padStart2(pct, 5)}%${detail}`);
29894
+ }
29895
+ lines.push("─".repeat(60));
29896
+ return lines;
29897
+ }
29898
+ toJSON() {
29899
+ return {
29900
+ totalMs: Math.round(this.getTotalMs()),
29901
+ steps: this.entries.map((e) => ({
29902
+ label: e.label,
29903
+ durationMs: Math.round(e.durationMs),
29904
+ ...e.detail && { detail: e.detail }
29905
+ }))
29906
+ };
29907
+ }
29908
+ }
29909
+ function formatMs(ms) {
29910
+ if (ms < 1000)
29911
+ return `${Math.round(ms)}ms`;
29912
+ return `${(ms / 1000).toFixed(2)}s`;
29913
+ }
29914
+ function padEnd(str3, len) {
29915
+ return str3.length >= len ? str3 : str3 + " ".repeat(len - str3.length);
29916
+ }
29917
+ function padStart2(str3, len) {
29918
+ return str3.length >= len ? str3 : " ".repeat(len - str3.length) + str3;
29919
+ }
29920
+
29739
29921
  // src/core/sync.ts
29740
29922
  import { existsSync as existsSync14, readFileSync as readFileSync4, writeFileSync as writeFileSync4, lstatSync } from "node:fs";
29741
29923
  import { rm as rm4, unlink as unlink2, rmdir, copyFile } from "node:fs/promises";
@@ -29784,7 +29966,20 @@ function mergeSyncResults(a, b) {
29784
29966
  ...purgedPaths.length > 0 && { purgedPaths },
29785
29967
  ...deletedArtifacts.length > 0 && { deletedArtifacts },
29786
29968
  ...mcpResults && { mcpResults },
29787
- ...nativeResult && { nativeResult }
29969
+ ...nativeResult && { nativeResult },
29970
+ ...mergeTiming(a.timing, b.timing)
29971
+ };
29972
+ }
29973
+ function mergeTiming(a, b) {
29974
+ if (!a && !b)
29975
+ return {};
29976
+ const aSteps = (a?.steps ?? []).map((s) => ({ ...s, label: `user:${s.label}` }));
29977
+ const bSteps = (b?.steps ?? []).map((s) => ({ ...s, label: `project:${s.label}` }));
29978
+ return {
29979
+ timing: {
29980
+ totalMs: (a?.totalMs ?? 0) + (b?.totalMs ?? 0),
29981
+ steps: [...aSteps, ...bSteps]
29982
+ }
29788
29983
  };
29789
29984
  }
29790
29985
  function resolveNativePluginSource(vp) {
@@ -30474,8 +30669,11 @@ async function syncVscodeWorkspaceFile(workspacePath, config, configPath, previo
30474
30669
  const existingWorkspace = JSON.parse(existingContent);
30475
30670
  const folders = Array.isArray(existingWorkspace.folders) ? existingWorkspace.folders : [];
30476
30671
  const reconciled = reconcileVscodeWorkspaceFolders(workspacePath, folders, previousState.vscodeWorkspaceRepos, config.repositories);
30477
- if (reconciled.added.length > 0 || reconciled.removed.length > 0) {
30478
- await updateRepositories({ remove: reconciled.removed, add: reconciled.added.map((p) => ({ path: p })) }, workspacePath);
30672
+ if (reconciled.added.length > 0 || reconciled.removed.length > 0 || reconciled.renamed.length > 0) {
30673
+ const updateResult = reconciled.renamed.length > 0 ? await setRepositories(reconciled.updatedRepos, workspacePath) : await updateRepositories({ remove: reconciled.removed, add: reconciled.added.map((p) => ({ path: p })) }, workspacePath);
30674
+ if (!updateResult.success) {
30675
+ throw new Error(updateResult.error ?? "Failed to update repositories");
30676
+ }
30479
30677
  updatedConfig = await parseWorkspaceConfig(configPath);
30480
30678
  if (reconciled.removed.length > 0) {
30481
30679
  messages.push(`Repositories removed (from .code-workspace): ${reconciled.removed.join(", ")}`);
@@ -30483,6 +30681,9 @@ async function syncVscodeWorkspaceFile(workspacePath, config, configPath, previo
30483
30681
  if (reconciled.added.length > 0) {
30484
30682
  messages.push(`Repositories added (from .code-workspace): ${reconciled.added.join(", ")}`);
30485
30683
  }
30684
+ if (reconciled.renamed.length > 0) {
30685
+ messages.push(`Repository names updated (from .code-workspace): ${reconciled.renamed.join(", ")}`);
30686
+ }
30486
30687
  }
30487
30688
  } catch {}
30488
30689
  }
@@ -30523,6 +30724,7 @@ async function persistSyncState(workspacePath, pluginResults, workspaceFileResul
30523
30724
  async function syncWorkspace(workspacePath = process.cwd(), options2 = {}) {
30524
30725
  await migrateWorkspaceSkillsV1toV2(workspacePath);
30525
30726
  const { offline = false, dryRun = false, workspaceSourceBase, skipAgentFiles = false } = options2;
30727
+ const sw = new Stopwatch;
30526
30728
  const configDir = join16(workspacePath, CONFIG_DIR);
30527
30729
  const configPath = join16(configDir, WORKSPACE_CONFIG_FILE);
30528
30730
  if (!existsSync14(configPath)) {
@@ -30557,11 +30759,13 @@ async function syncWorkspace(workspacePath = process.cwd(), options2 = {}) {
30557
30759
  ]
30558
30760
  };
30559
30761
  }
30560
- await ensureMarketplacesRegistered(filteredPlans.map((plan) => plan.source));
30561
- const validatedPlugins = await validateAllPlugins(filteredPlans, workspacePath, offline);
30762
+ const marketplaceResults = await sw.measure("marketplace-registration", () => ensureMarketplacesRegistered(filteredPlans.map((plan) => plan.source)));
30763
+ await seedFetchCacheFromMarketplaces(marketplaceResults);
30764
+ const validatedPlugins = await sw.measure("plugin-validation", () => validateAllPlugins(filteredPlans, workspacePath, offline), `${filteredPlans.length} plugin(s)`);
30562
30765
  let validatedWorkspaceSource = null;
30563
30766
  const workspaceSourceWarnings = [];
30564
30767
  if (config.workspace?.source) {
30768
+ sw.start("workspace-source-validation");
30565
30769
  const sourceBasePath = workspaceSourceBase ?? workspacePath;
30566
30770
  const wsSourceResult = await validatePlugin(config.workspace.source, sourceBasePath, offline);
30567
30771
  if (wsSourceResult.success) {
@@ -30569,6 +30773,7 @@ async function syncWorkspace(workspacePath = process.cwd(), options2 = {}) {
30569
30773
  } else {
30570
30774
  workspaceSourceWarnings.push(`Workspace source: ${wsSourceResult.error}`);
30571
30775
  }
30776
+ sw.stop("workspace-source-validation");
30572
30777
  }
30573
30778
  const failedValidations = validatedPlugins.filter((v) => !v.success);
30574
30779
  const validPlugins = validatedPlugins.filter((v) => v.success);
@@ -30589,23 +30794,24 @@ ${failedValidations.map((v) => ` - ${v.plugin}: ${v.error}`).join(`
30589
30794
  paths: getPreviouslySyncedFiles(previousState, client)
30590
30795
  })).filter((p) => p.paths.length > 0) : [];
30591
30796
  if (!dryRun) {
30592
- await selectivePurgeWorkspace(workspacePath, previousState, syncClients);
30797
+ await sw.measure("selective-purge", () => selectivePurgeWorkspace(workspacePath, previousState, syncClients));
30593
30798
  }
30594
30799
  const isV1Fallback = config.version === undefined || config.version < 2;
30595
30800
  const disabledSkillsSet = isV1Fallback ? new Set(config.disabledSkills ?? []) : undefined;
30596
30801
  const enabledSkillsSet = isV1Fallback && config.enabledSkills ? new Set(config.enabledSkills) : undefined;
30597
- const allSkills = await collectAllSkills(validPlugins, disabledSkillsSet, enabledSkillsSet);
30802
+ const allSkills = await sw.measure("skill-collection", () => collectAllSkills(validPlugins, disabledSkillsSet, enabledSkillsSet));
30598
30803
  const pluginSkillMaps = buildPluginSkillNameMaps(allSkills);
30599
30804
  const syncMode = config.syncMode ?? "symlink";
30600
- const pluginResults = await Promise.all(validPlugins.map(async (validatedPlugin) => {
30805
+ const pluginResults = await sw.measure("plugin-copy", () => Promise.all(validPlugins.map(async (validatedPlugin) => {
30601
30806
  const skillNameMap = pluginSkillMaps.get(validatedPlugin.resolved);
30602
30807
  const result = await copyValidatedPlugin(validatedPlugin, workspacePath, validatedPlugin.clients, dryRun, skillNameMap, undefined, syncMode);
30603
30808
  return { ...result, scope: "project" };
30604
- }));
30605
- const nativeResult = await syncNativePlugins(validPlugins, previousState, "project", workspacePath, dryRun, warnings, messages);
30809
+ })), `${validPlugins.length} plugin(s)`);
30810
+ const nativeResult = await sw.measure("native-plugin-sync", () => syncNativePlugins(validPlugins, previousState, "project", workspacePath, dryRun, warnings, messages));
30606
30811
  let workspaceFileResults = [];
30607
30812
  const skipWorkspaceFiles = !!config.workspace?.source && !validatedWorkspaceSource;
30608
30813
  if (config.workspace && !skipWorkspaceFiles) {
30814
+ sw.start("workspace-files");
30609
30815
  const sourcePath = validatedWorkspaceSource?.resolved;
30610
30816
  const filesToCopy = [...config.workspace.files];
30611
30817
  if (hasRepositories && sourcePath) {
@@ -30642,18 +30848,20 @@ ${fileValidationErrors.map((e) => ` - ${e}`).join(`
30642
30848
  await copyFile(agentsPath, claudePath);
30643
30849
  }
30644
30850
  }
30851
+ sw.stop("workspace-files");
30645
30852
  }
30646
30853
  if (!config.workspace && !dryRun && !skipAgentFiles) {
30647
30854
  await updateAgentFiles(workspacePath);
30648
30855
  }
30649
30856
  let vscodeState;
30650
30857
  if (syncClients.includes("vscode") && !dryRun) {
30651
- const result = await syncVscodeWorkspaceFile(workspacePath, config, configPath, previousState, messages);
30858
+ const result = await sw.measure("vscode-workspace-file", () => syncVscodeWorkspaceFile(workspacePath, config, configPath, previousState, messages));
30652
30859
  config = result.config;
30653
30860
  if (result.hash && result.repos) {
30654
30861
  vscodeState = { hash: result.hash, repos: result.repos };
30655
30862
  }
30656
30863
  }
30864
+ sw.start("mcp-sync");
30657
30865
  const mcpResults = {};
30658
30866
  if (syncClients.includes("vscode")) {
30659
30867
  const trackedMcpServers = getPreviouslySyncedMcpServers(previousState, "vscode");
@@ -30711,6 +30919,7 @@ ${fileValidationErrors.map((e) => ` - ${e}`).join(`
30711
30919
  }
30712
30920
  mcpResults.copilot = copilotMcp;
30713
30921
  }
30922
+ sw.stop("mcp-sync");
30714
30923
  const PROJECT_MCP_CLIENTS = new Set(["claude", "codex", "vscode", "copilot", "universal"]);
30715
30924
  const { servers: allMcpServers } = collectMcpServers(validPlugins);
30716
30925
  if (allMcpServers.size > 0) {
@@ -30729,12 +30938,12 @@ ${fileValidationErrors.map((e) => ` - ${e}`).join(`
30729
30938
  const deletedArtifacts = computeDeletedArtifacts(previousState, newStatePaths, syncClients, resolvedMappings, availableSkillNames);
30730
30939
  const { pluginsByClient: nativePluginsByClient } = collectNativePluginSources(validPlugins);
30731
30940
  if (!dryRun) {
30732
- await persistSyncState(workspacePath, pluginResults, workspaceFileResults, syncClients, nativePluginsByClient, nativeResult, {
30941
+ await sw.measure("persist-state", () => persistSyncState(workspacePath, pluginResults, workspaceFileResults, syncClients, nativePluginsByClient, nativeResult, {
30733
30942
  ...vscodeState && { vscodeState },
30734
30943
  ...Object.keys(mcpResults).length > 0 && {
30735
30944
  mcpTrackedServers: Object.fromEntries(Object.entries(mcpResults).map(([scope, r]) => [scope, r.trackedServers]))
30736
30945
  }
30737
- });
30946
+ }));
30738
30947
  }
30739
30948
  return {
30740
30949
  success: !hasFailures,
@@ -30748,11 +30957,36 @@ ${fileValidationErrors.map((e) => ` - ${e}`).join(`
30748
30957
  ...warnings.length > 0 && { warnings },
30749
30958
  ...messages.length > 0 && { messages },
30750
30959
  ...Object.keys(mcpResults).length > 0 && { mcpResults },
30751
- ...nativeResult && { nativeResult }
30960
+ ...nativeResult && { nativeResult },
30961
+ timing: sw.toJSON()
30752
30962
  };
30753
30963
  }
30964
+ async function seedFetchCacheFromMarketplaces(results) {
30965
+ for (const result of results) {
30966
+ if (!result.success || !result.name)
30967
+ continue;
30968
+ const entry = await getMarketplace(result.name);
30969
+ if (!entry || entry.source.type !== "github")
30970
+ continue;
30971
+ seedFetchCache(entry.source.location, entry.path);
30972
+ const branch = readGitBranch(entry.path);
30973
+ if (branch) {
30974
+ seedFetchCache(entry.source.location, entry.path, branch);
30975
+ }
30976
+ }
30977
+ }
30978
+ function readGitBranch(repoPath) {
30979
+ try {
30980
+ const head = readFileSync4(join16(repoPath, ".git", "HEAD"), "utf-8").trim();
30981
+ const prefix = "ref: refs/heads/";
30982
+ return head.startsWith(prefix) ? head.slice(prefix.length) : null;
30983
+ } catch {
30984
+ return null;
30985
+ }
30986
+ }
30754
30987
  async function syncUserWorkspace(options2 = {}) {
30755
30988
  await migrateUserWorkspaceSkillsV1toV2();
30989
+ const sw = new Stopwatch;
30756
30990
  const homeDir = resolve9(getHomeDir());
30757
30991
  const config = await getUserWorkspaceConfig();
30758
30992
  if (!config) {
@@ -30770,8 +31004,9 @@ async function syncUserWorkspace(options2 = {}) {
30770
31004
  const { plans: allPluginPlans, warnings: planWarnings } = buildPluginSyncPlans(config.plugins, workspaceClients, "user");
30771
31005
  const pluginPlans = allPluginPlans.filter((plan) => plan.clients.length > 0 || plan.nativeClients.length > 0);
30772
31006
  const syncClients = collectSyncClients(workspaceClients, pluginPlans);
30773
- await ensureMarketplacesRegistered(pluginPlans.map((plan) => plan.source));
30774
- const validatedPlugins = await validateAllPlugins(pluginPlans, homeDir, offline);
31007
+ const marketplaceResults = await sw.measure("marketplace-registration", () => ensureMarketplacesRegistered(pluginPlans.map((plan) => plan.source)));
31008
+ await seedFetchCacheFromMarketplaces(marketplaceResults);
31009
+ const validatedPlugins = await sw.measure("plugin-validation", () => validateAllPlugins(pluginPlans, homeDir, offline), `${pluginPlans.length} plugin(s)`);
30775
31010
  const failedValidations = validatedPlugins.filter((v) => !v.success);
30776
31011
  const validPlugins = validatedPlugins.filter((v) => v.success);
30777
31012
  const warnings = [
@@ -30786,21 +31021,22 @@ ${failedValidations.map((v) => ` - ${v.plugin}: ${v.error}`).join(`
30786
31021
  }
30787
31022
  const previousState = await loadSyncState(homeDir);
30788
31023
  if (!dryRun) {
30789
- await selectivePurgeWorkspace(homeDir, previousState, syncClients);
31024
+ await sw.measure("selective-purge", () => selectivePurgeWorkspace(homeDir, previousState, syncClients));
30790
31025
  }
30791
31026
  const isV1FallbackUser = config.version === undefined || config.version < 2;
30792
31027
  const disabledSkillsSet = isV1FallbackUser ? new Set(config.disabledSkills ?? []) : undefined;
30793
31028
  const enabledSkillsSet = isV1FallbackUser && config.enabledSkills ? new Set(config.enabledSkills) : undefined;
30794
- const allSkills = await collectAllSkills(validPlugins, disabledSkillsSet, enabledSkillsSet);
31029
+ const allSkills = await sw.measure("skill-collection", () => collectAllSkills(validPlugins, disabledSkillsSet, enabledSkillsSet));
30795
31030
  const pluginSkillMaps = buildPluginSkillNameMaps(allSkills);
30796
31031
  const syncMode = config.syncMode ?? "symlink";
30797
- const pluginResults = await Promise.all(validPlugins.map(async (vp) => {
31032
+ const pluginResults = await sw.measure("plugin-copy", () => Promise.all(validPlugins.map(async (vp) => {
30798
31033
  const skillNameMap = pluginSkillMaps.get(vp.resolved);
30799
31034
  const resolvedUserMappings2 = resolveClientMappings(vp.clients, USER_CLIENT_MAPPINGS);
30800
31035
  const result = await copyValidatedPlugin(vp, homeDir, vp.clients, dryRun, skillNameMap, resolvedUserMappings2, syncMode);
30801
31036
  return { ...result, scope: "user" };
30802
- }));
31037
+ })), `${validPlugins.length} plugin(s)`);
30803
31038
  const { totalCopied, totalFailed, totalSkipped, totalGenerated } = countCopyResults(pluginResults, []);
31039
+ sw.start("mcp-sync");
30804
31040
  const mcpResults = {};
30805
31041
  if (syncClients.includes("vscode")) {
30806
31042
  const trackedMcpServers = getPreviouslySyncedMcpServers(previousState, "vscode");
@@ -30840,6 +31076,7 @@ ${failedValidations.map((v) => ` - ${v.plugin}: ${v.error}`).join(`
30840
31076
  }
30841
31077
  mcpResults.copilot = copilotMcp;
30842
31078
  }
31079
+ sw.stop("mcp-sync");
30843
31080
  const USER_MCP_CLIENTS = new Set(["claude", "codex", "vscode", "copilot", "universal"]);
30844
31081
  const { servers: allUserMcpServers } = collectMcpServers(validPlugins);
30845
31082
  if (allUserMcpServers.size > 0) {
@@ -30849,7 +31086,7 @@ ${failedValidations.map((v) => ` - ${v.plugin}: ${v.error}`).join(`
30849
31086
  }
30850
31087
  }
30851
31088
  }
30852
- const nativeResult = await syncNativePlugins(validPlugins, previousState, "user", homeDir, dryRun, warnings, messages);
31089
+ const nativeResult = await sw.measure("native-plugin-sync", () => syncNativePlugins(validPlugins, previousState, "user", homeDir, dryRun, warnings, messages));
30853
31090
  const availableUserSkillNames = await collectAvailableSkillNames(validPlugins);
30854
31091
  const allCopyResultsForState = pluginResults.flatMap((r) => r.copyResults);
30855
31092
  const resolvedUserMappings = resolveClientMappings(syncClients, USER_CLIENT_MAPPINGS);
@@ -30857,12 +31094,12 @@ ${failedValidations.map((v) => ` - ${v.plugin}: ${v.error}`).join(`
30857
31094
  const deletedArtifacts = computeDeletedArtifacts(previousState, newStatePaths, syncClients, resolvedUserMappings, availableUserSkillNames);
30858
31095
  if (!dryRun) {
30859
31096
  const { pluginsByClient: nativePluginsByClient } = collectNativePluginSources(validPlugins);
30860
- await persistSyncState(homeDir, pluginResults, [], syncClients, nativePluginsByClient, nativeResult, {
31097
+ await sw.measure("persist-state", () => persistSyncState(homeDir, pluginResults, [], syncClients, nativePluginsByClient, nativeResult, {
30861
31098
  clientMappings: USER_CLIENT_MAPPINGS,
30862
31099
  ...Object.keys(mcpResults).length > 0 && {
30863
31100
  mcpTrackedServers: Object.fromEntries(Object.entries(mcpResults).map(([scope, r]) => [scope, r.trackedServers]))
30864
31101
  }
30865
- });
31102
+ }));
30866
31103
  }
30867
31104
  return {
30868
31105
  success: totalFailed === 0,
@@ -30875,7 +31112,8 @@ ${failedValidations.map((v) => ` - ${v.plugin}: ${v.error}`).join(`
30875
31112
  ...warnings.length > 0 && { warnings },
30876
31113
  ...messages.length > 0 && { messages },
30877
31114
  ...Object.keys(mcpResults).length > 0 && { mcpResults },
30878
- ...nativeResult && { nativeResult }
31115
+ ...nativeResult && { nativeResult },
31116
+ timing: sw.toJSON()
30879
31117
  };
30880
31118
  }
30881
31119
  var import_json53, VSCODE_TEMPLATE_FILE = "template.code-workspace";
@@ -34103,11 +34341,11 @@ var package_default;
34103
34341
  var init_package = __esm(() => {
34104
34342
  package_default = {
34105
34343
  name: "allagents",
34106
- version: "1.4.9",
34344
+ version: "1.4.11",
34107
34345
  description: "CLI tool for managing multi-repo AI agent workspaces with plugin synchronization",
34108
34346
  type: "module",
34109
34347
  bin: {
34110
- allagents: "./dist/index.js"
34348
+ allagents: "dist/index.js"
34111
34349
  },
34112
34350
  files: [
34113
34351
  "dist"
@@ -34149,7 +34387,7 @@ var init_package = __esm(() => {
34149
34387
  license: "MIT",
34150
34388
  repository: {
34151
34389
  type: "git",
34152
- url: "https://github.com/EntityProcess/allagents.git"
34390
+ url: "git+https://github.com/EntityProcess/allagents.git"
34153
34391
  },
34154
34392
  homepage: "https://allagents.dev",
34155
34393
  dependencies: {
@@ -36080,6 +36318,8 @@ var syncCmd = import_cmd_ts2.command({
36080
36318
  return;
36081
36319
  }
36082
36320
  let combined = null;
36321
+ const { resetFetchCache: resetFetchCache2 } = await Promise.resolve().then(() => (init_plugin(), exports_plugin));
36322
+ resetFetchCache2();
36083
36323
  if (userConfigExists) {
36084
36324
  const userResult = await syncUserWorkspace({ offline, dryRun, force });
36085
36325
  combined = userResult;
@@ -36182,6 +36422,20 @@ native:`);
36182
36422
  console.log(line);
36183
36423
  }
36184
36424
  }
36425
+ if (process.env.ALLAGENTS_DEBUG?.includes("timing") && result.timing) {
36426
+ console.error("");
36427
+ const totalMs = result.timing.totalMs;
36428
+ console.error(`[debug] Sync timing (total: ${formatTimingMs(totalMs)})`);
36429
+ console.error(`[debug] ${"─".repeat(56)}`);
36430
+ for (const step of result.timing.steps) {
36431
+ const pct = totalMs > 0 ? (step.durationMs / totalMs * 100).toFixed(1) : "0.0";
36432
+ const detail = step.detail ? ` [${step.detail}]` : "";
36433
+ const label = step.label.padEnd(40);
36434
+ const duration = formatTimingMs(step.durationMs).padStart(8);
36435
+ console.error(`[debug] ${label} ${duration} ${pct.padStart(5)}%${detail}`);
36436
+ }
36437
+ console.error(`[debug] ${"─".repeat(56)}`);
36438
+ }
36185
36439
  if (!result.success || result.totalFailed > 0) {
36186
36440
  process.exit(1);
36187
36441
  }
@@ -36198,6 +36452,11 @@ native:`);
36198
36452
  }
36199
36453
  }
36200
36454
  });
36455
+ function formatTimingMs(ms) {
36456
+ if (ms < 1000)
36457
+ return `${Math.round(ms)}ms`;
36458
+ return `${(ms / 1000).toFixed(2)}s`;
36459
+ }
36201
36460
  var statusCmd = import_cmd_ts2.command({
36202
36461
  name: "status",
36203
36462
  description: buildDescription(statusMeta),
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "allagents",
3
- "version": "1.4.9",
3
+ "version": "1.4.11",
4
4
  "description": "CLI tool for managing multi-repo AI agent workspaces with plugin synchronization",
5
5
  "type": "module",
6
6
  "bin": {
7
- "allagents": "./dist/index.js"
7
+ "allagents": "dist/index.js"
8
8
  },
9
9
  "files": [
10
10
  "dist"
@@ -46,7 +46,7 @@
46
46
  "license": "MIT",
47
47
  "repository": {
48
48
  "type": "git",
49
- "url": "https://github.com/EntityProcess/allagents.git"
49
+ "url": "git+https://github.com/EntityProcess/allagents.git"
50
50
  },
51
51
  "homepage": "https://allagents.dev",
52
52
  "dependencies": {