myshell-tools 1.0.0

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.
Files changed (45) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/LICENSE +21 -0
  3. package/README.md +318 -0
  4. package/data/orchestrator.json +113 -0
  5. package/package.json +49 -0
  6. package/src/auth/recovery.mjs +328 -0
  7. package/src/auth/refresh.mjs +373 -0
  8. package/src/chef.mjs +348 -0
  9. package/src/cli/doctor.mjs +568 -0
  10. package/src/cli/reset.mjs +447 -0
  11. package/src/cli/status.mjs +379 -0
  12. package/src/cli.mjs +429 -0
  13. package/src/commands/doctor.mjs +375 -0
  14. package/src/commands/help.mjs +324 -0
  15. package/src/commands/status.mjs +331 -0
  16. package/src/monitor/health.mjs +486 -0
  17. package/src/monitor/performance.mjs +442 -0
  18. package/src/monitor/report.mjs +535 -0
  19. package/src/orchestrator/classify.mjs +391 -0
  20. package/src/orchestrator/confidence.mjs +151 -0
  21. package/src/orchestrator/handoffs.mjs +231 -0
  22. package/src/orchestrator/review.mjs +222 -0
  23. package/src/providers/balance.mjs +201 -0
  24. package/src/providers/claude.mjs +236 -0
  25. package/src/providers/codex.mjs +255 -0
  26. package/src/providers/detect.mjs +185 -0
  27. package/src/providers/errors.mjs +373 -0
  28. package/src/providers/select.mjs +162 -0
  29. package/src/repl-enhanced.mjs +417 -0
  30. package/src/repl.mjs +321 -0
  31. package/src/state/archive.mjs +366 -0
  32. package/src/state/atomic.mjs +116 -0
  33. package/src/state/cleanup.mjs +440 -0
  34. package/src/state/recovery.mjs +461 -0
  35. package/src/state/session.mjs +147 -0
  36. package/src/ui/errors.mjs +456 -0
  37. package/src/ui/formatter.mjs +327 -0
  38. package/src/ui/icons.mjs +318 -0
  39. package/src/ui/progress.mjs +468 -0
  40. package/templates/prompts/confidence-format.txt +14 -0
  41. package/templates/prompts/ic-with-feedback.txt +41 -0
  42. package/templates/prompts/ic.txt +13 -0
  43. package/templates/prompts/manager-review.txt +40 -0
  44. package/templates/prompts/manager.txt +14 -0
  45. package/templates/prompts/worker.txt +12 -0
