@iinm/plain-agent 1.0.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.
Files changed (79) hide show
  1. package/.config/agents.library/code-simplifier.md +5 -0
  2. package/.config/agents.library/qa-engineer.md +74 -0
  3. package/.config/agents.library/software-architect.md +278 -0
  4. package/.config/agents.predefined/worker.md +3 -0
  5. package/.config/config.predefined.json +825 -0
  6. package/.config/prompts.library/code-review.md +8 -0
  7. package/.config/prompts.library/feature-dev.md +6 -0
  8. package/.config/prompts.predefined/shortcuts/commit-by-user.md +9 -0
  9. package/.config/prompts.predefined/shortcuts/commit.md +10 -0
  10. package/.config/prompts.predefined/shortcuts/general-question.md +6 -0
  11. package/LICENSE +21 -0
  12. package/README.md +624 -0
  13. package/bin/plain +3 -0
  14. package/bin/plain-interrupt +6 -0
  15. package/bin/plain-notify-desktop +19 -0
  16. package/bin/plain-notify-terminal-bell +3 -0
  17. package/package.json +57 -0
  18. package/sandbox/bin/plain-sandbox +972 -0
  19. package/src/agent.d.ts +48 -0
  20. package/src/agent.mjs +159 -0
  21. package/src/agentLoop.mjs +369 -0
  22. package/src/agentState.mjs +41 -0
  23. package/src/cliArgs.mjs +45 -0
  24. package/src/cliFormatter.mjs +217 -0
  25. package/src/cliInteractive.mjs +739 -0
  26. package/src/config.d.ts +48 -0
  27. package/src/config.mjs +168 -0
  28. package/src/context/consumeInterruptMessage.mjs +30 -0
  29. package/src/context/loadAgentRoles.mjs +272 -0
  30. package/src/context/loadPrompts.mjs +312 -0
  31. package/src/context/loadUserMessageContext.mjs +147 -0
  32. package/src/env.mjs +46 -0
  33. package/src/main.mjs +202 -0
  34. package/src/mcp.mjs +202 -0
  35. package/src/model.d.ts +109 -0
  36. package/src/modelCaller.mjs +29 -0
  37. package/src/modelDefinition.d.ts +73 -0
  38. package/src/prompt.mjs +128 -0
  39. package/src/providers/anthropic.d.ts +248 -0
  40. package/src/providers/anthropic.mjs +596 -0
  41. package/src/providers/gemini.d.ts +208 -0
  42. package/src/providers/gemini.mjs +752 -0
  43. package/src/providers/openai.d.ts +281 -0
  44. package/src/providers/openai.mjs +551 -0
  45. package/src/providers/openaiCompatible.d.ts +147 -0
  46. package/src/providers/openaiCompatible.mjs +658 -0
  47. package/src/providers/platform/azure.mjs +42 -0
  48. package/src/providers/platform/bedrock.mjs +74 -0
  49. package/src/providers/platform/googleCloud.mjs +34 -0
  50. package/src/subagent.mjs +247 -0
  51. package/src/tmpfile.mjs +27 -0
  52. package/src/tool.d.ts +74 -0
  53. package/src/toolExecutor.mjs +236 -0
  54. package/src/toolInputValidator.mjs +183 -0
  55. package/src/toolUseApprover.mjs +98 -0
  56. package/src/tools/askGoogle.mjs +135 -0
  57. package/src/tools/delegateToSubagent.d.ts +4 -0
  58. package/src/tools/delegateToSubagent.mjs +48 -0
  59. package/src/tools/execCommand.d.ts +22 -0
  60. package/src/tools/execCommand.mjs +200 -0
  61. package/src/tools/fetchWebPage.mjs +96 -0
  62. package/src/tools/patchFile.d.ts +4 -0
  63. package/src/tools/patchFile.mjs +96 -0
  64. package/src/tools/reportAsSubagent.d.ts +3 -0
  65. package/src/tools/reportAsSubagent.mjs +44 -0
  66. package/src/tools/tavilySearch.d.ts +6 -0
  67. package/src/tools/tavilySearch.mjs +57 -0
  68. package/src/tools/tmuxCommand.d.ts +14 -0
  69. package/src/tools/tmuxCommand.mjs +194 -0
  70. package/src/tools/writeFile.d.ts +4 -0
  71. package/src/tools/writeFile.mjs +56 -0
  72. package/src/utils/evalJSONConfig.mjs +48 -0
  73. package/src/utils/matchValue.d.ts +6 -0
  74. package/src/utils/matchValue.mjs +40 -0
  75. package/src/utils/noThrow.mjs +31 -0
  76. package/src/utils/notify.mjs +28 -0
  77. package/src/utils/parseFileRange.mjs +18 -0
  78. package/src/utils/readFileRange.mjs +33 -0
  79. package/src/utils/retryOnError.mjs +41 -0
