agent-switchboard 0.2.3 → 0.3.1

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 (104) hide show
  1. package/README.md +29 -28
  2. package/dist/agents/registry.d.ts +0 -6
  3. package/dist/agents/registry.js +0 -13
  4. package/dist/agents/registry.js.map +1 -1
  5. package/dist/commands/distribution.d.ts +3 -4
  6. package/dist/commands/distribution.js +18 -116
  7. package/dist/commands/distribution.js.map +1 -1
  8. package/dist/config/application-config.d.ts +1 -1
  9. package/dist/config/application-config.js +2 -10
  10. package/dist/config/application-config.js.map +1 -1
  11. package/dist/config/mcp-config.js +1 -3
  12. package/dist/config/mcp-config.js.map +1 -1
  13. package/dist/config/paths.d.ts +11 -7
  14. package/dist/config/paths.js +29 -9
  15. package/dist/config/paths.js.map +1 -1
  16. package/dist/config/schemas.d.ts +331 -43
  17. package/dist/config/schemas.js +109 -15
  18. package/dist/config/schemas.js.map +1 -1
  19. package/dist/config/switchboard-config.js +0 -1
  20. package/dist/config/switchboard-config.js.map +1 -1
  21. package/dist/extensions/api.d.ts +49 -0
  22. package/dist/extensions/api.js +41 -0
  23. package/dist/extensions/api.js.map +1 -0
  24. package/dist/extensions/loader.d.ts +17 -0
  25. package/dist/extensions/loader.js +73 -0
  26. package/dist/extensions/loader.js.map +1 -0
  27. package/dist/hooks/distribution.js +1 -16
  28. package/dist/hooks/distribution.js.map +1 -1
  29. package/dist/index.js +77 -123
  30. package/dist/index.js.map +1 -1
  31. package/dist/library/distribute-bundle.js +1 -19
  32. package/dist/library/distribute-bundle.js.map +1 -1
  33. package/dist/library/fs.d.ts +12 -0
  34. package/dist/library/fs.js +81 -0
  35. package/dist/library/fs.js.map +1 -1
  36. package/dist/library/sources.d.ts +6 -1
  37. package/dist/library/sources.js +73 -16
  38. package/dist/library/sources.js.map +1 -1
  39. package/dist/rules/agents.d.ts +0 -4
  40. package/dist/rules/agents.js +0 -10
  41. package/dist/rules/agents.js.map +1 -1
  42. package/dist/rules/distribution.d.ts +1 -2
  43. package/dist/rules/distribution.js +20 -79
  44. package/dist/rules/distribution.js.map +1 -1
  45. package/dist/skills/distribution.d.ts +0 -22
  46. package/dist/skills/distribution.js +70 -185
  47. package/dist/skills/distribution.js.map +1 -1
  48. package/dist/skills/importer.js +2 -33
  49. package/dist/skills/importer.js.map +1 -1
  50. package/dist/subagents/codex-distribute.d.ts +13 -0
  51. package/dist/subagents/codex-distribute.js +273 -0
  52. package/dist/subagents/codex-distribute.js.map +1 -0
  53. package/dist/subagents/distribution.d.ts +3 -4
  54. package/dist/subagents/distribution.js +30 -410
  55. package/dist/subagents/distribution.js.map +1 -1
  56. package/dist/targets/builtin/claude-code.d.ts +2 -0
  57. package/dist/targets/builtin/claude-code.js +73 -0
  58. package/dist/targets/builtin/claude-code.js.map +1 -0
  59. package/dist/targets/builtin/claude-desktop.d.ts +2 -0
  60. package/dist/targets/builtin/claude-desktop.js +14 -0
  61. package/dist/targets/builtin/claude-desktop.js.map +1 -0
  62. package/dist/targets/builtin/codex.d.ts +3 -0
  63. package/dist/targets/builtin/codex.js +57 -0
  64. package/dist/targets/builtin/codex.js.map +1 -0
  65. package/dist/targets/builtin/common.d.ts +22 -0
  66. package/dist/targets/builtin/common.js +55 -0
  67. package/dist/targets/builtin/common.js.map +1 -0
  68. package/dist/targets/builtin/cursor.d.ts +2 -0
  69. package/dist/targets/builtin/cursor.js +95 -0
  70. package/dist/targets/builtin/cursor.js.map +1 -0
  71. package/dist/targets/builtin/gemini.d.ts +2 -0
  72. package/dist/targets/builtin/gemini.js +62 -0
  73. package/dist/targets/builtin/gemini.js.map +1 -0
  74. package/dist/targets/builtin/index.d.ts +10 -0
  75. package/dist/targets/builtin/index.js +19 -0
  76. package/dist/targets/builtin/index.js.map +1 -0
  77. package/dist/targets/builtin/opencode.d.ts +2 -0
  78. package/dist/targets/builtin/opencode.js +67 -0
  79. package/dist/targets/builtin/opencode.js.map +1 -0
  80. package/dist/targets/builtin/trae.d.ts +3 -0
  81. package/dist/targets/builtin/trae.js +56 -0
  82. package/dist/targets/builtin/trae.js.map +1 -0
  83. package/dist/targets/dsl/compiler.d.ts +13 -0
  84. package/dist/targets/dsl/compiler.js +215 -0
  85. package/dist/targets/dsl/compiler.js.map +1 -0
  86. package/dist/targets/dsl/index.d.ts +2 -0
  87. package/dist/targets/dsl/index.js +3 -0
  88. package/dist/targets/dsl/index.js.map +1 -0
  89. package/dist/targets/dsl/transforms.d.ts +77 -0
  90. package/dist/targets/dsl/transforms.js +159 -0
  91. package/dist/targets/dsl/transforms.js.map +1 -0
  92. package/dist/targets/init.d.ts +14 -0
  93. package/dist/targets/init.js +28 -0
  94. package/dist/targets/init.js.map +1 -0
  95. package/dist/targets/registry.d.ts +38 -0
  96. package/dist/targets/registry.js +101 -0
  97. package/dist/targets/registry.js.map +1 -0
  98. package/dist/targets/types.d.ts +93 -0
  99. package/dist/targets/types.js +28 -0
  100. package/dist/targets/types.js.map +1 -0
  101. package/dist/ui/plugin-ui.d.ts +1 -1
  102. package/dist/ui/plugin-ui.js +5 -12
  103. package/dist/ui/plugin-ui.js.map +1 -1
  104. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -10,7 +10,6 @@ import { confirm } from '@inquirer/prompts';
