@iinm/plain-agent 1.2.1 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -15
- package/package.json +1 -1
- package/src/claudeCodePlugin.mjs +164 -0
- package/src/cliArgs.mjs +114 -36
- package/src/cliFormatter.mjs +1 -1
- package/src/cliInteractive.mjs +5 -3
- package/src/config.d.ts +2 -6
- package/src/context/loadAgentRoles.mjs +12 -6
- package/src/context/loadPrompts.mjs +17 -10
- package/src/main.mjs +35 -11
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.
|
|
@@ -122,7 +122,7 @@ Create the configuration.
|
|
|
122
122
|
```
|
|
123
123
|
|
|
124
124
|
<details>
|
|
125
|
-
<summary>Other provider examples</summary>
|
|
125
|
+
<summary><b>Other provider examples</b></summary>
|
|
126
126
|
|
|
127
127
|
```js
|
|
128
128
|
{
|
|
@@ -149,6 +149,59 @@ Create the configuration.
|
|
|
149
149
|
```
|
|
150
150
|
</details>
|
|
151
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
|
+
|
|
152
205
|
Run the agent.
|
|
153
206
|
|
|
154
207
|
```sh
|
|
@@ -162,7 +215,7 @@ Run in batch mode (non-interactive).
|
|
|
162
215
|
In batch mode, config files are not loaded automatically. Only the files specified with `--config` are loaded.
|
|
163
216
|
|
|
164
217
|
```sh
|
|
165
|
-
plain
|
|
218
|
+
plain batch "Add tests for src/main.mjs" \
|
|
166
219
|
--config ~/.config/plain-agent/config.local.json \
|
|
167
220
|
--config .plain-agent/config.json
|
|
168
221
|
```
|
|
@@ -223,7 +276,7 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
223
276
|
### Example
|
|
224
277
|
|
|
225
278
|
<details>
|
|
226
|
-
<summary>YOLO mode example (requires sandbox for safety)</summary>
|
|
279
|
+
<summary><b>YOLO mode example (requires sandbox for safety)</b></summary>
|
|
227
280
|
|
|
228
281
|
```js
|
|
229
282
|
{
|
|
@@ -270,7 +323,7 @@ The agent loads configuration files in the following order. Settings in later fi
|
|
|
270
323
|
</details>
|
|
271
324
|
|
|
272
325
|
<details>
|
|
273
|
-
<summary>Full example</summary>
|
|
326
|
+
<summary><b>Full example</b></summary>
|
|
274
327
|
|
|
275
328
|
```js
|
|
276
329
|
{
|
|
@@ -467,20 +520,29 @@ The agent searches for subagent definitions in the following directories:
|
|
|
467
520
|
|
|
468
521
|
Example:
|
|
469
522
|
|
|
470
|
-
```sh
|
|
471
|
-
git clone --depth 1 https://github.com/anthropics/claude-code .plain-agent/claude-code-plugins/anthropics/claude-code
|
|
472
|
-
git clone --depth 1 https://github.com/awslabs/agent-plugins .plain-agent/claude-code-plugins/awslabs/agent-plugins
|
|
473
|
-
```
|
|
474
|
-
|
|
475
523
|
```js
|
|
476
524
|
// .plain-agent/config.json
|
|
477
525
|
{
|
|
478
526
|
"claudeCodePlugins": [
|
|
479
|
-
{
|
|
480
|
-
|
|
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
|
+
}
|
|
481
540
|
]
|
|
482
541
|
}
|
|
542
|
+
```
|
|
483
543
|
|
|
544
|
+
```sh
|
|
545
|
+
plain install-claude-code-plugins
|
|
484
546
|
```
|
|
485
547
|
|
|
486
548
|
## Development
|
|
@@ -502,7 +564,7 @@ npx npm-check-updates -t minor -c 3 -u
|
|
|
502
564
|
## Appendix: Creating Least-Privilege Users for Cloud Providers
|
|
503
565
|
|
|
504
566
|
<details>
|
|
505
|
-
<summary>Amazon Bedrock</summary>
|
|
567
|
+
<summary><b>Amazon Bedrock</b></summary>
|
|
506
568
|
|
|
507
569
|
```sh
|
|
508
570
|
# IAM Identity Center
|
|
@@ -582,7 +644,7 @@ aws bedrock-runtime invoke-model \
|
|
|
582
644
|
</details>
|
|
583
645
|
|
|
584
646
|
<details>
|
|
585
|
-
<summary>Azure - Microsoft Foundry</summary>
|
|
647
|
+
<summary><b>Azure - Microsoft Foundry</b></summary>
|
|
586
648
|
|
|
587
649
|
```sh
|
|
588
650
|
resource_group=FIXME
|
|
@@ -615,7 +677,7 @@ az login --service-principal -u "$app_id" -p "$app_secret" --tenant "$tenant_id"
|
|
|
615
677
|
</details>
|
|
616
678
|
|
|
617
679
|
<details>
|
|
618
|
-
<summary>Google Cloud Vertex AI</summary>
|
|
680
|
+
<summary><b>Google Cloud Vertex AI</b></summary>
|
|
619
681
|
|
|
620
682
|
```sh
|
|
621
683
|
project_id=FIXME
|
package/package.json
CHANGED
|
@@ -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
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);
|
package/src/config.d.ts
CHANGED
|
@@ -3,11 +3,7 @@ import { ToolUsePattern } from "./tool";
|
|
|
3
3
|
import { AskURLToolOptions } from "./tools/askURL.mjs";
|
|
4
4
|
import { AskWebToolOptions } from "./tools/askWeb.mjs";
|
|
5
5
|
import { ExecCommandSanboxConfig } from "./tools/execCommand";
|
|
6
|
-
|
|
7
|
-
export type ClaudeCodePluginConfig = {
|
|
8
|
-
name: string;
|
|
9
|
-
path: string;
|
|
10
|
-
};
|
|
6
|
+
import { ClaudeCodePluginRepo } from "./claudeCodePlugin.mjs";
|
|
11
7
|
|
|
12
8
|
export type AppConfig = {
|
|
13
9
|
model?: string;
|
|
@@ -25,7 +21,7 @@ export type AppConfig = {
|
|
|
25
21
|
};
|
|
26
22
|
mcpServers?: Record<string, MCPServerConfig>;
|
|
27
23
|
notifyCmd?: string;
|
|
28
|
-
claudeCodePlugins?:
|
|
24
|
+
claudeCodePlugins?: ClaudeCodePluginRepo[];
|
|
29
25
|
};
|
|
30
26
|
|
|
31
27
|
export type MCPServerConfig = {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @import { ClaudeCodePlugin } from "../claudeCodePlugin.mjs" */
|
|
2
|
+
|
|
1
3
|
import crypto from "node:crypto";
|
|
2
4
|
import fs from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
@@ -7,7 +9,6 @@ import {
|
|
|
7
9
|
AGENT_PROJECT_METADATA_DIR,
|
|
8
10
|
AGENT_ROOT,
|
|
9
11
|
AGENT_USER_CONFIG_DIR,
|
|
10
|
-
CLAUDE_CODE_PLUGIN_DIR,
|
|
11
12
|
} from "../env.mjs";
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -21,10 +22,11 @@ import {
|
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Load all agent roles from the predefined directories.
|
|
24
|
-
* @param {
|
|
25
|
+
* @param {ClaudeCodePlugin[]} [claudeCodePlugins]
|
|
25
26
|
* @returns {Promise<Map<string, AgentRole>>}
|
|
26
27
|
*/
|
|
27
28
|
export async function loadAgentRoles(claudeCodePlugins) {
|
|
29
|
+
/** @type {Array<{dir: string, idPrefix: string, only?: RegExp}>} */
|
|
28
30
|
const agentDirs = [
|
|
29
31
|
{
|
|
30
32
|
dir: path.resolve(AGENT_ROOT, ".config", "agents.predefined"),
|
|
@@ -41,11 +43,10 @@ export async function loadAgentRoles(claudeCodePlugins) {
|
|
|
41
43
|
// Add plugin directories if provided
|
|
42
44
|
if (claudeCodePlugins) {
|
|
43
45
|
for (const plugin of claudeCodePlugins) {
|
|
44
|
-
const pluginBase = path.join(CLAUDE_CODE_PLUGIN_DIR, plugin.path);
|
|
45
|
-
|
|
46
46
|
agentDirs.push({
|
|
47
|
-
dir: path.join(
|
|
47
|
+
dir: path.join(plugin.path, "agents"),
|
|
48
48
|
idPrefix: `claude/${plugin.name}:`,
|
|
49
|
+
only: plugin.only,
|
|
49
50
|
});
|
|
50
51
|
}
|
|
51
52
|
}
|
|
@@ -53,7 +54,7 @@ export async function loadAgentRoles(claudeCodePlugins) {
|
|
|
53
54
|
/** @type {Map<string, AgentRole>} */
|
|
54
55
|
const roles = new Map();
|
|
55
56
|
|
|
56
|
-
for (const { dir, idPrefix } of agentDirs) {
|
|
57
|
+
for (const { dir, idPrefix, only } of agentDirs) {
|
|
57
58
|
const files = await getMarkdownFiles(dir).catch((err) => {
|
|
58
59
|
if (err.code !== "ENOENT") {
|
|
59
60
|
console.warn(`Failed to list agent roles in ${dir}:`, err);
|
|
@@ -70,6 +71,11 @@ export async function loadAgentRoles(claudeCodePlugins) {
|
|
|
70
71
|
|
|
71
72
|
if (content === null) continue;
|
|
72
73
|
|
|
74
|
+
// Filter by only pattern if specified
|
|
75
|
+
if (only && !only.test(file)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
73
79
|
let role = parseAgentRole(file, content, fullPath, idPrefix);
|
|
74
80
|
if (role.import) {
|
|
75
81
|
role = await mergeRemoteRole(role, file, fullPath);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/** @import { ClaudeCodePlugin } from "../claudeCodePlugin.mjs" */
|
|
2
|
+
|
|
1
3
|
import { execFileSync } from "node:child_process";
|
|
2
4
|
import crypto from "node:crypto";
|
|
3
5
|
import fs from "node:fs/promises";
|
|
@@ -8,7 +10,6 @@ import {
|
|
|
8
10
|
AGENT_PROJECT_METADATA_DIR,
|
|
9
11
|
AGENT_ROOT,
|
|
10
12
|
AGENT_USER_CONFIG_DIR,
|
|
11
|
-
CLAUDE_CODE_PLUGIN_DIR,
|
|
12
13
|
} from "../env.mjs";
|
|
13
14
|
|
|
14
15
|
/**
|
|
@@ -25,10 +26,11 @@ import {
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
28
|
* Load all prompts from the predefined directories.
|
|
28
|
-
* @param {
|
|
29
|
+
* @param {ClaudeCodePlugin[]} [claudeCodePlugins]
|
|
29
30
|
* @returns {Promise<Map<string, Prompt>>}
|
|
30
31
|
*/
|
|
31
32
|
export async function loadPrompts(claudeCodePlugins) {
|
|
33
|
+
/** @type {Array<{dir: string, idPrefix: string, only?: RegExp}>} */
|
|
32
34
|
const promptDirs = [
|
|
33
35
|
{
|
|
34
36
|
dir: path.resolve(AGENT_ROOT, ".config", "prompts.predefined"),
|
|
@@ -49,18 +51,18 @@ export async function loadPrompts(claudeCodePlugins) {
|
|
|
49
51
|
// Add plugin directories if provided
|
|
50
52
|
if (claudeCodePlugins) {
|
|
51
53
|
for (const plugin of claudeCodePlugins) {
|
|
52
|
-
const pluginBase = path.join(CLAUDE_CODE_PLUGIN_DIR, plugin.path);
|
|
53
|
-
|
|
54
54
|
// Commands
|
|
55
55
|
promptDirs.push({
|
|
56
|
-
dir: path.join(
|
|
56
|
+
dir: path.join(plugin.path, "commands"),
|
|
57
57
|
idPrefix: `claude/${plugin.name}/commands:`,
|
|
58
|
+
only: plugin.only,
|
|
58
59
|
});
|
|
59
60
|
|
|
60
61
|
// Skills
|
|
61
62
|
promptDirs.push({
|
|
62
|
-
dir: path.join(
|
|
63
|
+
dir: path.join(plugin.path, "skills"),
|
|
63
64
|
idPrefix: `claude/${plugin.name}/skills:`,
|
|
65
|
+
only: plugin.only,
|
|
64
66
|
});
|
|
65
67
|
}
|
|
66
68
|
}
|
|
@@ -68,7 +70,7 @@ export async function loadPrompts(claudeCodePlugins) {
|
|
|
68
70
|
/** @type {Map<string, Prompt>} */
|
|
69
71
|
const prompts = new Map();
|
|
70
72
|
|
|
71
|
-
for (const { dir, idPrefix } of promptDirs) {
|
|
73
|
+
for (const { dir, idPrefix, only } of promptDirs) {
|
|
72
74
|
const files = await getMarkdownFiles(dir).catch((err) => {
|
|
73
75
|
if (err.code !== "ENOENT") {
|
|
74
76
|
console.warn(`Failed to list prompts in ${dir}:`, err);
|
|
@@ -85,6 +87,11 @@ export async function loadPrompts(claudeCodePlugins) {
|
|
|
85
87
|
|
|
86
88
|
if (content === null) continue;
|
|
87
89
|
|
|
90
|
+
// Filter by only pattern if specified
|
|
91
|
+
if (only && !only.test(file)) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
88
95
|
// Ignore all files in the skills/ directory except for SKILL.md.
|
|
89
96
|
if (fullPath.match(/\/skills\//) && !file.endsWith("/SKILL.md")) {
|
|
90
97
|
continue;
|
|
@@ -240,6 +247,7 @@ async function getMarkdownFiles(dir, baseDir = dir) {
|
|
|
240
247
|
*/
|
|
241
248
|
function parsePrompt(relativePath, fileContent, fullPath, idPrefix = "") {
|
|
242
249
|
const rawId = relativePath.replace(/\/SKILL\.md$/, "").replace(/\.md$/, "");
|
|
250
|
+
const isSkill = relativePath.endsWith("SKILL.md");
|
|
243
251
|
const isShortcut = rawId.startsWith("shortcuts/");
|
|
244
252
|
const id = isShortcut
|
|
245
253
|
? idPrefix + rawId.replace(/^shortcuts\//, "")
|
|
@@ -257,7 +265,7 @@ function parsePrompt(relativePath, fileContent, fullPath, idPrefix = "") {
|
|
|
257
265
|
content: fileContent.trim(),
|
|
258
266
|
filePath: fullPath,
|
|
259
267
|
isShortcut,
|
|
260
|
-
isSkill
|
|
268
|
+
isSkill,
|
|
261
269
|
};
|
|
262
270
|
}
|
|
263
271
|
|
|
@@ -281,7 +289,7 @@ function parsePrompt(relativePath, fileContent, fullPath, idPrefix = "") {
|
|
|
281
289
|
parseFrontmatterField(match[1], "user-invocable") === "true" ||
|
|
282
290
|
undefined,
|
|
283
291
|
isShortcut,
|
|
284
|
-
isSkill
|
|
292
|
+
isSkill,
|
|
285
293
|
};
|
|
286
294
|
}
|
|
287
295
|
const userInvocable = frontmatter["user-invocable"];
|
|
@@ -304,7 +312,6 @@ function parsePrompt(relativePath, fileContent, fullPath, idPrefix = "") {
|
|
|
304
312
|
* @param {string} field
|
|
305
313
|
* @returns {string | undefined}
|
|
306
314
|
*/
|
|
307
|
-
|
|
308
315
|
function parseFrontmatterField(frontmatter, field) {
|
|
309
316
|
const regex = new RegExp(`^${field}:\\s*(.*)$`, "m");
|
|
310
317
|
const match = frontmatter.match(regex);
|
package/src/main.mjs
CHANGED
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
|
|
5
5
|
import { styleText } from "node:util";
|
|
6
6
|
import { createAgent } from "./agent.mjs";
|
|
7
|
+
import {
|
|
8
|
+
installClaudeCodePlugins,
|
|
9
|
+
resolvePluginPaths,
|
|
10
|
+
} from "./claudeCodePlugin.mjs";
|
|
7
11
|
import { parseCliArgs, printHelp } from "./cliArgs.mjs";
|
|
8
12
|
import { startBatchSession } from "./cliBatch.mjs";
|
|
9
13
|
import { startInteractiveSession } from "./cliInteractive.mjs";
|
|
@@ -29,11 +33,11 @@ import { writeFileTool } from "./tools/writeFile.mjs";
|
|
|
29
33
|
import { createToolUseApprover } from "./toolUseApprover.mjs";
|
|
30
34
|
|
|
31
35
|
const cliArgs = parseCliArgs(process.argv);
|
|
32
|
-
if (cliArgs.
|
|
36
|
+
if (cliArgs.subcommand.type === "help") {
|
|
33
37
|
printHelp();
|
|
34
38
|
}
|
|
35
39
|
|
|
36
|
-
if (cliArgs.
|
|
40
|
+
if (cliArgs.subcommand.type === "list-models") {
|
|
37
41
|
const { appConfig } = await loadAppConfig({ skipTrustCheck: true });
|
|
38
42
|
if (!appConfig.models || appConfig.models.length === 0) {
|
|
39
43
|
console.error("No models found in configuration.");
|
|
@@ -48,6 +52,11 @@ if (cliArgs.listModels) {
|
|
|
48
52
|
process.exit(0);
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
if (cliArgs.subcommand.type === "install-claude-code-plugins") {
|
|
56
|
+
await installClaudeCodePlugins();
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
(async () => {
|
|
52
61
|
const startTime = new Date();
|
|
53
62
|
const sessionId = [
|
|
@@ -56,12 +65,18 @@ if (cliArgs.listModels) {
|
|
|
56
65
|
`0${startTime.getMinutes()}`.slice(-2),
|
|
57
66
|
].join("-");
|
|
58
67
|
const tmuxSessionId = `agent-${sessionId}`;
|
|
59
|
-
|
|
68
|
+
|
|
69
|
+
const isBatchMode = cliArgs.subcommand.type === "batch";
|
|
70
|
+
const configFiles =
|
|
71
|
+
cliArgs.subcommand.type === "batch" ||
|
|
72
|
+
cliArgs.subcommand.type === "interactive"
|
|
73
|
+
? cliArgs.subcommand.config
|
|
74
|
+
: [];
|
|
60
75
|
|
|
61
76
|
const { appConfig, loadedConfigPath } = await loadAppConfig({
|
|
62
77
|
skipUserConfig: isBatchMode,
|
|
63
78
|
skipTrustCheck: isBatchMode,
|
|
64
|
-
configFiles
|
|
79
|
+
configFiles,
|
|
65
80
|
});
|
|
66
81
|
|
|
67
82
|
// In batch mode, skip human-readable output
|
|
@@ -121,9 +136,17 @@ if (cliArgs.listModels) {
|
|
|
121
136
|
}
|
|
122
137
|
}
|
|
123
138
|
|
|
124
|
-
const
|
|
125
|
-
const
|
|
126
|
-
|
|
139
|
+
const modelFromConfig = appConfig.model || "";
|
|
140
|
+
const modelFromArgs =
|
|
141
|
+
cliArgs.subcommand.type === "batch" ||
|
|
142
|
+
cliArgs.subcommand.type === "interactive"
|
|
143
|
+
? cliArgs.subcommand.model
|
|
144
|
+
: null;
|
|
145
|
+
const modelNameWithVariant = modelFromArgs || modelFromConfig;
|
|
146
|
+
|
|
147
|
+
const pluginPaths = resolvePluginPaths(appConfig.claudeCodePlugins ?? []);
|
|
148
|
+
const agentRoles = await loadAgentRoles(pluginPaths);
|
|
149
|
+
const prompts = await loadPrompts(pluginPaths);
|
|
127
150
|
|
|
128
151
|
const prompt = createPrompt({
|
|
129
152
|
username: USER_NAME,
|
|
@@ -216,19 +239,20 @@ if (cliArgs.listModels) {
|
|
|
216
239
|
},
|
|
217
240
|
};
|
|
218
241
|
|
|
219
|
-
if (
|
|
220
|
-
|
|
242
|
+
if (cliArgs.subcommand.type === "batch") {
|
|
243
|
+
const task = cliArgs.subcommand.task;
|
|
244
|
+
if (!task) {
|
|
221
245
|
throw new Error("Batch task is required in batch mode");
|
|
222
246
|
}
|
|
223
247
|
await startBatchSession({
|
|
224
248
|
...sessionOptions,
|
|
225
|
-
task
|
|
249
|
+
task,
|
|
226
250
|
});
|
|
227
251
|
} else {
|
|
228
252
|
startInteractiveSession({
|
|
229
253
|
...sessionOptions,
|
|
230
254
|
notifyCmd: appConfig.notifyCmd || AGENT_NOTIFY_CMD_DEFAULT,
|
|
231
|
-
claudeCodePlugins: appConfig.claudeCodePlugins,
|
|
255
|
+
claudeCodePlugins: resolvePluginPaths(appConfig.claudeCodePlugins ?? []),
|
|
232
256
|
});
|
|
233
257
|
}
|
|
234
258
|
})().catch((err) => {
|