@automagik/genie 0.260201.2240

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.
Files changed (59) hide show
  1. package/.github/workflows/publish.yml +26 -0
  2. package/.worktrees/.metadata.json +3 -0
  3. package/README.md +532 -0
  4. package/bun.lock +101 -0
  5. package/dist/claudio.js +76 -0
  6. package/dist/genie.js +201 -0
  7. package/dist/term.js +136 -0
  8. package/install.sh +351 -0
  9. package/package.json +37 -0
  10. package/scripts/version.ts +48 -0
  11. package/src/claudio.ts +128 -0
  12. package/src/commands/launch.ts +245 -0
  13. package/src/commands/models.ts +43 -0
  14. package/src/commands/profiles.ts +95 -0
  15. package/src/commands/setup.ts +5 -0
  16. package/src/genie-commands/hooks.ts +317 -0
  17. package/src/genie-commands/install.ts +351 -0
  18. package/src/genie-commands/setup.ts +282 -0
  19. package/src/genie-commands/shortcuts.ts +62 -0
  20. package/src/genie-commands/update.ts +228 -0
  21. package/src/genie.ts +106 -0
  22. package/src/lib/api-client.ts +109 -0
  23. package/src/lib/claude-settings.ts +252 -0
  24. package/src/lib/config.ts +109 -0
  25. package/src/lib/genie-config.ts +164 -0
  26. package/src/lib/hook-manager.ts +130 -0
  27. package/src/lib/hook-script.ts +256 -0
  28. package/src/lib/hooks/compose.ts +72 -0
  29. package/src/lib/hooks/index.ts +163 -0
  30. package/src/lib/hooks/presets/audited.ts +191 -0
  31. package/src/lib/hooks/presets/collaborative.ts +143 -0
  32. package/src/lib/hooks/presets/sandboxed.ts +153 -0
  33. package/src/lib/hooks/presets/supervised.ts +66 -0
  34. package/src/lib/hooks/utils/escape.ts +46 -0
  35. package/src/lib/log-reader.ts +213 -0
  36. package/src/lib/picker.ts +62 -0
  37. package/src/lib/session-metadata.ts +58 -0
  38. package/src/lib/system-detect.ts +185 -0
  39. package/src/lib/tmux.ts +410 -0
  40. package/src/lib/version.ts +15 -0
  41. package/src/lib/wizard.ts +104 -0
  42. package/src/lib/worktree.ts +362 -0
  43. package/src/term-commands/attach.ts +23 -0
  44. package/src/term-commands/exec.ts +34 -0
  45. package/src/term-commands/hook.ts +42 -0
  46. package/src/term-commands/ls.ts +33 -0
  47. package/src/term-commands/new.ts +73 -0
  48. package/src/term-commands/pane.ts +81 -0
  49. package/src/term-commands/read.ts +70 -0
  50. package/src/term-commands/rm.ts +47 -0
  51. package/src/term-commands/send.ts +34 -0
  52. package/src/term-commands/shortcuts.ts +355 -0
  53. package/src/term-commands/split.ts +87 -0
  54. package/src/term-commands/status.ts +116 -0
  55. package/src/term-commands/window.ts +72 -0
  56. package/src/term.ts +192 -0
  57. package/src/types/config.ts +17 -0
  58. package/src/types/genie-config.ts +104 -0
  59. package/tsconfig.json +17 -0
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@automagik/genie",
3
+ "version": "0.260201.2240",
4
+ "description": "Dual CLI: claudio (Claude profiles) + term (terminal orchestration)",
5
+ "type": "module",
6
+ "bin": {
7
+ "claudio": "./dist/claudio.js",
8
+ "term": "./dist/term.js",
9
+ "genie": "./dist/genie.js"
10
+ },
11
+ "scripts": {
12
+ "dev:claudio": "bun --watch src/claudio.ts",
13
+ "dev:term": "bun --watch src/term.ts",
14
+ "version": "bun run scripts/version.ts",
15
+ "prebuild": "bun run version",
16
+ "build": "bun build src/claudio.ts src/term.ts src/genie.ts --outdir dist --target bun --minify && chmod +x dist/*.js",
17
+ "prepack": "bun run build"
18
+ },
19
+ "dependencies": {
20
+ "@inquirer/prompts": "^7.0.0",
21
+ "commander": "^12.1.0",
22
+ "zod": "^3.22.4",
23
+ "uuid": "^11.1.0"
24
+ },
25
+ "devDependencies": {
26
+ "@types/bun": "^1.1.0",
27
+ "@types/node": "^20.10.5",
28
+ "typescript": "^5.3.3"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/namastexlabs/genie-cli.git"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ }
37
+ }
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env bun
2
+
3
+ /**
4
+ * Pre-build script: generates datetime-based version and updates files
5
+ * Format: 0.YYMMDD.HHMM (e.g., 0.260201.1430 = Feb 1, 2026 at 14:30)
6
+ */
7
+
8
+ import { readFile, writeFile } from 'fs/promises';
9
+ import { join, dirname } from 'path';
10
+
11
+ // Generate version from current datetime
12
+ function generateVersion(): string {
13
+ const now = new Date();
14
+ const yy = String(now.getFullYear()).slice(-2);
15
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
16
+ const dd = String(now.getDate()).padStart(2, '0');
17
+ const hh = String(now.getHours()).padStart(2, '0');
18
+ const min = String(now.getMinutes()).padStart(2, '0');
19
+
20
+ return `0.${yy}${mm}${dd}.${hh}${min}`;
21
+ }
22
+
23
+ async function main() {
24
+ const version = generateVersion();
25
+ const rootDir = join(dirname(import.meta.path), '..');
26
+
27
+ // Update package.json
28
+ const packagePath = join(rootDir, 'package.json');
29
+ const packageJson = JSON.parse(await readFile(packagePath, 'utf-8'));
30
+ packageJson.version = version;
31
+ await writeFile(packagePath, JSON.stringify(packageJson, null, 2) + '\n');
32
+
33
+ // Update src/lib/version.ts
34
+ const versionPath = join(rootDir, 'src/lib/version.ts');
35
+ const versionContent = await readFile(versionPath, 'utf-8');
36
+ const updatedContent = versionContent.replace(
37
+ /export const VERSION = '[^']+';/,
38
+ `export const VERSION = '${version}';`
39
+ );
40
+ await writeFile(versionPath, updatedContent);
41
+
42
+ console.log(`Version: ${version}`);
43
+ }
44
+
45
+ main().catch((err) => {
46
+ console.error('Version script failed:', err);
47
+ process.exit(1);
48
+ });
package/src/claudio.ts ADDED
@@ -0,0 +1,128 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { Command } from 'commander';
4
+ import { setupCommand } from './commands/setup.js';
5
+ import { launchProfile, launchDefaultProfile, type LaunchOptions } from './commands/launch.js';
6
+ import {
7
+ profilesListCommand,
8
+ profilesAddCommand,
9
+ profilesRemoveCommand,
10
+ profilesDefaultCommand,
11
+ profilesShowCommand,
12
+ } from './commands/profiles.js';
13
+ import { modelsCommand, configCommand } from './commands/models.js';
14
+
15
+ const program = new Command();
16
+
17
+ program
18
+ .name('claudio')
19
+ .description('Launch Claude Code with custom LLM router profiles')
20
+ .version('0.2.0')
21
+ .option('--hooks <presets>', 'Override hooks (comma-separated: collaborative,supervised,sandboxed,audited)')
22
+ .option('--no-hooks', 'Disable all hooks');
23
+
24
+ // Setup command
25
+ program
26
+ .command('setup')
27
+ .description('First-time setup wizard')
28
+ .action(async () => {
29
+ await setupCommand();
30
+ });
31
+
32
+ // Launch command (explicit)
33
+ program
34
+ .command('launch [profile]')
35
+ .description('Launch Claude Code with optional profile')
36
+ .option('--hooks <presets>', 'Override hooks (comma-separated)')
37
+ .option('--no-hooks', 'Disable all hooks')
38
+ .action(async (profile: string | undefined, cmdOptions) => {
39
+ const options: LaunchOptions = {
40
+ hooks: cmdOptions.hooks,
41
+ noHooks: cmdOptions.noHooks,
42
+ };
43
+
44
+ if (profile) {
45
+ await launchProfile(profile, options);
46
+ } else {
47
+ await launchDefaultProfile(options);
48
+ }
49
+ });
50
+
51
+ // Profiles command with subcommands
52
+ const profiles = program
53
+ .command('profiles')
54
+ .description('Manage profiles');
55
+
56
+ profiles
57
+ .command('list', { isDefault: true })
58
+ .description('List all profiles (* = default)')
59
+ .action(async () => {
60
+ await profilesListCommand();
61
+ });
62
+
63
+ profiles
64
+ .command('add')
65
+ .description('Add new profile (interactive picker)')
66
+ .action(async () => {
67
+ await profilesAddCommand();
68
+ });
69
+
70
+ profiles
71
+ .command('rm <name>')
72
+ .description('Delete profile')
73
+ .action(async (name: string) => {
74
+ await profilesRemoveCommand(name);
75
+ });
76
+
77
+ profiles
78
+ .command('default <name>')
79
+ .description('Set default profile')
80
+ .action(async (name: string) => {
81
+ await profilesDefaultCommand(name);
82
+ });
83
+
84
+ profiles
85
+ .command('show <name>')
86
+ .description('Show profile details')
87
+ .action(async (name: string) => {
88
+ await profilesShowCommand(name);
89
+ });
90
+
91
+ // Models command
92
+ program
93
+ .command('models')
94
+ .description('List available models from router')
95
+ .action(async () => {
96
+ await modelsCommand();
97
+ });
98
+
99
+ // Config command
100
+ program
101
+ .command('config')
102
+ .description('Show current config (URL, default profile)')
103
+ .action(async () => {
104
+ await configCommand();
105
+ });
106
+
107
+ // Handle profile launch (no args = default, or named profile)
108
+ // This is the default action when no command is specified
109
+ program.action(async (options, command) => {
110
+ const args = command.args;
111
+
112
+ const launchOptions: LaunchOptions = {
113
+ hooks: options.hooks,
114
+ noHooks: options.hooks === false, // --no-hooks sets hooks to false
115
+ };
116
+
117
+ if (args.length === 0) {
118
+ // No arguments - launch default profile
119
+ await launchDefaultProfile(launchOptions);
120
+ return;
121
+ }
122
+
123
+ // Profile name provided - launch named profile
124
+ const profileName = args[0];
125
+ await launchProfile(profileName, launchOptions);
126
+ });
127
+
128
+ program.parse();
@@ -0,0 +1,245 @@
1
+ import { spawn } from 'child_process';
2
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { loadConfig, getDefaultProfile, configExists } from '../lib/config.js';
5
+ import { loadGenieConfig, genieConfigExists, getGenieDir, ensureGenieDir } from '../lib/genie-config.js';
6
+ import { describeEnabledHooks, hasEnabledHooks, parseHookNames } from '../lib/hooks/index.js';
7
+ import * as tmux from '../lib/tmux.js';
8
+
9
+ const SESSION_NAME = 'genie';
10
+
11
+ export interface LaunchOptions {
12
+ hooks?: string;
13
+ noHooks?: boolean;
14
+ }
15
+
16
+ /**
17
+ * Get the AGENTS.md system prompt if it exists in the current directory
18
+ */
19
+ function getAgentsSystemPrompt(): string | null {
20
+ const agentsPath = join(process.cwd(), 'AGENTS.md');
21
+ if (existsSync(agentsPath)) {
22
+ return readFileSync(agentsPath, 'utf-8');
23
+ }
24
+ return null;
25
+ }
26
+
27
+ /**
28
+ * Get Claude CLI arguments including system prompt if AGENTS.md exists
29
+ */
30
+ function getClaudeArgs(): string[] {
31
+ const prompt = getAgentsSystemPrompt();
32
+ if (prompt) {
33
+ return ['--system-prompt', prompt];
34
+ }
35
+ return [];
36
+ }
37
+
38
+ /**
39
+ * Get the Claude command string for shell execution
40
+ */
41
+ function getClaudeCommand(): string {
42
+ const prompt = getAgentsSystemPrompt();
43
+ if (prompt) {
44
+ // Escape for shell: replace single quotes with '\''
45
+ const escaped = prompt.replace(/'/g, "'\\''");
46
+ return `claude --system-prompt '${escaped}'`;
47
+ }
48
+ return 'claude';
49
+ }
50
+
51
+ /**
52
+ * Display hook information before launch
53
+ */
54
+ async function displayHookInfo(options: LaunchOptions): Promise<void> {
55
+ // Handle --no-hooks
56
+ if (options.noHooks) {
57
+ console.log('\x1b[33m⚠️ Hooks disabled via --no-hooks\x1b[0m');
58
+ return;
59
+ }
60
+
61
+ // Handle --hooks override
62
+ if (options.hooks) {
63
+ const presets = parseHookNames(options.hooks);
64
+ if (presets.length > 0) {
65
+ console.log(`\x1b[36m🪝 Using hooks: ${presets.join(', ')}\x1b[0m`);
66
+ }
67
+ return;
68
+ }
69
+
70
+ // Load from genie config
71
+ if (genieConfigExists()) {
72
+ const genieConfig = await loadGenieConfig();
73
+ if (hasEnabledHooks(genieConfig)) {
74
+ const descriptions = describeEnabledHooks(genieConfig);
75
+ console.log('\x1b[36m🪝 Active hooks:\x1b[0m');
76
+ for (const desc of descriptions) {
77
+ console.log(` ${desc}`);
78
+ }
79
+ }
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Write a shell-sourceable hooks environment file
85
+ * This can be sourced before running claude to set up hook-related env vars
86
+ */
87
+ async function writeHooksEnvFile(): Promise<string | null> {
88
+ if (!genieConfigExists()) {
89
+ return null;
90
+ }
91
+
92
+ const genieConfig = await loadGenieConfig();
93
+ if (!hasEnabledHooks(genieConfig)) {
94
+ return null;
95
+ }
96
+
97
+ ensureGenieDir();
98
+ const envFile = join(getGenieDir(), 'hooks-env.sh');
99
+
100
+ // For now, we just export a marker that hooks are configured
101
+ // The actual hooks run in SDK applications, not the CLI
102
+ const content = `# Genie hooks environment
103
+ # Generated by claudio launch
104
+ # This file indicates hooks are configured in ~/.genie/config.json
105
+ export GENIE_HOOKS_ENABLED="${genieConfig.hooks.enabled.join(',')}"
106
+ `;
107
+
108
+ writeFileSync(envFile, content, 'utf-8');
109
+ return envFile;
110
+ }
111
+
112
+ export async function launchProfile(profileName: string, options: LaunchOptions = {}): Promise<void> {
113
+ const config = await loadConfig();
114
+ const profile = config.profiles[profileName];
115
+
116
+ if (!profile) {
117
+ console.error(`❌ Profile "${profileName}" not found`);
118
+ console.log(`\nAvailable profiles: ${Object.keys(config.profiles).join(', ')}`);
119
+ process.exit(1);
120
+ }
121
+
122
+ // Display hook information
123
+ await displayHookInfo(options);
124
+
125
+ // Write hooks environment file
126
+ const hooksEnvFile = await writeHooksEnvFile();
127
+
128
+ const isInsideTmux = !!process.env.TMUX;
129
+
130
+ // Environment setup command
131
+ const envSetupParts = [
132
+ `export LC_ALL=C.UTF-8`,
133
+ `export LANG=C.UTF-8`,
134
+ `export ANTHROPIC_BASE_URL="${config.apiUrl}"`,
135
+ `export ANTHROPIC_AUTH_TOKEN="${config.apiKey}"`,
136
+ `export ANTHROPIC_DEFAULT_OPUS_MODEL="${profile.opus}"`,
137
+ `export ANTHROPIC_DEFAULT_SONNET_MODEL="${profile.sonnet}"`,
138
+ `export ANTHROPIC_DEFAULT_HAIKU_MODEL="${profile.haiku}"`,
139
+ ];
140
+
141
+ // Source hooks env file if it exists
142
+ if (hooksEnvFile) {
143
+ envSetupParts.push(`source "${hooksEnvFile}"`);
144
+ }
145
+
146
+ envSetupParts.push(getClaudeCommand());
147
+
148
+ const envSetup = envSetupParts.join('; ');
149
+
150
+ if (isInsideTmux) {
151
+ // Already inside tmux - run directly in current terminal
152
+ console.log(`🚀 Launching "${profileName}"...`);
153
+
154
+ // Set environment variables
155
+ process.env.LC_ALL = 'C.UTF-8';
156
+ process.env.LANG = 'C.UTF-8';
157
+ process.env.ANTHROPIC_BASE_URL = config.apiUrl;
158
+ process.env.ANTHROPIC_AUTH_TOKEN = config.apiKey;
159
+ process.env.ANTHROPIC_DEFAULT_OPUS_MODEL = profile.opus;
160
+ process.env.ANTHROPIC_DEFAULT_SONNET_MODEL = profile.sonnet;
161
+ process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = profile.haiku;
162
+
163
+ // Spawn claude with inherited stdio (replaces this process)
164
+ const child = spawn('claude', getClaudeArgs(), {
165
+ stdio: 'inherit',
166
+ env: process.env,
167
+ });
168
+
169
+ child.on('error', (error) => {
170
+ console.error(`❌ Failed to launch: ${error.message}`);
171
+ process.exit(1);
172
+ });
173
+
174
+ child.on('exit', (code) => {
175
+ process.exit(code || 0);
176
+ });
177
+ } else {
178
+ // Outside tmux - create or reuse "genie" session
179
+ let session = await tmux.findSessionByName(SESSION_NAME);
180
+
181
+ if (session) {
182
+ // Session exists - add a new window
183
+ console.log(`🚀 Adding "${profileName}" to session "${SESSION_NAME}"...`);
184
+
185
+ const window = await tmux.createWindow(session.id, profileName);
186
+ if (!window) {
187
+ console.error('❌ Failed to create window');
188
+ process.exit(1);
189
+ }
190
+
191
+ const panes = await tmux.listPanes(window.id);
192
+ await tmux.executeCommand(panes[0].id, envSetup);
193
+ } else {
194
+ // No session - create it
195
+ console.log(`🚀 Creating session "${SESSION_NAME}" with "${profileName}"...`);
196
+
197
+ session = await tmux.createSession(SESSION_NAME);
198
+ if (!session) {
199
+ console.error('❌ Failed to create tmux session');
200
+ process.exit(1);
201
+ }
202
+
203
+ // Rename the default window to the profile name
204
+ const windows = await tmux.listWindows(session.id);
205
+ await tmux.renameWindow(windows[0].id, profileName);
206
+
207
+ const panes = await tmux.listPanes(windows[0].id);
208
+ await tmux.executeCommand(panes[0].id, envSetup);
209
+ }
210
+
211
+ console.log(`✅ Ready`);
212
+ console.log(`\nAttaching to session...`);
213
+
214
+ // Attach to session
215
+ const child = spawn('tmux', ['attach', '-t', SESSION_NAME], {
216
+ stdio: 'inherit',
217
+ });
218
+
219
+ child.on('error', (error) => {
220
+ console.error(`❌ Failed to attach: ${error.message}`);
221
+ process.exit(1);
222
+ });
223
+
224
+ child.on('exit', (code) => {
225
+ process.exit(code || 0);
226
+ });
227
+ }
228
+ }
229
+
230
+ export async function launchDefaultProfile(options: LaunchOptions = {}): Promise<void> {
231
+ if (!configExists()) {
232
+ console.error('❌ No config found. Run `claudio setup` first.');
233
+ process.exit(1);
234
+ }
235
+
236
+ const defaultProfile = await getDefaultProfile();
237
+
238
+ if (!defaultProfile) {
239
+ console.error('❌ No default profile set.');
240
+ console.log('\nRun `claudio setup` to configure, or use `claudio <profile>` to launch a specific profile.');
241
+ process.exit(1);
242
+ }
243
+
244
+ await launchProfile(defaultProfile, options);
245
+ }
@@ -0,0 +1,43 @@
1
+ import { loadConfig, configExists, getDefaultProfile } from '../lib/config.js';
2
+ import { testConnection } from '../lib/api-client.js';
3
+
4
+ export async function modelsCommand(): Promise<void> {
5
+ if (!configExists()) {
6
+ console.error('❌ No config found. Run `claudio setup` first.');
7
+ process.exit(1);
8
+ }
9
+
10
+ const config = await loadConfig();
11
+
12
+ process.stdout.write('Fetching models... ');
13
+ const result = await testConnection(config.apiUrl, config.apiKey);
14
+
15
+ if (!result.success) {
16
+ console.log('❌');
17
+ console.error(`\n❌ ${result.message}`);
18
+ process.exit(1);
19
+ }
20
+
21
+ console.log(`✓\n`);
22
+ console.log(`Available models (${result.modelCount}):\n`);
23
+
24
+ for (const model of result.models) {
25
+ console.log(` ${model.id}`);
26
+ }
27
+ }
28
+
29
+ export async function configCommand(): Promise<void> {
30
+ if (!configExists()) {
31
+ console.error('❌ No config found. Run `claudio setup` first.');
32
+ process.exit(1);
33
+ }
34
+
35
+ const config = await loadConfig();
36
+ const profileCount = Object.keys(config.profiles).length;
37
+
38
+ console.log('\nClaudio Config\n');
39
+ console.log(` API URL: ${config.apiUrl}`);
40
+ console.log(` API Key: ${config.apiKey.slice(0, 8)}...${config.apiKey.slice(-4)}`);
41
+ console.log(` Default Profile: ${config.defaultProfile || '(none)'}`);
42
+ console.log(` Profiles: ${profileCount}`);
43
+ }
@@ -0,0 +1,95 @@
1
+ import {
2
+ listProfiles,
3
+ removeProfile,
4
+ setDefaultProfile,
5
+ getProfile,
6
+ configExists,
7
+ } from '../lib/config.js';
8
+ import { runAddProfileWizard } from '../lib/wizard.js';
9
+ import { promptConfirm } from '../lib/picker.js';
10
+
11
+ export async function profilesListCommand(): Promise<void> {
12
+ if (!configExists()) {
13
+ console.error('❌ No config found. Run `claudio setup` first.');
14
+ process.exit(1);
15
+ }
16
+
17
+ const profiles = await listProfiles();
18
+
19
+ if (profiles.length === 0) {
20
+ console.log('No profiles configured.');
21
+ console.log('Run `claudio profiles add` to create one.');
22
+ return;
23
+ }
24
+
25
+ console.log('\nProfiles:\n');
26
+ for (const { name, profile, isDefault } of profiles) {
27
+ const marker = isDefault ? ' *' : '';
28
+ console.log(` ${name}${marker}`);
29
+ console.log(` opus: ${profile.opus}`);
30
+ console.log(` sonnet: ${profile.sonnet}`);
31
+ console.log(` haiku: ${profile.haiku}`);
32
+ console.log('');
33
+ }
34
+
35
+ console.log('(* = default)');
36
+ }
37
+
38
+ export async function profilesAddCommand(): Promise<void> {
39
+ await runAddProfileWizard();
40
+ }
41
+
42
+ export async function profilesRemoveCommand(name: string): Promise<void> {
43
+ if (!configExists()) {
44
+ console.error('❌ No config found. Run `claudio setup` first.');
45
+ process.exit(1);
46
+ }
47
+
48
+ const profile = await getProfile(name);
49
+ if (!profile) {
50
+ console.error(`❌ Profile "${name}" not found`);
51
+ process.exit(1);
52
+ }
53
+
54
+ const confirmed = await promptConfirm(`Delete profile "${name}"?`);
55
+ if (!confirmed) {
56
+ console.log('Cancelled.');
57
+ return;
58
+ }
59
+
60
+ await removeProfile(name);
61
+ console.log(`✓ Profile "${name}" deleted`);
62
+ }
63
+
64
+ export async function profilesDefaultCommand(name: string): Promise<void> {
65
+ if (!configExists()) {
66
+ console.error('❌ No config found. Run `claudio setup` first.');
67
+ process.exit(1);
68
+ }
69
+
70
+ try {
71
+ await setDefaultProfile(name);
72
+ console.log(`✓ Default profile set to "${name}"`);
73
+ } catch (error: any) {
74
+ console.error(`❌ ${error.message}`);
75
+ process.exit(1);
76
+ }
77
+ }
78
+
79
+ export async function profilesShowCommand(name: string): Promise<void> {
80
+ if (!configExists()) {
81
+ console.error('❌ No config found. Run `claudio setup` first.');
82
+ process.exit(1);
83
+ }
84
+
85
+ const profile = await getProfile(name);
86
+ if (!profile) {
87
+ console.error(`❌ Profile "${name}" not found`);
88
+ process.exit(1);
89
+ }
90
+
91
+ console.log(`\nProfile: ${name}\n`);
92
+ console.log(` opus: ${profile.opus}`);
93
+ console.log(` sonnet: ${profile.sonnet}`);
94
+ console.log(` haiku: ${profile.haiku}`);
95
+ }
@@ -0,0 +1,5 @@
1
+ import { runSetupWizard } from '../lib/wizard.js';
2
+
3
+ export async function setupCommand(): Promise<void> {
4
+ await runSetupWizard();
5
+ }