feat: unify mosaic CLI — single binary, no PATH conflict
- Rename framework bash launcher: mosaic → mosaic-launch
- npm CLI (@mosaic/cli) is now the single 'mosaic' binary on PATH
- Add launcher delegation: mosaic {claude,codex,opencode,pi,yolo}
delegates to mosaic-launch via execFileSync with inherited stdio
- Add delegated management commands: init, doctor, sync, seq,
bootstrap, coord, upgrade
- install.sh: remove framework bin PATH requirement, clean up old
mosaic binary on upgrade, simplified summary output
- framework install.sh: preserve sources/ dir (fixes rsync noise
about agent-skills)
- session-run.sh: update references to mosaic-launch
Users now only need ~/.npm-global/bin on PATH. The npm CLI finds
mosaic-launch at its absolute path (~/.config/mosaic/bin/mosaic-launch).
This commit is contained in:
@@ -22,6 +22,94 @@ const program = new Command();
|
||||
|
||||
program.name('mosaic').description('Mosaic Stack CLI').version(CLI_VERSION);
|
||||
|
||||
// ─── framework launcher delegation ──────────────────────────────────────
|
||||
// These commands delegate to the bash framework launcher (mosaic-launch)
|
||||
// which handles runtime injection, mission context, session locks, etc.
|
||||
|
||||
import { execFileSync } from 'node:child_process';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const MOSAIC_HOME = process.env['MOSAIC_HOME'] ?? join(homedir(), '.config', 'mosaic');
|
||||
const LAUNCHER_PATH = join(MOSAIC_HOME, 'bin', 'mosaic-launch');
|
||||
|
||||
/**
|
||||
* Delegate a command to the framework's mosaic-launch bash script.
|
||||
* Passes all remaining args through and inherits stdio.
|
||||
*/
|
||||
function delegateToLauncher(command: string, args: string[]): never {
|
||||
if (!existsSync(LAUNCHER_PATH)) {
|
||||
console.error(`Framework launcher not found: ${LAUNCHER_PATH}`);
|
||||
console.error(
|
||||
'Install the framework: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh) --framework',
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
execFileSync(LAUNCHER_PATH, [command, ...args], {
|
||||
stdio: 'inherit',
|
||||
env: { ...process.env, MOSAIC_HOME },
|
||||
});
|
||||
process.exit(0);
|
||||
} catch (err) {
|
||||
const code = (err as { status?: number }).status ?? 1;
|
||||
process.exit(code);
|
||||
}
|
||||
}
|
||||
|
||||
// Runtime launchers
|
||||
for (const runtime of ['claude', 'codex', 'opencode', 'pi'] as const) {
|
||||
program
|
||||
.command(runtime)
|
||||
.description(
|
||||
`Launch ${
|
||||
runtime === 'pi'
|
||||
? 'Pi'
|
||||
: runtime === 'claude'
|
||||
? 'Claude Code'
|
||||
: runtime.charAt(0).toUpperCase() + runtime.slice(1)
|
||||
} with Mosaic injection`,
|
||||
)
|
||||
.allowUnknownOption(true)
|
||||
.allowExcessArguments(true)
|
||||
.action((_opts: unknown, cmd: Command) => {
|
||||
delegateToLauncher(runtime, cmd.args);
|
||||
});
|
||||
}
|
||||
|
||||
// Yolo mode
|
||||
program
|
||||
.command('yolo <runtime>')
|
||||
.description('Launch a runtime in dangerous-permissions mode')
|
||||
.allowUnknownOption(true)
|
||||
.allowExcessArguments(true)
|
||||
.action((runtime: string, _opts: unknown, cmd: Command) => {
|
||||
delegateToLauncher('yolo', [runtime, ...cmd.args]);
|
||||
});
|
||||
|
||||
// Framework management commands that delegate to mosaic-launch
|
||||
const DELEGATED_COMMANDS: Record<string, string> = {
|
||||
init: 'Generate SOUL.md (agent identity contract)',
|
||||
doctor: 'Health audit — detect drift and missing files',
|
||||
sync: 'Sync skills from canonical source',
|
||||
seq: 'sequential-thinking MCP management (check/fix/start)',
|
||||
bootstrap: 'Bootstrap a repo with Mosaic standards',
|
||||
coord: 'Manual mission coordinator tools',
|
||||
upgrade: 'Upgrade installed Mosaic release',
|
||||
};
|
||||
|
||||
for (const [name, desc] of Object.entries(DELEGATED_COMMANDS)) {
|
||||
program
|
||||
.command(name, { hidden: false })
|
||||
.description(desc)
|
||||
.allowUnknownOption(true)
|
||||
.allowExcessArguments(true)
|
||||
.action((_opts: unknown, cmd: Command) => {
|
||||
delegateToLauncher(name, cmd.args);
|
||||
});
|
||||
}
|
||||
|
||||
// ─── login ──────────────────────────────────────────────────────────────
|
||||
|
||||
program
|
||||
|
||||
Reference in New Issue
Block a user