agent-switchboard 0.4.2 → 0.4.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.
Files changed (115) hide show
  1. package/README.md +45 -0
  2. package/dist/agents/codex.d.ts +2 -2
  3. package/dist/agents/codex.js +13 -7
  4. package/dist/agents/codex.js.map +1 -1
  5. package/dist/agents/cursor.js +5 -17
  6. package/dist/agents/cursor.js.map +1 -1
  7. package/dist/agents/gemini.js +10 -50
  8. package/dist/agents/gemini.js.map +1 -1
  9. package/dist/agents/json-utils.d.ts +12 -0
  10. package/dist/agents/json-utils.js +27 -1
  11. package/dist/agents/json-utils.js.map +1 -1
  12. package/dist/agents/opencode.js +3 -3
  13. package/dist/agents/opencode.js.map +1 -1
  14. package/dist/agents/trae.js +5 -17
  15. package/dist/agents/trae.js.map +1 -1
  16. package/dist/commands/distribution.js +6 -1
  17. package/dist/commands/distribution.js.map +1 -1
  18. package/dist/config/application-config.d.ts +1 -0
  19. package/dist/config/application-config.js +36 -4
  20. package/dist/config/application-config.js.map +1 -1
  21. package/dist/config/layered-config.js +1 -5
  22. package/dist/config/layered-config.js.map +1 -1
  23. package/dist/config/mcp-config.d.ts +2 -3
  24. package/dist/config/mcp-config.js +2 -7
  25. package/dist/config/mcp-config.js.map +1 -1
  26. package/dist/config/schemas.js +1 -1
  27. package/dist/config/schemas.js.map +1 -1
  28. package/dist/extensions/api.d.ts +9 -0
  29. package/dist/extensions/api.js +40 -16
  30. package/dist/extensions/api.js.map +1 -1
  31. package/dist/extensions/loader.d.ts +2 -0
  32. package/dist/extensions/loader.js +9 -5
  33. package/dist/extensions/loader.js.map +1 -1
  34. package/dist/hooks/distribution.d.ts +3 -1
  35. package/dist/hooks/distribution.js +32 -13
  36. package/dist/hooks/distribution.js.map +1 -1
  37. package/dist/index.js +150 -37
  38. package/dist/index.js.map +1 -1
  39. package/dist/library/distribute-bundle.js +89 -28
  40. package/dist/library/distribute-bundle.js.map +1 -1
  41. package/dist/library/distribute.d.ts +2 -2
  42. package/dist/library/distribute.js +40 -16
  43. package/dist/library/distribute.js.map +1 -1
  44. package/dist/library/state.js +12 -4
  45. package/dist/library/state.js.map +1 -1
  46. package/dist/manifest/store.d.ts +12 -1
  47. package/dist/manifest/store.js +63 -8
  48. package/dist/manifest/store.js.map +1 -1
  49. package/dist/marketplace/plugin-loader.d.ts +2 -2
  50. package/dist/marketplace/plugin-loader.js +8 -6
  51. package/dist/marketplace/plugin-loader.js.map +1 -1
  52. package/dist/marketplace/source-loader.js +3 -1
  53. package/dist/marketplace/source-loader.js.map +1 -1
  54. package/dist/mcp/distribution.js +109 -41
  55. package/dist/mcp/distribution.js.map +1 -1
  56. package/dist/plugins/identity.d.ts +8 -0
  57. package/dist/plugins/identity.js +22 -0
  58. package/dist/plugins/identity.js.map +1 -0
  59. package/dist/plugins/index.d.ts +4 -0
  60. package/dist/plugins/index.js +59 -35
  61. package/dist/plugins/index.js.map +1 -1
  62. package/dist/rules/block-merge.d.ts +0 -7
  63. package/dist/rules/block-merge.js +2 -1
  64. package/dist/rules/block-merge.js.map +1 -1
  65. package/dist/rules/distribution.js +18 -5
  66. package/dist/rules/distribution.js.map +1 -1
  67. package/dist/rules/state.js +11 -2
  68. package/dist/rules/state.js.map +1 -1
  69. package/dist/skills/distribution.js +39 -9
  70. package/dist/skills/distribution.js.map +1 -1
  71. package/dist/subagents/distribution.js +6 -1
  72. package/dist/subagents/distribution.js.map +1 -1
  73. package/dist/subagents/inventory.js +3 -3
  74. package/dist/subagents/inventory.js.map +1 -1
  75. package/dist/sync/command.js +23 -5
  76. package/dist/sync/command.js.map +1 -1
  77. package/dist/targets/builtin/claude-code.js +3 -1
  78. package/dist/targets/builtin/claude-code.js.map +1 -1
  79. package/dist/targets/builtin/common.d.ts +4 -1
  80. package/dist/targets/builtin/common.js +11 -1
  81. package/dist/targets/builtin/common.js.map +1 -1
  82. package/dist/targets/builtin/cursor.js +2 -7
  83. package/dist/targets/builtin/cursor.js.map +1 -1
  84. package/dist/targets/builtin/opencode.js +3 -1
  85. package/dist/targets/builtin/opencode.js.map +1 -1
  86. package/dist/targets/dsl/compiler.js +60 -26
  87. package/dist/targets/dsl/compiler.js.map +1 -1
  88. package/dist/targets/init.d.ts +1 -1
  89. package/dist/targets/init.js +3 -2
  90. package/dist/targets/init.js.map +1 -1
  91. package/dist/ui/fuzzy-multi-select.js +2 -1
  92. package/dist/ui/fuzzy-multi-select.js.map +1 -1
  93. package/dist/ui/library-selector.js +8 -9
  94. package/dist/ui/library-selector.js.map +1 -1
  95. package/dist/ui/mcp-ui.d.ts +2 -0
  96. package/dist/ui/mcp-ui.js +4 -4
  97. package/dist/ui/mcp-ui.js.map +1 -1
  98. package/dist/ui/plugin-ui.js +2 -2
  99. package/dist/ui/plugin-ui.js.map +1 -1
  100. package/dist/ui/rule-ui.js +4 -5
  101. package/dist/ui/rule-ui.js.map +1 -1
  102. package/dist/ui/selection-state.d.ts +0 -1
  103. package/dist/ui/selection-state.js +2 -5
  104. package/dist/ui/selection-state.js.map +1 -1
  105. package/dist/ui/subagent-ui.js +2 -16
  106. package/dist/ui/subagent-ui.js.map +1 -1
  107. package/dist/util/cli.d.ts +1 -0
  108. package/dist/util/cli.js +14 -3
  109. package/dist/util/cli.js.map +1 -1
  110. package/dist/util/extras.d.ts +2 -0
  111. package/dist/util/extras.js +3 -1
  112. package/dist/util/extras.js.map +1 -1
  113. package/dist/util/markdown.js +8 -2
  114. package/dist/util/markdown.js.map +1 -1
  115. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -13,8 +13,8 @@ import ora from 'ora';
