@automagik/genie 0.260202.453 → 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.
@@ -1,6 +1,16 @@
1
1
  import * as tmux from '../lib/tmux.js';
2
+ import { getTerminalConfig } from '../lib/genie-config.js';
2
3
 
3
- export async function executeInSession(target: string, command: string): Promise<void> {
4
+ export interface ExecOptions {
5
+ quiet?: boolean;
6
+ timeout?: number;
7
+ }
8
+
9
+ export async function executeInSession(
10
+ target: string,
11
+ command: string,
12
+ options: ExecOptions = {}
13
+ ): Promise<void> {
4
14
  // Parse target: "session:window" or just "session"
5
15
  const [sessionName, windowName] = target.includes(':')
6
16
  ? target.split(':')
@@ -10,7 +20,9 @@ export async function executeInSession(target: string, command: string): Promise
10
20
  // Find or create session
11
21
  let session = await tmux.findSessionByName(sessionName);
12
22
  if (!session) {
13
- console.error(`Session "${sessionName}" not found, creating...`);
23
+ if (!options.quiet) {
24
+ console.error(`Session "${sessionName}" not found, creating...`);
25
+ }
14
26
  session = await tmux.createSession(sessionName);
15
27
  if (!session) {
16
28
  console.error(`Failed to create session "${sessionName}"`);
@@ -22,7 +34,9 @@ export async function executeInSession(target: string, command: string): Promise
22
34
  let windows = await tmux.listWindows(session.id);
23
35
  let targetWindow = windows.find(w => w.name === windowName);
24
36
  if (!targetWindow) {
25
- console.error(`Window "${windowName}" not found, creating...`);
37
+ if (!options.quiet) {
38
+ console.error(`Window "${windowName}" not found, creating...`);
39
+ }
26
40
  targetWindow = await tmux.createWindow(session.id, windowName);
27
41
  if (!targetWindow) {
28
42
  console.error(`Failed to create window "${windowName}"`);
@@ -37,11 +51,19 @@ export async function executeInSession(target: string, command: string): Promise
37
51
  process.exit(1);
38
52
  }
39
53
 
54
+ // Use config default if no timeout specified
55
+ const termConfig = getTerminalConfig();
56
+ const timeout = options.timeout ?? termConfig.execTimeout;
57
+
40
58
  // Run command synchronously using wait-for (no polling, no ugly markers)
41
- const { output, exitCode } = await tmux.runCommandSync(panes[0].id, command);
59
+ const { output, exitCode } = await tmux.runCommandSync(
60
+ panes[0].id,
61
+ command,
62
+ timeout
63
+ );
42
64
 
43
- // Output the result
44
- if (output) {
65
+ // Output the result (unless quiet mode)
66
+ if (output && !options.quiet) {
45
67
  console.log(output);
46
68
  }
47
69
 
@@ -1,4 +1,5 @@
1
1
  import * as logReader from '../lib/log-reader.js';
2
+ import { getTerminalConfig } from '../lib/genie-config.js';
2
3
 
3
4
  export interface ReadOptions {
4
5
  lines?: string;
@@ -15,9 +16,13 @@ export interface ReadOptions {
15
16
 
16
17
  export async function readSessionLogs(sessionName: string, options: ReadOptions): Promise<void> {
17
18
  try {
19
+ // Use config default if no lines specified
20
+ const termConfig = getTerminalConfig();
21
+ const defaultLines = termConfig.readLines;
22
+
18
23
  // Parse options
19
24
  const readOptions: logReader.ReadOptions = {
20
- lines: options.lines ? parseInt(options.lines, 10) : 100,
25
+ lines: options.lines ? parseInt(options.lines, 10) : defaultLines,
21
26
  from: options.from ? parseInt(options.from, 10) : undefined,
22
27
  to: options.to ? parseInt(options.to, 10) : undefined,
23
28
  range: options.range,
@@ -15,13 +15,13 @@ export function generateTmuxConfig(): string {
15
15
  # To use: add to ~/.tmux.conf or source this file
16
16
 
17
17
  # Ctrl+T: New window (tab) in current session
18
- bind-key -n C-t new-window
18
+ bind-key -n C-t new-window -c "#{pane_current_path}"
19
19
 
20
20
  # Ctrl+S: Vertical split (requires stty -ixon in shell rc)
21
- bind-key -n C-s split-window -v
21
+ bind-key -n C-s split-window -v -c "#{pane_current_path}"
22
22
 
23
- # Alt+S: Horizontal split
24
- bind-key -n M-s split-window -h
23
+ # Ctrl+Shift+S: Horizontal split
24
+ bind-key -n C-S split-window -h -c "#{pane_current_path}"
25
25
  `;
26
26
  }
27
27
 
@@ -49,7 +49,7 @@ genie-new-tab() {
49
49
  local session
50
50
  session=$(tmux display-message -p '#S' 2>/dev/null)
51
51
  if [ -n "$session" ]; then
52
- tmux new-window
52
+ tmux new-window -c "#{pane_current_path}"
53
53
  else
54
54
  echo "Not in a tmux session"
55
55
  fi
@@ -57,7 +57,7 @@ genie-new-tab() {
57
57
 
58
58
  genie-vsplit() {
59
59
  if tmux display-message -p '#S' >/dev/null 2>&1; then
60
- tmux split-window -v
60
+ tmux split-window -v -c "#{pane_current_path}"
61
61
  else
62
62
  echo "Not in a tmux session"
63
63
  fi
@@ -65,7 +65,7 @@ genie-vsplit() {
65
65
 
66
66
  genie-hsplit() {
67
67
  if tmux display-message -p '#S' >/dev/null 2>&1; then
68
- tmux split-window -h
68
+ tmux split-window -h -c "#{pane_current_path}"
69
69
  else
70
70
  echo "Not in a tmux session"
71
71
  fi
@@ -83,13 +83,13 @@ export function displayShortcuts(): void {
83
83
  console.log(`
84
84
  Warp-like Terminal Shortcuts for tmux + Termux
85
85
 
86
- ┌────────────┬────────────────────────────────────────┐
87
- │ Shortcut │ Action │
88
- ├────────────┼────────────────────────────────────────┤
89
- │ Ctrl+T │ New tab (window) in current session │
90
- │ Ctrl+S │ Vertical split in current session │
91
- Alt+S │ Horizontal split in current session │
92
- └────────────┴────────────────────────────────────────┘
86
+ ┌──────────────────┬────────────────────────────────────────┐
87
+ │ Shortcut │ Action │
88
+ ├──────────────────┼────────────────────────────────────────┤
89
+ │ Ctrl+T │ New tab (window) in current session │
90
+ │ Ctrl+S │ Vertical split in current session │
91
+ Ctrl+Shift+S │ Horizontal split in current session │
92
+ └──────────────────┴────────────────────────────────────────┘
93
93
 
94
94
  Termux Extra Keys (F1-F3):
95
95
  F1 → New tab F2 → Vertical split
package/src/term.ts CHANGED
@@ -22,6 +22,11 @@ program
22
22
  .name('term')
23
23
  .description(`AI-friendly terminal orchestration (tmux wrapper)
24
24
 
25
+ Collaborative Usage:
26
+ AI runs: term exec genie:shell '<command>'
27
+ Human watches: tmux attach -t genie
28
+ AI reads: term read genie
29
+
25
30
  Workflow: new → exec → read → rm
26
31
  Full control: window new/ls/rm, pane ls/rm, split, status`)
27
32
  .version(VERSION);
@@ -81,8 +86,13 @@ program
81
86
  program
82
87
  .command('exec <session> <command...>')
83
88
  .description('Execute command in a tmux session')
84
- .action(async (session: string, command: string[]) => {
85
- await execCmd.executeInSession(session, command.join(' '));
89
+ .option('-q, --quiet', 'Suppress stdout output')
90
+ .option('-t, --timeout <ms>', 'Timeout in milliseconds (default: 120000)')
91
+ .action(async (session: string, command: string[], options: { quiet?: boolean; timeout?: string }) => {
92
+ await execCmd.executeInSession(session, command.join(' '), {
93
+ quiet: options.quiet,
94
+ timeout: options.timeout ? parseInt(options.timeout, 10) : undefined,
95
+ });
86
96
  });
87
97
 
88
98
  program
@@ -1,112 +1,80 @@
1
1
  import { z } from 'zod';
2
2
 
3
3
  /**
4
- * Genie Configuration Schema
4
+ * Genie Configuration Schema v2
5
5
  *
6
6
  * Stored at ~/.genie/config.json
7
- * Manages hook presets and session configuration for the genie CLI.
7
+ * Manages session configuration, terminal defaults, and shortcuts for the genie CLI.
8
8
  */
9
9
 
10
- // Sandboxed preset configuration
11
- export const SandboxedConfigSchema = z.object({
12
- allowedPaths: z.array(z.string()).default(['~/projects', '/tmp']),
13
- });
14
-
15
- // Audited preset configuration
16
- export const AuditedConfigSchema = z.object({
17
- logPath: z.string().default('~/.genie/audit.log'),
18
- });
19
-
20
- // Supervised preset configuration
21
- export const SupervisedConfigSchema = z.object({
22
- alwaysAsk: z.array(z.string()).default(['Write', 'Edit']),
10
+ // Session configuration
11
+ export const SessionConfigSchema = z.object({
12
+ name: z.string().default('genie'),
13
+ defaultWindow: z.string().default('shell'),
14
+ autoCreate: z.boolean().default(true),
23
15
  });
24
16
 
25
- // Collaborative preset configuration
26
- export const CollaborativeConfigSchema = z.object({
27
- sessionName: z.string().default('genie'),
28
- windowName: z.string().default('shell'),
17
+ // Terminal configuration
18
+ export const TerminalConfigSchema = z.object({
19
+ execTimeout: z.number().default(120000),
20
+ readLines: z.number().default(100),
21
+ worktreeBase: z.string().default('.worktrees'),
29
22
  });
30
23
 
31
24
  // Logging configuration
32
25
  export const LoggingConfigSchema = z.object({
33
26
  tmuxDebug: z.boolean().default(false),
27
+ verbose: z.boolean().default(false),
34
28
  });
35
29
 
36
- // Hook presets configuration
37
- export const HooksConfigSchema = z.object({
38
- enabled: z.array(z.enum(['collaborative', 'supervised', 'sandboxed', 'audited'])).default([]),
39
- collaborative: CollaborativeConfigSchema.optional(),
40
- supervised: SupervisedConfigSchema.optional(),
41
- sandboxed: SandboxedConfigSchema.optional(),
42
- audited: AuditedConfigSchema.optional(),
30
+ // Shell configuration
31
+ export const ShellConfigSchema = z.object({
32
+ preference: z.enum(['auto', 'zsh', 'bash', 'fish']).default('auto'),
43
33
  });
44
34
 
45
- // Session configuration
46
- export const SessionConfigSchema = z.object({
47
- name: z.string().default('genie'),
48
- defaultWindow: z.string().default('shell'),
35
+ // Shortcuts configuration
36
+ export const ShortcutsConfigSchema = z.object({
37
+ tmuxInstalled: z.boolean().default(false),
38
+ shellInstalled: z.boolean().default(false),
39
+ });
40
+
41
+ // Claudio integration configuration
42
+ export const ClaudioConfigSchema = z.object({
43
+ enabled: z.boolean().default(false),
49
44
  });
50
45
 
51
46
  // Full genie configuration
52
47
  export const GenieConfigSchema = z.object({
53
- hooks: HooksConfigSchema.default({}),
48
+ version: z.number().default(2),
54
49
  session: SessionConfigSchema.default({}),
50
+ terminal: TerminalConfigSchema.default({}),
55
51
  logging: LoggingConfigSchema.default({}),
52
+ shell: ShellConfigSchema.default({}),
53
+ shortcuts: ShortcutsConfigSchema.default({}),
54
+ claudio: ClaudioConfigSchema.optional(),
55
+ installMethod: z.enum(['source', 'npm', 'bun']).optional(),
56
+ setupComplete: z.boolean().default(false),
57
+ lastSetupAt: z.string().optional(),
58
+ });
59
+
60
+ // Legacy v1 config schema (for migration)
61
+ export const GenieConfigV1Schema = z.object({
62
+ session: z.object({
63
+ name: z.string().default('genie'),
64
+ defaultWindow: z.string().default('shell'),
65
+ }).default({}),
66
+ logging: z.object({
67
+ tmuxDebug: z.boolean().default(false),
68
+ }).default({}),
56
69
  installMethod: z.enum(['source', 'npm', 'bun']).optional(),
57
70
  });
58
71
 
59
72
  // Inferred types
60
- export type SandboxedConfig = z.infer<typeof SandboxedConfigSchema>;
61
- export type AuditedConfig = z.infer<typeof AuditedConfigSchema>;
62
- export type SupervisedConfig = z.infer<typeof SupervisedConfigSchema>;
63
- export type CollaborativeConfig = z.infer<typeof CollaborativeConfigSchema>;
64
- export type HooksConfig = z.infer<typeof HooksConfigSchema>;
65
73
  export type SessionConfig = z.infer<typeof SessionConfigSchema>;
74
+ export type TerminalConfig = z.infer<typeof TerminalConfigSchema>;
66
75
  export type LoggingConfig = z.infer<typeof LoggingConfigSchema>;
76
+ export type ShellConfig = z.infer<typeof ShellConfigSchema>;
77
+ export type ShortcutsConfig = z.infer<typeof ShortcutsConfigSchema>;
78
+ export type ClaudioConfig = z.infer<typeof ClaudioConfigSchema>;
67
79
  export type GenieConfig = z.infer<typeof GenieConfigSchema>;
68
-
69
- // Preset names type
70
- export type PresetName = 'collaborative' | 'supervised' | 'sandboxed' | 'audited';
71
-
72
- // Preset description for UI
73
- export interface PresetDescription {
74
- name: PresetName;
75
- title: string;
76
- what: string;
77
- why: string;
78
- how: string;
79
- recommended?: boolean;
80
- }
81
-
82
- export const PRESET_DESCRIPTIONS: PresetDescription[] = [
83
- {
84
- name: 'collaborative',
85
- title: 'Collaborative',
86
- what: 'All terminal commands run through tmux',
87
- why: 'You can watch AI work in real-time',
88
- how: 'Bash commands → term exec genie:shell',
89
- recommended: true,
90
- },
91
- {
92
- name: 'supervised',
93
- title: 'Supervised',
94
- what: 'File changes require your approval',
95
- why: 'Prevents accidental overwrites',
96
- how: 'Write/Edit tools always ask permission',
97
- },
98
- {
99
- name: 'sandboxed',
100
- title: 'Sandboxed',
101
- what: 'Restrict file access to specific directories',
102
- why: 'Protects sensitive areas of your system',
103
- how: 'Operations outside sandbox are blocked',
104
- },
105
- {
106
- name: 'audited',
107
- title: 'Audited',
108
- what: 'Log all AI tool usage to a file',
109
- why: 'Review what the AI did after a session',
110
- how: 'Every tool call → ~/.genie/audit.log',
111
- },
112
- ];
80
+ export type GenieConfigV1 = z.infer<typeof GenieConfigV1Schema>;
@@ -1,317 +0,0 @@
1
- /**
2
- * Genie Hooks Commands
3
- *
4
- * Commands to install, uninstall, and test hooks in Claude Code.
5
- * Bridges the gap between genie config and Claude Code settings.
6
- */
7
-
8
- import {
9
- loadClaudeSettings,
10
- saveClaudeSettings,
11
- isGenieHookInstalled,
12
- addGenieHook,
13
- removeGenieHook,
14
- getClaudeSettingsPath,
15
- contractClaudePath,
16
- } from '../lib/claude-settings.js';
17
- import {
18
- hookScriptExists,
19
- writeHookScript,
20
- removeHookScript,
21
- testHookScript,
22
- getHookScriptDisplayPath,
23
- } from '../lib/hook-script.js';
24
- import {
25
- loadGenieConfig,
26
- getGenieConfigPath,
27
- } from '../lib/genie-config.js';
28
- import { describeEnabledHooks, hasEnabledHooks } from '../lib/hooks/index.js';
29
- import { checkCommand } from '../lib/system-detect.js';
30
-
31
- /**
32
- * Print a boxed success message
33
- */
34
- function printSuccessBox(lines: string[]): void {
35
- const maxLen = Math.max(...lines.map((l) => l.length));
36
- const width = maxLen + 4;
37
-
38
- console.log();
39
- console.log('\x1b[32m' + '+' + '-'.repeat(width) + '+' + '\x1b[0m');
40
- for (const line of lines) {
41
- const padding = ' '.repeat(maxLen - line.length);
42
- console.log('\x1b[32m' + '| ' + '\x1b[0m' + line + padding + '\x1b[32m |' + '\x1b[0m');
43
- }
44
- console.log('\x1b[32m' + '+' + '-'.repeat(width) + '+' + '\x1b[0m');
45
- console.log();
46
- }
47
-
48
- /**
49
- * Check required dependencies
50
- */
51
- async function checkDependencies(): Promise<{
52
- jq: boolean;
53
- tmux: boolean;
54
- term: boolean;
55
- errors: string[];
56
- }> {
57
- const errors: string[] = [];
58
-
59
- const jqCheck = await checkCommand('jq');
60
- const tmuxCheck = await checkCommand('tmux');
61
- const termCheck = await checkCommand('term');
62
-
63
- if (!jqCheck.exists) {
64
- errors.push('jq is required but not installed. Install with: brew install jq (or apt install jq)');
65
- }
66
-
67
- return {
68
- jq: jqCheck.exists,
69
- tmux: tmuxCheck.exists,
70
- term: termCheck.exists,
71
- errors,
72
- };
73
- }
74
-
75
- /**
76
- * Install hooks into Claude Code
77
- */
78
- export async function installHooksCommand(options: { force?: boolean } = {}): Promise<void> {
79
- console.log();
80
- console.log('\x1b[1m Installing Genie Hooks\x1b[0m');
81
- console.log();
82
-
83
- // Step 1: Check dependencies
84
- console.log('\x1b[2mChecking dependencies...\x1b[0m');
85
- const deps = await checkDependencies();
86
-
87
- if (deps.jq) {
88
- console.log(' \x1b[32m+\x1b[0m jq');
89
- } else {
90
- console.log(' \x1b[31m-\x1b[0m jq (required)');
91
- }
92
-
93
- if (deps.tmux) {
94
- console.log(' \x1b[32m+\x1b[0m tmux');
95
- } else {
96
- console.log(' \x1b[33m-\x1b[0m tmux (recommended for collaborative mode)');
97
- }
98
-
99
- if (deps.term) {
100
- console.log(' \x1b[32m+\x1b[0m term');
101
- } else {
102
- console.log(' \x1b[33m-\x1b[0m term (required for collaborative mode)');
103
- }
104
-
105
- if (deps.errors.length > 0) {
106
- console.log();
107
- for (const error of deps.errors) {
108
- console.log('\x1b[31mError:\x1b[0m ' + error);
109
- }
110
- process.exit(1);
111
- }
112
-
113
- console.log();
114
-
115
- // Step 2: Load genie config and check presets
116
- console.log('\x1b[2mLoading configuration...\x1b[0m');
117
- const config = await loadGenieConfig();
118
-
119
- if (!hasEnabledHooks(config)) {
120
- console.log();
121
- console.log('\x1b[33mNo hook presets are enabled.\x1b[0m');
122
- console.log('Run \x1b[36mgenie setup\x1b[0m to configure hook presets first.');
123
- console.log();
124
- process.exit(1);
125
- }
126
-
127
- const descriptions = describeEnabledHooks(config);
128
- console.log(' \x1b[32m+\x1b[0m Enabled presets: ' + config.hooks.enabled.join(', '));
129
- console.log();
130
-
131
- // Step 3: Check if collaborative preset needs tmux
132
- if (config.hooks.enabled.includes('collaborative')) {
133
- if (!deps.tmux) {
134
- console.log('\x1b[33mWarning:\x1b[0m Collaborative mode enabled but tmux not found.');
135
- console.log('Install tmux for the best experience: brew install tmux (or apt install tmux)');
136
- console.log();
137
- }
138
- if (!deps.term) {
139
- console.log('\x1b[33mWarning:\x1b[0m Collaborative mode enabled but term not found.');
140
- console.log('The term command is required for collaborative mode to work.');
141
- console.log();
142
- }
143
- }
144
-
145
- // Step 4: Check if already installed
146
- const settings = await loadClaudeSettings();
147
- if (isGenieHookInstalled(settings) && !options.force) {
148
- console.log('\x1b[33mGenie hooks are already installed.\x1b[0m');
149
- console.log('Use \x1b[36m--force\x1b[0m to reinstall.');
150
- console.log();
151
- return;
152
- }
153
-
154
- // Step 5: Write the hook script
155
- console.log('\x1b[2mWriting hook script...\x1b[0m');
156
- await writeHookScript();
157
- console.log(' \x1b[32m+\x1b[0m ' + getHookScriptDisplayPath());
158
- console.log();
159
-
160
- // Step 6: Update Claude settings
161
- console.log('\x1b[2mUpdating Claude Code settings...\x1b[0m');
162
- const updatedSettings = addGenieHook(settings);
163
- await saveClaudeSettings(updatedSettings);
164
- console.log(' \x1b[32m+\x1b[0m Hook registered in ' + contractClaudePath(getClaudeSettingsPath()));
165
- console.log();
166
-
167
- // Step 7: Print success
168
- const sessionName = config.hooks.collaborative?.sessionName || 'genie';
169
- printSuccessBox([
170
- '\x1b[32m+\x1b[0m Installation complete!',
171
- '',
172
- 'Restart Claude Code for changes to take effect.',
173
- '',
174
- 'After restart, all Bash commands will run in tmux.',
175
- `Watch with: tmux attach -t ${sessionName}`,
176
- ]);
177
- }
178
-
179
- /**
180
- * Uninstall hooks from Claude Code
181
- */
182
- export async function uninstallHooksCommand(options: { keepScript?: boolean } = {}): Promise<void> {
183
- console.log();
184
- console.log('\x1b[1m Uninstalling Genie Hooks\x1b[0m');
185
- console.log();
186
-
187
- // Step 1: Remove from Claude settings
188
- console.log('\x1b[2mRemoving from Claude Code settings...\x1b[0m');
189
- const settings = await loadClaudeSettings();
190
-
191
- if (!isGenieHookInstalled(settings)) {
192
- console.log(' \x1b[33m-\x1b[0m No genie hooks found in settings');
193
- } else {
194
- const updatedSettings = removeGenieHook(settings);
195
- await saveClaudeSettings(updatedSettings);
196
- console.log(' \x1b[32m+\x1b[0m Hook removed from ' + contractClaudePath(getClaudeSettingsPath()));
197
- }
198
- console.log();
199
-
200
- // Step 2: Remove hook script (unless --keep-script)
201
- if (!options.keepScript) {
202
- console.log('\x1b[2mRemoving hook script...\x1b[0m');
203
- if (hookScriptExists()) {
204
- removeHookScript();
205
- console.log(' \x1b[32m+\x1b[0m Deleted ' + getHookScriptDisplayPath());
206
- } else {
207
- console.log(' \x1b[33m-\x1b[0m Hook script not found');
208
- }
209
- console.log();
210
- } else {
211
- console.log('\x1b[2mKeeping hook script (--keep-script)\x1b[0m');
212
- console.log();
213
- }
214
-
215
- // Step 3: Print success
216
- printSuccessBox([
217
- '\x1b[32m+\x1b[0m Uninstallation complete!',
218
- '',
219
- 'Restart Claude Code for changes to take effect.',
220
- ]);
221
- }
222
-
223
- /**
224
- * Test the hook script
225
- */
226
- export async function testHooksCommand(): Promise<void> {
227
- console.log();
228
- console.log('\x1b[1m Testing Genie Hooks\x1b[0m');
229
- console.log();
230
-
231
- // Check if script exists
232
- if (!hookScriptExists()) {
233
- console.log('\x1b[31mError:\x1b[0m Hook script not found.');
234
- console.log('Run \x1b[36mgenie hooks install\x1b[0m first.');
235
- console.log();
236
- process.exit(1);
237
- }
238
-
239
- console.log('Script location: ' + getHookScriptDisplayPath());
240
- console.log();
241
-
242
- // Run test
243
- console.log('\x1b[2mTesting with sample Bash input...\x1b[0m');
244
- const result = await testHookScript();
245
-
246
- if (result.success) {
247
- console.log('\x1b[32m+\x1b[0m Hook script works correctly!');
248
- console.log();
249
- console.log('\x1b[2mSample output:\x1b[0m');
250
- try {
251
- const parsed = JSON.parse(result.output || '{}');
252
- console.log(JSON.stringify(parsed, null, 2));
253
- } catch {
254
- console.log(result.output);
255
- }
256
- } else {
257
- console.log('\x1b[31m-\x1b[0m Hook script test failed!');
258
- console.log();
259
- console.log('\x1b[31mError:\x1b[0m ' + result.error);
260
- if (result.output) {
261
- console.log();
262
- console.log('\x1b[2mOutput:\x1b[0m');
263
- console.log(result.output);
264
- }
265
- process.exit(1);
266
- }
267
-
268
- console.log();
269
- }
270
-
271
- /**
272
- * Show current hook configuration (moved from setup.ts)
273
- */
274
- export async function showHooksCommand(): Promise<void> {
275
- const config = await loadGenieConfig();
276
- const descriptions = describeEnabledHooks(config);
277
-
278
- console.log();
279
- console.log('\x1b[1m Current Hook Configuration\x1b[0m');
280
- console.log(` Genie config: ${contractClaudePath(getGenieConfigPath())}`);
281
- console.log(` Claude settings: ${contractClaudePath(getClaudeSettingsPath())}`);
282
- console.log();
283
-
284
- // Show enabled presets
285
- if (descriptions.length === 0) {
286
- console.log('\x1b[33m No hook presets enabled.\x1b[0m');
287
- console.log(' Run \x1b[36mgenie setup\x1b[0m to configure hooks.');
288
- } else {
289
- console.log('\x1b[2m Enabled presets:\x1b[0m');
290
- for (const desc of descriptions) {
291
- console.log(` \x1b[32m+\x1b[0m ${desc}`);
292
- }
293
- }
294
- console.log();
295
-
296
- // Show installation status
297
- const settings = await loadClaudeSettings();
298
- const installed = isGenieHookInstalled(settings);
299
- const scriptExists = hookScriptExists();
300
-
301
- console.log('\x1b[2m Installation status:\x1b[0m');
302
- if (installed && scriptExists) {
303
- console.log(' \x1b[32m+\x1b[0m Hooks installed in Claude Code');
304
- console.log(' \x1b[32m+\x1b[0m Hook script exists');
305
- } else if (installed && !scriptExists) {
306
- console.log(' \x1b[33m!\x1b[0m Hook registered but script missing');
307
- console.log(' Run \x1b[36mgenie hooks install --force\x1b[0m to fix');
308
- } else if (!installed && scriptExists) {
309
- console.log(' \x1b[33m!\x1b[0m Script exists but hook not registered');
310
- console.log(' Run \x1b[36mgenie hooks install\x1b[0m to register');
311
- } else {
312
- console.log(' \x1b[33m-\x1b[0m Hooks not installed');
313
- console.log(' Run \x1b[36mgenie hooks install\x1b[0m to install');
314
- }
315
-
316
- console.log();
317
- }