feat: add TypeScript installation wizard with @clack/prompts TUI
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>
This commit is contained in:
73
src/stages/soul-setup.ts
Normal file
73
src/stages/soul-setup.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { WizardPrompter } from '../prompter/interface.js';
|
||||
import type { WizardState, CommunicationStyle } from '../types.js';
|
||||
import { DEFAULTS } from '../constants.js';
|
||||
|
||||
export async function soulSetupStage(
|
||||
p: WizardPrompter,
|
||||
state: WizardState,
|
||||
): Promise<void> {
|
||||
if (state.installAction === 'keep') return;
|
||||
|
||||
p.separator();
|
||||
p.note(
|
||||
'Your agent identity defines how AI assistants behave,\n' +
|
||||
'their principles, and communication style.\n' +
|
||||
'This creates SOUL.md.',
|
||||
'Agent Identity',
|
||||
);
|
||||
|
||||
if (!state.soul.agentName) {
|
||||
state.soul.agentName = await p.text({
|
||||
message: 'What name should agents use?',
|
||||
placeholder: 'e.g., Jarvis, Assistant, Mosaic',
|
||||
defaultValue: DEFAULTS.agentName,
|
||||
validate: (v) => {
|
||||
if (v.length === 0) return 'Name cannot be empty';
|
||||
if (v.length > 50) return 'Name must be under 50 characters';
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (state.mode === 'advanced') {
|
||||
if (!state.soul.roleDescription) {
|
||||
state.soul.roleDescription = await p.text({
|
||||
message: 'Agent role description',
|
||||
placeholder: 'e.g., execution partner and visibility engine',
|
||||
defaultValue: DEFAULTS.roleDescription,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
state.soul.roleDescription ??= DEFAULTS.roleDescription;
|
||||
}
|
||||
|
||||
if (!state.soul.communicationStyle) {
|
||||
state.soul.communicationStyle = await p.select<CommunicationStyle>({
|
||||
message: 'Communication style',
|
||||
options: [
|
||||
{ value: 'direct', label: 'Direct', hint: 'Concise, no fluff, actionable' },
|
||||
{ value: 'friendly', label: 'Friendly', hint: 'Warm but efficient, conversational' },
|
||||
{ value: 'formal', label: 'Formal', hint: 'Professional, structured, thorough' },
|
||||
],
|
||||
initialValue: 'direct',
|
||||
});
|
||||
}
|
||||
|
||||
if (state.mode === 'advanced') {
|
||||
if (!state.soul.accessibility) {
|
||||
state.soul.accessibility = await p.text({
|
||||
message: 'Accessibility preferences',
|
||||
placeholder:
|
||||
"e.g., ADHD-friendly chunking, dyslexia-aware formatting, or 'none'",
|
||||
defaultValue: 'none',
|
||||
});
|
||||
}
|
||||
|
||||
if (!state.soul.customGuardrails) {
|
||||
state.soul.customGuardrails = await p.text({
|
||||
message: 'Custom guardrails (optional)',
|
||||
placeholder: 'e.g., Never auto-commit to main',
|
||||
defaultValue: '',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user