13
13
  import { distributeCommands } from './commands/distribution.js';
14
14
  import { importCommandFromFile } from './commands/importer.js';
15
15
  import { buildCommandInventory } from './commands/inventory.js';
16
- import { updateConfigLayer } from './config/layered-config.js';
17
- import { loadMcpConfig, stripLegacyEnabledFlagsFromMcpJson } from './config/mcp-config.js';
16
+ import { loadWritableConfigLayer, updateConfigLayer } from './config/layered-config.js';
17
+ import { loadMcpConfig, loadMcpConfigWithPlugins, stripLegacyEnabledFlagsFromMcpJson, } from './config/mcp-config.js';
18
18
  import { getAgentsDir, getAgentsHome, getClaudeDir, getCodexDir, getCommandsDir, getCursorDir, getGeminiDir, getHooksDir, getOpencodePath, getSkillsDir, getSourceCacheDir, } from './config/paths.js';
19
19
  import { scopeToLayerOptions } from './config/scope.js';
20
20
  import { loadSwitchboardConfig, loadSwitchboardConfigWithLayers, } from './config/switchboard-config.js';
@@ -23,6 +23,7 @@ import { loadHookLibrary } from './hooks/library.js';
23
23
  import { copyDirRecursive, ensureLibraryDirectories, isDir, isFile, listFilesRecursively, writeFileSecure, } from './library/fs.js';
