@iinm/plain-agent 1.1.1 → 1.3.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/README.md +106 -36
- package/package.json +4 -8
- package/src/claudeCodePlugin.mjs +164 -0
- package/src/cliArgs.mjs +114 -36
- package/src/cliFormatter.mjs +0 -9
- package/src/cliInteractive.mjs +5 -3
- package/src/config.d.ts +6 -20
- package/src/config.mjs +12 -8
- package/src/context/loadAgentRoles.mjs +12 -6
- package/src/context/loadPrompts.mjs +17 -10
- package/src/main.mjs +41 -25
- package/src/mcp.mjs +4 -8
- package/src/tools/askURL.mjs +201 -0
- package/src/tools/askWeb.mjs +200 -0
- package/src/tools/askGoogle.mjs +0 -135
- package/src/tools/fetchWebPage.mjs +0 -96
- package/src/tools/tavilySearch.d.ts +0 -6
- package/src/tools/tavilySearch.mjs +0 -57
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ npm install -g @iinm/plain-agent
|
|
|
33
33
|
List available models.
|
|
34
34
|
|
|
35
35
|
```sh
|
|
36
|
-
plain
|
|
36
|
+
plain list-models
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
Create the configuration.
|
|
@@ -87,19 +87,33 @@ Create the configuration.
|
|
|
87
87
|
|
|
88
88
|
// Optional
|
|
89
89
|
"tools": {
|
|
90
|
-
"
|
|
91
|
-
"
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
90
|
+
"askWeb": {
|
|
91
|
+
"provider": "gemini",
|
|
92
|
+
"apiKey": "FIXME",
|
|
93
|
+
"model": "gemini-3-flash-preview"
|
|
94
|
+
// Optional
|
|
95
|
+
// "baseURL": "<proxy_url>"
|
|
95
96
|
|
|
96
97
|
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
97
|
-
// "
|
|
98
|
+
// "provider": "gemini-vertex-ai",
|
|
98
99
|
// "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
|
|
100
|
+
// "model": "gemini-3-flash-preview"
|
|
101
|
+
// Optional:
|
|
102
|
+
// "account": "<service_account_email>"
|
|
99
103
|
},
|
|
100
104
|
|
|
101
|
-
"
|
|
102
|
-
"
|
|
105
|
+
"askURL": {
|
|
106
|
+
"provider": "gemini",
|
|
107
|
+
"apiKey": "FIXME"
|
|
108
|
+
// Optional
|
|
109
|
+
// "baseURL": "<proxy_url>"
|
|
110
|
+
|
|
111
|
+
// Or use Vertex AI (Requires gcloud CLI to get authentication token)
|
|
112
|
+
// "provider": "gemini-vertex-ai",
|
|
113
|
+
// "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
|
|
114
|
+
// "model": "gemini-3-flash-preview"
|
|
115
|
+
// Optional:
|
|
116
|
+
// "account": "<service_account_email>"
|
|
103
117
|
}
|
|
104
118
|
},
|
|
105
119
|
|
|
@@ -108,7 +122,7 @@ Create the configuration.
|
|
|
108
122
|
```
|
|
109
123
|
|
|
110
124
|
<details>
|
|
111
|
-
<summary>Other provider examples</summary>
|
|
125
|
+
<summary><b>Other provider examples</b></summary>
|
|
112
126
|
|
|
113
127
|
```js
|
|
114
128
|
{
|
|
@@ -135,6 +149,59 @@ Create the configuration.
|
|
|
135
149
|
```
|
|
136
150
|
</details>
|
|
137
151
|
|
|
152
|
+
<details>
|
|
153
|
+
<summary><b>Bedrock example using Claude Japan inference profiles</b></summary>
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
{
|
|
157
|
+
"models": [
|
|
158
|
+
{
|
|
159
|
+
"name": "claude-haiku-4-5",
|
|
160
|
+
"variant": "thinking-16k-bedrock-jp",
|
|
161
|
+
"platform": {
|
|
162
|
+
"name": "bedrock",
|
|
163
|
+
"variant": "jp"
|
|
164
|
+
},
|
|
165
|
+
"model": {
|
|
166
|
+
"format": "anthropic",
|
|
167
|
+
"config": {
|
|
168
|
+
"model": "jp.anthropic.claude-haiku-4-5-20251001-v1:0",
|
|
169
|
+
"max_tokens": 32768,
|
|
170
|
+
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"name": "claude-sonnet-4-6",
|
|
176
|
+
"variant": "thinking-16k-bedrock-jp",
|
|
177
|
+
"platform": {
|
|
178
|
+
"name": "bedrock",
|
|
179
|
+
"variant": "jp"
|
|
180
|
+
},
|
|
181
|
+
"model": {
|
|
182
|
+
"format": "anthropic",
|
|
183
|
+
"config": {
|
|
184
|
+
"model": "jp.anthropic.claude-sonnet-4-6",
|
|
185
|
+
"max_tokens": 32768,
|
|
186
|
+
"thinking": { "type": "enabled", "budget_tokens": 16384 }
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
"platforms": [
|
|
192
|
+
{
|
|
193
|
+
"name": "bedrock",
|
|
194
|
+
"variant": "jp",
|
|
195
|
+
"baseURL": "https://bedrock-runtime.ap-northeast-1.amazonaws.com",
|
|
196
|
+
"awsProfile": "FIXME"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
</details>
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
138
205
|
Run the agent.
|
|
139
206
|
|
|
140
207
|
```sh
|
|
@@ -148,7 +215,7 @@ Run in batch mode (non-interactive).
|
|
|
148
215
|
In batch mode, config files are not loaded automatically. Only the files specified with `--config` are loaded.
|
|
149
216
|
|
|
150
217
|
```sh
|
|
151
|
-
plain
|
|
218
|
+
plain batch "Add tests for src/main.mjs" \
|
|
152
219
|
--config ~/.config/plain-agent/config.local.json \
|
|
153
220
|
--config .plain-agent/config.json
|
|
154
221
|
```
|
|
@@ -173,9 +240,8 @@ The agent can use the following tools to assist with tasks:
|
|
|
173
240
|
- **write_file**: Write a file.
|
|
174
241
|
- **patch_file**: Patch a file.
|
|
175
242
|
- **tmux_command**: Run a tmux command.
|
|
176
|
-
- **
|
|
177
|
-
- **
|
|
178
|
-
- **search_web**: Search the web for information (requires Tavily API key).
|
|
243
|
+
- **ask_web**: Use the web search to answer questions that need up-to-date information or supporting sources. (requires Google API key or Vertex AI configuration).
|
|
244
|
+
- **ask_url**: Use one or more provided URLs to answer a question. Include the URLs in your question. (requires Google API key or Vertex AI configuration).
|
|
179
245
|
- **delegate_to_subagent**: Delegate a subtask to a subagent. The agent switches to a subagent role within the same conversation, focusing on the specified goal.
|
|
180
246
|
- **report_as_subagent**: Report completion and return to the main agent. Used by subagents to communicate results and restore the main agent role. After reporting, the subagent's conversation history is removed from the context.
|
|
181
247
|
|
|
@@ -210,7 +276,7 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
210
276
|
### Example
|
|
211
277
|
|
|
212
278
|
<details>
|
|
213
|
-
<summary>YOLO mode example (requires sandbox for safety)</summary>
|
|
279
|
+
<summary><b>YOLO mode example (requires sandbox for safety)</b></summary>
|
|
214
280
|
|
|
215
281
|
```js
|
|
216
282
|
{
|
|
@@ -218,12 +284,6 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
218
284
|
"defaultAction": "deny",
|
|
219
285
|
"maxApprovals": 100,
|
|
220
286
|
"patterns": [
|
|
221
|
-
// Prohibit direct access to external URLs (even GET requests can leak data via URL parameters)
|
|
222
|
-
{
|
|
223
|
-
"toolName": "fetch_web_page",
|
|
224
|
-
"action": "deny",
|
|
225
|
-
"reason": "Use ask_google instead"
|
|
226
|
-
},
|
|
227
287
|
{
|
|
228
288
|
"toolName": { "$regex": "^(write_file|patch_file)$" },
|
|
229
289
|
"action": "allow"
|
|
@@ -233,11 +293,10 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
233
293
|
"action": "allow"
|
|
234
294
|
},
|
|
235
295
|
{
|
|
236
|
-
"toolName": "
|
|
296
|
+
"toolName": { "$regex": "^(ask_web|ask_url)$" },
|
|
237
297
|
"action": "allow"
|
|
238
298
|
}
|
|
239
|
-
|
|
240
|
-
// ⚠️ Never do this. fetch_web_page and mcp run outside the sandbox, so they can send anything externally.
|
|
299
|
+
// ⚠️ Never do this. mcp run outside the sandbox, so they can send anything externally.
|
|
241
300
|
// {
|
|
242
301
|
// "toolName": { "$regex": "." },
|
|
243
302
|
// "action": "allow"
|
|
@@ -264,11 +323,12 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
264
323
|
</details>
|
|
265
324
|
|
|
266
325
|
<details>
|
|
267
|
-
<summary>Full example</summary>
|
|
326
|
+
<summary><b>Full example</b></summary>
|
|
268
327
|
|
|
269
328
|
```js
|
|
270
329
|
{
|
|
271
330
|
"autoApproval": {
|
|
331
|
+
"defaultAction": "ask",
|
|
272
332
|
// The maximum number of automatic approvals.
|
|
273
333
|
"maxApprovals": 50,
|
|
274
334
|
// Patterns are evaluated in order. First match wins.
|
|
@@ -290,8 +350,9 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
290
350
|
"input": { "command": "npm", "args": ["run", { "$regex": "^(check|test|lint|fix)$" }] },
|
|
291
351
|
"action": "allow"
|
|
292
352
|
},
|
|
353
|
+
|
|
293
354
|
{
|
|
294
|
-
"toolName": "
|
|
355
|
+
"toolName": { "$regex": "^(ask_web|ask_url)$" },
|
|
295
356
|
"action": "allow"
|
|
296
357
|
},
|
|
297
358
|
|
|
@@ -459,20 +520,29 @@ The agent searches for subagent definitions in the following directories:
|
|
|
459
520
|
|
|
460
521
|
Example:
|
|
461
522
|
|
|
462
|
-
```sh
|
|
463
|
-
git clone --depth 1 https://github.com/anthropics/claude-code .plain-agent/claude-code-plugins/anthropics/claude-code
|
|
464
|
-
git clone --depth 1 https://github.com/awslabs/agent-plugins .plain-agent/claude-code-plugins/awslabs/agent-plugins
|
|
465
|
-
```
|
|
466
|
-
|
|
467
523
|
```js
|
|
468
524
|
// .plain-agent/config.json
|
|
469
525
|
{
|
|
470
526
|
"claudeCodePlugins": [
|
|
471
|
-
{
|
|
472
|
-
|
|
527
|
+
{
|
|
528
|
+
"source": "https://github.com/anthropics/claude-code",
|
|
529
|
+
"plugins": [
|
|
530
|
+
{ "name": "feature-dev", "path": "plugins/feature-dev" },
|
|
531
|
+
{ "name": "code-review", "path": "plugins/code-review" }
|
|
532
|
+
]
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
"source": "https://github.com/anthropics/skills",
|
|
536
|
+
"plugins": [
|
|
537
|
+
{ "name": "anthropics", "path": "", "only": "pdf|pptx" }
|
|
538
|
+
]
|
|
539
|
+
}
|
|
473
540
|
]
|
|
474
541
|
}
|
|
542
|
+
```
|
|
475
543
|
|
|
544
|
+
```sh
|
|
545
|
+
plain install-claude-code-plugins
|
|
476
546
|
```
|
|
477
547
|
|
|
478
548
|
## Development
|
|
@@ -494,7 +564,7 @@ npx npm-check-updates -t minor -c 3 -u
|
|
|
494
564
|
## Appendix: Creating Least-Privilege Users for Cloud Providers
|
|
495
565
|
|
|
496
566
|
<details>
|
|
497
|
-
<summary>Amazon Bedrock</summary>
|
|
567
|
+
<summary><b>Amazon Bedrock</b></summary>
|
|
498
568
|
|
|
499
569
|
```sh
|
|
500
570
|
# IAM Identity Center
|
|
@@ -574,7 +644,7 @@ aws bedrock-runtime invoke-model \
|
|
|
574
644
|
</details>
|
|
575
645
|
|
|
576
646
|
<details>
|
|
577
|
-
<summary>Azure - Microsoft Foundry</summary>
|
|
647
|
+
<summary><b>Azure - Microsoft Foundry</b></summary>
|
|
578
648
|
|
|
579
649
|
```sh
|
|
580
650
|
resource_group=FIXME
|
|
@@ -607,7 +677,7 @@ az login --service-principal -u "$app_id" -p "$app_secret" --tenant "$tenant_id"
|
|
|
607
677
|
</details>
|
|
608
678
|
|
|
609
679
|
<details>
|
|
610
|
-
<summary>Google Cloud Vertex AI</summary>
|
|
680
|
+
<summary><b>Google Cloud Vertex AI</b></summary>
|
|
611
681
|
|
|
612
682
|
```sh
|
|
613
683
|
project_id=FIXME
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iinm/plain-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A lightweight CLI-based coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -37,21 +37,17 @@
|
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@aws-crypto/sha256-js": "^5.2.0",
|
|
39
39
|
"@aws-sdk/credential-providers": "^3.1001.0",
|
|
40
|
-
"@
|
|
41
|
-
"@
|
|
40
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
41
|
+
"@modelcontextprotocol/client": "^2.0.0-alpha.2",
|
|
42
42
|
"@smithy/protocol-http": "^5.3.10",
|
|
43
43
|
"@smithy/signature-v4": "^5.3.10",
|
|
44
44
|
"diff": "^8.0.3",
|
|
45
|
-
"js-yaml": "^4.1.1"
|
|
46
|
-
"jsdom": "^28.1.0",
|
|
47
|
-
"turndown": "^7.2.2"
|
|
45
|
+
"js-yaml": "^4.1.1"
|
|
48
46
|
},
|
|
49
47
|
"devDependencies": {
|
|
50
48
|
"@biomejs/biome": "^2.4.5",
|
|
51
49
|
"@types/js-yaml": "^4.0.9",
|
|
52
|
-
"@types/jsdom": "^28.0.0",
|
|
53
50
|
"@types/node": "^22.19.13",
|
|
54
|
-
"@types/turndown": "^5.0.6",
|
|
55
51
|
"typescript": "^5.9.3"
|
|
56
52
|
}
|
|
57
53
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadAppConfig } from "./config.mjs";
|
|
5
|
+
import { CLAUDE_CODE_PLUGIN_DIR } from "./env.mjs";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {Object} ClaudeCodePluginRepo
|
|
9
|
+
* @property {string} source
|
|
10
|
+
* @property {Array<{name: string, path: string, only?: string}>} plugins
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {Object} ClaudeCodePlugin
|
|
15
|
+
* @property {string} name
|
|
16
|
+
* @property {string} path
|
|
17
|
+
* @property {RegExp} [only]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Resolve plugin paths from hierarchical config structure.
|
|
22
|
+
* Converts {source, plugins} to flat {name, path, only} with full paths.
|
|
23
|
+
* @param {ClaudeCodePluginRepo[]} repos
|
|
24
|
+
* @returns {ClaudeCodePlugin[]}
|
|
25
|
+
*/
|
|
26
|
+
export function resolvePluginPaths(repos) {
|
|
27
|
+
if (!repos) return [];
|
|
28
|
+
|
|
29
|
+
/** @type {ClaudeCodePlugin[]} */
|
|
30
|
+
const result = [];
|
|
31
|
+
|
|
32
|
+
for (const repo of repos) {
|
|
33
|
+
const ownerRepo = extractOwnerRepo(repo.source);
|
|
34
|
+
if (!ownerRepo) {
|
|
35
|
+
console.warn(`Invalid source URL: ${repo.source}`);
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for (const plugin of repo.plugins) {
|
|
40
|
+
// Compile only pattern to RegExp
|
|
41
|
+
let only;
|
|
42
|
+
if (plugin.only) {
|
|
43
|
+
try {
|
|
44
|
+
only = new RegExp(plugin.only);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.warn(
|
|
47
|
+
`Invalid regex pattern "${plugin.only}" for plugin "${plugin.name}":`,
|
|
48
|
+
err instanceof Error ? err.message : String(err),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
result.push({
|
|
54
|
+
name: plugin.name,
|
|
55
|
+
path: path.join(CLAUDE_CODE_PLUGIN_DIR, ownerRepo, plugin.path),
|
|
56
|
+
only,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Install Claude Code plugins by cloning repositories.
|
|
66
|
+
*/
|
|
67
|
+
export async function installClaudeCodePlugins() {
|
|
68
|
+
const { appConfig } = await loadAppConfig({ skipTrustCheck: true });
|
|
69
|
+
const repos = appConfig.claudeCodePlugins ?? [];
|
|
70
|
+
|
|
71
|
+
if (repos.length === 0) {
|
|
72
|
+
console.log("No plugins configured.");
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let installed = 0;
|
|
77
|
+
let skipped = 0;
|
|
78
|
+
let failed = 0;
|
|
79
|
+
|
|
80
|
+
// Track paths for summary
|
|
81
|
+
/** @type {string[]} */
|
|
82
|
+
const installedPaths = [];
|
|
83
|
+
/** @type {string[]} */
|
|
84
|
+
const skippedPaths = [];
|
|
85
|
+
|
|
86
|
+
// Ensure plugin directory exists
|
|
87
|
+
await fs.mkdir(CLAUDE_CODE_PLUGIN_DIR, { recursive: true });
|
|
88
|
+
|
|
89
|
+
for (const repo of repos) {
|
|
90
|
+
const ownerRepo = extractOwnerRepo(repo.source);
|
|
91
|
+
if (!ownerRepo) {
|
|
92
|
+
console.error(`❌ Invalid source URL: ${repo.source}`);
|
|
93
|
+
failed++;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const destPath = path.join(CLAUDE_CODE_PLUGIN_DIR, ownerRepo);
|
|
98
|
+
|
|
99
|
+
// Check if already exists
|
|
100
|
+
const exists = await fs
|
|
101
|
+
.access(destPath)
|
|
102
|
+
.then(() => true)
|
|
103
|
+
.catch(() => false);
|
|
104
|
+
if (exists) {
|
|
105
|
+
console.log(`⏭️ Skipping ${repo.source} → ${destPath}: already installed`);
|
|
106
|
+
skippedPaths.push(destPath);
|
|
107
|
+
skipped++;
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Clone the repository
|
|
112
|
+
console.log(`📥 Installing ${repo.source}...`);
|
|
113
|
+
try {
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
execFile(
|
|
116
|
+
"git",
|
|
117
|
+
["clone", "--depth", "1", repo.source, destPath],
|
|
118
|
+
(err) => {
|
|
119
|
+
if (err) reject(err);
|
|
120
|
+
else resolve(undefined);
|
|
121
|
+
},
|
|
122
|
+
);
|
|
123
|
+
});
|
|
124
|
+
console.log(`✅ Installed to ${destPath}`);
|
|
125
|
+
installedPaths.push(destPath);
|
|
126
|
+
installed++;
|
|
127
|
+
} catch (error) {
|
|
128
|
+
console.error(
|
|
129
|
+
`❌ Failed to install: ${error instanceof Error ? error.message : String(error)}`,
|
|
130
|
+
);
|
|
131
|
+
failed++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
console.log(
|
|
136
|
+
`\n📊 Summary: ${installed} installed, ${skipped} skipped, ${failed} failed`,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (installedPaths.length > 0) {
|
|
140
|
+
console.log("\nInstalled:");
|
|
141
|
+
for (const p of installedPaths) {
|
|
142
|
+
console.log(` • ${p}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (skippedPaths.length > 0) {
|
|
147
|
+
console.log("\nSkipped:");
|
|
148
|
+
for (const p of skippedPaths) {
|
|
149
|
+
console.log(` • ${p}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Extract owner/repo from source URL.
|
|
156
|
+
* @param {string} source
|
|
157
|
+
* @returns {string|null}
|
|
158
|
+
*/
|
|
159
|
+
function extractOwnerRepo(source) {
|
|
160
|
+
// Handle: https://github.com/owner/repo
|
|
161
|
+
// Handle: git@github.com:owner/repo.git
|
|
162
|
+
const match = source.match(/[:/]([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
163
|
+
return match ? match[1] : null;
|
|
164
|
+
}
|
package/src/cliArgs.mjs
CHANGED
|
@@ -1,10 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {HelpSubcommand | InteractiveSubcommand | BatchSubcommand | ListModelsSubcommand | InstallClaudeCodePluginsSubcommand} Subcommand
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {{ type: 'help' }} HelpSubcommand
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{ type: 'interactive', config: string[], model: string | null }} InteractiveSubcommand
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{ type: 'batch', task: string, config: string[], model: string | null }} BatchSubcommand
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @typedef {{ type: 'list-models' }} ListModelsSubcommand
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @typedef {{ type: 'install-claude-code-plugins' }} InstallClaudeCodePluginsSubcommand
|
|
23
|
+
*/
|
|
24
|
+
|
|
1
25
|
/**
|
|
2
26
|
* @typedef {Object} CliArgs
|
|
3
|
-
* @property {
|
|
4
|
-
* @property {boolean} showHelp - Whether to show help message
|
|
5
|
-
* @property {boolean} listModels - Whether to list available models
|
|
6
|
-
* @property {string|null} batch - Task instruction for batch mode
|
|
7
|
-
* @property {string[]} config - Paths to additional config files for batch mode
|
|
27
|
+
* @property {Subcommand} subcommand - The subcommand to execute
|
|
8
28
|
*/
|
|
9
29
|
|
|
10
30
|
/**
|
|
@@ -14,30 +34,81 @@
|
|
|
14
34
|
*/
|
|
15
35
|
export function parseCliArgs(argv) {
|
|
16
36
|
const args = argv.slice(2);
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
};
|
|
37
|
+
const subcommandName = args[0];
|
|
38
|
+
|
|
39
|
+
if (["-h", "--help", "help"].includes(subcommandName)) {
|
|
40
|
+
return {
|
|
41
|
+
subcommand: { type: "help" },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
25
44
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
if (!subcommandName || subcommandName.startsWith("-")) {
|
|
46
|
+
// Interactive mode (default)
|
|
47
|
+
const config = [];
|
|
48
|
+
let model = null;
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < args.length; i++) {
|
|
51
|
+
if (args[i] === "-m" || args[i] === "--model") {
|
|
52
|
+
if (args[i + 1]) {
|
|
53
|
+
model = args[i + 1];
|
|
54
|
+
i++;
|
|
55
|
+
}
|
|
56
|
+
} else if (args[i] === "-c" || args[i] === "--config") {
|
|
57
|
+
if (args[i + 1]) {
|
|
58
|
+
config.push(args[i + 1]);
|
|
59
|
+
i++;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
37
62
|
}
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
subcommand: { type: "interactive", config, model },
|
|
66
|
+
};
|
|
38
67
|
}
|
|
39
68
|
|
|
40
|
-
|
|
69
|
+
if (subcommandName === "batch") {
|
|
70
|
+
const batchArgs = args.slice(1);
|
|
71
|
+
|
|
72
|
+
let task = null;
|
|
73
|
+
let model = null;
|
|
74
|
+
const config = [];
|
|
75
|
+
|
|
76
|
+
for (let i = 0; i < batchArgs.length; i++) {
|
|
77
|
+
if (batchArgs[i] === "-m" || batchArgs[i] === "--model") {
|
|
78
|
+
if (batchArgs[i + 1]) {
|
|
79
|
+
model = batchArgs[i + 1];
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
} else if (batchArgs[i] === "-c" || batchArgs[i] === "--config") {
|
|
83
|
+
if (batchArgs[i + 1]) {
|
|
84
|
+
config.push(batchArgs[i + 1]);
|
|
85
|
+
i++;
|
|
86
|
+
}
|
|
87
|
+
} else if (!batchArgs[i].startsWith("-") && !task) {
|
|
88
|
+
task = batchArgs[i];
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
subcommand: { type: "batch", task: task || "", config, model },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (subcommandName === "list-models") {
|
|
98
|
+
return {
|
|
99
|
+
subcommand: { type: "list-models" },
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (subcommandName === "install-claude-code-plugins") {
|
|
104
|
+
return {
|
|
105
|
+
subcommand: { type: "install-claude-code-plugins" },
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
subcommand: { type: "help" },
|
|
111
|
+
};
|
|
41
112
|
}
|
|
42
113
|
|
|
43
114
|
/**
|
|
@@ -46,22 +117,29 @@ export function parseCliArgs(argv) {
|
|
|
46
117
|
*/
|
|
47
118
|
export function printHelp(exitCode = 0) {
|
|
48
119
|
console.log(`
|
|
49
|
-
Usage:
|
|
50
|
-
|
|
120
|
+
Usage: plain [options]
|
|
121
|
+
plain batch [options] <task>
|
|
122
|
+
plain list-models
|
|
123
|
+
plain install-claude-code-plugins
|
|
51
124
|
|
|
52
125
|
Options:
|
|
53
126
|
-m, --model <model+variant> Model to use
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
-h, --help Show this help message
|
|
128
|
+
-c, --config <file> Config file to load
|
|
129
|
+
|
|
130
|
+
Subcommands:
|
|
131
|
+
batch <task> Run in batch mode with the given task instruction
|
|
132
|
+
list-models List available models
|
|
133
|
+
install-claude-code-plugins Install Claude Code plugins
|
|
59
134
|
|
|
60
135
|
Examples:
|
|
61
|
-
|
|
62
|
-
plain
|
|
63
|
-
|
|
64
|
-
|
|
136
|
+
plain -m gpt-5.4+thinking-medium
|
|
137
|
+
plain batch \\
|
|
138
|
+
-c ~/.config/plain-agent/config.local.json \\
|
|
139
|
+
-c .plain-agent/config.json \\
|
|
140
|
+
"Add tests for src/main.mjs"
|
|
141
|
+
plain list-models
|
|
142
|
+
plain install-claude-code-plugins
|
|
65
143
|
`);
|
|
66
144
|
process.exit(exitCode);
|
|
67
145
|
}
|
package/src/cliFormatter.mjs
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* @import { PatchFileInput } from "./tools/patchFile"
|
|
5
5
|
* @import { WriteFileInput } from "./tools/writeFile"
|
|
6
6
|
* @import { TmuxCommandInput } from "./tools/tmuxCommand"
|
|
7
|
-
* @import { TavilySearchInput } from "./tools/tavilySearch"
|
|
8
7
|
* @import { DelegateToSubagentInput } from "./tools/delegateToSubagent"
|
|
9
8
|
*/
|
|
10
9
|
|
|
@@ -86,14 +85,6 @@ export function formatToolUse(toolUse) {
|
|
|
86
85
|
].join("\n");
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
if (toolName === "search_web") {
|
|
90
|
-
/** @type {Partial<TavilySearchInput>} */
|
|
91
|
-
const tavilySearchInput = input;
|
|
92
|
-
return [`tool: ${toolName}`, `query: ${tavilySearchInput.query}`].join(
|
|
93
|
-
"\n",
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
88
|
if (toolName === "delegate_to_subagent") {
|
|
98
89
|
/** @type {Partial<DelegateToSubagentInput>} */
|
|
99
90
|
const delegateInput = input;
|
package/src/cliInteractive.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @import { Message } from "./model"
|
|
3
3
|
* @import { UserEventEmitter, AgentEventEmitter, AgentCommands } from "./agent"
|
|
4
|
-
* @import {
|
|
4
|
+
* @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { execFileSync } from "node:child_process";
|
|
@@ -197,7 +197,7 @@ const HELP_MESSAGE = [
|
|
|
197
197
|
* @property {string} notifyCmd
|
|
198
198
|
* @property {boolean} sandbox
|
|
199
199
|
* @property {() => Promise<void>} onStop
|
|
200
|
-
* @property {
|
|
200
|
+
* @property {ClaudeCodePlugin[]} [claudeCodePlugins]
|
|
201
201
|
*/
|
|
202
202
|
|
|
203
203
|
/**
|
|
@@ -257,7 +257,9 @@ export function startInteractiveSession({
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
const invocation = `${displayInvocation}${args ? ` ${args}` : ""}`;
|
|
260
|
-
const message =
|
|
260
|
+
const message = prompt.isSkill
|
|
261
|
+
? `System: This prompt was invoked as "${invocation}".\nPrompt path: ${prompt.filePath}\n\n${prompt.content}`
|
|
262
|
+
: `System: This prompt was invoked as "${invocation}".\n\n${prompt.content}`;
|
|
261
263
|
|
|
262
264
|
console.log(styleText("gray", "\n<prompt>"));
|
|
263
265
|
console.log(message);
|