@openagents-org/agent-launcher 0.2.173 → 0.2.175
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/icons/commandcode.svg +3 -0
- package/package.json +1 -1
- package/registry.json +259 -35
- package/src/adapters/base.js +19 -0
- package/src/adapters/codex.js +41 -1
- package/src/adapters/commandcode-stream.js +405 -0
- package/src/adapters/commandcode.js +853 -0
- package/src/adapters/hermes.js +56 -0
- package/src/adapters/index.js +4 -1
- package/src/adapters/kimi-stream.js +360 -0
- package/src/adapters/kimi.js +724 -13
- package/src/adapters/openclaw.js +28 -2
- package/src/adapters/opencode.js +86 -0
- package/src/adapters/pi-stream.js +28 -0
- package/src/adapters/pi.js +21 -2
- package/src/adapters/workspace-prompt.js +42 -8
- package/src/cli.js +182 -6
- package/src/config.js +13 -2
- package/src/daemon.js +192 -59
- package/src/env.js +22 -2
- package/src/index.js +13 -0
- package/src/installer.js +69 -1
- package/src/paths.js +315 -11
- package/src/probe.js +29 -8
- package/src/registry.js +4 -1
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure helpers for the Command Code adapter — argv construction, NDJSON frame
|
|
3
|
+
* interpretation, exit-code classification, and the version gate.
|
|
4
|
+
*
|
|
5
|
+
* Split out from commandcode.js so the parts with real decision logic are unit
|
|
6
|
+
* testable without spawning a CLI, a network, or a workspace.
|
|
7
|
+
*
|
|
8
|
+
* Two design choices drive everything here, and both come from Command Code's
|
|
9
|
+
* own headless contract (`cmd -p --output-format json`):
|
|
10
|
+
*
|
|
11
|
+
* 1. THE RESULT LINE IS AUTHORITATIVE. The stream ends with exactly one
|
|
12
|
+
* `{"type":"result",...}` object carrying `finalText`, `subtype` and
|
|
13
|
+
* `usage`. The assistant's answer is read from THERE, never reassembled
|
|
14
|
+
* from `text_delta` events. Event frames are progress decoration only, so
|
|
15
|
+
* an upstream schema change degrades the live tool/thinking ticker rather
|
|
16
|
+
* than losing the reply — the failure mode that keeps biting adapters
|
|
17
|
+
* which rebuild the answer from a delta stream.
|
|
18
|
+
* 2. EXIT CODES ARE A PUBLISHED CONTRACT. Command Code documents eleven of
|
|
19
|
+
* them (3 = not authenticated, 5 = rate limited, 10 = out of credits, …).
|
|
20
|
+
* Classification reads that number instead of regex-matching stderr, so
|
|
21
|
+
* the user-facing message is right even when the wording upstream changes.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
'use strict';
|
|
25
|
+
|
|
26
|
+
// Supported floor. 1.0.0 is the release that introduced
|
|
27
|
+
// `-p --output-format json` (CHANGELOG: "feat: `-p --output-format json` for
|
|
28
|
+
// cmd headless mode and scripting"). Below it there is no JSON stream to read
|
|
29
|
+
// at all, so the run is blocked rather than left to fail obscurely.
|
|
30
|
+
const COMMANDCODE_MIN_VERSION = '1.0.0';
|
|
31
|
+
// Highest version whose behavior we have verified. Newer is allowed — it just
|
|
32
|
+
// isn't claimed as tested.
|
|
33
|
+
const COMMANDCODE_TESTED_MAX_VERSION = '1.36.0';
|
|
34
|
+
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// Version gate
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
/** Extract a dotted version from `--version` output. Returns null if absent. */
|
|
40
|
+
function parseCommandCodeVersion(raw) {
|
|
41
|
+
if (!raw || typeof raw !== 'string') return null;
|
|
42
|
+
const m = raw.match(/(\d+)\.(\d+)\.(\d+)(?:[-.][0-9A-Za-z.]+)?/);
|
|
43
|
+
return m ? `${m[1]}.${m[2]}.${m[3]}` : null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Numeric compare of two dotted versions. Returns -1 / 0 / 1. */
|
|
47
|
+
function compareVersions(a, b) {
|
|
48
|
+
const pa = String(a).split('.').map((n) => parseInt(n, 10) || 0);
|
|
49
|
+
const pb = String(b).split('.').map((n) => parseInt(n, 10) || 0);
|
|
50
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
51
|
+
const x = pa[i] || 0;
|
|
52
|
+
const y = pb[i] || 0;
|
|
53
|
+
if (x !== y) return x < y ? -1 : 1;
|
|
54
|
+
}
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Classify a raw `--version` string.
|
|
60
|
+
*
|
|
61
|
+
* `supported: null` means "could not tell" — an unreadable version proceeds
|
|
62
|
+
* leniently rather than blocking a CLI that is probably fine. Only a version we
|
|
63
|
+
* positively read AND that is below the floor returns `false`.
|
|
64
|
+
*/
|
|
65
|
+
function classifyCommandCodeVersion(rawVersion) {
|
|
66
|
+
const version = parseCommandCodeVersion(rawVersion);
|
|
67
|
+
if (!version) return { version: null, supported: null, tested: null };
|
|
68
|
+
return {
|
|
69
|
+
version,
|
|
70
|
+
supported: compareVersions(version, COMMANDCODE_MIN_VERSION) >= 0,
|
|
71
|
+
tested: compareVersions(version, COMMANDCODE_TESTED_MAX_VERSION) <= 0,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ---------------------------------------------------------------------------
|
|
76
|
+
// Redaction
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
|
|
79
|
+
const SECRET_PATTERNS = [
|
|
80
|
+
// Provider API keys: sk-..., sk-ant-..., sk-or-..., and friends.
|
|
81
|
+
/\bsk-[A-Za-z0-9._-]{8,}\b/g,
|
|
82
|
+
// Bearer tokens in copied headers.
|
|
83
|
+
/\bBearer\s+[A-Za-z0-9._-]{8,}\b/gi,
|
|
84
|
+
// Long opaque blobs assigned to a secret-looking key.
|
|
85
|
+
/\b(api[_-]?key|token|secret|password|authorization)\b\s*[:=]\s*["']?[A-Za-z0-9._-]{8,}["']?/gi,
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
/** Redact obvious secret material from free text before it is logged. */
|
|
89
|
+
function redactSecrets(text) {
|
|
90
|
+
if (text == null) return text;
|
|
91
|
+
let out = String(text);
|
|
92
|
+
for (const re of SECRET_PATTERNS) out = out.replace(re, (m) => {
|
|
93
|
+
const eq = m.match(/\s*[:=]\s*/);
|
|
94
|
+
if (eq) return m.slice(0, m.indexOf(eq[0])) + eq[0] + '[redacted]';
|
|
95
|
+
return m.startsWith('Bearer') || m.startsWith('bearer') ? 'Bearer [redacted]' : '[redacted]';
|
|
96
|
+
});
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Truncate with an ellipsis, for previews and diagnostics. */
|
|
101
|
+
function truncate(s, n) {
|
|
102
|
+
s = String(s == null ? '' : s);
|
|
103
|
+
return s.length > n ? s.slice(0, n - 1) + '…' : s;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Redact an argv for logging. The prompt never rides in argv (it is piped over
|
|
108
|
+
* stdin), so the only sensitive-ish value here is a resumed session id.
|
|
109
|
+
*/
|
|
110
|
+
function redactArgs(args) {
|
|
111
|
+
const out = [];
|
|
112
|
+
for (let i = 0; i < args.length; i++) {
|
|
113
|
+
const a = args[i];
|
|
114
|
+
out.push(a);
|
|
115
|
+
if ((a === '--resume' || a === '-r') && args[i + 1]) {
|
|
116
|
+
out.push('<session-id>');
|
|
117
|
+
i++;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// argv
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Build the argv for one headless run.
|
|
129
|
+
*
|
|
130
|
+
* The PROMPT IS NOT HERE. `-p` is passed with no value and the prompt is piped
|
|
131
|
+
* over stdin, which Command Code auto-detects. That keeps arbitrarily long
|
|
132
|
+
* prompts (a workspace skill header plus channel recap plus the user's turn)
|
|
133
|
+
* away from the OS argv limit, and sidesteps quoting entirely on Windows,
|
|
134
|
+
* where an embedded quote in a user message is otherwise a command-line
|
|
135
|
+
* injection waiting to happen.
|
|
136
|
+
*
|
|
137
|
+
* @param {object} o
|
|
138
|
+
* @param {string} [o.model] `-m` model id (empty = the CLI's default)
|
|
139
|
+
* @param {string} [o.effort] `--effort` low | medium | high
|
|
140
|
+
* @param {string} [o.resumeSessionId] `--resume <id>` to continue a channel
|
|
141
|
+
* @param {boolean} [o.planMode] read-only exploration instead of `--yolo`
|
|
142
|
+
* @param {number} [o.maxTurns] `--max-turns` (CLI default is 100)
|
|
143
|
+
* @returns {string[]}
|
|
144
|
+
*/
|
|
145
|
+
function buildCommandCodeArgs(o = {}) {
|
|
146
|
+
const args = [
|
|
147
|
+
'-p',
|
|
148
|
+
'--output-format', 'json',
|
|
149
|
+
// Taste onboarding is an interactive first-run flow; without this an
|
|
150
|
+
// automated run can sit waiting for a human that will never arrive.
|
|
151
|
+
'--skip-onboarding',
|
|
152
|
+
// Auto-trust the project. The trust prompt is interactive too, and the
|
|
153
|
+
// working directory here was configured by the user in the launcher.
|
|
154
|
+
'--trust',
|
|
155
|
+
// A background self-update would swap the CLI — and with it the JSON
|
|
156
|
+
// contract this module parses — under a daemon that is mid-run. Upgrades
|
|
157
|
+
// happen through the launcher's install path instead.
|
|
158
|
+
'--no-auto-update',
|
|
159
|
+
// Session id to stderr. The result line carries `sessionId` too, but the
|
|
160
|
+
// docs mark that field OPTIONAL — it is absent exactly when a run fails
|
|
161
|
+
// early. This is the fallback that keeps a channel resumable.
|
|
162
|
+
'--verbose',
|
|
163
|
+
];
|
|
164
|
+
|
|
165
|
+
if (o.planMode) {
|
|
166
|
+
// Read-only exploration. Deliberately NOT combined with --yolo.
|
|
167
|
+
args.push('--plan');
|
|
168
|
+
} else {
|
|
169
|
+
// Headless blocks writes and shell by default, which would leave the agent
|
|
170
|
+
// able to talk but not to act.
|
|
171
|
+
args.push('--yolo');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const model = (o.model || '').trim();
|
|
175
|
+
if (model) args.push('--model', model);
|
|
176
|
+
|
|
177
|
+
const effort = (o.effort || '').trim();
|
|
178
|
+
if (effort) args.push('--effort', effort);
|
|
179
|
+
|
|
180
|
+
if (Number.isFinite(o.maxTurns) && o.maxTurns > 0) {
|
|
181
|
+
args.push('--max-turns', String(Math.floor(o.maxTurns)));
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const resume = (o.resumeSessionId || '').trim();
|
|
185
|
+
if (resume) args.push('--resume', resume);
|
|
186
|
+
|
|
187
|
+
return args;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
// NDJSON frames
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
|
|
194
|
+
/** Parse one NDJSON line. Returns null for blank lines and malformed JSON. */
|
|
195
|
+
function parseFrame(line) {
|
|
196
|
+
const s = String(line == null ? '' : line).trim();
|
|
197
|
+
if (!s || s[0] !== '{') return null;
|
|
198
|
+
try {
|
|
199
|
+
const v = JSON.parse(s);
|
|
200
|
+
return v && typeof v === 'object' ? v : null;
|
|
201
|
+
} catch {
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Human-readable verbs for Command Code's documented tool names, so the
|
|
207
|
+
// workspace ticker reads like activity rather than an API log.
|
|
208
|
+
const TOOL_LABELS = {
|
|
209
|
+
read_file: 'reading',
|
|
210
|
+
read_directory: 'listing',
|
|
211
|
+
write_file: 'writing',
|
|
212
|
+
edit_file: 'editing',
|
|
213
|
+
glob: 'searching',
|
|
214
|
+
grep: 'searching',
|
|
215
|
+
shell_command: 'running',
|
|
216
|
+
powershell: 'running',
|
|
217
|
+
shell_output: 'reading output',
|
|
218
|
+
monitor_command: 'monitoring',
|
|
219
|
+
shell_tasks: 'checking tasks',
|
|
220
|
+
kill_shell: 'stopping',
|
|
221
|
+
todo_write: 'planning',
|
|
222
|
+
web_search: 'searching the web',
|
|
223
|
+
web_fetch: 'fetching',
|
|
224
|
+
agent: 'delegating',
|
|
225
|
+
activate_skill: 'loading a skill',
|
|
226
|
+
taste: 'learning taste',
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/** Friendly verb for a tool name; unknown names pass through unchanged. */
|
|
230
|
+
function friendlyToolLabel(toolName) {
|
|
231
|
+
const name = String(toolName || '').trim();
|
|
232
|
+
return TOOL_LABELS[name] || name || 'working';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Frames that carry no user-visible progress. Listing them explicitly keeps
|
|
236
|
+
// them out of the `unknown` diagnostic channel, so a genuinely new event type
|
|
237
|
+
// still shows up in the log.
|
|
238
|
+
const IGNORED_EVENTS = new Set([
|
|
239
|
+
'run_start', 'turn_start', 'turn_end',
|
|
240
|
+
'model_request_start', 'model_request_end',
|
|
241
|
+
'tool_queued', 'tool_update', 'tool_hooks',
|
|
242
|
+
'text_delta', 'message_start', 'message_update', 'message_end',
|
|
243
|
+
'session_start', 'session_info', 'skill_loaded',
|
|
244
|
+
'compaction_start', 'compaction_done',
|
|
245
|
+
'subagent_start', 'subagent_stop', 'subagent_progress',
|
|
246
|
+
]);
|
|
247
|
+
|
|
248
|
+
const TOOL_STATES = {
|
|
249
|
+
tool_running: 'running',
|
|
250
|
+
tool_completed: 'completed',
|
|
251
|
+
tool_errored: 'errored',
|
|
252
|
+
tool_denied: 'denied',
|
|
253
|
+
tool_hook_blocked: 'blocked',
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Normalize one parsed frame into something the adapter can act on.
|
|
258
|
+
*
|
|
259
|
+
* Only two shapes matter: the single `result` line (authoritative — it carries
|
|
260
|
+
* the reply) and `event` frames (progress only). Anything unrecognized becomes
|
|
261
|
+
* `{ kind: 'unknown' }` with a short redacted preview rather than an
|
|
262
|
+
* exception, because the docs explicitly ask consumers to treat unknown event
|
|
263
|
+
* types as forward-compatible.
|
|
264
|
+
*
|
|
265
|
+
* @returns {{kind: string, [k: string]: any}}
|
|
266
|
+
*/
|
|
267
|
+
function interpretCommandCodeFrame(frame) {
|
|
268
|
+
if (!frame || typeof frame !== 'object') return { kind: 'ignored' };
|
|
269
|
+
|
|
270
|
+
if (frame.type === 'result') {
|
|
271
|
+
const usage = frame.usage && typeof frame.usage === 'object' ? frame.usage : null;
|
|
272
|
+
return {
|
|
273
|
+
kind: 'result',
|
|
274
|
+
subtype: typeof frame.subtype === 'string' ? frame.subtype : 'error',
|
|
275
|
+
// Always a string: the docs guarantee the field, empty on an error result.
|
|
276
|
+
finalText: typeof frame.finalText === 'string' ? frame.finalText : '',
|
|
277
|
+
// Both optional — absent when the run died before a session existed.
|
|
278
|
+
sessionId: typeof frame.sessionId === 'string' ? frame.sessionId : null,
|
|
279
|
+
stopReason: typeof frame.stopReason === 'string' ? frame.stopReason : null,
|
|
280
|
+
error: frame.error == null ? null : redactSecrets(String(frame.error)),
|
|
281
|
+
usage,
|
|
282
|
+
durationMs: Number.isFinite(frame.durationMs) ? frame.durationMs : null,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (frame.type !== 'event') return { kind: 'unknown', raw: truncate(redactSecrets(JSON.stringify(frame)), 200) };
|
|
287
|
+
|
|
288
|
+
const ev = frame.event;
|
|
289
|
+
if (!ev || typeof ev !== 'object' || typeof ev.type !== 'string') return { kind: 'ignored' };
|
|
290
|
+
|
|
291
|
+
if (TOOL_STATES[ev.type]) {
|
|
292
|
+
return {
|
|
293
|
+
kind: 'tool',
|
|
294
|
+
state: TOOL_STATES[ev.type],
|
|
295
|
+
toolCallId: typeof ev.toolCallId === 'string' ? ev.toolCallId : null,
|
|
296
|
+
toolName: typeof ev.toolName === 'string' ? ev.toolName : '',
|
|
297
|
+
label: friendlyToolLabel(ev.toolName),
|
|
298
|
+
description: ev.description ? truncate(redactSecrets(String(ev.description)), 160) : '',
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (ev.type === 'thinking_start') return { kind: 'thinking' };
|
|
303
|
+
|
|
304
|
+
if (ev.type === 'run_error' || ev.type === 'mod_error') {
|
|
305
|
+
const message = ev.error || ev.message || '';
|
|
306
|
+
return { kind: 'error', message: truncate(redactSecrets(String(message)), 400) };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (ev.type === 'run_end') {
|
|
310
|
+
const stopReason = ev.result && typeof ev.result === 'object' ? ev.result.stopReason : null;
|
|
311
|
+
return { kind: 'run_end', stopReason: typeof stopReason === 'string' ? stopReason : null };
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (IGNORED_EVENTS.has(ev.type)) return { kind: 'ignored' };
|
|
315
|
+
|
|
316
|
+
return { kind: 'unknown', raw: truncate(redactSecrets(ev.type), 80) };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---------------------------------------------------------------------------
|
|
320
|
+
// Exit codes
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Command Code's published exit-code contract.
|
|
325
|
+
*
|
|
326
|
+
* `partial` marks the codes that still produced usable output: a max-turns run
|
|
327
|
+
* returns the partial answer, so the adapter delivers it with a note instead of
|
|
328
|
+
* replacing it with an error.
|
|
329
|
+
*/
|
|
330
|
+
const EXIT_CODES = {
|
|
331
|
+
0: { kind: 'success', message: null },
|
|
332
|
+
1: { kind: 'cli_error', message: 'Command Code failed to complete the run. Check the daemon log for details, then retry.' },
|
|
333
|
+
3: { kind: 'auth_required', message: 'Command Code is not signed in. Run `command-code login`, or configure an API key, then retry.' },
|
|
334
|
+
4: { kind: 'permission_denied', message: 'Command Code was denied permission for an action it needed. Check the permission settings for this agent and retry.' },
|
|
335
|
+
5: { kind: 'rate_limited', message: 'Command Code hit a rate limit. Wait a moment, then retry.' },
|
|
336
|
+
6: { kind: 'network_error', message: 'Command Code could not reach its provider (network failure). Check connectivity, then retry.' },
|
|
337
|
+
7: { kind: 'server_error', message: 'The model provider returned a server error. This is usually temporary — retry shortly.' },
|
|
338
|
+
8: { kind: 'max_turns', message: 'The run hit its turn limit before finishing. The partial answer is above.', partial: true },
|
|
339
|
+
9: { kind: 'no_response', message: 'The model produced no response. Try rephrasing the request.' },
|
|
340
|
+
10: { kind: 'insufficient_credits', message: 'The Command Code account is out of credits. Top it up, or switch to a BYOK provider, then retry.' },
|
|
341
|
+
130: { kind: 'interrupted', message: 'The run was interrupted.' },
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Classify how a run ended.
|
|
346
|
+
*
|
|
347
|
+
* The exit code is the primary signal (a documented contract); the result
|
|
348
|
+
* line's `error` field only ever refines the MESSAGE, never the verdict. An
|
|
349
|
+
* undocumented code degrades to a generic failure rather than being reported
|
|
350
|
+
* as success.
|
|
351
|
+
*
|
|
352
|
+
* @param {object} o
|
|
353
|
+
* @param {number|null} o.code process exit code
|
|
354
|
+
* @param {string|null} [o.signal] terminating signal, if any
|
|
355
|
+
* @param {object|null} [o.result] the interpreted result frame, when one arrived
|
|
356
|
+
* @returns {{kind: string, ok: boolean, partial: boolean, userMessage: string|null}}
|
|
357
|
+
*/
|
|
358
|
+
function classifyCommandCodeExit({ code, signal = null, result = null } = {}) {
|
|
359
|
+
// A signal kill never reaches the exit-code table (code is null). Report it
|
|
360
|
+
// as an interruption: this is the stop-requested and watchdog-kill path.
|
|
361
|
+
if (signal) {
|
|
362
|
+
return { kind: 'interrupted', ok: false, partial: false, userMessage: EXIT_CODES[130].message };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const entry = EXIT_CODES[code];
|
|
366
|
+
if (!entry) {
|
|
367
|
+
return {
|
|
368
|
+
kind: 'cli_error',
|
|
369
|
+
ok: false,
|
|
370
|
+
partial: false,
|
|
371
|
+
userMessage: `Command Code exited unexpectedly (code ${code}). Check the daemon log, then retry.`,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (entry.kind === 'success') {
|
|
376
|
+
return { kind: 'success', ok: true, partial: false, userMessage: null };
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Prefer the CLI's own error text when it gave one — it is more specific than
|
|
380
|
+
// the generic per-code line, and it is already redacted by the interpreter.
|
|
381
|
+
const specific = result && result.error ? truncate(result.error, 400) : null;
|
|
382
|
+
return {
|
|
383
|
+
kind: entry.kind,
|
|
384
|
+
ok: false,
|
|
385
|
+
partial: !!entry.partial,
|
|
386
|
+
userMessage: specific || entry.message,
|
|
387
|
+
};
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
module.exports = {
|
|
391
|
+
COMMANDCODE_MIN_VERSION,
|
|
392
|
+
COMMANDCODE_TESTED_MAX_VERSION,
|
|
393
|
+
parseCommandCodeVersion,
|
|
394
|
+
compareVersions,
|
|
395
|
+
classifyCommandCodeVersion,
|
|
396
|
+
redactSecrets,
|
|
397
|
+
redactArgs,
|
|
398
|
+
truncate,
|
|
399
|
+
buildCommandCodeArgs,
|
|
400
|
+
parseFrame,
|
|
401
|
+
interpretCommandCodeFrame,
|
|
402
|
+
friendlyToolLabel,
|
|
403
|
+
classifyCommandCodeExit,
|
|
404
|
+
EXIT_CODES,
|
|
405
|
+
};
|