@iinm/plain-agent 1.10.1 → 1.10.3

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
@@ -3,14 +3,14 @@
3
3
  A lightweight, capable coding agent for the terminal.
4
4
 
5
5
  - **Multi-provider** — Use Claude, GPT, Gemini, or any OpenAI-compatible model via Bedrock, Vertex AI, or direct APIs.
6
- - **Fine-grained auto-approval** — Auto-approve tool calls by name and arguments using regex patterns.
6
+ - **Fine-grained auto-approval** — Auto-approve tool calls by name and arguments using regex patterns, with path validation on tool arguments.
7
7
  - **Sandboxed execution** — Run commands in a Docker container with filesystem and network isolation.
8
8
  - **Claude Code compatible** — Use Claude Code plugins, commands, subagents, and skills from `.claude/`.
9
9
  - **Zero external dependencies** — Built with Node.js standard libraries only.
10
10
 
11
11
  ## Limitations
12
12
 
13
- - **Path validation only covers tool arguments** — Path validation restricts only paths explicitly passed as tool-use arguments; it cannot control file access inside arbitrary scripts. Always use sandboxed execution when allowing arbitrary script execution.
13
+ - **Path validation only covers tool arguments** — It blocks paths outside the working directory, directory traversal (`..`), symlinks escaping the project, and git-ignored files — but only for paths explicitly passed as tool-use arguments; it cannot control file access inside arbitrary scripts. Always use sandboxed execution when allowing arbitrary script execution.
14
14
  - **Sequential subagent execution** — Subagents run one at a time rather than
15
15
  in parallel. The trade-off is full visibility: every step is streamed to
16
16
  your terminal so you can follow exactly what each subagent is doing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.10.1",
