@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.
- package/dist/claudio.js +44 -45
- package/dist/genie.js +58 -135
- package/dist/term.js +71 -66
- package/install.sh +43 -7
- package/package.json +1 -1
- package/src/claudio.ts +31 -21
- package/src/commands/launch.ts +12 -68
- package/src/genie-commands/doctor.ts +327 -0
- package/src/genie-commands/setup.ts +317 -199
- package/src/genie-commands/uninstall.ts +176 -0
- package/src/genie.ts +24 -44
- package/src/lib/claude-settings.ts +22 -64
- package/src/lib/genie-config.ts +169 -57
- package/src/lib/version.ts +1 -1
- package/src/term-commands/exec.ts +28 -6
- package/src/term-commands/read.ts +6 -1
- package/src/term-commands/shortcuts.ts +14 -14
- package/src/term.ts +12 -2
- package/src/types/genie-config.ts +49 -81
- package/src/genie-commands/hooks.ts +0 -317
- package/src/lib/hook-script.ts +0 -263
- package/src/lib/hooks/compose.ts +0 -72
- package/src/lib/hooks/index.ts +0 -163
- package/src/lib/hooks/presets/audited.ts +0 -191
- package/src/lib/hooks/presets/collaborative.ts +0 -143
- package/src/lib/hooks/presets/sandboxed.ts +0 -153
- package/src/lib/hooks/presets/supervised.ts +0 -66
- package/src/lib/hooks/utils/escape.ts +0 -46
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Genie Uninstall Command
|
|
3
|
+
*
|
|
4
|
+
* Removes Genie CLI entirely:
|
|
5
|
+
* - Remove hooks from Claude Code (migration cleanup)
|
|
6
|
+
* - Delete ~/.genie directory
|
|
7
|
+
* - Remove symlinks from ~/.local/bin
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { confirm } from '@inquirer/prompts';
|
|
11
|
+
import { existsSync, rmSync, unlinkSync, lstatSync } from 'fs';
|
|
12
|
+
import { homedir } from 'os';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import {
|
|
15
|
+
loadClaudeSettings,
|
|
16
|
+
saveClaudeSettings,
|
|
17
|
+
isGenieHookInstalled,
|
|
18
|
+
removeGenieHook,
|
|
19
|
+
hookScriptExists,
|
|
20
|
+
removeHookScript,
|
|
21
|
+
} from '../lib/claude-settings.js';
|
|
22
|
+
import { getGenieDir, contractPath } from '../lib/genie-config.js';
|
|
23
|
+
|
|
24
|
+
const LOCAL_BIN = join(homedir(), '.local', 'bin');
|
|
25
|
+
|
|
26
|
+
// Symlinks that may have been created by source install
|
|
27
|
+
const SYMLINKS = ['genie', 'term', 'claudio'];
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if a path is a symlink pointing to genie bin
|
|
31
|
+
*/
|
|
32
|
+
function isGenieSymlink(path: string): boolean {
|
|
33
|
+
try {
|
|
34
|
+
if (!existsSync(path)) return false;
|
|
35
|
+
const stat = lstatSync(path);
|
|
36
|
+
if (!stat.isSymbolicLink()) return false;
|
|
37
|
+
// We don't need to check target - if it's our binary name, remove it
|
|
38
|
+
return true;
|
|
39
|
+
} catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Remove genie symlinks from ~/.local/bin
|
|
46
|
+
*/
|
|
47
|
+
function removeSymlinks(): string[] {
|
|
48
|
+
const removed: string[] = [];
|
|
49
|
+
|
|
50
|
+
for (const name of SYMLINKS) {
|
|
51
|
+
const symlinkPath = join(LOCAL_BIN, name);
|
|
52
|
+
if (isGenieSymlink(symlinkPath)) {
|
|
53
|
+
try {
|
|
54
|
+
unlinkSync(symlinkPath);
|
|
55
|
+
removed.push(name);
|
|
56
|
+
} catch {
|
|
57
|
+
// Ignore errors - may not have permission
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return removed;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Uninstall Genie CLI entirely
|
|
67
|
+
*/
|
|
68
|
+
export async function uninstallCommand(): Promise<void> {
|
|
69
|
+
console.log();
|
|
70
|
+
console.log('\x1b[1m\x1b[33m Uninstall Genie CLI\x1b[0m');
|
|
71
|
+
console.log();
|
|
72
|
+
|
|
73
|
+
const genieDir = getGenieDir();
|
|
74
|
+
const hasGenieDir = existsSync(genieDir);
|
|
75
|
+
const hasHookScript = hookScriptExists();
|
|
76
|
+
|
|
77
|
+
// Check what will be removed
|
|
78
|
+
const settings = await loadClaudeSettings();
|
|
79
|
+
const hasHookRegistered = isGenieHookInstalled(settings);
|
|
80
|
+
|
|
81
|
+
// Count symlinks
|
|
82
|
+
const existingSymlinks = SYMLINKS.filter((name) =>
|
|
83
|
+
isGenieSymlink(join(LOCAL_BIN, name))
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
// Show what will be removed
|
|
87
|
+
console.log('\x1b[2mThis will remove:\x1b[0m');
|
|
88
|
+
if (hasHookRegistered) {
|
|
89
|
+
console.log(' \x1b[31m-\x1b[0m Hook registration from Claude Code');
|
|
90
|
+
}
|
|
91
|
+
if (hasHookScript) {
|
|
92
|
+
console.log(' \x1b[31m-\x1b[0m Hook script (~/.claude/hooks/genie-bash-hook.sh)');
|
|
93
|
+
}
|
|
94
|
+
if (hasGenieDir) {
|
|
95
|
+
console.log(' \x1b[31m-\x1b[0m Genie directory (' + contractPath(genieDir) + ')');
|
|
96
|
+
}
|
|
97
|
+
if (existingSymlinks.length > 0) {
|
|
98
|
+
console.log(' \x1b[31m-\x1b[0m Symlinks from ~/.local/bin: ' + existingSymlinks.join(', '));
|
|
99
|
+
}
|
|
100
|
+
console.log();
|
|
101
|
+
|
|
102
|
+
if (!hasGenieDir && !hasHookScript && !hasHookRegistered && existingSymlinks.length === 0) {
|
|
103
|
+
console.log('\x1b[33mNothing to uninstall.\x1b[0m');
|
|
104
|
+
console.log();
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Confirm with user
|
|
109
|
+
const proceed = await confirm({
|
|
110
|
+
message: 'Are you sure you want to uninstall Genie CLI?',
|
|
111
|
+
default: false,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
if (!proceed) {
|
|
115
|
+
console.log();
|
|
116
|
+
console.log('\x1b[2mUninstall cancelled.\x1b[0m');
|
|
117
|
+
console.log();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
console.log();
|
|
122
|
+
|
|
123
|
+
// 1. Remove hooks from Claude Code
|
|
124
|
+
if (hasHookRegistered) {
|
|
125
|
+
console.log('\x1b[2mRemoving hook from Claude Code...\x1b[0m');
|
|
126
|
+
try {
|
|
127
|
+
const updatedSettings = removeGenieHook(settings);
|
|
128
|
+
await saveClaudeSettings(updatedSettings);
|
|
129
|
+
console.log(' \x1b[32m+\x1b[0m Hook unregistered');
|
|
130
|
+
} catch (error: any) {
|
|
131
|
+
console.log(' \x1b[33m!\x1b[0m Could not unregister hook: ' + error.message);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 2. Remove hook script
|
|
136
|
+
if (hasHookScript) {
|
|
137
|
+
console.log('\x1b[2mRemoving hook script...\x1b[0m');
|
|
138
|
+
try {
|
|
139
|
+
removeHookScript();
|
|
140
|
+
console.log(' \x1b[32m+\x1b[0m Hook script removed');
|
|
141
|
+
} catch (error: any) {
|
|
142
|
+
console.log(' \x1b[33m!\x1b[0m Could not remove hook script: ' + error.message);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// 3. Remove symlinks
|
|
147
|
+
if (existingSymlinks.length > 0) {
|
|
148
|
+
console.log('\x1b[2mRemoving symlinks...\x1b[0m');
|
|
149
|
+
const removed = removeSymlinks();
|
|
150
|
+
if (removed.length > 0) {
|
|
151
|
+
console.log(' \x1b[32m+\x1b[0m Removed: ' + removed.join(', '));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 4. Delete ~/.genie directory
|
|
156
|
+
if (hasGenieDir) {
|
|
157
|
+
console.log('\x1b[2mRemoving genie directory...\x1b[0m');
|
|
158
|
+
try {
|
|
159
|
+
rmSync(genieDir, { recursive: true, force: true });
|
|
160
|
+
console.log(' \x1b[32m+\x1b[0m Directory removed');
|
|
161
|
+
} catch (error: any) {
|
|
162
|
+
console.log(' \x1b[33m!\x1b[0m Could not remove directory: ' + error.message);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log();
|
|
167
|
+
console.log('\x1b[32m+\x1b[0m Genie CLI uninstalled.');
|
|
168
|
+
console.log();
|
|
169
|
+
|
|
170
|
+
// Note about npm/bun global package
|
|
171
|
+
console.log('\x1b[2mNote: If you installed via npm/bun, also run:\x1b[0m');
|
|
172
|
+
console.log(' \x1b[36mbun remove -g @automagik/genie\x1b[0m');
|
|
173
|
+
console.log(' \x1b[2mor\x1b[0m');
|
|
174
|
+
console.log(' \x1b[36mnpm uninstall -g @automagik/genie\x1b[0m');
|
|
175
|
+
console.log();
|
|
176
|
+
}
|
package/src/genie.ts
CHANGED
|
@@ -3,14 +3,10 @@
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import { VERSION } from './lib/version.js';
|
|
5
5
|
import { installCommand } from './genie-commands/install.js';
|
|
6
|
-
import { setupCommand,
|
|
6
|
+
import { setupCommand, type SetupOptions } from './genie-commands/setup.js';
|
|
7
7
|
import { updateCommand } from './genie-commands/update.js';
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
installHooksCommand,
|
|
11
|
-
uninstallHooksCommand,
|
|
12
|
-
testHooksCommand,
|
|
13
|
-
} from './genie-commands/hooks.js';
|
|
8
|
+
import { uninstallCommand } from './genie-commands/uninstall.js';
|
|
9
|
+
import { doctorCommand } from './genie-commands/doctor.js';
|
|
14
10
|
import {
|
|
15
11
|
shortcutsShowCommand,
|
|
16
12
|
shortcutsInstallCommand,
|
|
@@ -32,54 +28,38 @@ program
|
|
|
32
28
|
.option('--yes', 'Auto-approve all installations')
|
|
33
29
|
.action(installCommand);
|
|
34
30
|
|
|
35
|
-
// Setup command -
|
|
31
|
+
// Setup command - configure genie settings
|
|
36
32
|
program
|
|
37
33
|
.command('setup')
|
|
38
|
-
.description('Configure
|
|
39
|
-
.option('--quick', '
|
|
40
|
-
.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
34
|
+
.description('Configure genie settings')
|
|
35
|
+
.option('--quick', 'Accept all defaults')
|
|
36
|
+
.option('--shortcuts', 'Only configure keyboard shortcuts')
|
|
37
|
+
.option('--claudio', 'Only configure Claudio integration')
|
|
38
|
+
.option('--terminal', 'Only configure terminal defaults')
|
|
39
|
+
.option('--session', 'Only configure session settings')
|
|
40
|
+
.option('--reset', 'Reset configuration to defaults')
|
|
41
|
+
.option('--show', 'Show current configuration')
|
|
42
|
+
.action(async (options: SetupOptions) => {
|
|
43
|
+
await setupCommand(options);
|
|
46
44
|
});
|
|
47
45
|
|
|
46
|
+
// Doctor command - diagnostic checks
|
|
47
|
+
program
|
|
48
|
+
.command('doctor')
|
|
49
|
+
.description('Run diagnostic checks on genie installation')
|
|
50
|
+
.action(doctorCommand);
|
|
51
|
+
|
|
48
52
|
// Update command - pull latest and rebuild
|
|
49
53
|
program
|
|
50
54
|
.command('update')
|
|
51
55
|
.description('Update Genie CLI to the latest version')
|
|
52
56
|
.action(updateCommand);
|
|
53
57
|
|
|
54
|
-
//
|
|
55
|
-
|
|
56
|
-
.command('hooks')
|
|
57
|
-
.description('Manage Claude Code hooks');
|
|
58
|
-
|
|
59
|
-
// Make 'show' the default action for bare `genie hooks`
|
|
60
|
-
hooks.action(showHooksCommand);
|
|
61
|
-
|
|
62
|
-
hooks
|
|
63
|
-
.command('show')
|
|
64
|
-
.description('Show current hook configuration')
|
|
65
|
-
.action(showHooksCommand);
|
|
66
|
-
|
|
67
|
-
hooks
|
|
68
|
-
.command('install')
|
|
69
|
-
.description('Install hooks into Claude Code')
|
|
70
|
-
.option('--force', 'Overwrite existing hooks')
|
|
71
|
-
.action(installHooksCommand);
|
|
72
|
-
|
|
73
|
-
hooks
|
|
58
|
+
// Uninstall command - remove genie CLI
|
|
59
|
+
program
|
|
74
60
|
.command('uninstall')
|
|
75
|
-
.description('Remove
|
|
76
|
-
.
|
|
77
|
-
.action(uninstallHooksCommand);
|
|
78
|
-
|
|
79
|
-
hooks
|
|
80
|
-
.command('test')
|
|
81
|
-
.description('Test the hook script')
|
|
82
|
-
.action(testHooksCommand);
|
|
61
|
+
.description('Remove Genie CLI and clean up hooks')
|
|
62
|
+
.action(uninstallCommand);
|
|
83
63
|
|
|
84
64
|
// Shortcuts command group - manage tmux keyboard shortcuts
|
|
85
65
|
const shortcuts = program
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Uses Zod with passthrough() to preserve unknown fields.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
8
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'fs';
|
|
9
9
|
import { homedir } from 'os';
|
|
10
10
|
import { join } from 'path';
|
|
11
11
|
import { z } from 'zod';
|
|
@@ -43,7 +43,7 @@ const ClaudeSettingsSchema = z.object({
|
|
|
43
43
|
|
|
44
44
|
export type ClaudeSettings = z.infer<typeof ClaudeSettingsSchema>;
|
|
45
45
|
|
|
46
|
-
// Constants for the genie hook
|
|
46
|
+
// Constants for the genie hook (used for migration/cleanup)
|
|
47
47
|
export const GENIE_HOOK_SCRIPT_NAME = 'genie-bash-hook.sh';
|
|
48
48
|
export const GENIE_HOOK_MATCHER = 'Bash';
|
|
49
49
|
|
|
@@ -69,7 +69,7 @@ export function getClaudeSettingsPath(): string {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Get the path to the genie hook script
|
|
72
|
+
* Get the path to the genie hook script (for cleanup)
|
|
73
73
|
*/
|
|
74
74
|
export function getGenieHookScriptPath(): string {
|
|
75
75
|
return join(CLAUDE_HOOKS_DIR, GENIE_HOOK_SCRIPT_NAME);
|
|
@@ -91,16 +91,6 @@ export function ensureClaudeDir(): void {
|
|
|
91
91
|
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
/**
|
|
95
|
-
* Ensure the Claude hooks directory exists
|
|
96
|
-
*/
|
|
97
|
-
export function ensureClaudeHooksDir(): void {
|
|
98
|
-
ensureClaudeDir();
|
|
99
|
-
if (!existsSync(CLAUDE_HOOKS_DIR)) {
|
|
100
|
-
mkdirSync(CLAUDE_HOOKS_DIR, { recursive: true });
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
94
|
/**
|
|
105
95
|
* Load Claude settings, returning defaults if not found
|
|
106
96
|
*/
|
|
@@ -136,23 +126,7 @@ export async function saveClaudeSettings(settings: ClaudeSettings): Promise<void
|
|
|
136
126
|
}
|
|
137
127
|
|
|
138
128
|
/**
|
|
139
|
-
*
|
|
140
|
-
*/
|
|
141
|
-
function createGenieHookEntry(scriptPath: string): z.infer<typeof MatcherHooksSchema> {
|
|
142
|
-
return {
|
|
143
|
-
matcher: GENIE_HOOK_MATCHER,
|
|
144
|
-
hooks: [
|
|
145
|
-
{
|
|
146
|
-
type: 'command',
|
|
147
|
-
command: scriptPath,
|
|
148
|
-
timeout: 600,
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Check if the genie hook is installed in the settings
|
|
129
|
+
* Check if the genie hook is installed in the settings (for migration cleanup)
|
|
156
130
|
*/
|
|
157
131
|
export function isGenieHookInstalled(settings: ClaudeSettings): boolean {
|
|
158
132
|
const preToolUse = settings.hooks?.PreToolUse;
|
|
@@ -170,40 +144,7 @@ export function isGenieHookInstalled(settings: ClaudeSettings): boolean {
|
|
|
170
144
|
}
|
|
171
145
|
|
|
172
146
|
/**
|
|
173
|
-
*
|
|
174
|
-
* Returns the modified settings (does not save to disk)
|
|
175
|
-
*/
|
|
176
|
-
export function addGenieHook(settings: ClaudeSettings): ClaudeSettings {
|
|
177
|
-
const scriptPath = getGenieHookScriptPath();
|
|
178
|
-
const hookEntry = createGenieHookEntry(scriptPath);
|
|
179
|
-
|
|
180
|
-
// Initialize hooks structure if needed
|
|
181
|
-
if (!settings.hooks) {
|
|
182
|
-
settings.hooks = {};
|
|
183
|
-
}
|
|
184
|
-
if (!settings.hooks.PreToolUse) {
|
|
185
|
-
settings.hooks.PreToolUse = [];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// Check if already installed
|
|
189
|
-
if (isGenieHookInstalled(settings)) {
|
|
190
|
-
return settings;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Remove any existing Bash matcher entries that might conflict
|
|
194
|
-
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter(
|
|
195
|
-
(entry) => !(entry.matcher === GENIE_HOOK_MATCHER &&
|
|
196
|
-
entry.hooks?.some((h) => h.command?.includes('genie')))
|
|
197
|
-
);
|
|
198
|
-
|
|
199
|
-
// Add the new hook entry
|
|
200
|
-
settings.hooks.PreToolUse.push(hookEntry);
|
|
201
|
-
|
|
202
|
-
return settings;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Remove the genie hook from Claude settings
|
|
147
|
+
* Remove the genie hook from Claude settings (for migration cleanup)
|
|
207
148
|
* Returns the modified settings (does not save to disk)
|
|
208
149
|
*/
|
|
209
150
|
export function removeGenieHook(settings: ClaudeSettings): ClaudeSettings {
|
|
@@ -237,6 +178,23 @@ export function removeGenieHook(settings: ClaudeSettings): ClaudeSettings {
|
|
|
237
178
|
return settings;
|
|
238
179
|
}
|
|
239
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Check if hook script exists (for cleanup)
|
|
183
|
+
*/
|
|
184
|
+
export function hookScriptExists(): boolean {
|
|
185
|
+
return existsSync(getGenieHookScriptPath());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Remove the hook script file (for cleanup)
|
|
190
|
+
*/
|
|
191
|
+
export function removeHookScript(): void {
|
|
192
|
+
const scriptPath = getGenieHookScriptPath();
|
|
193
|
+
if (existsSync(scriptPath)) {
|
|
194
|
+
unlinkSync(scriptPath);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
240
198
|
/**
|
|
241
199
|
* Contract home directory to ~ in a path (for display)
|
|
242
200
|
*/
|
package/src/lib/genie-config.ts
CHANGED
|
@@ -4,13 +4,16 @@ import { join } from 'path';
|
|
|
4
4
|
import {
|
|
5
5
|
GenieConfig,
|
|
6
6
|
GenieConfigSchema,
|
|
7
|
-
|
|
7
|
+
GenieConfigV1Schema,
|
|
8
8
|
LoggingConfig,
|
|
9
|
-
|
|
9
|
+
TerminalConfig,
|
|
10
|
+
SessionConfig,
|
|
11
|
+
ShortcutsConfig,
|
|
10
12
|
} from '../types/genie-config.js';
|
|
11
13
|
|
|
12
14
|
const GENIE_DIR = join(homedir(), '.genie');
|
|
13
15
|
const GENIE_CONFIG_FILE = join(GENIE_DIR, 'config.json');
|
|
16
|
+
const CONFIG_VERSION = 2;
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* Get the path to the genie config directory
|
|
@@ -42,21 +45,70 @@ export function ensureGenieDir(): void {
|
|
|
42
45
|
}
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/**
|
|
49
|
+
* Migrate v1 config to v2 format
|
|
50
|
+
*/
|
|
51
|
+
export function migrateConfig(oldConfig: unknown): GenieConfig {
|
|
52
|
+
// Try parsing as v1 config
|
|
53
|
+
const v1Result = GenieConfigV1Schema.safeParse(oldConfig);
|
|
54
|
+
|
|
55
|
+
if (v1Result.success) {
|
|
56
|
+
const v1 = v1Result.data;
|
|
57
|
+
return GenieConfigSchema.parse({
|
|
58
|
+
version: CONFIG_VERSION,
|
|
59
|
+
session: {
|
|
60
|
+
name: v1.session.name,
|
|
61
|
+
defaultWindow: v1.session.defaultWindow,
|
|
62
|
+
autoCreate: true,
|
|
63
|
+
},
|
|
64
|
+
terminal: {
|
|
65
|
+
execTimeout: 120000,
|
|
66
|
+
readLines: 100,
|
|
67
|
+
worktreeBase: '.worktrees',
|
|
68
|
+
},
|
|
69
|
+
logging: {
|
|
70
|
+
tmuxDebug: v1.logging.tmuxDebug,
|
|
71
|
+
verbose: false,
|
|
72
|
+
},
|
|
73
|
+
shell: {
|
|
74
|
+
preference: 'auto',
|
|
75
|
+
},
|
|
76
|
+
shortcuts: {
|
|
77
|
+
tmuxInstalled: false,
|
|
78
|
+
shellInstalled: false,
|
|
79
|
+
},
|
|
80
|
+
installMethod: v1.installMethod,
|
|
81
|
+
setupComplete: false,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// If not valid v1, return default config
|
|
86
|
+
return GenieConfigSchema.parse({});
|
|
87
|
+
}
|
|
88
|
+
|
|
45
89
|
/**
|
|
46
90
|
* Load genie config, returning defaults if not found
|
|
91
|
+
* Automatically migrates v1 configs to v2
|
|
47
92
|
*/
|
|
48
93
|
export async function loadGenieConfig(): Promise<GenieConfig> {
|
|
49
94
|
if (!existsSync(GENIE_CONFIG_FILE)) {
|
|
50
|
-
// Return default config
|
|
51
95
|
return GenieConfigSchema.parse({});
|
|
52
96
|
}
|
|
53
97
|
|
|
54
98
|
try {
|
|
55
99
|
const content = readFileSync(GENIE_CONFIG_FILE, 'utf-8');
|
|
56
100
|
const data = JSON.parse(content);
|
|
101
|
+
|
|
102
|
+
// Check if migration is needed (no version or version < 2)
|
|
103
|
+
if (!data.version || data.version < CONFIG_VERSION) {
|
|
104
|
+
const migrated = migrateConfig(data);
|
|
105
|
+
// Save migrated config
|
|
106
|
+
await saveGenieConfig(migrated);
|
|
107
|
+
return migrated;
|
|
108
|
+
}
|
|
109
|
+
|
|
57
110
|
return GenieConfigSchema.parse(data);
|
|
58
111
|
} catch (error: any) {
|
|
59
|
-
// If config is invalid, return defaults
|
|
60
112
|
console.warn(`Warning: Invalid genie config, using defaults: ${error.message}`);
|
|
61
113
|
return GenieConfigSchema.parse({});
|
|
62
114
|
}
|
|
@@ -84,59 +136,6 @@ export function getDefaultGenieConfig(): GenieConfig {
|
|
|
84
136
|
return GenieConfigSchema.parse({});
|
|
85
137
|
}
|
|
86
138
|
|
|
87
|
-
/**
|
|
88
|
-
* Check if a hook preset is enabled
|
|
89
|
-
*/
|
|
90
|
-
export function isPresetEnabled(config: GenieConfig, preset: PresetName): boolean {
|
|
91
|
-
return config.hooks.enabled.includes(preset);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Enable a hook preset
|
|
96
|
-
*/
|
|
97
|
-
export async function enablePreset(preset: PresetName): Promise<void> {
|
|
98
|
-
const config = await loadGenieConfig();
|
|
99
|
-
if (!config.hooks.enabled.includes(preset)) {
|
|
100
|
-
config.hooks.enabled.push(preset);
|
|
101
|
-
await saveGenieConfig(config);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Disable a hook preset
|
|
107
|
-
*/
|
|
108
|
-
export async function disablePreset(preset: PresetName): Promise<void> {
|
|
109
|
-
const config = await loadGenieConfig();
|
|
110
|
-
config.hooks.enabled = config.hooks.enabled.filter((p) => p !== preset);
|
|
111
|
-
await saveGenieConfig(config);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Get enabled presets
|
|
116
|
-
*/
|
|
117
|
-
export async function getEnabledPresets(): Promise<PresetName[]> {
|
|
118
|
-
const config = await loadGenieConfig();
|
|
119
|
-
return config.hooks.enabled;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Set enabled presets (replaces all)
|
|
124
|
-
*/
|
|
125
|
-
export async function setEnabledPresets(presets: PresetName[]): Promise<void> {
|
|
126
|
-
const config = await loadGenieConfig();
|
|
127
|
-
config.hooks.enabled = presets;
|
|
128
|
-
await saveGenieConfig(config);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Update hooks configuration
|
|
133
|
-
*/
|
|
134
|
-
export async function updateHooksConfig(hooks: Partial<HooksConfig>): Promise<void> {
|
|
135
|
-
const config = await loadGenieConfig();
|
|
136
|
-
config.hooks = { ...config.hooks, ...hooks };
|
|
137
|
-
await saveGenieConfig(config);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
139
|
/**
|
|
141
140
|
* Update logging configuration
|
|
142
141
|
*/
|
|
@@ -157,6 +156,12 @@ export function loadGenieConfigSync(): GenieConfig {
|
|
|
157
156
|
try {
|
|
158
157
|
const content = readFileSync(GENIE_CONFIG_FILE, 'utf-8');
|
|
159
158
|
const data = JSON.parse(content);
|
|
159
|
+
|
|
160
|
+
// Check if migration is needed
|
|
161
|
+
if (!data.version || data.version < CONFIG_VERSION) {
|
|
162
|
+
return migrateConfig(data);
|
|
163
|
+
}
|
|
164
|
+
|
|
160
165
|
return GenieConfigSchema.parse(data);
|
|
161
166
|
} catch {
|
|
162
167
|
return GenieConfigSchema.parse({});
|
|
@@ -199,3 +204,110 @@ export function contractPath(path: string): string {
|
|
|
199
204
|
}
|
|
200
205
|
return path;
|
|
201
206
|
}
|
|
207
|
+
|
|
208
|
+
// ============================================================================
|
|
209
|
+
// New helper functions for v2 config
|
|
210
|
+
// ============================================================================
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get terminal configuration
|
|
214
|
+
*/
|
|
215
|
+
export function getTerminalConfig(): TerminalConfig {
|
|
216
|
+
const config = loadGenieConfigSync();
|
|
217
|
+
return config.terminal;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Update terminal configuration
|
|
222
|
+
*/
|
|
223
|
+
export async function updateTerminalConfig(partial: Partial<TerminalConfig>): Promise<void> {
|
|
224
|
+
const config = await loadGenieConfig();
|
|
225
|
+
config.terminal = { ...config.terminal, ...partial };
|
|
226
|
+
await saveGenieConfig(config);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get session name from config
|
|
231
|
+
*/
|
|
232
|
+
export function getSessionName(): string {
|
|
233
|
+
const config = loadGenieConfigSync();
|
|
234
|
+
return config.session.name;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Get session configuration
|
|
239
|
+
*/
|
|
240
|
+
export function getSessionConfig(): SessionConfig {
|
|
241
|
+
const config = loadGenieConfigSync();
|
|
242
|
+
return config.session;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Update session configuration
|
|
247
|
+
*/
|
|
248
|
+
export async function updateSessionConfig(partial: Partial<SessionConfig>): Promise<void> {
|
|
249
|
+
const config = await loadGenieConfig();
|
|
250
|
+
config.session = { ...config.session, ...partial };
|
|
251
|
+
await saveGenieConfig(config);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Check if setup has been completed
|
|
256
|
+
*/
|
|
257
|
+
export function isSetupComplete(): boolean {
|
|
258
|
+
if (!genieConfigExists()) return false;
|
|
259
|
+
const config = loadGenieConfigSync();
|
|
260
|
+
return config.setupComplete ?? false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Mark setup as complete
|
|
265
|
+
*/
|
|
266
|
+
export async function markSetupComplete(): Promise<void> {
|
|
267
|
+
const config = await loadGenieConfig();
|
|
268
|
+
config.setupComplete = true;
|
|
269
|
+
config.lastSetupAt = new Date().toISOString();
|
|
270
|
+
await saveGenieConfig(config);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Reset config to defaults
|
|
275
|
+
*/
|
|
276
|
+
export async function resetConfig(): Promise<void> {
|
|
277
|
+
const defaultConfig = getDefaultGenieConfig();
|
|
278
|
+
await saveGenieConfig(defaultConfig);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Get shortcuts configuration
|
|
283
|
+
*/
|
|
284
|
+
export function getShortcutsConfig(): ShortcutsConfig {
|
|
285
|
+
const config = loadGenieConfigSync();
|
|
286
|
+
return config.shortcuts;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Update shortcuts configuration
|
|
291
|
+
*/
|
|
292
|
+
export async function updateShortcutsConfig(partial: Partial<ShortcutsConfig>): Promise<void> {
|
|
293
|
+
const config = await loadGenieConfig();
|
|
294
|
+
config.shortcuts = { ...config.shortcuts, ...partial };
|
|
295
|
+
await saveGenieConfig(config);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Check if claudio integration is enabled
|
|
300
|
+
*/
|
|
301
|
+
export function isClaudioEnabled(): boolean {
|
|
302
|
+
const config = loadGenieConfigSync();
|
|
303
|
+
return config.claudio?.enabled ?? false;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Update claudio configuration
|
|
308
|
+
*/
|
|
309
|
+
export async function updateClaudioConfig(enabled: boolean): Promise<void> {
|
|
310
|
+
const config = await loadGenieConfig();
|
|
311
|
+
config.claudio = { enabled };
|
|
312
|
+
await saveGenieConfig(config);
|
|
313
|
+
}
|
package/src/lib/version.ts
CHANGED