@automagik/genie 0.260202.530 → 0.260202.1607

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/install.sh CHANGED
@@ -903,13 +903,49 @@ ensure_path() {
903
903
  }
904
904
 
905
905
  run_setup() {
906
- # Run genie setup if available
907
- if check_command genie; then
908
- log "Running genie setup..."
909
- genie setup --quick 2>/dev/null || true
910
- elif [[ -x "$GENIE_BIN/genie.js" ]]; then
911
- log "Running genie setup..."
912
- "$GENIE_BIN/genie.js" setup --quick 2>/dev/null || true
906
+ # Skip for updates
907
+ if [[ "$INSTALL_MODE" == "update" ]]; then
908
+ return 0
909
+ fi
910
+
911
+ # Check if already configured
912
+ if [[ -f "$GENIE_HOME/config.json" ]]; then
913
+ local complete
914
+ if check_command jq; then
915
+ complete=$(jq -r '.setupComplete // false' "$GENIE_HOME/config.json" 2>/dev/null)
916
+ else
917
+ # Fallback: simple grep check
918
+ if grep -q '"setupComplete"[[:space:]]*:[[:space:]]*true' "$GENIE_HOME/config.json" 2>/dev/null; then
919
+ complete="true"
920
+ else
921
+ complete="false"
922
+ fi
923
+ fi
924
+ if [[ "$complete" == "true" ]]; then
925
+ log "Setup already complete, skipping"
926
+ return 0
927
+ fi
928
+ fi
929
+
930
+ # Run setup
931
+ if [[ "$AUTO_YES" == true ]]; then
932
+ if check_command genie; then
933
+ log "Running genie setup (quick mode)..."
934
+ genie setup --quick 2>/dev/null || true
935
+ elif [[ -x "$GENIE_BIN/genie.js" ]]; then
936
+ log "Running genie setup (quick mode)..."
937
+ "$GENIE_BIN/genie.js" setup --quick 2>/dev/null || true
938
+ fi
939
+ else
940
+ if confirm "Run genie setup now?"; then
941
+ if check_command genie; then
942
+ genie setup
943
+ elif [[ -x "$GENIE_BIN/genie.js" ]]; then
944
+ "$GENIE_BIN/genie.js" setup
945
+ fi
946
+ else
947
+ info "Skipped. Run 'genie setup' later to configure."
948
+ fi
913
949
  fi
914
950
  }
915
951
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automagik/genie",
3
- "version": "0.260202.0530",
3
+ "version": "0.260202.1607",
4
4
  "description": "Collaborative terminal toolkit for human + AI workflows",
5
5
  "type": "module",
