@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.
@@ -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, quickSetupCommand } from './genie-commands/setup.js';
6
+ import { setupCommand, type SetupOptions } from './genie-commands/setup.js';
7
7
  import { updateCommand } from './genie-commands/update.js';
8
- import {
9
- showHooksCommand,
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 - interactive hook configuration
31
+ // Setup command - configure genie settings
36
32
  program
37
33
  .command('setup')
38
- .description('Configure hooks and settings (interactive wizard)')
39
- .option('--quick', 'Use recommended defaults without prompts')
40
- .action(async (options) => {
41
- if (options.quick) {
42
- await quickSetupCommand();
43
- } else {
44
- await setupCommand();
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
- // Hooks command group - manage Claude Code hooks
55
- const hooks = program
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 hooks from Claude Code')
76
- .option('--keep-script', 'Keep the hook script file')
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
- * Create the genie hook entry for Claude settings
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
- * Add the genie hook to Claude settings
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
  */
@@ -4,13 +4,16 @@ import { join } from 'path';
4
4
  import {
5
5
  GenieConfig,
6
6
  GenieConfigSchema,
7
- HooksConfig,
7
+ GenieConfigV1Schema,
8
8
  LoggingConfig,
9
- PresetName,
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
+ }
@@ -1,5 +1,5 @@
1
1
  // Runtime version (baked in at build time)
2
- export const VERSION = '0.260202.0453';
2
+ export const VERSION = '0.260202.1607';
3
3
 
4
4
  // Generate version string from current datetime
5
5
  // Format: 0.YYMMDD.HHMM (e.g., 0.260201.1430 = Feb 1, 2026 at 14:30)