@iinm/plain-agent 1.6.0 → 1.7.0
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/README.md +6 -8
- package/package.json +4 -4
- package/src/agent.d.ts +1 -0
- package/src/agent.mjs +14 -0
- package/src/agentLoop.mjs +12 -9
- package/src/cliArgs.mjs +5 -5
- package/src/cliCommands.mjs +270 -0
- package/src/cliCompleter.mjs +222 -0
- package/src/cliFormatter.mjs +63 -1
- package/src/cliInteractive.mjs +64 -661
- package/src/cliPasteTransform.mjs +127 -0
- package/src/context/loadAgentRoles.mjs +5 -0
- package/src/context/loadPrompts.mjs +5 -0
- package/src/env.mjs +0 -5
- package/src/prompt.mjs +7 -6
- package/src/subagent.mjs +11 -4
- package/bin/plain-interrupt +0 -6
- package/src/context/consumeInterruptMessage.mjs +0 -30
|
@@ -745,7 +745,7 @@
|
|
|
745
745
|
"unit": "1M",
|
|
746
746
|
"costs": {
|
|
747
747
|
"input_tokens": 0.75,
|
|
748
|
-
"cached_tokens": -0.675,
|
|
748
|
+
"input_tokens_details.cached_tokens": -0.675,
|
|
749
749
|
"output_tokens": 4.5
|
|
750
750
|
}
|
|
751
751
|
}
|
|
@@ -772,7 +772,7 @@
|
|
|
772
772
|
"unit": "1M",
|
|
773
773
|
"costs": {
|
|
774
774
|
"input_tokens": 0.75,
|
|
775
|
-
"cached_tokens": -0.675,
|
|
775
|
+
"input_tokens_details.cached_tokens": -0.675,
|
|
776
776
|
"output_tokens": 4.5
|
|
777
777
|
}
|
|
778
778
|
}
|
|
@@ -799,7 +799,7 @@
|
|
|
799
799
|
"unit": "1M",
|
|
800
800
|
"costs": {
|
|
801
801
|
"input_tokens": 0.75,
|
|
802
|
-
"cached_tokens": -0.675,
|
|
802
|
+
"input_tokens_details.cached_tokens": -0.675,
|
|
803
803
|
"output_tokens": 4.5
|
|
804
804
|
}
|
|
805
805
|
}
|
|
@@ -826,7 +826,7 @@
|
|
|
826
826
|
"unit": "1M",
|
|
827
827
|
"costs": {
|
|
828
828
|
"input_tokens": 2.5,
|
|
829
|
-
"cached_tokens": -2.25,
|
|
829
|
+
"input_tokens_details.cached_tokens": -2.25,
|
|
830
830
|
"output_tokens": 15
|
|
831
831
|
}
|
|
832
832
|
}
|
|
@@ -853,7 +853,7 @@
|
|
|
853
853
|
"unit": "1M",
|
|
854
854
|
"costs": {
|
|
855
855
|
"input_tokens": 2.5,
|
|
856
|
-
"cached_tokens": -2.25,
|
|
856
|
+
"input_tokens_details.cached_tokens": -2.25,
|
|
857
857
|
"output_tokens": 15
|
|
858
858
|
}
|
|
859
859
|
}
|
|
@@ -880,7 +880,7 @@
|
|
|
880
880
|
"unit": "1M",
|
|
881
881
|
"costs": {
|
|
882
882
|
"input_tokens": 2.5,
|
|
883
|
-
"cached_tokens": -2.25,
|
|
883
|
+
"input_tokens_details.cached_tokens": -2.25,
|
|
884
884
|
"output_tokens": 15
|
|
885
885
|
}
|
|
886
886
|
}
|
package/README.md
CHANGED
|
@@ -238,9 +238,10 @@ Run in batch mode (non-interactive).
|
|
|
238
238
|
In batch mode, config files are not loaded automatically. Only the files specified with `--config` are loaded.
|
|
239
239
|
|
|
240
240
|
```sh
|
|
241
|
-
plain batch
|
|
242
|
-
|
|
243
|
-
|
|
241
|
+
plain batch \
|
|
242
|
+
-c ~/.config/plain-agent/config.local.json \
|
|
243
|
+
-c .plain-agent/config.json \
|
|
244
|
+
"Add tests for ..."
|
|
244
245
|
```
|
|
245
246
|
|
|
246
247
|
Display the help message.
|
|
@@ -249,11 +250,9 @@ Display the help message.
|
|
|
249
250
|
/help
|
|
250
251
|
```
|
|
251
252
|
|
|
252
|
-
Interrupt the agent while it's running
|
|
253
|
+
Interrupt the agent while it's running:
|
|
253
254
|
|
|
254
|
-
|
|
255
|
-
plain-interrupt Stop and report the progress
|
|
256
|
-
```
|
|
255
|
+
Press **Ctrl-C** to pause auto-approve. The agent will finish the current tool call, then return to the prompt.
|
|
257
256
|
|
|
258
257
|
## Available Tools
|
|
259
258
|
|
|
@@ -281,7 +280,6 @@ The agent can use the following tools to assist with tasks:
|
|
|
281
280
|
\__ .plain-agent/
|
|
282
281
|
\__ config.json # Project-specific configuration
|
|
283
282
|
\__ config.local.json # Project-specific local configuration (including secrets)
|
|
284
|
-
\__ interrupt-message.txt # Interrupt message consumed by the agent
|
|
285
283
|
\__ memory/ # Task-specific memory files
|
|
286
284
|
\__ prompts/ # Project-specific prompts
|
|
287
285
|
\__ agents/ # Project-specific agent roles
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iinm/plain-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "A lightweight CLI-based coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -36,11 +36,11 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@aws-crypto/sha256-js": "^5.2.0",
|
|
39
|
-
"@aws-sdk/credential-providers": "^3.
|
|
39
|
+
"@aws-sdk/credential-providers": "^3.1026.0",
|
|
40
40
|
"@cfworker/json-schema": "^4.1.1",
|
|
41
41
|
"@modelcontextprotocol/client": "^2.0.0-alpha.2",
|
|
42
|
-
"@smithy/protocol-http": "^5.3.
|
|
43
|
-
"@smithy/signature-v4": "^5.3.
|
|
42
|
+
"@smithy/protocol-http": "^5.3.13",
|
|
43
|
+
"@smithy/signature-v4": "^5.3.13",
|
|
44
44
|
"diff": "^8.0.4",
|
|
45
45
|
"js-yaml": "^4.1.1"
|
|
46
46
|
},
|
package/src/agent.d.ts
CHANGED
package/src/agent.mjs
CHANGED
|
@@ -144,6 +144,16 @@ export function createAgent({
|
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
// Pause signal: set by Ctrl-C during agent execution, checked after each tool batch completes
|
|
148
|
+
let paused = false;
|
|
149
|
+
/** @type {import("./agentLoop.mjs").PauseSignal} */
|
|
150
|
+
const pauseSignal = {
|
|
151
|
+
isPaused: () => paused,
|
|
152
|
+
reset: () => {
|
|
153
|
+
paused = false;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
147
157
|
const agentLoop = createAgentLoop({
|
|
148
158
|
callModel,
|
|
149
159
|
stateManager,
|
|
@@ -152,6 +162,7 @@ export function createAgent({
|
|
|
152
162
|
agentEventEmitter,
|
|
153
163
|
toolUseApprover,
|
|
154
164
|
subagentManager,
|
|
165
|
+
pauseSignal,
|
|
155
166
|
});
|
|
156
167
|
|
|
157
168
|
userEventEmitter.on("userInput", agentLoop.handleUserInput);
|
|
@@ -163,6 +174,9 @@ export function createAgent({
|
|
|
163
174
|
dumpMessages,
|
|
164
175
|
loadMessages,
|
|
165
176
|
getCostSummary: () => costTracker.calculateCost(),
|
|
177
|
+
pauseAutoApprove: () => {
|
|
178
|
+
paused = true;
|
|
179
|
+
},
|
|
166
180
|
},
|
|
167
181
|
};
|
|
168
182
|
}
|
package/src/agentLoop.mjs
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { styleText } from "node:util";
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} PauseSignal
|
|
13
|
+
* @property {() => boolean} isPaused - Returns true if auto-approve should be paused
|
|
14
|
+
* @property {() => void} reset - Resets the paused state
|
|
15
|
+
*/
|
|
11
16
|
|
|
12
17
|
/**
|
|
13
18
|
* @typedef {Object} AgentLoopConfig
|
|
@@ -18,6 +23,7 @@ import { consumeInterruptMessage } from "./context/consumeInterruptMessage.mjs";
|
|
|
18
23
|
* @property {AgentEventEmitter} agentEventEmitter - Event emitter for agent events
|
|
19
24
|
* @property {ToolUseApprover} toolUseApprover - Tool use approval checker
|
|
20
25
|
* @property {SubagentManager} subagentManager - Subagent manager instance
|
|
26
|
+
* @property {PauseSignal} pauseSignal - Signal to pause auto-approve after current tool completes
|
|
21
27
|
*/
|
|
22
28
|
|
|
23
29
|
/**
|
|
@@ -36,6 +42,7 @@ export function createAgentLoop({
|
|
|
36
42
|
agentEventEmitter,
|
|
37
43
|
toolUseApprover,
|
|
38
44
|
subagentManager,
|
|
45
|
+
pauseSignal,
|
|
39
46
|
}) {
|
|
40
47
|
const inputHandler = createInputHandler({
|
|
41
48
|
stateManager,
|
|
@@ -198,14 +205,10 @@ export function createAgentLoop({
|
|
|
198
205
|
stateManager.appendMessages([{ role: "user", content: toolResults }]);
|
|
199
206
|
}
|
|
200
207
|
|
|
201
|
-
|
|
202
|
-
if (
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
role: "user",
|
|
206
|
-
content: [{ type: "text", text: interruptMessage }],
|
|
207
|
-
},
|
|
208
|
-
]);
|
|
208
|
+
// Check if auto-approve was paused by Ctrl-C during tool execution
|
|
209
|
+
if (pauseSignal.isPaused()) {
|
|
210
|
+
pauseSignal.reset();
|
|
211
|
+
break;
|
|
209
212
|
}
|
|
210
213
|
}
|
|
211
214
|
}
|
package/src/cliArgs.mjs
CHANGED
|
@@ -125,10 +125,12 @@ Usage: plain [options]
|
|
|
125
125
|
Options:
|
|
126
126
|
-m, --model <model+variant> Model to use
|
|
127
127
|
-h, --help Show this help message
|
|
128
|
-
-c, --config <file> Config file to load
|
|
128
|
+
-c, --config <file> Config file to load (repeatable)
|
|
129
129
|
|
|
130
130
|
Subcommands:
|
|
131
|
-
batch <task> Run in batch mode with the given task instruction
|
|
131
|
+
batch <task> Run in batch mode with the given task instruction.
|
|
132
|
+
Config files are NOT auto-loaded in batch mode;
|
|
133
|
+
use -c to specify config files explicitly.
|
|
132
134
|
list-models List available models
|
|
133
135
|
install-claude-code-plugins Install Claude Code plugins
|
|
134
136
|
|
|
@@ -137,9 +139,7 @@ Examples:
|
|
|
137
139
|
plain batch \\
|
|
138
140
|
-c ~/.config/plain-agent/config.local.json \\
|
|
139
141
|
-c .plain-agent/config.json \\
|
|
140
|
-
"Add tests for
|
|
141
|
-
plain list-models
|
|
142
|
-
plain install-claude-code-plugins
|
|
142
|
+
"Add tests for ..."
|
|
143
143
|
`);
|
|
144
144
|
process.exit(exitCode);
|
|
145
145
|
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { UserEventEmitter, AgentCommands } from "./agent"
|
|
3
|
+
* @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
7
|
+
import { styleText } from "node:util";
|
|
8
|
+
import { formatCostSummary } from "./cliFormatter.mjs";
|
|
9
|
+
import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
|
|
10
|
+
import { loadPrompts } from "./context/loadPrompts.mjs";
|
|
11
|
+
import { loadUserMessageContext } from "./context/loadUserMessageContext.mjs";
|
|
12
|
+
import { CLAUDE_CODE_COMPATIBILITY_NOTES } from "./prompt.mjs";
|
|
13
|
+
import { parseFileRange } from "./utils/parseFileRange.mjs";
|
|
14
|
+
import { readFileRange } from "./utils/readFileRange.mjs";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @typedef {"prompt" | "continue"} CommandResult
|
|
18
|
+
* - "prompt": return control to prompt (state.turn = true; cli.prompt())
|
|
19
|
+
* - "continue": agent is now running, do nothing
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} CommandHandlerDeps
|
|
24
|
+
* @property {AgentCommands} agentCommands
|
|
25
|
+
* @property {UserEventEmitter} userEventEmitter
|
|
26
|
+
* @property {ClaudeCodePlugin[] | undefined} claudeCodePlugins
|
|
27
|
+
* @property {string} helpMessage
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Create command handler function for processing slash commands.
|
|
32
|
+
*
|
|
33
|
+
* @param {CommandHandlerDeps} deps
|
|
34
|
+
* @returns {(input: string) => Promise<CommandResult>}
|
|
35
|
+
*/
|
|
36
|
+
export function createCommandHandler({
|
|
37
|
+
agentCommands,
|
|
38
|
+
userEventEmitter,
|
|
39
|
+
claudeCodePlugins,
|
|
40
|
+
helpMessage,
|
|
41
|
+
}) {
|
|
42
|
+
/**
|
|
43
|
+
* Invoke an agent with the given id and goal.
|
|
44
|
+
* @param {string} id
|
|
45
|
+
* @param {string} goal
|
|
46
|
+
* @returns {Promise<CommandResult>}
|
|
47
|
+
*/
|
|
48
|
+
async function invokeAgent(id, goal) {
|
|
49
|
+
const agentRoles = await loadAgentRoles(claudeCodePlugins);
|
|
50
|
+
const agent = agentRoles.get(id);
|
|
51
|
+
const name = agent ? id : `custom:${id}`;
|
|
52
|
+
|
|
53
|
+
const [goalTextContent, ...goalImages] = await loadUserMessageContext(goal);
|
|
54
|
+
const goalText =
|
|
55
|
+
goalTextContent?.type === "text" ? goalTextContent.text : goal;
|
|
56
|
+
|
|
57
|
+
const messageText = `Delegate to "${name}" agent with goal: ${goalText}`;
|
|
58
|
+
userEventEmitter.emit("userInput", [
|
|
59
|
+
{ type: "text", text: messageText },
|
|
60
|
+
...goalImages,
|
|
61
|
+
]);
|
|
62
|
+
return "continue";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Invoke a prompt with the given id, args, and display invocation.
|
|
67
|
+
* @param {string} id
|
|
68
|
+
* @param {string} args
|
|
69
|
+
* @param {string} displayInvocation
|
|
70
|
+
* @returns {Promise<CommandResult>}
|
|
71
|
+
*/
|
|
72
|
+
async function invokePrompt(id, args, displayInvocation) {
|
|
73
|
+
const prompts = await loadPrompts(claudeCodePlugins);
|
|
74
|
+
const prompt = prompts.get(id);
|
|
75
|
+
|
|
76
|
+
if (!prompt) {
|
|
77
|
+
console.log(styleText("red", `\nPrompt not found: ${id}`));
|
|
78
|
+
return "prompt";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const [argsTextContent, ...argsImages] = args
|
|
82
|
+
? await loadUserMessageContext(args)
|
|
83
|
+
: [];
|
|
84
|
+
const argsText =
|
|
85
|
+
argsTextContent?.type === "text" ? argsTextContent.text : args;
|
|
86
|
+
|
|
87
|
+
const invocation = `${displayInvocation}${argsText ? ` ${argsText}` : ""}`;
|
|
88
|
+
const promptContent = prompt.claudeOriginated
|
|
89
|
+
? `${prompt.content}\n\n---\n\n${CLAUDE_CODE_COMPATIBILITY_NOTES}`
|
|
90
|
+
: prompt.content;
|
|
91
|
+
const message = prompt.isSkill
|
|
92
|
+
? `System: This prompt was invoked as "${invocation}".\nPrompt path: ${prompt.filePath}\n\n${promptContent}`
|
|
93
|
+
: `System: This prompt was invoked as "${invocation}".\n\n${promptContent}`;
|
|
94
|
+
|
|
95
|
+
userEventEmitter.emit("userInput", [
|
|
96
|
+
{ type: "text", text: message },
|
|
97
|
+
...argsImages,
|
|
98
|
+
]);
|
|
99
|
+
return "continue";
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Handle a complete user input string and return a CommandResult.
|
|
104
|
+
* @param {string} inputTrimmed
|
|
105
|
+
* @returns {Promise<CommandResult>}
|
|
106
|
+
*/
|
|
107
|
+
return async function handleCommand(inputTrimmed) {
|
|
108
|
+
// /help or help
|
|
109
|
+
if (["/help", "help"].includes(inputTrimmed.toLowerCase())) {
|
|
110
|
+
console.log(`\n${helpMessage}`);
|
|
111
|
+
return "prompt";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// !path — read file content and emit as user input
|
|
115
|
+
if (inputTrimmed.startsWith("!")) {
|
|
116
|
+
const fileRange = parseFileRange(inputTrimmed.slice(1));
|
|
117
|
+
if (fileRange instanceof Error) {
|
|
118
|
+
console.log(styleText("red", `\n${fileRange.message}`));
|
|
119
|
+
return "prompt";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const fileContent = await readFileRange(fileRange);
|
|
123
|
+
if (fileContent instanceof Error) {
|
|
124
|
+
console.log(styleText("red", `\n${fileContent.message}`));
|
|
125
|
+
return "prompt";
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const messageWithContext = await loadUserMessageContext(fileContent);
|
|
129
|
+
userEventEmitter.emit("userInput", messageWithContext);
|
|
130
|
+
return "continue";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// /dump
|
|
134
|
+
if (inputTrimmed.toLowerCase() === "/dump") {
|
|
135
|
+
await agentCommands.dumpMessages();
|
|
136
|
+
return "prompt";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// /load
|
|
140
|
+
if (inputTrimmed.toLowerCase() === "/load") {
|
|
141
|
+
await agentCommands.loadMessages();
|
|
142
|
+
return "prompt";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// /cost
|
|
146
|
+
if (inputTrimmed.toLowerCase() === "/cost") {
|
|
147
|
+
const summary = agentCommands.getCostSummary();
|
|
148
|
+
console.log(formatCostSummary(summary));
|
|
149
|
+
return "prompt";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// /agents or /agents:id
|
|
153
|
+
if (inputTrimmed === "/agents") {
|
|
154
|
+
const agentRoles = await loadAgentRoles(claudeCodePlugins);
|
|
155
|
+
|
|
156
|
+
console.log(styleText("bold", "\nAvailable Agent Roles:"));
|
|
157
|
+
if (agentRoles.size === 0) {
|
|
158
|
+
console.log(" No agent roles found.");
|
|
159
|
+
} else {
|
|
160
|
+
for (const role of agentRoles.values()) {
|
|
161
|
+
const maxLength = process.stdout.columns ?? 100;
|
|
162
|
+
const line = ` ${styleText("cyan", role.id.padEnd(20))} - ${role.description}`;
|
|
163
|
+
console.log(
|
|
164
|
+
line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return "prompt";
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (inputTrimmed.startsWith("/agents:")) {
|
|
172
|
+
const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/s);
|
|
173
|
+
if (!match) {
|
|
174
|
+
console.log(styleText("red", "\nInvalid agent invocation format."));
|
|
175
|
+
return "prompt";
|
|
176
|
+
}
|
|
177
|
+
return await invokeAgent(match[1], match[2] || "");
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// /prompts or /prompts:id
|
|
181
|
+
if (inputTrimmed.startsWith("/prompts")) {
|
|
182
|
+
const prompts = await loadPrompts(claudeCodePlugins);
|
|
183
|
+
|
|
184
|
+
if (inputTrimmed === "/prompts") {
|
|
185
|
+
console.log(styleText("bold", "\nAvailable Prompts:"));
|
|
186
|
+
if (prompts.size === 0) {
|
|
187
|
+
console.log(" No prompts found.");
|
|
188
|
+
} else {
|
|
189
|
+
for (const prompt of prompts.values()) {
|
|
190
|
+
const maxLength = process.stdout.columns ?? 100;
|
|
191
|
+
const line = ` ${styleText("cyan", prompt.id.padEnd(20))} - ${prompt.description}`;
|
|
192
|
+
console.log(
|
|
193
|
+
line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return "prompt";
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (inputTrimmed.startsWith("/prompts:")) {
|
|
201
|
+
const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/s);
|
|
202
|
+
if (!match) {
|
|
203
|
+
console.log(styleText("red", "\nInvalid prompt invocation format."));
|
|
204
|
+
return "prompt";
|
|
205
|
+
}
|
|
206
|
+
return await invokePrompt(
|
|
207
|
+
match[1],
|
|
208
|
+
match[2] || "",
|
|
209
|
+
`/prompts:${match[1]}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// /paste — read clipboard and emit as user input
|
|
215
|
+
if (inputTrimmed.startsWith("/paste")) {
|
|
216
|
+
const prompt = inputTrimmed.slice("/paste".length).trim();
|
|
217
|
+
let clipboard;
|
|
218
|
+
try {
|
|
219
|
+
if (process.platform === "darwin") {
|
|
220
|
+
clipboard = execFileSync("pbpaste", { encoding: "utf8" });
|
|
221
|
+
} else if (process.platform === "linux") {
|
|
222
|
+
clipboard = execFileSync("xsel", ["--clipboard", "--output"], {
|
|
223
|
+
encoding: "utf8",
|
|
224
|
+
});
|
|
225
|
+
} else {
|
|
226
|
+
console.log(
|
|
227
|
+
styleText(
|
|
228
|
+
"red",
|
|
229
|
+
`\nUnsupported platform for /paste: ${process.platform}`,
|
|
230
|
+
),
|
|
231
|
+
);
|
|
232
|
+
return "prompt";
|
|
233
|
+
}
|
|
234
|
+
} catch (e) {
|
|
235
|
+
const errorMessage = e instanceof Error ? e.message : String(e);
|
|
236
|
+
console.log(
|
|
237
|
+
styleText(
|
|
238
|
+
"red",
|
|
239
|
+
`\nFailed to get clipboard content: ${errorMessage}`,
|
|
240
|
+
),
|
|
241
|
+
);
|
|
242
|
+
return "prompt";
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const combinedInput = prompt ? `${prompt}\n\n${clipboard}` : clipboard;
|
|
246
|
+
const messageWithContext = await loadUserMessageContext(combinedInput);
|
|
247
|
+
userEventEmitter.emit("userInput", messageWithContext);
|
|
248
|
+
return "continue";
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// /<id> — shortcut for prompts in shortcuts/ directory
|
|
252
|
+
if (inputTrimmed.startsWith("/")) {
|
|
253
|
+
const match = inputTrimmed.match(/^\/([^ ]+)(?:\s+(.*))?$/);
|
|
254
|
+
if (match) {
|
|
255
|
+
const id = match[1];
|
|
256
|
+
const prompts = await loadPrompts(claudeCodePlugins);
|
|
257
|
+
const prompt = prompts.get(id);
|
|
258
|
+
|
|
259
|
+
if (prompt?.isShortcut) {
|
|
260
|
+
return await invokePrompt(id, match[2] || "", `/${id}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Default: emit as plain user input
|
|
266
|
+
const messageWithContext = await loadUserMessageContext(inputTrimmed);
|
|
267
|
+
userEventEmitter.emit("userInput", messageWithContext);
|
|
268
|
+
return "continue";
|
|
269
|
+
};
|
|
270
|
+
}
|