@@ -0,0 +1,8 @@
1
+ ---
2
+ import: https://raw.githubusercontent.com/anthropics/claude-code/db8834ba1d72e9a26fba30ac85f3bc4316bb0689/plugins/code-review/commands/code-review.md
3
+ ---
4
+
5
+ - Parallel execution of subagents is not supported. Delegate to subagents sequentially.
6
+ - If CLAUDE.md is not found, refer to AGENTS.md instead for project rules and conventions.
7
+ - If the PR branch is already checked out, review changes from local files instead of fetching from GitHub.
8
+ - After explaining the review results to the user, ask whether to post the comments to GitHub as well.
@@ -0,0 +1,6 @@
1
+ ---
2
+ import: https://raw.githubusercontent.com/anthropics/claude-code/5cff78741f54a0dcfaeb11d29b9ea9a83f3882ff/plugins/feature-dev/commands/feature-dev.md
3
+ ---
4
+
5
+ - Use memory file instead of TodoWrite
6
+ - Parallel execution of subagents is not supported. Delegate to subagents sequentially.
@@ -0,0 +1,9 @@
1
+ ---
2
+ description: Create a commit message based on staged changes
3
+ ---
4
+
5
+ Create a commit.
6
+ - Understand the staged changes: git ["diff", "--staged"]
7
+ - Check the commit message format: git ["log", "--no-merges", "--oneline", "-n", "10"]
8
+ - Create a concise and descriptive commit message that follows the project's commit convention.
9
+ - Create a commit: git ["commit", "-m", "<commit message>"]
@@ -0,0 +1,10 @@
1
+ ---
2
+ description: Create a commit message with Co-authored-by trailer
3
+ ---
4
+
5
+ Create a commit.
6
+ - Understand the staged changes: git ["diff", "--staged"]
7
+ - Check the commit message format: git ["log", "--no-merges", "--oneline", "-n", "10"]
8
+ - Create a concise and descriptive commit message that follows the project's commit convention.
9
+ - Create a commit:
10
+ exec_command: git ["commit", "-m", "<commit message>", "-m", "", "-m", "Co-authored-by: Plain Agent <plain-agent+<model-name>@localhost>"]
@@ -0,0 +1,6 @@
1
+ ---
2
+ description: Answer general questions unrelated to the project
3
+ ---
4
+
5
+ - Skip project context discovery.
6
+ - Use web search for unknown information and provide evidence-based answers.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Shumpei IINUMA
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,624 @@
1
+ # Plain Agent
2
+
3
+ A lightweight CLI-based coding agent.
4
+
5
+ - **Safety controls** — Configure per-tool approval rules and run in a sandbox for stronger isolation
6
+ - **Multi-provider** — Works with Anthropic, OpenAI, Gemini, AWS Bedrock, Azure, Vertex AI, and more
7
+ - **Sequential subagent delegation** — Delegate subtasks to specialized subagents with full visibility into their actions
8
+ - **MCP support** — Connect to external MCP servers to extend available tools
9
+ - **Claude Code compatible** *(experimental)* — Reuse Claude Code plugins, agents, commands, and skills
10
+
11
+ ## Safety Controls
12
+
13
+ This CLI tool automatically allows the execution of certain tools but requires explicit approval for security-sensitive operations, such as accessing parent directories.
14
+ The security rules are defined in [`config.predefined.json`](https://github.com/iinm/plain-agent/blob/main/.config/config.predefined.json) and [`toolInputValidator.mjs`](https://github.com/iinm/plain-agent/blob/main/src/toolInputValidator.mjs) within this repository.
15
+
16
+ ⚠️ The `write_file` and `patch_file` tools block direct access to git-ignored files. However, `exec_command` can access any files in your environment.
17
+ Use a sandbox for stronger isolation.
18
+
19
+ ## Requirements
20
+
21
+ - Linux or macOS
22
+ - Node.js 22 or later
23
+ - LLM provider credentials (API keys, AWS SSO, gcloud CLI, or Azure CLI)
24
+ - [ripgrep](https://github.com/burntsushi/ripgrep)
25
+ - [fd](https://github.com/sharkdp/fd)
26
+
27
+ ## Quick Start
28
+
29
+ ```sh
30
+ npm install -g @iinm/plain-agent
31
+ ```
32
+
33
+ List available models.
34
+
35
+ ```sh
36
+ curl https://raw.githubusercontent.com/iinm/plain-agent/refs/heads/main/.config/config.predefined.json \
37
+ | jq -r '.models[] | "\(.name)+\(.variant)"'
38
+ ```
39
+
40
+ Create the configuration.
41
+
42
+ ```js
43
+ // ~/.config/config.local.json
44
+ {
45
+ // Default model used by ./bin/agent
46
+ "model": "gpt-5.4+thinking-high",
47
+
48
+ "platforms": [
49
+ {
50
+ "name": "anthropic",
51
+ "variant": "default",
52
+ "apiKey": "FIXME"
53
+ },
54
+ {
55
+ "name": "gemini",
56
+ "variant": "default",
57
+ "apiKey": "FIXME"
58
+ },
59
+ {
60
+ "name": "openai",
61
+ "variant": "default",
62
+ "apiKey": "FIXME"
63
+ }
64
+ ],
65
+
66
+ // Optional
67
+ "tools": {
68
+ "askGoogle": {
69
+ "model": "gemini-3-flash-preview"
70
+
71
+ // Google AI Studio
72
+ "apiKey": "FIXME"
73
+
74
+ // Or use Vertex AI (Requires gcloud CLI to get authentication token)
75
+ // "platform": "vertex-ai",
76
+ // "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
77
+ },
78
+ "tavily": {
79
+ "apiKey": "FIXME"
80
+ }
81
+ }
82
+ }
83
+
84
+ ```
85
+
86
+ <details>
87
+ <summary>Extra provider examples</summary>
88
+
89
+ ```js
90
+ {
91
+ "platforms": [
92
+ {
93
+ // Requires Azure CLI to get access token
94
+ "name": "azure",
95
+ "variant": "default",
96
+ "baseURL": "https://<resource>.openai.azure.com/openai",
97
+ // Optional
98
+ "azureConfigDir": "/home/xxx/.azure-for-agent"
99
+ },
100
+ {
101
+ "name": "bedrock",
102
+ "variant": "default",
103
+ "baseURL": "https://bedrock-runtime.<region>.amazonaws.com",
104
+ "awsProfile": "FIXME"
105
+ },
106
+ {
107
+ // Requires gcloud CLI to get authentication token
108
+ "name": "vertex-ai",
109
+ "variant": "default",
110
+ "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/<location>",
111
+ // Optional
112
+ "account": "<service_account_email>"
113
+ },
114
+ {
115
+ "name": "openai",
116
+ "variant": "ollama",
117
+ "baseURL": "https://ollama.com",
118
+ "apiKey": "FIXME"
119
+ },
120
+ {
121
+ "name": "openai",
122
+ "variant": "huggingface",
123
+ "baseURL": "https://router.huggingface.co",
124
+ "apiKey": "FIXME"
125
+ },
126
+ {
127
+ "name": "openai",
128
+ "variant": "xai",
129
+ "apiKey": "FIXME"
130
+ }
131
+ ]
132
+ }
133
+ ```
134
+ </details>
135
+
136
+ Run the agent.
137
+
138
+ ```sh
139
+ plain
140
+
141
+ # Or specify a specific model
142
+ plain -m <model>+<variant>
143
+ ```
144
+
145
+ Display the help message.
146
+
147
+ ```
148
+ /help
149
+ ```
150
+
151
+ Interrupt the agent while it's running by providing additional instructions:
152
+
153
+ ```sh
154
+ plain-interrupt Stop and report the progress
155
+ ```
156
+
157
+ ## Available Tools
158
+
159
+ The agent can use the following tools to assist with tasks:
160
+
161
+ - **exec_command**: Run a command without shell interpretation.
162
+ - **write_file**: Write a file.
163
+ - **patch_file**: Patch a file.
164
+ - **tmux_command**: Run a tmux command.
165
+ - **fetch_web_page**: Fetch and extract web page content from a given URL, returning it as Markdown.
166
+ - **search_web**: Search the web for information (requires Tavily API key).
167
+ - **ask_google**: Ask Google a question using natural language (requires Gemini API key).
168
+ - **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.
169
+ - **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.
170
+
171
+ ## Directory Structure
172
+
173
+ ```
174
+ ~/.config/plain-agent/
175
+ \__ config.json # User configuration
176
+ \__ config.local.json # User local configuration (including secrets)
177
+ \__ prompts/ # Global/User-defined prompts
178
+ \__ agents/ # Global/User-defined agent roles
179
+
180
+ <project-root>
181
+ \__ .plain-agent/
182
+ \__ config.json # Project-specific configuration
183
+ \__ config.local.json # Project-specific local configuration (including secrets)
184
+ \__ interrupt-message.txt # Interrupt message consumed by the agent
185
+ \__ memory/ # Task-specific memory files
186
+ \__ prompts/ # Project-specific prompts
187
+ \__ agents/ # Project-specific agent roles
188
+ ```
189
+
190
+ ## Configuration
191
+
192
+ The agent loads configuration files in the following order. Settings in later files will override those in earlier files.
193
+
194
+ - `~/.config/plain-agent/config.json`: User configuration for all projects.
195
+ - `~/.config/plain-agent/config.local.json`: User local configuration, typically for API keys.
196
+ - `.plain-agent/config.json`: Project-specific configuration.
197
+ - `.plain-agent/config.local.json`: Project-specific local configuration, typically for API keys or local development overrides.
198
+
199
+ ### Example
200
+
201
+ <details>
202
+ <summary>YOLO mode example (requires sandbox for safety)</summary>
203
+
204
+ ```js
205
+ {
206
+ "autoApproval": {
207
+ // Automatically deny unmatched tools instead of asking
208
+ "defaultAction": "deny",
209
+ // The maximum number of automatic approvals.
210
+ "maxApprovals": 100,
211
+ // Patterns are evaluated in order. First match wins.
212
+ "patterns": [
213
+ // Prohibit direct access to external URLs
214
+ {
215
+ "toolName": "fetch_web_page",
216
+ "action": "deny",
217
+ "reason": "Use ask_google instead"
218
+ },
219
+ {
220
+ "toolName": { "$regex": "^(write_file|patch_file)$" },
221
+ "action": "allow"
222
+ },
223
+ {
224
+ "toolName": { "$regex": "^(exec_command|tmux_command)$" },
225
+ "action": "allow"
226
+ },
227
+ {
228
+ "toolName": "ask_google",
229
+ "action": "allow"
230
+ }
231
+
232
+ // ⚠️ Never do this. fetch_web_page and mcp run outside the sandbox, so they can send anything externally.
233
+ // {
234
+ // "toolName": { "$regex": "." },
235
+ // "action": "allow"
236
+ // }
237
+ ]
238
+ },
239
+ "sandbox": {
240
+ "command": "plain-sandbox",
241
+ "args": ["--dockerfile", ".agent/sandbox/Dockerfile", "--allow-write", "--skip-build", "--keep-alive", "30"],
242
+ "separator": "--",
243
+ "rules": [
244
+ {
245
+ "pattern": {
246
+ "command": "npm",
247
+ "args": ["ci"]
248
+ },
249
+ "mode": "sandbox",
250
+ "extraArgs": ["--allow-net", "0.0.0.0/0"]
251
+ }
252
+ ]
253
+ }
254
+ }
255
+ ```
256
+ </details>
257
+
258
+ <details>
259
+ <summary>Full example</summary>
260
+
261
+ ```js
262
+ {
263
+ "autoApproval": {
264
+ // The maximum number of automatic approvals.
265
+ "maxApprovals": 50,
266
+ // Patterns are evaluated in order. First match wins.
267
+ "patterns": [
268
+ {
269
+ "toolName": { "$regex": "^(write_file|patch_file)$" },
270
+ "input": { "filePath": { "$regex": "^\\.plain-agent/memory/.+\\.md$" } },
271
+ "action": "allow"
272
+ },
273
+ {
274
+ "toolName": { "$regex": "^(write_file|patch_file)$" },
275
+ "input": { "filePath": { "$regex": "^(\\./)?src/" } },
276
+ "action": "allow"
277
+ },
278
+
279
+ // ⚠️ `npm run test` may execute arbitrary code and access git-ignored files.
280
+ // It must be run in a sandbox.
281
+ {
282
+ "toolName": "exec_command",
283
+ "input": { "command": "npm", "args": ["run", { "$regex": "^(check|test|lint|fix)$" }] },
284
+ "action": "allow"
285
+ },
286
+ {
287
+ "toolName": "ask_google",
288
+ "action": "allow"
289
+ },
290
+
291
+ // MCP Tool naming convention: mcp__<serverName>__<toolName>
292
+ {
293
+ "toolName": { "$regex": "slack_(read|search)_.+" },
294
+ "action": "allow"
295
+ }
296
+ ]
297
+ },
298
+
299
+ // (Optional) Sandbox environment for the exec_command and tmux_command tools
300
+ // https://github.com/iinm/plain-agent/tree/main/sandbox
301
+ "sandbox": {
302
+ "command": "plain-sandbox",
303
+ "args": ["--dockerfile", ".agent/sandbox/Dockerfile", "--allow-write", "--skip-build", "--keep-alive", "30"],
304
+ // separator is inserted between sandbox flags and the user command to prevent bypasses
305
+ "separator": "--",
306
+
307
+ "rules": [
308
+ // Run specific commands outside the sandbox
309
+ {
310
+ "pattern": {
311
+ "command": { "$regex": "^(gh|docker)$" }
312
+ },
313
+ "mode": "unsandboxed"
314
+ },
315
+ // Run commands in the sandbox with network access
316
+ {
317
+ "pattern": {
318
+ "command": "npm",
319
+ "args": ["install"]
320
+ },
321
+ "mode": "sandbox",
322
+ // Allow access to registry.npmjs.org
323
+ "additionalArgs": ["--allow-net", "registry.npmjs.org"]
324
+ }
325
+ ]
326
+ },
327
+
328
+ // Configure MCP servers for extended functionality
329
+ "mcpServers": {
330
+ "chrome_devtools": {
331
+ "command": "npx",
332
+ "args": ["-y", "chrome-devtools-mcp@latest", "--isolated"]
333
+ },
334
+ // ⚠️ Add this to config.local.json to avoid committing secrets to Git
335
+ "slack": {
336
+ "command": "npx",
337
+ "args": ["-y", "mcp-remote", "https://mcp.slack.com/mcp", "--header", "Authorization:Bearer FIXME"],
338
+ },
339
+ "notion": {
340
+ "command": "npx",
341
+ "args": ["-y", "mcp-remote", "https://mcp.notion.com/mcp"],
342
+ "options": {
343
+ // Enable only specific tools (optional - if not specified, all tools are enabled)
344
+ "enabledTools": ["notion-search", "notion-fetch"]
345
+ }
346
+ },
347
+ "aws_knowledge": {
348
+ "command": "npx",
349
+ "args": ["-y", "mcp-remote", "https://knowledge-mcp.global.api.aws"]
350
+ },
351
+ // ⚠️ Add this to config.local.json to avoid committing secrets to Git
352
+ "google_developer-knowledge": {
353
+ "command": "npx",
354
+ "args": ["-y", "mcp-remote", "https://developerknowledge.googleapis.com/mcp", "--header", "X-Goog-Api-Key:FIXME"]
355
+ }
356
+ },
357
+
358
+ // Override default notification command
359
+ // "notifyCmd": "/path/to/notification-command"
360
+ }
361
+ ```
362
+ </details>
363
+
364
+ ## Prompts
365
+
366
+ You can define reusable prompts in Markdown files. These are especially useful for common tasks like creating commit messages or conducting retrospectives.
367
+
368
+ ### Prompt File Format
369
+
370
+ Prompts are Markdown files with a YAML frontmatter:
371
+
372
+ ```md
373
+ ---
374
+ description: Create a commit message based on staged changes
375
+ ---
376
+
377
+ Review the staged changes and create a concise commit message following the conventional commits specification.
378
+ ```
379
+
380
+ You can also import remote prompts with the `import` field:
381
+
382
+ ```md
383
+ ---
384
+ import: https://raw.githubusercontent.com/anthropics/claude-code/5cff78741f54a0dcfaeb11d29b9ea9a83f3882ff/plugins/feature-dev/commands/feature-dev.md
385
+ ---
386
+
387
+ - Use memory file instead of TodoWrite
388
+ - Parallel execution of subagents is not supported. Delegate to subagents sequentially.
389
+ ```
390
+
391
+ Remote prompts are fetched and cached locally. The local content will be appended to the imported content.
392
+
393
+ ### Locations
394
+
395
+ The agent searches for prompts in the following directories:
396
+
397
+ - `~/.config/plain-agent/prompts/` (Global/User-defined prompts)
398
+ - `.plain-agent/prompts/` (Project-specific prompts)
399
+ - `.claude/commands/` (Claude-specific commands, prefixed with `claude/commands:`)
400
+ - `.claude/skills/` (Claude-specific skills, prefixed with `claude/skills:`)
401
+
402
+ The prompt ID is the relative path of the file without the `.md` extension. For example, `.plain-agent/prompts/retro.md` becomes `/prompts:retro`.
403
+
404
+ ### Shortcuts
405
+
406
+ Prompts located in a `shortcuts/` subdirectory (e.g., `.plain-agent/prompts/shortcuts/review.md`) can be invoked directly as a top-level command (e.g., `/review`). This is useful for frequently used tasks. If a prompt is in a `shortcuts/` subdirectory, its ID is simplified by removing the `shortcuts/` prefix for use as a shortcut (e.g., `shortcuts/review` becomes `/review`).
407
+
408
+ ## Subagents
409
+
410
+ Subagents are specialized agents that can be delegated specific tasks. They allow you to break down complex workflows into focused, manageable components.
411
+
412
+ ### Subagent File Format
413
+
414
+ Subagent definitions are Markdown files with a YAML frontmatter:
415
+
416
+ ```md
417
+ ---
418
+ description: Simplifies and refines code for clarity and maintainability
419
+ ---
420
+
421
+ You are a code simplifier. Your role is to refactor code while preserving its functionality.
422
+ ```
423
+
424
+ You can also import remote subagent definitions with the `import` field:
425
+
426
+ ```md
427
+ ---
428
+ import: https://raw.githubusercontent.com/anthropics/claude-code/f7ab5c799caf2ec8c7cd1b99d2bc2f158459ef5e/plugins/pr-review-toolkit/agents/code-simplifier.md
429
+ ---
430
+
431
+ Use AGENTS.md instead of CLAUDE.md in this project.
432
+ ```
433
+
434
+ Remote subagents are fetched and cached locally. The local content will be appended to the imported content.
435
+
436
+ ### Locations
437
+
438
+ The agent searches for subagent definitions in the following directories:
439
+
440
+ - `~/.config/plain-agent/agents/` (Global/User-defined agents)
441
+ - `.plain-agent/agents/` (Project-specific agents)
442
+ - `.claude/agents/` (Claude-specific agents)
443
+
444
+ The subagent ID is the relative path of the file without the `.md` extension. For example, `.plain-agent/agents/worker.md` becomes `worker`.
445
+
446
+ ## Claude Code Plugin Support
447
+
448
+ Example:
449
+
450
+ ```sh
451
+ git clone --depth 1 https://github.com/anthropics/claude-code .plain-agent/claude-code-plugins/anthropics/claude-code
452
+ git clone --depth 1 https://github.com/awslabs/agent-plugins .plain-agent/claude-code-plugins/awslabs/agent-plugins
453
+ ```
454
+
455
+ ```js
456
+ // .plain-agent/config.json
457
+ {
458
+ "claudeCodePlugins": [
459
+ { "name": "pr-review-toolkit", "path": "anthropics/claude-code/plugins/pr-review-toolkit" },
460
+ { "name": "aws-serverless", "path": "awslabs/agent-plugins/plugins/aws-serverless" }
461
+ ]
462
+ }
463
+
464
+ ```
465
+
466
+ ## Development
467
+
468
+ ```sh
469
+ # Run lint, typecheck, and test
470
+ npm run check
471
+
472
+ # Fix lint errors
473
+ npm run fix
474
+ # or
475
+ npm run fix -- --unsafe
476
+
477
+ # Update dependencies
478
+ npx npm-check-updates -t minor -c 3
479
+ npx npm-check-updates -t minor -c 3 -u
480
+ ```
481
+
482
+ ## Appendix: Creating Least-Privilege Users for Cloud Providers
483
+
484
+ <details>
485
+ <summary>Amazon Bedrock</summary>
486
+
487
+ ```sh
488
+ # IAM Identity Center
489
+ identity_center_instance_arn="FIXME" # e.g., arn:aws:sso:::instance/ssoins-xxxxxxxxxxxxxxxx"
490
+ identity_store_id=FIXME
491
+ aws_account_id=FIXME
492
+
493
+ # Create a permission set
494
+ permission_set_arn=$(aws sso-admin create-permission-set \
495
+ --instance-arn "$identity_center_instance_arn" \
496
+ --name "BedrockForCodingAgent" \
497
+ --description "Allows only Bedrock model invocation" \
498
+ --query "PermissionSet.PermissionSetArn" --output text)
499
+
500
+ # Add a policy to the permission set
501
+ policy='{
502
+ "Version": "2012-10-17",
503
+ "Statement": [
504
+ {
505
+ "Effect": "Allow",
506
+ "Action": [
507
+ "bedrock:InvokeModel",
508
+ "bedrock:InvokeModelWithResponseStream"
509
+ ],
510
+ "Resource": [
511
+ "arn:aws:bedrock:*::foundation-model/*",
512
+ "arn:aws:bedrock:*:*:inference-profile/*"
513
+ ]
514
+ }
515
+ ]
516
+ }'
517
+
518
+ aws sso-admin put-inline-policy-to-permission-set \
519
+ --instance-arn "$identity_center_instance_arn" \
520
+ --permission-set-arn "$permission_set_arn" \
521
+ --inline-policy "$policy"
522
+
523
+ # Create an SSO user
524
+ sso_user_name=FIXME
525
+ sso_user_email=FIXME
526
+ sso_user_family_name=FIXME
527
+ sso_user_given_name=FIXME
528
+
529
+ user_id=$(aws identitystore create-user \
530
+ --identity-store-id "$identity_store_id" \
531
+ --user-name "$sso_user_name" \
532
+ --display-name "$sso_user_name" \
533
+ --name "FamilyName=${sso_user_family_name},GivenName=${sso_user_given_name}" \
534
+ --emails Value=${sso_user_email},Primary=true \
535
+ --query "UserId" --output text)
536
+
537
+ # Associate the user, permission set, and account
538
+ aws sso-admin create-account-assignment \
539
+ --instance-arn "$identity_center_instance_arn" \
540
+ --target-id "$aws_account_id" \
541
+ --target-type AWS_ACCOUNT \
542
+ --permission-set-arn "$permission_set_arn" \
543
+ --principal-type USER \
544
+ --principal-id "$user_id"
545
+
546
+ # Verify the setup
547
+ aws configure sso
548
+ # profile: CodingAgent
549
+
550
+ profile=CodingAgent
551
+ aws sso login --profile "$profile"
552
+
553
+ echo '{"anthropic_version": "bedrock-2023-05-31", "max_tokens": 1024, "messages": [{"role": "user", "content": "Hello"}]}' > request.json
554
+
555
+ aws bedrock-runtime invoke-model \
556
+ --model-id global.anthropic.claude-haiku-4-5-20251001-v1:0 \
557
+ --body fileb://request.json \
558
+ --profile "$profile" \
559
+ --region ap-northeast-1 \
560
+ response.json
561
+ ```
562
+ </details>
563
+
564
+ <details>
565
+ <summary>Azure - Microsoft Foundry</summary>
566
+
567
+ ```sh
568
+ resource_group=FIXME
569
+ account_name=FIXME # resource name
570
+
571
+ # Create a service principal
572
+ service_principal=$(az ad sp create-for-rbac --name "CodingAgentServicePrincipal" --skip-assignment)
573
+ echo "$service_principal"
574
+ app_id=$(echo "$service_principal" | jq -r .appId)
575
+
576
+ # Assign role permissions
577
+ # https://learn.microsoft.com/en-us/azure/ai-foundry/openai/how-to/role-based-access-control?view=foundry-classic#azure-openai-roles
578
+ resource_id=$(az cognitiveservices account show \
579
+ --name "$account_name" \
580
+ --resource-group "$resource_group" \
581
+ --query id --output tsv)
582
+
583
+ az role assignment create \
584
+ --role "Cognitive Services OpenAI User" \
585
+ --assignee "$app_id" \
586
+ --scope "$resource_id"
587
+
588
+ # Log in with the service principal
589
+ export app_secret=$(echo "$service_principal" | jq -r .password)
590
+ export tenant_id=$(echo "$service_principal" | jq -r .tenant)
591
+
592
+ export AZURE_CONFIG_DIR=$HOME/.azure-for-agent # Change the location to store credentials
593
+ az login --service-principal -u "$app_id" -p "$app_secret" --tenant "$tenant_id"
594
+ ```
595
+ </details>
596
+
597
+ <details>
598
+ <summary>Google Cloud Vertex AI</summary>
599
+
600
+ ```sh
601
+ project_id=FIXME
602
+ service_account_name=FIXME
603
+ service_account_email="${service_account_name}@${project_id}.iam.gserviceaccount.com"
604
+ your_account_email=FIXME
605
+
606
+ # Create a service account
607
+ gcloud iam service-accounts create "$service_account_name" \
608
+ --project "$project_id" --display-name "Vertex AI Caller Service Account for Coding Agent"
609
+
610
+ # Grant permissions
611
+ gcloud projects add-iam-policy-binding "$project_id" \
612
+ --member "serviceAccount:$service_account_email" \
613
+ --role="roles/aiplatform.serviceAgent"
614
+
615
+ # Allow your account to impersonate the service account
616
+ gcloud iam service-accounts add-iam-policy-binding "$service_account_email" \
617
+ --project "$project_id" \
618
+ --member "user:$your_account_email" \
619
+ --role "roles/iam.serviceAccountTokenCreator"
620
+
621
+ # Verify that tokens can be issued
622
+ gcloud auth print-access-token --impersonate-service-account "$service_account_email"
623
+ ```
624
+ </details>
package/bin/plain ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+
3
+ import("../src/main.mjs");