agent-switchboard 0.3.3 → 0.3.5
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/agents/codex.js +1 -1
- package/dist/agents/codex.js.map +1 -1
- package/dist/commands/distribution.d.ts +1 -1
- package/dist/commands/distribution.js +19 -10
- 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 +7 -1
- package/dist/hooks/distribution.js.map +1 -1
- package/dist/index.js +262 -197
- 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 +1 -0
- package/dist/rules/distribution.js +5 -1
- package/dist/rules/distribution.js.map +1 -1
- package/dist/skills/distribution.d.ts +1 -0
- package/dist/skills/distribution.js +30 -16
- package/dist/skills/distribution.js.map +1 -1
- package/dist/subagents/distribution.d.ts +1 -1
- package/dist/subagents/distribution.js +34 -13
- 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 +3 -1
- package/dist/targets/builtin/codex.js.map +1 -1
- package/dist/targets/builtin/cursor.js +4 -2
- 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 +4 -2
- 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,195 +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 activeAppIds = config.applications.active;
|
|
218
|
-
const mcpDistribution = await applyToAgents(scope, undefined, { useSpinner: false });
|
|
219
|
-
const ruleDistribution = distributeRules(undefined, { activeAppIds }, scope);
|
|
220
|
-
const commandDistribution = distributeCommands(scope, activeAppIds);
|
|
221
|
-
const agentDistribution = distributeSubagents(scope, activeAppIds);
|
|
222
|
-
const skillDistribution = distributeSkills(scope, {
|
|
223
|
-
useAgentsDir: config.distribution.use_agents_dir,
|
|
224
|
-
activeAppIds,
|
|
225
|
-
});
|
|
226
|
-
const hookDistribution = distributeHooks(scope, activeAppIds);
|
|
227
|
-
const distSections = [
|
|
228
|
-
{
|
|
229
|
-
label: 'mcp',
|
|
230
|
-
results: mcpDistribution,
|
|
231
|
-
emptyMessage: 'no apps configured',
|
|
232
|
-
getTargetLabel: (r) => r.application,
|
|
233
|
-
getPath: (r) => r.filePath,
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
label: 'rules',
|
|
237
|
-
results: ruleDistribution.results,
|
|
238
|
-
emptyMessage: 'none',
|
|
239
|
-
getTargetLabel: (r) => r.agent,
|
|
240
|
-
getPath: (r) => r.filePath,
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
label: 'commands',
|
|
244
|
-
results: commandDistribution.results,
|
|
245
|
-
emptyMessage: 'none',
|
|
246
|
-
getTargetLabel: (r) => r.platform,
|
|
247
|
-
getPath: (r) => r.filePath,
|
|
248
|
-
},
|
|
249
|
-
{
|
|
250
|
-
label: 'agents',
|
|
251
|
-
results: agentDistribution.results,
|
|
252
|
-
emptyMessage: 'none',
|
|
253
|
-
getTargetLabel: (r) => r.platform,
|
|
254
|
-
getPath: (r) => r.filePath,
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
label: 'skills',
|
|
258
|
-
results: skillDistribution.results,
|
|
259
|
-
emptyMessage: 'none',
|
|
260
|
-
getTargetLabel: (r) => {
|
|
261
|
-
const sr = r;
|
|
262
|
-
return sr.platform === 'agents' ? 'codex+gemini+opencode' : sr.platform;
|
|
263
|
-
},
|
|
264
|
-
getPath: (r) => r.targetDir,
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
label: 'hooks',
|
|
268
|
-
results: hookDistribution.results,
|
|
269
|
-
emptyMessage: 'none',
|
|
270
|
-
getTargetLabel: (r) => r.platform,
|
|
271
|
-
getPath: (r) => {
|
|
272
|
-
const hr = r;
|
|
273
|
-
return 'filePath' in hr ? hr.filePath : hr.targetDir;
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
];
|
|
277
|
-
const { hasErrors } = printCompactDistributions(distSections);
|
|
278
|
-
console.log();
|
|
279
|
-
if (hasErrors) {
|
|
280
|
-
console.log(chalk.red('✗ Sync completed with errors.'));
|
|
281
|
-
process.exit(1);
|
|
121
|
+
console.log(chalk.green('✓ Sync complete.'));
|
|
282
122
|
}
|
|
283
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
|
+
}
|
|
284
132
|
console.log(chalk.green('✓ Sync complete.'));
|
|
285
133
|
}
|
|
286
134
|
}
|
|
@@ -291,6 +139,211 @@ program
|
|
|
291
139
|
process.exit(1);
|
|
292
140
|
}
|
|
293
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
|
+
}
|
|
294
347
|
function resolveScope(input) {
|
|
295
348
|
if (!input)
|
|
296
349
|
return undefined;
|
|
@@ -670,7 +723,11 @@ ruleCommand.action(async (options) => {
|
|
|
670
723
|
}
|
|
671
724
|
}
|
|
672
725
|
}
|
|
673
|
-
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);
|
|
674
731
|
if (distribution.results.length > 0) {
|
|
675
732
|
console.log();
|
|
676
733
|
printDistributionResults({
|
|
@@ -721,7 +778,7 @@ commandRoot.action(async (options) => {
|
|
|
721
778
|
return;
|
|
722
779
|
console.log();
|
|
723
780
|
printActiveSelection('commands', selection.enabled);
|
|
724
|
-
const out = distributeCommands(scope, config.applications.active);
|
|
781
|
+
const out = distributeCommands(scope, config.applications.active, new Set(config.applications.assume_installed));
|
|
725
782
|
if (out.results.length > 0) {
|
|
726
783
|
console.log();
|
|
727
784
|
printDistributionResults({
|
|
@@ -871,7 +928,7 @@ agentRoot.action(async (options) => {
|
|
|
871
928
|
return;
|
|
872
929
|
console.log();
|
|
873
930
|
printActiveSelection('agents', selection.enabled);
|
|
874
|
-
const out = distributeSubagents(scope, config.applications.active);
|
|
931
|
+
const out = distributeSubagents(scope, config.applications.active, new Set(config.applications.assume_installed));
|
|
875
932
|
if (out.results.length > 0) {
|
|
876
933
|
console.log();
|
|
877
934
|
printDistributionResults({
|
|
@@ -1022,13 +1079,20 @@ skillRoot.action(async (options) => {
|
|
|
1022
1079
|
const out = distributeSkills(scope, {
|
|
1023
1080
|
useAgentsDir: config.distribution.use_agents_dir,
|
|
1024
1081
|
activeAppIds: config.applications.active,
|
|
1082
|
+
assumeInstalled: new Set(config.applications.assume_installed),
|
|
1025
1083
|
});
|
|
1026
1084
|
if (out.results.length > 0) {
|
|
1027
1085
|
console.log();
|
|
1028
1086
|
printDistributionResults({
|
|
1029
1087
|
title: 'Skill distribution',
|
|
1030
1088
|
results: out.results,
|
|
1031
|
-
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
|
+
},
|
|
1032
1096
|
getPath: (result) => result.targetDir,
|
|
1033
1097
|
});
|
|
1034
1098
|
}
|
|
@@ -1161,7 +1225,7 @@ hookRoot.action(async (options) => {
|
|
|
1161
1225
|
return;
|
|
1162
1226
|
console.log();
|
|
1163
1227
|
printActiveSelection('hooks', selection.enabled);
|
|
1164
|
-
const out = distributeHooks(scope, config.applications.active);
|
|
1228
|
+
const out = distributeHooks(scope, config.applications.active, new Set(config.applications.assume_installed));
|
|
1165
1229
|
if (out.results.length > 0) {
|
|
1166
1230
|
console.log();
|
|
1167
1231
|
printDistributionResults({
|
|
@@ -1314,10 +1378,11 @@ hookRoot
|
|
|
1314
1378
|
}
|
|
1315
1379
|
});
|
|
1316
1380
|
async function applyToAgents(scope, enabledServerNames, options) {
|
|
1317
|
-
const mcpConfig = loadMcpConfigWithPlugins();
|
|
1381
|
+
const mcpConfig = loadMcpConfigWithPlugins(scope);
|
|
1318
1382
|
const switchboardConfig = loadSwitchboardConfig(scopeToLoadOptions(scope));
|
|
1319
1383
|
await initTargets(switchboardConfig);
|
|
1320
1384
|
const useSpinner = options?.useSpinner ?? true;
|
|
1385
|
+
const assumeInstalled = options?.assumeInstalled ?? new Set(switchboardConfig.applications.assume_installed);
|
|
1321
1386
|
const results = [];
|
|
1322
1387
|
if (switchboardConfig.applications.active.length === 0) {
|
|
1323
1388
|
if (useSpinner) {
|
|
@@ -1348,7 +1413,7 @@ async function applyToAgents(scope, enabledServerNames, options) {
|
|
|
1348
1413
|
const enabledServers = Object.fromEntries(Object.entries(mcpConfig.mcpServers).filter(([name]) => activeSet.has(name)));
|
|
1349
1414
|
const configToApply = { mcpServers: enabledServers };
|
|
1350
1415
|
const target = getTargetById(agentId);
|
|
1351
|
-
if (target?.isInstalled?.() === false) {
|
|
1416
|
+
if (!assumeInstalled.has(agentId) && target?.isInstalled?.() === false) {
|
|
1352
1417
|
persist(chalk.gray('○'), `${chalk.cyan(agentId)} ${chalk.gray('(not installed, skipped)')}`);
|
|
1353
1418
|
results.push({
|
|
1354
1419
|
application: agentId,
|