@link-assistant/agent 0.4.0 → 0.5.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/README.md +3 -0
- package/package.json +1 -1
- package/src/cli/continuous-mode.js +450 -0
- package/src/index.js +393 -208
package/README.md
CHANGED
|
@@ -218,6 +218,9 @@ Stdin Mode Options:
|
|
|
218
218
|
--no-interactive Only accept JSON input
|
|
219
219
|
--auto-merge-queued-messages Merge rapidly arriving lines (default: true)
|
|
220
220
|
--no-auto-merge-queued-messages Treat each line as separate message
|
|
221
|
+
--always-accept-stdin Keep accepting input after agent finishes (default: true)
|
|
222
|
+
--no-always-accept-stdin Single-message mode - exit after first response
|
|
223
|
+
--compact-json Output compact JSON for program-to-program use
|
|
221
224
|
|
|
222
225
|
--help Show help
|
|
223
226
|
--version Show version number
|
package/package.json
CHANGED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Continuous stdin mode for the Agent CLI.
|
|
3
|
+
* Keeps the session alive and processes messages as they arrive.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Server } from '../server/server.ts';
|
|
7
|
+
import { Instance } from '../project/instance.ts';
|
|
8
|
+
import { Bus } from '../bus/index.ts';
|
|
9
|
+
import { Session } from '../session/index.ts';
|
|
10
|
+
import { SessionPrompt } from '../session/prompt.ts';
|
|
11
|
+
import { createEventHandler } from '../json-standard/index.ts';
|
|
12
|
+
import { createContinuousStdinReader } from './input-queue.js';
|
|
13
|
+
|
|
14
|
+
// Shared error tracking
|
|
15
|
+
let hasError = false;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Set error state
|
|
19
|
+
* @param {boolean} value - Error state value
|
|
20
|
+
*/
|
|
21
|
+
export function setHasError(value) {
|
|
22
|
+
hasError = value;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get error state
|
|
27
|
+
* @returns {boolean}
|
|
28
|
+
*/
|
|
29
|
+
export function getHasError() {
|
|
30
|
+
return hasError;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Output JSON status message to stderr
|
|
35
|
+
* @param {object} status - Status object to output
|
|
36
|
+
* @param {boolean} compact - If true, output compact JSON (single line)
|
|
37
|
+
*/
|
|
38
|
+
function outputStatus(status, compact = false) {
|
|
39
|
+
const json = compact
|
|
40
|
+
? JSON.stringify(status)
|
|
41
|
+
: JSON.stringify(status, null, 2);
|
|
42
|
+
console.error(json);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Run server mode with continuous stdin input
|
|
47
|
+
* Keeps the session alive and processes messages as they arrive
|
|
48
|
+
*/
|
|
49
|
+
export async function runContinuousServerMode(
|
|
50
|
+
argv,
|
|
51
|
+
providerID,
|
|
52
|
+
modelID,
|
|
53
|
+
systemMessage,
|
|
54
|
+
appendSystemMessage,
|
|
55
|
+
jsonStandard
|
|
56
|
+
) {
|
|
57
|
+
const compactJson = argv['compact-json'] === true;
|
|
58
|
+
const isInteractive = argv.interactive !== false;
|
|
59
|
+
const autoMerge = argv['auto-merge-queued-messages'] !== false;
|
|
60
|
+
|
|
61
|
+
// Start server like OpenCode does
|
|
62
|
+
const server = Server.listen({ port: 0, hostname: '127.0.0.1' });
|
|
63
|
+
let unsub = null;
|
|
64
|
+
let stdinReader = null;
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
// Create a session
|
|
68
|
+
const createRes = await fetch(
|
|
69
|
+
`http://${server.hostname}:${server.port}/session`,
|
|
70
|
+
{
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({}),
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
const session = await createRes.json();
|
|
77
|
+
const sessionID = session.id;
|
|
78
|
+
|
|
79
|
+
if (!sessionID) {
|
|
80
|
+
throw new Error('Failed to create session');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Create event handler for the selected JSON standard
|
|
84
|
+
const eventHandler = createEventHandler(jsonStandard, sessionID);
|
|
85
|
+
|
|
86
|
+
// Track if we're currently processing a message
|
|
87
|
+
let isProcessing = false;
|
|
88
|
+
const pendingMessages = [];
|
|
89
|
+
|
|
90
|
+
// Process messages from the queue
|
|
91
|
+
const processMessage = async (message) => {
|
|
92
|
+
if (isProcessing) {
|
|
93
|
+
pendingMessages.push(message);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
isProcessing = true;
|
|
98
|
+
const messageText = message.message || 'hi';
|
|
99
|
+
const parts = [{ type: 'text', text: messageText }];
|
|
100
|
+
|
|
101
|
+
// Create a promise to wait for this message to complete
|
|
102
|
+
const messagePromise = new Promise((resolve) => {
|
|
103
|
+
const checkIdle = Bus.subscribeAll((event) => {
|
|
104
|
+
if (
|
|
105
|
+
event.type === 'session.idle' &&
|
|
106
|
+
event.properties.sessionID === sessionID
|
|
107
|
+
) {
|
|
108
|
+
checkIdle();
|
|
109
|
+
resolve();
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Send message
|
|
115
|
+
fetch(
|
|
116
|
+
`http://${server.hostname}:${server.port}/session/${sessionID}/message`,
|
|
117
|
+
{
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/json' },
|
|
120
|
+
body: JSON.stringify({
|
|
121
|
+
parts,
|
|
122
|
+
model: { providerID, modelID },
|
|
123
|
+
system: systemMessage,
|
|
124
|
+
appendSystem: appendSystemMessage,
|
|
125
|
+
}),
|
|
126
|
+
}
|
|
127
|
+
).catch((error) => {
|
|
128
|
+
hasError = true;
|
|
129
|
+
eventHandler.output({
|
|
130
|
+
type: 'error',
|
|
131
|
+
timestamp: Date.now(),
|
|
132
|
+
sessionID,
|
|
133
|
+
error: error instanceof Error ? error.message : String(error),
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
await messagePromise;
|
|
138
|
+
isProcessing = false;
|
|
139
|
+
|
|
140
|
+
// Process next pending message if any
|
|
141
|
+
if (pendingMessages.length > 0) {
|
|
142
|
+
const nextMessage = pendingMessages.shift();
|
|
143
|
+
processMessage(nextMessage);
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Subscribe to all bus events and output in selected format
|
|
148
|
+
unsub = Bus.subscribeAll((event) => {
|
|
149
|
+
if (event.type === 'message.part.updated') {
|
|
150
|
+
const part = event.properties.part;
|
|
151
|
+
if (part.sessionID !== sessionID) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (part.type === 'step-start') {
|
|
156
|
+
eventHandler.output({
|
|
157
|
+
type: 'step_start',
|
|
158
|
+
timestamp: Date.now(),
|
|
159
|
+
sessionID,
|
|
160
|
+
part,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (part.type === 'step-finish') {
|
|
165
|
+
eventHandler.output({
|
|
166
|
+
type: 'step_finish',
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
sessionID,
|
|
169
|
+
part,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (part.type === 'text' && part.time?.end) {
|
|
174
|
+
eventHandler.output({
|
|
175
|
+
type: 'text',
|
|
176
|
+
timestamp: Date.now(),
|
|
177
|
+
sessionID,
|
|
178
|
+
part,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (part.type === 'tool' && part.state.status === 'completed') {
|
|
183
|
+
eventHandler.output({
|
|
184
|
+
type: 'tool_use',
|
|
185
|
+
timestamp: Date.now(),
|
|
186
|
+
sessionID,
|
|
187
|
+
part,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (event.type === 'session.error') {
|
|
193
|
+
const props = event.properties;
|
|
194
|
+
if (props.sessionID !== sessionID || !props.error) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
hasError = true;
|
|
198
|
+
eventHandler.output({
|
|
199
|
+
type: 'error',
|
|
200
|
+
timestamp: Date.now(),
|
|
201
|
+
sessionID,
|
|
202
|
+
error: props.error,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Create continuous stdin reader
|
|
208
|
+
stdinReader = createContinuousStdinReader({
|
|
209
|
+
interactive: isInteractive,
|
|
210
|
+
autoMerge,
|
|
211
|
+
onMessage: (message) => {
|
|
212
|
+
processMessage(message);
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
// Wait for stdin to end (EOF or close)
|
|
217
|
+
await new Promise((resolve) => {
|
|
218
|
+
const checkRunning = setInterval(() => {
|
|
219
|
+
if (!stdinReader.isRunning()) {
|
|
220
|
+
clearInterval(checkRunning);
|
|
221
|
+
// Wait for any pending messages to complete
|
|
222
|
+
const waitForPending = () => {
|
|
223
|
+
if (!isProcessing && pendingMessages.length === 0) {
|
|
224
|
+
resolve();
|
|
225
|
+
} else {
|
|
226
|
+
setTimeout(waitForPending, 100);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
waitForPending();
|
|
230
|
+
}
|
|
231
|
+
}, 100);
|
|
232
|
+
|
|
233
|
+
// Also handle SIGINT
|
|
234
|
+
process.on('SIGINT', () => {
|
|
235
|
+
outputStatus(
|
|
236
|
+
{
|
|
237
|
+
type: 'status',
|
|
238
|
+
message: 'Received SIGINT. Shutting down...',
|
|
239
|
+
},
|
|
240
|
+
compactJson
|
|
241
|
+
);
|
|
242
|
+
clearInterval(checkRunning);
|
|
243
|
+
resolve();
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
} finally {
|
|
247
|
+
if (stdinReader) {
|
|
248
|
+
stdinReader.stop();
|
|
249
|
+
}
|
|
250
|
+
if (unsub) {
|
|
251
|
+
unsub();
|
|
252
|
+
}
|
|
253
|
+
server.stop();
|
|
254
|
+
await Instance.dispose();
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Run direct mode with continuous stdin input
|
|
260
|
+
* Keeps the session alive and processes messages as they arrive
|
|
261
|
+
*/
|
|
262
|
+
export async function runContinuousDirectMode(
|
|
263
|
+
argv,
|
|
264
|
+
providerID,
|
|
265
|
+
modelID,
|
|
266
|
+
systemMessage,
|
|
267
|
+
appendSystemMessage,
|
|
268
|
+
jsonStandard
|
|
269
|
+
) {
|
|
270
|
+
const compactJson = argv['compact-json'] === true;
|
|
271
|
+
const isInteractive = argv.interactive !== false;
|
|
272
|
+
const autoMerge = argv['auto-merge-queued-messages'] !== false;
|
|
273
|
+
|
|
274
|
+
let unsub = null;
|
|
275
|
+
let stdinReader = null;
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
// Create a session directly
|
|
279
|
+
const session = await Session.createNext({
|
|
280
|
+
directory: process.cwd(),
|
|
281
|
+
});
|
|
282
|
+
const sessionID = session.id;
|
|
283
|
+
|
|
284
|
+
// Create event handler for the selected JSON standard
|
|
285
|
+
const eventHandler = createEventHandler(jsonStandard, sessionID);
|
|
286
|
+
|
|
287
|
+
// Track if we're currently processing a message
|
|
288
|
+
let isProcessing = false;
|
|
289
|
+
const pendingMessages = [];
|
|
290
|
+
|
|
291
|
+
// Process messages from the queue
|
|
292
|
+
const processMessage = async (message) => {
|
|
293
|
+
if (isProcessing) {
|
|
294
|
+
pendingMessages.push(message);
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
isProcessing = true;
|
|
299
|
+
const messageText = message.message || 'hi';
|
|
300
|
+
const parts = [{ type: 'text', text: messageText }];
|
|
301
|
+
|
|
302
|
+
// Create a promise to wait for this message to complete
|
|
303
|
+
const messagePromise = new Promise((resolve) => {
|
|
304
|
+
const checkIdle = Bus.subscribeAll((event) => {
|
|
305
|
+
if (
|
|
306
|
+
event.type === 'session.idle' &&
|
|
307
|
+
event.properties.sessionID === sessionID
|
|
308
|
+
) {
|
|
309
|
+
checkIdle();
|
|
310
|
+
resolve();
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Send message directly
|
|
316
|
+
SessionPrompt.prompt({
|
|
317
|
+
sessionID,
|
|
318
|
+
parts,
|
|
319
|
+
model: { providerID, modelID },
|
|
320
|
+
system: systemMessage,
|
|
321
|
+
appendSystem: appendSystemMessage,
|
|
322
|
+
}).catch((error) => {
|
|
323
|
+
hasError = true;
|
|
324
|
+
eventHandler.output({
|
|
325
|
+
type: 'error',
|
|
326
|
+
timestamp: Date.now(),
|
|
327
|
+
sessionID,
|
|
328
|
+
error: error instanceof Error ? error.message : String(error),
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
await messagePromise;
|
|
333
|
+
isProcessing = false;
|
|
334
|
+
|
|
335
|
+
// Process next pending message if any
|
|
336
|
+
if (pendingMessages.length > 0) {
|
|
337
|
+
const nextMessage = pendingMessages.shift();
|
|
338
|
+
processMessage(nextMessage);
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
// Subscribe to all bus events and output in selected format
|
|
343
|
+
unsub = Bus.subscribeAll((event) => {
|
|
344
|
+
if (event.type === 'message.part.updated') {
|
|
345
|
+
const part = event.properties.part;
|
|
346
|
+
if (part.sessionID !== sessionID) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (part.type === 'step-start') {
|
|
351
|
+
eventHandler.output({
|
|
352
|
+
type: 'step_start',
|
|
353
|
+
timestamp: Date.now(),
|
|
354
|
+
sessionID,
|
|
355
|
+
part,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (part.type === 'step-finish') {
|
|
360
|
+
eventHandler.output({
|
|
361
|
+
type: 'step_finish',
|
|
362
|
+
timestamp: Date.now(),
|
|
363
|
+
sessionID,
|
|
364
|
+
part,
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (part.type === 'text' && part.time?.end) {
|
|
369
|
+
eventHandler.output({
|
|
370
|
+
type: 'text',
|
|
371
|
+
timestamp: Date.now(),
|
|
372
|
+
sessionID,
|
|
373
|
+
part,
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (part.type === 'tool' && part.state.status === 'completed') {
|
|
378
|
+
eventHandler.output({
|
|
379
|
+
type: 'tool_use',
|
|
380
|
+
timestamp: Date.now(),
|
|
381
|
+
sessionID,
|
|
382
|
+
part,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (event.type === 'session.error') {
|
|
388
|
+
const props = event.properties;
|
|
389
|
+
if (props.sessionID !== sessionID || !props.error) {
|
|
390
|
+
return;
|
|
391
|
+
}
|
|
392
|
+
hasError = true;
|
|
393
|
+
eventHandler.output({
|
|
394
|
+
type: 'error',
|
|
395
|
+
timestamp: Date.now(),
|
|
396
|
+
sessionID,
|
|
397
|
+
error: props.error,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Create continuous stdin reader
|
|
403
|
+
stdinReader = createContinuousStdinReader({
|
|
404
|
+
interactive: isInteractive,
|
|
405
|
+
autoMerge,
|
|
406
|
+
onMessage: (message) => {
|
|
407
|
+
processMessage(message);
|
|
408
|
+
},
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
// Wait for stdin to end (EOF or close)
|
|
412
|
+
await new Promise((resolve) => {
|
|
413
|
+
const checkRunning = setInterval(() => {
|
|
414
|
+
if (!stdinReader.isRunning()) {
|
|
415
|
+
clearInterval(checkRunning);
|
|
416
|
+
// Wait for any pending messages to complete
|
|
417
|
+
const waitForPending = () => {
|
|
418
|
+
if (!isProcessing && pendingMessages.length === 0) {
|
|
419
|
+
resolve();
|
|
420
|
+
} else {
|
|
421
|
+
setTimeout(waitForPending, 100);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
waitForPending();
|
|
425
|
+
}
|
|
426
|
+
}, 100);
|
|
427
|
+
|
|
428
|
+
// Also handle SIGINT
|
|
429
|
+
process.on('SIGINT', () => {
|
|
430
|
+
outputStatus(
|
|
431
|
+
{
|
|
432
|
+
type: 'status',
|
|
433
|
+
message: 'Received SIGINT. Shutting down...',
|
|
434
|
+
},
|
|
435
|
+
compactJson
|
|
436
|
+
);
|
|
437
|
+
clearInterval(checkRunning);
|
|
438
|
+
resolve();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
} finally {
|
|
442
|
+
if (stdinReader) {
|
|
443
|
+
stdinReader.stop();
|
|
444
|
+
}
|
|
445
|
+
if (unsub) {
|
|
446
|
+
unsub();
|
|
447
|
+
}
|
|
448
|
+
await Instance.dispose();
|
|
449
|
+
}
|
|
450
|
+
}
|
package/src/index.js
CHANGED
|
@@ -18,6 +18,10 @@ import { AuthCommand } from './cli/cmd/auth.ts';
|
|
|
18
18
|
import { Flag } from './flag/flag.ts';
|
|
19
19
|
import { FormatError } from './cli/error.ts';
|
|
20
20
|
import { UI } from './cli/ui.ts';
|
|
21
|
+
import {
|
|
22
|
+
runContinuousServerMode,
|
|
23
|
+
runContinuousDirectMode,
|
|
24
|
+
} from './cli/continuous-mode.js';
|
|
21
25
|
import { createRequire } from 'module';
|
|
22
26
|
import { readFileSync } from 'fs';
|
|
23
27
|
import { dirname, join } from 'path';
|
|
@@ -132,30 +136,21 @@ function readStdinWithTimeout(timeout = null) {
|
|
|
132
136
|
* Output JSON status message to stderr
|
|
133
137
|
* This prevents the status message from interfering with JSON output parsing
|
|
134
138
|
* @param {object} status - Status object to output
|
|
139
|
+
* @param {boolean} compact - If true, output compact JSON (single line)
|
|
135
140
|
*/
|
|
136
|
-
function outputStatus(status) {
|
|
137
|
-
|
|
141
|
+
function outputStatus(status, compact = false) {
|
|
142
|
+
const json = compact
|
|
143
|
+
? JSON.stringify(status)
|
|
144
|
+
: JSON.stringify(status, null, 2);
|
|
145
|
+
console.error(json);
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
console.error(`Agent version: ${pkg.version}`);
|
|
147
|
-
console.error(`Command: ${process.argv.join(' ')}`);
|
|
148
|
-
console.error(`Working directory: ${process.cwd()}`);
|
|
149
|
-
console.error(`Script path: ${import.meta.path}`);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Log dry-run mode if enabled
|
|
153
|
-
if (Flag.OPENCODE_DRY_RUN) {
|
|
154
|
-
console.error(
|
|
155
|
-
`[DRY RUN MODE] No actual API calls or package installations will be made`
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Parse model configuration from argv
|
|
150
|
+
* @param {object} argv - Command line arguments
|
|
151
|
+
* @returns {object} - { providerID, modelID }
|
|
152
|
+
*/
|
|
153
|
+
async function parseModelConfig(argv) {
|
|
159
154
|
// Parse model argument (handle model IDs with slashes like groq/qwen/qwen3-32b)
|
|
160
155
|
const modelParts = argv.model.split('/');
|
|
161
156
|
let providerID = modelParts[0] || 'opencode';
|
|
@@ -170,13 +165,15 @@ async function runAgentMode(argv, request) {
|
|
|
170
165
|
const creds = await ClaudeOAuth.getCredentials();
|
|
171
166
|
|
|
172
167
|
if (!creds?.accessToken) {
|
|
173
|
-
|
|
174
|
-
|
|
168
|
+
const compactJson = argv['compact-json'] === true;
|
|
169
|
+
outputStatus(
|
|
170
|
+
{
|
|
175
171
|
type: 'error',
|
|
176
172
|
errorType: 'AuthenticationError',
|
|
177
173
|
message:
|
|
178
174
|
'No Claude OAuth credentials found in ~/.claude/.credentials.json. Either authenticate with Claude Code CLI first, or use: agent auth login (select Anthropic > Claude Pro/Max)',
|
|
179
|
-
}
|
|
175
|
+
},
|
|
176
|
+
compactJson
|
|
180
177
|
);
|
|
181
178
|
process.exit(1);
|
|
182
179
|
}
|
|
@@ -191,26 +188,27 @@ async function runAgentMode(argv, request) {
|
|
|
191
188
|
modelID = 'claude-sonnet-4-5';
|
|
192
189
|
} else if (!['claude-oauth', 'anthropic'].includes(providerID)) {
|
|
193
190
|
// If user specified a different provider, warn them
|
|
194
|
-
|
|
195
|
-
|
|
191
|
+
const compactJson = argv['compact-json'] === true;
|
|
192
|
+
outputStatus(
|
|
193
|
+
{
|
|
196
194
|
type: 'warning',
|
|
197
195
|
message: `--use-existing-claude-oauth is set but model uses provider "${providerID}". Using OAuth credentials anyway.`,
|
|
198
|
-
}
|
|
196
|
+
},
|
|
197
|
+
compactJson
|
|
199
198
|
);
|
|
200
199
|
providerID = 'claude-oauth';
|
|
201
200
|
}
|
|
202
201
|
}
|
|
203
202
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
if (!isValidJsonStandard(jsonStandard)) {
|
|
207
|
-
console.error(
|
|
208
|
-
`Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`
|
|
209
|
-
);
|
|
210
|
-
process.exit(1);
|
|
211
|
-
}
|
|
203
|
+
return { providerID, modelID };
|
|
204
|
+
}
|
|
212
205
|
|
|
213
|
-
|
|
206
|
+
/**
|
|
207
|
+
* Read system message from files if specified
|
|
208
|
+
* @param {object} argv - Command line arguments
|
|
209
|
+
* @returns {object} - { systemMessage, appendSystemMessage }
|
|
210
|
+
*/
|
|
211
|
+
async function readSystemMessages(argv) {
|
|
214
212
|
let systemMessage = argv['system-message'];
|
|
215
213
|
let appendSystemMessage = argv['append-system-message'];
|
|
216
214
|
|
|
@@ -244,6 +242,41 @@ async function runAgentMode(argv, request) {
|
|
|
244
242
|
appendSystemMessage = await file.text();
|
|
245
243
|
}
|
|
246
244
|
|
|
245
|
+
return { systemMessage, appendSystemMessage };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function runAgentMode(argv, request) {
|
|
249
|
+
// Note: verbose flag and logging are now initialized in middleware
|
|
250
|
+
// See main() function for the middleware that sets up Flag and Log.init()
|
|
251
|
+
|
|
252
|
+
// Log version and command info in verbose mode
|
|
253
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
254
|
+
console.error(`Agent version: ${pkg.version}`);
|
|
255
|
+
console.error(`Command: ${process.argv.join(' ')}`);
|
|
256
|
+
console.error(`Working directory: ${process.cwd()}`);
|
|
257
|
+
console.error(`Script path: ${import.meta.path}`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Log dry-run mode if enabled
|
|
261
|
+
if (Flag.OPENCODE_DRY_RUN) {
|
|
262
|
+
console.error(
|
|
263
|
+
`[DRY RUN MODE] No actual API calls or package installations will be made`
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const { providerID, modelID } = await parseModelConfig(argv);
|
|
268
|
+
|
|
269
|
+
// Validate and get JSON standard
|
|
270
|
+
const jsonStandard = argv['json-standard'];
|
|
271
|
+
if (!isValidJsonStandard(jsonStandard)) {
|
|
272
|
+
console.error(
|
|
273
|
+
`Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`
|
|
274
|
+
);
|
|
275
|
+
process.exit(1);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const { systemMessage, appendSystemMessage } = await readSystemMessages(argv);
|
|
279
|
+
|
|
247
280
|
// Logging is already initialized in middleware, no need to call Log.init() again
|
|
248
281
|
|
|
249
282
|
// Wrap in Instance.provide for OpenCode infrastructure
|
|
@@ -278,6 +311,81 @@ async function runAgentMode(argv, request) {
|
|
|
278
311
|
process.exit(hasError ? 1 : 0);
|
|
279
312
|
}
|
|
280
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Run agent in continuous stdin mode
|
|
316
|
+
* Keeps accepting input until EOF or SIGINT
|
|
317
|
+
* @param {object} argv - Command line arguments
|
|
318
|
+
*/
|
|
319
|
+
async function runContinuousAgentMode(argv) {
|
|
320
|
+
// Note: verbose flag and logging are now initialized in middleware
|
|
321
|
+
// See main() function for the middleware that sets up Flag and Log.init()
|
|
322
|
+
|
|
323
|
+
const compactJson = argv['compact-json'] === true;
|
|
324
|
+
|
|
325
|
+
// Log version and command info in verbose mode
|
|
326
|
+
if (Flag.OPENCODE_VERBOSE) {
|
|
327
|
+
console.error(`Agent version: ${pkg.version}`);
|
|
328
|
+
console.error(`Command: ${process.argv.join(' ')}`);
|
|
329
|
+
console.error(`Working directory: ${process.cwd()}`);
|
|
330
|
+
console.error(`Script path: ${import.meta.path}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Log dry-run mode if enabled
|
|
334
|
+
if (Flag.OPENCODE_DRY_RUN) {
|
|
335
|
+
console.error(
|
|
336
|
+
`[DRY RUN MODE] No actual API calls or package installations will be made`
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const { providerID, modelID } = await parseModelConfig(argv);
|
|
341
|
+
|
|
342
|
+
// Validate and get JSON standard
|
|
343
|
+
const jsonStandard = argv['json-standard'];
|
|
344
|
+
if (!isValidJsonStandard(jsonStandard)) {
|
|
345
|
+
outputStatus(
|
|
346
|
+
{
|
|
347
|
+
type: 'error',
|
|
348
|
+
message: `Invalid JSON standard: ${jsonStandard}. Use "opencode" or "claude".`,
|
|
349
|
+
},
|
|
350
|
+
compactJson
|
|
351
|
+
);
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const { systemMessage, appendSystemMessage } = await readSystemMessages(argv);
|
|
356
|
+
|
|
357
|
+
// Wrap in Instance.provide for OpenCode infrastructure
|
|
358
|
+
await Instance.provide({
|
|
359
|
+
directory: process.cwd(),
|
|
360
|
+
fn: async () => {
|
|
361
|
+
if (argv.server) {
|
|
362
|
+
// SERVER MODE: Start server and communicate via HTTP
|
|
363
|
+
await runContinuousServerMode(
|
|
364
|
+
argv,
|
|
365
|
+
providerID,
|
|
366
|
+
modelID,
|
|
367
|
+
systemMessage,
|
|
368
|
+
appendSystemMessage,
|
|
369
|
+
jsonStandard
|
|
370
|
+
);
|
|
371
|
+
} else {
|
|
372
|
+
// DIRECT MODE: Run everything in single process
|
|
373
|
+
await runContinuousDirectMode(
|
|
374
|
+
argv,
|
|
375
|
+
providerID,
|
|
376
|
+
modelID,
|
|
377
|
+
systemMessage,
|
|
378
|
+
appendSystemMessage,
|
|
379
|
+
jsonStandard
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// Explicitly exit to ensure process terminates
|
|
386
|
+
process.exit(hasError ? 1 : 0);
|
|
387
|
+
}
|
|
388
|
+
|
|
281
389
|
async function runServerMode(
|
|
282
390
|
request,
|
|
283
391
|
providerID,
|
|
@@ -565,85 +673,253 @@ async function main() {
|
|
|
565
673
|
.command(McpCommand)
|
|
566
674
|
// Auth subcommand
|
|
567
675
|
.command(AuthCommand)
|
|
568
|
-
// Default
|
|
569
|
-
.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
676
|
+
// Default command for agent mode (when no subcommand specified)
|
|
677
|
+
.command({
|
|
678
|
+
command: '$0',
|
|
679
|
+
describe: 'Run agent in interactive or stdin mode (default)',
|
|
680
|
+
builder: (yargs) =>
|
|
681
|
+
yargs
|
|
682
|
+
.option('model', {
|
|
683
|
+
type: 'string',
|
|
684
|
+
description: 'Model to use in format providerID/modelID',
|
|
685
|
+
default: 'opencode/grok-code',
|
|
686
|
+
})
|
|
687
|
+
.option('json-standard', {
|
|
688
|
+
type: 'string',
|
|
689
|
+
description:
|
|
690
|
+
'JSON output format standard: "opencode" (default) or "claude" (experimental)',
|
|
691
|
+
default: 'opencode',
|
|
692
|
+
choices: ['opencode', 'claude'],
|
|
693
|
+
})
|
|
694
|
+
.option('system-message', {
|
|
695
|
+
type: 'string',
|
|
696
|
+
description: 'Full override of the system message',
|
|
697
|
+
})
|
|
698
|
+
.option('system-message-file', {
|
|
699
|
+
type: 'string',
|
|
700
|
+
description: 'Full override of the system message from file',
|
|
701
|
+
})
|
|
702
|
+
.option('append-system-message', {
|
|
703
|
+
type: 'string',
|
|
704
|
+
description: 'Append to the default system message',
|
|
705
|
+
})
|
|
706
|
+
.option('append-system-message-file', {
|
|
707
|
+
type: 'string',
|
|
708
|
+
description: 'Append to the default system message from file',
|
|
709
|
+
})
|
|
710
|
+
.option('server', {
|
|
711
|
+
type: 'boolean',
|
|
712
|
+
description: 'Run in server mode (default)',
|
|
713
|
+
default: true,
|
|
714
|
+
})
|
|
715
|
+
.option('verbose', {
|
|
716
|
+
type: 'boolean',
|
|
717
|
+
description:
|
|
718
|
+
'Enable verbose mode to debug API requests (shows system prompt, token counts, etc.)',
|
|
719
|
+
default: false,
|
|
720
|
+
})
|
|
721
|
+
.option('dry-run', {
|
|
722
|
+
type: 'boolean',
|
|
723
|
+
description:
|
|
724
|
+
'Simulate operations without making actual API calls or package installations (useful for testing)',
|
|
725
|
+
default: false,
|
|
726
|
+
})
|
|
727
|
+
.option('use-existing-claude-oauth', {
|
|
728
|
+
type: 'boolean',
|
|
729
|
+
description:
|
|
730
|
+
'Use existing Claude OAuth credentials from ~/.claude/.credentials.json (from Claude Code CLI)',
|
|
731
|
+
default: false,
|
|
732
|
+
})
|
|
733
|
+
.option('prompt', {
|
|
734
|
+
alias: 'p',
|
|
735
|
+
type: 'string',
|
|
736
|
+
description:
|
|
737
|
+
'Prompt message to send directly (bypasses stdin reading)',
|
|
738
|
+
})
|
|
739
|
+
.option('disable-stdin', {
|
|
740
|
+
type: 'boolean',
|
|
741
|
+
description:
|
|
742
|
+
'Disable stdin streaming mode (requires --prompt or shows help)',
|
|
743
|
+
default: false,
|
|
744
|
+
})
|
|
745
|
+
.option('stdin-stream-timeout', {
|
|
746
|
+
type: 'number',
|
|
747
|
+
description:
|
|
748
|
+
'Optional timeout in milliseconds for stdin reading (default: no timeout)',
|
|
749
|
+
})
|
|
750
|
+
.option('auto-merge-queued-messages', {
|
|
751
|
+
type: 'boolean',
|
|
752
|
+
description:
|
|
753
|
+
'Enable auto-merging of rapidly arriving input lines into single messages (default: true)',
|
|
754
|
+
default: true,
|
|
755
|
+
})
|
|
756
|
+
.option('interactive', {
|
|
757
|
+
type: 'boolean',
|
|
758
|
+
description:
|
|
759
|
+
'Enable interactive mode to accept manual input as plain text strings (default: true). Use --no-interactive to only accept JSON input.',
|
|
760
|
+
default: true,
|
|
761
|
+
})
|
|
762
|
+
.option('always-accept-stdin', {
|
|
763
|
+
type: 'boolean',
|
|
764
|
+
description:
|
|
765
|
+
'Keep accepting stdin input even after the agent finishes work (default: true). Use --no-always-accept-stdin for single-message mode.',
|
|
766
|
+
default: true,
|
|
767
|
+
})
|
|
768
|
+
.option('compact-json', {
|
|
769
|
+
type: 'boolean',
|
|
770
|
+
description:
|
|
771
|
+
'Output compact JSON (single line) instead of pretty-printed JSON (default: false). Useful for program-to-program communication.',
|
|
772
|
+
default: false,
|
|
773
|
+
}),
|
|
774
|
+
handler: async (argv) => {
|
|
775
|
+
const compactJson = argv['compact-json'] === true;
|
|
776
|
+
|
|
777
|
+
// Check if --prompt flag was provided
|
|
778
|
+
if (argv.prompt) {
|
|
779
|
+
// Direct prompt mode - bypass stdin entirely
|
|
780
|
+
const request = { message: argv.prompt };
|
|
781
|
+
await runAgentMode(argv, request);
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
// Check if --disable-stdin was set without --prompt
|
|
786
|
+
if (argv['disable-stdin']) {
|
|
787
|
+
// Output a helpful message suggesting to use --prompt
|
|
788
|
+
outputStatus(
|
|
789
|
+
{
|
|
790
|
+
type: 'error',
|
|
791
|
+
message:
|
|
792
|
+
'No prompt provided. Use -p/--prompt to specify a message, or remove --disable-stdin to read from stdin.',
|
|
793
|
+
hint: 'Example: agent -p "Hello, how are you?"',
|
|
794
|
+
},
|
|
795
|
+
compactJson
|
|
796
|
+
);
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
// Check if stdin is a TTY (interactive terminal)
|
|
801
|
+
if (process.stdin.isTTY) {
|
|
802
|
+
// Enter interactive terminal mode with continuous listening
|
|
803
|
+
const isInteractive = argv.interactive !== false;
|
|
804
|
+
const autoMerge = argv['auto-merge-queued-messages'] !== false;
|
|
805
|
+
const alwaysAcceptStdin = argv['always-accept-stdin'] !== false;
|
|
806
|
+
|
|
807
|
+
// Exit if --no-always-accept-stdin is set (single message mode not supported in TTY)
|
|
808
|
+
if (!alwaysAcceptStdin) {
|
|
809
|
+
outputStatus(
|
|
810
|
+
{
|
|
811
|
+
type: 'error',
|
|
812
|
+
message:
|
|
813
|
+
'Single message mode (--no-always-accept-stdin) is not supported in interactive terminal mode.',
|
|
814
|
+
hint: 'Use piped input or --prompt for single messages.',
|
|
815
|
+
},
|
|
816
|
+
compactJson
|
|
817
|
+
);
|
|
818
|
+
process.exit(1);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
outputStatus(
|
|
822
|
+
{
|
|
823
|
+
type: 'status',
|
|
824
|
+
mode: 'interactive-terminal',
|
|
825
|
+
message:
|
|
826
|
+
'Agent CLI in interactive terminal mode. Type your message and press Enter.',
|
|
827
|
+
hint: 'Press CTRL+C to exit. Use --help for options.',
|
|
828
|
+
acceptedFormats: isInteractive
|
|
829
|
+
? ['JSON object with "message" field', 'Plain text']
|
|
830
|
+
: ['JSON object with "message" field'],
|
|
831
|
+
options: {
|
|
832
|
+
interactive: isInteractive,
|
|
833
|
+
autoMergeQueuedMessages: autoMerge,
|
|
834
|
+
alwaysAcceptStdin,
|
|
835
|
+
compactJson,
|
|
836
|
+
},
|
|
837
|
+
},
|
|
838
|
+
compactJson
|
|
839
|
+
);
|
|
840
|
+
|
|
841
|
+
// Use continuous mode for interactive terminal
|
|
842
|
+
await runContinuousAgentMode(argv);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// stdin is piped - enter stdin listening mode
|
|
847
|
+
const isInteractive = argv.interactive !== false;
|
|
848
|
+
const autoMerge = argv['auto-merge-queued-messages'] !== false;
|
|
849
|
+
const alwaysAcceptStdin = argv['always-accept-stdin'] !== false;
|
|
850
|
+
|
|
851
|
+
outputStatus(
|
|
852
|
+
{
|
|
853
|
+
type: 'status',
|
|
854
|
+
mode: 'stdin-stream',
|
|
855
|
+
message: alwaysAcceptStdin
|
|
856
|
+
? 'Agent CLI in continuous listening mode. Accepts JSON and plain text input.'
|
|
857
|
+
: 'Agent CLI in single-message mode. Accepts JSON and plain text input.',
|
|
858
|
+
hint: 'Press CTRL+C to exit. Use --help for options.',
|
|
859
|
+
acceptedFormats: isInteractive
|
|
860
|
+
? ['JSON object with "message" field', 'Plain text']
|
|
861
|
+
: ['JSON object with "message" field'],
|
|
862
|
+
options: {
|
|
863
|
+
interactive: isInteractive,
|
|
864
|
+
autoMergeQueuedMessages: autoMerge,
|
|
865
|
+
alwaysAcceptStdin,
|
|
866
|
+
compactJson,
|
|
867
|
+
},
|
|
868
|
+
},
|
|
869
|
+
compactJson
|
|
870
|
+
);
|
|
871
|
+
|
|
872
|
+
// Use continuous mode if --always-accept-stdin is enabled (default)
|
|
873
|
+
if (alwaysAcceptStdin) {
|
|
874
|
+
await runContinuousAgentMode(argv);
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Single-message mode (--no-always-accept-stdin)
|
|
879
|
+
const timeout = argv['stdin-stream-timeout'] ?? null;
|
|
880
|
+
const input = await readStdinWithTimeout(timeout);
|
|
881
|
+
const trimmedInput = input.trim();
|
|
882
|
+
|
|
883
|
+
if (trimmedInput === '') {
|
|
884
|
+
outputStatus(
|
|
885
|
+
{
|
|
886
|
+
type: 'status',
|
|
887
|
+
message: 'No input received. Exiting.',
|
|
888
|
+
},
|
|
889
|
+
compactJson
|
|
890
|
+
);
|
|
891
|
+
yargsInstance.showHelp();
|
|
892
|
+
process.exit(0);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
// Try to parse as JSON, if it fails treat it as plain text message
|
|
896
|
+
let request;
|
|
897
|
+
try {
|
|
898
|
+
request = JSON.parse(trimmedInput);
|
|
899
|
+
} catch (_e) {
|
|
900
|
+
// Not JSON
|
|
901
|
+
if (!isInteractive) {
|
|
902
|
+
// In non-interactive mode, only accept JSON
|
|
903
|
+
outputStatus(
|
|
904
|
+
{
|
|
905
|
+
type: 'error',
|
|
906
|
+
message:
|
|
907
|
+
'Invalid JSON input. In non-interactive mode (--no-interactive), only JSON input is accepted.',
|
|
908
|
+
hint: 'Use --interactive to accept plain text, or provide valid JSON: {"message": "your text"}',
|
|
909
|
+
},
|
|
910
|
+
compactJson
|
|
911
|
+
);
|
|
912
|
+
process.exit(1);
|
|
913
|
+
}
|
|
914
|
+
// In interactive mode, treat as plain text message
|
|
915
|
+
request = {
|
|
916
|
+
message: trimmedInput,
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Run agent mode
|
|
921
|
+
await runAgentMode(argv, request);
|
|
922
|
+
},
|
|
647
923
|
})
|
|
648
924
|
// Initialize logging early for all CLI commands
|
|
649
925
|
// This prevents debug output from appearing in CLI unless --verbose is used
|
|
@@ -695,99 +971,8 @@ async function main() {
|
|
|
695
971
|
})
|
|
696
972
|
.help();
|
|
697
973
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
// If a command was executed (like mcp), yargs handles it
|
|
701
|
-
// Otherwise, check if we should run in agent mode (stdin piped)
|
|
702
|
-
const commandExecuted = argv._ && argv._.length > 0;
|
|
703
|
-
|
|
704
|
-
if (!commandExecuted) {
|
|
705
|
-
// Check if --prompt flag was provided
|
|
706
|
-
if (argv.prompt) {
|
|
707
|
-
// Direct prompt mode - bypass stdin entirely
|
|
708
|
-
const request = { message: argv.prompt };
|
|
709
|
-
await runAgentMode(argv, request);
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
// Check if --disable-stdin was set without --prompt
|
|
714
|
-
if (argv['disable-stdin']) {
|
|
715
|
-
// Output a helpful message suggesting to use --prompt
|
|
716
|
-
outputStatus({
|
|
717
|
-
type: 'error',
|
|
718
|
-
message:
|
|
719
|
-
'No prompt provided. Use -p/--prompt to specify a message, or remove --disable-stdin to read from stdin.',
|
|
720
|
-
hint: 'Example: agent -p "Hello, how are you?"',
|
|
721
|
-
});
|
|
722
|
-
process.exit(1);
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
// Check if stdin is a TTY (interactive terminal)
|
|
726
|
-
// If it is, show help instead of waiting for input
|
|
727
|
-
if (process.stdin.isTTY) {
|
|
728
|
-
yargsInstance.showHelp();
|
|
729
|
-
process.exit(0);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
// stdin is piped - enter stdin listening mode
|
|
733
|
-
// Output status message to inform user what's happening
|
|
734
|
-
const isInteractive = argv.interactive !== false;
|
|
735
|
-
const autoMerge = argv['auto-merge-queued-messages'] !== false;
|
|
736
|
-
|
|
737
|
-
outputStatus({
|
|
738
|
-
type: 'status',
|
|
739
|
-
mode: 'stdin-stream',
|
|
740
|
-
message:
|
|
741
|
-
'Agent CLI in stdin listening mode. Accepts JSON and plain text input.',
|
|
742
|
-
hint: 'Press CTRL+C to exit. Use --help for options.',
|
|
743
|
-
acceptedFormats: isInteractive
|
|
744
|
-
? ['JSON object with "message" field', 'Plain text']
|
|
745
|
-
: ['JSON object with "message" field'],
|
|
746
|
-
options: {
|
|
747
|
-
interactive: isInteractive,
|
|
748
|
-
autoMergeQueuedMessages: autoMerge,
|
|
749
|
-
},
|
|
750
|
-
});
|
|
751
|
-
|
|
752
|
-
// Read stdin with optional timeout
|
|
753
|
-
const timeout = argv['stdin-stream-timeout'] ?? null;
|
|
754
|
-
const input = await readStdinWithTimeout(timeout);
|
|
755
|
-
const trimmedInput = input.trim();
|
|
756
|
-
|
|
757
|
-
if (trimmedInput === '') {
|
|
758
|
-
outputStatus({
|
|
759
|
-
type: 'status',
|
|
760
|
-
message: 'No input received. Exiting.',
|
|
761
|
-
});
|
|
762
|
-
yargsInstance.showHelp();
|
|
763
|
-
process.exit(0);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
// Try to parse as JSON, if it fails treat it as plain text message
|
|
767
|
-
let request;
|
|
768
|
-
try {
|
|
769
|
-
request = JSON.parse(trimmedInput);
|
|
770
|
-
} catch (_e) {
|
|
771
|
-
// Not JSON
|
|
772
|
-
if (!isInteractive) {
|
|
773
|
-
// In non-interactive mode, only accept JSON
|
|
774
|
-
outputStatus({
|
|
775
|
-
type: 'error',
|
|
776
|
-
message:
|
|
777
|
-
'Invalid JSON input. In non-interactive mode (--no-interactive), only JSON input is accepted.',
|
|
778
|
-
hint: 'Use --interactive to accept plain text, or provide valid JSON: {"message": "your text"}',
|
|
779
|
-
});
|
|
780
|
-
process.exit(1);
|
|
781
|
-
}
|
|
782
|
-
// In interactive mode, treat as plain text message
|
|
783
|
-
request = {
|
|
784
|
-
message: trimmedInput,
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
// Run agent mode
|
|
789
|
-
await runAgentMode(argv, request);
|
|
790
|
-
}
|
|
974
|
+
// Parse arguments (handlers will be called automatically)
|
|
975
|
+
await yargsInstance.argv;
|
|
791
976
|
} catch (error) {
|
|
792
977
|
hasError = true;
|
|
793
978
|
console.error(
|