agent-switchboard 0.3.2 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -0
- package/dist/commands/distribution.d.ts +1 -1
- package/dist/commands/distribution.js +18 -7
- package/dist/commands/distribution.js.map +1 -1
- package/dist/config/application-config.js +5 -1
- package/dist/config/application-config.js.map +1 -1
- package/dist/config/mcp-config.d.ts +2 -1
- package/dist/config/mcp-config.js +3 -2
- package/dist/config/mcp-config.js.map +1 -1
- package/dist/config/schemas.d.ts +21 -0
- package/dist/config/schemas.js +2 -0
- package/dist/config/schemas.js.map +1 -1
- package/dist/hooks/distribution.d.ts +1 -1
- package/dist/hooks/distribution.js +10 -1
- package/dist/hooks/distribution.js.map +1 -1
- package/dist/index.js +263 -195
- package/dist/index.js.map +1 -1
- package/dist/library/state.d.ts +2 -0
- package/dist/library/state.js +6 -0
- package/dist/library/state.js.map +1 -1
- package/dist/rules/distribution.d.ts +2 -0
- package/dist/rules/distribution.js +7 -2
- package/dist/rules/distribution.js.map +1 -1
- package/dist/skills/distribution.d.ts +2 -0
- package/dist/skills/distribution.js +31 -6
- package/dist/skills/distribution.js.map +1 -1
- package/dist/subagents/distribution.d.ts +1 -1
- package/dist/subagents/distribution.js +33 -10
- package/dist/subagents/distribution.js.map +1 -1
- package/dist/targets/builtin/claude-code.js +2 -0
- package/dist/targets/builtin/claude-code.js.map +1 -1
- package/dist/targets/builtin/codex.js +2 -0
- package/dist/targets/builtin/codex.js.map +1 -1
- package/dist/targets/builtin/cursor.js +2 -0
- package/dist/targets/builtin/cursor.js.map +1 -1
- package/dist/targets/builtin/gemini.js +2 -0
- package/dist/targets/builtin/gemini.js.map +1 -1
- package/dist/targets/builtin/opencode.js +2 -0
- package/dist/targets/builtin/opencode.js.map +1 -1
- package/dist/targets/registry.d.ts +5 -2
- package/dist/targets/registry.js +6 -3
- package/dist/targets/registry.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -22,7 +22,7 @@ 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, loadMcpEnabledState, saveMcpEnabledState, } from './library/state.js';
|
|
25
|
+
import { loadLibraryStateSection, loadMcpEnabledState, resetAgentSyncCache, saveMcpEnabledState, } from './library/state.js';
|
|
26
26
|
import { readMarketplace } from './marketplace/reader.js';
|
|
27
27
|
import { buildPluginIndex } from './plugins/index.js';
|
|
28
28
|
import { composeActiveRules } from './rules/composer.js';
|
|
@@ -37,7 +37,7 @@ import { distributeSubagents } from './subagents/distribution.js';
|
|
|
37
37
|
import { importSubagentFromFile } from './subagents/importer.js';
|
|
38
38
|
import { buildSubagentInventory } from './subagents/inventory.js';
|
|
39
39
|
import { initTargets } from './targets/init.js';
|
|
40
|
-
import { filterInstalled, getTargetById, getTargetsForSection } from './targets/registry.js';
|
|
40
|
+
import { filterInstalled, getTargetById, getTargetsForSection, registerConfigTargets, } 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';
|
|
@@ -73,9 +73,6 @@ program
|
|
|
73
73
|
.action(async (options) => {
|
|
74
74
|
try {
|
|
75
75
|
const scope = resolveScope(options);
|
|
76
|
-
const loadOptions = scopeToLoadOptions(scope);
|
|
77
|
-
const { config, layers } = loadSwitchboardConfigWithLayers(loadOptions);
|
|
78
|
-
await initTargets(config);
|
|
79
76
|
console.log(chalk.yellow('⚠ Sync overwrites agent config without diff.'));
|
|
80
77
|
console.log();
|
|
81
78
|
if (options.update !== false) {
|
|
@@ -92,193 +89,46 @@ program
|
|
|
92
89
|
}
|
|
93
90
|
}
|
|
94
91
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
: chalk.gray('none configured');
|
|
114
|
-
console.log(`${chalk.blue('Apps:')} ${appsLabel}`);
|
|
115
|
-
console.log();
|
|
116
|
-
const cursorSkillsDeduped = config.applications.active.includes('claude-code') &&
|
|
117
|
-
resolveEffectiveSectionConfig('skills', 'claude-code', scope).enabled.length > 0;
|
|
118
|
-
console.log(chalk.blue('Inventory:'));
|
|
119
|
-
{
|
|
120
|
-
const sections = ['mcp', 'rules', 'commands', 'agents', 'skills', 'hooks'];
|
|
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
|
-
}
|
|
129
|
-
const termWidth = process.stdout.columns || 80;
|
|
130
|
-
const maxSectionLen = Math.max(...sections.map((s) => s.length));
|
|
131
|
-
const maxCountLen = Math.max(...sections.map((s) => `(${config[s].enabled.length})`.length));
|
|
132
|
-
const prefixPlainLen = 2 + maxSectionLen + 1 + maxCountLen + 2;
|
|
133
|
-
const fitPreview = (ids, maxWidth) => {
|
|
134
|
-
if (ids.length === 0)
|
|
135
|
-
return chalk.gray('none');
|
|
136
|
-
const full = ids.join(', ');
|
|
137
|
-
if (full.length <= maxWidth)
|
|
138
|
-
return full;
|
|
139
|
-
let text = '';
|
|
140
|
-
let shown = 0;
|
|
141
|
-
for (let i = 0; i < ids.length; i++) {
|
|
142
|
-
const sep = shown > 0 ? ', ' : '';
|
|
143
|
-
const candidate = text + sep + ids[i];
|
|
144
|
-
const remaining = ids.length - (i + 1);
|
|
145
|
-
if (remaining > 0) {
|
|
146
|
-
const suffix = `, ... (+${remaining} more)`;
|
|
147
|
-
if (candidate.length + suffix.length > maxWidth && shown > 0) {
|
|
148
|
-
const left = ids.length - shown;
|
|
149
|
-
return `${text}${chalk.gray(`, ... (+${left} more)`)}`;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
text = candidate;
|
|
153
|
-
shown++;
|
|
154
|
-
}
|
|
155
|
-
return text;
|
|
156
|
-
};
|
|
157
|
-
for (const section of sections) {
|
|
158
|
-
const globalActive = config[section].enabled;
|
|
159
|
-
const globalCount = globalActive.length;
|
|
160
|
-
const supported = new Set(sectionPlatforms[section] ?? []);
|
|
161
|
-
const applicableApps = config.applications.active.filter((id) => supported.has(id));
|
|
162
|
-
const effectiveByApp = new Map();
|
|
163
|
-
for (const appId of applicableApps) {
|
|
164
|
-
effectiveByApp.set(appId, resolveEffectiveSectionConfig(section, appId, scope).enabled);
|
|
165
|
-
}
|
|
166
|
-
const perAppParts = applicableApps.map((appId) => {
|
|
167
|
-
const eff = effectiveByApp.get(appId) ?? [];
|
|
168
|
-
const delta = eff.length - globalCount;
|
|
169
|
-
const d = delta === 0 ? '' : delta > 0 ? `(+${delta})` : `(${delta})`;
|
|
170
|
-
return `${appId}:${eff.length}${d}`;
|
|
171
|
-
});
|
|
172
|
-
const union = new Set();
|
|
173
|
-
for (const [, ids] of effectiveByApp) {
|
|
174
|
-
for (const id of ids)
|
|
175
|
-
union.add(id);
|
|
176
|
-
}
|
|
177
|
-
const previewIds = globalActive.length > 0 ? [...globalActive] : [...union];
|
|
178
|
-
const paddedSection = section.padEnd(maxSectionLen);
|
|
179
|
-
const countStr = `(${globalCount})`.padStart(maxCountLen);
|
|
180
|
-
const appsStr = perAppParts.join(' ');
|
|
181
|
-
console.log(` ${chalk.cyan(paddedSection)} ${chalk.gray(countStr)} ${appsStr}`);
|
|
182
|
-
if (previewIds.length > 0) {
|
|
183
|
-
const indent = ' '.repeat(prefixPlainLen);
|
|
184
|
-
const previewWidth = Math.max(20, termWidth - prefixPlainLen - 2);
|
|
185
|
-
const preview = fitPreview(previewIds, previewWidth);
|
|
186
|
-
console.log(`${indent}${chalk.gray('→')} ${preview}`);
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
// Show enabled plugins summary
|
|
191
|
-
{
|
|
192
|
-
const pluginIndex = buildPluginIndex();
|
|
193
|
-
const enabledPluginRefs = config.plugins.enabled;
|
|
194
|
-
if (enabledPluginRefs.length > 0) {
|
|
195
|
-
const names = enabledPluginRefs
|
|
196
|
-
.map((pid) => {
|
|
197
|
-
const p = pluginIndex.get(pid);
|
|
198
|
-
return p ? pid : chalk.strikethrough(pid);
|
|
199
|
-
})
|
|
200
|
-
.join(', ');
|
|
201
|
-
console.log(` ${chalk.magenta('plugins')} ${chalk.gray(`(${enabledPluginRefs.length})`)} ${names}`);
|
|
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);
|
|
202
110
|
}
|
|
203
|
-
|
|
204
|
-
|
|
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);
|
|
205
120
|
}
|
|
206
|
-
|
|
207
|
-
console.log();
|
|
208
|
-
const notes = ['rules, skills also distribute to gemini'];
|
|
209
|
-
if (cursorSkillsDeduped)
|
|
210
|
-
notes.push('cursor reads skills via claude-code');
|
|
211
|
-
for (let i = 0; i < notes.length; i++) {
|
|
212
|
-
const prefix = i === 0 ? ' Note: ' : ' ';
|
|
213
|
-
const suffix = i === notes.length - 1 ? '.' : '.';
|
|
214
|
-
console.log(chalk.gray(`${prefix}${notes[i]}${suffix}`));
|
|
215
|
-
}
|
|
216
|
-
console.log();
|
|
217
|
-
const mcpDistribution = await applyToAgents(scope, undefined, { useSpinner: false });
|
|
218
|
-
const ruleDistribution = distributeRules(undefined, undefined, scope);
|
|
219
|
-
const commandDistribution = distributeCommands(scope);
|
|
220
|
-
const agentDistribution = distributeSubagents(scope);
|
|
221
|
-
const skillDistribution = distributeSkills(scope, {
|
|
222
|
-
useAgentsDir: config.distribution.use_agents_dir,
|
|
223
|
-
});
|
|
224
|
-
const hookDistribution = distributeHooks(scope);
|
|
225
|
-
const distSections = [
|
|
226
|
-
{
|
|
227
|
-
label: 'mcp',
|
|
228
|
-
results: mcpDistribution,
|
|
229
|
-
emptyMessage: 'no apps configured',
|
|
230
|
-
getTargetLabel: (r) => r.application,
|
|
231
|
-
getPath: (r) => r.filePath,
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
label: 'rules',
|
|
235
|
-
results: ruleDistribution.results,
|
|
236
|
-
emptyMessage: 'none',
|
|
237
|
-
getTargetLabel: (r) => r.agent,
|
|
238
|
-
getPath: (r) => r.filePath,
|
|
239
|
-
},
|
|
240
|
-
{
|
|
241
|
-
label: 'commands',
|
|
242
|
-
results: commandDistribution.results,
|
|
243
|
-
emptyMessage: 'none',
|
|
244
|
-
getTargetLabel: (r) => r.platform,
|
|
245
|
-
getPath: (r) => r.filePath,
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
label: 'agents',
|
|
249
|
-
results: agentDistribution.results,
|
|
250
|
-
emptyMessage: 'none',
|
|
251
|
-
getTargetLabel: (r) => r.platform,
|
|
252
|
-
getPath: (r) => r.filePath,
|
|
253
|
-
},
|
|
254
|
-
{
|
|
255
|
-
label: 'skills',
|
|
256
|
-
results: skillDistribution.results,
|
|
257
|
-
emptyMessage: 'none',
|
|
258
|
-
getTargetLabel: (r) => {
|
|
259
|
-
const sr = r;
|
|
260
|
-
return sr.platform === 'agents' ? 'codex+gemini+opencode' : sr.platform;
|
|
261
|
-
},
|
|
262
|
-
getPath: (r) => r.targetDir,
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
label: 'hooks',
|
|
266
|
-
results: hookDistribution.results,
|
|
267
|
-
emptyMessage: 'none',
|
|
268
|
-
getTargetLabel: (r) => r.platform,
|
|
269
|
-
getPath: (r) => {
|
|
270
|
-
const hr = r;
|
|
271
|
-
return 'filePath' in hr ? hr.filePath : hr.targetDir;
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
];
|
|
275
|
-
const { hasErrors } = printCompactDistributions(distSections);
|
|
276
|
-
console.log();
|
|
277
|
-
if (hasErrors) {
|
|
278
|
-
console.log(chalk.red('✗ Sync completed with errors.'));
|
|
279
|
-
process.exit(1);
|
|
121
|
+
console.log(chalk.green('✓ Sync complete.'));
|
|
280
122
|
}
|
|
281
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
|
+
}
|
|
282
132
|
console.log(chalk.green('✓ Sync complete.'));
|
|
283
133
|
}
|
|
284
134
|
}
|
|
@@ -289,6 +139,211 @@ program
|
|
|
289
139
|
process.exit(1);
|
|
290
140
|
}
|
|
291
141
|
});
|
|
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
|
+
}
|
|
292
347
|
function resolveScope(input) {
|
|
293
348
|
if (!input)
|
|
294
349
|
return undefined;
|
|
@@ -668,7 +723,11 @@ ruleCommand.action(async (options) => {
|
|
|
668
723
|
}
|
|
669
724
|
}
|
|
670
725
|
}
|
|
671
|
-
const distribution = distributeRules(composeActiveRules(scope), {
|
|
726
|
+
const distribution = distributeRules(composeActiveRules(scope), {
|
|
727
|
+
force: !selectionChanged,
|
|
728
|
+
activeAppIds: config.applications.active,
|
|
729
|
+
assumeInstalled: new Set(config.applications.assume_installed),
|
|
730
|
+
}, scope);
|
|
672
731
|
if (distribution.results.length > 0) {
|
|
673
732
|
console.log();
|
|
674
733
|
printDistributionResults({
|
|
@@ -719,7 +778,7 @@ commandRoot.action(async (options) => {
|
|
|
719
778
|
return;
|
|
720
779
|
console.log();
|
|
721
780
|
printActiveSelection('commands', selection.enabled);
|
|
722
|
-
const out = distributeCommands(scope);
|
|
781
|
+
const out = distributeCommands(scope, config.applications.active, new Set(config.applications.assume_installed));
|
|
723
782
|
if (out.results.length > 0) {
|
|
724
783
|
console.log();
|
|
725
784
|
printDistributionResults({
|
|
@@ -869,7 +928,7 @@ agentRoot.action(async (options) => {
|
|
|
869
928
|
return;
|
|
870
929
|
console.log();
|
|
871
930
|
printActiveSelection('agents', selection.enabled);
|
|
872
|
-
const out = distributeSubagents(scope);
|
|
931
|
+
const out = distributeSubagents(scope, config.applications.active, new Set(config.applications.assume_installed));
|
|
873
932
|
if (out.results.length > 0) {
|
|
874
933
|
console.log();
|
|
875
934
|
printDistributionResults({
|
|
@@ -1019,13 +1078,21 @@ skillRoot.action(async (options) => {
|
|
|
1019
1078
|
printActiveSelection('skills', selection.enabled);
|
|
1020
1079
|
const out = distributeSkills(scope, {
|
|
1021
1080
|
useAgentsDir: config.distribution.use_agents_dir,
|
|
1081
|
+
activeAppIds: config.applications.active,
|
|
1082
|
+
assumeInstalled: new Set(config.applications.assume_installed),
|
|
1022
1083
|
});
|
|
1023
1084
|
if (out.results.length > 0) {
|
|
1024
1085
|
console.log();
|
|
1025
1086
|
printDistributionResults({
|
|
1026
1087
|
title: 'Skill distribution',
|
|
1027
1088
|
results: out.results,
|
|
1028
|
-
getTargetLabel: (result) =>
|
|
1089
|
+
getTargetLabel: (result) => {
|
|
1090
|
+
if (result.platform === 'agents') {
|
|
1091
|
+
const members = ['codex', 'gemini', 'opencode'].filter((a) => config.applications.active.includes(a));
|
|
1092
|
+
return members.length > 0 ? members.join('+') : 'agents';
|
|
1093
|
+
}
|
|
1094
|
+
return result.platform;
|
|
1095
|
+
},
|
|
1029
1096
|
getPath: (result) => result.targetDir,
|
|
1030
1097
|
});
|
|
1031
1098
|
}
|
|
@@ -1158,7 +1225,7 @@ hookRoot.action(async (options) => {
|
|
|
1158
1225
|
return;
|
|
1159
1226
|
console.log();
|
|
1160
1227
|
printActiveSelection('hooks', selection.enabled);
|
|
1161
|
-
const out = distributeHooks(scope);
|
|
1228
|
+
const out = distributeHooks(scope, config.applications.active, new Set(config.applications.assume_installed));
|
|
1162
1229
|
if (out.results.length > 0) {
|
|
1163
1230
|
console.log();
|
|
1164
1231
|
printDistributionResults({
|
|
@@ -1311,10 +1378,11 @@ hookRoot
|
|
|
1311
1378
|
}
|
|
1312
1379
|
});
|
|
1313
1380
|
async function applyToAgents(scope, enabledServerNames, options) {
|
|
1314
|
-
const mcpConfig = loadMcpConfigWithPlugins();
|
|
1381
|
+
const mcpConfig = loadMcpConfigWithPlugins(scope);
|
|
1315
1382
|
const switchboardConfig = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1316
1383
|
await initTargets(switchboardConfig);
|
|
1317
1384
|
const useSpinner = options?.useSpinner ?? true;
|
|
1385
|
+
const assumeInstalled = options?.assumeInstalled ?? new Set(switchboardConfig.applications.assume_installed);
|
|
1318
1386
|
const results = [];
|
|
1319
1387
|
if (switchboardConfig.applications.active.length === 0) {
|
|
1320
1388
|
if (useSpinner) {
|
|
@@ -1345,7 +1413,7 @@ async function applyToAgents(scope, enabledServerNames, options) {
|
|
|
1345
1413
|
const enabledServers = Object.fromEntries(Object.entries(mcpConfig.mcpServers).filter(([name]) => activeSet.has(name)));
|
|
1346
1414
|
const configToApply = { mcpServers: enabledServers };
|
|
1347
1415
|
const target = getTargetById(agentId);
|
|
1348
|
-
if (target?.isInstalled?.() === false) {
|
|
1416
|
+
if (!assumeInstalled.has(agentId) && target?.isInstalled?.() === false) {
|
|
1349
1417
|
persist(chalk.gray('○'), `${chalk.cyan(agentId)} ${chalk.gray('(not installed, skipped)')}`);
|
|
1350
1418
|
results.push({
|
|
1351
1419
|
application: agentId,
|