agent-switchboard 0.3.6 → 0.3.7
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 +9 -9
- package/dist/config/application-config.d.ts +1 -0
- package/dist/config/application-config.js +57 -38
- package/dist/config/application-config.js.map +1 -1
- package/dist/config/layered-config.d.ts +1 -0
- package/dist/config/layered-config.js +11 -1
- package/dist/config/layered-config.js.map +1 -1
- package/dist/config/schemas.d.ts +56 -35
- package/dist/config/schemas.js +24 -9
- package/dist/config/schemas.js.map +1 -1
- package/dist/index.js +69 -437
- package/dist/index.js.map +1 -1
- package/dist/library/distribute-bundle.js +6 -9
- package/dist/library/distribute-bundle.js.map +1 -1
- package/dist/library/distribute.js +7 -10
- package/dist/library/distribute.js.map +1 -1
- package/dist/library/state.d.ts +3 -0
- package/dist/library/state.js +25 -5
- package/dist/library/state.js.map +1 -1
- package/dist/mcp/distribution.d.ts +13 -0
- package/dist/mcp/distribution.js +121 -0
- package/dist/mcp/distribution.js.map +1 -0
- package/dist/rules/composer.js +22 -18
- package/dist/rules/composer.js.map +1 -1
- package/dist/rules/distribution.d.ts +1 -1
- package/dist/rules/distribution.js +22 -5
- package/dist/rules/distribution.js.map +1 -1
- package/dist/rules/state.d.ts +3 -0
- package/dist/rules/state.js +27 -3
- package/dist/rules/state.js.map +1 -1
- package/dist/sync/command.d.ts +15 -0
- package/dist/sync/command.js +268 -0
- package/dist/sync/command.js.map +1 -0
- package/dist/targets/registry.d.ts +1 -1
- package/dist/targets/registry.js +1 -1
- package/dist/ui/library-selector.js +25 -2
- package/dist/ui/library-selector.js.map +1 -1
- package/dist/ui/rule-ui.d.ts +2 -2
- package/dist/ui/rule-ui.js +13 -4
- package/dist/ui/rule-ui.js.map +1 -1
- package/dist/ui/selection-state.d.ts +7 -0
- package/dist/ui/selection-state.js +13 -0
- package/dist/ui/selection-state.js.map +1 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13,39 +13,42 @@ 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 { resolveEffectiveSectionConfig } from './config/application-config.js';
|
|
17
16
|
import { updateConfigLayer } from './config/layered-config.js';
|
|
18
|
-
import { loadMcpConfig,
|
|
17
|
+
import { loadMcpConfig, stripLegacyEnabledFlagsFromMcpJson } from './config/mcp-config.js';
|
|
19
18
|
import { getAgentsDir, getAgentsHome, getClaudeDir, getCodexDir, getCommandsDir, getCursorDir, getGeminiDir, getHooksDir, getOpencodePath, getSkillsDir, getSourceCacheDir, } from './config/paths.js';
|
|
19
|
+
import { scopeToLayerOptions } from './config/scope.js';
|
|
20
20
|
import { loadSwitchboardConfig, loadSwitchboardConfigWithLayers, } from './config/switchboard-config.js';
|
|
21
21
|
import { distributeHooks } from './hooks/distribution.js';
|
|
22
22
|
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
|
-
import { loadLibraryStateSection,
|
|
25
|
+
import { loadLibraryStateSection, saveMcpEnabledState } from './library/state.js';
|
|
26
26
|
import { readMarketplace } from './marketplace/reader.js';
|
|
27
|
+
import { distributeMcp } from './mcp/distribution.js';
|
|
27
28
|
import { buildPluginIndex } from './plugins/index.js';
|
|
28
29
|
import { composeActiveRules } from './rules/composer.js';
|
|
29
30
|
import { distributeRules, listIndirectAgents, listPerFileAgents, listUnsupportedAgents, } from './rules/distribution.js';
|
|
30
31
|
import { buildRuleInventory } from './rules/inventory.js';
|
|
31
32
|
import { loadRuleLibrary } from './rules/library.js';
|
|
32
|
-
import { loadRuleState, updateRuleState } from './rules/state.js';
|
|
33
|
+
import { loadRuleState, loadWritableRuleState, updateRuleState } from './rules/state.js';
|
|
33
34
|
import { distributeSkills } from './skills/distribution.js';
|
|
34
35
|
import { importSkill, listSkillsInDirectory } from './skills/importer.js';
|
|
35
36
|
import { buildSkillInventory } from './skills/inventory.js';
|
|
36
37
|
import { distributeSubagents } from './subagents/distribution.js';
|
|
37
38
|
import { importSubagentFromFile } from './subagents/importer.js';
|
|
38
39
|
import { buildSubagentInventory } from './subagents/inventory.js';
|
|
40
|
+
import { runSyncCommand } from './sync/command.js';
|
|
39
41
|
import { initTargets } from './targets/init.js';
|
|
40
|
-
import {
|
|
42
|
+
import { getTargetsForSection } from './targets/registry.js';
|
|
41
43
|
import { showCommandSelector } from './ui/command-ui.js';
|
|
42
44
|
import { showHookSelector } from './ui/hook-ui.js';
|
|
43
45
|
import { showMcpServerUI } from './ui/mcp-ui.js';
|
|
44
46
|
import { printPluginInfo, printPluginList } from './ui/plugin-ui.js';
|
|
45
47
|
import { showRuleSelector } from './ui/rule-ui.js';
|
|
48
|
+
import { shouldPersistSelection } from './ui/selection-state.js';
|
|
46
49
|
import { showSkillSelector } from './ui/skill-ui.js';
|
|
47
50
|
import { showSubagentSelector } from './ui/subagent-ui.js';
|
|
48
|
-
import { printActiveSelection, printAgentSyncStatus,
|
|
51
|
+
import { printActiveSelection, printAgentSyncStatus, printDistributionResults, printTable, } from './util/cli.js';
|
|
49
52
|
const program = new Command();
|
|
50
53
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
51
54
|
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '../package.json'), 'utf-8'));
|
|
@@ -58,7 +61,7 @@ Examples:
|
|
|
58
61
|
$ asb mcp Enable/disable MCP servers interactively
|
|
59
62
|
$ asb rule Select and order rule snippets
|
|
60
63
|
$ asb sync Push all libraries to every active agent
|
|
61
|
-
$ asb sync
|
|
64
|
+
$ asb sync -P . Sync with project-level overrides
|
|
62
65
|
|
|
63
66
|
Alias: agent-switchboard
|
|
64
67
|
Config: ~/.agent-switchboard/config.toml`);
|
|
@@ -68,69 +71,21 @@ program
|
|
|
68
71
|
.command('sync')
|
|
69
72
|
.description('Synchronize active MCP servers, rules, commands, agents, and skills to application targets')
|
|
70
73
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
71
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
74
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
72
75
|
.option('--no-update', 'Skip updating remote sources')
|
|
73
76
|
.action(async (options) => {
|
|
74
77
|
try {
|
|
75
78
|
const scope = resolveScope(options);
|
|
76
|
-
|
|
79
|
+
const hasErrors = await runSyncCommand({
|
|
80
|
+
scope,
|
|
81
|
+
updateSources: options.update !== false,
|
|
82
|
+
});
|
|
77
83
|
console.log();
|
|
78
|
-
if (
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.log(chalk.blue('Sources:'));
|
|
82
|
-
for (const result of remoteResults) {
|
|
83
|
-
if (result.status === 'updated') {
|
|
84
|
-
console.log(` ${chalk.green('✓')} ${chalk.cyan(result.namespace)} ${chalk.dim(result.url)}`);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
console.log(` ${chalk.yellow('⚠')} ${chalk.cyan(result.namespace)} ${chalk.yellow(result.error ?? 'update failed')}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
if (scope?.project) {
|
|
93
|
-
// Dual sync: global first, then project
|
|
94
|
-
const { config: globalConfig, layers: globalLayers } = loadSwitchboardConfigWithLayers(scopeToLoadOptions(undefined));
|
|
95
|
-
await initTargets(globalConfig);
|
|
96
|
-
console.log(chalk.blue.bold('── Global ──'));
|
|
97
|
-
const globalErrors = await runSyncPhase({
|
|
98
|
-
scope: undefined,
|
|
99
|
-
config: globalConfig,
|
|
100
|
-
layers: globalLayers,
|
|
101
|
-
});
|
|
102
|
-
console.log();
|
|
103
|
-
resetAgentSyncCache();
|
|
104
|
-
console.log(chalk.blue.bold(`── Project: ${shortenPath(scope.project)} ──`));
|
|
105
|
-
const { config: projectConfig, layers: projectLayers } = loadSwitchboardConfigWithLayers(scopeToLoadOptions(scope));
|
|
106
|
-
// Register any project-level [targets] not seen during initTargets (which only ran once)
|
|
107
|
-
const projectTargets = projectConfig.targets;
|
|
108
|
-
if (projectTargets && Object.keys(projectTargets).length > 0) {
|
|
109
|
-
registerConfigTargets(projectTargets);
|
|
110
|
-
}
|
|
111
|
-
const projectErrors = await runSyncPhase({
|
|
112
|
-
scope,
|
|
113
|
-
config: projectConfig,
|
|
114
|
-
layers: projectLayers,
|
|
115
|
-
});
|
|
116
|
-
console.log();
|
|
117
|
-
if (globalErrors || projectErrors) {
|
|
118
|
-
console.log(chalk.red('✗ Sync completed with errors.'));
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
console.log(chalk.green('✓ Sync complete.'));
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
const { config, layers } = loadSwitchboardConfigWithLayers(scopeToLoadOptions(scope));
|
|
125
|
-
await initTargets(config);
|
|
126
|
-
const hasErrors = await runSyncPhase({ scope, config, layers });
|
|
127
|
-
console.log();
|
|
128
|
-
if (hasErrors) {
|
|
129
|
-
console.log(chalk.red('✗ Sync completed with errors.'));
|
|
130
|
-
process.exit(1);
|
|
131
|
-
}
|
|
132
|
-
console.log(chalk.green('✓ Sync complete.'));
|
|
84
|
+
if (hasErrors) {
|
|
85
|
+
console.log(chalk.red('✗ Sync completed with errors.'));
|
|
86
|
+
process.exit(1);
|
|
133
87
|
}
|
|
88
|
+
console.log(chalk.green('✓ Sync complete.'));
|
|
134
89
|
}
|
|
135
90
|
catch (error) {
|
|
136
91
|
if (error instanceof Error) {
|
|
@@ -139,211 +94,6 @@ program
|
|
|
139
94
|
process.exit(1);
|
|
140
95
|
}
|
|
141
96
|
});
|
|
142
|
-
async function runSyncPhase({ scope, config, layers }) {
|
|
143
|
-
// Config summary
|
|
144
|
-
const activeLayers = [];
|
|
145
|
-
if (layers.user.exists)
|
|
146
|
-
activeLayers.push(shortenPath(layers.user.path));
|
|
147
|
-
if (layers.profile?.exists)
|
|
148
|
-
activeLayers.push(shortenPath(layers.profile.path));
|
|
149
|
-
if (layers.project?.exists)
|
|
150
|
-
activeLayers.push(shortenPath(layers.project.path));
|
|
151
|
-
console.log(`${chalk.blue('Config:')} ${activeLayers.length > 0 ? chalk.dim(activeLayers.join(' + ')) : chalk.gray('no config files')}`);
|
|
152
|
-
const assumeInstalledSet = new Set(config.applications.assume_installed);
|
|
153
|
-
const appsLabel = config.applications.active.length > 0
|
|
154
|
-
? config.applications.active
|
|
155
|
-
.map((id) => {
|
|
156
|
-
const t = getTargetById(id);
|
|
157
|
-
if (t?.isInstalled?.() === false) {
|
|
158
|
-
if (assumeInstalledSet.has(id))
|
|
159
|
-
return chalk.yellow(`${id} (assumed installed)`);
|
|
160
|
-
return chalk.gray(`${id} (not installed)`);
|
|
161
|
-
}
|
|
162
|
-
return chalk.cyan(id);
|
|
163
|
-
})
|
|
164
|
-
.join(', ')
|
|
165
|
-
: chalk.gray('none configured');
|
|
166
|
-
console.log(`${chalk.blue('Apps:')} ${appsLabel}`);
|
|
167
|
-
console.log();
|
|
168
|
-
const cursorSkillsDeduped = config.applications.active.includes('claude-code') &&
|
|
169
|
-
resolveEffectiveSectionConfig('skills', 'claude-code', scope).enabled.length > 0;
|
|
170
|
-
console.log(chalk.blue('Inventory:'));
|
|
171
|
-
{
|
|
172
|
-
const sections = ['mcp', 'rules', 'commands', 'agents', 'skills', 'hooks'];
|
|
173
|
-
const sectionPlatforms = {};
|
|
174
|
-
for (const s of sections) {
|
|
175
|
-
let ids = filterInstalled(getTargetsForSection(s), assumeInstalledSet).map((t) => t.id);
|
|
176
|
-
if (s === 'skills' && cursorSkillsDeduped) {
|
|
177
|
-
ids = ids.filter((id) => id !== 'cursor');
|
|
178
|
-
}
|
|
179
|
-
sectionPlatforms[s] = ids;
|
|
180
|
-
}
|
|
181
|
-
const termWidth = process.stdout.columns || 80;
|
|
182
|
-
const maxSectionLen = Math.max(...sections.map((s) => s.length));
|
|
183
|
-
const maxCountLen = Math.max(...sections.map((s) => `(${config[s].enabled.length})`.length));
|
|
184
|
-
const prefixPlainLen = 2 + maxSectionLen + 1 + maxCountLen + 2;
|
|
185
|
-
const fitPreview = (ids, maxWidth) => {
|
|
186
|
-
if (ids.length === 0)
|
|
187
|
-
return chalk.gray('none');
|
|
188
|
-
const full = ids.join(', ');
|
|
189
|
-
if (full.length <= maxWidth)
|
|
190
|
-
return full;
|
|
191
|
-
let text = '';
|
|
192
|
-
let shown = 0;
|
|
193
|
-
for (let i = 0; i < ids.length; i++) {
|
|
194
|
-
const sep = shown > 0 ? ', ' : '';
|
|
195
|
-
const candidate = text + sep + ids[i];
|
|
196
|
-
const remaining = ids.length - (i + 1);
|
|
197
|
-
if (remaining > 0) {
|
|
198
|
-
const suffix = `, ... (+${remaining} more)`;
|
|
199
|
-
if (candidate.length + suffix.length > maxWidth && shown > 0) {
|
|
200
|
-
const left = ids.length - shown;
|
|
201
|
-
return `${text}${chalk.gray(`, ... (+${left} more)`)}`;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
text = candidate;
|
|
205
|
-
shown++;
|
|
206
|
-
}
|
|
207
|
-
return text;
|
|
208
|
-
};
|
|
209
|
-
for (const section of sections) {
|
|
210
|
-
const globalActive = config[section].enabled;
|
|
211
|
-
const globalCount = globalActive.length;
|
|
212
|
-
const supported = new Set(sectionPlatforms[section] ?? []);
|
|
213
|
-
const applicableApps = config.applications.active.filter((id) => supported.has(id));
|
|
214
|
-
const effectiveByApp = new Map();
|
|
215
|
-
for (const appId of applicableApps) {
|
|
216
|
-
effectiveByApp.set(appId, resolveEffectiveSectionConfig(section, appId, scope).enabled);
|
|
217
|
-
}
|
|
218
|
-
const perAppParts = applicableApps.map((appId) => {
|
|
219
|
-
const eff = effectiveByApp.get(appId) ?? [];
|
|
220
|
-
const delta = eff.length - globalCount;
|
|
221
|
-
const d = delta === 0 ? '' : delta > 0 ? `(+${delta})` : `(${delta})`;
|
|
222
|
-
return `${appId}:${eff.length}${d}`;
|
|
223
|
-
});
|
|
224
|
-
const union = new Set();
|
|
225
|
-
for (const [, ids] of effectiveByApp) {
|
|
226
|
-
for (const id of ids)
|
|
227
|
-
union.add(id);
|
|
228
|
-
}
|
|
229
|
-
const previewIds = globalActive.length > 0 ? [...globalActive] : [...union];
|
|
230
|
-
const paddedSection = section.padEnd(maxSectionLen);
|
|
231
|
-
const countStr = `(${globalCount})`.padStart(maxCountLen);
|
|
232
|
-
const appsStr = perAppParts.join(' ');
|
|
233
|
-
console.log(` ${chalk.cyan(paddedSection)} ${chalk.gray(countStr)} ${appsStr}`);
|
|
234
|
-
if (previewIds.length > 0) {
|
|
235
|
-
const indent = ' '.repeat(prefixPlainLen);
|
|
236
|
-
const previewWidth = Math.max(20, termWidth - prefixPlainLen - 2);
|
|
237
|
-
const preview = fitPreview(previewIds, previewWidth);
|
|
238
|
-
console.log(`${indent}${chalk.gray('→')} ${preview}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
// Show enabled plugins summary
|
|
243
|
-
{
|
|
244
|
-
const pluginIndex = buildPluginIndex();
|
|
245
|
-
const enabledPluginRefs = config.plugins.enabled;
|
|
246
|
-
if (enabledPluginRefs.length > 0) {
|
|
247
|
-
const names = enabledPluginRefs
|
|
248
|
-
.map((pid) => {
|
|
249
|
-
const p = pluginIndex.get(pid);
|
|
250
|
-
return p ? pid : chalk.strikethrough(pid);
|
|
251
|
-
})
|
|
252
|
-
.join(', ');
|
|
253
|
-
console.log(` ${chalk.magenta('plugins')} ${chalk.gray(`(${enabledPluginRefs.length})`)} ${names}`);
|
|
254
|
-
}
|
|
255
|
-
else if (pluginIndex.plugins.length > 0) {
|
|
256
|
-
console.log(` ${chalk.magenta('plugins')} ${chalk.gray('(0)')} ${chalk.gray(`${pluginIndex.plugins.length} available`)}`);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
console.log();
|
|
260
|
-
const notes = [];
|
|
261
|
-
if (cursorSkillsDeduped && config.applications.active.includes('cursor')) {
|
|
262
|
-
notes.push('cursor reads skills via claude-code');
|
|
263
|
-
}
|
|
264
|
-
if (config.distribution.use_agents_dir) {
|
|
265
|
-
const agentsMembers = ['codex', 'gemini', 'opencode'].filter((a) => config.applications.active.includes(a));
|
|
266
|
-
if (agentsMembers.length > 0) {
|
|
267
|
-
notes.push(`skills for ${agentsMembers.join(', ')} sync to shared .agents/skills`);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
for (let i = 0; i < notes.length; i++) {
|
|
271
|
-
const prefix = i === 0 ? ' Note: ' : ' ';
|
|
272
|
-
console.log(chalk.gray(`${prefix}${notes[i]}.`));
|
|
273
|
-
}
|
|
274
|
-
if (notes.length > 0)
|
|
275
|
-
console.log();
|
|
276
|
-
const activeAppIds = config.applications.active;
|
|
277
|
-
const mcpDistribution = await applyToAgents(scope, undefined, {
|
|
278
|
-
useSpinner: false,
|
|
279
|
-
assumeInstalled: assumeInstalledSet,
|
|
280
|
-
});
|
|
281
|
-
const ruleDistribution = distributeRules(undefined, { activeAppIds, assumeInstalled: assumeInstalledSet }, scope);
|
|
282
|
-
const commandDistribution = distributeCommands(scope, activeAppIds, assumeInstalledSet);
|
|
283
|
-
const agentDistribution = distributeSubagents(scope, activeAppIds, assumeInstalledSet);
|
|
284
|
-
const skillDistribution = distributeSkills(scope, {
|
|
285
|
-
useAgentsDir: config.distribution.use_agents_dir,
|
|
286
|
-
activeAppIds,
|
|
287
|
-
assumeInstalled: assumeInstalledSet,
|
|
288
|
-
});
|
|
289
|
-
const hookDistribution = distributeHooks(scope, activeAppIds, assumeInstalledSet);
|
|
290
|
-
const distSections = [
|
|
291
|
-
{
|
|
292
|
-
label: 'mcp',
|
|
293
|
-
results: mcpDistribution,
|
|
294
|
-
emptyMessage: 'no apps configured',
|
|
295
|
-
getTargetLabel: (r) => r.application,
|
|
296
|
-
getPath: (r) => r.filePath,
|
|
297
|
-
},
|
|
298
|
-
{
|
|
299
|
-
label: 'rules',
|
|
300
|
-
results: ruleDistribution.results,
|
|
301
|
-
emptyMessage: 'none',
|
|
302
|
-
getTargetLabel: (r) => r.agent,
|
|
303
|
-
getPath: (r) => r.filePath,
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
label: 'commands',
|
|
307
|
-
results: commandDistribution.results,
|
|
308
|
-
emptyMessage: 'none',
|
|
309
|
-
getTargetLabel: (r) => r.platform,
|
|
310
|
-
getPath: (r) => r.filePath,
|
|
311
|
-
},
|
|
312
|
-
{
|
|
313
|
-
label: 'agents',
|
|
314
|
-
results: agentDistribution.results,
|
|
315
|
-
emptyMessage: 'none',
|
|
316
|
-
getTargetLabel: (r) => r.platform,
|
|
317
|
-
getPath: (r) => r.filePath,
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
label: 'skills',
|
|
321
|
-
results: skillDistribution.results,
|
|
322
|
-
emptyMessage: 'none',
|
|
323
|
-
getTargetLabel: (r) => {
|
|
324
|
-
const sr = r;
|
|
325
|
-
if (sr.platform === 'agents') {
|
|
326
|
-
const members = ['codex', 'gemini', 'opencode'].filter((a) => activeAppIds.includes(a));
|
|
327
|
-
return members.length > 0 ? members.join('+') : 'agents';
|
|
328
|
-
}
|
|
329
|
-
return sr.platform;
|
|
330
|
-
},
|
|
331
|
-
getPath: (r) => r.targetDir,
|
|
332
|
-
},
|
|
333
|
-
{
|
|
334
|
-
label: 'hooks',
|
|
335
|
-
results: hookDistribution.results,
|
|
336
|
-
emptyMessage: 'none',
|
|
337
|
-
getTargetLabel: (r) => r.platform,
|
|
338
|
-
getPath: (r) => {
|
|
339
|
-
const hr = r;
|
|
340
|
-
return 'filePath' in hr ? hr.filePath : hr.targetDir;
|
|
341
|
-
},
|
|
342
|
-
},
|
|
343
|
-
];
|
|
344
|
-
const { hasErrors } = printCompactDistributions(distSections);
|
|
345
|
-
return hasErrors;
|
|
346
|
-
}
|
|
347
97
|
function resolveScope(input) {
|
|
348
98
|
if (!input)
|
|
349
99
|
return undefined;
|
|
@@ -357,14 +107,6 @@ function resolveScope(input) {
|
|
|
357
107
|
project: project,
|
|
358
108
|
};
|
|
359
109
|
}
|
|
360
|
-
function scopeToLoadOptions(scope) {
|
|
361
|
-
return scope
|
|
362
|
-
? {
|
|
363
|
-
profile: scope.profile ?? undefined,
|
|
364
|
-
projectPath: scope.project ?? undefined,
|
|
365
|
-
}
|
|
366
|
-
: undefined;
|
|
367
|
-
}
|
|
368
110
|
function defaultCommandSourceDir(platform) {
|
|
369
111
|
switch (platform) {
|
|
370
112
|
case 'claude-code':
|
|
@@ -533,11 +275,11 @@ program
|
|
|
533
275
|
.command('mcp')
|
|
534
276
|
.description('Interactive UI to enable/disable MCP servers')
|
|
535
277
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
536
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
278
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
537
279
|
.action(async (options) => {
|
|
538
280
|
try {
|
|
539
281
|
const scope = resolveScope(options);
|
|
540
|
-
const { config, layers } = loadSwitchboardConfigWithLayers(
|
|
282
|
+
const { config, layers } = loadSwitchboardConfigWithLayers(scopeToLayerOptions(scope));
|
|
541
283
|
const mcpConfig = loadMcpConfig();
|
|
542
284
|
// Determine initial selection:
|
|
543
285
|
// - If writing to project/profile scope, only use that layer's explicit [mcp].enabled (no fallback).
|
|
@@ -585,7 +327,7 @@ program
|
|
|
585
327
|
}
|
|
586
328
|
}
|
|
587
329
|
// Step 3: Apply to registered agents
|
|
588
|
-
await
|
|
330
|
+
await distributeMcp(scope, selectedServers);
|
|
589
331
|
// Step 4: Show summary
|
|
590
332
|
showSummary(selectedServers, scope);
|
|
591
333
|
}
|
|
@@ -600,13 +342,13 @@ const ruleCommand = program
|
|
|
600
342
|
.command('rule')
|
|
601
343
|
.description('Select and order rule snippets interactively, then sync to agents')
|
|
602
344
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
603
|
-
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
345
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml');
|
|
604
346
|
ruleCommand
|
|
605
347
|
.command('list')
|
|
606
348
|
.description('Display rule snippets and sync information')
|
|
607
349
|
.option('--json', 'Output inventory as JSON')
|
|
608
350
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
609
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
351
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
610
352
|
.action((options) => {
|
|
611
353
|
try {
|
|
612
354
|
const scope = resolveScope(options);
|
|
@@ -678,7 +420,7 @@ ruleCommand
|
|
|
678
420
|
ruleCommand.action(async (options) => {
|
|
679
421
|
try {
|
|
680
422
|
const scope = resolveScope(options);
|
|
681
|
-
const config = loadSwitchboardConfig(
|
|
423
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
682
424
|
await initTargets(config);
|
|
683
425
|
const selection = await showRuleSelector({ scope, pageSize: config.ui.page_size });
|
|
684
426
|
if (!selection) {
|
|
@@ -686,24 +428,22 @@ ruleCommand.action(async (options) => {
|
|
|
686
428
|
}
|
|
687
429
|
const rules = loadRuleLibrary();
|
|
688
430
|
const ruleMap = new Map(rules.map((rule) => [rule.id, rule]));
|
|
689
|
-
const previousState =
|
|
431
|
+
const previousState = loadWritableRuleState(scope);
|
|
432
|
+
const effectiveState = loadRuleState(scope);
|
|
690
433
|
const desiredEnabled = selection.enabled;
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
const updatedState =
|
|
698
|
-
|
|
699
|
-
return current;
|
|
700
|
-
}
|
|
701
|
-
return {
|
|
434
|
+
const selectionChanged = selection.explicitEmpty ||
|
|
435
|
+
shouldPersistSelection({
|
|
436
|
+
currentEnabled: previousState.enabled,
|
|
437
|
+
effectiveEnabled: effectiveState.enabled,
|
|
438
|
+
selectedEnabled: desiredEnabled,
|
|
439
|
+
});
|
|
440
|
+
const updatedState = selectionChanged
|
|
441
|
+
? updateRuleState((current) => ({
|
|
702
442
|
...current,
|
|
703
443
|
enabled: desiredEnabled,
|
|
704
444
|
agentSync: {},
|
|
705
|
-
}
|
|
706
|
-
|
|
445
|
+
}), scope)
|
|
446
|
+
: previousState;
|
|
707
447
|
if (!selectionChanged) {
|
|
708
448
|
console.log(chalk.gray('\nNo changes to enabled rules. Refreshing agent files...'));
|
|
709
449
|
}
|
|
@@ -725,7 +465,7 @@ ruleCommand.action(async (options) => {
|
|
|
725
465
|
}
|
|
726
466
|
const distribution = distributeRules(composeActiveRules(scope), {
|
|
727
467
|
force: !selectionChanged,
|
|
728
|
-
activeAppIds: config.applications.
|
|
468
|
+
activeAppIds: config.applications.enabled,
|
|
729
469
|
assumeInstalled: new Set(config.applications.assume_installed),
|
|
730
470
|
}, scope);
|
|
731
471
|
if (distribution.results.length > 0) {
|
|
@@ -767,18 +507,18 @@ const commandRoot = program
|
|
|
767
507
|
.command('command')
|
|
768
508
|
.description('Select slash commands interactively and distribute to agents')
|
|
769
509
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
770
|
-
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
510
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml');
|
|
771
511
|
commandRoot.action(async (options) => {
|
|
772
512
|
try {
|
|
773
513
|
const scope = resolveScope(options);
|
|
774
|
-
const config = loadSwitchboardConfig(
|
|
514
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
775
515
|
await initTargets(config);
|
|
776
516
|
const selection = await showCommandSelector({ scope, pageSize: config.ui.page_size });
|
|
777
517
|
if (!selection)
|
|
778
518
|
return;
|
|
779
519
|
console.log();
|
|
780
520
|
printActiveSelection('commands', selection.enabled);
|
|
781
|
-
const out = distributeCommands(scope, config.applications.
|
|
521
|
+
const out = distributeCommands(scope, config.applications.enabled, new Set(config.applications.assume_installed));
|
|
782
522
|
if (out.results.length > 0) {
|
|
783
523
|
console.log();
|
|
784
524
|
printDistributionResults({
|
|
@@ -862,7 +602,7 @@ commandRoot
|
|
|
862
602
|
.description('Display command inventory and sync information')
|
|
863
603
|
.option('--json', 'Output inventory as JSON')
|
|
864
604
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
865
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
605
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
866
606
|
.action((options) => {
|
|
867
607
|
try {
|
|
868
608
|
const scope = resolveScope(options);
|
|
@@ -917,18 +657,18 @@ const agentRoot = program
|
|
|
917
657
|
.command('agent')
|
|
918
658
|
.description('Select agent definitions interactively and distribute to applications')
|
|
919
659
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
920
|
-
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
660
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml');
|
|
921
661
|
agentRoot.action(async (options) => {
|
|
922
662
|
try {
|
|
923
663
|
const scope = resolveScope(options);
|
|
924
|
-
const config = loadSwitchboardConfig(
|
|
664
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
925
665
|
await initTargets(config);
|
|
926
666
|
const selection = await showSubagentSelector({ scope, pageSize: config.ui.page_size });
|
|
927
667
|
if (!selection)
|
|
928
668
|
return;
|
|
929
669
|
console.log();
|
|
930
670
|
printActiveSelection('agents', selection.enabled);
|
|
931
|
-
const out = distributeSubagents(scope, config.applications.
|
|
671
|
+
const out = distributeSubagents(scope, config.applications.enabled, new Set(config.applications.assume_installed));
|
|
932
672
|
if (out.results.length > 0) {
|
|
933
673
|
console.log();
|
|
934
674
|
printDistributionResults({
|
|
@@ -1009,7 +749,7 @@ agentRoot
|
|
|
1009
749
|
.description('Display agent inventory and sync information')
|
|
1010
750
|
.option('--json', 'Output inventory as JSON')
|
|
1011
751
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1012
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
752
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
1013
753
|
.action((options) => {
|
|
1014
754
|
try {
|
|
1015
755
|
const scope = resolveScope(options);
|
|
@@ -1065,11 +805,11 @@ const skillRoot = program
|
|
|
1065
805
|
.command('skill')
|
|
1066
806
|
.description('Select skill bundles interactively and distribute to agents')
|
|
1067
807
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1068
|
-
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
808
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml');
|
|
1069
809
|
skillRoot.action(async (options) => {
|
|
1070
810
|
try {
|
|
1071
811
|
const scope = resolveScope(options);
|
|
1072
|
-
const config = loadSwitchboardConfig(
|
|
812
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1073
813
|
await initTargets(config);
|
|
1074
814
|
const selection = await showSkillSelector({ scope, pageSize: config.ui.page_size });
|
|
1075
815
|
if (!selection)
|
|
@@ -1078,7 +818,7 @@ skillRoot.action(async (options) => {
|
|
|
1078
818
|
printActiveSelection('skills', selection.enabled);
|
|
1079
819
|
const out = distributeSkills(scope, {
|
|
1080
820
|
useAgentsDir: config.distribution.use_agents_dir,
|
|
1081
|
-
activeAppIds: config.applications.
|
|
821
|
+
activeAppIds: config.applications.enabled,
|
|
1082
822
|
assumeInstalled: new Set(config.applications.assume_installed),
|
|
1083
823
|
});
|
|
1084
824
|
if (out.results.length > 0) {
|
|
@@ -1088,7 +828,7 @@ skillRoot.action(async (options) => {
|
|
|
1088
828
|
results: out.results,
|
|
1089
829
|
getTargetLabel: (result) => {
|
|
1090
830
|
if (result.platform === 'agents') {
|
|
1091
|
-
const members = ['codex', 'gemini', 'opencode'].filter((a) => config.applications.
|
|
831
|
+
const members = ['codex', 'gemini', 'opencode'].filter((a) => config.applications.enabled.includes(a));
|
|
1092
832
|
return members.length > 0 ? members.join('+') : 'agents';
|
|
1093
833
|
}
|
|
1094
834
|
return result.platform;
|
|
@@ -1109,7 +849,7 @@ skillRoot
|
|
|
1109
849
|
.description('Display skill inventory and sync information')
|
|
1110
850
|
.option('--json', 'Output inventory as JSON')
|
|
1111
851
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1112
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
852
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
1113
853
|
.action((options) => {
|
|
1114
854
|
try {
|
|
1115
855
|
const scope = resolveScope(options);
|
|
@@ -1215,17 +955,17 @@ const hookRoot = program
|
|
|
1215
955
|
.command('hook')
|
|
1216
956
|
.description('Select hooks interactively and distribute to Claude Code')
|
|
1217
957
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1218
|
-
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
958
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml');
|
|
1219
959
|
hookRoot.action(async (options) => {
|
|
1220
960
|
try {
|
|
1221
961
|
const scope = resolveScope(options);
|
|
1222
|
-
const config = loadSwitchboardConfig(
|
|
962
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1223
963
|
const selection = await showHookSelector({ scope, pageSize: config.ui.page_size });
|
|
1224
964
|
if (!selection)
|
|
1225
965
|
return;
|
|
1226
966
|
console.log();
|
|
1227
967
|
printActiveSelection('hooks', selection.enabled);
|
|
1228
|
-
const out = distributeHooks(scope, config.applications.
|
|
968
|
+
const out = distributeHooks(scope, config.applications.enabled, new Set(config.applications.assume_installed));
|
|
1229
969
|
if (out.results.length > 0) {
|
|
1230
970
|
console.log();
|
|
1231
971
|
printDistributionResults({
|
|
@@ -1248,7 +988,7 @@ hookRoot
|
|
|
1248
988
|
.description('Display hook library entries')
|
|
1249
989
|
.option('--json', 'Output inventory as JSON')
|
|
1250
990
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1251
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
991
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
1252
992
|
.action((options) => {
|
|
1253
993
|
try {
|
|
1254
994
|
const scope = resolveScope(options);
|
|
@@ -1377,114 +1117,6 @@ hookRoot
|
|
|
1377
1117
|
process.exit(1);
|
|
1378
1118
|
}
|
|
1379
1119
|
});
|
|
1380
|
-
async function applyToAgents(scope, enabledServerNames, options) {
|
|
1381
|
-
const mcpConfig = loadMcpConfigWithPlugins(scope);
|
|
1382
|
-
const switchboardConfig = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1383
|
-
await initTargets(switchboardConfig);
|
|
1384
|
-
const useSpinner = options?.useSpinner ?? true;
|
|
1385
|
-
const assumeInstalled = options?.assumeInstalled ?? new Set(switchboardConfig.applications.assume_installed);
|
|
1386
|
-
const results = [];
|
|
1387
|
-
if (switchboardConfig.applications.active.length === 0) {
|
|
1388
|
-
if (useSpinner) {
|
|
1389
|
-
console.log(chalk.yellow('\n⚠ No applications found in the active configuration stack.'));
|
|
1390
|
-
console.log();
|
|
1391
|
-
console.log('Add applications under the relevant TOML layer (user, profile, or project).');
|
|
1392
|
-
console.log(chalk.dim(' Example: [applications]\n active = ["claude-code", "cursor"]'));
|
|
1393
|
-
}
|
|
1394
|
-
return results;
|
|
1395
|
-
}
|
|
1396
|
-
// Global MCP servers list (from UI selection or config)
|
|
1397
|
-
const globalMcpServers = enabledServerNames ?? loadMcpEnabledState(scope);
|
|
1398
|
-
for (const agentId of switchboardConfig.applications.active) {
|
|
1399
|
-
const spinner = useSpinner ? ora({ indent: 2 }).start(`Applying to ${agentId}...`) : null;
|
|
1400
|
-
const persist = (symbol, text) => {
|
|
1401
|
-
if (!spinner)
|
|
1402
|
-
return;
|
|
1403
|
-
spinner.stopAndPersist({ symbol: ` ${symbol}`, text });
|
|
1404
|
-
};
|
|
1405
|
-
try {
|
|
1406
|
-
const agentMcpConfig = resolveEffectiveSectionConfig('mcp', agentId, scope);
|
|
1407
|
-
// If user selected servers via UI, use that as base; otherwise use per-agent resolved config
|
|
1408
|
-
const agentActiveServers = enabledServerNames
|
|
1409
|
-
? agentMcpConfig.enabled.filter((s) => globalMcpServers.includes(s))
|
|
1410
|
-
: agentMcpConfig.enabled;
|
|
1411
|
-
// Filter to only enabled servers for this agent
|
|
1412
|
-
const activeSet = new Set(agentActiveServers);
|
|
1413
|
-
const enabledServers = Object.fromEntries(Object.entries(mcpConfig.mcpServers).filter(([name]) => activeSet.has(name)));
|
|
1414
|
-
const configToApply = { mcpServers: enabledServers };
|
|
1415
|
-
const target = getTargetById(agentId);
|
|
1416
|
-
if (!assumeInstalled.has(agentId) && target?.isInstalled?.() === false) {
|
|
1417
|
-
persist(chalk.gray('○'), `${chalk.cyan(agentId)} ${chalk.gray('(not installed, skipped)')}`);
|
|
1418
|
-
results.push({
|
|
1419
|
-
application: agentId,
|
|
1420
|
-
filePath: '(not installed)',
|
|
1421
|
-
status: 'skipped',
|
|
1422
|
-
reason: 'not installed',
|
|
1423
|
-
});
|
|
1424
|
-
continue;
|
|
1425
|
-
}
|
|
1426
|
-
if (!target?.mcp) {
|
|
1427
|
-
persist(chalk.yellow('⚠'), `${chalk.cyan(agentId)} - no MCP handler (skipped)`);
|
|
1428
|
-
results.push({
|
|
1429
|
-
application: agentId,
|
|
1430
|
-
filePath: '(unknown)',
|
|
1431
|
-
status: 'skipped',
|
|
1432
|
-
reason: 'no MCP handler',
|
|
1433
|
-
});
|
|
1434
|
-
continue;
|
|
1435
|
-
}
|
|
1436
|
-
const mcpHandler = target.mcp;
|
|
1437
|
-
const readFileSafe = (p) => {
|
|
1438
|
-
try {
|
|
1439
|
-
return fs.readFileSync(p, 'utf-8');
|
|
1440
|
-
}
|
|
1441
|
-
catch {
|
|
1442
|
-
return null;
|
|
1443
|
-
}
|
|
1444
|
-
};
|
|
1445
|
-
if (scope?.project && mcpHandler.applyProjectConfig) {
|
|
1446
|
-
const projectPath = mcpHandler.projectConfigPath?.(scope.project) ?? 'project config';
|
|
1447
|
-
const before = readFileSafe(projectPath);
|
|
1448
|
-
mcpHandler.applyProjectConfig(scope.project, configToApply);
|
|
1449
|
-
const after = readFileSafe(projectPath);
|
|
1450
|
-
const changed = before !== after;
|
|
1451
|
-
persist(chalk.green('✓'), `${chalk.cyan(agentId)} ${chalk.dim(shortenPath(projectPath))}`);
|
|
1452
|
-
results.push({
|
|
1453
|
-
application: agentId,
|
|
1454
|
-
filePath: projectPath,
|
|
1455
|
-
status: changed ? 'written' : 'skipped',
|
|
1456
|
-
reason: changed ? 'applied' : 'up-to-date',
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
else {
|
|
1460
|
-
const configPath = mcpHandler.configPath();
|
|
1461
|
-
const before = readFileSafe(configPath);
|
|
1462
|
-
mcpHandler.applyConfig(configToApply);
|
|
1463
|
-
const after = readFileSafe(configPath);
|
|
1464
|
-
const changed = before !== after;
|
|
1465
|
-
persist(chalk.green('✓'), `${chalk.cyan(agentId)} ${chalk.dim(shortenPath(configPath))}`);
|
|
1466
|
-
results.push({
|
|
1467
|
-
application: agentId,
|
|
1468
|
-
filePath: configPath,
|
|
1469
|
-
status: changed ? 'written' : 'skipped',
|
|
1470
|
-
reason: changed ? 'applied' : 'up-to-date',
|
|
1471
|
-
});
|
|
1472
|
-
}
|
|
1473
|
-
}
|
|
1474
|
-
catch (error) {
|
|
1475
|
-
if (error instanceof Error) {
|
|
1476
|
-
persist(chalk.yellow('⚠'), `${chalk.cyan(agentId)} - ${error.message} (skipped)`);
|
|
1477
|
-
results.push({
|
|
1478
|
-
application: agentId,
|
|
1479
|
-
filePath: '(unknown)',
|
|
1480
|
-
status: 'error',
|
|
1481
|
-
error: `${error.message} (skipped)`,
|
|
1482
|
-
});
|
|
1483
|
-
}
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
return results;
|
|
1487
|
-
}
|
|
1488
1120
|
/**
|
|
1489
1121
|
* Show summary of enabled/disabled servers and applied agents
|
|
1490
1122
|
*/
|
|
@@ -1507,10 +1139,10 @@ function showSummary(selectedServers, scope) {
|
|
|
1507
1139
|
console.log(` ${chalk.gray('✗')} ${server}`);
|
|
1508
1140
|
}
|
|
1509
1141
|
}
|
|
1510
|
-
const switchboardConfig = loadSwitchboardConfig(
|
|
1511
|
-
if (switchboardConfig.applications.
|
|
1512
|
-
console.log(chalk.blue(`\nApplied to applications (${switchboardConfig.applications.
|
|
1513
|
-
for (const agent of switchboardConfig.applications.
|
|
1142
|
+
const switchboardConfig = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1143
|
+
if (switchboardConfig.applications.enabled.length > 0) {
|
|
1144
|
+
console.log(chalk.blue(`\nApplied to applications (${switchboardConfig.applications.enabled.length}):`));
|
|
1145
|
+
for (const agent of switchboardConfig.applications.enabled) {
|
|
1514
1146
|
console.log(` ${chalk.dim('•')} ${agent}`);
|
|
1515
1147
|
}
|
|
1516
1148
|
}
|
|
@@ -1539,7 +1171,7 @@ const pluginRoot = program
|
|
|
1539
1171
|
.command('plugin')
|
|
1540
1172
|
.description('Manage plugins: interactive selection, install/uninstall, marketplace sources')
|
|
1541
1173
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1542
|
-
.option('--project <path>', 'Project directory containing .asb.toml');
|
|
1174
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml');
|
|
1543
1175
|
pluginRoot.action((_options) => {
|
|
1544
1176
|
pluginRoot.outputHelp();
|
|
1545
1177
|
});
|
|
@@ -1548,13 +1180,13 @@ pluginRoot
|
|
|
1548
1180
|
.alias('ls')
|
|
1549
1181
|
.description('List all discoverable plugins from configured sources')
|
|
1550
1182
|
.option('-p, --profile <name>', 'Profile configuration to use')
|
|
1551
|
-
.option('--project <path>', 'Project directory containing .asb.toml')
|
|
1183
|
+
.option('-P, --project <path>', 'Project directory containing .asb.toml')
|
|
1552
1184
|
.option('--json', 'Output as JSON')
|
|
1553
1185
|
.action((options) => {
|
|
1554
1186
|
try {
|
|
1555
1187
|
const index = buildPluginIndex();
|
|
1556
1188
|
const scope = resolveScope(options);
|
|
1557
|
-
const config = loadSwitchboardConfig(
|
|
1189
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1558
1190
|
const enabledList = config.plugins.enabled;
|
|
1559
1191
|
const enabledSet = new Set(enabledList);
|
|
1560
1192
|
if (options.json) {
|
|
@@ -1606,7 +1238,7 @@ pluginRoot
|
|
|
1606
1238
|
function pluginEnableAction(id, options) {
|
|
1607
1239
|
try {
|
|
1608
1240
|
const scope = resolveScope(options);
|
|
1609
|
-
const config = loadSwitchboardConfig(
|
|
1241
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1610
1242
|
if (config.plugins.enabled.includes(id)) {
|
|
1611
1243
|
console.log(chalk.yellow(`⚠ Plugin "${id}" is already enabled.`));
|
|
1612
1244
|
return;
|
|
@@ -1623,7 +1255,7 @@ function pluginEnableAction(id, options) {
|
|
|
1623
1255
|
...(layer.plugins ?? {}),
|
|
1624
1256
|
enabled: [...(layer.plugins?.enabled ?? []), id],
|
|
1625
1257
|
},
|
|
1626
|
-
}),
|
|
1258
|
+
}), scopeToLayerOptions(scope));
|
|
1627
1259
|
console.log(chalk.green(`✓ Plugin "${id}" enabled.`));
|
|
1628
1260
|
}
|
|
1629
1261
|
catch (error) {
|
|
@@ -1636,7 +1268,7 @@ function pluginEnableAction(id, options) {
|
|
|
1636
1268
|
function pluginDisableAction(id, options) {
|
|
1637
1269
|
try {
|
|
1638
1270
|
const scope = resolveScope(options);
|
|
1639
|
-
const config = loadSwitchboardConfig(
|
|
1271
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1640
1272
|
if (!config.plugins.enabled.includes(id)) {
|
|
1641
1273
|
console.log(chalk.yellow(`⚠ Plugin "${id}" is not enabled.`));
|
|
1642
1274
|
return;
|
|
@@ -1647,7 +1279,7 @@ function pluginDisableAction(id, options) {
|
|
|
1647
1279
|
...(layer.plugins ?? {}),
|
|
1648
1280
|
enabled: (layer.plugins?.enabled ?? []).filter((x) => x !== id),
|
|
1649
1281
|
},
|
|
1650
|
-
}),
|
|
1282
|
+
}), scopeToLayerOptions(scope));
|
|
1651
1283
|
console.log(chalk.green(`✓ Plugin "${id}" disabled.`));
|
|
1652
1284
|
}
|
|
1653
1285
|
catch (error) {
|
|
@@ -1660,7 +1292,7 @@ function pluginDisableAction(id, options) {
|
|
|
1660
1292
|
function pluginUninstallAction(id, options) {
|
|
1661
1293
|
try {
|
|
1662
1294
|
const scope = resolveScope(options);
|
|
1663
|
-
const config = loadSwitchboardConfig(
|
|
1295
|
+
const config = loadSwitchboardConfig(scopeToLayerOptions(scope));
|
|
1664
1296
|
if (!config.plugins.enabled.includes(id)) {
|
|
1665
1297
|
console.log(chalk.yellow(`⚠ Plugin "${id}" is not enabled.`));
|
|
1666
1298
|
return;
|
|
@@ -1671,7 +1303,7 @@ function pluginUninstallAction(id, options) {
|
|
|
1671
1303
|
...(layer.plugins ?? {}),
|
|
1672
1304
|
enabled: (layer.plugins?.enabled ?? []).filter((x) => x !== id),
|
|
1673
1305
|
},
|
|
1674
|
-
}),
|
|
1306
|
+
}), scopeToLayerOptions(scope));
|
|
1675
1307
|
console.log(chalk.green(`✓ Plugin "${id}" uninstalled.`));
|
|
1676
1308
|
}
|
|
1677
1309
|
catch (error) {
|
|
@@ -1683,7 +1315,7 @@ function pluginUninstallAction(id, options) {
|
|
|
1683
1315
|
}
|
|
1684
1316
|
const pluginScopeOpts = [
|
|
1685
1317
|
['-p, --profile <name>', 'Profile configuration to use'],
|
|
1686
|
-
['--project <path>', 'Project directory containing .asb.toml'],
|
|
1318
|
+
['-P, --project <path>', 'Project directory containing .asb.toml'],
|
|
1687
1319
|
];
|
|
1688
1320
|
const enableCmd = pluginRoot.command('enable <id>').description('Add a plugin to the enabled list');
|
|
1689
1321
|
for (const [flag, desc] of pluginScopeOpts)
|