@iinm/plain-agent 1.6.0 → 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.
@@ -1,164 +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
- import { Transform } from "node:stream";
10
7
  import { styleText } from "node:util";
8
+ import { createCommandHandler } from "./cliCommands.mjs";
9
+ import { createCompleter, SLASH_COMMANDS } from "./cliCompleter.mjs";
11
10
  import {
12
11
  formatCostSummary,
13
12
  formatProviderTokenUsage,
14
- formatToolResult,
15
- formatToolUse,
13
+ printMessage,
16
14
  } from "./cliFormatter.mjs";
15
+ import {
16
+ createPasteTransform,
17
+ resolvePastePlaceholders,
18
+ } from "./cliPasteTransform.mjs";
17
19
  import { consumeInterruptMessage } from "./context/consumeInterruptMessage.mjs";
18
- import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
19
- import { loadPrompts } from "./context/loadPrompts.mjs";
20
- import { loadUserMessageContext } from "./context/loadUserMessageContext.mjs";
21
20
  import { notify } from "./utils/notify.mjs";
22
- import { parseFileRange } from "./utils/parseFileRange.mjs";
23
- import { readFileRange } from "./utils/readFileRange.mjs";
24
-
25
- // Define available slash commands for tab completion
26
- const SLASH_COMMANDS = [
27
- { name: "/help", description: "Display this help message" },
28
- { name: "/agents", description: "List available agent roles" },
29
- {
30
- name: "/agents:<id>",
31
- description:
32
- "Delegate to an agent with the given ID (e.g., /agents:code-simplifier)",
33
- },
34
- { name: "/prompts", description: "List available prompts" },
35
- {
36
- name: "/prompts:<id>",
37
- description:
38
- "Invoke a prompt with the given ID (e.g., /prompts:feature-dev)",
39
- },
40
- {
41
- name: "/<id>",
42
- description:
43
- "Shortcut for prompts in the shortcuts/ directory (e.g., /commit)",
44
- },
45
- { name: "/paste", description: "Paste content from clipboard" },
46
- {
47
- name: "/resume",
48
- description: "Resume conversation after an LLM provider error",
49
- },
50
- { name: "/dump", description: "Save current messages to a JSON file" },
51
- { name: "/load", description: "Load messages from a JSON file" },
52
- { name: "/cost", description: "Display session cost and token usage" },
53
- ];
54
-
55
- /**
56
- * @typedef {Object} CompletionCandidate
57
- * @property {string} name
58
- * @property {string} description
59
- */
60
-
61
- /**
62
- * Find candidates that match the line, prioritizing prefix matches.
63
- * @param {(string | CompletionCandidate)[]} candidates
64
- * @param {string} line
65
- * @param {number} queryStartIndex
66
- * @returns {(string | CompletionCandidate)[]}
67
- */
68
- function findMatches(candidates, line, queryStartIndex) {
69
- const query = line.slice(queryStartIndex);
70
- const prefixMatches = [];
71
- const partialMatches = [];
72
-
73
- for (const candidate of candidates) {
74
- const name = typeof candidate === "string" ? candidate : candidate.name;
75
- if (name.startsWith(line)) {
76
- prefixMatches.push(candidate);
77
- } else if (
78
- query.length > 0 &&
79
- name.slice(queryStartIndex).includes(query)
80
- ) {
81
- partialMatches.push(candidate);
82
- }
83
- }
84
-
85
- return [...prefixMatches, ...partialMatches];
86
- }
87
-
88
- /**
89
- * Return the longest common prefix of the given strings.
90
- * @param {string[]} strings
91
- * @returns {string}
92
- */
93
- function commonPrefix(strings) {
94
- if (strings.length === 0) return "";
95
- let prefix = strings[0];
96
- for (let i = 1; i < strings.length; i++) {
97
- while (!strings[i].startsWith(prefix)) {
98
- prefix = prefix.slice(0, -1);
99
- }
100
- }
101
- return prefix;
102
- }
103
-
104
- /**
105
- * Display completion candidates and invoke the readline callback.
106
- *
107
- * Node.js readline normally requires two consecutive Tab presses to show the
108
- * candidate list. This helper lets readline handle the common-prefix
109
- * auto-completion first, then prints the candidate list on the next tick and
110
- * redraws the prompt so the display stays clean.
111
- *
112
- * @param {import("node:readline").Interface} rl
113
- * @param {(string | CompletionCandidate)[]} candidates
114
- * @param {string} line
115
- * @param {(err: Error | null, result: [string[], string]) => void} callback
116
- */
117
- function showCompletions(rl, candidates, line, callback) {
118
- const names = candidates.map((c) => (typeof c === "string" ? c : c.name));
119
- if (candidates.length <= 1) {
120
- callback(null, [names, line]);
121
- return;
122
- }
123
- const prefix = commonPrefix(names);
124
- if (prefix.length > line.length) {
125
- // Let readline insert the common prefix.
126
- callback(null, [[prefix], line]);
127
- } else {
128
- // Nothing new to insert.
129
- callback(null, [[], line]);
130
- }
131
- // After readline finishes its own refresh, print the candidate list and
132
- // redraw the prompt line. We cannot use rl.prompt(true) because its
133
- // internal _refreshLine clears everything below the prompt start, which
134
- // erases the candidate list we just wrote. Instead we manually re-output
135
- // the prompt and current line content.
136
- setTimeout(() => {
137
- const maxLength = process.stdout.columns ?? 100;
138
- const list = candidates
139
- .map((c) => {
140
- if (typeof c === "string") return c;
141
- const nameText = c.name.padEnd(25);
142
- const separator = " - ";
143
- const descText = c.description;
144
-
145
- // 画面幅に合わせて説明文をカット(色を付ける前に計算)
146
- const availableWidth =
147
- maxLength - nameText.length - separator.length - 3;
148
- const displayDesc =
149
- descText.length > availableWidth && availableWidth > 0
150
- ? `${descText.slice(0, availableWidth)}...`
151
- : descText;
152
-
153
- const name = styleText("cyan", nameText);
154
- const description = styleText("dim", displayDesc);
155
- return `${name}${separator}${description}`;
156
- })
157
- .join("\r\n");
158
- process.stdout.write(`\r\n${list}\r\n`);
159
- process.stdout.write(`${rl.getPrompt()}${rl.line}`);
160
- }, 0);
161
- }
162
21
 