6
6
  "bin": {
package/src/claudio.ts CHANGED
@@ -13,14 +13,29 @@ import {
13
13
  } from './commands/profiles.js';
14
14
  import { modelsCommand, configCommand } from './commands/models.js';
15
15
 
16
+ // Extract Claude passthrough args (everything after --)
17
+ // Must be done before Commander parses argv
18
+ const separatorIndex = process.argv.indexOf('--');
19
+ const claudePassthroughArgs: string[] = separatorIndex !== -1
20
+ ? process.argv.slice(separatorIndex + 1)
21
+ : [];
22
+ const claudioArgs = separatorIndex !== -1
23
+ ? process.argv.slice(0, separatorIndex)
24
+ : process.argv;
25
+
26
+ // Special case: "claudio -- <claude args>" with no claudio args
27
+ // Launch default profile directly, bypassing Commander
28
+ if (claudePassthroughArgs.length > 0 && claudioArgs.length === 2) {
29
+ // Only have [node, script] before --, so just launch with passthrough
30
+ await launchDefaultProfile({ claudeArgs: claudePassthroughArgs });
31
+ } else {
32
+
16
33
  const program = new Command();
17
34
 
18
35
  program
19
36
  .name('claudio')
20
37
  .description('Launch Claude Code with custom LLM router profiles')
21
- .version(VERSION)
22
- .option('--hooks <presets>', 'Override hooks (comma-separated: collaborative,supervised,sandboxed,audited)')
23
- .option('--no-hooks', 'Disable all hooks');
38
+ .version(VERSION);
24
39
 
25
40
  // Setup command
26
41
  program
@@ -33,13 +48,10 @@ program
33
48
  // Launch command (explicit)
34
49
  program
35
50
  .command('launch [profile]')
36
- .description('Launch Claude Code with optional profile')
37
- .option('--hooks <presets>', 'Override hooks (comma-separated)')
38
- .option('--no-hooks', 'Disable all hooks')
39
- .action(async (profile: string | undefined, cmdOptions) => {
51
+ .description('Launch Claude Code with optional profile (use -- to pass args to Claude)')
52
+ .action(async (profile: string | undefined) => {
40
53
  const options: LaunchOptions = {
41
- hooks: cmdOptions.hooks,
42
- noHooks: cmdOptions.noHooks,
54
+ claudeArgs: claudePassthroughArgs,
43
55
  };
44
56
 
45
57
  if (profile) {
@@ -107,23 +119,21 @@ program
107
119
 
108
120
  // Handle profile launch (no args = default, or named profile)
109
121
  // This is the default action when no command is specified
110
- program.action(async (options, command) => {
111
- const args = command.args;
122
+ program.action(async (_options, command) => {
123
+ const args = command.args as string[];
124
+ const profileName = args[0];
112
125
 
113
126
  const launchOptions: LaunchOptions = {
114
- hooks: options.hooks,
115
- noHooks: options.hooks === false, // --no-hooks sets hooks to false
127
+ claudeArgs: claudePassthroughArgs,
116
128
  };
117
129
 
118
- if (args.length === 0) {
119
- // No arguments - launch default profile
130
+ if (profileName) {
131
+ await launchProfile(profileName, launchOptions);
132
+ } else {
120
133
  await launchDefaultProfile(launchOptions);
121
- return;
122
134
  }
123
-
124
- // Profile name provided - launch named profile
125
- const profileName = args[0];
126
- await launchProfile(profileName, launchOptions);
127
135
  });
128
136
 
129
- program.parse();
137
+ program.parse(claudioArgs);
138
+
139
+ } // end else block for special case
@@ -2,12 +2,9 @@ import { spawn } from 'child_process';
2
2
  import { existsSync, readFileSync } from 'fs';
3
3
  import { join } from 'path';
4
4
  import { loadConfig, getDefaultProfile, configExists } from '../lib/config.js';
5
- import { loadGenieConfig, genieConfigExists } from '../lib/genie-config.js';
6
- import { describeEnabledHooks, hasEnabledHooks, parseHookNames } from '../lib/hooks/index.js';
7
5
 
8
6
  export interface LaunchOptions {
9
- hooks?: string;
10
- noHooks?: boolean;
7
+ claudeArgs?: string[];
11
8
  }
12
9
 
13
10
  /**
@@ -24,61 +21,14 @@ function getAgentsSystemPrompt(): string | null {
24
21
  /**
25
22
  * Get Claude CLI arguments including system prompt if AGENTS.md exists
26
23
  */
27
- function getClaudeArgs(): string[] {
24
+ function getClaudeArgs(passthroughArgs: string[] = []): string[] {
25
+ const args: string[] = [];
28
26
  const prompt = getAgentsSystemPrompt();
29
27
  if (prompt) {
30
- return ['--system-prompt', prompt];
28
+ args.push('--system-prompt', prompt);
31
29
  }
32
- return [];
33
- }
34
-
35
- /**
36
- * Display hook information before launch
37
- */
38
- async function displayHookInfo(options: LaunchOptions): Promise<void> {
39
- // Handle --no-hooks
40
- if (options.noHooks) {
41
- console.log('\x1b[33m⚠️ Hooks disabled via --no-hooks\x1b[0m');
42
- return;
43
- }
44
-
45
- // Handle --hooks override
46
- if (options.hooks) {
47
- const presets = parseHookNames(options.hooks);
48
- if (presets.length > 0) {
49
- console.log(`\x1b[36m🪝 Using hooks: ${presets.join(', ')}\x1b[0m`);
50
- }
51
- return;
52
- }
53
-
54
- // Load from genie config
55
- if (genieConfigExists()) {
56
- const genieConfig = await loadGenieConfig();
57
- if (hasEnabledHooks(genieConfig)) {
58
- const descriptions = describeEnabledHooks(genieConfig);
59
- console.log('\x1b[36m🪝 Active hooks:\x1b[0m');
60
- for (const desc of descriptions) {
61
- console.log(` ${desc}`);
62
- }
63
- }
64
- }
65
- }
66
-
67
- /**
68
- * Set hooks environment variables directly in process.env
69
- * This ensures Claude inherits the hooks configuration
70
- */
71
- async function setHooksEnvVars(): Promise<void> {
72
- if (!genieConfigExists()) {
73
- return;
74
- }
75
-
76
- const genieConfig = await loadGenieConfig();
77
- if (!hasEnabledHooks(genieConfig)) {
78
- return;
79
- }
80
-
81
- process.env.GENIE_HOOKS_ENABLED = genieConfig.hooks.enabled.join(',');
30
+ args.push(...passthroughArgs);
31
+ return args;
82
32
  }
83
33
 
84
34
  export async function launchProfile(profileName: string, options: LaunchOptions = {}): Promise<void> {
@@ -86,18 +36,12 @@ export async function launchProfile(profileName: string, options: LaunchOptions
86
36
  const profile = config.profiles[profileName];
87
37
 
88
38
  if (!profile) {
89
- console.error(`❌ Profile "${profileName}" not found`);
39
+ console.error(`Profile "${profileName}" not found`);
90
40
  console.log(`\nAvailable profiles: ${Object.keys(config.profiles).join(', ')}`);
91
41
  process.exit(1);
92
42
  }
93
43
 
94
- // Display hook information
95
- await displayHookInfo(options);
96
-
97
- // Set hooks environment variables
98
- await setHooksEnvVars();
99
-
100
- console.log(`🚀 Launching "${profileName}"...`);
44
+ console.log(`Launching "${profileName}"...`);
101
45
 
102
46
  // Set environment variables
103
47
  process.env.LC_ALL = 'C.UTF-8';
@@ -109,13 +53,13 @@ export async function launchProfile(profileName: string, options: LaunchOptions
109
53
  process.env.ANTHROPIC_DEFAULT_HAIKU_MODEL = profile.haiku;
110
54
 
111
55
  // Spawn claude with inherited stdio
112
- const child = spawn('claude', getClaudeArgs(), {
56
+ const child = spawn('claude', getClaudeArgs(options.claudeArgs || []), {
113
57
  stdio: 'inherit',
114
58
  env: process.env,
115
59
  });
116
60
 
117
61
  child.on('error', (error) => {
118
- console.error(`❌ Failed to launch: ${error.message}`);
62
+ console.error(`Failed to launch: ${error.message}`);
119
63
  process.exit(1);
120
64
  });
121
65
 
@@ -126,14 +70,14 @@ export async function launchProfile(profileName: string, options: LaunchOptions
126
70
 
127
71
  export async function launchDefaultProfile(options: LaunchOptions = {}): Promise<void> {
128
72
  if (!configExists()) {
129
- console.error('No config found. Run `claudio setup` first.');
73
+ console.error('No config found. Run `claudio setup` first.');
130
74
  process.exit(1);
131
75
  }
132
76
 
133
77
  const defaultProfile = await getDefaultProfile();
134
78
 
135
79
  if (!defaultProfile) {
136
- console.error('No default profile set.');
80
+ console.error('No default profile set.');
137
81
  console.log('\nRun `claudio setup` to configure, or use `claudio <profile>` to launch a specific profile.');
138
82
  process.exit(1);
139
83
  }
@@ -0,0 +1,327 @@
1
+ /**
2
+ * Genie Doctor Command
3
+ *
4
+ * Diagnostic tool to check the health of the genie installation.
5
+ * Checks prerequisites, configuration, and tmux connectivity.
6
+ */
7
+
8
+ import {
9
+ checkCommand,
10
+ } from '../lib/system-detect.js';
11
+ import {
12
+ getClaudeSettingsPath,
13
+ contractClaudePath,
14
+ } from '../lib/claude-settings.js';
15
+ import {
16
+ loadGenieConfig,
17
+ genieConfigExists,
18
+ getGenieConfigPath,
19
+ isSetupComplete,
20
+ } from '../lib/genie-config.js';
21
+ import { $ } from 'bun';
22
+ import { existsSync } from 'fs';
23
+
24
+ interface CheckResult {
25
+ name: string;
26
+ status: 'pass' | 'fail' | 'warn';
27
+ message?: string;
28
+ suggestion?: string;
29
+ }
30
+
31
+ /**
32
+ * Print section header
33
+ */
34
+ function printSectionHeader(title: string): void {
35
+ console.log();
36
+ console.log(`\x1b[1m${title}:\x1b[0m`);
37
+ }
38
+
39
+ /**
40
+ * Print a check result
41
+ */
42
+ function printCheckResult(result: CheckResult): void {
43
+ const icons = {
44
+ pass: '\x1b[32m\u2713\x1b[0m',
45
+ fail: '\x1b[31m\u2717\x1b[0m',
46
+ warn: '\x1b[33m!\x1b[0m',
47
+ };
48
+
49
+ const icon = icons[result.status];
50
+ const message = result.message ? ` ${result.message}` : '';
51
+ console.log(` ${icon} ${result.name}${message}`);
52
+
53
+ if (result.suggestion) {
54
+ console.log(` \x1b[2m${result.suggestion}\x1b[0m`);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Check prerequisites (tmux, jq, bun, Claude Code)
60
+ */
61
+ async function checkPrerequisites(): Promise<CheckResult[]> {
62
+ const results: CheckResult[] = [];
63
+
64
+ // Check tmux
65
+ const tmuxCheck = await checkCommand('tmux');
66
+ if (tmuxCheck.exists) {
67
+ results.push({
68
+ name: 'tmux',
69
+ status: 'pass',
70
+ message: tmuxCheck.version || '',
71
+ });
72
+ } else {
73
+ results.push({
74
+ name: 'tmux',
75
+ status: 'fail',
76
+ suggestion: 'Install with: brew install tmux (or apt install tmux)',
77
+ });
78
+ }
79
+
80
+ // Check jq
81
+ const jqCheck = await checkCommand('jq');
82
+ if (jqCheck.exists) {
83
+ results.push({
84
+ name: 'jq',
85
+ status: 'pass',
86
+ message: jqCheck.version || '',
87
+ });
88
+ } else {
89
+ results.push({
90
+ name: 'jq',
91
+ status: 'fail',
92
+ suggestion: 'Install with: brew install jq (or apt install jq)',
93
+ });
94
+ }
95
+
96
+ // Check bun
97
+ const bunCheck = await checkCommand('bun');
98
+ if (bunCheck.exists) {
99
+ results.push({
100
+ name: 'bun',
101
+ status: 'pass',
102
+ message: bunCheck.version || '',
103
+ });
104
+ } else {
105
+ results.push({
106
+ name: 'bun',
107
+ status: 'fail',
108
+ suggestion: 'Install with: curl -fsSL https://bun.sh/install | bash',
109
+ });
110
+ }
111
+
112
+ // Check Claude Code
113
+ const claudeCheck = await checkCommand('claude');
114
+ if (claudeCheck.exists) {
115
+ results.push({
116
+ name: 'Claude Code',
117
+ status: 'pass',
118
+ message: claudeCheck.version || '',
119
+ });
120
+ } else {
121
+ results.push({
122
+ name: 'Claude Code',
123
+ status: 'warn',
124
+ suggestion: 'Install with: npm install -g @anthropic-ai/claude-code',
125
+ });
126
+ }
127
+
128
+ return results;
129
+ }
130
+
131
+ /**
132
+ * Check configuration
133
+ */
134
+ async function checkConfiguration(): Promise<CheckResult[]> {
135
+ const results: CheckResult[] = [];
136
+
137
+ // Check if genie config exists
138
+ if (genieConfigExists()) {
139
+ results.push({
140
+ name: 'Genie config exists',
141
+ status: 'pass',
142
+ message: contractClaudePath(getGenieConfigPath()),
143
+ });
144
+ } else {
145
+ results.push({
146
+ name: 'Genie config exists',
147
+ status: 'warn',
148
+ message: 'not found',
149
+ suggestion: 'Run: genie setup',
150
+ });
151
+ }
152
+
153
+ // Check if setup is complete
154
+ if (isSetupComplete()) {
155
+ results.push({
156
+ name: 'Setup complete',
157
+ status: 'pass',
158
+ });
159
+ } else {
160
+ results.push({
161
+ name: 'Setup complete',
162
+ status: 'warn',
163
+ message: 'not completed',
164
+ suggestion: 'Run: genie setup',
165
+ });
166
+ }
167
+
168
+ // Check if claudio config exists
169
+ const claudeSettingsPath = getClaudeSettingsPath();
170
+ if (existsSync(claudeSettingsPath)) {
171
+ results.push({
172
+ name: 'Claude settings exists',
173
+ status: 'pass',
174
+ message: contractClaudePath(claudeSettingsPath),
175
+ });
176
+ } else {
177
+ results.push({
178
+ name: 'Claude settings exists',
179
+ status: 'warn',
180
+ message: 'not found',
181
+ suggestion: 'Claude Code creates this on first run',
182
+ });
183
+ }
184
+
185
+ return results;
186
+ }
187
+
188
+ /**
189
+ * Check tmux status
190
+ */
191
+ async function checkTmux(): Promise<CheckResult[]> {
192
+ const results: CheckResult[] = [];
193
+
194
+ // Check if tmux server is running
195
+ try {
196
+ const serverResult = await $`tmux list-sessions 2>/dev/null`.quiet();
197
+ if (serverResult.exitCode === 0) {
198
+ results.push({
199
+ name: 'Server running',
200
+ status: 'pass',
201
+ });
202
+ } else {
203
+ results.push({
204
+ name: 'Server running',
205
+ status: 'warn',
206
+ message: 'no sessions',
207
+ suggestion: 'Start with: tmux new-session -d -s genie',
208
+ });
209
+ return results;
210
+ }
211
+ } catch {
212
+ results.push({
213
+ name: 'Server running',
214
+ status: 'warn',
215
+ message: 'could not check',
216
+ });
217
+ return results;
218
+ }
219
+
220
+ // Check if genie session exists
221
+ const config = await loadGenieConfig();
222
+ const sessionName = config.session.name;
223
+
224
+ try {
225
+ const sessionResult = await $`tmux has-session -t ${sessionName} 2>/dev/null`.quiet();
226
+ if (sessionResult.exitCode === 0) {
227
+ results.push({
228
+ name: `Session '${sessionName}' exists`,
229
+ status: 'pass',
230
+ });
231
+ } else {
232
+ results.push({
233
+ name: `Session '${sessionName}' exists`,
234
+ status: 'warn',
235
+ suggestion: `Start with: tmux new-session -d -s ${sessionName}`,
236
+ });
237
+ }
238
+ } catch {
239
+ results.push({
240
+ name: `Session '${sessionName}' exists`,
241
+ status: 'warn',
242
+ message: 'could not check',
243
+ });
244
+ }
245
+
246
+ // Check if term exec works
247
+ try {
248
+ const termCheck = await checkCommand('term');
249
+ if (termCheck.exists) {
250
+ results.push({
251
+ name: 'term command available',
252
+ status: 'pass',
253
+ });
254
+ } else {
255
+ results.push({
256
+ name: 'term command available',
257
+ status: 'fail',
258
+ suggestion: 'Ensure genie-cli is properly installed',
259
+ });
260
+ }
261
+ } catch {
262
+ results.push({
263
+ name: 'term command available',
264
+ status: 'warn',
265
+ message: 'could not check',
266
+ });
267
+ }
268
+
269
+ return results;
270
+ }
271
+
272
+ /**
273
+ * Main doctor command
274
+ */
275
+ export async function doctorCommand(): Promise<void> {
276
+ console.log();
277
+ console.log('\x1b[1mGenie Doctor\x1b[0m');
278
+ console.log('\x1b[2m' + '\u2500'.repeat(40) + '\x1b[0m');
279
+
280
+ let hasErrors = false;
281
+ let hasWarnings = false;
282
+
283
+ // Prerequisites
284
+ printSectionHeader('Prerequisites');
285
+ const prereqResults = await checkPrerequisites();
286
+ for (const result of prereqResults) {
287
+ printCheckResult(result);
288
+ if (result.status === 'fail') hasErrors = true;
289
+ if (result.status === 'warn') hasWarnings = true;
290
+ }
291
+
292
+ // Configuration
293
+ printSectionHeader('Configuration');
294
+ const configResults = await checkConfiguration();
295
+ for (const result of configResults) {
296
+ printCheckResult(result);
297
+ if (result.status === 'fail') hasErrors = true;
298
+ if (result.status === 'warn') hasWarnings = true;
299
+ }
300
+
301
+ // Tmux
302
+ printSectionHeader('Tmux');
303
+ const tmuxResults = await checkTmux();
304
+ for (const result of tmuxResults) {
305
+ printCheckResult(result);
306
+ if (result.status === 'fail') hasErrors = true;
307
+ if (result.status === 'warn') hasWarnings = true;
308
+ }
309
+
310
+ // Summary
311
+ console.log();
312
+ console.log('\x1b[2m' + '\u2500'.repeat(40) + '\x1b[0m');
313
+
314
+ if (hasErrors) {
315
+ console.log('\x1b[31mSome checks failed.\x1b[0m Run \x1b[36mgenie setup\x1b[0m to fix.');
316
+ } else if (hasWarnings) {
317
+ console.log('\x1b[33mSome warnings detected.\x1b[0m Everything should still work.');
318
+ } else {
319
+ console.log('\x1b[32mAll checks passed!\x1b[0m');
320
+ }
321
+
322
+ console.log();
323
+
324
+ if (hasErrors) {
325
+ process.exit(1);
326
+ }
327
+ }