@@ -0,0 +1,236 @@
1
+ /**
2
+ * claude.mjs — Claude CLI subprocess wrapper with robust error handling
3
+ */
4
+
5
+ import { spawnSync } from 'child_process';
6
+ import {
7
+ executeWithRecovery,
8
+ parseCliOutput,
9
+ createFriendlyErrorMessage,
10
+ CliError,
11
+ defaultRetryCallback
12
+ } from './errors.mjs';
13
+
14
+ /**
15
+ * Execute a Claude command with the specified model and prompt
16
+ */
17
+ export async function executeClaude(claudeBin, model, prompt, options = {}) {
18
+ const startTime = Date.now();
19
+
20
+ const args = [
21
+ '-p',
22
+ '--model', model,
23
+ '--output-format', 'stream-json',
24
+ '--verbose'
25
+ ];
26
+
27
+ // Add the prompt as the final argument
28
+ args.push(prompt);
29
+
30
+ let proc;
31
+ try {
32
+ proc = await executeWithRecovery(claudeBin, args, {
33
+ provider: 'claude',
34
+ timeoutMs: options.timeoutMs || 120000,
35
+ cwd: options.cwd || process.cwd(),
36
+ onRetry: options.onRetry || defaultRetryCallback
37
+ });
38
+ } catch (error) {
39
+ // Return error result in expected format
40
+ return {
41
+ success: false,
42
+ output: '',
43
+ confidence: null,
44
+ escalate: false,
45
+ reasoning: 'CLI execution failed',
46
+ durationMs: Date.now() - startTime,
47
+ model,
48
+ provider: 'claude',
49
+ exitCode: error.details?.exitCode || -1,
50
+ stderr: error.details?.stderr || error.message,
51
+ error: createFriendlyErrorMessage(error, 'claude')
52
+ };
53
+ }
54
+
55
+ const durationMs = Date.now() - startTime;
56
+
57
+ // Parse streaming JSON output
58
+ let output = '';
59
+ let confidence = null;
60
+ let escalate = false;
61
+ let reasoning = '';
62
+
63
+ try {
64
+ if (proc.stdout) {
65
+ // Claude streams JSON objects, one per line
66
+ const lines = proc.stdout.trim().split('\n').filter(l => l.trim());
67
+
68
+ for (const line of lines) {
69
+ try {
70
+ const data = JSON.parse(line);
71
+
72
+ // Look for assistant messages with content
73
+ if (data.type === 'assistant' && data.message && data.message.content) {
74
+ for (const content of data.message.content) {
75
+ if (content.type === 'text' && content.text) {
76
+ output += content.text;
77
+ }
78
+ }
79
+ }
80
+ } catch {
81
+ // If not JSON, treat as plain text (fallback)
82
+ output += line + '\n';
83
+ }
84
+ }
85
+ }
86
+
87
+ // Try to extract confidence and escalation info from output
88
+ const confidenceMatch = output.match(/"confidence":\s*([0-9.]+)/);
89
+ const escalateMatch = output.match(/"escalate":\s*(true|false)/);
90
+ const reasonMatch = output.match(/"reason":\s*"([^"]+)"/);
91
+
92
+ if (confidenceMatch) confidence = parseFloat(confidenceMatch[1]);
93
+ if (escalateMatch) escalate = escalateMatch[1] === 'true';
94
+ if (reasonMatch) reasoning = reasonMatch[1];
95
+
96
+ } catch (err) {
97
+ // Fallback to plain text if JSON parsing fails
98
+ output = proc.stdout || '';
99
+ }
100
+
101
+ // Parse CLI output for better error handling
102
+ const parsedOutput = parseCliOutput(proc.stdout, proc.stderr, proc.status);
103
+
104
+ return {
105
+ success: parsedOutput.success,
106
+ output: output.trim(),
107
+ confidence,
108
+ escalate,
109
+ reasoning,
110
+ durationMs,
111
+ model,
112
+ provider: 'claude',
113
+ exitCode: proc.status,
114
+ stderr: proc.stderr || '',
115
+ error: parsedOutput.success ? null : createFriendlyErrorMessage({
116
+ message: parsedOutput.error,
117
+ details: {
118
+ errorType: parsedOutput.errorType,
119
+ suggestions: parsedOutput.suggestions,
120
+ isRecoverable: parsedOutput.isRecoverable
121
+ }
122
+ }, 'claude')
123
+ };
124
+ }
125
+
126
+ /**
127
+ * Build a hierarchical prompt for Claude based on tier and context
128
+ */
129
+ export function buildClaudePrompt(tier, task, context = {}) {
130
+ let prompt = '';
131
+
132
+ // Enhanced prompts with manager feedback support
133
+ switch (tier) {
134
+ case 'worker':
135
+ prompt = `You are a WORKER in an AI organization hierarchy. Handle this specific, simple task efficiently:
136
+
137
+ TASK: ${task}
138
+
139
+ You are the cheapest, fastest model in the org chart. Focus on:
140
+ - File lookups, grep operations, simple reads
141
+ - Quick information gathering
142
+ - Basic operations that don't require complex reasoning
143
+
144
+ Work efficiently and honestly. If the task is more complex than you can handle confidently, be honest about it.
145
+
146
+ End your response with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
147
+ break;
148
+
149
+ case 'ic':
150
+ // Check if this is a retry with manager feedback
151
+ if (context.managerNotes) {
152
+ prompt = `You are an IC (Individual Contributor) in an AI organization. You handle most of the implementation work:
153
+
154
+ TASK: ${task}
155
+
156
+ MANAGER FEEDBACK (from previous attempt):
157
+ ═══════════════════════════════════════
158
+ ${context.managerNotes}
159
+ ═══════════════════════════════════════
160
+
161
+ Your manager has reviewed your previous work and wants you to address the above issues.
162
+ Focus on fixing exactly what they pointed out.
163
+
164
+ ${context.attempt ? `This is attempt #${context.attempt}. Apply the manager's feedback carefully.` : ''}
165
+
166
+ You are the primary workhorse - most tasks should be completed at your level. Focus on:
167
+ - Code implementation and editing
168
+ - Refactoring and improvements
169
+ - Running tests and debugging
170
+ - Git operations and file management
171
+
172
+ If you encounter something that needs architectural decisions, security review, or complex debugging, escalate to your manager.
173
+
174
+ End your response with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
175
+ } else {
176
+ prompt = `You are an IC (Individual Contributor) in an AI organization. You handle most of the implementation work:
177
+
178
+ TASK: ${task}
179
+
180
+ You are the primary workhorse - most tasks should be completed at your level. Focus on:
181
+ - Code implementation and editing
182
+ - Refactoring and improvements
183
+ - Running tests and debugging
184
+ - Git operations and file management
185
+
186
+ If you encounter something that needs architectural decisions, security review, or complex debugging, escalate to your manager.
187
+
188
+ End your response with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
189
+ }
190
+ break;
191
+
192
+ case 'manager':
193
+ // Check if this is a manager review operation
194
+ if (context.operation === 'review') {
195
+ prompt = task; // task already contains the full review prompt
196
+ } else {
197
+ prompt = `You are a MANAGER in an AI organization. Handle high-level decisions and review complex problems:
198
+
199
+ TASK: ${task}
200
+
201
+ You handle:
202
+ - Architecture decisions
203
+ - Security reviews
204
+ - Complex debugging that requires deep reasoning
205
+ - Code reviews and quality decisions
206
+ - Escalated issues from ICs
207
+
208
+ Either solve the problem completely or provide specific guidance for your team to implement.
209
+
210
+ End your response with: {"confidence": 0.0-1.0, "escalate": false, "reason": "solution approach", "needs_review": false}`;
211
+ }
212
+ break;
213
+
214
+ default:
215
+ prompt = `Handle this task: ${task}
216
+
217
+ End your response with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
218
+ }
219
+
220
+ // Add context if available (skip for review operations)
221
+ if (context.operation !== 'review') {
222
+ if (context.files && context.files.length > 0) {
223
+ prompt += `\n\nRelevant files:\n${context.files.map(f => `- ${f}`).join('\n')}`;
224
+ }
225
+
226
+ if (context.constraints && context.constraints.length > 0) {
227
+ prompt += `\n\nConstraints:\n${context.constraints.map(c => `- ${c}`).join('\n')}`;
228
+ }
229
+
230
+ if (context.previous && tier === 'manager' && !context.managerNotes) {
231
+ prompt += `\n\nPREVIOUS ATTEMPT (escalated to you):\n${context.previous.output}\n\nConfidence was: ${context.previous.confidence}\nReason for escalation: ${context.previous.reasoning}`;
232
+ }
233
+ }
234
+
235
+ return prompt;
236
+ }
@@ -0,0 +1,255 @@
1
+ /**
2
+ * codex.mjs — Codex CLI subprocess wrapper with robust error handling
3
+ * Adapted from archive/dual-brain/hooks/gpt-work-dispatcher.mjs
4
+ */
5
+
6
+ import { spawnSync } from 'child_process';
7
+ import {
8
+ executeWithRecovery,
9
+ parseCliOutput,
10
+ createFriendlyErrorMessage,
11
+ CliError,
12
+ defaultRetryCallback
13
+ } from './errors.mjs';
14
+
15
+ /**
16
+ * Execute a Codex command with the specified model and prompt
17
+ */
18
+ export async function executeCodex(codexBin, model, prompt, options = {}) {
19
+ const startTime = Date.now();
20
+
21
+ const args = [
22
+ 'exec', '--json', '--ephemeral',
23
+ '-m', model,
24
+ '-s', 'danger-full-access',
25
+ prompt
26
+ ];
27
+
28
+ let proc;
29
+ try {
30
+ proc = await executeWithRecovery(codexBin, args, {
31
+ provider: 'codex',
32
+ timeoutMs: options.timeoutMs || 120000,
33
+ cwd: options.cwd || process.cwd(),
34
+ onRetry: options.onRetry || defaultRetryCallback
35
+ });
36
+ } catch (error) {
37
+ // Return error result in expected format
38
+ return {
39
+ success: false,
40
+ output: '',
41
+ confidence: null,
42
+ escalate: false,
43
+ reasoning: 'CLI execution failed',
44
+ durationMs: Date.now() - startTime,
45
+ model,
46
+ provider: 'codex',
47
+ usage: null,
48
+ errors: [error.message],
49
+ exitCode: error.details?.exitCode || -1,
50
+ stderr: error.details?.stderr || error.message,
51
+ error: createFriendlyErrorMessage(error, 'codex')
52
+ };
53
+ }
54
+
55
+ const durationMs = Date.now() - startTime;
56
+
57
+ // Parse JSONL output (each line is a JSON object)
58
+ let output = '';
59
+ let confidence = null;
60
+ let escalate = false;
61
+ let reasoning = '';
62
+ let usage = null;
63
+ const errors = [];
64
+
65
+ try {
66
+ const messages = (proc.stdout || '')
67
+ .split('\n')
68
+ .filter(l => l.trim())
69
+ .map(l => {
70
+ try {
71
+ return JSON.parse(l);
72
+ } catch {
73
+ return null;
74
+ }
75
+ })
76
+ .filter(Boolean);
77
+
78
+ // Extract agent messages (the actual AI response)
79
+ const agentMessages = messages
80
+ .filter(m => m.type === 'item.completed' && m.item?.type === 'agent_message')
81
+ .map(m => m.item.text);
82
+
83
+ output = agentMessages.join('\n\n');
84
+
85
+ // Get usage statistics
86
+ const turnCompleted = messages.find(m => m.type === 'turn.completed');
87
+ if (turnCompleted) {
88
+ usage = turnCompleted.usage;
89
+ }
90
+
91
+ // Collect errors
92
+ messages
93
+ .filter(m => m.type === 'error' || m.type === 'turn.failed')
94
+ .forEach(m => errors.push(m.message || m.error?.message || 'unknown error'));
95
+
96
+ // Try to extract confidence and escalation info from the last agent message
97
+ if (output) {
98
+ const confidenceMatch = output.match(/"confidence":\s*([0-9.]+)/);
99
+ const escalateMatch = output.match(/"escalate":\s*(true|false)/);
100
+ const reasonMatch = output.match(/"reason":\s*"([^"]+)"/);
101
+
102
+ if (confidenceMatch) confidence = parseFloat(confidenceMatch[1]);
103
+ if (escalateMatch) escalate = escalateMatch[1] === 'true';
104
+ if (reasonMatch) reasoning = reasonMatch[1];
105
+ }
106
+
107
+ } catch (err) {
108
+ errors.push(`Parse error: ${err.message}`);
109
+ output = proc.stdout || '';
110
+ }
111
+
112
+ // Parse CLI output for better error handling
113
+ const parsedOutput = parseCliOutput(proc.stdout, proc.stderr, proc.status);
114
+
115
+ return {
116
+ success: parsedOutput.success && errors.length === 0,
117
+ output: output.trim(),
118
+ confidence,
119
+ escalate,
120
+ reasoning,
121
+ durationMs,
122
+ model,
123
+ provider: 'codex',
124
+ usage,
125
+ errors,
126
+ exitCode: proc.status,
127
+ stderr: proc.stderr || '',
128
+ error: (parsedOutput.success && errors.length === 0) ? null :
129
+ createFriendlyErrorMessage({
130
+ message: errors.length > 0 ? errors.join('; ') : parsedOutput.error,
131
+ details: {
132
+ errorType: parsedOutput.errorType,
133
+ suggestions: parsedOutput.suggestions,
134
+ isRecoverable: parsedOutput.isRecoverable
135
+ }
136
+ }, 'codex')
137
+ };
138
+ }
139
+
140
+ /**
141
+ * Build a hierarchical prompt for Codex based on tier and context
142
+ */
143
+ export function buildCodexPrompt(tier, task, context = {}) {
144
+ let prompt = '';
145
+
146
+ // Enhanced prompts with manager feedback support
147
+ switch (tier) {
148
+ case 'worker':
149
+ prompt = `You are a WORKER in an AI organization hierarchy. Handle this specific, simple task efficiently:
150
+
151
+ TASK: ${task}
152
+
153
+ You are the cheapest, fastest model in the org chart. Focus on:
154
+ - File lookups, grep operations, simple reads
155
+ - Quick information gathering
156
+ - Basic operations that don't require complex reasoning
157
+
158
+ Work efficiently and honestly. If the task is more complex than you can handle confidently, be honest about it.
159
+
160
+ When complete, output a summary and end with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
161
+ break;
162
+
163
+ case 'ic':
164
+ // Check if this is a retry with manager feedback
165
+ if (context.managerNotes) {
166
+ prompt = `You are an IC (Individual Contributor) in an AI organization. You handle most implementation work:
167
+
168
+ TASK: ${task}
169
+
170
+ MANAGER FEEDBACK (from previous attempt):
171
+ ═══════════════════════════════════════
172
+ ${context.managerNotes}
173
+ ═══════════════════════════════════════
174
+
175
+ Your manager has reviewed your previous work and wants you to address the above issues.
176
+ Focus on fixing exactly what they pointed out.
177
+
178
+ ${context.attempt ? `This is attempt #${context.attempt}. Apply the manager's feedback carefully.` : ''}
179
+
180
+ Own this task completely. Edit files directly. Run tests to verify your changes.
181
+
182
+ When complete, output:
183
+ 1. What you changed (files and behavior)
184
+ 2. How you addressed the manager's feedback
185
+ 3. Tests run and results (if applicable)
186
+ 4. Remaining risks or edge cases
187
+ 5. End with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
188
+ } else {
189
+ prompt = `You are an IC (Individual Contributor) in an AI organization. You handle most implementation work:
190
+
191
+ TASK: ${task}
192
+
193
+ You are the primary workhorse - most tasks should be completed at your level. Focus on:
194
+ - Code implementation and editing
195
+ - Refactoring and improvements
196
+ - Running tests and debugging
197
+ - Git operations and file management
198
+
199
+ Own this task completely. Edit files directly. Run tests to verify your changes.
200
+
201
+ If you encounter something requiring architectural decisions, security review, or complex debugging, escalate to your manager.
202
+
203
+ When complete, output:
204
+ 1. What you changed (files and behavior)
205
+ 2. Tests run and results (if applicable)
206
+ 3. Remaining risks or edge cases
207
+ 4. End with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
208
+ }
209
+ break;
210
+
211
+ case 'manager':
212
+ // Check if this is a manager review operation
213
+ if (context.operation === 'review') {
214
+ prompt = task; // task already contains the full review prompt
215
+ } else {
216
+ prompt = `You are a MANAGER in an AI organization. Handle high-level decisions and review complex problems:
217
+
218
+ TASK: ${task}
219
+
220
+ You handle:
221
+ - Architecture decisions
222
+ - Security reviews
223
+ - Complex debugging that requires deep reasoning
224
+ - Code reviews and quality decisions
225
+ - Escalated issues from ICs
226
+
227
+ Either solve the problem completely or provide specific guidance for your team to implement.
228
+
229
+ When complete, output your decision/solution and end with: {"confidence": 0.0-1.0, "escalate": false, "reason": "solution approach", "needs_review": false}`;
230
+ }
231
+ break;
232
+
233
+ default:
234
+ prompt = `Handle this task: ${task}
235
+
236
+ End with: {"confidence": 0.0-1.0, "escalate": true|false, "reason": "brief explanation", "needs_review": true|false}`;
237
+ }
238
+
239
+ // Add context if available (skip for review operations)
240
+ if (context.operation !== 'review') {
241
+ if (context.files && context.files.length > 0) {
242
+ prompt += `\n\nRelevant files:\n${context.files.map(f => `- ${f}`).join('\n')}`;
243
+ }
244
+
245
+ if (context.constraints && context.constraints.length > 0) {
246
+ prompt += `\n\nConstraints:\n${context.constraints.map(c => `- ${c}`).join('\n')}`;
247
+ }
248
+
249
+ if (context.previous && tier === 'manager' && !context.managerNotes) {
250
+ prompt += `\n\nPREVIOUS ATTEMPT (escalated to you):\nOutput: ${context.previous.output}\nConfidence: ${context.previous.confidence}\nEscalation reason: ${context.previous.reasoning}`;
251
+ }
252
+ }
253
+
254
+ return prompt;
255
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * detect.mjs — CLI detection and auth status checking for Claude and Codex
3
+ * Adapted from archive/dual-brain/install.mjs
4
+ */
5
+
6
+ import { spawnSync } from 'child_process';
7
+ import { existsSync, readFileSync } from 'fs';
8
+ import { join, resolve } from 'path';
9
+
10
+ /**
11
+ * Run a command and capture output safely
12
+ */
13
+ function run(cmd, args = [], options = {}) {
14
+ try {
15
+ return spawnSync(cmd, args, {
16
+ encoding: 'utf8',
17
+ stdio: ['pipe', 'pipe', 'pipe'],
18
+ timeout: 10000,
19
+ ...options
20
+ });
21
+ } catch (err) {
22
+ return { status: -1, stdout: '', stderr: err.message };
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Detect Claude CLI installation and authentication status
28
+ */
29
+ export function detectClaude() {
30
+ const result = {
31
+ installed: false,
32
+ version: null,
33
+ authed: false,
34
+ models: [],
35
+ bin: 'claude'
36
+ };
37
+
38
+ // Try to get version
39
+ const ver = run('claude', ['--version']);
40
+ if (ver.status === 0 && ver.stdout.trim()) {
41
+ result.installed = true;
42
+ result.version = ver.stdout.trim().split('\n')[0];
43
+ }
44
+
45
+ // Fallback: check if claude exists in PATH
46
+ if (!result.installed) {
47
+ const which = run('which', ['claude']);
48
+ if (which.status === 0 && which.stdout.trim()) {
49
+ result.installed = true;
50
+ result.bin = which.stdout.trim();
51
+ }
52
+ }
53
+
54
+ // Check authentication via credential files
55
+ const credPaths = [
56
+ join(process.env.HOME || '', '.claude', '.credentials.json'),
57
+ join(process.env.HOME || '', '.claude', 'credentials.json'),
58
+ resolve(process.cwd(), '.replit-tools', '.claude-persistent', '.credentials.json'),
59
+ ];
60
+
61
+ for (const p of credPaths) {
62
+ try {
63
+ const cred = JSON.parse(readFileSync(p, 'utf8'));
64
+ if (cred.claudeAiOauth || cred.apiKey || cred.oauth_token) {
65
+ result.authed = true;
66
+ break;
67
+ }
68
+ } catch {}
69
+ }
70
+
71
+ // Fallback: check auth status command
72
+ if (!result.authed && result.installed) {
73
+ const auth = run('claude', ['auth', 'status']);
74
+ const out = ((auth.stdout || '') + (auth.stderr || '')).toLowerCase();
75
+ if (out.includes('logged in') || out.includes('authenticated') || out.includes('valid')) {
76
+ result.authed = true;
77
+ }
78
+ }
79
+
80
+ // If installed and authed, assume standard models are available
81
+ if (result.installed && result.authed) {
82
+ result.models = ['opus', 'sonnet', 'haiku'];
83
+ }
84
+
85
+ return result;
86
+ }
87
+
88
+ /**
89
+ * Detect Codex CLI installation and authentication status
90
+ */
91
+ export function detectCodex() {
92
+ const result = {
93
+ installed: false,
94
+ version: null,
95
+ authed: false,
96
+ path: null,
97
+ models: []
98
+ };
99
+
100
+ // Try which first
101
+ const which = run('which', ['codex']);
102
+ if (which.status === 0 && which.stdout.trim()) {
103
+ result.path = which.stdout.trim();
104
+ result.installed = true;
105
+ }
106
+
107
+ // Try common fallback locations
108
+ if (!result.installed) {
109
+ const home = process.env.HOME || '';
110
+ const fallbacks = [
111
+ join(home, '.local', 'bin', 'codex'),
112
+ join(home, 'bin', 'codex'),
113
+ '/usr/local/bin/codex',
114
+ ];
115
+ for (const p of fallbacks) {
116
+ if (existsSync(p)) {
117
+ result.path = p;
118
+ result.installed = true;
119
+ break;
120
+ }
121
+ }
122
+ }
123
+
124
+ if (result.installed && result.path) {
125
+ // Get version
126
+ const ver = run(result.path, ['--version']);
127
+ if (ver.status === 0) {
128
+ result.version = ver.stdout.trim().split('\n')[0];
129
+ }
130
+
131
+ // Check login status
132
+ const login = run(result.path, ['login', 'status']);
133
+ const out = ((login.stdout || '') + (login.stderr || '')).toLowerCase();
134
+ if (login.status === 0 || out.includes('logged in') || out.includes('authenticated')) {
135
+ result.authed = true;
136
+ }
137
+
138
+ // If installed and authed, assume standard models
139
+ if (result.authed) {
140
+ result.models = ['gpt-5.5', 'gpt-5.4', 'gpt-4.1-mini'];
141
+ }
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Detect all available providers and their capabilities
149
+ */
150
+ export function detectEnvironment() {
151
+ const claude = detectClaude();
152
+ const codex = detectCodex();
153
+
154
+ return {
155
+ claude,
156
+ codex,
157
+ hasProviders: (claude.installed && claude.authed) || (codex.installed && codex.authed),
158
+ workspace: resolve(process.cwd())
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Get available models organized by tier
164
+ */
165
+ export function getAvailableModels(env) {
166
+ const models = {
167
+ worker: [],
168
+ ic: [],
169
+ manager: []
170
+ };
171
+
172
+ if (env.claude.installed && env.claude.authed) {
173
+ models.worker.push({ provider: 'claude', model: 'haiku', bin: env.claude.bin });
174
+ models.ic.push({ provider: 'claude', model: 'sonnet', bin: env.claude.bin });
175
+ models.manager.push({ provider: 'claude', model: 'opus', bin: env.claude.bin });
176
+ }
177
+
178
+ if (env.codex.installed && env.codex.authed) {
179
+ models.worker.push({ provider: 'codex', model: 'gpt-4.1-mini', bin: env.codex.path });
180
+ models.ic.push({ provider: 'codex', model: 'gpt-5.4', bin: env.codex.path });
181
+ models.manager.push({ provider: 'codex', model: 'gpt-5.5', bin: env.codex.path });
182
+ }
183
+
184
+ return models;
185
+ }