163
22
  const HELP_MESSAGE = [
164
23
  "Commands:",
@@ -190,57 +49,6 @@ const HELP_MESSAGE = [
190
49
  .replace(/^ {2}\/.+?(?= - )/gm, (m) => styleText("cyan", m))
191
50
  .replace(/^ {2}.+?(?= - )/gm, (m) => styleText("blue", m));
192
51
 
193
- // Bracketed paste mode sequences
194
- const BRACKETED_PASTE_START = "\x1b[200~";
195
- const BRACKETED_PASTE_END = "\x1b[201~";
196
-
197
- // Store for pasted content
198
- const pastedContentStore = new Map();
199
-
200
- /**
201
- * Generate a short hash for paste reference
202
- * @param {string} content
203
- * @returns {string}
204
- */
205
- function generatePasteHash(content) {
206
- let hash = 0;
207
- for (let i = 0; i < content.length; i++) {
208
- const char = content.charCodeAt(i);
209
- hash = (hash << 5) - hash + char;
210
- hash = hash & hash; // Convert to 32bit integer
211
- }
212
- return Math.abs(hash).toString(16).padStart(6, "0").slice(0, 6);
213
- }
214
-
215
- /**
216
- * Resolve paste placeholders and append context tags
217
- * @param {string} input
218
- * @returns {string}
219
- */
220
- function resolvePastePlaceholders(input) {
221
- /** @type {string[]} */
222
- const contexts = [];
223
-
224
- // Collect paste content for context tags while keeping placeholders
225
- const text = input.replace(/\[pasted#([a-f0-9]{6})\]/g, (match, hash) => {
226
- const content = pastedContentStore.get(hash);
227
- if (content !== undefined) {
228
- pastedContentStore.delete(hash); // Clean up after use
229
- contexts.push(
230
- `<context location="pasted#${hash}">\n${content}\n</context>`,
231
- );
232
- }
233
- return match; // Keep placeholder in text
234
- });
235
-
236
- // Append contexts to the end of input
237
- if (contexts.length > 0) {
238
- return [text, ...contexts].join("\n\n");
239
- }
240
-
241
- return text;
242
- }
243
-
244
52
  /**
245
53
  * @typedef {object} CliOptions
246
54
  * @property {UserEventEmitter} userEventEmitter
@@ -275,61 +83,6 @@ export function startInteractiveSession({
275
83
  subagentName: "",
276
84
  };
277
85
 
278
- /**
279
- * @param {string} id
280
- * @param {string} goal
281
- * @returns {Promise<void>}
282
- */
283
- async function invokeAgent(id, goal) {
284
- const agentRoles = await loadAgentRoles(claudeCodePlugins);
285
- const agent = agentRoles.get(id);
286
- const name = agent ? id : `custom:${id}`;
287
-
288
- const [goalTextContent, ...goalImages] = await loadUserMessageContext(goal);
289
- const goalText =
290
- goalTextContent?.type === "text" ? goalTextContent.text : goal;
291
-
292
- const messageText = `Delegate to "${name}" agent with goal: ${goalText}`;
293
- userEventEmitter.emit("userInput", [
294
- { type: "text", text: messageText },
295
- ...goalImages,
296
- ]);
297
- }
298
-
299
- /**
300
- * @param {string} id
301
- * @param {string} args
302
- * @param {string} displayInvocation
303
- * @returns {Promise<void>}
304
- */
305
- async function invokePrompt(id, args, displayInvocation) {
306
- const prompts = await loadPrompts(claudeCodePlugins);
307
- const prompt = prompts.get(id);
308
-
309
- if (!prompt) {
310
- console.log(styleText("red", `\nPrompt not found: ${id}`));
311
- state.turn = true;
312
- cli.prompt();
313
- return;
314
- }
315
-
316
- const [argsTextContent, ...argsImages] = args
317
- ? await loadUserMessageContext(args)
318
- : [];
319
- const argsText =
320
- argsTextContent?.type === "text" ? argsTextContent.text : args;
321
-
322
- const invocation = `${displayInvocation}${argsText ? ` ${argsText}` : ""}`;
323
- const message = prompt.isSkill
324
- ? `System: This prompt was invoked as "${invocation}".\nPrompt path: ${prompt.filePath}\n\n${prompt.content}`
325
- : `System: This prompt was invoked as "${invocation}".\n\n${prompt.content}`;
326
-
327
- userEventEmitter.emit("userInput", [
328
- { type: "text", text: message },
329
- ...argsImages,
330
- ]);
331
- }
332
-
333
86
  const getCliPrompt = (subagentName = "") =>
334
87
  [
335
88
  "",
@@ -343,175 +96,6 @@ export function startInteractiveSession({
343
96
  "> ",
344
97
  ].join("\n");
345
98
 
346
- // Indirect reference for exit handler (assigned after confirmExit is defined)
347
- let onExitRequest = () => {};
348
-
349
- // Create a transform stream to handle bracketed paste before readline
350
- let inPasteMode = false;
351
- let pasteBuffer = "";
352
-
353
- const pasteTransform = new Transform({
354
- transform(chunk, _encoding, callback) {
355
- let data = chunk.toString("utf8");
356
-
357
- // Handle Ctrl-C and Ctrl-D
358
- if (data.includes("\x03") || data.includes("\x04")) {
359
- // Ctrl-C / Ctrl-D: request exit (handled by confirmExit)
360
- onExitRequest();
361
- callback();
362
- return;
363
- }
364
-
365
- while (data.length > 0) {
366
- if (inPasteMode) {
367
- const endIdx = data.indexOf(BRACKETED_PASTE_END);
368
- if (endIdx !== -1) {
369
- // End of paste
370
- pasteBuffer += data.slice(0, endIdx);
371
- data = data.slice(endIdx + BRACKETED_PASTE_END.length);
372
- inPasteMode = false;
373
-
374
- // Handle paste content
375
- if (pasteBuffer) {
376
- // Remove trailing newline for single-line paste detection
377
- const trimmedPaste = pasteBuffer.replace(/\n$/, "");
378
-
379
- // For single-line paste, insert directly without placeholder
380
- if (!trimmedPaste.includes("\n")) {
381
- this.push(trimmedPaste);
382
- } else {
383
- // For multi-line paste, use placeholder
384
- const hash = generatePasteHash(pasteBuffer);
385
- pastedContentStore.set(hash, pasteBuffer);
386
- this.push(`[pasted#${hash}] `);
387
- }
388
- }
389
- pasteBuffer = "";
390
- } else {
391
- // Still in paste mode
392
- pasteBuffer += data;
393
- data = "";
394
- }
395
- } else {
396
- const startIdx = data.indexOf(BRACKETED_PASTE_START);
397
- if (startIdx !== -1) {
398
- // Start of paste
399
- // Output any data before the paste
400
- if (startIdx > 0) {
401
- this.push(data.slice(0, startIdx));
402
- }
403
- data = data.slice(startIdx + BRACKETED_PASTE_START.length);
404
- inPasteMode = true;
405
- pasteBuffer = "";
406
- } else {
407
- // Normal data
408
- this.push(data);
409
- data = "";
410
- }
411
- }
412
- }
413
-
414
- callback();
415
- },
416
- });
417
-
418
- // Set up transformed stdin for readline
419
- process.stdin.pipe(pasteTransform);
420
-
421
- // Enable bracketed paste mode
422
- if (process.stdout.isTTY) {
423
- process.stdout.write("\x1b[?2004h");
424
- }
425
-
426
- let currentCliPrompt = getCliPrompt();
427
- const cli = readline.createInterface({
428
- input: pasteTransform,
429
- output: process.stdout,
430
- prompt: currentCliPrompt,
431
- /**
432
- * @param {string} line
433
- * @param {(err?: Error | null, result?: [string[], string]) => void} callback
434
- */
435
- completer: (line, callback) => {
436
- (async () => {
437
- try {
438
- const prompts = await loadPrompts(claudeCodePlugins);
439
- const agentRoles = await loadAgentRoles(claudeCodePlugins);
440
-
441
- if (line.startsWith("/agents:")) {
442
- const prefix = "/agents:";
443
- const candidates = Array.from(agentRoles.values()).map((a) => ({
444
- name: `${prefix}${a.id}`,
445
- description: a.description,
446
- }));
447
- const hits = findMatches(candidates, line, prefix.length);
448
-
449
- showCompletions(cli, hits, line, callback);
450
- return;
451
- }
452
-
453
- if (line.startsWith("/prompts:")) {
454
- const prefix = "/prompts:";
455
- const candidates = Array.from(prompts.values()).map((p) => ({
456
- name: `${prefix}${p.id}`,
457
- description: p.description,
458
- }));
459
- const hits = findMatches(candidates, line, prefix.length);
460
-
461
- showCompletions(cli, hits, line, callback);
462
- return;
463
- }
464
-
465
- if (line.startsWith("/")) {
466
- const shortcuts = Array.from(prompts.values())
467
- .filter((p) => p.isShortcut)
468
- .map((p) => ({
469
- name: `/${p.id}`,
470
- description: p.description,
471
- }));
472
-
473
- const allCommands = [...SLASH_COMMANDS, ...shortcuts].filter(
474
- (cmd) => {
475
- const name = typeof cmd === "string" ? cmd : cmd.name;
476
- return (
477
- name !== "/<id>" &&
478
- (name === "/agents:" || !name.startsWith("/agent:")) &&
479
- (name === "/prompts:" || !name.startsWith("/prompt:"))
480
- );
481
- },
482
- );
483
-
484
- const hits = findMatches(allCommands, line, 1);
485
-
486
- showCompletions(cli, hits, line, callback);
487
- return;
488
- }
489
-
490
- callback(null, [[], line]);
491
- } catch (err) {
492
- const error = err instanceof Error ? err : new Error(String(err));
493
- callback(error, [[], line]);
494
- }
495
- })();
496
- },
497
- });
498
-
499
- // Disable automatic prompt redraw on resize during agent turn
500
- // @ts-expect-error - internal property
501
- const originalRefreshLine = cli._refreshLine?.bind(cli);
502
- if (originalRefreshLine) {
503
- // @ts-expect-error - internal property
504
- cli._refreshLine = (...args) => {
505
- if (state.turn) {
506
- originalRefreshLine(...args);
507
- }
508
- };
509
- }
510
-
511
- readline.emitKeypressEvents(process.stdin);
512
- if (process.stdin.isTTY) {
513
- process.stdin.setRawMode(true);
514
- }
515
99
  // Cleanup handler to disable bracketed paste mode on exit
516
100
  const cleanup = () => {
517
101
  if (process.stdout.isTTY) {
@@ -547,12 +131,53 @@ export function startInteractiveSession({
547
131
  console.log(styleText("yellow", "\nPress Ctrl-C or Ctrl-D again to exit."));
548
132
  };
549
133
 
550
- // Wire up exit request handler for Ctrl-C / Ctrl-D
551
- onExitRequest = confirmExit;
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
+
145
+ let currentCliPrompt = getCliPrompt();
146
+ /** @type {import("node:readline").Interface} */
147
+ const cli = readline.createInterface({
148
+ input: pasteTransform,
149
+ output: process.stdout,
150
+ prompt: currentCliPrompt,
151
+ completer: createCompleter(() => cli, claudeCodePlugins),
152
+ });
153
+
154
+ // Disable automatic prompt redraw on resize during agent turn
155
+ // @ts-expect-error - internal property
156
+ const originalRefreshLine = cli._refreshLine?.bind(cli);
157
+ if (originalRefreshLine) {
158
+ // @ts-expect-error - internal property
159
+ cli._refreshLine = (...args) => {
160
+ if (state.turn) {
161
+ originalRefreshLine(...args);
162
+ }
163
+ };
164
+ }
165
+
166
+ readline.emitKeypressEvents(process.stdin);
167
+ if (process.stdin.isTTY) {
168
+ process.stdin.setRawMode(true);
169
+ }
552
170
 
553
171
  // Handle readline close (e.g., stdin closed externally)
554
172
  cli.on("close", handleExit);
555
173
 
174
+ const handleCommand = createCommandHandler({
175
+ agentCommands,
176
+ userEventEmitter,
177
+ claudeCodePlugins,
178
+ helpMessage: HELP_MESSAGE,
179
+ });
180
+
556
181
  /**
557
182
  * Process the complete user input.
558
183
  * @param {string} input
@@ -575,182 +200,11 @@ export function startInteractiveSession({
575
200
  cli.setPrompt(currentCliPrompt);
576
201
  await consumeInterruptMessage();
577
202
 
578
- if (["/help", "help"].includes(inputTrimmed.toLowerCase())) {
579
- console.log(`\n${HELP_MESSAGE}`);
203
+ const result = await handleCommand(inputTrimmed);
204
+ if (result === "prompt") {
580
205
  state.turn = true;
581
206
  cli.prompt();
582
- return;
583
207
  }
584
-
585
- if (inputTrimmed.startsWith("!")) {
586
- const fileRange = parseFileRange(inputTrimmed.slice(1));
587
- if (fileRange instanceof Error) {
588
- console.log(styleText("red", `\n${fileRange.message}`));
589
- state.turn = true;
590
- cli.prompt();
591
- return;
592
- }
593
-
594
- const fileContent = await readFileRange(fileRange);
595
- if (fileContent instanceof Error) {
596
- console.log(styleText("red", `\n${fileContent.message}`));
597
- state.turn = true;
598
- cli.prompt();
599
- return;
600
- }
601
-
602
- const messageWithContext = await loadUserMessageContext(fileContent);
603
-
604
- userEventEmitter.emit("userInput", messageWithContext);
605
- return;
606
- }
607
-
608
- if (inputTrimmed.toLowerCase() === "/dump") {
609
- await agentCommands.dumpMessages();
610
- state.turn = true;
611
- cli.prompt();
612
- return;
613
- }
614
-
615
- if (inputTrimmed.toLowerCase() === "/load") {
616
- await agentCommands.loadMessages();
617
- state.turn = true;
618
- cli.prompt();
619
- return;
620
- }
621
-
622
- if (inputTrimmed.toLowerCase() === "/cost") {
623
- const summary = agentCommands.getCostSummary();
624
- console.log(formatCostSummary(summary));
625
- state.turn = true;
626
- cli.prompt();
627
- return;
628
- }
629
-
630
- if (inputTrimmed === "/agents") {
631
- const agentRoles = await loadAgentRoles(claudeCodePlugins);
632
-
633
- console.log(styleText("bold", "\nAvailable Agent Roles:"));
634
- if (agentRoles.size === 0) {
635
- console.log(" No agent roles found.");
636
- } else {
637
- for (const role of agentRoles.values()) {
638
- const maxLength = process.stdout.columns ?? 100;
639
- const line = ` ${styleText("cyan", role.id.padEnd(20))} - ${role.description}`;
640
- console.log(
641
- line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
642
- );
643
- }
644
- }
645
- state.turn = true;
646
- cli.prompt();
647
- return;
648
- }
649
-
650
- if (inputTrimmed.startsWith("/prompts")) {
651
- const prompts = await loadPrompts(claudeCodePlugins);
652
-
653
- if (inputTrimmed === "/prompts") {
654
- console.log(styleText("bold", "\nAvailable Prompts:"));
655
- if (prompts.size === 0) {
656
- console.log(" No prompts found.");
657
- } else {
658
- for (const prompt of prompts.values()) {
659
- const maxLength = process.stdout.columns ?? 100;
660
- const line = ` ${styleText("cyan", prompt.id.padEnd(20))} - ${prompt.description}`;
661
- console.log(
662
- line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
663
- );
664
- }
665
- }
666
- state.turn = true;
667
- cli.prompt();
668
- return;
669
- }
670
-
671
- if (inputTrimmed.startsWith("/prompts:")) {
672
- const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/s);
673
- if (!match) {
674
- console.log(styleText("red", "\nInvalid prompt invocation format."));
675
- state.turn = true;
676
- cli.prompt();
677
- return;
678
- }
679
- await invokePrompt(match[1], match[2] || "", `/prompts:${match[1]}`);
680
- return;
681
- }
682
- }
683
-
684
- if (inputTrimmed.startsWith("/agents:")) {
685
- const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/s);
686
- if (!match) {
687
- console.log(styleText("red", "\nInvalid agent invocation format."));
688
- state.turn = true;
689
- cli.prompt();
690
- return;
691
- }
692
- await invokeAgent(match[1], match[2] || "");
693
- return;
694
- }
695
-
696
- if (inputTrimmed.startsWith("/paste")) {
697
- const prompt = inputTrimmed.slice("/paste".length).trim();
698
- let clipboard;
699
- try {
700
- if (process.platform === "darwin") {
701
- clipboard = execFileSync("pbpaste", { encoding: "utf8" });
702
- } else if (process.platform === "linux") {
703
- clipboard = execFileSync("xsel", ["--clipboard", "--output"], {
704
- encoding: "utf8",
705
- });
706
- } else {
707
- console.log(
708
- styleText(
709
- "red",
710
- `\nUnsupported platform for /paste: ${process.platform}`,
711
- ),
712
- );
713
- state.turn = true;
714
- cli.prompt();
715
- return;
716
- }
717
- } catch (e) {
718
- const errorMessage = e instanceof Error ? e.message : String(e);
719
- console.log(
720
- styleText(
721
- "red",
722
- `\nFailed to get clipboard content: ${errorMessage}`,
723
- ),
724
- );
725
- state.turn = true;
726
- cli.prompt();
727
- return;
728
- }
729
-
730
- const combinedInput = prompt ? `${prompt}\n\n${clipboard}` : clipboard;
731
-
732
- const messageWithContext = await loadUserMessageContext(combinedInput);
733
- userEventEmitter.emit("userInput", messageWithContext);
734
- return;
735
- }
736
-
737
- // Handle shortcuts for prompts in shortcuts/ directory
738
- if (inputTrimmed.startsWith("/")) {
739
- const match = inputTrimmed.match(/^\/([^ ]+)(?:\s+(.*))?$/);
740
- if (match) {
741
- const id = match[1];
742
- const prompts = await loadPrompts(claudeCodePlugins);
743
- const prompt = prompts.get(id);
744
-
745
- if (prompt?.isShortcut) {
746
- await invokePrompt(id, match[2] || "", `/${id}`);
747
- return;
748
- }
749
- }
750
- }
751
-
752
- const messageWithContext = await loadUserMessageContext(inputTrimmed);
753
- userEventEmitter.emit("userInput", messageWithContext);
754
208
  }
755
209
 
756
210
  cli.on("line", async (lineInput) => {
@@ -866,64 +320,3 @@ export function startInteractiveSession({
866
320
  process.on("exit", cleanup);
867
321
  process.on("SIGTERM", cleanup);
868
322
  }
869
-
870
- /**
871
- * @param {Message} message
872
- */
873
- function printMessage(message) {
874
- switch (message.role) {
875
- case "assistant": {
876
- // console.log(styleText("bold", "\nAgent:"));
877
- for (const part of message.content) {
878
- switch (part.type) {
879
- // Note: Streamで表示するためここでは表示しない
880
- // case "thinking":
881
- // console.log(
882
- // [
883
- // styleText("blue", "<thinking>"),
884
- // part.thinking,
885
- // styleText("blue", "</thinking>\n"),
886
- // ].join("\n"),
887
- // );
888
- // break;
889
- // case "text":
890
- // console.log(part.text);
891
- // break;
892
- case "tool_use":
893
- console.log(styleText("bold", "\nTool call:"));
894
- console.log(formatToolUse(part));
895
- break;
896
- }
897
- }
898
- break;
899
- }
900
- case "user": {
901
- for (const part of message.content) {
902
- switch (part.type) {
903
- case "tool_result": {
904
- console.log(styleText("bold", "\nTool result:"));
905
- console.log(formatToolResult(part));
906
- break;
907
- }
908
- case "text": {
909
- console.log(styleText("bold", "\nUser:"));
910
- console.log(part.text);
911
- break;
912
- }
913
- case "image": {
914
- break;
915
- }
916
- default: {
917
- console.log(styleText("bold", "\nUnknown Message Format:"));
918
- console.log(JSON.stringify(part, null, 2));
919
- }
920
- }
921
- }
922
- break;
923
- }
924
- default: {
925
- console.log(styleText("bold", "\nUnknown Message Format:"));
926
- console.log(JSON.stringify(message, null, 2));
927
- }
928
- }
929
- }