3
+ "version": "1.10.3",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -32,7 +32,7 @@ export function resolvePluginPaths(repos) {
32
32
  for (const repo of repos) {
33
33
  const ownerRepo = extractOwnerRepo(repo.source);
34
34
  if (!ownerRepo) {
35
- console.warn(`Invalid source URL: ${repo.source}`);
35
+ console.error(`Invalid source URL: ${repo.source}`);
36
36
  continue;
37
37
  }
38
38
 
@@ -43,7 +43,7 @@ export function resolvePluginPaths(repos) {
43
43
  try {
44
44
  only = new RegExp(plugin.only);
45
45
  } catch (err) {
46
- console.warn(
46
+ console.error(
47
47
  `Invalid regex pattern "${plugin.only}" for plugin "${plugin.name}":`,
48
48
  err instanceof Error ? err.message : String(err),
49
49
  );
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @import { UserEventEmitter, AgentEventEmitter, AgentCommands } from "./agent"
2
+ * @import { UserEventEmitter, AgentEventEmitter, AgentCommands } from "../agent"
3
3
  */
4
4
 
5
- import { formatCostForBatch } from "./cliFormatter.mjs";
6
- import { appendUsageRecord, buildUsageRecord } from "./usageStore.mjs";
5
+ import { appendUsageRecord, buildUsageRecord } from "../usageStore.mjs";
6
+ import { formatCostForBatch } from "./formatter.mjs";
7
7
 
8
8
  /**
9
9
  * @typedef {object} BatchSessionOptions
@@ -1,18 +1,18 @@
1
1
  /**
2
- * @import { UserEventEmitter, AgentCommands } from "./agent"
3
- * @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
2
+ * @import { UserEventEmitter, AgentCommands } from "../agent"
3
+ * @import { ClaudeCodePlugin } from "../claudeCodePlugin.mjs"
4
4
  */
5
5
 
6
6
  import { execFileSync } from "node:child_process";
7
7
  import { styleText } from "node:util";
8
- import { formatCostSummary } from "./cliFormatter.mjs";
9
- import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
10
- import { loadPrompts } from "./context/loadPrompts.mjs";
11
- import { loadUserMessageContext } from "./context/loadUserMessageContext.mjs";
12
- import { CLAUDE_CODE_COMPATIBILITY_NOTES } from "./prompt.mjs";
13
- import { parseFileRange } from "./utils/parseFileRange.mjs";
14
- import { readFileRange } from "./utils/readFileRange.mjs";
15
- import { toOneLine } from "./utils/toOneLine.mjs";
8
+ import { loadAgentRoles } from "../context/loadAgentRoles.mjs";
9
+ import { loadPrompts } from "../context/loadPrompts.mjs";
10
+ import { loadUserMessageContext } from "../context/loadUserMessageContext.mjs";
11
+ import { CLAUDE_CODE_COMPATIBILITY_NOTES } from "../prompt.mjs";
12
+ import { parseFileRange } from "../utils/parseFileRange.mjs";
13
+ import { readFileRange } from "../utils/readFileRange.mjs";
14
+ import { toOneLine } from "../utils/toOneLine.mjs";
15
+ import { formatCostSummary } from "./formatter.mjs";
16
16
 
17
17
  /**
18
18
  * @typedef {"prompt" | "continue"} CommandResult
@@ -75,7 +75,7 @@ export function createCommandHandler({
75
75
  const prompt = prompts.get(id);
76
76
 
77
77
  if (!prompt) {
78
- console.log(styleText("red", `\nPrompt not found: ${id}`));
78
+ console.error(styleText("red", `\nPrompt not found: ${id}`));
79
79
  return "prompt";
80
80
  }
81
81
 
@@ -116,13 +116,13 @@ export function createCommandHandler({
116
116
  if (inputTrimmed.startsWith("!")) {
117
117
  const fileRange = parseFileRange(inputTrimmed.slice(1));
118
118
  if (fileRange instanceof Error) {
119
- console.log(styleText("red", `\n${fileRange.message}`));
119
+ console.error(styleText("red", `\n${fileRange.message}`));
120
120
  return "prompt";
121
121
  }
122
122
 
123
123
  const fileContent = await readFileRange(fileRange);
124
124
  if (fileContent instanceof Error) {
125
- console.log(styleText("red", `\n${fileContent.message}`));
125
+ console.error(styleText("red", `\n${fileContent.message}`));
126
126
  return "prompt";
127
127
  }
128
128
 
@@ -174,7 +174,7 @@ export function createCommandHandler({
174
174
  if (inputTrimmed.startsWith("/agents:")) {
175
175
  const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/s);
176
176
  if (!match) {
177
- console.log(styleText("red", "\nInvalid agent invocation format."));
177
+ console.error(styleText("red", "\nInvalid agent invocation format."));
178
178
  return "prompt";
179
179
  }
180
180
  return await invokeAgent(match[1], match[2] || "");
@@ -204,7 +204,9 @@ export function createCommandHandler({
204
204
  if (inputTrimmed.startsWith("/prompts:")) {
205
205
  const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/s);
206
206
  if (!match) {
207
- console.log(styleText("red", "\nInvalid prompt invocation format."));
207
+ console.error(
208
+ styleText("red", "\nInvalid prompt invocation format."),
209
+ );
208
210
  return "prompt";
209
211
  }
210
212
  return await invokePrompt(
@@ -1,11 +1,11 @@
1
1
  /**
2
- * @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
2
+ * @import { ClaudeCodePlugin } from "../claudeCodePlugin.mjs"
3
3
  */
4
4
 
5
5
  import { styleText } from "node:util";
6
- import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
7
- import { loadPrompts } from "./context/loadPrompts.mjs";
8
- import { toOneLine } from "./utils/toOneLine.mjs";
6
+ import { loadAgentRoles } from "../context/loadAgentRoles.mjs";
7
+ import { loadPrompts } from "../context/loadPrompts.mjs";
8
+ import { toOneLine } from "../utils/toOneLine.mjs";
9
9
 
10
10
  // Define available slash commands for tab completion
11
11
  export const SLASH_COMMANDS = [
@@ -1,9 +1,9 @@
1
1
  /**
2
- * @import { UsageRecord } from "./usageStore.mjs"
2
+ * @import { UsageRecord } from "../usageStore.mjs"
3
3
  */
4
4
 
5
5
  import { styleText } from "node:util";
6
- import * as usageStore from "./usageStore.mjs";
6
+ import * as usageStore from "../usageStore.mjs";
7
7
 
8
8
  /**
9
9
  * @typedef {Object} CostPeriod
@@ -275,7 +275,7 @@ function formatCost(value) {
275
275
  * Run the `plain cost` subcommand.
276
276
  *
277
277
  * @param {{ from: string | null, to: string | null }} args
278
- * @param {{ readUsageRecords?: typeof import("./usageStore.mjs").readUsageRecords }} [deps]
278
+ * @param {{ readUsageRecords?: typeof import("../usageStore.mjs").readUsageRecords }} [deps]
279
279
  * @returns {Promise<number>} exit code
280
280
  */
281
281
  export async function runCostCommand(args, deps = {}) {