myshell-tools 1.0.0 → 2.0.0-alpha.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.
- package/CHANGELOG.md +44 -69
- package/LICENSE +21 -21
- package/README.md +178 -318
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +106 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/cost.d.ts +36 -0
- package/dist/commands/cost.js +103 -0
- package/dist/commands/cost.js.map +1 -0
- package/dist/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.js +115 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/core/assess.d.ts +25 -0
- package/dist/core/assess.js +142 -0
- package/dist/core/assess.js.map +1 -0
- package/dist/core/classify.d.ts +19 -0
- package/dist/core/classify.js +80 -0
- package/dist/core/classify.js.map +1 -0
- package/dist/core/escalate.d.ts +32 -0
- package/dist/core/escalate.js +57 -0
- package/dist/core/escalate.js.map +1 -0
- package/dist/core/index.d.ts +13 -0
- package/dist/core/index.js +12 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/orchestrate.d.ts +42 -0
- package/dist/core/orchestrate.js +439 -0
- package/dist/core/orchestrate.js.map +1 -0
- package/dist/core/policy.d.ts +9 -0
- package/dist/core/policy.js +27 -0
- package/dist/core/policy.js.map +1 -0
- package/dist/core/prompt.d.ts +26 -0
- package/dist/core/prompt.js +125 -0
- package/dist/core/prompt.js.map +1 -0
- package/dist/core/review.d.ts +46 -0
- package/dist/core/review.js +148 -0
- package/dist/core/review.js.map +1 -0
- package/dist/core/route.d.ts +28 -0
- package/dist/core/route.js +52 -0
- package/dist/core/route.js.map +1 -0
- package/dist/core/types.d.ts +141 -0
- package/dist/core/types.js +14 -0
- package/dist/core/types.js.map +1 -0
- package/dist/infra/atomic.d.ts +53 -0
- package/dist/infra/atomic.js +171 -0
- package/dist/infra/atomic.js.map +1 -0
- package/dist/infra/clock.d.ts +9 -0
- package/dist/infra/clock.js +15 -0
- package/dist/infra/clock.js.map +1 -0
- package/dist/infra/index.d.ts +9 -0
- package/dist/infra/index.js +7 -0
- package/dist/infra/index.js.map +1 -0
- package/dist/infra/ledger.d.ts +49 -0
- package/dist/infra/ledger.js +90 -0
- package/dist/infra/ledger.js.map +1 -0
- package/dist/infra/paths.d.ts +28 -0
- package/dist/infra/paths.js +38 -0
- package/dist/infra/paths.js.map +1 -0
- package/dist/infra/pricing.d.ts +47 -0
- package/dist/infra/pricing.js +151 -0
- package/dist/infra/pricing.js.map +1 -0
- package/dist/infra/session.d.ts +28 -0
- package/dist/infra/session.js +61 -0
- package/dist/infra/session.js.map +1 -0
- package/dist/interface/render.d.ts +27 -0
- package/dist/interface/render.js +134 -0
- package/dist/interface/render.js.map +1 -0
- package/dist/interface/repl.d.ts +23 -0
- package/dist/interface/repl.js +90 -0
- package/dist/interface/repl.js.map +1 -0
- package/dist/interface/run.d.ts +20 -0
- package/dist/interface/run.js +31 -0
- package/dist/interface/run.js.map +1 -0
- package/dist/providers/claude-parse.d.ts +24 -0
- package/dist/providers/claude-parse.js +113 -0
- package/dist/providers/claude-parse.js.map +1 -0
- package/dist/providers/claude.d.ts +45 -0
- package/dist/providers/claude.js +122 -0
- package/dist/providers/claude.js.map +1 -0
- package/dist/providers/codex-parse.d.ts +32 -0
- package/dist/providers/codex-parse.js +145 -0
- package/dist/providers/codex-parse.js.map +1 -0
- package/dist/providers/codex.d.ts +44 -0
- package/dist/providers/codex.js +124 -0
- package/dist/providers/codex.js.map +1 -0
- package/dist/providers/detect.d.ts +49 -0
- package/dist/providers/detect.js +125 -0
- package/dist/providers/detect.js.map +1 -0
- package/dist/providers/errors.d.ts +49 -0
- package/dist/providers/errors.js +189 -0
- package/dist/providers/errors.js.map +1 -0
- package/dist/providers/index.d.ts +9 -0
- package/dist/providers/index.js +7 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/port.d.ts +74 -0
- package/dist/providers/port.js +16 -0
- package/dist/providers/port.js.map +1 -0
- package/dist/providers/registry.d.ts +21 -0
- package/dist/providers/registry.js +34 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/ui/banner.d.ts +19 -0
- package/dist/ui/banner.js +32 -0
- package/dist/ui/banner.js.map +1 -0
- package/dist/ui/spinner.d.ts +27 -0
- package/dist/ui/spinner.js +67 -0
- package/dist/ui/spinner.js.map +1 -0
- package/dist/ui/theme.d.ts +32 -0
- package/dist/ui/theme.js +56 -0
- package/dist/ui/theme.js.map +1 -0
- package/package.json +55 -49
- package/data/orchestrator.json +0 -113
- package/src/auth/recovery.mjs +0 -328
- package/src/auth/refresh.mjs +0 -373
- package/src/chef.mjs +0 -348
- package/src/cli/doctor.mjs +0 -568
- package/src/cli/reset.mjs +0 -447
- package/src/cli/status.mjs +0 -379
- package/src/cli.mjs +0 -429
- package/src/commands/doctor.mjs +0 -375
- package/src/commands/help.mjs +0 -324
- package/src/commands/status.mjs +0 -331
- package/src/monitor/health.mjs +0 -486
- package/src/monitor/performance.mjs +0 -442
- package/src/monitor/report.mjs +0 -535
- package/src/orchestrator/classify.mjs +0 -391
- package/src/orchestrator/confidence.mjs +0 -151
- package/src/orchestrator/handoffs.mjs +0 -231
- package/src/orchestrator/review.mjs +0 -222
- package/src/providers/balance.mjs +0 -201
- package/src/providers/claude.mjs +0 -236
- package/src/providers/codex.mjs +0 -255
- package/src/providers/detect.mjs +0 -185
- package/src/providers/errors.mjs +0 -373
- package/src/providers/select.mjs +0 -162
- package/src/repl-enhanced.mjs +0 -417
- package/src/repl.mjs +0 -321
- package/src/state/archive.mjs +0 -366
- package/src/state/atomic.mjs +0 -116
- package/src/state/cleanup.mjs +0 -440
- package/src/state/recovery.mjs +0 -461
- package/src/state/session.mjs +0 -147
- package/src/ui/errors.mjs +0 -456
- package/src/ui/formatter.mjs +0 -327
- package/src/ui/icons.mjs +0 -318
- package/src/ui/progress.mjs +0 -468
- package/templates/prompts/confidence-format.txt +0 -14
- package/templates/prompts/ic-with-feedback.txt +0 -41
- package/templates/prompts/ic.txt +0 -13
- package/templates/prompts/manager-review.txt +0 -40
- package/templates/prompts/manager.txt +0 -14
- package/templates/prompts/worker.txt +0 -12
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/claude.ts — Claude CLI adapter implementing the Provider port.
|
|
3
|
+
*
|
|
4
|
+
* Spawns `claude -p --output-format stream-json --verbose` via execa v9,
|
|
5
|
+
* delivers the prompt via STDIN (never as an argv argument), and streams
|
|
6
|
+
* parsed ProviderEvents to the caller as they arrive.
|
|
7
|
+
*
|
|
8
|
+
* Sandbox enforcement note (Phase-4 item):
|
|
9
|
+
* The `req.sandbox` level is accepted but NOT yet translated into Claude CLI
|
|
10
|
+
* flags. Default headless `claude -p` is inherently safe — tool calls that
|
|
11
|
+
* would require elevated permissions are auto-denied by the Claude CLI. Full
|
|
12
|
+
* sandbox→flag mapping (e.g. `--allowedTools` restrictions) is deferred to
|
|
13
|
+
* Phase 4. Do NOT pass `--dangerously-skip-permissions` or any
|
|
14
|
+
* permission-bypass flag here.
|
|
15
|
+
*
|
|
16
|
+
* Authentication note:
|
|
17
|
+
* Auth state is OPTIMISTIC at detect() time — we cannot cheaply probe it.
|
|
18
|
+
* The real auth state surfaces at run time: an unauthenticated run produces
|
|
19
|
+
* a stderr that classifyError() maps to category 'auth'.
|
|
20
|
+
*
|
|
21
|
+
* Execa v9 streaming:
|
|
22
|
+
* We use `subprocess[Symbol.asyncIterator]()` (i.e. `for await … of subprocess`)
|
|
23
|
+
* which by default iterates over stdout lines as strings. Confirmed from
|
|
24
|
+
* types/subprocess/subprocess.d.ts: the subprocess IS an AsyncIterable that
|
|
25
|
+
* yields one string per stdout line.
|
|
26
|
+
*/
|
|
27
|
+
import { execa } from 'execa';
|
|
28
|
+
import { detectProvider } from './detect.js';
|
|
29
|
+
import { classifyError } from './errors.js';
|
|
30
|
+
import { parseClaudeLine } from './claude-parse.js';
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Model alias mapping
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
/**
|
|
35
|
+
* Map a concrete model id to a CLI-safe alias so a stale full id never 404s.
|
|
36
|
+
*
|
|
37
|
+
* Patterns:
|
|
38
|
+
* - claude-opus-* → 'opus'
|
|
39
|
+
* - claude-sonnet-* → 'sonnet'
|
|
40
|
+
* - claude-haiku-* → 'haiku'
|
|
41
|
+
* - anything else → returned unchanged
|
|
42
|
+
*/
|
|
43
|
+
export function toClaudeModelArg(model) {
|
|
44
|
+
if (model.startsWith('claude-opus'))
|
|
45
|
+
return 'opus';
|
|
46
|
+
if (model.startsWith('claude-sonnet'))
|
|
47
|
+
return 'sonnet';
|
|
48
|
+
if (model.startsWith('claude-haiku'))
|
|
49
|
+
return 'haiku';
|
|
50
|
+
return model;
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Factory
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Create a Claude provider adapter.
|
|
57
|
+
*
|
|
58
|
+
* @param opts.bin - Override the binary name/path (default: `'claude'`).
|
|
59
|
+
*/
|
|
60
|
+
export function createClaudeProvider(opts) {
|
|
61
|
+
const bin = opts?.bin ?? 'claude';
|
|
62
|
+
return {
|
|
63
|
+
id: 'claude',
|
|
64
|
+
detect() {
|
|
65
|
+
return detectProvider('claude');
|
|
66
|
+
},
|
|
67
|
+
async *run(req, signal) {
|
|
68
|
+
const args = [
|
|
69
|
+
'-p',
|
|
70
|
+
'--output-format',
|
|
71
|
+
'stream-json',
|
|
72
|
+
'--verbose',
|
|
73
|
+
'--model',
|
|
74
|
+
toClaudeModelArg(req.model),
|
|
75
|
+
];
|
|
76
|
+
// Spawn with reject:false so we always get the result object (never throws).
|
|
77
|
+
// cancelSignal wires our AbortSignal directly to execa's termination path.
|
|
78
|
+
const subprocess = execa(bin, args, {
|
|
79
|
+
cwd: req.cwd,
|
|
80
|
+
input: req.prompt, // deliver prompt via STDIN, not argv
|
|
81
|
+
cancelSignal: signal,
|
|
82
|
+
timeout: req.timeoutMs,
|
|
83
|
+
reject: false,
|
|
84
|
+
});
|
|
85
|
+
let emittedTerminal = false;
|
|
86
|
+
// Stream stdout line by line.
|
|
87
|
+
// execa v9: the subprocess itself is an AsyncIterable that yields one
|
|
88
|
+
// string per stdout line (from types/subprocess/subprocess.d.ts).
|
|
89
|
+
try {
|
|
90
|
+
for await (const line of subprocess) {
|
|
91
|
+
const events = parseClaudeLine(line);
|
|
92
|
+
for (const ev of events) {
|
|
93
|
+
yield ev;
|
|
94
|
+
if (ev.type === 'done' || ev.type === 'error') {
|
|
95
|
+
emittedTerminal = true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Iteration errors (e.g. stream abort) are handled below via the result.
|
|
102
|
+
}
|
|
103
|
+
// Wait for the subprocess to fully exit and collect the result.
|
|
104
|
+
const result = await subprocess;
|
|
105
|
+
if (!emittedTerminal) {
|
|
106
|
+
if (result.isCanceled) {
|
|
107
|
+
yield {
|
|
108
|
+
type: 'error',
|
|
109
|
+
error: classifyError('cancelled', 1),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
else if (result.failed || (result.exitCode !== undefined && result.exitCode !== 0)) {
|
|
113
|
+
yield {
|
|
114
|
+
type: 'error',
|
|
115
|
+
error: classifyError(typeof result.stderr === 'string' ? result.stderr : '', result.exitCode ?? 1),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=claude.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude.js","sourceRoot":"","sources":["../../src/providers/claude.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/codex-parse.ts — stateful JSONL parser for `codex exec --json`.
|
|
3
|
+
*
|
|
4
|
+
* STATEFUL MODULE: codex streams assistant text across multiple `agent_message`
|
|
5
|
+
* items and the final `turn.completed` event does NOT repeat the full text, so
|
|
6
|
+
* we accumulate text in a closure and surface the complete value in `done`.
|
|
7
|
+
*
|
|
8
|
+
* Pure in all other respects: no I/O, no execa, no side effects. The factory
|
|
9
|
+
* function `createCodexParser()` returns a closure that is hermetic and
|
|
10
|
+
* fixture-testable.
|
|
11
|
+
*
|
|
12
|
+
* Codex `codex exec --json` event schema (JSONL, one object per line):
|
|
13
|
+
* - thread.started / turn.started → emit nothing
|
|
14
|
+
* - item.completed (agent_message) → text event + accumulate to buffer
|
|
15
|
+
* - item.completed (reasoning) → reasoning event (if text present)
|
|
16
|
+
* - item.completed (command_execution / file_change / mcp_tool_call) → tool event
|
|
17
|
+
* - turn.completed → usage event + done event (no costUsd)
|
|
18
|
+
* - turn.failed / error → error event
|
|
19
|
+
* - unknown / malformed JSON → emit nothing (never throw)
|
|
20
|
+
*/
|
|
21
|
+
import type { ProviderEvent } from './port.js';
|
|
22
|
+
/**
|
|
23
|
+
* Create a stateful parser for `codex exec --json` stdout.
|
|
24
|
+
*
|
|
25
|
+
* Returns a function that accepts one JSONL line at a time and returns 0 or
|
|
26
|
+
* more {@link ProviderEvent}s. The closure accumulates agent_message text so
|
|
27
|
+
* that `done.text` contains the full concatenated assistant reply.
|
|
28
|
+
*
|
|
29
|
+
* The returned parser never throws — malformed JSON or unknown event types
|
|
30
|
+
* silently return [].
|
|
31
|
+
*/
|
|
32
|
+
export declare function createCodexParser(): (line: string) => ProviderEvent[];
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/codex-parse.ts — stateful JSONL parser for `codex exec --json`.
|
|
3
|
+
*
|
|
4
|
+
* STATEFUL MODULE: codex streams assistant text across multiple `agent_message`
|
|
5
|
+
* items and the final `turn.completed` event does NOT repeat the full text, so
|
|
6
|
+
* we accumulate text in a closure and surface the complete value in `done`.
|
|
7
|
+
*
|
|
8
|
+
* Pure in all other respects: no I/O, no execa, no side effects. The factory
|
|
9
|
+
* function `createCodexParser()` returns a closure that is hermetic and
|
|
10
|
+
* fixture-testable.
|
|
11
|
+
*
|
|
12
|
+
* Codex `codex exec --json` event schema (JSONL, one object per line):
|
|
13
|
+
* - thread.started / turn.started → emit nothing
|
|
14
|
+
* - item.completed (agent_message) → text event + accumulate to buffer
|
|
15
|
+
* - item.completed (reasoning) → reasoning event (if text present)
|
|
16
|
+
* - item.completed (command_execution / file_change / mcp_tool_call) → tool event
|
|
17
|
+
* - turn.completed → usage event + done event (no costUsd)
|
|
18
|
+
* - turn.failed / error → error event
|
|
19
|
+
* - unknown / malformed JSON → emit nothing (never throw)
|
|
20
|
+
*/
|
|
21
|
+
import { classifyError } from './errors.js';
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
function mapUsage(u) {
|
|
26
|
+
const base = {
|
|
27
|
+
inputTokens: u.input_tokens ?? 0,
|
|
28
|
+
outputTokens: u.output_tokens ?? 0,
|
|
29
|
+
};
|
|
30
|
+
// exactOptionalPropertyTypes: only include cachedInputTokens when it is a
|
|
31
|
+
// number — omit the key entirely otherwise.
|
|
32
|
+
if (typeof u.cached_input_tokens === 'number') {
|
|
33
|
+
return { ...base, cachedInputTokens: u.cached_input_tokens };
|
|
34
|
+
}
|
|
35
|
+
return base;
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Public API
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Create a stateful parser for `codex exec --json` stdout.
|
|
42
|
+
*
|
|
43
|
+
* Returns a function that accepts one JSONL line at a time and returns 0 or
|
|
44
|
+
* more {@link ProviderEvent}s. The closure accumulates agent_message text so
|
|
45
|
+
* that `done.text` contains the full concatenated assistant reply.
|
|
46
|
+
*
|
|
47
|
+
* The returned parser never throws — malformed JSON or unknown event types
|
|
48
|
+
* silently return [].
|
|
49
|
+
*/
|
|
50
|
+
export function createCodexParser() {
|
|
51
|
+
let accumulatedText = '';
|
|
52
|
+
return function parseCodexLine(line) {
|
|
53
|
+
const trimmed = line.trim();
|
|
54
|
+
if (!trimmed)
|
|
55
|
+
return [];
|
|
56
|
+
let parsed;
|
|
57
|
+
try {
|
|
58
|
+
parsed = JSON.parse(trimmed);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return [];
|
|
62
|
+
}
|
|
63
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
64
|
+
return [];
|
|
65
|
+
const obj = parsed;
|
|
66
|
+
const eventType = obj['type'];
|
|
67
|
+
// -----------------------------------------------------------------------
|
|
68
|
+
// thread.started / turn.started — emit nothing
|
|
69
|
+
// -----------------------------------------------------------------------
|
|
70
|
+
if (eventType === 'thread.started' || eventType === 'turn.started') {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
// -----------------------------------------------------------------------
|
|
74
|
+
// item.completed — dispatch on item.type
|
|
75
|
+
// -----------------------------------------------------------------------
|
|
76
|
+
if (eventType === 'item.completed') {
|
|
77
|
+
const ev = parsed;
|
|
78
|
+
const item = ev.item;
|
|
79
|
+
if (typeof item !== 'object' || item === null)
|
|
80
|
+
return [];
|
|
81
|
+
const itemType = item.type;
|
|
82
|
+
if (itemType === 'agent_message') {
|
|
83
|
+
const msg = item;
|
|
84
|
+
const delta = typeof msg.text === 'string' ? msg.text : '';
|
|
85
|
+
accumulatedText += delta;
|
|
86
|
+
return [{ type: 'text', delta }];
|
|
87
|
+
}
|
|
88
|
+
if (itemType === 'reasoning') {
|
|
89
|
+
const r = item;
|
|
90
|
+
if (typeof r.text === 'string' && r.text.length > 0) {
|
|
91
|
+
return [{ type: 'reasoning', delta: r.text }];
|
|
92
|
+
}
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
if (itemType === 'command_execution' ||
|
|
96
|
+
itemType === 'file_change' ||
|
|
97
|
+
itemType === 'mcp_tool_call') {
|
|
98
|
+
const act = item;
|
|
99
|
+
const toolEvent = typeof act.detail === 'string'
|
|
100
|
+
? { type: 'tool', name: itemType, phase: 'end', detail: act.detail }
|
|
101
|
+
: { type: 'tool', name: itemType, phase: 'end' };
|
|
102
|
+
return [toolEvent];
|
|
103
|
+
}
|
|
104
|
+
// Unknown item type — emit nothing
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
// -----------------------------------------------------------------------
|
|
108
|
+
// turn.completed — emit usage + done (no costUsd)
|
|
109
|
+
// -----------------------------------------------------------------------
|
|
110
|
+
if (eventType === 'turn.completed') {
|
|
111
|
+
const ev = parsed;
|
|
112
|
+
const usage = ev.usage !== undefined && ev.usage !== null
|
|
113
|
+
? mapUsage(ev.usage)
|
|
114
|
+
: { inputTokens: 0, outputTokens: 0 };
|
|
115
|
+
return [
|
|
116
|
+
{ type: 'usage', usage },
|
|
117
|
+
{
|
|
118
|
+
type: 'done',
|
|
119
|
+
text: accumulatedText,
|
|
120
|
+
usage,
|
|
121
|
+
raw: parsed,
|
|
122
|
+
},
|
|
123
|
+
];
|
|
124
|
+
}
|
|
125
|
+
// -----------------------------------------------------------------------
|
|
126
|
+
// turn.failed — emit error
|
|
127
|
+
// -----------------------------------------------------------------------
|
|
128
|
+
if (eventType === 'turn.failed') {
|
|
129
|
+
const ev = parsed;
|
|
130
|
+
const message = ev.error?.message ?? 'turn failed';
|
|
131
|
+
return [{ type: 'error', error: classifyError(message, 1) }];
|
|
132
|
+
}
|
|
133
|
+
// -----------------------------------------------------------------------
|
|
134
|
+
// error — emit error
|
|
135
|
+
// -----------------------------------------------------------------------
|
|
136
|
+
if (eventType === 'error') {
|
|
137
|
+
const ev = parsed;
|
|
138
|
+
const message = ev.message ?? 'unknown error';
|
|
139
|
+
return [{ type: 'error', error: classifyError(message, 1) }];
|
|
140
|
+
}
|
|
141
|
+
// Unknown event type — emit nothing
|
|
142
|
+
return [];
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=codex-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex-parse.js","sourceRoot":"","sources":["../../src/providers/codex-parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/codex.ts — Codex CLI adapter implementing the Provider port.
|
|
3
|
+
*
|
|
4
|
+
* Spawns `codex exec --json -m <model> --sandbox <level>` via execa v9,
|
|
5
|
+
* delivers the prompt via STDIN (never as an argv argument), and streams
|
|
6
|
+
* parsed ProviderEvents to the caller as they arrive.
|
|
7
|
+
*
|
|
8
|
+
* Sandbox mapping:
|
|
9
|
+
* read-only → '--sandbox read-only'
|
|
10
|
+
* workspace-write → '--sandbox workspace-write'
|
|
11
|
+
* full-access → '--sandbox danger-full-access'
|
|
12
|
+
*
|
|
13
|
+
* Authentication note:
|
|
14
|
+
* Auth state is OPTIMISTIC at detect() time — we cannot cheaply probe it.
|
|
15
|
+
* The real auth state surfaces at run time: an unauthenticated run produces
|
|
16
|
+
* a stderr/event that classifyError() maps to category 'auth'.
|
|
17
|
+
*
|
|
18
|
+
* costUsd note:
|
|
19
|
+
* Codex does NOT report a USD cost. The `done` event therefore omits
|
|
20
|
+
* `costUsd` entirely. The orchestrator will fall back to pricing-table
|
|
21
|
+
* estimation from the usage tokens — this is the documented behaviour.
|
|
22
|
+
*
|
|
23
|
+
* Execa v9 streaming:
|
|
24
|
+
* We use `for await (const line of subprocess)` which iterates over stdout
|
|
25
|
+
* lines as strings. Confirmed from execa types: the subprocess IS an
|
|
26
|
+
* AsyncIterable<string>.
|
|
27
|
+
*/
|
|
28
|
+
import type { Provider, SandboxLevel } from './port.js';
|
|
29
|
+
/**
|
|
30
|
+
* Map the abstract {@link SandboxLevel} to the concrete `--sandbox` argument
|
|
31
|
+
* that the Codex CLI accepts.
|
|
32
|
+
*
|
|
33
|
+
* NEVER default to 'danger-full-access' — always require the caller to opt in
|
|
34
|
+
* explicitly by passing SandboxLevel 'full-access'.
|
|
35
|
+
*/
|
|
36
|
+
export declare function toSandboxArg(level: SandboxLevel): string;
|
|
37
|
+
/**
|
|
38
|
+
* Create a Codex provider adapter.
|
|
39
|
+
*
|
|
40
|
+
* @param opts.bin - Override the binary name/path (default: `'codex'`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function createCodexProvider(opts?: {
|
|
43
|
+
bin?: string;
|
|
44
|
+
}): Provider;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/codex.ts — Codex CLI adapter implementing the Provider port.
|
|
3
|
+
*
|
|
4
|
+
* Spawns `codex exec --json -m <model> --sandbox <level>` via execa v9,
|
|
5
|
+
* delivers the prompt via STDIN (never as an argv argument), and streams
|
|
6
|
+
* parsed ProviderEvents to the caller as they arrive.
|
|
7
|
+
*
|
|
8
|
+
* Sandbox mapping:
|
|
9
|
+
* read-only → '--sandbox read-only'
|
|
10
|
+
* workspace-write → '--sandbox workspace-write'
|
|
11
|
+
* full-access → '--sandbox danger-full-access'
|
|
12
|
+
*
|
|
13
|
+
* Authentication note:
|
|
14
|
+
* Auth state is OPTIMISTIC at detect() time — we cannot cheaply probe it.
|
|
15
|
+
* The real auth state surfaces at run time: an unauthenticated run produces
|
|
16
|
+
* a stderr/event that classifyError() maps to category 'auth'.
|
|
17
|
+
*
|
|
18
|
+
* costUsd note:
|
|
19
|
+
* Codex does NOT report a USD cost. The `done` event therefore omits
|
|
20
|
+
* `costUsd` entirely. The orchestrator will fall back to pricing-table
|
|
21
|
+
* estimation from the usage tokens — this is the documented behaviour.
|
|
22
|
+
*
|
|
23
|
+
* Execa v9 streaming:
|
|
24
|
+
* We use `for await (const line of subprocess)` which iterates over stdout
|
|
25
|
+
* lines as strings. Confirmed from execa types: the subprocess IS an
|
|
26
|
+
* AsyncIterable<string>.
|
|
27
|
+
*/
|
|
28
|
+
import { execa } from 'execa';
|
|
29
|
+
import { detectProvider } from './detect.js';
|
|
30
|
+
import { classifyError } from './errors.js';
|
|
31
|
+
import { createCodexParser } from './codex-parse.js';
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Sandbox argument mapping
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
/**
|
|
36
|
+
* Map the abstract {@link SandboxLevel} to the concrete `--sandbox` argument
|
|
37
|
+
* that the Codex CLI accepts.
|
|
38
|
+
*
|
|
39
|
+
* NEVER default to 'danger-full-access' — always require the caller to opt in
|
|
40
|
+
* explicitly by passing SandboxLevel 'full-access'.
|
|
41
|
+
*/
|
|
42
|
+
export function toSandboxArg(level) {
|
|
43
|
+
switch (level) {
|
|
44
|
+
case 'read-only':
|
|
45
|
+
return 'read-only';
|
|
46
|
+
case 'workspace-write':
|
|
47
|
+
return 'workspace-write';
|
|
48
|
+
case 'full-access':
|
|
49
|
+
return 'danger-full-access';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Factory
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
/**
|
|
56
|
+
* Create a Codex provider adapter.
|
|
57
|
+
*
|
|
58
|
+
* @param opts.bin - Override the binary name/path (default: `'codex'`).
|
|
59
|
+
*/
|
|
60
|
+
export function createCodexProvider(opts) {
|
|
61
|
+
const bin = opts?.bin ?? 'codex';
|
|
62
|
+
return {
|
|
63
|
+
id: 'codex',
|
|
64
|
+
detect() {
|
|
65
|
+
return detectProvider('codex');
|
|
66
|
+
},
|
|
67
|
+
async *run(req, signal) {
|
|
68
|
+
const args = [
|
|
69
|
+
'exec',
|
|
70
|
+
'--json',
|
|
71
|
+
'-m',
|
|
72
|
+
req.model,
|
|
73
|
+
'--sandbox',
|
|
74
|
+
toSandboxArg(req.sandbox),
|
|
75
|
+
];
|
|
76
|
+
// Spawn with reject:false so we always get the result object (never throws).
|
|
77
|
+
// cancelSignal wires our AbortSignal directly to execa's termination path.
|
|
78
|
+
const subprocess = execa(bin, args, {
|
|
79
|
+
cwd: req.cwd,
|
|
80
|
+
input: req.prompt, // deliver prompt via STDIN, not argv
|
|
81
|
+
cancelSignal: signal,
|
|
82
|
+
timeout: req.timeoutMs,
|
|
83
|
+
reject: false,
|
|
84
|
+
});
|
|
85
|
+
// One parser instance per run — holds the accumulated text closure.
|
|
86
|
+
const parseCodexLine = createCodexParser();
|
|
87
|
+
let emittedTerminal = false;
|
|
88
|
+
// Stream stdout line by line.
|
|
89
|
+
// execa v9: the subprocess itself is an AsyncIterable that yields one
|
|
90
|
+
// string per stdout line (from types/subprocess/subprocess.d.ts).
|
|
91
|
+
try {
|
|
92
|
+
for await (const line of subprocess) {
|
|
93
|
+
const events = parseCodexLine(line);
|
|
94
|
+
for (const ev of events) {
|
|
95
|
+
yield ev;
|
|
96
|
+
if (ev.type === 'done' || ev.type === 'error') {
|
|
97
|
+
emittedTerminal = true;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
// Iteration errors (e.g. stream abort) are handled below via the result.
|
|
104
|
+
}
|
|
105
|
+
// Wait for the subprocess to fully exit and collect the result.
|
|
106
|
+
const result = await subprocess;
|
|
107
|
+
if (!emittedTerminal) {
|
|
108
|
+
if (result.isCanceled) {
|
|
109
|
+
yield {
|
|
110
|
+
type: 'error',
|
|
111
|
+
error: classifyError('cancelled', 1),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
else if (result.failed || (result.exitCode !== undefined && result.exitCode !== 0)) {
|
|
115
|
+
yield {
|
|
116
|
+
type: 'error',
|
|
117
|
+
error: classifyError(typeof result.stderr === 'string' ? result.stderr : '', result.exitCode ?? 1),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/providers/codex.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAG9B,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAErD,8EAA8E;AAC9E,2BAA2B;AAC3B,8EAA8E;AAE9E;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmB;IAC9C,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,WAAW;YACd,OAAO,WAAW,CAAC;QACrB,KAAK,iBAAiB;YACpB,OAAO,iBAAiB,CAAC;QAC3B,KAAK,aAAa;YAChB,OAAO,oBAAoB,CAAC;IAChC,CAAC;AACH,CAAC;AAED,8EAA8E;AAC9E,UAAU;AACV,8EAA8E;AAE9E;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAuB;IACzD,MAAM,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI,OAAO,CAAC;IAEjC,OAAO;QACL,EAAE,EAAE,OAAO;QAEX,MAAM;YACJ,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;QACjC,CAAC;QAED,KAAK,CAAC,CAAC,GAAG,CAAC,GAAoB,EAAE,MAAmB;YAClD,MAAM,IAAI,GAAG;gBACX,MAAM;gBACN,QAAQ;gBACR,IAAI;gBACJ,GAAG,CAAC,KAAK;gBACT,WAAW;gBACX,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC;aAC1B,CAAC;YAEF,6EAA6E;YAC7E,2EAA2E;YAC3E,MAAM,UAAU,GAAG,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE;gBAClC,GAAG,EAAE,GAAG,CAAC,GAAG;gBACZ,KAAK,EAAE,GAAG,CAAC,MAAM,EAAO,qCAAqC;gBAC7D,YAAY,EAAE,MAAM;gBACpB,OAAO,EAAE,GAAG,CAAC,SAAS;gBACtB,MAAM,EAAE,KAAK;aACd,CAAC,CAAC;YAEH,oEAAoE;YACpE,MAAM,cAAc,GAAG,iBAAiB,EAAE,CAAC;YAE3C,IAAI,eAAe,GAAG,KAAK,CAAC;YAE5B,8BAA8B;YAC9B,sEAAsE;YACtE,kEAAkE;YAClE,IAAI,CAAC;gBACH,IAAI,KAAK,EAAE,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;oBACpC,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;oBACpC,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,CAAC;wBACxB,MAAM,EAAE,CAAC;wBACT,IAAI,EAAE,CAAC,IAAI,KAAK,MAAM,IAAI,EAAE,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;4BAC9C,eAAe,GAAG,IAAI,CAAC;wBACzB,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,yEAAyE;YAC3E,CAAC;YAED,gEAAgE;YAChE,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC;YAEhC,IAAI,CAAC,eAAe,EAAE,CAAC;gBACrB,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;oBACtB,MAAM;wBACJ,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,aAAa,CAAC,WAAW,EAAE,CAAC,CAAC;qBACrC,CAAC;gBACJ,CAAC;qBAAM,IAAI,MAAM,CAAC,MAAM,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,SAAS,IAAI,MAAM,CAAC,QAAQ,KAAK,CAAC,CAAC,EAAE,CAAC;oBACrF,MAAM;wBACJ,IAAI,EAAE,OAAO;wBACb,KAAK,EAAE,aAAa,CAClB,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EACtD,MAAM,CAAC,QAAQ,IAAI,CAAC,CACrB;qBACF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/detect.ts
|
|
3
|
+
*
|
|
4
|
+
* Provider-detection logic.
|
|
5
|
+
*
|
|
6
|
+
* Claude detection is REAL: spawns `claude --version` to probe installation.
|
|
7
|
+
* Codex detection is REAL: spawns `codex --version` to probe installation.
|
|
8
|
+
* Authentication is OPTIMISTIC for both — we cannot cheaply probe auth state
|
|
9
|
+
* without spending API quota. The real auth state surfaces at run time and is
|
|
10
|
+
* classified by errors.ts (category 'auth').
|
|
11
|
+
*/
|
|
12
|
+
export interface ProviderStatus {
|
|
13
|
+
readonly id: 'claude' | 'codex';
|
|
14
|
+
readonly installed: boolean;
|
|
15
|
+
readonly version: string | null;
|
|
16
|
+
readonly authenticated: boolean;
|
|
17
|
+
readonly binaryPath: string | null;
|
|
18
|
+
readonly availableModels: readonly string[];
|
|
19
|
+
}
|
|
20
|
+
export interface EnvironmentStatus {
|
|
21
|
+
readonly claude: ProviderStatus;
|
|
22
|
+
readonly codex: ProviderStatus;
|
|
23
|
+
readonly hasAnyProvider: boolean;
|
|
24
|
+
readonly platform: NodeJS.Platform;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Detect whether a provider CLI is installed and (optimistically) usable.
|
|
28
|
+
*
|
|
29
|
+
* For 'claude': runs `claude --version` to confirm the binary is present and
|
|
30
|
+
* executable. Authentication is reported as `true` optimistically — we cannot
|
|
31
|
+
* cheaply probe auth state without spending API quota. If the user is not
|
|
32
|
+
* authenticated, the real auth failure surfaces at run time via classifyError()
|
|
33
|
+
* (category 'auth').
|
|
34
|
+
*
|
|
35
|
+
* For 'codex': runs `codex --version` to confirm the binary is present and
|
|
36
|
+
* executable. Authentication is reported as `true` optimistically for the same
|
|
37
|
+
* reason — real auth failures surface at run time via classifyError().
|
|
38
|
+
*/
|
|
39
|
+
export declare function detectProvider(id: 'claude' | 'codex'): Promise<ProviderStatus>;
|
|
40
|
+
/**
|
|
41
|
+
* Detect the full environment — both providers — in parallel.
|
|
42
|
+
*
|
|
43
|
+
* Stub: delegates to detectProvider for each provider ID.
|
|
44
|
+
*/
|
|
45
|
+
export declare function detectEnvironment(): Promise<EnvironmentStatus>;
|
|
46
|
+
/**
|
|
47
|
+
* Return the shell command a user should run to install the given provider.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getInstallCommand(id: 'claude' | 'codex'): string;
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/providers/detect.ts
|
|
3
|
+
*
|
|
4
|
+
* Provider-detection logic.
|
|
5
|
+
*
|
|
6
|
+
* Claude detection is REAL: spawns `claude --version` to probe installation.
|
|
7
|
+
* Codex detection is REAL: spawns `codex --version` to probe installation.
|
|
8
|
+
* Authentication is OPTIMISTIC for both — we cannot cheaply probe auth state
|
|
9
|
+
* without spending API quota. The real auth state surfaces at run time and is
|
|
10
|
+
* classified by errors.ts (category 'auth').
|
|
11
|
+
*/
|
|
12
|
+
import { execa } from 'execa';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Internal factory
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
function notDetected(id) {
|
|
17
|
+
return {
|
|
18
|
+
id,
|
|
19
|
+
installed: false,
|
|
20
|
+
version: null,
|
|
21
|
+
authenticated: false,
|
|
22
|
+
binaryPath: null,
|
|
23
|
+
availableModels: [],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
// Public API
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
/**
|
|
30
|
+
* Detect whether a provider CLI is installed and (optimistically) usable.
|
|
31
|
+
*
|
|
32
|
+
* For 'claude': runs `claude --version` to confirm the binary is present and
|
|
33
|
+
* executable. Authentication is reported as `true` optimistically — we cannot
|
|
34
|
+
* cheaply probe auth state without spending API quota. If the user is not
|
|
35
|
+
* authenticated, the real auth failure surfaces at run time via classifyError()
|
|
36
|
+
* (category 'auth').
|
|
37
|
+
*
|
|
38
|
+
* For 'codex': runs `codex --version` to confirm the binary is present and
|
|
39
|
+
* executable. Authentication is reported as `true` optimistically for the same
|
|
40
|
+
* reason — real auth failures surface at run time via classifyError().
|
|
41
|
+
*/
|
|
42
|
+
export async function detectProvider(id) {
|
|
43
|
+
if (id === 'claude') {
|
|
44
|
+
try {
|
|
45
|
+
const result = await execa('claude', ['--version'], {
|
|
46
|
+
reject: false,
|
|
47
|
+
timeout: 10_000,
|
|
48
|
+
});
|
|
49
|
+
if (result.exitCode === 0) {
|
|
50
|
+
return {
|
|
51
|
+
id: 'claude',
|
|
52
|
+
installed: true,
|
|
53
|
+
version: result.stdout.trim(),
|
|
54
|
+
binaryPath: 'claude',
|
|
55
|
+
availableModels: ['opus', 'sonnet', 'haiku'],
|
|
56
|
+
/**
|
|
57
|
+
* Authentication is OPTIMISTIC: we assume the user is authenticated
|
|
58
|
+
* if the binary is installed. The real auth state surfaces at run
|
|
59
|
+
* time when classifyError() maps an auth-related stderr to 'auth'.
|
|
60
|
+
*/
|
|
61
|
+
authenticated: true,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// Binary not found or spawn error — fall through to notDetected
|
|
67
|
+
}
|
|
68
|
+
return notDetected('claude');
|
|
69
|
+
}
|
|
70
|
+
// codex: run `codex --version` to probe installation
|
|
71
|
+
try {
|
|
72
|
+
const result = await execa('codex', ['--version'], {
|
|
73
|
+
reject: false,
|
|
74
|
+
timeout: 10_000,
|
|
75
|
+
});
|
|
76
|
+
if (result.exitCode === 0) {
|
|
77
|
+
return {
|
|
78
|
+
id: 'codex',
|
|
79
|
+
installed: true,
|
|
80
|
+
version: result.stdout.trim(),
|
|
81
|
+
binaryPath: 'codex',
|
|
82
|
+
availableModels: ['gpt-5.5', 'gpt-5.4', 'gpt-5.4-mini'],
|
|
83
|
+
/**
|
|
84
|
+
* Authentication is OPTIMISTIC: we assume the user is authenticated
|
|
85
|
+
* if the binary is installed. The real auth state surfaces at run
|
|
86
|
+
* time when classifyError() maps an auth-related stderr to 'auth'.
|
|
87
|
+
*/
|
|
88
|
+
authenticated: true,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// Binary not found or spawn error — fall through to notDetected
|
|
94
|
+
}
|
|
95
|
+
return notDetected('codex');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Detect the full environment — both providers — in parallel.
|
|
99
|
+
*
|
|
100
|
+
* Stub: delegates to detectProvider for each provider ID.
|
|
101
|
+
*/
|
|
102
|
+
export async function detectEnvironment() {
|
|
103
|
+
const [claude, codex] = await Promise.all([
|
|
104
|
+
detectProvider('claude'),
|
|
105
|
+
detectProvider('codex'),
|
|
106
|
+
]);
|
|
107
|
+
return {
|
|
108
|
+
claude,
|
|
109
|
+
codex,
|
|
110
|
+
hasAnyProvider: claude.installed || codex.installed,
|
|
111
|
+
platform: process.platform,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Return the shell command a user should run to install the given provider.
|
|
116
|
+
*/
|
|
117
|
+
export function getInstallCommand(id) {
|
|
118
|
+
switch (id) {
|
|
119
|
+
case 'claude':
|
|
120
|
+
return 'npm install -g @anthropic-ai/claude-code';
|
|
121
|
+
case 'codex':
|
|
122
|
+
return 'npm install -g @openai/codex';
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=detect.js.map
|