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