@ryuu-reinzz/ryuu-cli 0.0.0-stage → 0.3.1
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 +560 -0
- package/LICENSE +21 -0
- package/README.md +1213 -2
- package/package.json +45 -4
- package/src/agent/agent.js +1194 -0
- package/src/agent/approvals.js +76 -0
- package/src/agent/context.js +106 -0
- package/src/agent/planner.js +237 -0
- package/src/agent/worklog.js +318 -0
- package/src/cli/app.js +463 -0
- package/src/cli/commands.js +1150 -0
- package/src/cli/input.js +287 -0
- package/src/cli/markdown.js +527 -0
- package/src/cli/prompts.js +255 -0
- package/src/cli/renderer.js +2155 -0
- package/src/cli/settings-editor.js +280 -0
- package/src/cli/settings-tui.js +669 -0
- package/src/cli/tui.js +1737 -0
- package/src/cli/width.js +48 -0
- package/src/config/load.js +132 -0
- package/src/config/scaffold.js +50 -0
- package/src/config/settings-registry.js +495 -0
- package/src/config/settings.js +304 -0
- package/src/config/validate.js +451 -0
- package/src/index.js +449 -0
- package/src/providers/catalog.js +282 -0
- package/src/providers/index.js +84 -0
- package/src/providers/nvidia.js +645 -0
- package/src/providers/registry.js +104 -0
- package/src/services/clipboard.js +38 -0
- package/src/services/diff.js +153 -0
- package/src/services/doctor.js +126 -0
- package/src/services/logger.js +190 -0
- package/src/services/preferences.js +249 -0
- package/src/services/processes.js +345 -0
- package/src/services/provider-settings.js +226 -0
- package/src/services/sessions.js +279 -0
- package/src/services/undo.js +133 -0
- package/src/services/ushell.js +146 -0
- package/src/services/websearch/manager.js +616 -0
- package/src/services/websearch/mcp.js +233 -0
- package/src/services/websearch/normalize.js +284 -0
- package/src/services/websearch/providers.js +178 -0
- package/src/services/websearch/serper.js +263 -0
- package/src/services/workspace.js +589 -0
- package/src/tools/filesystem.js +304 -0
- package/src/tools/git.js +114 -0
- package/src/tools/index.js +48 -0
- package/src/tools/project.js +160 -0
- package/src/tools/registry.js +118 -0
- package/src/tools/search.js +210 -0
- package/src/tools/shell.js +279 -0
- package/src/tools/url.js +542 -0
- package/src/tools/web.js +67 -0
- package/src/utils/errors.js +160 -0
- package/src/utils/network.js +129 -0
- package/src/utils/redaction.js +73 -0
- package/src/utils/validation.js +370 -0
- package/src/utils/version.js +24 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
import { buildRawConfig, checkRuntime } from './config/load.js';
|
|
7
|
+
import { validateConfig } from './config/validate.js';
|
|
8
|
+
import { ConfigError, formatErrorBlock } from './utils/errors.js';
|
|
9
|
+
import { createApp } from './cli/app.js';
|
|
10
|
+
import { formatDoctor, runDoctor } from './services/doctor.js';
|
|
11
|
+
import { createActiveProvider } from './providers/index.js';
|
|
12
|
+
import { SettingsStore, settingsModelFor, settingsProviderId } from './config/settings.js';
|
|
13
|
+
import { buildSettingsRegistry } from './config/settings-registry.js';
|
|
14
|
+
import { buildSettingsSkeleton, ensureSettingsFile } from './config/scaffold.js';
|
|
15
|
+
import { buildProviders, parseExtraProviders } from './providers/catalog.js';
|
|
16
|
+
import { SettingsSession, runSettingsEditor } from './cli/settings-editor.js';
|
|
17
|
+
import { SettingsTui } from './cli/settings-tui.js';
|
|
18
|
+
import { applyPersistedModel, applyPersistedProvider, stateDirFor } from './services/preferences.js';
|
|
19
|
+
import { createLogger } from './services/logger.js';
|
|
20
|
+
import { Workspace } from './services/workspace.js';
|
|
21
|
+
import { readVersion } from './utils/version.js';
|
|
22
|
+
|
|
23
|
+
// The version lives in package.json; this module re-exports the reader so the
|
|
24
|
+
// CLI entry point and the tests keep one import site.
|
|
25
|
+
export { readVersion };
|
|
26
|
+
|
|
27
|
+
const HELP = `Ryuu Agent CLI — autonomous coding agent in your terminal
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
ryuu Start the interactive agent
|
|
31
|
+
ryuu "task" Run one task non-interactively
|
|
32
|
+
ryuu --task "task" Same, explicit flag
|
|
33
|
+
echo "task" | ryuu Read the task from stdin
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-h, --help Show this help
|
|
37
|
+
-v, --version Show the version
|
|
38
|
+
--doctor Run diagnostics (offline)
|
|
39
|
+
--live With --doctor: one real API request
|
|
40
|
+
--workspace <path> Workspace override for this run
|
|
41
|
+
--provider <id> Provider override for this run, e.g. tokenharbor
|
|
42
|
+
--model <id> Model override for this run
|
|
43
|
+
--max-steps <n> Optional step cap (0 = unlimited, default)
|
|
44
|
+
-y, --yes Auto-approve file writes (commands still ask)
|
|
45
|
+
--auto-approve Auto-approve ALL actions incl. commands (AI works without asking)
|
|
46
|
+
--read-only Deny all writes and command execution
|
|
47
|
+
--settings Edit the global settings file and exit
|
|
48
|
+
--settings --tui Full-screen settings editor (requires a TTY)
|
|
49
|
+
--settings --list Print the editable settings and exit
|
|
50
|
+
--no-color Disable ANSI colors
|
|
51
|
+
--plain Plain output (no spinner/colors)
|
|
52
|
+
--tui Full-screen UI: scrollable transcript, click/tap
|
|
53
|
+
blocks to expand, live composer (requires a TTY)
|
|
54
|
+
--debug Verbose logs and stack traces
|
|
55
|
+
|
|
56
|
+
Every setting lives in one file: ~/.config/ryuu/config.json (Windows:
|
|
57
|
+
%APPDATA%\\ryuu, macOS: ~/Library/Application Support/ryuu; RYUU_CONFIG_DIR
|
|
58
|
+
overrides the folder). CLI flags win for one run, and the .ryuu preferences file
|
|
59
|
+
of the current workspace keeps the last /provider and /model choice.
|
|
60
|
+
|
|
61
|
+
Commands inside the CLI: /help, /status, /worklog, /files, /tree, /tools,
|
|
62
|
+
/processes, /logs, /expand, /cancel, /history, /sessions, /diff, /undo, /config,
|
|
63
|
+
/doctor, /provider, /model, /addmodel, /delmodel, /cmodel, /custommodel,
|
|
64
|
+
/workspace, /clear, /memory, /resume, /bash, /exit.
|
|
65
|
+
A line starting with "!" runs a shell command yourself (same as /bash).
|
|
66
|
+
While a task runs, slash commands still work; a new prompt waits until it ends.
|
|
67
|
+
|
|
68
|
+
Documentation: README.md · Secrets: provider keys in config.json, written
|
|
69
|
+
owner-only and never printed. Run ryuu --settings to add them.
|
|
70
|
+
Providers: nvidia, tokenharbor; list them with /provider, switch with
|
|
71
|
+
/provider user <name>, or add your own with /provider add <name> <url> [apiKey].
|
|
72
|
+
`;
|
|
73
|
+
|
|
74
|
+
const SETTINGS_HELP = `Ryuu Agent CLI — global settings
|
|
75
|
+
|
|
76
|
+
Usage:
|
|
77
|
+
ryuu --settings Numbered line editor (saves every change immediately)
|
|
78
|
+
ryuu --settings --tui Full-screen editor (requires an interactive terminal)
|
|
79
|
+
ryuu --settings --list Print the editable settings and exit
|
|
80
|
+
ryuu --settings --help Show this help
|
|
81
|
+
|
|
82
|
+
Editor commands:
|
|
83
|
+
<number> <value> Save a value (values may contain spaces)
|
|
84
|
+
/list Redraw the numbered list
|
|
85
|
+
/show <number> Show a setting, its description and constraints
|
|
86
|
+
/reset <number> Reset one setting to its default (asks first)
|
|
87
|
+
/help Show the editor commands
|
|
88
|
+
/exit Leave the editor (each change is already saved)
|
|
89
|
+
|
|
90
|
+
Secrets are always masked in the list and are never shown back.
|
|
91
|
+
The file is created with every key empty on first run (or first save) and
|
|
92
|
+
written atomically with owner-only permissions; a malformed file is reported
|
|
93
|
+
and never overwritten.
|
|
94
|
+
|
|
95
|
+
Precedence: CLI flags > workspace .ryuu preferences > config.json > built-in
|
|
96
|
+
defaults. Environment variables are not read (RYUU_CONFIG_DIR only sets where
|
|
97
|
+
this file lives).
|
|
98
|
+
`;
|
|
99
|
+
|
|
100
|
+
/** Parse argv into options plus a positional task. */
|
|
101
|
+
export function parseArgs(argv) {
|
|
102
|
+
const options = {
|
|
103
|
+
task: null,
|
|
104
|
+
doctor: false,
|
|
105
|
+
live: false,
|
|
106
|
+
help: false,
|
|
107
|
+
version: false,
|
|
108
|
+
tui: false,
|
|
109
|
+
settings: false,
|
|
110
|
+
list: false,
|
|
111
|
+
};
|
|
112
|
+
const positional = [];
|
|
113
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
114
|
+
const arg = argv[index];
|
|
115
|
+
const [flag, inlineValue] = arg.startsWith('--') && arg.includes('=') ? arg.split(/=(.*)/s, 2) : [arg, undefined];
|
|
116
|
+
const value = () => {
|
|
117
|
+
if (inlineValue !== undefined) return inlineValue;
|
|
118
|
+
index += 1;
|
|
119
|
+
return argv[index];
|
|
120
|
+
};
|
|
121
|
+
switch (flag) {
|
|
122
|
+
case '-h':
|
|
123
|
+
case '--help':
|
|
124
|
+
options.help = true;
|
|
125
|
+
break;
|
|
126
|
+
case '-v':
|
|
127
|
+
case '--version':
|
|
128
|
+
options.version = true;
|
|
129
|
+
break;
|
|
130
|
+
case '--doctor':
|
|
131
|
+
options.doctor = true;
|
|
132
|
+
break;
|
|
133
|
+
case '--live':
|
|
134
|
+
options.live = true;
|
|
135
|
+
break;
|
|
136
|
+
case '--task':
|
|
137
|
+
options.task = value();
|
|
138
|
+
break;
|
|
139
|
+
case '--workspace':
|
|
140
|
+
options.workspace = value();
|
|
141
|
+
break;
|
|
142
|
+
case '--model':
|
|
143
|
+
options.model = value();
|
|
144
|
+
break;
|
|
145
|
+
case '--provider':
|
|
146
|
+
options.provider = value();
|
|
147
|
+
break;
|
|
148
|
+
case '--max-steps':
|
|
149
|
+
options.maxSteps = value();
|
|
150
|
+
break;
|
|
151
|
+
case '-y':
|
|
152
|
+
case '--yes':
|
|
153
|
+
options.yes = true;
|
|
154
|
+
break;
|
|
155
|
+
case '--auto-approve':
|
|
156
|
+
options.autoApprove = true;
|
|
157
|
+
break;
|
|
158
|
+
case '--read-only':
|
|
159
|
+
options.readOnly = true;
|
|
160
|
+
break;
|
|
161
|
+
case '--no-color':
|
|
162
|
+
options.color = false;
|
|
163
|
+
break;
|
|
164
|
+
case '--plain':
|
|
165
|
+
options.plain = true;
|
|
166
|
+
break;
|
|
167
|
+
case '--tui':
|
|
168
|
+
options.tui = true;
|
|
169
|
+
break;
|
|
170
|
+
case '--settings':
|
|
171
|
+
options.settings = true;
|
|
172
|
+
break;
|
|
173
|
+
case '--list':
|
|
174
|
+
options.list = true;
|
|
175
|
+
break;
|
|
176
|
+
case '--debug':
|
|
177
|
+
options.debug = true;
|
|
178
|
+
break;
|
|
179
|
+
case '--':
|
|
180
|
+
positional.push(...argv.slice(index + 1));
|
|
181
|
+
index = argv.length;
|
|
182
|
+
break;
|
|
183
|
+
default:
|
|
184
|
+
if (arg.startsWith('-')) {
|
|
185
|
+
throw new ConfigError(`Unknown option: ${arg}. Run ryuu --help.`);
|
|
186
|
+
}
|
|
187
|
+
positional.push(arg);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!options.task && positional.length > 0) options.task = positional.join(' ');
|
|
191
|
+
return options;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async function readStdin() {
|
|
195
|
+
if (process.stdin.isTTY) return '';
|
|
196
|
+
let text = '';
|
|
197
|
+
for await (const chunk of process.stdin) text += chunk;
|
|
198
|
+
return text.trim();
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function applyOverrides(raw, options) {
|
|
202
|
+
if (options.workspace) raw.workspace = options.workspace;
|
|
203
|
+
if (options.provider) raw.provider = options.provider;
|
|
204
|
+
if (options.model) raw.model = options.model;
|
|
205
|
+
if (options.maxSteps !== undefined) raw.maxSteps = options.maxSteps;
|
|
206
|
+
if (options.readOnly) raw.approvalPolicy = 'read-only';
|
|
207
|
+
if (options.debug) {
|
|
208
|
+
raw.debug = 'true';
|
|
209
|
+
raw.logLevel = 'debug';
|
|
210
|
+
}
|
|
211
|
+
return raw;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Providers available to the settings editor (built-ins plus extraProviders). */
|
|
215
|
+
function settingsProviders(settings) {
|
|
216
|
+
const raw = buildRawConfig(settings);
|
|
217
|
+
let extras = [];
|
|
218
|
+
try {
|
|
219
|
+
extras = parseExtraProviders(raw.extraProviders, []);
|
|
220
|
+
} catch {
|
|
221
|
+
extras = [];
|
|
222
|
+
}
|
|
223
|
+
return buildProviders({ credentials: raw.providerCredentials, extras });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* `ryuu --settings [--tui|--list]`.
|
|
228
|
+
*
|
|
229
|
+
* Only the settings file and the provider catalog are touched: no agent, no
|
|
230
|
+
* tools, no provider client and no model request are created here.
|
|
231
|
+
*/
|
|
232
|
+
async function runSettingsMode(options) {
|
|
233
|
+
const store = new SettingsStore();
|
|
234
|
+
const { data, exists } = await store.read();
|
|
235
|
+
const makeRegistry = (current) =>
|
|
236
|
+
buildSettingsRegistry({ providers: settingsProviders(current), settings: current });
|
|
237
|
+
|
|
238
|
+
if (options.list) {
|
|
239
|
+
const session = new SettingsSession({ store, makeRegistry, data, file: store.file });
|
|
240
|
+
process.stdout.write(`${session.listLines().join('\n')}\n`);
|
|
241
|
+
return 0;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// A missing file is initialized before the editor opens, so an immediate exit
|
|
245
|
+
// still leaves a private (0600) settings file behind instead of nothing. The
|
|
246
|
+
// scaffold lists every editable key with an empty value.
|
|
247
|
+
if (!exists) await store.save(buildSettingsSkeleton());
|
|
248
|
+
|
|
249
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
250
|
+
if (!interactive) {
|
|
251
|
+
const needed = options.tui
|
|
252
|
+
? '--settings --tui needs an interactive terminal.'
|
|
253
|
+
: '--settings needs an interactive terminal.';
|
|
254
|
+
process.stderr.write(`[error] ${needed} Use \`ryuu --settings --list\` for a non-interactive listing.\n`);
|
|
255
|
+
return 2;
|
|
256
|
+
}
|
|
257
|
+
if (options.tui) {
|
|
258
|
+
const tui = new SettingsTui({
|
|
259
|
+
input: process.stdin,
|
|
260
|
+
output: process.stdout,
|
|
261
|
+
store,
|
|
262
|
+
makeRegistry,
|
|
263
|
+
data,
|
|
264
|
+
version: readVersion(),
|
|
265
|
+
color: options.color ?? null,
|
|
266
|
+
});
|
|
267
|
+
try {
|
|
268
|
+
await tui.run();
|
|
269
|
+
} finally {
|
|
270
|
+
tui.close();
|
|
271
|
+
}
|
|
272
|
+
return 0;
|
|
273
|
+
}
|
|
274
|
+
await runSettingsEditor({ store, makeRegistry, data });
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
279
|
+
// Wipe the terminal when the CLI is invoked so the banner starts on a clean
|
|
280
|
+
// screen. console.clear() is a no-op when stdout is not a TTY (pipes, CI).
|
|
281
|
+
console.clear();
|
|
282
|
+
let options;
|
|
283
|
+
try {
|
|
284
|
+
options = parseArgs(argv);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
process.stderr.write(`${error.message}\n`);
|
|
287
|
+
return 2;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (options.help) {
|
|
291
|
+
process.stdout.write(options.settings ? SETTINGS_HELP : HELP);
|
|
292
|
+
return 0;
|
|
293
|
+
}
|
|
294
|
+
if (options.version) {
|
|
295
|
+
process.stdout.write(`ryuu-agent-cli ${readVersion()}\n`);
|
|
296
|
+
return 0;
|
|
297
|
+
}
|
|
298
|
+
if (options.list && !options.settings) {
|
|
299
|
+
process.stderr.write('--list is only valid with --settings. Run ryuu --settings --list.\n');
|
|
300
|
+
return 2;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
checkRuntime();
|
|
305
|
+
} catch (error) {
|
|
306
|
+
process.stderr.write(`${formatErrorBlock(error)}\n`);
|
|
307
|
+
return 2;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Settings mode never builds the agent, tools or a provider: it only reads
|
|
311
|
+
// and writes the global settings file.
|
|
312
|
+
if (options.settings) {
|
|
313
|
+
try {
|
|
314
|
+
return await runSettingsMode(options);
|
|
315
|
+
} catch (error) {
|
|
316
|
+
process.stderr.write(`${formatErrorBlock(error, { debug: options.debug })}\n`);
|
|
317
|
+
return 2;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// config.json is the only configuration source. A missing file is created
|
|
322
|
+
// with every key present but empty, so a new install has something to edit; a
|
|
323
|
+
// malformed file warns and is left untouched, and the resulting "no API key"
|
|
324
|
+
// error points at --settings.
|
|
325
|
+
let settings = null;
|
|
326
|
+
try {
|
|
327
|
+
settings = (await ensureSettingsFile()).data;
|
|
328
|
+
} catch (error) {
|
|
329
|
+
process.stderr.write(`[warn] ${error.message}\n`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
let config;
|
|
333
|
+
try {
|
|
334
|
+
const raw = applyOverrides(buildRawConfig(settings ?? {}, { cwd: process.cwd() }), options);
|
|
335
|
+
// A provider/model picked with /provider and /model is persisted under
|
|
336
|
+
// <workspace>/.ryuu and wins over config.json on later runs; explicit
|
|
337
|
+
// --provider/--model flags win for a single run and never rewrite the
|
|
338
|
+
// saved choice. config.json is the fallback before the catalog defaults.
|
|
339
|
+
const storedProvider = settings ? settingsProviderId(settings) : null;
|
|
340
|
+
raw.provider = options.provider || null;
|
|
341
|
+
raw.model = null;
|
|
342
|
+
const stateDir = stateDirFor(raw.workspace);
|
|
343
|
+
await applyPersistedProvider(raw, { flagProvider: options.provider, stateDir });
|
|
344
|
+
if (!raw.provider) raw.provider = storedProvider;
|
|
345
|
+
await applyPersistedModel(raw, {
|
|
346
|
+
flagModel: options.model,
|
|
347
|
+
stateDir,
|
|
348
|
+
providerId: raw.provider || null,
|
|
349
|
+
});
|
|
350
|
+
if (!raw.model && settings && raw.provider) raw.model = settingsModelFor(settings, raw.provider);
|
|
351
|
+
const result = validateConfig(raw, { requireApiKey: !options.doctor, cwd: process.cwd() });
|
|
352
|
+
config = result.config;
|
|
353
|
+
for (const warning of result.warnings) process.stderr.write(`[warn] ${warning}\n`);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
process.stderr.write(`${formatErrorBlock(error, { debug: options.debug })}\n`);
|
|
356
|
+
return 2;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (options.doctor) {
|
|
360
|
+
const logger = createLogger(config, { stderr: process.stderr });
|
|
361
|
+
let workspace = null;
|
|
362
|
+
try {
|
|
363
|
+
workspace = new Workspace(config.workspace, { logger });
|
|
364
|
+
const provider = config.apiKey ? createActiveProvider(config, { logger }) : null;
|
|
365
|
+
const report = await runDoctor({ config, workspace, provider, live: options.live, logger });
|
|
366
|
+
process.stdout.write(`${formatDoctor(report)}\n`);
|
|
367
|
+
return report.ok ? 0 : 1;
|
|
368
|
+
} catch (error) {
|
|
369
|
+
process.stderr.write(`${formatErrorBlock(error, { debug: options.debug })}\n`);
|
|
370
|
+
return 1;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
const task = options.task || (await readStdin());
|
|
375
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY) && !options.task && !task;
|
|
376
|
+
|
|
377
|
+
let app;
|
|
378
|
+
try {
|
|
379
|
+
app = await createApp({
|
|
380
|
+
config,
|
|
381
|
+
cliOptions: {
|
|
382
|
+
interactive,
|
|
383
|
+
yes: options.yes,
|
|
384
|
+
autoApprove: options.autoApprove,
|
|
385
|
+
plain: options.plain || !interactive,
|
|
386
|
+
color: options.color,
|
|
387
|
+
tui: options.tui,
|
|
388
|
+
},
|
|
389
|
+
version: readVersion(),
|
|
390
|
+
});
|
|
391
|
+
} catch (error) {
|
|
392
|
+
process.stderr.write(`${formatErrorBlock(error, { debug: options.debug })}\n`);
|
|
393
|
+
return 2;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (interactive) {
|
|
397
|
+
await app.runInteractive();
|
|
398
|
+
// Belt and braces: whatever the UI left attached, a paused stdin lets the
|
|
399
|
+
// process end as soon as /exit returns, instead of waiting for a keypress.
|
|
400
|
+
process.stdin.pause?.();
|
|
401
|
+
return 0;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (!task) {
|
|
405
|
+
process.stderr.write('No task provided in non-interactive mode.\n\n');
|
|
406
|
+
process.stderr.write(HELP);
|
|
407
|
+
await app.close();
|
|
408
|
+
return 2;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
app.banner();
|
|
412
|
+
app.renderer.info(
|
|
413
|
+
options.autoApprove
|
|
414
|
+
? 'Non-interactive mode: --auto-approve is active; all writes, commands and destructive actions are auto-approved. Shell execution is NOT a sandbox.'
|
|
415
|
+
: "Non-interactive mode: approvals are denied unless --yes (writes) or --auto-approve (all actions) is used.",
|
|
416
|
+
);
|
|
417
|
+
try {
|
|
418
|
+
const summary = await app.runTask(task);
|
|
419
|
+
await app.close();
|
|
420
|
+
if (summary.status === 'completed' || summary.status === 'step-limit') return summary.status === 'completed' ? 0 : 1;
|
|
421
|
+
if (summary.status === 'cancelled') return 130;
|
|
422
|
+
return 1;
|
|
423
|
+
} catch (error) {
|
|
424
|
+
process.stderr.write(`${formatErrorBlock(error, { debug: options.debug })}\n`);
|
|
425
|
+
await app.close();
|
|
426
|
+
return 3;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function isInvokedDirectly() {
|
|
431
|
+
if (!process.argv[1]) return false;
|
|
432
|
+
try {
|
|
433
|
+
return fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
434
|
+
} catch {
|
|
435
|
+
return path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const invokedDirectly = isInvokedDirectly();
|
|
440
|
+
if (invokedDirectly) {
|
|
441
|
+
main()
|
|
442
|
+
.then((code) => {
|
|
443
|
+
process.exitCode = code;
|
|
444
|
+
})
|
|
445
|
+
.catch((error) => {
|
|
446
|
+
process.stderr.write(`${formatErrorBlock(error, { debug: true })}\n`);
|
|
447
|
+
process.exitCode = 1;
|
|
448
|
+
});
|
|
449
|
+
}
|