@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 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
- - **Safety controls** — Configure approval rules and sandboxing for safe execution
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
- ## Safety Controls
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
- **Auto-Approval**: Tools with no side effects and no sensitive data access are automatically approved based on patterns defined in [`config.predefined.json#autoApproval`](https://github.com/iinm/plain-agent/blob/main/config/config.predefined.json).
24
+ ## Limitations
18
25
 
19
- **Path Validation**: All file paths in tool inputs are validated to remain within the working directory and under git control.
20
-
21
- ⚠️ `write_file` and `patch_file` require explicit path arguments. However, `exec_command` can run arbitrary code where file access cannot be validated. Use a sandbox for stronger isolation.
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>Other provider examples</b></summary>
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.7.15",
3
+ "version": "1.7.16",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -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
- `args: ${JSON.stringify(execCommandInput.args)}`,
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
- `args: ${JSON.stringify(tmuxCommandInput.args)}`,
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
  },