@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as tmux from '../lib/tmux.js';
|
|
2
|
+
|
|
3
|
+
export interface StatusOptions {
|
|
4
|
+
command?: string;
|
|
5
|
+
json?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface SessionStatus {
|
|
9
|
+
session: string;
|
|
10
|
+
id: string;
|
|
11
|
+
attached: boolean;
|
|
12
|
+
windows: number;
|
|
13
|
+
panes: number;
|
|
14
|
+
state: 'idle' | 'busy';
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface CommandStatus {
|
|
18
|
+
id: string;
|
|
19
|
+
paneId: string;
|
|
20
|
+
command: string;
|
|
21
|
+
status: 'pending' | 'completed' | 'error';
|
|
22
|
+
exitCode?: number;
|
|
23
|
+
startTime: string;
|
|
24
|
+
result?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function getStatus(session: string, options: StatusOptions = {}): Promise<void> {
|
|
28
|
+
try {
|
|
29
|
+
// If checking a specific command
|
|
30
|
+
if (options.command) {
|
|
31
|
+
await getCommandStatus(options.command, options.json);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Find session by name
|
|
36
|
+
const sessionObj = await tmux.findSessionByName(session);
|
|
37
|
+
if (!sessionObj) {
|
|
38
|
+
console.error(`Session "${session}" not found`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Get windows and panes
|
|
43
|
+
const windows = await tmux.listWindows(sessionObj.id);
|
|
44
|
+
let totalPanes = 0;
|
|
45
|
+
|
|
46
|
+
for (const window of windows) {
|
|
47
|
+
const panes = await tmux.listPanes(window.id);
|
|
48
|
+
totalPanes += panes.length;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Determine state (simplified: idle if session exists, could be enhanced with activity detection)
|
|
52
|
+
const state: 'idle' | 'busy' = 'idle';
|
|
53
|
+
|
|
54
|
+
const status: SessionStatus = {
|
|
55
|
+
session: sessionObj.name,
|
|
56
|
+
id: sessionObj.id,
|
|
57
|
+
attached: sessionObj.attached,
|
|
58
|
+
windows: windows.length,
|
|
59
|
+
panes: totalPanes,
|
|
60
|
+
state,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (options.json) {
|
|
64
|
+
console.log(JSON.stringify(status, null, 2));
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(`Session: ${status.session}`);
|
|
69
|
+
console.log(`ID: ${status.id}`);
|
|
70
|
+
console.log(`Attached: ${status.attached ? 'yes' : 'no'}`);
|
|
71
|
+
console.log(`Windows: ${status.windows}`);
|
|
72
|
+
console.log(`Panes: ${status.panes}`);
|
|
73
|
+
console.log(`State: ${status.state}`);
|
|
74
|
+
} catch (error: any) {
|
|
75
|
+
console.error(`Error getting status: ${error.message}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function getCommandStatus(commandId: string, json?: boolean): Promise<void> {
|
|
81
|
+
try {
|
|
82
|
+
const result = await tmux.checkCommandStatus(commandId);
|
|
83
|
+
|
|
84
|
+
if (!result) {
|
|
85
|
+
console.error(`Command "${commandId}" not found`);
|
|
86
|
+
process.exit(1);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const status: CommandStatus = {
|
|
90
|
+
id: result.id,
|
|
91
|
+
paneId: result.paneId,
|
|
92
|
+
command: result.command,
|
|
93
|
+
status: result.status,
|
|
94
|
+
exitCode: result.exitCode,
|
|
95
|
+
startTime: result.startTime.toISOString(),
|
|
96
|
+
result: result.result,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
if (json) {
|
|
100
|
+
console.log(JSON.stringify(status, null, 2));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
console.log(`Command: ${status.id}`);
|
|
105
|
+
console.log(`Status: ${status.status}`);
|
|
106
|
+
console.log(`Exit Code: ${status.exitCode ?? 'N/A'}`);
|
|
107
|
+
console.log(`Start Time: ${status.startTime}`);
|
|
108
|
+
|
|
109
|
+
if (status.result) {
|
|
110
|
+
console.log(`\nOutput:\n${status.result}`);
|
|
111
|
+
}
|
|
112
|
+
} catch (error: any) {
|
|
113
|
+
console.error(`Error getting command status: ${error.message}`);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as tmux from '../lib/tmux.js';
|
|
2
|
+
|
|
3
|
+
export interface WindowListOptions {
|
|
4
|
+
json?: boolean;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function listWindows(session: string, options: WindowListOptions = {}): Promise<void> {
|
|
8
|
+
try {
|
|
9
|
+
// Find session by name first
|
|
10
|
+
const sessionObj = await tmux.findSessionByName(session);
|
|
11
|
+
if (!sessionObj) {
|
|
12
|
+
console.error(`Session "${session}" not found`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const windows = await tmux.listWindows(sessionObj.id);
|
|
17
|
+
|
|
18
|
+
if (options.json) {
|
|
19
|
+
console.log(JSON.stringify(windows, null, 2));
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (windows.length === 0) {
|
|
24
|
+
console.log('No windows found');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
console.log('WINDOW ID\t\tNAME\t\t\tACTIVE');
|
|
29
|
+
console.log('─'.repeat(60));
|
|
30
|
+
|
|
31
|
+
for (const window of windows) {
|
|
32
|
+
const active = window.active ? 'yes' : 'no';
|
|
33
|
+
console.log(`${window.id}\t\t${window.name}\t\t\t${active}`);
|
|
34
|
+
}
|
|
35
|
+
} catch (error: any) {
|
|
36
|
+
console.error(`Error listing windows: ${error.message}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function createWindow(session: string, name: string): Promise<void> {
|
|
42
|
+
try {
|
|
43
|
+
// Find session by name first
|
|
44
|
+
const sessionObj = await tmux.findSessionByName(session);
|
|
45
|
+
if (!sessionObj) {
|
|
46
|
+
console.error(`Session "${session}" not found`);
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const window = await tmux.createWindow(sessionObj.id, name);
|
|
51
|
+
|
|
52
|
+
if (window) {
|
|
53
|
+
console.log(`Window created: ${window.id}`);
|
|
54
|
+
} else {
|
|
55
|
+
console.error('Failed to create window');
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
} catch (error: any) {
|
|
59
|
+
console.error(`Error creating window: ${error.message}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function removeWindow(windowId: string): Promise<void> {
|
|
65
|
+
try {
|
|
66
|
+
await tmux.killWindow(windowId);
|
|
67
|
+
console.log(`Window removed: ${windowId}`);
|
|
68
|
+
} catch (error: any) {
|
|
69
|
+
console.error(`Error removing window: ${error.message}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
}
|
package/src/term.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import * as newCmd from './term-commands/new.js';
|
|
5
|
+
import * as lsCmd from './term-commands/ls.js';
|
|
6
|
+
import * as attachCmd from './term-commands/attach.js';
|
|
7
|
+
import * as rmCmd from './term-commands/rm.js';
|
|
8
|
+
import * as readCmd from './term-commands/read.js';
|
|
9
|
+
import * as execCmd from './term-commands/exec.js';
|
|
10
|
+
import * as sendCmd from './term-commands/send.js';
|
|
11
|
+
import * as splitCmd from './term-commands/split.js';
|
|
12
|
+
import * as hookCmd from './term-commands/hook.js';
|
|
13
|
+
import * as windowCmd from './term-commands/window.js';
|
|
14
|
+
import * as paneCmd from './term-commands/pane.js';
|
|
15
|
+
import * as statusCmd from './term-commands/status.js';
|
|
16
|
+
import * as shortcutsCmd from './term-commands/shortcuts.js';
|
|
17
|
+
|
|
18
|
+
const program = new Command();
|
|
19
|
+
|
|
20
|
+
program
|
|
21
|
+
.name('term')
|
|
22
|
+
.description(`AI-friendly terminal orchestration (tmux wrapper)
|
|
23
|
+
|
|
24
|
+
Workflow: new → exec → read → rm
|
|
25
|
+
Full control: window new/ls/rm, pane ls/rm, split, status`)
|
|
26
|
+
.version('0.2.0');
|
|
27
|
+
|
|
28
|
+
// Session management
|
|
29
|
+
program
|
|
30
|
+
.command('new <name>')
|
|
31
|
+
.description('Create a new tmux session')
|
|
32
|
+
.option('-d, --workspace <path>', 'Working directory for the session')
|
|
33
|
+
.option('-w, --worktree', 'Create git worktree in .worktrees/<name>/')
|
|
34
|
+
.action(async (name: string, options: { workspace?: string; worktree?: boolean }) => {
|
|
35
|
+
await newCmd.createNewSession(name, options);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
program
|
|
39
|
+
.command('ls')
|
|
40
|
+
.description('List all tmux sessions')
|
|
41
|
+
.option('--json', 'Output as JSON')
|
|
42
|
+
.action(async (options: { json?: boolean }) => {
|
|
43
|
+
await lsCmd.listAllSessions(options);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
program
|
|
47
|
+
.command('attach <name>')
|
|
48
|
+
.description('Attach to a tmux session')
|
|
49
|
+
.action(async (name: string) => {
|
|
50
|
+
await attachCmd.attachToSession(name);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
program
|
|
54
|
+
.command('rm <name>')
|
|
55
|
+
.description('Remove a tmux session')
|
|
56
|
+
.option('--keep-worktree', 'Keep worktree folder when removing session')
|
|
57
|
+
.action(async (name: string, options: { keepWorktree?: boolean }) => {
|
|
58
|
+
await rmCmd.removeSession(name, options);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Log reading (CRITICAL for AI orchestration)
|
|
62
|
+
program
|
|
63
|
+
.command('read <session>')
|
|
64
|
+
.description('Read logs from a tmux session')
|
|
65
|
+
.option('-n, --lines <number>', 'Number of lines to read (default: 100)', '100')
|
|
66
|
+
.option('--from <line>', 'Start line number')
|
|
67
|
+
.option('--to <line>', 'End line number')
|
|
68
|
+
.option('--range <range>', 'Line range (e.g., 100:200)')
|
|
69
|
+
.option('--search <pattern>', 'Search for pattern')
|
|
70
|
+
.option('--grep <pattern>', 'Regex search pattern')
|
|
71
|
+
.option('-f, --follow', 'Follow mode (live tail)')
|
|
72
|
+
.option('--all', 'Export entire scrollback buffer')
|
|
73
|
+
.option('--reverse', 'Reverse chronological (newest first)')
|
|
74
|
+
.option('--json', 'Output as JSON')
|
|
75
|
+
.action(async (session: string, options: readCmd.ReadOptions) => {
|
|
76
|
+
await readCmd.readSessionLogs(session, options);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Command execution
|
|
80
|
+
program
|
|
81
|
+
.command('exec <session> <command...>')
|
|
82
|
+
.description('Execute command in a tmux session')
|
|
83
|
+
.action(async (session: string, command: string[]) => {
|
|
84
|
+
await execCmd.executeInSession(session, command.join(' '));
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
program
|
|
88
|
+
.command('send <session> <keys>')
|
|
89
|
+
.description('Send raw keys to a tmux session')
|
|
90
|
+
.action(async (session: string, keys: string) => {
|
|
91
|
+
await sendCmd.sendKeysToSession(session, keys);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Pane splitting
|
|
95
|
+
program
|
|
96
|
+
.command('split <session> [direction]')
|
|
97
|
+
.description('Split pane in a tmux session (h=horizontal, v=vertical)')
|
|
98
|
+
.option('-d, --workspace <path>', 'Working directory for the new pane')
|
|
99
|
+
.option('-w, --worktree <branch>', 'Create git worktree in .worktrees/<branch>/')
|
|
100
|
+
.action(async (session: string, direction: string | undefined, options: { workspace?: string; worktree?: string }) => {
|
|
101
|
+
await splitCmd.splitSessionPane(session, direction, options);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Status command
|
|
105
|
+
program
|
|
106
|
+
.command('status <session>')
|
|
107
|
+
.description('Check session state (idle/busy, pane count)')
|
|
108
|
+
.option('--command <id>', 'Check specific command status')
|
|
109
|
+
.option('--json', 'Output as JSON')
|
|
110
|
+
.action(async (session: string, options: statusCmd.StatusOptions) => {
|
|
111
|
+
await statusCmd.getStatus(session, options);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Window management
|
|
115
|
+
const windowProgram = program.command('window').description('Manage tmux windows');
|
|
116
|
+
|
|
117
|
+
windowProgram
|
|
118
|
+
.command('new <session> <name>')
|
|
119
|
+
.description('Create a new window in session')
|
|
120
|
+
.action(async (session: string, name: string) => {
|
|
121
|
+
await windowCmd.createWindow(session, name);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
windowProgram
|
|
125
|
+
.command('ls <session>')
|
|
126
|
+
.description('List windows in session')
|
|
127
|
+
.option('--json', 'Output as JSON')
|
|
128
|
+
.action(async (session: string, options: { json?: boolean }) => {
|
|
129
|
+
await windowCmd.listWindows(session, options);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
windowProgram
|
|
133
|
+
.command('rm <window-id>')
|
|
134
|
+
.description('Remove window by ID')
|
|
135
|
+
.action(async (windowId: string) => {
|
|
136
|
+
await windowCmd.removeWindow(windowId);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Pane management
|
|
140
|
+
const paneProgram = program.command('pane').description('Manage tmux panes');
|
|
141
|
+
|
|
142
|
+
paneProgram
|
|
143
|
+
.command('ls <session>')
|
|
144
|
+
.description('List all panes in session')
|
|
145
|
+
.option('--json', 'Output as JSON')
|
|
146
|
+
.action(async (session: string, options: { json?: boolean }) => {
|
|
147
|
+
await paneCmd.listPanes(session, options);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
paneProgram
|
|
151
|
+
.command('rm <pane-id>')
|
|
152
|
+
.description('Remove pane by ID')
|
|
153
|
+
.action(async (paneId: string) => {
|
|
154
|
+
await paneCmd.removePane(paneId);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Hook management
|
|
158
|
+
const hookProgram = program.command('hook').description('Manage tmux hooks');
|
|
159
|
+
|
|
160
|
+
hookProgram
|
|
161
|
+
.command('set <event> <command>')
|
|
162
|
+
.description('Set a tmux hook')
|
|
163
|
+
.action(async (event: string, command: string) => {
|
|
164
|
+
await hookCmd.setHook(event, command);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
hookProgram
|
|
168
|
+
.command('list')
|
|
169
|
+
.description('List all tmux hooks')
|
|
170
|
+
.action(async () => {
|
|
171
|
+
await hookCmd.listHooks();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
hookProgram
|
|
175
|
+
.command('rm <event>')
|
|
176
|
+
.description('Remove a tmux hook')
|
|
177
|
+
.action(async (event: string) => {
|
|
178
|
+
await hookCmd.removeHook(event);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Shortcuts command
|
|
182
|
+
program
|
|
183
|
+
.command('shortcuts')
|
|
184
|
+
.description('Warp-like keyboard shortcuts for tmux/Termux')
|
|
185
|
+
.option('--tmux', 'Output tmux.conf snippet')
|
|
186
|
+
.option('--termux', 'Output termux.properties snippet')
|
|
187
|
+
.option('--install', 'Install to config files (interactive)')
|
|
188
|
+
.action(async (options: shortcutsCmd.ShortcutsOptions) => {
|
|
189
|
+
await shortcutsCmd.handleShortcuts(options);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
program.parse();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
export const ProfileSchema = z.object({
|
|
4
|
+
opus: z.string(),
|
|
5
|
+
sonnet: z.string(),
|
|
6
|
+
haiku: z.string(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export const ConfigSchema = z.object({
|
|
10
|
+
apiUrl: z.string().url(),
|
|
11
|
+
apiKey: z.string().min(1),
|
|
12
|
+
defaultProfile: z.string().optional(),
|
|
13
|
+
profiles: z.record(ProfileSchema),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export type Profile = z.infer<typeof ProfileSchema>;
|
|
17
|
+
export type Config = z.infer<typeof ConfigSchema>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Genie Configuration Schema
|
|
5
|
+
*
|
|
6
|
+
* Stored at ~/.genie/config.json
|
|
7
|
+
* Manages hook presets and session configuration for the genie CLI.
|
|
8
|
+
*/
|
|
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']),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Collaborative preset configuration
|
|
26
|
+
export const CollaborativeConfigSchema = z.object({
|
|
27
|
+
sessionName: z.string().default('genie'),
|
|
28
|
+
windowName: z.string().default('shell'),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Hook presets configuration
|
|
32
|
+
export const HooksConfigSchema = z.object({
|
|
33
|
+
enabled: z.array(z.enum(['collaborative', 'supervised', 'sandboxed', 'audited'])).default([]),
|
|
34
|
+
collaborative: CollaborativeConfigSchema.optional(),
|
|
35
|
+
supervised: SupervisedConfigSchema.optional(),
|
|
36
|
+
sandboxed: SandboxedConfigSchema.optional(),
|
|
37
|
+
audited: AuditedConfigSchema.optional(),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// Session configuration
|
|
41
|
+
export const SessionConfigSchema = z.object({
|
|
42
|
+
name: z.string().default('genie'),
|
|
43
|
+
defaultWindow: z.string().default('shell'),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Full genie configuration
|
|
47
|
+
export const GenieConfigSchema = z.object({
|
|
48
|
+
hooks: HooksConfigSchema.default({}),
|
|
49
|
+
session: SessionConfigSchema.default({}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Inferred types
|
|
53
|
+
export type SandboxedConfig = z.infer<typeof SandboxedConfigSchema>;
|
|
54
|
+
export type AuditedConfig = z.infer<typeof AuditedConfigSchema>;
|
|
55
|
+
export type SupervisedConfig = z.infer<typeof SupervisedConfigSchema>;
|
|
56
|
+
export type CollaborativeConfig = z.infer<typeof CollaborativeConfigSchema>;
|
|
57
|
+
export type HooksConfig = z.infer<typeof HooksConfigSchema>;
|
|
58
|
+
export type SessionConfig = z.infer<typeof SessionConfigSchema>;
|
|
59
|
+
export type GenieConfig = z.infer<typeof GenieConfigSchema>;
|
|
60
|
+
|
|
61
|
+
// Preset names type
|
|
62
|
+
export type PresetName = 'collaborative' | 'supervised' | 'sandboxed' | 'audited';
|
|
63
|
+
|
|
64
|
+
// Preset description for UI
|
|
65
|
+
export interface PresetDescription {
|
|
66
|
+
name: PresetName;
|
|
67
|
+
title: string;
|
|
68
|
+
what: string;
|
|
69
|
+
why: string;
|
|
70
|
+
how: string;
|
|
71
|
+
recommended?: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export const PRESET_DESCRIPTIONS: PresetDescription[] = [
|
|
75
|
+
{
|
|
76
|
+
name: 'collaborative',
|
|
77
|
+
title: 'Collaborative',
|
|
78
|
+
what: 'All terminal commands run through tmux',
|
|
79
|
+
why: 'You can watch AI work in real-time',
|
|
80
|
+
how: 'Bash commands → term exec genie:shell',
|
|
81
|
+
recommended: true,
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'supervised',
|
|
85
|
+
title: 'Supervised',
|
|
86
|
+
what: 'File changes require your approval',
|
|
87
|
+
why: 'Prevents accidental overwrites',
|
|
88
|
+
how: 'Write/Edit tools always ask permission',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'sandboxed',
|
|
92
|
+
title: 'Sandboxed',
|
|
93
|
+
what: 'Restrict file access to specific directories',
|
|
94
|
+
why: 'Protects sensitive areas of your system',
|
|
95
|
+
how: 'Operations outside sandbox are blocked',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'audited',
|
|
99
|
+
title: 'Audited',
|
|
100
|
+
what: 'Log all AI tool usage to a file',
|
|
101
|
+
why: 'Review what the AI did after a session',
|
|
102
|
+
how: 'Every tool call → ~/.genie/audit.log',
|
|
103
|
+
},
|
|
104
|
+
];
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"lib": ["ES2022"],
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"declaration": true,
|
|
8
|
+
"outDir": "dist",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"types": ["bun"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src/**/*"],
|
|
16
|
+
"exclude": ["node_modules", "dist"]
|
|
17
|
+
}
|