@iinm/plain-agent 1.7.15 → 1.7.16
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 +55 -34
- package/package.json +1 -1
- package/src/cliFormatter.mjs +45 -2
- package/src/main.mjs +2 -1
- package/src/mcp.mjs +5 -2
package/README.md
CHANGED
|
@@ -4,21 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
# Plain Agent
|
|
6
6
|
|
|
7
|
-
A lightweight CLI-based coding agent.
|
|
7
|
+
A lightweight CLI-based coding agent with zero framework dependencies.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
- **Multi-provider** — Supports Anthropic, OpenAI, Gemini, Bedrock, Azure, Vertex AI, and more
|
|
11
|
-
- **Sequential subagent delegation** — Delegate subtasks to specialized subagents with full visibility
|
|
12
|
-
- **MCP support** — Connect to external MCP servers to extend available tools
|
|
13
|
-
- **Claude Code compatible** — Reuse Claude Code plugins, agents, commands, and skills
|
|
9
|
+
## Why Plain Agent?
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
- **Multi-provider** — Use Claude, GPT, Gemini, or any OpenAI-compatible model.
|
|
12
|
+
Switch providers without changing your workflow.
|
|
13
|
+
- **Fine-grained approval rules** — Auto-approve commands by name, arguments,
|
|
14
|
+
and file paths using regex patterns
|
|
15
|
+
([`config.predefined.json`](https://github.com/iinm/plain-agent/blob/main/config/config.predefined.json)).
|
|
16
|
+
- **Path validation** — File paths must stay within the working directory
|
|
17
|
+
and git-ignored files (`.env`, etc.) are blocked.
|
|
18
|
+
- **Sandboxed execution** — Run the agent's shell commands inside a Docker
|
|
19
|
+
container with network access restricted to allowlisted destinations
|
|
20
|
+
(e.g., `registry.npmjs.org` only for `npm install`).
|
|
21
|
+
- **Extensible** — Define prompts and subagents in Markdown.
|
|
22
|
+
Connect MCP servers. Reuse Claude Code plugins.
|
|
16
23
|
|
|
17
|
-
|
|
24
|
+
## Limitations
|
|
18
25
|
|
|
19
|
-
**
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
- **Sequential subagent execution** — Subagents run one at a time rather than
|
|
27
|
+
in parallel. The trade-off is full visibility: every step is streamed to
|
|
28
|
+
your terminal so you can follow exactly what each subagent is doing.
|
|
22
29
|
|
|
23
30
|
## Requirements
|
|
24
31
|
|
|
@@ -67,32 +74,11 @@ Create the configuration.
|
|
|
67
74
|
"variant": "default",
|
|
68
75
|
"apiKey": "FIXME"
|
|
69
76
|
},
|
|
70
|
-
{
|
|
71
|
-
// Requires Azure CLI to get access token
|
|
72
|
-
"name": "azure",
|
|
73
|
-
"variant": "openai",
|
|
74
|
-
"baseURL": "https://<resource>.openai.azure.com/openai",
|
|
75
|
-
// Optional
|
|
76
|
-
"azureConfigDir": "/home/xxx/.azure-for-agent"
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
"name": "bedrock",
|
|
80
|
-
"variant": "default",
|
|
81
|
-
"baseURL": "https://bedrock-runtime.<region>.amazonaws.com",
|
|
82
|
-
"awsProfile": "FIXME"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
// Requires gcloud CLI to get authentication token
|
|
86
|
-
"name": "vertex-ai",
|
|
87
|
-
"variant": "default",
|
|
88
|
-
"baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/<location>",
|
|
89
|
-
// Optional
|
|
90
|
-
"account": "<service_account_email>"
|
|
91
|
-
}
|
|
92
77
|
],
|
|
93
78
|
|
|
94
79
|
// Optional
|
|
95
80
|
"tools": {
|
|
81
|
+
// askWeb: Searches the web to answer questions requiring up-to-date information or external sources.
|
|
96
82
|
"askWeb": {
|
|
97
83
|
"provider": "gemini",
|
|
98
84
|
"apiKey": "FIXME",
|
|
@@ -108,6 +94,8 @@ Create the configuration.
|
|
|
108
94
|
// "account": "<service_account_email>"
|
|
109
95
|
},
|
|
110
96
|
|
|
97
|
+
// askURL: Answers questions based on provided URL content.
|
|
98
|
+
// Directly injecting URL content into context is not supported to prevent prompt injection.
|
|
111
99
|
"askURL": {
|
|
112
100
|
"provider": "gemini",
|
|
113
101
|
"apiKey": "FIXME"
|
|
@@ -129,7 +117,40 @@ Create the configuration.
|
|
|
129
117
|
```
|
|
130
118
|
|
|
131
119
|
<details>
|
|
132
|
-
<summary><b>
|
|
120
|
+
<summary><b>Azure / Bedrock / Vertex AI provider examples</b></summary>
|
|
121
|
+
|
|
122
|
+
```js
|
|
123
|
+
{
|
|
124
|
+
"platforms": [
|
|
125
|
+
{
|
|
126
|
+
// Requires Azure CLI to get access token
|
|
127
|
+
"name": "azure",
|
|
128
|
+
"variant": "openai",
|
|
129
|
+
"baseURL": "https://<resource>.openai.azure.com/openai",
|
|
130
|
+
// Optional
|
|
131
|
+
"azureConfigDir": "/home/xxx/.azure-for-agent"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"name": "bedrock",
|
|
135
|
+
"variant": "default",
|
|
136
|
+
"baseURL": "https://bedrock-runtime.<region>.amazonaws.com",
|
|
137
|
+
"awsProfile": "FIXME"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
// Requires gcloud CLI to get authentication token
|
|
141
|
+
"name": "vertex-ai",
|
|
142
|
+
"variant": "default",
|
|
143
|
+
"baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/<location>",
|
|
144
|
+
// Optional
|
|
145
|
+
"account": "<service_account_email>"
|
|
146
|
+
}
|
|
147
|
+
]
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
</details>
|
|
151
|
+
|
|
152
|
+
<details>
|
|
153
|
+
<summary><b>OpenAI compatible provider examples</b></summary>
|
|
133
154
|
|
|
134
155
|
```js
|
|
135
156
|
{
|
package/package.json
CHANGED
package/src/cliFormatter.mjs
CHANGED
|
@@ -11,6 +11,49 @@
|
|
|
11
11
|
import { styleText } from "node:util";
|
|
12
12
|
import { createPatch } from "diff";
|
|
13
13
|
|
|
14
|
+
/** Length above which a single-line arg forces block-form rendering. */
|
|
15
|
+
const ARG_BLOCK_LENGTH_THRESHOLD = 60;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Format an args array for display.
|
|
19
|
+
* Uses compact JSON for short single-line args; switches to a YAML-style
|
|
20
|
+
* block form when any arg contains newlines or exceeds
|
|
21
|
+
* {@link ARG_BLOCK_LENGTH_THRESHOLD} characters so that long scripts passed
|
|
22
|
+
* to `bash -c`, `python -c`, `node -e`, etc. stay readable.
|
|
23
|
+
* @param {unknown} args
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
export function formatArgs(args) {
|
|
27
|
+
if (!Array.isArray(args) || args.length === 0) {
|
|
28
|
+
return `args: ${JSON.stringify(args ?? [])}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const needsBlock = args.some(
|
|
32
|
+
(a) =>
|
|
33
|
+
typeof a === "string" &&
|
|
34
|
+
(a.includes("\n") || a.length > ARG_BLOCK_LENGTH_THRESHOLD),
|
|
35
|
+
);
|
|
36
|
+
if (!needsBlock) {
|
|
37
|
+
return `args: ${JSON.stringify(args)}`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = ["args:"];
|
|
41
|
+
for (const arg of args) {
|
|
42
|
+
if (
|
|
43
|
+
typeof arg === "string" &&
|
|
44
|
+
(arg.includes("\n") || arg.length > ARG_BLOCK_LENGTH_THRESHOLD)
|
|
45
|
+
) {
|
|
46
|
+
lines.push(" - |");
|
|
47
|
+
for (const line of arg.split("\n")) {
|
|
48
|
+
lines.push(` ${line}`);
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
lines.push(` - ${JSON.stringify(arg)}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return lines.join("\n");
|
|
55
|
+
}
|
|
56
|
+
|
|
14
57
|
/**
|
|
15
58
|
* Format tool use for display.
|
|
16
59
|
* @param {MessageContentToolUse} toolUse
|
|
@@ -25,7 +68,7 @@ export function formatToolUse(toolUse) {
|
|
|
25
68
|
return [
|
|
26
69
|
`tool: ${toolName}`,
|
|
27
70
|
`command: ${JSON.stringify(execCommandInput.command)}`,
|
|
28
|
-
|
|
71
|
+
formatArgs(execCommandInput.args),
|
|
29
72
|
].join("\n");
|
|
30
73
|
}
|
|
31
74
|
|
|
@@ -82,7 +125,7 @@ export function formatToolUse(toolUse) {
|
|
|
82
125
|
return [
|
|
83
126
|
`tool: ${toolName}`,
|
|
84
127
|
`command: ${tmuxCommandInput.command}`,
|
|
85
|
-
|
|
128
|
+
formatArgs(tmuxCommandInput.args),
|
|
86
129
|
].join("\n");
|
|
87
130
|
}
|
|
88
131
|
|
package/src/main.mjs
CHANGED
|
@@ -123,7 +123,7 @@ if (cliArgs.subcommand.type === "install-claude-code-plugins") {
|
|
|
123
123
|
}),
|
|
124
124
|
);
|
|
125
125
|
|
|
126
|
-
for (const { serverName, tools, cleanup } of mcpResults) {
|
|
126
|
+
for (const { serverName, tools, stderrLogPath, cleanup } of mcpResults) {
|
|
127
127
|
mcpTools.push(...tools);
|
|
128
128
|
mcpCleanups.push(cleanup);
|
|
129
129
|
if (!isBatchMode) {
|
|
@@ -133,6 +133,7 @@ if (cliArgs.subcommand.type === "install-claude-code-plugins") {
|
|
|
133
133
|
`✅ Successfully connected to MCP server: ${serverName}`,
|
|
134
134
|
),
|
|
135
135
|
);
|
|
136
|
+
console.log(` ⤷ stderr log: ${stderrLogPath}`);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
}
|
package/src/mcp.mjs
CHANGED
|
@@ -15,6 +15,7 @@ const OUTPUT_MAX_LENGTH = 1024 * 8;
|
|
|
15
15
|
/**
|
|
16
16
|
* @typedef {Object} SetupMCPServrResult
|
|
17
17
|
* @property {Tool[]} tools
|
|
18
|
+
* @property {string} stderrLogPath
|
|
18
19
|
* @property {() => Promise<void>} cleanup
|
|
19
20
|
*/
|
|
20
21
|
|
|
@@ -26,7 +27,7 @@ const OUTPUT_MAX_LENGTH = 1024 * 8;
|
|
|
26
27
|
export async function setupMCPServer(serverName, serverConfig) {
|
|
27
28
|
const { options, ...params } = serverConfig;
|
|
28
29
|
|
|
29
|
-
const { client, cleanup } = await startMCPServer({
|
|
30
|
+
const { client, stderrLogPath, cleanup } = await startMCPServer({
|
|
30
31
|
serverName,
|
|
31
32
|
params,
|
|
32
33
|
});
|
|
@@ -41,6 +42,7 @@ export async function setupMCPServer(serverName, serverConfig) {
|
|
|
41
42
|
|
|
42
43
|
return {
|
|
43
44
|
tools,
|
|
45
|
+
stderrLogPath,
|
|
44
46
|
cleanup: async () => {
|
|
45
47
|
cleanup();
|
|
46
48
|
await client.close();
|
|
@@ -56,7 +58,7 @@ export async function setupMCPServer(serverName, serverConfig) {
|
|
|
56
58
|
|
|
57
59
|
/**
|
|
58
60
|
* @param {MCPClientOptions} options - The options for the client.
|
|
59
|
-
* @returns {Promise<{client: Client; cleanup: () => void}>} - The MCP client and cleanup function.
|
|
61
|
+
* @returns {Promise<{client: Client; stderrLogPath: string; cleanup: () => void}>} - The MCP client, stderr log path, and cleanup function.
|
|
60
62
|
*/
|
|
61
63
|
async function startMCPServer(options) {
|
|
62
64
|
const mcpClient = await import("@modelcontextprotocol/client");
|
|
@@ -88,6 +90,7 @@ async function startMCPServer(options) {
|
|
|
88
90
|
|
|
89
91
|
return {
|
|
90
92
|
client,
|
|
93
|
+
stderrLogPath: logPath,
|
|
91
94
|
cleanup: () => {
|
|
92
95
|
stderrLogFile.close();
|
|
93
96
|
},
|