24
24
  import { addLocalSource, addRemoteSource, getSources, inferSourceName, isGitUrl, parseGitUrl, removeSource, updateRemoteSources, validateSourcePath, } from './library/sources.js';
25
25
  import { loadLibraryStateSection, saveMcpEnabledState } from './library/state.js';
26
+ import { loadManifest, saveManifest } from './manifest/store.js';
26
27
  import { readMarketplace } from './marketplace/reader.js';
27
28
  import { distributeMcp } from './mcp/distribution.js';
28
29
  import { buildPluginIndex } from './plugins/index.js';
@@ -106,6 +107,29 @@ function resolveScope(input) {
106
107
  project: project,
107
108
  };
108
109
  }
110
+ function getScopedProjectMode(scope, config) {
111
+ return scope?.project ? config.distribution.project.mode : undefined;
112
+ }
113
+ function shouldSkipProjectDistribution(scope, config) {
114
+ return getScopedProjectMode(scope, config) === 'none';
115
+ }
116
+ function printProjectDistributionDisabledMessage() {
117
+ console.log(chalk.gray('Project distribution is disabled by `[distribution.project].mode = "none"`.'));
118
+ }
119
+ function loadManagedProjectManifest(scope, projectMode) {
120
+ if (!scope?.project || projectMode !== 'managed')
121
+ return undefined;
122
+ const result = loadManifest(scope.project);
123
+ if (result.corrupt) {
124
+ throw new Error(`Corrupt managed manifest in ${scope.project}. Fix or remove it before retrying.`);
125
+ }
126
+ return result.manifest;
127
+ }
128
+ function saveManagedProjectManifest(scope, projectMode, manifest) {
129
+ if (!scope?.project || projectMode !== 'managed' || !manifest)
130
+ return;
131
+ saveManifest(scope.project, manifest);
132
+ }
109
133
  function defaultCommandSourceDir(platform) {
110
134
  switch (platform) {
111
135
  case 'claude-code':
@@ -280,6 +304,7 @@ program
280
304
  const scope = resolveScope(options);
281
305
  const { config, layers } = loadSwitchboardConfigWithLayers(scopeToLayerOptions(scope));
282
306
  const mcpConfig = loadMcpConfig();
307
+ const pluginIndex = buildPluginIndex(scope);
283
308
  // Determine initial selection:
284
309
  // - If writing to project/profile scope, only use that layer's explicit [mcp].enabled (no fallback).
285
310
  // - If writing to user scope and [mcp].enabled is missing, fall back to legacy mcp.json enabled flags.
@@ -304,10 +329,22 @@ program
304
329
  .map(([name]) => name);
305
330
  }
306
331
  }
332
+ const scopedPluginRefs = scope?.project
333
+ ? (layers.project?.config?.plugins?.enabled ?? [])
334
+ : scope?.profile
335
+ ? (layers.profile?.config?.plugins?.enabled ?? [])
336
+ : (layers.user.config?.plugins?.enabled ?? []);
337
+ const pluginMcpServers = pluginIndex.expand(scopedPluginRefs).mcp;
338
+ const currentActiveSet = new Set(currentActive);
339
+ for (const serverName of pluginMcpServers) {
340
+ currentActiveSet.add(serverName);
341
+ }
342
+ currentActive = [...currentActiveSet];
307
343
  // Step 1: Show UI and get selected servers
308
344
  const selectedServers = await showMcpServerUI({
309
345
  pageSize: config.ui.page_size,
310
346
  enabled: currentActive,
347
+ scope,
311
348
  });
312
349
  // Step 2: Save active state to config.toml (appropriate layer based on scope)
313
350
  const spinner = ora('Updating MCP configuration...').start();
@@ -326,7 +363,18 @@ program
326
363
  }
327
364
  }
328
365
  // Step 3: Apply to registered agents
