diff --git a/packages/mosaic/README.md b/packages/mosaic/README.md new file mode 100644 index 0000000..24f4e43 --- /dev/null +++ b/packages/mosaic/README.md @@ -0,0 +1,340 @@ +# Mosaic Agent Framework + +Universal agent standards layer for Claude Code, Codex, and OpenCode. + +One config, every runtime, same standards. + +> **This repository is a generic framework baseline.** No personal data, credentials, user-specific preferences, or machine-specific paths should be committed. All personalization happens at install time via `mosaic init` or by editing files in `~/.config/mosaic/` after installation. + +## Quick Install + +### Mac / Linux + +```bash +curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh +``` + +### Windows (PowerShell) + +```powershell +irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex +``` + +### From Source (any platform) + +```bash +git clone https://git.mosaicstack.dev/mosaic/bootstrap.git ~/src/mosaic-bootstrap +cd ~/src/mosaic-bootstrap && bash install.sh +``` + +If Node.js 18+ is available, the remote installer automatically uses the TypeScript wizard instead of the bash installer for a richer setup experience. + +The installer will: +- Install the framework to `~/.config/mosaic/` +- Add `~/.config/mosaic/bin` to your PATH +- Sync runtime adapters and skills +- Install and configure sequential-thinking MCP (hard requirement) +- Run a health audit +- Detect existing installs and prompt to keep or overwrite local files +- Prompt you to run `mosaic init` to set up your agent identity + +## First Run + +After install, open a new terminal (or `source ~/.bashrc`) and run: + +```bash +mosaic init +``` + +If Node.js 18+ is installed, this launches an interactive wizard with two modes: + +- **Quick Start** (~2 min): agent name + communication style, sensible defaults for everything else +- **Advanced**: full customization of identity, user profile, tools, runtimes, and skills + +The wizard configures three files loaded into every agent session: +- `SOUL.md` — agent identity contract (name, style, guardrails) +- `USER.md` — your user profile (name, timezone, accessibility, preferences) +- `TOOLS.md` — machine-level tool reference (git providers, credentials, CLI patterns) + +It also detects installed runtimes (Claude, Codex, OpenCode), configures sequential-thinking MCP, and offers curated skill selection from 8 categories. + +### Non-Interactive Mode + +For CI or scripted installs: + +```bash +mosaic init --non-interactive --name Jarvis --style direct --user-name Jason --timezone America/Chicago +``` + +All flags: `--name`, `--role`, `--style`, `--user-name`, `--pronouns`, `--timezone`, `--mosaic-home`, `--source-dir`. + +### Legacy Fallback + +If Node.js is unavailable, `mosaic init` falls back to the bash-based `mosaic-init` script. + +## Launching Agent Sessions + +```bash +mosaic claude # Launch Claude Code with full Mosaic injection +mosaic codex # Launch Codex with full Mosaic injection +mosaic opencode # Launch OpenCode with full Mosaic injection +``` + +The launcher: +1. Verifies `~/.config/mosaic` exists +2. Verifies `SOUL.md` exists (auto-runs `mosaic init` if missing) +3. Injects `AGENTS.md` into the runtime +4. Forwards all arguments to the runtime CLI + +You can still launch runtimes directly (`claude`, `codex`, etc.) — thin runtime adapters will tell the agent to read `~/.config/mosaic/AGENTS.md`. + +## Architecture + +``` +~/.config/mosaic/ +├── AGENTS.md ← THE source of truth (all standards, all runtimes) +├── SOUL.md ← Agent identity (generated by mosaic init) +├── USER.md ← User profile and accessibility (generated by mosaic init) +├── TOOLS.md ← Machine-level tool reference (generated by mosaic init) +├── STANDARDS.md ← Machine-wide standards +├── guides/E2E-DELIVERY.md ← Mandatory E2E software delivery procedure +├── guides/PRD.md ← Mandatory PRD requirements gate before coding +├── guides/DOCUMENTATION.md ← Mandatory documentation standard and gates +├── bin/ ← CLI tools (mosaic, mosaic-init, mosaic-doctor, etc.) +├── dist/ ← Bundled wizard (mosaic-wizard.mjs) +├── guides/ ← Operational guides +├── tools/ ← Tool suites: git, portainer, authentik, coolify, codex, etc. +├── runtime/ ← Runtime adapters + runtime-specific references +│ ├── claude/CLAUDE.md +│ ├── claude/RUNTIME.md +│ ├── opencode/AGENTS.md +│ ├── opencode/RUNTIME.md +│ ├── codex/instructions.md +│ ├── codex/RUNTIME.md +│ └── mcp/SEQUENTIAL-THINKING.json +├── skills/ ← Universal skills (synced from mosaic/agent-skills) +├── skills-local/ ← Local cross-runtime skills +└── templates/ ← SOUL.md template, project templates +``` + +### How AGENTS.md Gets Loaded + +| Launch method | Injection mechanism | +|--------------|-------------------| +| `mosaic claude` | `--append-system-prompt` with composed runtime contract (`AGENTS.md` + runtime reference) | +| `mosaic codex` | Writes composed runtime contract to `~/.codex/instructions.md` before launch | +| `mosaic opencode` | Writes composed runtime contract to `~/.config/opencode/AGENTS.md` before launch | +| `claude` (direct) | `~/.claude/CLAUDE.md` thin pointer → load AGENTS + runtime reference | +| `codex` (direct) | `~/.codex/instructions.md` thin pointer → load AGENTS + runtime reference | +| `opencode` (direct) | `~/.config/opencode/AGENTS.md` thin pointer → load AGENTS + runtime reference | + +Mosaic `AGENTS.md` enforces loading `guides/E2E-DELIVERY.md` before execution and +requires `guides/PRD.md` before coding and `guides/DOCUMENTATION.md` for code/API/auth/infra documentation gates. + +## Management Commands + +```bash +mosaic help # Show all commands +mosaic init # Interactive wizard (or legacy init) +mosaic doctor # Health audit — detect drift and missing files +mosaic sync # Sync skills from canonical source +mosaic bootstrap # Bootstrap a repo with Mosaic standards +mosaic upgrade check # Check release upgrade status (no changes) +mosaic upgrade # Upgrade installed Mosaic release (keeps SOUL.md by default) +mosaic upgrade --dry-run # Preview release upgrade without changes +mosaic upgrade --ref main # Upgrade from a specific branch/tag/commit ref +mosaic upgrade --overwrite # Upgrade release and overwrite local files +mosaic upgrade project ... # Project file cleanup mode (see below) +``` + +## Upgrading Mosaic Release + +Upgrade the installed framework in place: + +```bash +# Default (safe): keep local SOUL.md, USER.md, TOOLS.md + memory +mosaic upgrade + +# Check current/target release info without changing files +mosaic upgrade check + +# Non-interactive +mosaic upgrade --yes + +# Pull a specific ref +mosaic upgrade --ref main + +# Force full overwrite (fresh install semantics) +mosaic upgrade --overwrite --yes +``` + +`mosaic upgrade` re-runs the remote installer and passes install mode controls (`keep`/`overwrite`). +This is the manual upgrade path today and is suitable for future app-driven update checks. + +## Upgrading Projects + +After centralizing AGENTS.md and SOUL.md, existing projects may have stale files: + +```bash +# Preview what would change across all projects +mosaic upgrade project --all --dry-run + +# Apply to all projects +mosaic upgrade project --all + +# Apply to a specific project +mosaic upgrade project ~/src/my-project +``` + +Backward compatibility is preserved for historical usage: + +```bash +mosaic upgrade --all # still routes to project-upgrade +mosaic upgrade ~/src/my-repo # still routes to project-upgrade +``` + +What it does per project: + +| File | Action | +|------|--------| +| `SOUL.md` | Removed — now global at `~/.config/mosaic/SOUL.md` | +| `CLAUDE.md` | Replaced with thin pointer to global AGENTS.md | +| `AGENTS.md` | Stale load-order sections stripped; project content preserved | + +Backups (`.mosaic-bak`) are created before any modification. + +## Universal Skills + +The installer syncs skills from `mosaic/agent-skills` into `~/.config/mosaic/skills/`, then links each skill into runtime directories (`~/.claude/skills`, `~/.codex/skills`, `~/.config/opencode/skills`). + +```bash +mosaic sync # Full sync (clone + link) +~/.config/mosaic/bin/mosaic-sync-skills --link-only # Re-link only +``` + +## Runtime Compatibility + +The installer pushes thin runtime adapters as regular files (not symlinks): + +- `~/.claude/CLAUDE.md` — pointer to `~/.config/mosaic/AGENTS.md` +- `~/.claude/settings.json`, `hooks-config.json`, `context7-integration.md` +- `~/.config/opencode/AGENTS.md` — pointer to `~/.config/mosaic/AGENTS.md` +- `~/.codex/instructions.md` — pointer to `~/.config/mosaic/AGENTS.md` +- `~/.claude/settings.json`, `~/.codex/config.toml`, and `~/.config/opencode/config.json` include sequential-thinking MCP config + +Re-sync manually: + +```bash +~/.config/mosaic/bin/mosaic-link-runtime-assets +``` + +## MCP Registration + +### How MCPs Are Configured in Claude Code + +**MCPs must be registered via `claude mcp add` — not by hand-editing `~/.claude/settings.json`.** + +`settings.json` controls hooks, model, plugins, and allowed commands. The `mcpServers` key in +`settings.json` is silently ignored by Claude Code's MCP loader. The correct file is `~/.claude.json`, +which is managed by the `claude mcp` CLI. + +```bash +# Register a stdio MCP (user scope = all projects, persists across sessions) +claude mcp add --scope user -- npx -y + +# Register an HTTP MCP (e.g. OpenBrain) +claude mcp add --scope user --transport http \ + --header "Authorization: Bearer " + +# List registered MCPs +claude mcp list +``` + +**Scope options:** +- `--scope user` — writes to `~/.claude.json`, available in all projects (recommended for shared tools) +- `--scope project` — writes to `.claude/settings.json` in the project root, committed to the repo +- `--scope local` — default, machine-local only, not committed + +**Transport for HTTP MCPs must be `http`** — not `sse`. `type: "sse"` is a deprecated protocol +that silently fails to connect against FastMCP streamable HTTP servers. + +### sequential-thinking MCP (Hard Requirement) + +sequential-thinking MCP is required for Mosaic Stack. The installer registers it automatically. +To verify or re-register manually: + +```bash +~/.config/mosaic/bin/mosaic-ensure-sequential-thinking +~/.config/mosaic/bin/mosaic-ensure-sequential-thinking --check +``` + +### OpenBrain Semantic Memory (Recommended) + +OpenBrain is the shared cross-agent memory layer. Register once per machine: + +```bash +claude mcp add --scope user --transport http openbrain https://your-openbrain-host/mcp \ + --header "Authorization: Bearer YOUR_TOKEN" +``` + +See [mosaic/openbrain](https://git.mosaicstack.dev/mosaic/openbrain) for setup and API docs. + +## Bootstrap Any Repo + +Attach any repository to the Mosaic standards layer: + +```bash +mosaic bootstrap /path/to/repo +``` + +This creates `.mosaic/`, `scripts/agent/`, and an `AGENTS.md` if missing. + +## Quality Rails + +Apply and verify quality templates: + +```bash +~/.config/mosaic/bin/mosaic-quality-apply --template typescript-node --target /path/to/repo +~/.config/mosaic/bin/mosaic-quality-verify --target /path/to/repo +``` + +Templates: `typescript-node`, `typescript-nextjs`, `monorepo` + +## Health Audit + +```bash +mosaic doctor # Standard audit +~/.config/mosaic/bin/mosaic-doctor --fail-on-warn # Strict mode +``` + +## Wizard Development + +The installation wizard is a TypeScript project in the root of this repo. + +```bash +pnpm install # Install dependencies +pnpm dev # Run wizard from source (tsx) +pnpm build # Bundle to dist/mosaic-wizard.mjs +pnpm test # Run tests (30 tests, vitest) +pnpm typecheck # TypeScript type checking +``` + +The wizard uses `@clack/prompts` for the interactive TUI and supports `--non-interactive` mode via `HeadlessPrompter` for CI and scripted installs. The bundled output (`dist/mosaic-wizard.mjs`) is committed to the repo so installs work without `node_modules`. + +## Re-installing / Updating + +Pull the latest and re-run the installer: + +```bash +cd ~/src/mosaic-bootstrap && git pull && bash install.sh +``` + +If an existing install is detected, the installer prompts for: +- `keep` (recommended): preserve local `SOUL.md`, `USER.md`, `TOOLS.md`, and `memory/` +- `overwrite`: replace everything in `~/.config/mosaic` + +Or use the one-liner again — it always pulls the latest: + +```bash +curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh +``` diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json new file mode 100644 index 0000000..626c5d9 --- /dev/null +++ b/packages/mosaic/package.json @@ -0,0 +1,39 @@ +{ + "name": "@mosaic/mosaic", + "version": "0.1.0", + "type": "module", + "description": "Mosaic installation wizard and meta-package entry point", + "bin": { + "mosaic-wizard": "./dist/mosaic-wizard.mjs" + }, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": ["dist"], + "scripts": { + "build": "tsdown", + "typecheck": "tsc --noEmit", + "lint": "eslint src/", + "test": "vitest run", + "dev": "tsx src/index.ts" + }, + "dependencies": { + "@clack/prompts": "^0.9", + "commander": "^13", + "picocolors": "^1.1", + "yaml": "^2.7", + "zod": "^3.24" + }, + "devDependencies": { + "@types/node": "^22", + "tsdown": "^0.12", + "tsx": "^4", + "typescript": "^5", + "vitest": "^2" + } +} diff --git a/packages/mosaic/src/config/config-service.ts b/packages/mosaic/src/config/config-service.ts new file mode 100644 index 0000000..ae67c65 --- /dev/null +++ b/packages/mosaic/src/config/config-service.ts @@ -0,0 +1,26 @@ +import type { SoulConfig, UserConfig, ToolsConfig, InstallAction } from '../types.js'; +import { FileConfigAdapter } from './file-adapter.js'; + +/** + * ConfigService interface — abstracts config read/write operations. + * Currently backed by FileConfigAdapter (writes .md files from templates). + * Designed for future swap to SqliteConfigAdapter or PostgresConfigAdapter. + */ +export interface ConfigService { + readSoul(): Promise; + readUser(): Promise; + readTools(): Promise; + + writeSoul(config: SoulConfig): Promise; + writeUser(config: UserConfig): Promise; + writeTools(config: ToolsConfig): Promise; + + syncFramework(action: InstallAction): Promise; +} + +export function createConfigService( + mosaicHome: string, + sourceDir: string, +): ConfigService { + return new FileConfigAdapter(mosaicHome, sourceDir); +} diff --git a/packages/mosaic/src/config/file-adapter.ts b/packages/mosaic/src/config/file-adapter.ts new file mode 100644 index 0000000..b8308f9 --- /dev/null +++ b/packages/mosaic/src/config/file-adapter.ts @@ -0,0 +1,163 @@ +import { readFileSync, existsSync } from 'node:fs'; +import { join } from 'node:path'; +import type { ConfigService } from './config-service.js'; +import type { + SoulConfig, + UserConfig, + ToolsConfig, + InstallAction, +} from '../types.js'; +import { soulSchema, userSchema, toolsSchema } from './schemas.js'; +import { renderTemplate } from '../template/engine.js'; +import { + buildSoulTemplateVars, + buildUserTemplateVars, + buildToolsTemplateVars, +} from '../template/builders.js'; +import { atomicWrite, backupFile, syncDirectory } from '../platform/file-ops.js'; + +/** + * Parse a SoulConfig from an existing SOUL.md file. + */ +function parseSoulFromMarkdown(content: string): SoulConfig { + const config: SoulConfig = {}; + + const nameMatch = content.match(/You are \*\*(.+?)\*\*/); + if (nameMatch) config.agentName = nameMatch[1]; + + const roleMatch = content.match(/Role identity: (.+)/); + if (roleMatch) config.roleDescription = roleMatch[1]; + + if (content.includes('Be direct, concise')) { + config.communicationStyle = 'direct'; + } else if (content.includes('Be warm and conversational')) { + config.communicationStyle = 'friendly'; + } else if (content.includes('Use professional, structured')) { + config.communicationStyle = 'formal'; + } + + return config; +} + +/** + * Parse a UserConfig from an existing USER.md file. + */ +function parseUserFromMarkdown(content: string): UserConfig { + const config: UserConfig = {}; + + const nameMatch = content.match(/\*\*Name:\*\* (.+)/); + if (nameMatch) config.userName = nameMatch[1]; + + const pronounsMatch = content.match(/\*\*Pronouns:\*\* (.+)/); + if (pronounsMatch) config.pronouns = pronounsMatch[1]; + + const tzMatch = content.match(/\*\*Timezone:\*\* (.+)/); + if (tzMatch) config.timezone = tzMatch[1]; + + return config; +} + +/** + * Parse a ToolsConfig from an existing TOOLS.md file. + */ +function parseToolsFromMarkdown(content: string): ToolsConfig { + const config: ToolsConfig = {}; + + const credsMatch = content.match(/\*\*Location:\*\* (.+)/); + if (credsMatch) config.credentialsLocation = credsMatch[1]; + + return config; +} + +export class FileConfigAdapter implements ConfigService { + constructor( + private mosaicHome: string, + private sourceDir: string, + ) {} + + async readSoul(): Promise { + const path = join(this.mosaicHome, 'SOUL.md'); + if (!existsSync(path)) return {}; + return parseSoulFromMarkdown(readFileSync(path, 'utf-8')); + } + + async readUser(): Promise { + const path = join(this.mosaicHome, 'USER.md'); + if (!existsSync(path)) return {}; + return parseUserFromMarkdown(readFileSync(path, 'utf-8')); + } + + async readTools(): Promise { + const path = join(this.mosaicHome, 'TOOLS.md'); + if (!existsSync(path)) return {}; + return parseToolsFromMarkdown(readFileSync(path, 'utf-8')); + } + + async writeSoul(config: SoulConfig): Promise { + const validated = soulSchema.parse(config); + const templatePath = this.findTemplate('SOUL.md.template'); + if (!templatePath) return; + + const template = readFileSync(templatePath, 'utf-8'); + const vars = buildSoulTemplateVars(validated); + const output = renderTemplate(template, vars); + + const outPath = join(this.mosaicHome, 'SOUL.md'); + backupFile(outPath); + atomicWrite(outPath, output); + } + + async writeUser(config: UserConfig): Promise { + const validated = userSchema.parse(config); + const templatePath = this.findTemplate('USER.md.template'); + if (!templatePath) return; + + const template = readFileSync(templatePath, 'utf-8'); + const vars = buildUserTemplateVars(validated); + const output = renderTemplate(template, vars); + + const outPath = join(this.mosaicHome, 'USER.md'); + backupFile(outPath); + atomicWrite(outPath, output); + } + + async writeTools(config: ToolsConfig): Promise { + const validated = toolsSchema.parse(config); + const templatePath = this.findTemplate('TOOLS.md.template'); + if (!templatePath) return; + + const template = readFileSync(templatePath, 'utf-8'); + const vars = buildToolsTemplateVars(validated); + const output = renderTemplate(template, vars); + + const outPath = join(this.mosaicHome, 'TOOLS.md'); + backupFile(outPath); + atomicWrite(outPath, output); + } + + async syncFramework(action: InstallAction): Promise { + const preservePaths = + action === 'keep' || action === 'reconfigure' + ? ['SOUL.md', 'USER.md', 'TOOLS.md', 'memory'] + : []; + + syncDirectory(this.sourceDir, this.mosaicHome, { + preserve: preservePaths, + excludeGit: true, + }); + } + + /** + * Look for template in source dir first, then mosaic home. + */ + private findTemplate(name: string): string | null { + const candidates = [ + join(this.sourceDir, 'templates', name), + join(this.mosaicHome, 'templates', name), + ]; + for (const path of candidates) { + if (existsSync(path)) return path; + } + return null; + } +} diff --git a/packages/mosaic/src/config/schemas.ts b/packages/mosaic/src/config/schemas.ts new file mode 100644 index 0000000..f4f942e --- /dev/null +++ b/packages/mosaic/src/config/schemas.ts @@ -0,0 +1,51 @@ +import { z } from 'zod'; + +export const communicationStyleSchema = z + .enum(['direct', 'friendly', 'formal']) + .default('direct'); + +export const soulSchema = z + .object({ + agentName: z.string().min(1).max(50).default('Assistant'), + roleDescription: z + .string() + .default('execution partner and visibility engine'), + communicationStyle: communicationStyleSchema, + accessibility: z.string().default('none'), + customGuardrails: z.string().default(''), + }) + .partial(); + +export const gitProviderSchema = z.object({ + name: z.string().min(1), + url: z.string().min(1), + cli: z.string().min(1), + purpose: z.string().min(1), +}); + +export const userSchema = z + .object({ + userName: z.string().default(''), + pronouns: z.string().default('They/Them'), + timezone: z.string().default('UTC'), + background: z.string().default('(not configured)'), + accessibilitySection: z + .string() + .default( + '(No specific accommodations configured. Edit this section to add any.)', + ), + communicationPrefs: z.string().default(''), + personalBoundaries: z + .string() + .default('(Edit this section to add any personal boundaries.)'), + projectsTable: z.string().default(''), + }) + .partial(); + +export const toolsSchema = z + .object({ + gitProviders: z.array(gitProviderSchema).default([]), + credentialsLocation: z.string().default('none'), + customToolsSection: z.string().default(''), + }) + .partial(); diff --git a/packages/mosaic/src/constants.ts b/packages/mosaic/src/constants.ts new file mode 100644 index 0000000..444d402 --- /dev/null +++ b/packages/mosaic/src/constants.ts @@ -0,0 +1,38 @@ +import { homedir } from 'node:os'; +import { join } from 'node:path'; + +export const VERSION = '0.2.0'; + +export const DEFAULT_MOSAIC_HOME = join(homedir(), '.config', 'mosaic'); + +export const DEFAULTS = { + agentName: 'Assistant', + roleDescription: 'execution partner and visibility engine', + communicationStyle: 'direct' as const, + pronouns: 'They/Them', + timezone: 'UTC', + background: '(not configured)', + accessibilitySection: '(No specific accommodations configured. Edit this section to add any.)', + personalBoundaries: '(Edit this section to add any personal boundaries.)', + projectsTable: `| Project | Stack | Registry | +|---------|-------|----------| +| (none configured) | | |`, + credentialsLocation: 'none', + customToolsSection: `## Custom Tools + +(Add any machine-specific tools, scripts, or workflows here.)`, + gitProvidersTable: `| Instance | URL | CLI | Purpose | +|----------|-----|-----|---------| +| (add your git providers here) | | | |`, +}; + +export const RECOMMENDED_SKILLS = new Set([ + 'brainstorming', + 'code-review-excellence', + 'lint', + 'systematic-debugging', + 'verification-before-completion', + 'writing-plans', + 'executing-plans', + 'architecture-patterns', +]); diff --git a/packages/mosaic/src/errors.ts b/packages/mosaic/src/errors.ts new file mode 100644 index 0000000..d73f9ee --- /dev/null +++ b/packages/mosaic/src/errors.ts @@ -0,0 +1,20 @@ +export class WizardCancelledError extends Error { + override name = 'WizardCancelledError'; + constructor() { + super('Wizard cancelled by user'); + } +} + +export class ValidationError extends Error { + override name = 'ValidationError'; + constructor(message: string) { + super(message); + } +} + +export class TemplateError extends Error { + override name = 'TemplateError'; + constructor(templatePath: string, message: string) { + super(`Template error in ${templatePath}: ${message}`); + } +} diff --git a/packages/mosaic/src/index.ts b/packages/mosaic/src/index.ts new file mode 100644 index 0000000..4dcce68 --- /dev/null +++ b/packages/mosaic/src/index.ts @@ -0,0 +1,81 @@ +import { Command } from 'commander'; +import { homedir } from 'node:os'; +import { join } from 'node:path'; +import { ClackPrompter } from './prompter/clack-prompter.js'; +import { HeadlessPrompter } from './prompter/headless-prompter.js'; +import { createConfigService } from './config/config-service.js'; +import { runWizard } from './wizard.js'; +import { WizardCancelledError } from './errors.js'; +import { VERSION, DEFAULT_MOSAIC_HOME } from './constants.js'; +import type { CommunicationStyle } from './types.js'; + +const program = new Command() + .name('mosaic-wizard') + .description('Mosaic Installation Wizard') + .version(VERSION); + +program + .option('--non-interactive', 'Run without prompts (uses defaults + flags)') + .option( + '--source-dir ', + 'Source directory for framework files', + ) + .option( + '--mosaic-home ', + 'Target config directory', + DEFAULT_MOSAIC_HOME, + ) + // SOUL.md overrides + .option('--name ', 'Agent name') + .option('--role ', 'Agent role description') + .option('--style