@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.
- package/.github/workflows/publish.yml +26 -0
- package/.worktrees/.metadata.json +3 -0
- package/README.md +532 -0
- package/bun.lock +101 -0
- package/dist/claudio.js +76 -0
- package/dist/genie.js +201 -0
- package/dist/term.js +136 -0
- package/install.sh +351 -0
- package/package.json +37 -0
- package/scripts/version.ts +48 -0
- package/src/claudio.ts +128 -0
- package/src/commands/launch.ts +245 -0
- package/src/commands/models.ts +43 -0
- package/src/commands/profiles.ts +95 -0
- package/src/commands/setup.ts +5 -0
- package/src/genie-commands/hooks.ts +317 -0
- package/src/genie-commands/install.ts +351 -0
- package/src/genie-commands/setup.ts +282 -0
- package/src/genie-commands/shortcuts.ts +62 -0
- package/src/genie-commands/update.ts +228 -0
- package/src/genie.ts +106 -0
- package/src/lib/api-client.ts +109 -0
- package/src/lib/claude-settings.ts +252 -0
- package/src/lib/config.ts +109 -0
- package/src/lib/genie-config.ts +164 -0
- package/src/lib/hook-manager.ts +130 -0
- package/src/lib/hook-script.ts +256 -0
- package/src/lib/hooks/compose.ts +72 -0
- package/src/lib/hooks/index.ts +163 -0
- package/src/lib/hooks/presets/audited.ts +191 -0
- package/src/lib/hooks/presets/collaborative.ts +143 -0
- package/src/lib/hooks/presets/sandboxed.ts +153 -0
- package/src/lib/hooks/presets/supervised.ts +66 -0
- package/src/lib/hooks/utils/escape.ts +46 -0
- package/src/lib/log-reader.ts +213 -0
- package/src/lib/picker.ts +62 -0
- package/src/lib/session-metadata.ts +58 -0
- package/src/lib/system-detect.ts +185 -0
- package/src/lib/tmux.ts +410 -0
- package/src/lib/version.ts +15 -0
- package/src/lib/wizard.ts +104 -0
- package/src/lib/worktree.ts +362 -0
- package/src/term-commands/attach.ts +23 -0
- package/src/term-commands/exec.ts +34 -0
- package/src/term-commands/hook.ts +42 -0
- package/src/term-commands/ls.ts +33 -0
- package/src/term-commands/new.ts +73 -0
- package/src/term-commands/pane.ts +81 -0
- package/src/term-commands/read.ts +70 -0
- package/src/term-commands/rm.ts +47 -0
- package/src/term-commands/send.ts +34 -0
- package/src/term-commands/shortcuts.ts +355 -0
- package/src/term-commands/split.ts +87 -0
- package/src/term-commands/status.ts +116 -0
- package/src/term-commands/window.ts +72 -0
- package/src/term.ts +192 -0
- package/src/types/config.ts +17 -0
- package/src/types/genie-config.ts +104 -0
- 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
|
+
}
|