Replace bash mosaic-init with a modern 9-stage wizard: - SOUL.md identity, USER.md profile, TOOLS.md configuration - Runtime detection (Claude, Codex, OpenCode) + MCP setup - Skills catalog with categorized selection - Quick Start and Advanced modes - HeadlessPrompter for --non-interactive and CI usage - ConfigService abstraction layer for future DB migration - Bundled as single dist/mosaic-wizard.mjs via tsdown - mosaic init now prefers wizard when Node.js is available - 30 tests covering stages, templates, and integration Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
85 lines
2.3 KiB
TypeScript
85 lines
2.3 KiB
TypeScript
import type { WizardPrompter } from '../prompter/interface.js';
|
|
import type { WizardState } from '../types.js';
|
|
import { loadSkillsCatalog } from '../skills/catalog.js';
|
|
import { SKILL_CATEGORIES, categorizeSkill } from '../skills/categories.js';
|
|
|
|
function truncate(str: string, max: number): string {
|
|
if (str.length <= max) return str;
|
|
return str.slice(0, max - 1) + '\u2026';
|
|
}
|
|
|
|
export async function skillsSelectStage(
|
|
p: WizardPrompter,
|
|
state: WizardState,
|
|
): Promise<void> {
|
|
p.separator();
|
|
|
|
const spin = p.spinner();
|
|
spin.update('Loading skills catalog...');
|
|
|
|
const catalog = loadSkillsCatalog(state.mosaicHome);
|
|
spin.stop(`Found ${catalog.length} available skills`);
|
|
|
|
if (catalog.length === 0) {
|
|
p.warn(
|
|
"No skills found. Run 'mosaic sync' after installation to fetch skills.",
|
|
);
|
|
state.selectedSkills = [];
|
|
return;
|
|
}
|
|
|
|
if (state.mode === 'quick') {
|
|
const defaults = catalog
|
|
.filter((s) => s.recommended)
|
|
.map((s) => s.name);
|
|
state.selectedSkills = defaults;
|
|
p.note(
|
|
`Selected ${defaults.length} recommended skills.\n` +
|
|
`Run 'mosaic sync' later to browse the full catalog.`,
|
|
'Skills',
|
|
);
|
|
return;
|
|
}
|
|
|
|
// Advanced mode: categorized browsing
|
|
p.note(
|
|
'Skills give agents domain expertise for specific tasks.\n' +
|
|
'Browse by category and select the ones you want.\n' +
|
|
"You can always change this later with 'mosaic sync'.",
|
|
'Skills Selection',
|
|
);
|
|
|
|
// Build grouped options
|
|
const grouped: Record<
|
|
string,
|
|
{ value: string; label: string; hint?: string; selected?: boolean }[]
|
|
> = {};
|
|
|
|
// Initialize all categories
|
|
for (const categoryName of Object.keys(SKILL_CATEGORIES)) {
|
|
grouped[categoryName] = [];
|
|
}
|
|
|
|
for (const skill of catalog) {
|
|
const category = categorizeSkill(skill.name, skill.description);
|
|
if (!grouped[category]) grouped[category] = [];
|
|
grouped[category].push({
|
|
value: skill.name,
|
|
label: skill.name,
|
|
hint: truncate(skill.description, 60),
|
|
selected: skill.recommended,
|
|
});
|
|
}
|
|
|
|
// Remove empty categories
|
|
for (const key of Object.keys(grouped)) {
|
|
if (grouped[key].length === 0) delete grouped[key];
|
|
}
|
|
|
|
state.selectedSkills = await p.groupMultiselect({
|
|
message: 'Select skills (space to toggle)',
|
|
options: grouped,
|
|
required: false,
|
|
});
|
|
}
|