10
10
  import chalk from 'chalk';
11
11
  import { Command } from 'commander';
12
12
  import ora from 'ora';
13
- import { getAgentById } from './agents/registry.js';
14
13
  import { distributeCommands } from './commands/distribution.js';
15
14
  import { importCommandFromFile } from './commands/importer.js';
16
15
  import { buildCommandInventory } from './commands/inventory.js';
@@ -21,12 +20,11 @@ import { getAgentsDir, getAgentsHome, getClaudeDir, getCodexDir, getCommandsDir,
21
20
  import { loadSwitchboardConfig, loadSwitchboardConfigWithLayers, } from './config/switchboard-config.js';
22
21
  import { distributeHooks } from './hooks/distribution.js';
23
22
  import { loadHookLibrary } from './hooks/library.js';
24
- import { ensureLibraryDirectories, writeFileSecure } from './library/fs.js';
23
+ import { copyDirRecursive, ensureLibraryDirectories, isDir, isFile, listFilesRecursively, writeFileSecure, } from './library/fs.js';
25
24
  import { addLocalSource, addRemoteSource, getSources, inferSourceName, isGitUrl, parseGitUrl, removeSource, updateRemoteSources, validateSourcePath, } from './library/sources.js';
26
25
  import { loadLibraryStateSection, loadMcpEnabledState, saveMcpEnabledState, } from './library/state.js';
27
26
  import { readMarketplace } from './marketplace/reader.js';
28
27
  import { buildPluginIndex } from './plugins/index.js';
29
- import { RULE_SUPPORTED_AGENTS } from './rules/agents.js';
30
28
  import { composeActiveRules } from './rules/composer.js';
31
29
  import { distributeRules, listIndirectAgents, listPerFileAgents, listUnsupportedAgents, } from './rules/distribution.js';
32
30
  import { buildRuleInventory } from './rules/inventory.js';
@@ -38,6 +36,8 @@ import { buildSkillInventory } from './skills/inventory.js';
38
36
  import { distributeSubagents } from './subagents/distribution.js';
39
37
  import { importSubagentFromFile } from './subagents/importer.js';
40
38
  import { buildSubagentInventory } from './subagents/inventory.js';
39
+ import { initTargets } from './targets/init.js';
40
+ import { filterInstalled, getTargetById, getTargetsForSection } from './targets/registry.js';
41
41
  import { showCommandSelector } from './ui/command-ui.js';
42
42
  import { showHookSelector } from './ui/hook-ui.js';
43
43
  import { showMcpServerUI } from './ui/mcp-ui.js';
@@ -75,6 +75,7 @@ program
75
75
  const scope = resolveScope(options);
76
76
  const loadOptions = scopeToLoadOptions(scope);
77
77
  const { config, layers } = loadSwitchboardConfigWithLayers(loadOptions);
78
+ await initTargets(config);
78
79
  console.log(chalk.yellow('⚠ Sync overwrites agent config without diff.'));
79
80
  console.log();
80
81
  if (options.update !== false) {
@@ -101,7 +102,14 @@ program
101
102
  activeLayers.push(shortenPath(layers.project.path));
102
103
  console.log(`${chalk.blue('Config:')} ${activeLayers.length > 0 ? chalk.dim(activeLayers.join(' + ')) : chalk.gray('no config files')}`);
103
104
  const appsLabel = config.applications.active.length > 0
104
- ? chalk.cyan(config.applications.active.join(', '))
105
+ ? config.applications.active
106
+ .map((id) => {
107
+ const t = getTargetById(id);
108
+ if (t?.isInstalled?.() === false)
109
+ return chalk.gray(`${id} (not installed)`);
110
+ return chalk.cyan(id);
111
+ })
112
+ .join(', ')
105
113
  : chalk.gray('none configured');
106
114
  console.log(`${chalk.blue('Apps:')} ${appsLabel}`);
107
115
  console.log();
@@ -110,26 +118,14 @@ program
110
118
  console.log(chalk.blue('Inventory:'));
111
119
  {
112
120
  const sections = ['mcp', 'rules', 'commands', 'agents', 'skills', 'hooks'];
113
- // Keep in sync with platform lists in each distribution module
114
- const sectionPlatforms = {
115
- mcp: [
116
- 'claude-code',
117
- 'claude-desktop',
118
- 'codex',
119
- 'cursor',
120
- 'gemini',
121
- 'opencode',
122
- 'trae',
123
- 'trae-cn',
124
- ],
125
- rules: ['claude-code', 'codex', 'cursor', 'gemini', 'opencode', 'trae', 'trae-cn'],
126
- commands: ['claude-code', 'codex', 'cursor', 'gemini', 'opencode'],
127
- agents: ['claude-code', 'opencode', 'cursor'],
128
- skills: cursorSkillsDeduped
129
- ? ['claude-code', 'codex', 'gemini', 'opencode', 'trae', 'trae-cn']
130
- : ['claude-code', 'codex', 'gemini', 'opencode', 'cursor', 'trae', 'trae-cn'],
131
- hooks: ['claude-code'],
132
- };
121
+ const sectionPlatforms = {};
122
+ for (const s of sections) {
123
+ let ids = filterInstalled(getTargetsForSection(s)).map((t) => t.id);
124
+ if (s === 'skills' && cursorSkillsDeduped) {
125
+ ids = ids.filter((id) => id !== 'cursor');
126
+ }
127
+ sectionPlatforms[s] = ids;
128
+ }
133
129
  const termWidth = process.stdout.columns || 80;
134
130
  const maxSectionLen = Math.max(...sections.map((s) => s.length));
135
131
  const maxCountLen = Math.max(...sections.map((s) => `(${config[s].enabled.length})`.length));
@@ -194,9 +190,7 @@ program
194
190
  // Show enabled plugins summary
195
191
  {
196
192
  const pluginIndex = buildPluginIndex();
197
- const enabledPluginRefs = Object.entries(config.plugins.enabled)
198
- .filter(([, v]) => v === true)
199
- .map(([k]) => k);
193
+ const enabledPluginRefs = config.plugins.enabled;
200
194
  if (enabledPluginRefs.length > 0) {
201
195
  const names = enabledPluginRefs
202
196
  .map((pid) => {
@@ -295,22 +289,6 @@ program
295
289
  process.exit(1);
296
290
  }
297
291
  });
298
- function isDir(p) {
299
- try {
300
- return fs.existsSync(p) && fs.statSync(p).isDirectory();
301
- }
302
- catch {
303
- return false;
304
- }
305
- }
306
- function isFile(p) {
307
- try {
308
- return fs.existsSync(p) && fs.statSync(p).isFile();
309
- }
310
- catch {
311
- return false;
312
- }
313
- }
314
292
  function resolveScope(input) {
315
293
  if (!input)
316
294
  return undefined;
@@ -377,26 +355,6 @@ function defaultSkillSourceDir(platform) {
377
355
  return path.join(getCursorDir(), 'skills');
378
356
  }
379
357
  }
380
- function listFilesRecursively(root, filterExts) {
381
- const out = [];
382
- const exts = new Set(filterExts.map((e) => e.toLowerCase()));
383
- const walk = (dir) => {
384
- const entries = fs.readdirSync(dir, { withFileTypes: true });
385
- for (const entry of entries) {
386
- const abs = path.join(dir, entry.name);
387
- if (entry.isDirectory()) {
388
- walk(abs);
389
- }
390
- else if (entry.isFile()) {
391
- const ext = path.extname(entry.name).toLowerCase();
392
- if (exts.has(ext))
393
- out.push(abs);
394
- }
395
- }
396
- };
397
- walk(root);
398
- return out;
399
- }
400
358
  async function confirmOverwrite(filePath, force) {
401
359
  if (!fs.existsSync(filePath))
402
360
  return true;
@@ -404,29 +362,6 @@ async function confirmOverwrite(filePath, force) {
404
362
  return true;
405
363
  return await confirm({ message: `File exists: ${filePath}. Overwrite?`, default: false });
406
364
  }
407
- function copyDirRecursive(src, dest) {
408
- fs.mkdirSync(dest, { recursive: true });
409
- const entries = fs.readdirSync(src, { withFileTypes: true });
410
- for (const entry of entries) {
411
- const srcPath = path.join(src, entry.name);
412
- const destPath = path.join(dest, entry.name);
413
- if (entry.isDirectory()) {
414
- copyDirRecursive(srcPath, destPath);
415
- }
416
- else {
417
- fs.copyFileSync(srcPath, destPath);
418
- // Preserve executable permissions
419
- try {
420
- const mode = fs.statSync(srcPath).mode;
421
- if (mode & 0o111)
422
- fs.chmodSync(destPath, mode & 0o777);
423
- }
424
- catch {
425
- // Ignore
426
- }
427
- }
428
- }
429
- }
430
365
  /**
431
366
  * Extract hooks from Claude Code's ~/.claude/settings.json and import as a
432
367
  * bundle into ~/.asb/hooks/. Copies referenced script files if they exist
@@ -662,7 +597,7 @@ ruleCommand
662
597
  console.log();
663
598
  printAgentSyncStatus({
664
599
  agentSync: inventory.state.agentSync,
665
- agents: RULE_SUPPORTED_AGENTS,
600
+ agents: getTargetsForSection('rules').map((t) => t.id),
666
601
  });
667
602
  const unsupportedAgents = listUnsupportedAgents();
668
603
  if (unsupportedAgents.length > 0) {
@@ -689,6 +624,7 @@ ruleCommand.action(async (options) => {
689
624
  try {
690
625
  const scope = resolveScope(options);
691
626
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
627
+ await initTargets(config);
692
628
  const selection = await showRuleSelector({ scope, pageSize: config.ui.page_size });
693
629
  if (!selection) {
694
630
  return;
@@ -777,6 +713,7 @@ commandRoot.action(async (options) => {
777
713
  try {
778
714
  const scope = resolveScope(options);
779
715
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
716
+ await initTargets(config);
780
717
  const selection = await showCommandSelector({ scope, pageSize: config.ui.page_size });
781
718
  if (!selection)
782
719
  return;
@@ -926,6 +863,7 @@ agentRoot.action(async (options) => {
926
863
  try {
927
864
  const scope = resolveScope(options);
928
865
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
866
+ await initTargets(config);
929
867
  const selection = await showSubagentSelector({ scope, pageSize: config.ui.page_size });
930
868
  if (!selection)
931
869
  return;
@@ -1073,6 +1011,7 @@ skillRoot.action(async (options) => {
1073
1011
  try {
1074
1012
  const scope = resolveScope(options);
1075
1013
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
1014
+ await initTargets(config);
1076
1015
  const selection = await showSkillSelector({ scope, pageSize: config.ui.page_size });
1077
1016
  if (!selection)
1078
1017
  return;
@@ -1374,6 +1313,7 @@ hookRoot
1374
1313
  async function applyToAgents(scope, enabledServerNames, options) {
1375
1314
  const mcpConfig = loadMcpConfigWithPlugins();
1376
1315
  const switchboardConfig = loadSwitchboardConfig(scopeToLoadOptions(scope));
1316
+ await initTargets(switchboardConfig);
1377
1317
  const useSpinner = options?.useSpinner ?? true;
1378
1318
  const results = [];
1379
1319
  if (switchboardConfig.applications.active.length === 0) {
@@ -1404,7 +1344,28 @@ async function applyToAgents(scope, enabledServerNames, options) {
1404
1344
  const activeSet = new Set(agentActiveServers);
1405
1345
  const enabledServers = Object.fromEntries(Object.entries(mcpConfig.mcpServers).filter(([name]) => activeSet.has(name)));
1406
1346
  const configToApply = { mcpServers: enabledServers };
1407
- const agent = getAgentById(agentId);
1347
+ const target = getTargetById(agentId);
1348
+ if (target?.isInstalled?.() === false) {
1349
+ persist(chalk.gray('○'), `${chalk.cyan(agentId)} ${chalk.gray('(not installed, skipped)')}`);
1350
+ results.push({
1351
+ application: agentId,
1352
+ filePath: '(not installed)',
1353
+ status: 'skipped',
1354
+ reason: 'not installed',
1355
+ });
1356
+ continue;
1357
+ }
1358
+ if (!target?.mcp) {
1359
+ persist(chalk.yellow('⚠'), `${chalk.cyan(agentId)} - no MCP handler (skipped)`);
1360
+ results.push({
1361
+ application: agentId,
1362
+ filePath: '(unknown)',
1363
+ status: 'skipped',
1364
+ reason: 'no MCP handler',
1365
+ });
1366
+ continue;
1367
+ }
1368
+ const mcpHandler = target.mcp;
1408
1369
  const readFileSafe = (p) => {
1409
1370
  try {
1410
1371
  return fs.readFileSync(p, 'utf-8');
@@ -1413,11 +1374,10 @@ async function applyToAgents(scope, enabledServerNames, options) {
1413
1374
  return null;
1414
1375
  }
1415
1376
  };
1416
- // Use project-level config when --project is specified
1417
- if (scope?.project && agent.applyProjectConfig) {
1418
- const projectPath = agent.projectConfigPath?.(scope.project) ?? 'project config';
1377
+ if (scope?.project && mcpHandler.applyProjectConfig) {
1378
+ const projectPath = mcpHandler.projectConfigPath?.(scope.project) ?? 'project config';
1419
1379
  const before = readFileSafe(projectPath);
1420
- agent.applyProjectConfig(scope.project, configToApply);
1380
+ mcpHandler.applyProjectConfig(scope.project, configToApply);
1421
1381
  const after = readFileSafe(projectPath);
1422
1382
  const changed = before !== after;
1423
1383
  persist(chalk.green('✓'), `${chalk.cyan(agentId)} ${chalk.dim(shortenPath(projectPath))}`);
@@ -1429,9 +1389,9 @@ async function applyToAgents(scope, enabledServerNames, options) {
1429
1389
  });
1430
1390
  }
1431
1391
  else {
1432
- const configPath = agent.configPath();
1392
+ const configPath = mcpHandler.configPath();
1433
1393
  const before = readFileSafe(configPath);
1434
- agent.applyConfig(configToApply);
1394
+ mcpHandler.applyConfig(configToApply);
1435
1395
  const after = readFileSafe(configPath);
1436
1396
  const changed = before !== after;
1437
1397
  persist(chalk.green('✓'), `${chalk.cyan(agentId)} ${chalk.dim(shortenPath(configPath))}`);
@@ -1527,21 +1487,22 @@ pluginRoot
1527
1487
  const index = buildPluginIndex();
1528
1488
  const scope = resolveScope(options);
1529
1489
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
1530
- const enabledMap = config.plugins.enabled;
1490
+ const enabledList = config.plugins.enabled;
1491
+ const enabledSet = new Set(enabledList);
1531
1492
  if (options.json) {
1532
1493
  console.log(JSON.stringify(index.plugins.map((p) => {
1533
1494
  const ref = p.meta.sourceKind === 'marketplace' ? `${p.id}@${p.meta.sourceName}` : p.id;
1534
1495
  return {
1535
1496
  id: p.id,
1536
1497
  ref,
1537
- enabled: enabledMap[ref] ?? null,
1498
+ enabled: enabledSet.has(ref),
1538
1499
  ...p.meta,
1539
1500
  components: Object.fromEntries(Object.entries(p.components).map(([k, v]) => [k, v.length])),
1540
1501
  };
1541
1502
  }), null, 2));
1542
1503
  return;
1543
1504
  }
1544
- printPluginList(index.plugins, enabledMap);
1505
+ printPluginList(index.plugins, enabledList);
1545
1506
  }
1546
1507
  catch (error) {
1547
1508
  if (error instanceof Error) {
@@ -1578,7 +1539,7 @@ function pluginEnableAction(id, options) {
1578
1539
  try {
1579
1540
  const scope = resolveScope(options);
1580
1541
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
1581
- if (config.plugins.enabled[id] === true) {
1542
+ if (config.plugins.enabled.includes(id)) {
1582
1543
  console.log(chalk.yellow(`⚠ Plugin "${id}" is already enabled.`));
1583
1544
  return;
1584
1545
  }
@@ -1592,7 +1553,7 @@ function pluginEnableAction(id, options) {
1592
1553
  ...layer,
1593
1554
  plugins: {
1594
1555
  ...(layer.plugins ?? {}),
1595
- enabled: { ...(layer.plugins?.enabled ?? {}), [id]: true },
1556
+ enabled: [...(layer.plugins?.enabled ?? []), id],
1596
1557
  },
1597
1558
  }), scopeToLoadOptions(scope));
1598
1559
  console.log(chalk.green(`✓ Plugin "${id}" enabled.`));
@@ -1608,19 +1569,15 @@ function pluginDisableAction(id, options) {
1608
1569
  try {
1609
1570
  const scope = resolveScope(options);
1610
1571
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
1611
- if (config.plugins.enabled[id] === undefined) {
1612
- console.log(chalk.yellow(`⚠ Plugin "${id}" is not installed.`));
1613
- return;
1614
- }
1615
- if (config.plugins.enabled[id] === false) {
1616
- console.log(chalk.yellow(`⚠ Plugin "${id}" is already disabled.`));
1572
+ if (!config.plugins.enabled.includes(id)) {
1573
+ console.log(chalk.yellow(`⚠ Plugin "${id}" is not enabled.`));
1617
1574
  return;
1618
1575
  }
1619
1576
  updateConfigLayer((layer) => ({
1620
1577
  ...layer,
1621
1578
  plugins: {
1622
1579
  ...(layer.plugins ?? {}),
1623
- enabled: { ...(layer.plugins?.enabled ?? {}), [id]: false },
1580
+ enabled: (layer.plugins?.enabled ?? []).filter((x) => x !== id),
1624
1581
  },
1625
1582
  }), scopeToLoadOptions(scope));
1626
1583
  console.log(chalk.green(`✓ Plugin "${id}" disabled.`));
@@ -1636,18 +1593,17 @@ function pluginUninstallAction(id, options) {
1636
1593
  try {
1637
1594
  const scope = resolveScope(options);
1638
1595
  const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
1639
- if (config.plugins.enabled[id] === undefined) {
1640
- console.log(chalk.yellow(`⚠ Plugin "${id}" is not installed.`));
1596
+ if (!config.plugins.enabled.includes(id)) {
1597
+ console.log(chalk.yellow(`⚠ Plugin "${id}" is not enabled.`));
1641
1598
  return;
1642
1599
  }
1643
- updateConfigLayer((layer) => {
1644
- const updated = { ...(layer.plugins?.enabled ?? {}) };
1645
- delete updated[id];
1646
- return {
1647
- ...layer,
1648
- plugins: { ...(layer.plugins ?? {}), enabled: updated },
1649
- };
1650
- }, scopeToLoadOptions(scope));
1600
+ updateConfigLayer((layer) => ({
1601
+ ...layer,
1602
+ plugins: {
1603
+ ...(layer.plugins ?? {}),
1604
+ enabled: (layer.plugins?.enabled ?? []).filter((x) => x !== id),
1605
+ },
1606
+ }), scopeToLoadOptions(scope));
1651
1607
  console.log(chalk.green(`✓ Plugin "${id}" uninstalled.`));
1652
1608
  }
1653
1609
  catch (error) {
@@ -1661,27 +1617,25 @@ const pluginScopeOpts = [
1661
1617
  ['-p, --profile <name>', 'Profile configuration to use'],
1662
1618
  ['--project <path>', 'Project directory containing .asb.toml'],
1663
1619
  ];
1664
- const enableCmd = pluginRoot
1665
- .command('enable <id>')
1666
- .description('Enable a plugin (set plugins.enabled to true)');
1620
+ const enableCmd = pluginRoot.command('enable <id>').description('Add a plugin to the enabled list');
1667
1621
  for (const [flag, desc] of pluginScopeOpts)
1668
1622
  enableCmd.option(flag, desc);
1669
1623
  enableCmd.action(pluginEnableAction);
1670
1624
  const installCmd = pluginRoot
1671
1625
  .command('install <id>')
1672
- .description('Enable a plugin (alias for enable)');
1626
+ .description('Add a plugin to the enabled list (alias for enable)');
1673
1627
  for (const [flag, desc] of pluginScopeOpts)
1674
1628
  installCmd.option(flag, desc);
1675
1629
  installCmd.action(pluginEnableAction);
1676
1630
  const disableCmd = pluginRoot
1677
1631
  .command('disable <id>')
1678
- .description('Disable a plugin (set plugins.enabled to false)');
1632
+ .description('Remove a plugin from the enabled list');
1679
1633
  for (const [flag, desc] of pluginScopeOpts)
1680
1634
  disableCmd.option(flag, desc);
1681
1635
  disableCmd.action(pluginDisableAction);
1682
1636
  const uninstallCmd = pluginRoot
1683
1637
  .command('uninstall <id>')
1684
- .description('Remove a plugin from plugins.enabled');
1638
+ .description('Remove a plugin from the enabled list (alias for disable)');
1685
1639
  for (const [flag, desc] of pluginScopeOpts)
1686
1640
  uninstallCmd.option(flag, desc);
1687
1641
  uninstallCmd.action(pluginUninstallAction);
@@ -1915,7 +1869,7 @@ program
1915
1869
  .action(() => {
1916
1870
  console.error(chalk.red('✗ `asb source` has been removed.\n' +
1917
1871
  ' Sources are now managed under `asb plugin marketplace`.\n' +
1918
- ' Config has moved from [library.sources] to [plugins.sources].\n' +
1872
+ ' Config has moved from [library.sources] to [plugins].\n' +
1919
1873
  ' Run `asb plugin marketplace --help` for usage.'));
1920
1874
  process.exit(1);
1921
1875
  });