@iinm/plain-agent 1.5.4 → 1.6.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/.config/config.predefined.json +6 -6
- package/package.json +4 -4
- package/src/cliCommands.mjs +270 -0
- package/src/cliCompleter.mjs +222 -0
- package/src/cliFormatter.mjs +63 -1
- package/src/cliInteractive.mjs +72 -501
- package/src/cliPasteTransform.mjs +128 -0
- package/src/context/loadAgentRoles.mjs +5 -0
- package/src/context/loadPrompts.mjs +5 -0
- package/src/prompt.mjs +7 -6
- package/src/subagent.mjs +4 -1
package/src/cliInteractive.mjs
CHANGED
|
@@ -1,163 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { Message } from "./model"
|
|
3
2
|
* @import { UserEventEmitter, AgentEventEmitter, AgentCommands } from "./agent"
|
|
4
3
|
* @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
|
|
5
4
|
*/
|
|
6
5
|
|
|
7
|
-
import { execFileSync } from "node:child_process";
|
|
8
6
|
import readline from "node:readline";
|
|
9
7
|
import { styleText } from "node:util";
|
|
8
|
+
import { createCommandHandler } from "./cliCommands.mjs";
|
|
9
|
+
import { createCompleter, SLASH_COMMANDS } from "./cliCompleter.mjs";
|
|
10
10
|
import {
|
|
11
11
|
formatCostSummary,
|
|
12
12
|
formatProviderTokenUsage,
|
|
13
|
-
|
|
14
|
-
formatToolUse,
|
|
13
|
+
printMessage,
|
|
15
14
|
} from "./cliFormatter.mjs";
|
|
15
|
+
import {
|
|
16
|
+
createPasteTransform,
|
|
17
|
+
resolvePastePlaceholders,
|
|
18
|
+
} from "./cliPasteTransform.mjs";
|
|
16
19
|
import { consumeInterruptMessage } from "./context/consumeInterruptMessage.mjs";
|
|
17
|
-
import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
|
|
18
|
-
import { loadPrompts } from "./context/loadPrompts.mjs";
|
|
19
|
-
import { loadUserMessageContext } from "./context/loadUserMessageContext.mjs";
|
|
20
20
|
import { notify } from "./utils/notify.mjs";
|
|
21
|
-
import { parseFileRange } from "./utils/parseFileRange.mjs";
|
|
22
|
-
import { readFileRange } from "./utils/readFileRange.mjs";
|
|
23
|
-
|
|
24
|
-
// Define available slash commands for tab completion
|
|
25
|
-
const SLASH_COMMANDS = [
|
|
26
|
-
{ name: "/help", description: "Display this help message" },
|
|
27
|
-
{ name: "/agents", description: "List available agent roles" },
|
|
28
|
-
{
|
|
29
|
-
name: "/agents:<id>",
|
|
30
|
-
description:
|
|
31
|
-
"Delegate to an agent with the given ID (e.g., /agents:code-simplifier)",
|
|
32
|
-
},
|
|
33
|
-
{ name: "/prompts", description: "List available prompts" },
|
|
34
|
-
{
|
|
35
|
-
name: "/prompts:<id>",
|
|
36
|
-
description:
|
|
37
|
-
"Invoke a prompt with the given ID (e.g., /prompts:feature-dev)",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
name: "/<id>",
|
|
41
|
-
description:
|
|
42
|
-
"Shortcut for prompts in the shortcuts/ directory (e.g., /commit)",
|
|
43
|
-
},
|
|
44
|
-
{ name: "/paste", description: "Paste content from clipboard" },
|
|
45
|
-
{
|
|
46
|
-
name: "/resume",
|
|
47
|
-
description: "Resume conversation after an LLM provider error",
|
|
48
|
-
},
|
|
49
|
-
{ name: "/dump", description: "Save current messages to a JSON file" },
|
|
50
|
-
{ name: "/load", description: "Load messages from a JSON file" },
|
|
51
|
-
{ name: "/cost", description: "Display session cost and token usage" },
|
|
52
|
-
];
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* @typedef {Object} CompletionCandidate
|
|
56
|
-
* @property {string} name
|
|
57
|
-
* @property {string} description
|
|
58
|
-
*/
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Find candidates that match the line, prioritizing prefix matches.
|
|
62
|
-
* @param {(string | CompletionCandidate)[]} candidates
|
|
63
|
-
* @param {string} line
|
|
64
|
-
* @param {number} queryStartIndex
|
|
65
|
-
* @returns {(string | CompletionCandidate)[]}
|
|
66
|
-
*/
|
|
67
|
-
function findMatches(candidates, line, queryStartIndex) {
|
|
68
|
-
const query = line.slice(queryStartIndex);
|
|
69
|
-
const prefixMatches = [];
|
|
70
|
-
const partialMatches = [];
|
|
71
|
-
|
|
72
|
-
for (const candidate of candidates) {
|
|
73
|
-
const name = typeof candidate === "string" ? candidate : candidate.name;
|
|
74
|
-
if (name.startsWith(line)) {
|
|
75
|
-
prefixMatches.push(candidate);
|
|
76
|
-
} else if (
|
|
77
|
-
query.length > 0 &&
|
|
78
|
-
name.slice(queryStartIndex).includes(query)
|
|
79
|
-
) {
|
|
80
|
-
partialMatches.push(candidate);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return [...prefixMatches, ...partialMatches];
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Return the longest common prefix of the given strings.
|
|
89
|
-
* @param {string[]} strings
|
|
90
|
-
* @returns {string}
|
|
91
|
-
*/
|
|
92
|
-
function commonPrefix(strings) {
|
|
93
|
-
if (strings.length === 0) return "";
|
|
94
|
-
let prefix = strings[0];
|
|
95
|
-
for (let i = 1; i < strings.length; i++) {
|
|
96
|
-
while (!strings[i].startsWith(prefix)) {
|
|
97
|
-
prefix = prefix.slice(0, -1);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
return prefix;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Display completion candidates and invoke the readline callback.
|
|
105
|
-
*
|
|
106
|
-
* Node.js readline normally requires two consecutive Tab presses to show the
|
|
107
|
-
* candidate list. This helper lets readline handle the common-prefix
|
|
108
|
-
* auto-completion first, then prints the candidate list on the next tick and
|
|
109
|
-
* redraws the prompt so the display stays clean.
|
|
110
|
-
*
|
|
111
|
-
* @param {import("node:readline").Interface} rl
|
|
112
|
-
* @param {(string | CompletionCandidate)[]} candidates
|
|
113
|
-
* @param {string} line
|
|
114
|
-
* @param {(err: Error | null, result: [string[], string]) => void} callback
|
|
115
|
-
*/
|
|
116
|
-
function showCompletions(rl, candidates, line, callback) {
|
|
117
|
-
const names = candidates.map((c) => (typeof c === "string" ? c : c.name));
|
|
118
|
-
if (candidates.length <= 1) {
|
|
119
|
-
callback(null, [names, line]);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const prefix = commonPrefix(names);
|
|
123
|
-
if (prefix.length > line.length) {
|
|
124
|
-
// Let readline insert the common prefix.
|
|
125
|
-
callback(null, [[prefix], line]);
|
|
126
|
-
} else {
|
|
127
|
-
// Nothing new to insert.
|
|
128
|
-
callback(null, [[], line]);
|
|
129
|
-
}
|
|
130
|
-
// After readline finishes its own refresh, print the candidate list and
|
|
131
|
-
// redraw the prompt line. We cannot use rl.prompt(true) because its
|
|
132
|
-
// internal _refreshLine clears everything below the prompt start, which
|
|
133
|
-
// erases the candidate list we just wrote. Instead we manually re-output
|
|
134
|
-
// the prompt and current line content.
|
|
135
|
-
setTimeout(() => {
|
|
136
|
-
const maxLength = process.stdout.columns ?? 100;
|
|
137
|
-
const list = candidates
|
|
138
|
-
.map((c) => {
|
|
139
|
-
if (typeof c === "string") return c;
|
|
140
|
-
const nameText = c.name.padEnd(25);
|
|
141
|
-
const separator = " - ";
|
|
142
|
-
const descText = c.description;
|
|
143
|
-
|
|
144
|
-
// 画面幅に合わせて説明文をカット(色を付ける前に計算)
|
|
145
|
-
const availableWidth =
|
|
146
|
-
maxLength - nameText.length - separator.length - 3;
|
|
147
|
-
const displayDesc =
|
|
148
|
-
descText.length > availableWidth && availableWidth > 0
|
|
149
|
-
? `${descText.slice(0, availableWidth)}...`
|
|
150
|
-
: descText;
|
|
151
|
-
|
|
152
|
-
const name = styleText("cyan", nameText);
|
|
153
|
-
const description = styleText("dim", displayDesc);
|
|
154
|
-
return `${name}${separator}${description}`;
|
|
155
|
-
})
|
|
156
|
-
.join("\r\n");
|
|
157
|
-
process.stdout.write(`\r\n${list}\r\n`);
|
|
158
|
-
process.stdout.write(`${rl.getPrompt()}${rl.line}`);
|
|
159
|
-
}, 0);
|
|
160
|
-
}
|
|
161
21
|
|
|
162
22
|
const HELP_MESSAGE = [
|
|
163
23
|
"Commands:",
|
|
@@ -223,45 +83,6 @@ export function startInteractiveSession({
|
|
|
223
83
|
subagentName: "",
|
|
224
84
|
};
|
|
225
85
|
|
|
226
|
-
/**
|
|
227
|
-
* @param {string} id
|
|
228
|
-
* @param {string} goal
|
|
229
|
-
* @returns {Promise<void>}
|
|
230
|
-
*/
|
|
231
|
-
async function invokeAgent(id, goal) {
|
|
232
|
-
const agentRoles = await loadAgentRoles(claudeCodePlugins);
|
|
233
|
-
const agent = agentRoles.get(id);
|
|
234
|
-
const name = agent ? id : `custom:${id}`;
|
|
235
|
-
const message = `Delegate to "${name}" agent with goal: ${goal}`;
|
|
236
|
-
|
|
237
|
-
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* @param {string} id
|
|
242
|
-
* @param {string} args
|
|
243
|
-
* @param {string} displayInvocation
|
|
244
|
-
* @returns {Promise<void>}
|
|
245
|
-
*/
|
|
246
|
-
async function invokePrompt(id, args, displayInvocation) {
|
|
247
|
-
const prompts = await loadPrompts(claudeCodePlugins);
|
|
248
|
-
const prompt = prompts.get(id);
|
|
249
|
-
|
|
250
|
-
if (!prompt) {
|
|
251
|
-
console.log(styleText("red", `\nPrompt not found: ${id}`));
|
|
252
|
-
state.turn = true;
|
|
253
|
-
cli.prompt();
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const invocation = `${displayInvocation}${args ? ` ${args}` : ""}`;
|
|
258
|
-
const message = prompt.isSkill
|
|
259
|
-
? `System: This prompt was invoked as "${invocation}".\nPrompt path: ${prompt.filePath}\n\n${prompt.content}`
|
|
260
|
-
: `System: This prompt was invoked as "${invocation}".\n\n${prompt.content}`;
|
|
261
|
-
|
|
262
|
-
userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
86
|
const getCliPrompt = (subagentName = "") =>
|
|
266
87
|
[
|
|
267
88
|
"",
|
|
@@ -275,77 +96,59 @@ export function startInteractiveSession({
|
|
|
275
96
|
"> ",
|
|
276
97
|
].join("\n");
|
|
277
98
|
|
|
99
|
+
// Cleanup handler to disable bracketed paste mode on exit
|
|
100
|
+
const cleanup = () => {
|
|
101
|
+
if (process.stdout.isTTY) {
|
|
102
|
+
process.stdout.write("\x1b[?2004l");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Handle exit signals
|
|
107
|
+
let isExiting = false;
|
|
108
|
+
const handleExit = async () => {
|
|
109
|
+
if (isExiting) return;
|
|
110
|
+
isExiting = true;
|
|
111
|
+
|
|
112
|
+
cleanup();
|
|
113
|
+
const summary = agentCommands.getCostSummary();
|
|
114
|
+
console.log();
|
|
115
|
+
console.log(formatCostSummary(summary));
|
|
116
|
+
await onStop();
|
|
117
|
+
process.exit(0);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Double-press exit confirmation
|
|
121
|
+
let lastExitAttempt = 0;
|
|
122
|
+
const EXIT_CONFIRM_TIMEOUT = 1500;
|
|
123
|
+
|
|
124
|
+
const confirmExit = () => {
|
|
125
|
+
const now = Date.now();
|
|
126
|
+
if (now - lastExitAttempt < EXIT_CONFIRM_TIMEOUT) {
|
|
127
|
+
handleExit();
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
lastExitAttempt = now;
|
|
131
|
+
console.log(styleText("yellow", "\nPress Ctrl-C or Ctrl-D again to exit."));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Create a transform stream to handle bracketed paste before readline
|
|
135
|
+
const pasteTransform = createPasteTransform(confirmExit);
|
|
136
|
+
|
|
137
|
+
// Set up transformed stdin for readline
|
|
138
|
+
process.stdin.pipe(pasteTransform);
|
|
139
|
+
|
|
140
|
+
// Enable bracketed paste mode
|
|
141
|
+
if (process.stdout.isTTY) {
|
|
142
|
+
process.stdout.write("\x1b[?2004h");
|
|
143
|
+
}
|
|
144
|
+
|
|
278
145
|
let currentCliPrompt = getCliPrompt();
|
|
146
|
+
/** @type {import("node:readline").Interface} */
|
|
279
147
|
const cli = readline.createInterface({
|
|
280
|
-
input:
|
|
148
|
+
input: pasteTransform,
|
|
281
149
|
output: process.stdout,
|
|
282
150
|
prompt: currentCliPrompt,
|
|
283
|
-
|
|
284
|
-
* @param {string} line
|
|
285
|
-
* @param {(err?: Error | null, result?: [string[], string]) => void} callback
|
|
286
|
-
*/
|
|
287
|
-
completer: (line, callback) => {
|
|
288
|
-
(async () => {
|
|
289
|
-
try {
|
|
290
|
-
const prompts = await loadPrompts(claudeCodePlugins);
|
|
291
|
-
const agentRoles = await loadAgentRoles(claudeCodePlugins);
|
|
292
|
-
|
|
293
|
-
if (line.startsWith("/agents:")) {
|
|
294
|
-
const prefix = "/agents:";
|
|
295
|
-
const candidates = Array.from(agentRoles.values()).map((a) => ({
|
|
296
|
-
name: `${prefix}${a.id}`,
|
|
297
|
-
description: a.description,
|
|
298
|
-
}));
|
|
299
|
-
const hits = findMatches(candidates, line, prefix.length);
|
|
300
|
-
|
|
301
|
-
showCompletions(cli, hits, line, callback);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
if (line.startsWith("/prompts:")) {
|
|
306
|
-
const prefix = "/prompts:";
|
|
307
|
-
const candidates = Array.from(prompts.values()).map((p) => ({
|
|
308
|
-
name: `${prefix}${p.id}`,
|
|
309
|
-
description: p.description,
|
|
310
|
-
}));
|
|
311
|
-
const hits = findMatches(candidates, line, prefix.length);
|
|
312
|
-
|
|
313
|
-
showCompletions(cli, hits, line, callback);
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
if (line.startsWith("/")) {
|
|
318
|
-
const shortcuts = Array.from(prompts.values())
|
|
319
|
-
.filter((p) => p.isShortcut)
|
|
320
|
-
.map((p) => ({
|
|
321
|
-
name: `/${p.id}`,
|
|
322
|
-
description: p.description,
|
|
323
|
-
}));
|
|
324
|
-
|
|
325
|
-
const allCommands = [...SLASH_COMMANDS, ...shortcuts].filter(
|
|
326
|
-
(cmd) => {
|
|
327
|
-
const name = typeof cmd === "string" ? cmd : cmd.name;
|
|
328
|
-
return (
|
|
329
|
-
name !== "/<id>" &&
|
|
330
|
-
(name === "/agents:" || !name.startsWith("/agent:")) &&
|
|
331
|
-
(name === "/prompts:" || !name.startsWith("/prompt:"))
|
|
332
|
-
);
|
|
333
|
-
},
|
|
334
|
-
);
|
|
335
|
-
|
|
336
|
-
const hits = findMatches(allCommands, line, 1);
|
|
337
|
-
|
|
338
|
-
showCompletions(cli, hits, line, callback);
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
callback(null, [[], line]);
|
|
343
|
-
} catch (err) {
|
|
344
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
345
|
-
callback(error, [[], line]);
|
|
346
|
-
}
|
|
347
|
-
})();
|
|
348
|
-
},
|
|
151
|
+
completer: createCompleter(() => cli, claudeCodePlugins),
|
|
349
152
|
});
|
|
350
153
|
|
|
351
154
|
// Disable automatic prompt redraw on resize during agent turn
|
|
@@ -365,20 +168,14 @@ export function startInteractiveSession({
|
|
|
365
168
|
process.stdin.setRawMode(true);
|
|
366
169
|
}
|
|
367
170
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
const summary = agentCommands.getCostSummary();
|
|
371
|
-
console.log();
|
|
372
|
-
console.log(formatCostSummary(summary));
|
|
373
|
-
await onStop();
|
|
374
|
-
}
|
|
171
|
+
// Handle readline close (e.g., stdin closed externally)
|
|
172
|
+
cli.on("close", handleExit);
|
|
375
173
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
174
|
+
const handleCommand = createCommandHandler({
|
|
175
|
+
agentCommands,
|
|
176
|
+
userEventEmitter,
|
|
177
|
+
claudeCodePlugins,
|
|
178
|
+
helpMessage: HELP_MESSAGE,
|
|
382
179
|
});
|
|
383
180
|
|
|
384
181
|
/**
|
|
@@ -390,7 +187,9 @@ export function startInteractiveSession({
|
|
|
390
187
|
// Prevent concurrent input processing from multi-line paste
|
|
391
188
|
state.turn = false;
|
|
392
189
|
|
|
393
|
-
|
|
190
|
+
// Resolve paste placeholders to original content
|
|
191
|
+
const resolvedInput = resolvePastePlaceholders(input);
|
|
192
|
+
const inputTrimmed = resolvedInput.trim();
|
|
394
193
|
|
|
395
194
|
if (inputTrimmed.length === 0) {
|
|
396
195
|
state.turn = true;
|
|
@@ -401,182 +200,11 @@ export function startInteractiveSession({
|
|
|
401
200
|
cli.setPrompt(currentCliPrompt);
|
|
402
201
|
await consumeInterruptMessage();
|
|
403
202
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
state.turn = true;
|
|
407
|
-
cli.prompt();
|
|
408
|
-
return;
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (inputTrimmed.startsWith("!")) {
|
|
412
|
-
const fileRange = parseFileRange(inputTrimmed.slice(1));
|
|
413
|
-
if (fileRange instanceof Error) {
|
|
414
|
-
console.log(styleText("red", `\n${fileRange.message}`));
|
|
415
|
-
state.turn = true;
|
|
416
|
-
cli.prompt();
|
|
417
|
-
return;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const fileContent = await readFileRange(fileRange);
|
|
421
|
-
if (fileContent instanceof Error) {
|
|
422
|
-
console.log(styleText("red", `\n${fileContent.message}`));
|
|
423
|
-
state.turn = true;
|
|
424
|
-
cli.prompt();
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
const messageWithContext = await loadUserMessageContext(fileContent);
|
|
429
|
-
|
|
430
|
-
userEventEmitter.emit("userInput", messageWithContext);
|
|
431
|
-
return;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
if (inputTrimmed.toLowerCase() === "/dump") {
|
|
435
|
-
await agentCommands.dumpMessages();
|
|
436
|
-
state.turn = true;
|
|
437
|
-
cli.prompt();
|
|
438
|
-
return;
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
if (inputTrimmed.toLowerCase() === "/load") {
|
|
442
|
-
await agentCommands.loadMessages();
|
|
443
|
-
state.turn = true;
|
|
444
|
-
cli.prompt();
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (inputTrimmed.toLowerCase() === "/cost") {
|
|
449
|
-
const summary = agentCommands.getCostSummary();
|
|
450
|
-
console.log(formatCostSummary(summary));
|
|
451
|
-
state.turn = true;
|
|
452
|
-
cli.prompt();
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (inputTrimmed === "/agents") {
|
|
457
|
-
const agentRoles = await loadAgentRoles(claudeCodePlugins);
|
|
458
|
-
|
|
459
|
-
console.log(styleText("bold", "\nAvailable Agent Roles:"));
|
|
460
|
-
if (agentRoles.size === 0) {
|
|
461
|
-
console.log(" No agent roles found.");
|
|
462
|
-
} else {
|
|
463
|
-
for (const role of agentRoles.values()) {
|
|
464
|
-
const maxLength = process.stdout.columns ?? 100;
|
|
465
|
-
const line = ` ${styleText("cyan", role.id.padEnd(20))} - ${role.description}`;
|
|
466
|
-
console.log(
|
|
467
|
-
line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
|
|
468
|
-
);
|
|
469
|
-
}
|
|
470
|
-
}
|
|
203
|
+
const result = await handleCommand(inputTrimmed);
|
|
204
|
+
if (result === "prompt") {
|
|
471
205
|
state.turn = true;
|
|
472
206
|
cli.prompt();
|
|
473
|
-
return;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (inputTrimmed.startsWith("/prompts")) {
|
|
477
|
-
const prompts = await loadPrompts(claudeCodePlugins);
|
|
478
|
-
|
|
479
|
-
if (inputTrimmed === "/prompts") {
|
|
480
|
-
console.log(styleText("bold", "\nAvailable Prompts:"));
|
|
481
|
-
if (prompts.size === 0) {
|
|
482
|
-
console.log(" No prompts found.");
|
|
483
|
-
} else {
|
|
484
|
-
for (const prompt of prompts.values()) {
|
|
485
|
-
const maxLength = process.stdout.columns ?? 100;
|
|
486
|
-
const line = ` ${styleText("cyan", prompt.id.padEnd(20))} - ${prompt.description}`;
|
|
487
|
-
console.log(
|
|
488
|
-
line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
|
|
489
|
-
);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
state.turn = true;
|
|
493
|
-
cli.prompt();
|
|
494
|
-
return;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (inputTrimmed.startsWith("/prompts:")) {
|
|
498
|
-
const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/);
|
|
499
|
-
if (!match) {
|
|
500
|
-
console.log(styleText("red", "\nInvalid prompt invocation format."));
|
|
501
|
-
state.turn = true;
|
|
502
|
-
cli.prompt();
|
|
503
|
-
return;
|
|
504
|
-
}
|
|
505
|
-
await invokePrompt(match[1], match[2] || "", `/prompts:${match[1]}`);
|
|
506
|
-
return;
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
if (inputTrimmed.startsWith("/agents:")) {
|
|
511
|
-
const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/);
|
|
512
|
-
if (!match) {
|
|
513
|
-
console.log(styleText("red", "\nInvalid agent invocation format."));
|
|
514
|
-
state.turn = true;
|
|
515
|
-
cli.prompt();
|
|
516
|
-
return;
|
|
517
|
-
}
|
|
518
|
-
await invokeAgent(match[1], match[2] || "");
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
if (inputTrimmed.startsWith("/paste")) {
|
|
523
|
-
const prompt = inputTrimmed.slice("/paste".length).trim();
|
|
524
|
-
let clipboard;
|
|
525
|
-
try {
|
|
526
|
-
if (process.platform === "darwin") {
|
|
527
|
-
clipboard = execFileSync("pbpaste", { encoding: "utf8" });
|
|
528
|
-
} else if (process.platform === "linux") {
|
|
529
|
-
clipboard = execFileSync("xsel", ["--clipboard", "--output"], {
|
|
530
|
-
encoding: "utf8",
|
|
531
|
-
});
|
|
532
|
-
} else {
|
|
533
|
-
console.log(
|
|
534
|
-
styleText(
|
|
535
|
-
"red",
|
|
536
|
-
`\nUnsupported platform for /paste: ${process.platform}`,
|
|
537
|
-
),
|
|
538
|
-
);
|
|
539
|
-
state.turn = true;
|
|
540
|
-
cli.prompt();
|
|
541
|
-
return;
|
|
542
|
-
}
|
|
543
|
-
} catch (e) {
|
|
544
|
-
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
545
|
-
console.log(
|
|
546
|
-
styleText(
|
|
547
|
-
"red",
|
|
548
|
-
`\nFailed to get clipboard content: ${errorMessage}`,
|
|
549
|
-
),
|
|
550
|
-
);
|
|
551
|
-
state.turn = true;
|
|
552
|
-
cli.prompt();
|
|
553
|
-
return;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
const combinedInput = prompt ? `${prompt}\n\n${clipboard}` : clipboard;
|
|
557
|
-
|
|
558
|
-
const messageWithContext = await loadUserMessageContext(combinedInput);
|
|
559
|
-
userEventEmitter.emit("userInput", messageWithContext);
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// Handle shortcuts for prompts in shortcuts/ directory
|
|
564
|
-
if (inputTrimmed.startsWith("/")) {
|
|
565
|
-
const match = inputTrimmed.match(/^\/([^ ]+)(?:\s+(.*))?$/);
|
|
566
|
-
if (match) {
|
|
567
|
-
const id = match[1];
|
|
568
|
-
const prompts = await loadPrompts(claudeCodePlugins);
|
|
569
|
-
const prompt = prompts.get(id);
|
|
570
|
-
|
|
571
|
-
if (prompt?.isShortcut) {
|
|
572
|
-
await invokePrompt(id, match[2] || "", `/${id}`);
|
|
573
|
-
return;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
207
|
}
|
|
577
|
-
|
|
578
|
-
const messageWithContext = await loadUserMessageContext(inputTrimmed);
|
|
579
|
-
userEventEmitter.emit("userInput", messageWithContext);
|
|
580
208
|
}
|
|
581
209
|
|
|
582
210
|
cli.on("line", async (lineInput) => {
|
|
@@ -590,7 +218,7 @@ export function startInteractiveSession({
|
|
|
590
218
|
return;
|
|
591
219
|
}
|
|
592
220
|
|
|
593
|
-
//
|
|
221
|
+
// Check for multi-line delimiter
|
|
594
222
|
if (lineInput.trim() === '"""') {
|
|
595
223
|
if (state.multiLineBuffer === null) {
|
|
596
224
|
state.multiLineBuffer = [];
|
|
@@ -687,65 +315,8 @@ export function startInteractiveSession({
|
|
|
687
315
|
});
|
|
688
316
|
|
|
689
317
|
cli.prompt();
|
|
690
|
-
}
|
|
691
318
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
function printMessage(message) {
|
|
696
|
-
switch (message.role) {
|
|
697
|
-
case "assistant": {
|
|
698
|
-
// console.log(styleText("bold", "\nAgent:"));
|
|
699
|
-
for (const part of message.content) {
|
|
700
|
-
switch (part.type) {
|
|
701
|
-
// Note: Streamで表示するためここでは表示しない
|
|
702
|
-
// case "thinking":
|
|
703
|
-
// console.log(
|
|
704
|
-
// [
|
|
705
|
-
// styleText("blue", "<thinking>"),
|
|
706
|
-
// part.thinking,
|
|
707
|
-
// styleText("blue", "</thinking>\n"),
|
|
708
|
-
// ].join("\n"),
|
|
709
|
-
// );
|
|
710
|
-
// break;
|
|
711
|
-
// case "text":
|
|
712
|
-
// console.log(part.text);
|
|
713
|
-
// break;
|
|
714
|
-
case "tool_use":
|
|
715
|
-
console.log(styleText("bold", "\nTool call:"));
|
|
716
|
-
console.log(formatToolUse(part));
|
|
717
|
-
break;
|
|
718
|
-
}
|
|
719
|
-
}
|
|
720
|
-
break;
|
|
721
|
-
}
|
|
722
|
-
case "user": {
|
|
723
|
-
for (const part of message.content) {
|
|
724
|
-
switch (part.type) {
|
|
725
|
-
case "tool_result": {
|
|
726
|
-
console.log(styleText("bold", "\nTool result:"));
|
|
727
|
-
console.log(formatToolResult(part));
|
|
728
|
-
break;
|
|
729
|
-
}
|
|
730
|
-
case "text": {
|
|
731
|
-
console.log(styleText("bold", "\nUser:"));
|
|
732
|
-
console.log(part.text);
|
|
733
|
-
break;
|
|
734
|
-
}
|
|
735
|
-
case "image": {
|
|
736
|
-
break;
|
|
737
|
-
}
|
|
738
|
-
default: {
|
|
739
|
-
console.log(styleText("bold", "\nUnknown Message Format:"));
|
|
740
|
-
console.log(JSON.stringify(part, null, 2));
|
|
741
|
-
}
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
break;
|
|
745
|
-
}
|
|
746
|
-
default: {
|
|
747
|
-
console.log(styleText("bold", "\nUnknown Message Format:"));
|
|
748
|
-
console.log(JSON.stringify(message, null, 2));
|
|
749
|
-
}
|
|
750
|
-
}
|
|
319
|
+
// Register cleanup handlers
|
|
320
|
+
process.on("exit", cleanup);
|
|
321
|
+
process.on("SIGTERM", cleanup);
|
|
751
322
|
}
|