agent-switchboard 0.1.29 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -46
- package/dist/commands/distribution.js +1 -1
- package/dist/commands/distribution.js.map +1 -1
- package/dist/commands/inventory.js +1 -1
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/library.d.ts +1 -1
- package/dist/commands/library.js +3 -3
- package/dist/commands/library.js.map +1 -1
- package/dist/config/application-config.d.ts +14 -3
- package/dist/config/application-config.js +61 -6
- package/dist/config/application-config.js.map +1 -1
- package/dist/config/mcp-config.d.ts +7 -0
- package/dist/config/mcp-config.js +25 -0
- package/dist/config/mcp-config.js.map +1 -1
- package/dist/config/schemas.d.ts +828 -273
- package/dist/config/schemas.js +62 -18
- package/dist/config/schemas.js.map +1 -1
- package/dist/hooks/distribution.js +1 -1
- package/dist/hooks/distribution.js.map +1 -1
- package/dist/hooks/library.d.ts +1 -1
- package/dist/hooks/library.js +3 -3
- package/dist/hooks/library.js.map +1 -1
- package/dist/index.js +323 -67
- package/dist/index.js.map +1 -1
- package/dist/library/sources.d.ts +3 -2
- package/dist/library/sources.js +16 -17
- package/dist/library/sources.js.map +1 -1
- package/dist/library/state.d.ts +6 -6
- package/dist/library/state.js +15 -15
- package/dist/library/state.js.map +1 -1
- package/dist/marketplace/plugin-loader.d.ts +3 -0
- package/dist/marketplace/plugin-loader.js +53 -2
- package/dist/marketplace/plugin-loader.js.map +1 -1
- package/dist/marketplace/reader.d.ts +22 -0
- package/dist/marketplace/reader.js +122 -17
- package/dist/marketplace/reader.js.map +1 -1
- package/dist/marketplace/schemas.d.ts +48 -12
- package/dist/marketplace/schemas.js +4 -1
- package/dist/marketplace/schemas.js.map +1 -1
- package/dist/marketplace/source-loader.d.ts +6 -6
- package/dist/marketplace/source-loader.js +8 -8
- package/dist/marketplace/source-loader.js.map +1 -1
- package/dist/plugins/index.d.ts +57 -0
- package/dist/plugins/index.js +304 -0
- package/dist/plugins/index.js.map +1 -0
- package/dist/rules/composer.js +4 -4
- package/dist/rules/composer.js.map +1 -1
- package/dist/rules/inventory.js +1 -1
- package/dist/rules/inventory.js.map +1 -1
- package/dist/rules/library.d.ts +1 -1
- package/dist/rules/library.js +19 -3
- package/dist/rules/library.js.map +1 -1
- package/dist/rules/schema.d.ts +3 -3
- package/dist/rules/schema.js +1 -1
- package/dist/rules/schema.js.map +1 -1
- package/dist/rules/state.js +12 -12
- package/dist/rules/state.js.map +1 -1
- package/dist/skills/distribution.js +5 -5
- package/dist/skills/distribution.js.map +1 -1
- package/dist/skills/inventory.js +1 -1
- package/dist/skills/inventory.js.map +1 -1
- package/dist/skills/library.d.ts +1 -1
- package/dist/skills/library.js +3 -3
- package/dist/skills/library.js.map +1 -1
- package/dist/subagents/distribution.js +2 -2
- package/dist/subagents/distribution.js.map +1 -1
- package/dist/subagents/inventory.js +1 -1
- package/dist/subagents/inventory.js.map +1 -1
- package/dist/subagents/library.d.ts +1 -1
- package/dist/subagents/library.js +3 -3
- package/dist/subagents/library.js.map +1 -1
- package/dist/ui/library-selector.d.ts +1 -1
- package/dist/ui/library-selector.js +7 -7
- package/dist/ui/library-selector.js.map +1 -1
- package/dist/ui/plugin-ui.d.ts +3 -0
- package/dist/ui/plugin-ui.js +77 -0
- package/dist/ui/plugin-ui.js.map +1 -0
- package/dist/ui/rule-ui.d.ts +1 -1
- package/dist/ui/rule-ui.js +4 -4
- package/dist/ui/rule-ui.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14,16 +14,18 @@ import { getAgentById } from './agents/registry.js';
|
|
|
14
14
|
import { distributeCommands } from './commands/distribution.js';
|
|
15
15
|
import { importCommandFromFile } from './commands/importer.js';
|
|
16
16
|
import { buildCommandInventory } from './commands/inventory.js';
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
17
|
+
import { resolveEffectiveSectionConfig } from './config/application-config.js';
|
|
18
|
+
import { updateConfigLayer } from './config/layered-config.js';
|
|
19
|
+
import { loadMcpConfig, loadMcpConfigWithPlugins, stripLegacyEnabledFlagsFromMcpJson, } from './config/mcp-config.js';
|
|
19
20
|
import { getAgentsDir, getAgentsHome, getClaudeDir, getCodexDir, getCommandsDir, getCursorDir, getGeminiDir, getHooksDir, getOpencodePath, getSkillsDir, getSourceCacheDir, } from './config/paths.js';
|
|
20
21
|
import { loadSwitchboardConfig, loadSwitchboardConfigWithLayers, } from './config/switchboard-config.js';
|
|
21
22
|
import { distributeHooks } from './hooks/distribution.js';
|
|
22
23
|
import { loadHookLibrary } from './hooks/library.js';
|
|
23
24
|
import { ensureLibraryDirectories, writeFileSecure } from './library/fs.js';
|
|
24
25
|
import { addLocalSource, addRemoteSource, getSources, inferSourceName, isGitUrl, parseGitUrl, removeSource, updateRemoteSources, validateSourcePath, } from './library/sources.js';
|
|
25
|
-
import { loadLibraryStateSection,
|
|
26
|
+
import { loadLibraryStateSection, loadMcpEnabledState, saveMcpEnabledState, } from './library/state.js';
|
|
26
27
|
import { readMarketplace } from './marketplace/reader.js';
|
|
28
|
+
import { buildPluginIndex } from './plugins/index.js';
|
|
27
29
|
import { RULE_SUPPORTED_AGENTS } from './rules/agents.js';
|
|
28
30
|
import { composeActiveRules } from './rules/composer.js';
|
|
29
31
|
import { distributeRules, listIndirectAgents, listPerFileAgents, listUnsupportedAgents, } from './rules/distribution.js';
|
|
@@ -39,6 +41,7 @@ import { buildSubagentInventory } from './subagents/inventory.js';
|
|
|
39
41
|
import { showCommandSelector } from './ui/command-ui.js';
|
|
40
42
|
import { showHookSelector } from './ui/hook-ui.js';
|
|
41
43
|
import { showMcpServerUI } from './ui/mcp-ui.js';
|
|
44
|
+
import { printPluginInfo, printPluginList } from './ui/plugin-ui.js';
|
|
42
45
|
import { showRuleSelector } from './ui/rule-ui.js';
|
|
43
46
|
import { showSkillSelector } from './ui/skill-ui.js';
|
|
44
47
|
import { showSubagentSelector } from './ui/subagent-ui.js';
|
|
@@ -103,7 +106,7 @@ program
|
|
|
103
106
|
console.log(`${chalk.blue('Apps:')} ${appsLabel}`);
|
|
104
107
|
console.log();
|
|
105
108
|
const cursorSkillsDeduped = config.applications.active.includes('claude-code') &&
|
|
106
|
-
|
|
109
|
+
resolveEffectiveSectionConfig('skills', 'claude-code', scope).enabled.length > 0;
|
|
107
110
|
console.log(chalk.blue('Inventory:'));
|
|
108
111
|
{
|
|
109
112
|
const sections = ['mcp', 'rules', 'commands', 'agents', 'skills', 'hooks'];
|
|
@@ -120,7 +123,7 @@ program
|
|
|
120
123
|
};
|
|
121
124
|
const termWidth = process.stdout.columns || 80;
|
|
122
125
|
const maxSectionLen = Math.max(...sections.map((s) => s.length));
|
|
123
|
-
const maxCountLen = Math.max(...sections.map((s) => `(${config[s].
|
|
126
|
+
const maxCountLen = Math.max(...sections.map((s) => `(${config[s].enabled.length})`.length));
|
|
124
127
|
const prefixPlainLen = 2 + maxSectionLen + 1 + maxCountLen + 2;
|
|
125
128
|
const fitPreview = (ids, maxWidth) => {
|
|
126
129
|
if (ids.length === 0)
|
|
@@ -147,13 +150,13 @@ program
|
|
|
147
150
|
return text;
|
|
148
151
|
};
|
|
149
152
|
for (const section of sections) {
|
|
150
|
-
const globalActive = config[section].
|
|
153
|
+
const globalActive = config[section].enabled;
|
|
151
154
|
const globalCount = globalActive.length;
|
|
152
155
|
const supported = new Set(sectionPlatforms[section] ?? []);
|
|
153
156
|
const applicableApps = config.applications.active.filter((id) => supported.has(id));
|
|
154
157
|
const effectiveByApp = new Map();
|
|
155
158
|
for (const appId of applicableApps) {
|
|
156
|
-
effectiveByApp.set(appId,
|
|
159
|
+
effectiveByApp.set(appId, resolveEffectiveSectionConfig(section, appId, scope).enabled);
|
|
157
160
|
}
|
|
158
161
|
const perAppParts = applicableApps.map((appId) => {
|
|
159
162
|
const eff = effectiveByApp.get(appId) ?? [];
|
|
@@ -179,6 +182,25 @@ program
|
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
}
|
|
185
|
+
// Show enabled plugins summary
|
|
186
|
+
{
|
|
187
|
+
const pluginIndex = buildPluginIndex();
|
|
188
|
+
const enabledPluginRefs = Object.entries(config.plugins.enabled)
|
|
189
|
+
.filter(([, v]) => v === true)
|
|
190
|
+
.map(([k]) => k);
|
|
191
|
+
if (enabledPluginRefs.length > 0) {
|
|
192
|
+
const names = enabledPluginRefs
|
|
193
|
+
.map((pid) => {
|
|
194
|
+
const p = pluginIndex.get(pid);
|
|
195
|
+
return p ? pid : chalk.strikethrough(pid);
|
|
196
|
+
})
|
|
197
|
+
.join(', ');
|
|
198
|
+
console.log(` ${chalk.magenta('plugins')} ${chalk.gray(`(${enabledPluginRefs.length})`)} ${names}`);
|
|
199
|
+
}
|
|
200
|
+
else if (pluginIndex.plugins.length > 0) {
|
|
201
|
+
console.log(` ${chalk.magenta('plugins')} ${chalk.gray('(0)')} ${chalk.gray(`${pluginIndex.plugins.length} available`)}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
182
204
|
console.log();
|
|
183
205
|
const notes = ['rules, skills also distribute to gemini'];
|
|
184
206
|
if (cursorSkillsDeduped)
|
|
@@ -506,7 +528,7 @@ async function importHooksFromClaudeCode(opts) {
|
|
|
506
528
|
fs.writeFileSync(path.join(bundleDir, 'hook.json'), `${JSON.stringify(hookJson, null, 2)}\n`, 'utf-8');
|
|
507
529
|
const eventCount = Object.keys(rewrittenHooks).length;
|
|
508
530
|
console.log(`\n${chalk.green('✓')} Imported ${eventCount} event(s) + ${scriptsCopied} script(s) → ${chalk.dim(bundleDir)}`);
|
|
509
|
-
console.log(chalk.gray(' Activate with: asb hook (interactive) or add to [hooks].
|
|
531
|
+
console.log(chalk.gray(' Activate with: asb hook (interactive) or add to [hooks].enabled in config.toml'));
|
|
510
532
|
}
|
|
511
533
|
program
|
|
512
534
|
.command('mcp')
|
|
@@ -519,21 +541,21 @@ program
|
|
|
519
541
|
const { config, layers } = loadSwitchboardConfigWithLayers(scopeToLoadOptions(scope));
|
|
520
542
|
const mcpConfig = loadMcpConfig();
|
|
521
543
|
// Determine initial selection:
|
|
522
|
-
// - If writing to project/profile scope, only use that layer's explicit [mcp].
|
|
523
|
-
// - If writing to user scope and [mcp].
|
|
544
|
+
// - If writing to project/profile scope, only use that layer's explicit [mcp].enabled (no fallback).
|
|
545
|
+
// - If writing to user scope and [mcp].enabled is missing, fall back to legacy mcp.json enabled flags.
|
|
524
546
|
let currentActive = [];
|
|
525
547
|
if (scope?.project) {
|
|
526
|
-
const
|
|
527
|
-
currentActive = Array.isArray(
|
|
548
|
+
const projectEnabled = layers.project?.config?.mcp?.enabled;
|
|
549
|
+
currentActive = Array.isArray(projectEnabled) ? [...projectEnabled] : [];
|
|
528
550
|
}
|
|
529
551
|
else if (scope?.profile) {
|
|
530
|
-
const
|
|
531
|
-
currentActive = Array.isArray(
|
|
552
|
+
const profileEnabled = layers.profile?.config?.mcp?.enabled;
|
|
553
|
+
currentActive = Array.isArray(profileEnabled) ? [...profileEnabled] : [];
|
|
532
554
|
}
|
|
533
555
|
else {
|
|
534
|
-
const
|
|
535
|
-
if (Array.isArray(
|
|
536
|
-
currentActive = [...
|
|
556
|
+
const userEnabled = layers.user.config?.mcp?.enabled;
|
|
557
|
+
if (Array.isArray(userEnabled)) {
|
|
558
|
+
currentActive = [...userEnabled];
|
|
537
559
|
}
|
|
538
560
|
else {
|
|
539
561
|
// Read from legacy mcp.json enabled flags
|
|
@@ -549,13 +571,13 @@ program
|
|
|
549
571
|
});
|
|
550
572
|
// Step 2: Save active state to config.toml (appropriate layer based on scope)
|
|
551
573
|
const spinner = ora('Updating MCP configuration...').start();
|
|
552
|
-
|
|
574
|
+
saveMcpEnabledState(selectedServers, scope);
|
|
553
575
|
const layerName = scope?.project
|
|
554
576
|
? 'project .asb.toml'
|
|
555
577
|
: scope?.profile
|
|
556
578
|
? `profile ${scope.profile}.toml`
|
|
557
579
|
: 'config.toml';
|
|
558
|
-
spinner.succeed(chalk.green(`✓ Updated ${layerName} [mcp].
|
|
580
|
+
spinner.succeed(chalk.green(`✓ Updated ${layerName} [mcp].enabled`));
|
|
559
581
|
// After user-level write, strip legacy enabled flags from mcp.json (definition-only file).
|
|
560
582
|
if (!scope?.profile && !scope?.project) {
|
|
561
583
|
const cleaned = stripLegacyEnabledFlagsFromMcpJson();
|
|
@@ -594,7 +616,7 @@ ruleCommand
|
|
|
594
616
|
console.log(JSON.stringify({
|
|
595
617
|
snippets: inventory.snippets,
|
|
596
618
|
agentSync: inventory.state.agentSync,
|
|
597
|
-
activeOrder: inventory.state.
|
|
619
|
+
activeOrder: inventory.state.enabled,
|
|
598
620
|
}, null, 2));
|
|
599
621
|
return;
|
|
600
622
|
}
|
|
@@ -665,34 +687,34 @@ ruleCommand.action(async (options) => {
|
|
|
665
687
|
const rules = loadRuleLibrary();
|
|
666
688
|
const ruleMap = new Map(rules.map((rule) => [rule.id, rule]));
|
|
667
689
|
const previousState = loadRuleState(scope);
|
|
668
|
-
const
|
|
690
|
+
const desiredEnabled = selection.enabled;
|
|
669
691
|
const arraysEqual = (a, b) => {
|
|
670
692
|
if (a.length !== b.length)
|
|
671
693
|
return false;
|
|
672
694
|
return a.every((value, index) => value === b[index]);
|
|
673
695
|
};
|
|
674
|
-
const selectionChanged = !arraysEqual(previousState.
|
|
696
|
+
const selectionChanged = !arraysEqual(previousState.enabled, desiredEnabled);
|
|
675
697
|
const updatedState = updateRuleState((current) => {
|
|
676
698
|
if (!selectionChanged) {
|
|
677
699
|
return current;
|
|
678
700
|
}
|
|
679
701
|
return {
|
|
680
702
|
...current,
|
|
681
|
-
|
|
703
|
+
enabled: desiredEnabled,
|
|
682
704
|
agentSync: {},
|
|
683
705
|
};
|
|
684
706
|
}, scope);
|
|
685
707
|
if (!selectionChanged) {
|
|
686
|
-
console.log(chalk.gray('\nNo changes to
|
|
708
|
+
console.log(chalk.gray('\nNo changes to enabled rules. Refreshing agent files...'));
|
|
687
709
|
}
|
|
688
710
|
else {
|
|
689
711
|
console.log();
|
|
690
|
-
if (updatedState.
|
|
691
|
-
console.log(chalk.yellow('⚠
|
|
712
|
+
if (updatedState.enabled.length === 0) {
|
|
713
|
+
console.log(chalk.yellow('⚠ Enabled rule set cleared. Agents will receive empty instructions on next sync.'));
|
|
692
714
|
}
|
|
693
715
|
else {
|
|
694
|
-
console.log(chalk.green('✓ Updated
|
|
695
|
-
for (const [index, id] of updatedState.
|
|
716
|
+
console.log(chalk.green('✓ Updated enabled rule order:'));
|
|
717
|
+
for (const [index, id] of updatedState.enabled.entries()) {
|
|
696
718
|
const rule = ruleMap.get(id);
|
|
697
719
|
const title = rule?.metadata.title?.trim();
|
|
698
720
|
const name = title && title.length > 0 ? title : id;
|
|
@@ -750,7 +772,7 @@ commandRoot.action(async (options) => {
|
|
|
750
772
|
if (!selection)
|
|
751
773
|
return;
|
|
752
774
|
console.log();
|
|
753
|
-
printActiveSelection('commands', selection.
|
|
775
|
+
printActiveSelection('commands', selection.enabled);
|
|
754
776
|
const out = distributeCommands(scope);
|
|
755
777
|
if (out.results.length > 0) {
|
|
756
778
|
console.log();
|
|
@@ -844,7 +866,7 @@ commandRoot
|
|
|
844
866
|
console.log(JSON.stringify({
|
|
845
867
|
entries: inventory.entries,
|
|
846
868
|
agentSync: inventory.state.agentSync,
|
|
847
|
-
|
|
869
|
+
enabled: inventory.state.enabled,
|
|
848
870
|
}, null, 2));
|
|
849
871
|
return;
|
|
850
872
|
}
|
|
@@ -899,7 +921,7 @@ agentRoot.action(async (options) => {
|
|
|
899
921
|
if (!selection)
|
|
900
922
|
return;
|
|
901
923
|
console.log();
|
|
902
|
-
printActiveSelection('agents', selection.
|
|
924
|
+
printActiveSelection('agents', selection.enabled);
|
|
903
925
|
const out = distributeSubagents(scope);
|
|
904
926
|
if (out.results.length > 0) {
|
|
905
927
|
console.log();
|
|
@@ -990,7 +1012,7 @@ agentRoot
|
|
|
990
1012
|
console.log(JSON.stringify({
|
|
991
1013
|
entries: inventory.entries,
|
|
992
1014
|
agentSync: inventory.state.agentSync,
|
|
993
|
-
activeOrder: inventory.state.
|
|
1015
|
+
activeOrder: inventory.state.enabled,
|
|
994
1016
|
}, null, 2));
|
|
995
1017
|
return;
|
|
996
1018
|
}
|
|
@@ -1046,7 +1068,7 @@ skillRoot.action(async (options) => {
|
|
|
1046
1068
|
if (!selection)
|
|
1047
1069
|
return;
|
|
1048
1070
|
console.log();
|
|
1049
|
-
printActiveSelection('skills', selection.
|
|
1071
|
+
printActiveSelection('skills', selection.enabled);
|
|
1050
1072
|
const out = distributeSkills(scope, {
|
|
1051
1073
|
useAgentsDir: config.distribution.use_agents_dir,
|
|
1052
1074
|
});
|
|
@@ -1081,7 +1103,7 @@ skillRoot
|
|
|
1081
1103
|
console.log(JSON.stringify({
|
|
1082
1104
|
entries: inventory.entries,
|
|
1083
1105
|
agentSync: inventory.state.agentSync,
|
|
1084
|
-
|
|
1106
|
+
enabled: inventory.state.enabled,
|
|
1085
1107
|
}, null, 2));
|
|
1086
1108
|
return;
|
|
1087
1109
|
}
|
|
@@ -1187,7 +1209,7 @@ hookRoot.action(async (options) => {
|
|
|
1187
1209
|
if (!selection)
|
|
1188
1210
|
return;
|
|
1189
1211
|
console.log();
|
|
1190
|
-
printActiveSelection('hooks', selection.
|
|
1212
|
+
printActiveSelection('hooks', selection.enabled);
|
|
1191
1213
|
const out = distributeHooks(scope);
|
|
1192
1214
|
if (out.results.length > 0) {
|
|
1193
1215
|
console.log();
|
|
@@ -1217,7 +1239,7 @@ hookRoot
|
|
|
1217
1239
|
const scope = resolveScope(options);
|
|
1218
1240
|
const entries = loadHookLibrary();
|
|
1219
1241
|
const state = loadLibraryStateSection('hooks', scope);
|
|
1220
|
-
const
|
|
1242
|
+
const enabledSet = new Set(state.enabled);
|
|
1221
1243
|
if (options.json) {
|
|
1222
1244
|
console.log(JSON.stringify({
|
|
1223
1245
|
entries: entries.map((e) => ({
|
|
@@ -1225,9 +1247,9 @@ hookRoot
|
|
|
1225
1247
|
name: e.name,
|
|
1226
1248
|
description: e.description,
|
|
1227
1249
|
events: Object.keys(e.hooks),
|
|
1228
|
-
|
|
1250
|
+
enabled: enabledSet.has(e.id),
|
|
1229
1251
|
})),
|
|
1230
|
-
|
|
1252
|
+
enabled: state.enabled,
|
|
1231
1253
|
}, null, 2));
|
|
1232
1254
|
return;
|
|
1233
1255
|
}
|
|
@@ -1238,7 +1260,7 @@ hookRoot
|
|
|
1238
1260
|
console.log(chalk.blue('Hooks:'));
|
|
1239
1261
|
const header = ['ID', 'Active', 'Name', 'Events', 'Description'];
|
|
1240
1262
|
const rows = entries.map((e) => {
|
|
1241
|
-
const active =
|
|
1263
|
+
const active = enabledSet.has(e.id);
|
|
1242
1264
|
const activePlain = active ? 'yes' : 'no';
|
|
1243
1265
|
const events = Object.keys(e.hooks).join(', ');
|
|
1244
1266
|
const desc = e.description ?? '—';
|
|
@@ -1341,7 +1363,7 @@ hookRoot
|
|
|
1341
1363
|
}
|
|
1342
1364
|
});
|
|
1343
1365
|
async function applyToAgents(scope, enabledServerNames, options) {
|
|
1344
|
-
const mcpConfig =
|
|
1366
|
+
const mcpConfig = loadMcpConfigWithPlugins();
|
|
1345
1367
|
const switchboardConfig = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1346
1368
|
const useSpinner = options?.useSpinner ?? true;
|
|
1347
1369
|
const results = [];
|
|
@@ -1355,7 +1377,7 @@ async function applyToAgents(scope, enabledServerNames, options) {
|
|
|
1355
1377
|
return results;
|
|
1356
1378
|
}
|
|
1357
1379
|
// Global MCP servers list (from UI selection or config)
|
|
1358
|
-
const globalMcpServers = enabledServerNames ??
|
|
1380
|
+
const globalMcpServers = enabledServerNames ?? loadMcpEnabledState(scope);
|
|
1359
1381
|
for (const agentId of switchboardConfig.applications.active) {
|
|
1360
1382
|
const spinner = useSpinner ? ora({ indent: 2 }).start(`Applying to ${agentId}...`) : null;
|
|
1361
1383
|
const persist = (symbol, text) => {
|
|
@@ -1364,11 +1386,11 @@ async function applyToAgents(scope, enabledServerNames, options) {
|
|
|
1364
1386
|
spinner.stopAndPersist({ symbol: ` ${symbol}`, text });
|
|
1365
1387
|
};
|
|
1366
1388
|
try {
|
|
1367
|
-
const agentMcpConfig =
|
|
1389
|
+
const agentMcpConfig = resolveEffectiveSectionConfig('mcp', agentId, scope);
|
|
1368
1390
|
// If user selected servers via UI, use that as base; otherwise use per-agent resolved config
|
|
1369
1391
|
const agentActiveServers = enabledServerNames
|
|
1370
|
-
? agentMcpConfig.
|
|
1371
|
-
: agentMcpConfig.
|
|
1392
|
+
? agentMcpConfig.enabled.filter((s) => globalMcpServers.includes(s))
|
|
1393
|
+
: agentMcpConfig.enabled;
|
|
1372
1394
|
// Filter to only enabled servers for this agent
|
|
1373
1395
|
const activeSet = new Set(agentActiveServers);
|
|
1374
1396
|
const enabledServers = Object.fromEntries(Object.entries(mcpConfig.mcpServers).filter(([name]) => activeSet.has(name)));
|
|
@@ -1475,12 +1497,193 @@ function printMarketplaceSummary(localPath) {
|
|
|
1475
1497
|
console.log(chalk.yellow(` ⚠ Failed to read marketplace: ${msg}`));
|
|
1476
1498
|
}
|
|
1477
1499
|
}
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
.
|
|
1481
|
-
|
|
1500
|
+
// ── Plugin management ──────────────────────────────────────────────
|
|
1501
|
+
const pluginRoot = program
|
|
1502
|
+
.command('plugin')
|
|
1503
|
+
.description('Manage plugins: interactive selection, install/uninstall, marketplace sources')
|
|
1504
|
+
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1505
|
+
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
1506
|
+
pluginRoot.action((_options) => {
|
|
1507
|
+
pluginRoot.outputHelp();
|
|
1508
|
+
});
|
|
1509
|
+
pluginRoot
|
|
1510
|
+
.command('list')
|
|
1511
|
+
.alias('ls')
|
|
1512
|
+
.description('List all discoverable plugins from configured sources')
|
|
1513
|
+
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1514
|
+
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
1515
|
+
.option('--json', 'Output as JSON')
|
|
1516
|
+
.action((options) => {
|
|
1517
|
+
try {
|
|
1518
|
+
const index = buildPluginIndex();
|
|
1519
|
+
const scope = resolveScope(options);
|
|
1520
|
+
const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1521
|
+
const enabledMap = config.plugins.enabled;
|
|
1522
|
+
if (options.json) {
|
|
1523
|
+
console.log(JSON.stringify(index.plugins.map((p) => {
|
|
1524
|
+
const ref = p.meta.sourceKind === 'marketplace' ? `${p.id}@${p.meta.sourceName}` : p.id;
|
|
1525
|
+
return {
|
|
1526
|
+
id: p.id,
|
|
1527
|
+
ref,
|
|
1528
|
+
enabled: enabledMap[ref] ?? null,
|
|
1529
|
+
...p.meta,
|
|
1530
|
+
components: Object.fromEntries(Object.entries(p.components).map(([k, v]) => [k, v.length])),
|
|
1531
|
+
};
|
|
1532
|
+
}), null, 2));
|
|
1533
|
+
return;
|
|
1534
|
+
}
|
|
1535
|
+
printPluginList(index.plugins, enabledMap);
|
|
1536
|
+
}
|
|
1537
|
+
catch (error) {
|
|
1538
|
+
if (error instanceof Error) {
|
|
1539
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}`));
|
|
1540
|
+
}
|
|
1541
|
+
process.exit(1);
|
|
1542
|
+
}
|
|
1543
|
+
});
|
|
1544
|
+
pluginRoot
|
|
1545
|
+
.command('info <id>')
|
|
1546
|
+
.description('Show detailed information about a plugin')
|
|
1547
|
+
.action((id) => {
|
|
1548
|
+
try {
|
|
1549
|
+
const index = buildPluginIndex();
|
|
1550
|
+
const plugin = index.get(id);
|
|
1551
|
+
if (!plugin) {
|
|
1552
|
+
console.error(chalk.red(`✗ Plugin "${id}" not found.`));
|
|
1553
|
+
console.log(chalk.dim(' Available plugins:'));
|
|
1554
|
+
for (const p of index.plugins) {
|
|
1555
|
+
console.log(chalk.dim(` ${p.id}`));
|
|
1556
|
+
}
|
|
1557
|
+
process.exit(1);
|
|
1558
|
+
}
|
|
1559
|
+
printPluginInfo(plugin);
|
|
1560
|
+
}
|
|
1561
|
+
catch (error) {
|
|
1562
|
+
if (error instanceof Error) {
|
|
1563
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}`));
|
|
1564
|
+
}
|
|
1565
|
+
process.exit(1);
|
|
1566
|
+
}
|
|
1567
|
+
});
|
|
1568
|
+
function pluginEnableAction(id, options) {
|
|
1569
|
+
try {
|
|
1570
|
+
const scope = resolveScope(options);
|
|
1571
|
+
const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1572
|
+
if (config.plugins.enabled[id] === true) {
|
|
1573
|
+
console.log(chalk.yellow(`⚠ Plugin "${id}" is already enabled.`));
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const index = buildPluginIndex();
|
|
1577
|
+
if (!index.get(id)) {
|
|
1578
|
+
console.error(chalk.red(`✗ Plugin "${id}" not found.`));
|
|
1579
|
+
console.log(chalk.dim(' Run `asb plugin list` to see available plugins.'));
|
|
1580
|
+
process.exit(1);
|
|
1581
|
+
}
|
|
1582
|
+
updateConfigLayer((layer) => ({
|
|
1583
|
+
...layer,
|
|
1584
|
+
plugins: {
|
|
1585
|
+
...(layer.plugins ?? {}),
|
|
1586
|
+
enabled: { ...(layer.plugins?.enabled ?? {}), [id]: true },
|
|
1587
|
+
},
|
|
1588
|
+
}), scopeToLoadOptions(scope));
|
|
1589
|
+
console.log(chalk.green(`✓ Plugin "${id}" enabled.`));
|
|
1590
|
+
}
|
|
1591
|
+
catch (error) {
|
|
1592
|
+
if (error instanceof Error) {
|
|
1593
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}`));
|
|
1594
|
+
}
|
|
1595
|
+
process.exit(1);
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
function pluginDisableAction(id, options) {
|
|
1599
|
+
try {
|
|
1600
|
+
const scope = resolveScope(options);
|
|
1601
|
+
const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1602
|
+
if (config.plugins.enabled[id] === undefined) {
|
|
1603
|
+
console.log(chalk.yellow(`⚠ Plugin "${id}" is not installed.`));
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (config.plugins.enabled[id] === false) {
|
|
1607
|
+
console.log(chalk.yellow(`⚠ Plugin "${id}" is already disabled.`));
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
updateConfigLayer((layer) => ({
|
|
1611
|
+
...layer,
|
|
1612
|
+
plugins: {
|
|
1613
|
+
...(layer.plugins ?? {}),
|
|
1614
|
+
enabled: { ...(layer.plugins?.enabled ?? {}), [id]: false },
|
|
1615
|
+
},
|
|
1616
|
+
}), scopeToLoadOptions(scope));
|
|
1617
|
+
console.log(chalk.green(`✓ Plugin "${id}" disabled.`));
|
|
1618
|
+
}
|
|
1619
|
+
catch (error) {
|
|
1620
|
+
if (error instanceof Error) {
|
|
1621
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}`));
|
|
1622
|
+
}
|
|
1623
|
+
process.exit(1);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
function pluginUninstallAction(id, options) {
|
|
1627
|
+
try {
|
|
1628
|
+
const scope = resolveScope(options);
|
|
1629
|
+
const config = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1630
|
+
if (config.plugins.enabled[id] === undefined) {
|
|
1631
|
+
console.log(chalk.yellow(`⚠ Plugin "${id}" is not installed.`));
|
|
1632
|
+
return;
|
|
1633
|
+
}
|
|
1634
|
+
updateConfigLayer((layer) => {
|
|
1635
|
+
const updated = { ...(layer.plugins?.enabled ?? {}) };
|
|
1636
|
+
delete updated[id];
|
|
1637
|
+
return {
|
|
1638
|
+
...layer,
|
|
1639
|
+
plugins: { ...(layer.plugins ?? {}), enabled: updated },
|
|
1640
|
+
};
|
|
1641
|
+
}, scopeToLoadOptions(scope));
|
|
1642
|
+
console.log(chalk.green(`✓ Plugin "${id}" uninstalled.`));
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
if (error instanceof Error) {
|
|
1646
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}`));
|
|
1647
|
+
}
|
|
1648
|
+
process.exit(1);
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
const pluginScopeOpts = [
|
|
1652
|
+
['-p, --profile <name>', 'Profile configuration to use'],
|
|
1653
|
+
['--project <path>', 'Project directory containing .asb.toml'],
|
|
1654
|
+
];
|
|
1655
|
+
const enableCmd = pluginRoot
|
|
1656
|
+
.command('enable <id>')
|
|
1657
|
+
.description('Enable a plugin (set plugins.enabled to true)');
|
|
1658
|
+
for (const [flag, desc] of pluginScopeOpts)
|
|
1659
|
+
enableCmd.option(flag, desc);
|
|
1660
|
+
enableCmd.action(pluginEnableAction);
|
|
1661
|
+
const installCmd = pluginRoot
|
|
1662
|
+
.command('install <id>')
|
|
1663
|
+
.description('Enable a plugin (alias for enable)');
|
|
1664
|
+
for (const [flag, desc] of pluginScopeOpts)
|
|
1665
|
+
installCmd.option(flag, desc);
|
|
1666
|
+
installCmd.action(pluginEnableAction);
|
|
1667
|
+
const disableCmd = pluginRoot
|
|
1668
|
+
.command('disable <id>')
|
|
1669
|
+
.description('Disable a plugin (set plugins.enabled to false)');
|
|
1670
|
+
for (const [flag, desc] of pluginScopeOpts)
|
|
1671
|
+
disableCmd.option(flag, desc);
|
|
1672
|
+
disableCmd.action(pluginDisableAction);
|
|
1673
|
+
const uninstallCmd = pluginRoot
|
|
1674
|
+
.command('uninstall <id>')
|
|
1675
|
+
.description('Remove a plugin from plugins.enabled');
|
|
1676
|
+
for (const [flag, desc] of pluginScopeOpts)
|
|
1677
|
+
uninstallCmd.option(flag, desc);
|
|
1678
|
+
uninstallCmd.action(pluginUninstallAction);
|
|
1679
|
+
// ── Plugin marketplace (source management) ─────────────────────────
|
|
1680
|
+
const mktRoot = pluginRoot
|
|
1681
|
+
.command('marketplace')
|
|
1682
|
+
.alias('market')
|
|
1683
|
+
.description('Manage plugin sources (local paths, git repos, marketplaces)');
|
|
1684
|
+
mktRoot
|
|
1482
1685
|
.command('add')
|
|
1483
|
-
.description('Add a
|
|
1686
|
+
.description('Add a marketplace or library source')
|
|
1484
1687
|
.argument('<location>', 'Local path or git URL (e.g., https://github.com/org/repo)')
|
|
1485
1688
|
.argument('[name]', 'Namespace (defaults to repo or directory name)')
|
|
1486
1689
|
.action((location, nameArg) => {
|
|
@@ -1515,10 +1718,11 @@ sourceRoot
|
|
|
1515
1718
|
console.log(chalk.dim(` Ref: ${parsed.ref}`));
|
|
1516
1719
|
if (parsed.subdir)
|
|
1517
1720
|
console.log(chalk.dim(` Subdir: ${parsed.subdir}`));
|
|
1518
|
-
if (validation.
|
|
1721
|
+
if (validation.kind === 'marketplace') {
|
|
1519
1722
|
printMarketplaceSummary(effectivePath);
|
|
1520
1723
|
}
|
|
1521
1724
|
else {
|
|
1725
|
+
console.log(chalk.dim(` Type: ${validation.kind}`));
|
|
1522
1726
|
console.log(chalk.dim(` Found: ${validation.found.join(', ')}`));
|
|
1523
1727
|
if (validation.missing.length > 0) {
|
|
1524
1728
|
console.log(chalk.dim(` Missing: ${validation.missing.join(', ')}`));
|
|
@@ -1533,7 +1737,7 @@ sourceRoot
|
|
|
1533
1737
|
}
|
|
1534
1738
|
addLocalSource(name, location);
|
|
1535
1739
|
console.log(chalk.green(`\n✓ Added source "${name}" at ${path.resolve(location)}`));
|
|
1536
|
-
if (validation.
|
|
1740
|
+
if (validation.kind === 'marketplace') {
|
|
1537
1741
|
printMarketplaceSummary(path.resolve(location));
|
|
1538
1742
|
}
|
|
1539
1743
|
else {
|
|
@@ -1544,8 +1748,9 @@ sourceRoot
|
|
|
1544
1748
|
}
|
|
1545
1749
|
}
|
|
1546
1750
|
console.log();
|
|
1547
|
-
console.log(chalk.dim('
|
|
1548
|
-
chalk.cyan(
|
|
1751
|
+
console.log(chalk.dim('Plugins from this source are now discoverable. Run ') +
|
|
1752
|
+
chalk.cyan('asb plugin list') +
|
|
1753
|
+
chalk.dim(' to see them.'));
|
|
1549
1754
|
}
|
|
1550
1755
|
catch (error) {
|
|
1551
1756
|
if (error instanceof Error) {
|
|
@@ -1554,10 +1759,11 @@ sourceRoot
|
|
|
1554
1759
|
process.exit(1);
|
|
1555
1760
|
}
|
|
1556
1761
|
});
|
|
1557
|
-
|
|
1762
|
+
mktRoot
|
|
1558
1763
|
.command('remove')
|
|
1559
|
-
.
|
|
1560
|
-
.
|
|
1764
|
+
.alias('rm')
|
|
1765
|
+
.description('Remove a marketplace source by name')
|
|
1766
|
+
.argument('<name>', 'Source namespace to remove')
|
|
1561
1767
|
.action((name) => {
|
|
1562
1768
|
try {
|
|
1563
1769
|
const sources = getSources();
|
|
@@ -1577,9 +1783,43 @@ sourceRoot
|
|
|
1577
1783
|
process.exit(1);
|
|
1578
1784
|
}
|
|
1579
1785
|
});
|
|
1580
|
-
|
|
1786
|
+
mktRoot
|
|
1787
|
+
.command('update')
|
|
1788
|
+
.description('Pull latest changes for all remote sources (or a specific one)')
|
|
1789
|
+
.argument('[name]', 'Specific source namespace to update')
|
|
1790
|
+
.action((name) => {
|
|
1791
|
+
try {
|
|
1792
|
+
const results = updateRemoteSources();
|
|
1793
|
+
const filtered = name ? results.filter((r) => r.namespace === name) : results;
|
|
1794
|
+
if (filtered.length === 0) {
|
|
1795
|
+
if (name) {
|
|
1796
|
+
console.log(chalk.yellow(`⚠ No remote source named "${name}" found.`));
|
|
1797
|
+
}
|
|
1798
|
+
else {
|
|
1799
|
+
console.log(chalk.yellow('⚠ No remote sources configured.'));
|
|
1800
|
+
}
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
for (const r of filtered) {
|
|
1804
|
+
if (r.status === 'updated') {
|
|
1805
|
+
console.log(chalk.green(` ✓ ${r.namespace}: updated from ${r.url}`));
|
|
1806
|
+
}
|
|
1807
|
+
else {
|
|
1808
|
+
console.log(chalk.red(` ✗ ${r.namespace}: ${r.error}`));
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
catch (error) {
|
|
1813
|
+
if (error instanceof Error) {
|
|
1814
|
+
console.error(chalk.red(`\n✗ Error: ${error.message}`));
|
|
1815
|
+
}
|
|
1816
|
+
process.exit(1);
|
|
1817
|
+
}
|
|
1818
|
+
});
|
|
1819
|
+
mktRoot
|
|
1581
1820
|
.command('list')
|
|
1582
|
-
.
|
|
1821
|
+
.alias('ls')
|
|
1822
|
+
.description('List all configured marketplace sources')
|
|
1583
1823
|
.option('--json', 'Output inventory as JSON')
|
|
1584
1824
|
.action((options) => {
|
|
1585
1825
|
try {
|
|
@@ -1589,19 +1829,23 @@ sourceRoot
|
|
|
1589
1829
|
return;
|
|
1590
1830
|
}
|
|
1591
1831
|
if (sources.length === 0) {
|
|
1592
|
-
console.log(chalk.yellow('⚠ No
|
|
1593
|
-
console.log(chalk.dim(' Use `asb
|
|
1832
|
+
console.log(chalk.yellow('⚠ No marketplace sources configured.'));
|
|
1833
|
+
console.log(chalk.dim(' Use `asb plugin marketplace add <location> [name]` to add one.'));
|
|
1594
1834
|
return;
|
|
1595
1835
|
}
|
|
1596
|
-
console.log(chalk.blue('\
|
|
1836
|
+
console.log(chalk.blue('\nMarketplace sources:'));
|
|
1597
1837
|
const header = ['Namespace', 'Type', 'Source', 'Status', 'Contains'];
|
|
1598
1838
|
const rows = sources.map((src) => {
|
|
1599
1839
|
const isRemote = !!src.remote;
|
|
1600
1840
|
const exists = fs.existsSync(src.path);
|
|
1601
1841
|
const validation = exists
|
|
1602
1842
|
? validateSourcePath(src.path)
|
|
1603
|
-
: { found: [], missing: [],
|
|
1604
|
-
const typePlain = validation.
|
|
1843
|
+
: { found: [], missing: [], kind: 'plugin' };
|
|
1844
|
+
const typePlain = validation.kind === 'marketplace'
|
|
1845
|
+
? 'marketplace'
|
|
1846
|
+
: isRemote
|
|
1847
|
+
? 'plugin (remote)'
|
|
1848
|
+
: 'plugin';
|
|
1605
1849
|
const sourcePlain = isRemote ? (src.remote?.url ?? src.path) : src.path;
|
|
1606
1850
|
let statusPlain;
|
|
1607
1851
|
if (isRemote) {
|
|
@@ -1611,7 +1855,7 @@ sourceRoot
|
|
|
1611
1855
|
statusPlain = exists ? 'ok' : 'missing';
|
|
1612
1856
|
}
|
|
1613
1857
|
let containsPlain;
|
|
1614
|
-
if (validation.
|
|
1858
|
+
if (validation.kind === 'marketplace' && exists) {
|
|
1615
1859
|
try {
|
|
1616
1860
|
const mp = readMarketplace(src.path);
|
|
1617
1861
|
containsPlain = `${mp.plugins.length} plugin(s)`;
|
|
@@ -1627,7 +1871,7 @@ sourceRoot
|
|
|
1627
1871
|
{ plain: src.namespace, formatted: chalk.cyan(src.namespace) },
|
|
1628
1872
|
{
|
|
1629
1873
|
plain: typePlain,
|
|
1630
|
-
formatted: validation.
|
|
1874
|
+
formatted: validation.kind === 'marketplace'
|
|
1631
1875
|
? chalk.magenta(typePlain)
|
|
1632
1876
|
: isRemote
|
|
1633
1877
|
? chalk.blue(typePlain)
|
|
@@ -1651,8 +1895,20 @@ sourceRoot
|
|
|
1651
1895
|
process.exit(1);
|
|
1652
1896
|
}
|
|
1653
1897
|
});
|
|
1654
|
-
|
|
1655
|
-
|
|
1898
|
+
mktRoot.action(() => {
|
|
1899
|
+
mktRoot.commands.find((c) => c.name() === 'list')?.parse(process.argv);
|
|
1900
|
+
});
|
|
1901
|
+
// ── Removed: `asb source` ──────────────────────────────────────────
|
|
1902
|
+
program
|
|
1903
|
+
.command('source')
|
|
1904
|
+
.description('[removed] Use `asb plugin marketplace` instead')
|
|
1905
|
+
.allowUnknownOption(true)
|
|
1906
|
+
.action(() => {
|
|
1907
|
+
console.error(chalk.red('✗ `asb source` has been removed.\n' +
|
|
1908
|
+
' Sources are now managed under `asb plugin marketplace`.\n' +
|
|
1909
|
+
' Config has moved from [library.sources] to [plugins.sources].\n' +
|
|
1910
|
+
' Run `asb plugin marketplace --help` for usage.'));
|
|
1911
|
+
process.exit(1);
|
|
1656
1912
|
});
|
|
1657
1913
|
program.parse(process.argv);
|
|
1658
1914
|
//# sourceMappingURL=index.js.map
|