329
- await distributeMcp(scope, selectedServers);
366
+ const projectMode = getScopedProjectMode(scope, config);
367
+ const manifest = loadManagedProjectManifest(scope, projectMode);
368
+ try {
369
+ await distributeMcp(scope, selectedServers, { manifest, projectMode });
370
+ }
371
+ finally {
372
+ saveManagedProjectManifest(scope, projectMode, manifest);
373
+ }
374
+ if (shouldSkipProjectDistribution(scope, config)) {
375
+ console.log();
376
+ printProjectDistributionDisabledMessage();
377
+ }
330
378
  // Step 4: Show summary
331
379
  showSummary(selectedServers, scope);
332
380
  }
@@ -425,13 +473,12 @@ ruleCommand.action(async (options) => {
425
473
  if (!selection) {
426
474
  return;
427
475
  }
428
- const { rules, writableState: previousState, effectiveState } = loadRuleRuntimeContext(scope);
476
+ const { rules, writableState: previousState, activeState } = loadRuleRuntimeContext(scope);
429
477
  const ruleMap = new Map(rules.map((rule) => [rule.id, rule]));
430
478
  const desiredEnabled = selection.enabled;
431
479
  const selectionChanged = selection.explicitEmpty ||
432
480
  shouldPersistSelection({
433
- currentEnabled: previousState.enabled,
434
- effectiveEnabled: effectiveState.enabled,
481
+ effectiveEnabled: activeState.enabled,
435
482
  selectedEnabled: desiredEnabled,
436
483
  });
437
484
  const updatedState = selectionChanged
@@ -460,11 +507,21 @@ ruleCommand.action(async (options) => {
460
507
  }
461
508
  }
462
509
  }
463
- const distribution = distributeRules({
464
- force: !selectionChanged,
465
- activeAppIds: config.applications.enabled,
466
- assumeInstalled: new Set(config.applications.assume_installed),
467
- }, scope);
510
+ const projectMode = getScopedProjectMode(scope, config);
511
+ const manifest = loadManagedProjectManifest(scope, projectMode);
512
+ let distribution;
513
+ try {
514
+ distribution = distributeRules({
515
+ force: !selectionChanged,
516
+ activeAppIds: config.applications.enabled,
517
+ assumeInstalled: new Set(config.applications.assume_installed),
518
+ manifest,
519
+ projectMode,
520
+ }, scope);
521
+ }
522
+ finally {
523
+ saveManagedProjectManifest(scope, projectMode, manifest);
524
+ }
468
525
  if (distribution.results.length > 0) {
469
526
  console.log();
470
527
  printDistributionResults({
@@ -474,6 +531,10 @@ ruleCommand.action(async (options) => {
474
531
  getPath: (result) => result.filePath,
475
532
  });
476
533
  }
534
+ if (shouldSkipProjectDistribution(scope, config)) {
535
+ console.log();
536
+ printProjectDistributionDisabledMessage();
537
+ }
477
538
  const distributionErrors = distribution.results.filter((r) => r.status === 'error');
478
539
  if (distributionErrors.length > 0) {
479
540
  process.exit(1);
@@ -515,7 +576,18 @@ commandRoot.action(async (options) => {
515
576
  return;
516
577
  console.log();
517
578
  printActiveSelection('commands', selection.enabled);
518
- const out = distributeCommands(scope, config.applications.enabled, new Set(config.applications.assume_installed));
579
+ const projectMode = getScopedProjectMode(scope, config);
580
+ const manifest = loadManagedProjectManifest(scope, projectMode);
581
+ let out;
582
+ try {
583
+ out = distributeCommands(scope, config.applications.enabled, new Set(config.applications.assume_installed), {
584
+ manifest,
585
+ projectMode,
586
+ });
587
+ }
588
+ finally {
589
+ saveManagedProjectManifest(scope, projectMode, manifest);
590
+ }
519
591
  if (out.results.length > 0) {
520
592
  console.log();
521
593
  printDistributionResults({
@@ -525,6 +597,10 @@ commandRoot.action(async (options) => {
525
597
  getPath: (result) => result.filePath,
526
598
  });
527
599
  }
600
+ if (shouldSkipProjectDistribution(scope, config)) {
601
+ console.log();
602
+ printProjectDistributionDisabledMessage();
603
+ }
528
604
  // Guidance: unsupported platforms for commands
529
605
  console.log();
530
606
  console.log(chalk.gray('Unsupported platforms (manual steps required): Claude Desktop'));
@@ -665,7 +741,18 @@ agentRoot.action(async (options) => {
665
741
  return;
666
742
  console.log();
667
743
  printActiveSelection('agents', selection.enabled);
668
- const out = distributeSubagents(scope, config.applications.enabled, new Set(config.applications.assume_installed));
744
+ const projectMode = getScopedProjectMode(scope, config);
745
+ const manifest = loadManagedProjectManifest(scope, projectMode);
746
+ let out;
747
+ try {
748
+ out = distributeSubagents(scope, config.applications.enabled, new Set(config.applications.assume_installed), {
749
+ manifest,
750
+ projectMode,
751
+ });
752
+ }
753
+ finally {
754
+ saveManagedProjectManifest(scope, projectMode, manifest);
755
+ }
669
756
  if (out.results.length > 0) {
670
757
  console.log();
671
758
  printDistributionResults({
@@ -675,6 +762,10 @@ agentRoot.action(async (options) => {
675
762
  getPath: (result) => result.filePath,
676
763
  });
677
764
  }
765
+ if (shouldSkipProjectDistribution(scope, config)) {
766
+ console.log();
767
+ printProjectDistributionDisabledMessage();
768
+ }
678
769
  console.log();
679
770
  console.log(chalk.gray('Unsupported platforms (manual steps required): Codex, Gemini'));
680
771
  }
@@ -813,11 +904,21 @@ skillRoot.action(async (options) => {
813
904
  return;
814
905
  console.log();
815
906
  printActiveSelection('skills', selection.enabled);
816
- const out = distributeSkills(scope, {
817
- useAgentsDir: config.distribution.use_agents_dir,
818
- activeAppIds: config.applications.enabled,
819
- assumeInstalled: new Set(config.applications.assume_installed),
820
- });
907
+ const projectMode = getScopedProjectMode(scope, config);
908
+ const manifest = loadManagedProjectManifest(scope, projectMode);
909
+ let out;
910
+ try {
911
+ out = distributeSkills(scope, {
912
+ useAgentsDir: config.distribution.use_agents_dir,
913
+ activeAppIds: config.applications.enabled,
914
+ assumeInstalled: new Set(config.applications.assume_installed),
915
+ manifest,
916
+ projectMode,
917
+ });
918
+ }
919
+ finally {
920
+ saveManagedProjectManifest(scope, projectMode, manifest);
921
+ }
821
922
  if (out.results.length > 0) {
822
923
  console.log();
823
924
  printDistributionResults({
@@ -833,6 +934,10 @@ skillRoot.action(async (options) => {
833
934
  getPath: (result) => result.targetDir,
834
935
  });
835
936
  }
937
+ if (shouldSkipProjectDistribution(scope, config)) {
938
+ console.log();
939
+ printProjectDistributionDisabledMessage();
940
+ }
836
941
  }
837
942
  catch (error) {
838
943
  if (error instanceof Error) {
@@ -962,7 +1067,9 @@ hookRoot.action(async (options) => {
962
1067
  return;
963
1068
  console.log();
964
1069
  printActiveSelection('hooks', selection.enabled);
965
- const out = distributeHooks(scope, config.applications.enabled, new Set(config.applications.assume_installed));
1070
+ const out = distributeHooks(scope, config.applications.enabled, new Set(config.applications.assume_installed), {
1071
+ projectMode: getScopedProjectMode(scope, config),
1072
+ });
966
1073
  if (out.results.length > 0) {
967
1074
  console.log();
968
1075
  printDistributionResults({
@@ -972,6 +1079,10 @@ hookRoot.action(async (options) => {
972
1079
  getPath: (result) => 'filePath' in result ? result.filePath : result.targetDir,
973
1080
  });
974
1081
  }
1082
+ if (shouldSkipProjectDistribution(scope, config)) {
1083
+ console.log();
1084
+ printProjectDistributionDisabledMessage();
1085
+ }
975
1086
  }
976
1087
  catch (error) {
977
1088
  if (error instanceof Error) {
@@ -1118,7 +1229,7 @@ hookRoot
1118
1229
  * Show summary of enabled/disabled servers and applied agents
1119
1230
  */
1120
1231
  function showSummary(selectedServers, scope) {
1121
- const mcpConfig = loadMcpConfig();
1232
+ const mcpConfig = loadMcpConfigWithPlugins(scope);
1122
1233
  const allServers = Object.keys(mcpConfig.mcpServers);
1123
1234
  const enabledServers = selectedServers;
1124
1235
  const disabledServers = allServers.filter((s) => !selectedServers.includes(s));
@@ -1235,9 +1346,10 @@ pluginRoot
1235
1346
  function pluginEnableAction(id, options) {
1236
1347
  try {
1237
1348
  const scope = resolveScope(options);
1238
- const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
1239
- if (config.plugins.enabled.includes(id)) {
1240
- console.log(chalk.yellow(`⚠ Plugin "${id}" is already enabled.`));
1349
+ const layerOpts = scopeToLayerOptions(scope);
1350
+ const layer = loadWritableConfigLayer(layerOpts);
1351
+ if (layer.config.plugins?.enabled?.includes(id)) {
1352
+ console.log(chalk.yellow(`⚠ Plugin "${id}" is already enabled in this config layer.`));
1241
1353
  return;
1242
1354
  }
1243
1355
  const index = buildPluginIndex(scope);
@@ -1246,13 +1358,13 @@ function pluginEnableAction(id, options) {
1246
1358
  console.log(chalk.dim(' Run `asb plugin list` to see available plugins.'));
1247
1359
  process.exit(1);
1248
1360
  }
1249
- updateConfigLayer((layer) => ({
1250
- ...layer,
1361
+ updateConfigLayer((l) => ({
1362
+ ...l,
1251
1363
  plugins: {
1252
- ...(layer.plugins ?? {}),
1253
- enabled: [...(layer.plugins?.enabled ?? []), id],
1364
+ ...(l.plugins ?? {}),
1365
+ enabled: [...(l.plugins?.enabled ?? []), id],
1254
1366
  },
1255
- }), scopeToLayerOptions(scope));
1367
+ }), layerOpts);
1256
1368
  console.log(chalk.green(`✓ Plugin "${id}" enabled.`));
1257
1369
  }
1258
1370
  catch (error) {
@@ -1265,18 +1377,19 @@ function pluginEnableAction(id, options) {
1265
1377
  function pluginRemoveAction(id, options, verb) {
1266
1378
  try {
1267
1379
  const scope = resolveScope(options);
1268
- const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
1269
- if (!config.plugins.enabled.includes(id)) {
1270
- console.log(chalk.yellow(`⚠ Plugin "${id}" is not enabled.`));
1380
+ const layerOpts = scopeToLayerOptions(scope);
1381
+ const layer = loadWritableConfigLayer(layerOpts);
1382
+ if (!layer.config.plugins?.enabled?.includes(id)) {
1383
+ console.log(chalk.yellow(`⚠ Plugin "${id}" is not enabled in this config layer.`));
1271
1384
  return;
1272
1385
  }
1273
- updateConfigLayer((layer) => ({
1274
- ...layer,
1386
+ updateConfigLayer((l) => ({
1387
+ ...l,
1275
1388
  plugins: {
1276
- ...(layer.plugins ?? {}),
1277
- enabled: (layer.plugins?.enabled ?? []).filter((x) => x !== id),
1389
+ ...(l.plugins ?? {}),
1390
+ enabled: (l.plugins?.enabled ?? []).filter((x) => x !== id),
1278
1391
  },
1279
- }), scopeToLayerOptions(scope));
1392
+ }), layerOpts);
1280
1393
  console.log(chalk.green(`✓ Plugin "${id}" ${verb}.`));
1281
1394
  }
1282
1395
  catch (error) {
@@ -1532,7 +1645,7 @@ mktRoot
1532
1645
  }
1533
1646
  });
1534
1647
  mktRoot.action(() => {
1535
- mktRoot.commands.find((c) => c.name() === 'list')?.parse(process.argv);
1648
+ mktRoot.outputHelp();
1536
1649
  });
1537
1650
  // ── Removed: `asb source` ──────────────────────────────────────────
1538
1651
  program