@iinm/plain-agent 1.0.5 → 1.0.6

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
@@ -2,9 +2,9 @@
2
2
 
3
3
  A lightweight CLI-based coding agent.
4
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
5
+ - **Safety controls** — Configure approval rules and sandboxing for safe execution
6
+ - **Multi-provider** — Supports Anthropic, OpenAI, Gemini, Bedrock, Azure, Vertex AI, and more
7
+ - **Sequential subagent delegation** — Delegate subtasks to specialized subagents with full visibility
8
8
  - **MCP support** — Connect to external MCP servers to extend available tools
9
9
  - **Claude Code compatible** *(experimental)* — Reuse Claude Code plugins, agents, commands, and skills
10
10
 
@@ -13,8 +13,7 @@ A lightweight CLI-based coding agent.
13
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
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
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.
16
+ ⚠️ `write_file` and `patch_file` block access to git-ignored files. `exec_command` blocks direct path arguments (e.g., `ls .env`), but cannot block access from executed programs (e.g., `node script.js`). Use a sandbox for stronger isolation.
18
17
 
19
18
  ## Requirements
20
19
 
@@ -34,8 +33,7 @@ npm install -g @iinm/plain-agent
34
33
  List available models.
35
34
 
36
35
  ```sh
37
- curl https://raw.githubusercontent.com/iinm/plain-agent/refs/heads/main/.config/config.predefined.json \
38
- | jq -r '.models[] | "\(.name)+\(.variant)"'
36
+ plain --list-models
39
37
  ```
40
38
 
41
39
  Create the configuration.
@@ -43,9 +41,10 @@ Create the configuration.
43
41
  ```js
44
42
  // ~/.config/plain-agent/config.local.json
45
43
  {
46
- // Default model
47
44
  "model": "gpt-5.4+thinking-high",
45
+ // "model": "claude-sonnet-4-6+thinking-16k",
48
46
 
47
+ // Configure the providers you want to use
49
48
  "platforms": [
50
49
  {
51
50
  "name": "anthropic",
@@ -61,13 +60,35 @@ Create the configuration.
61
60
  "name": "openai",
62
61
  "variant": "default",
63
62
  "apiKey": "FIXME"
63
+ },
64
+ {
65
+ // Requires Azure CLI to get access token
66
+ "name": "azure",
67
+ "variant": "default",
68
+ "baseURL": "https://<resource>.openai.azure.com/openai",
69
+ // Optional
70
+ "azureConfigDir": "/home/xxx/.azure-for-agent"
71
+ },
72
+ {
73
+ "name": "bedrock",
74
+ "variant": "default",
75
+ "baseURL": "https://bedrock-runtime.<region>.amazonaws.com",
76
+ "awsProfile": "FIXME"
77
+ },
78
+ {
79
+ // Requires gcloud CLI to get authentication token
80
+ "name": "vertex-ai",
81
+ "variant": "default",
82
+ "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/<location>",
83
+ // Optional
84
+ "account": "<service_account_email>"
64
85
  }
65
86
  ],
66
87
 
67
88
  // Optional
68
89
  "tools": {
69
90
  "askGoogle": {
70
- "model": "gemini-3-flash-preview"
91
+ "model": "gemini-3-flash-preview",
71
92
 
72
93
  // Google AI Studio
73
94
  "apiKey": "FIXME"
@@ -76,42 +97,22 @@ Create the configuration.
76
97
  // "platform": "vertex-ai",
77
98
  // "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project_id>/locations/<location>",
78
99
  },
79
- "tavily": {
80
- "apiKey": "FIXME"
100
+
101
+ "searchWeb": {
102
+ "tavilyApiKey": "FIXME"
81
103
  }
82
- }
104
+ },
105
+
83
106
  }
84
107
 
85
108
  ```
86
109
 
87
110
  <details>
88
- <summary>Extra provider examples</summary>
111
+ <summary>Other provider examples</summary>
89
112
 
90
113
  ```js
91
114
  {
92
115
  "platforms": [
93
- {
94
- // Requires Azure CLI to get access token
95
- "name": "azure",
96
- "variant": "default",
97
- "baseURL": "https://<resource>.openai.azure.com/openai",
98
- // Optional
99
- "azureConfigDir": "/home/xxx/.azure-for-agent"
100
- },
101
- {
102
- "name": "bedrock",
103
- "variant": "default",
104
- "baseURL": "https://bedrock-runtime.<region>.amazonaws.com",
105
- "awsProfile": "FIXME"
106
- },
107
- {
108
- // Requires gcloud CLI to get authentication token
109
- "name": "vertex-ai",
110
- "variant": "default",
111
- "baseURL": "https://aiplatform.googleapis.com/v1beta1/projects/<project>/locations/<location>",
112
- // Optional
113
- "account": "<service_account_email>"
114
- },
115
116
  {
116
117
  "name": "openai",
117
118
  "variant": "ollama",
@@ -164,8 +165,8 @@ The agent can use the following tools to assist with tasks:
164
165
  - **patch_file**: Patch a file.
165
166
  - **tmux_command**: Run a tmux command.
166
167
  - **fetch_web_page**: Fetch and extract web page content from a given URL, returning it as Markdown.
167
- - **search_web**: Search the web for information (requires Tavily API key).
168
168
  - **ask_google**: Ask Google a question using natural language (requires Gemini API key).
169
+ - **search_web**: Search the web for information (requires Tavily API key).
169
170
  - **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.
170
171
  - **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.
171
172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/cliArgs.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
  * @typedef {Object} CliArgs
3
3
  * @property {string|null} model - Model name with variant
4
4
  * @property {boolean} showHelp - Whether to show help message
5
+ * @property {boolean} listModels - Whether to list available models
5
6
  */
6
7
 
7
8
  /**
@@ -12,13 +13,15 @@
12
13
  export function parseCliArgs(argv) {
13
14
  const args = argv.slice(2);
14
15
  /** @type {CliArgs} */
15
- const result = { model: null, showHelp: false };
16
+ const result = { model: null, showHelp: false, listModels: false };
16
17
 
17
18
  for (let i = 0; i < args.length; i++) {
18
19
  if (args[i] === "-m" && args[i + 1]) {
19
20
  result.model = args[++i];
20
21
  } else if (args[i] === "-h" || args[i] === "--help") {
21
22
  result.showHelp = true;
23
+ } else if (args[i] === "-l" || args[i] === "--list-models") {
24
+ result.listModels = true;
22
25
  }
23
26
  }
24
27
 
@@ -35,6 +38,7 @@ Usage: agent [options]
35
38
 
36
39
  Options:
37
40
  -m <model+variant> Model to use
41
+ -l, --list-models List available models
38
42
  -h, --help Show this help message
39
43
 
40
44
  Examples:
package/src/config.d.ts CHANGED
@@ -18,9 +18,6 @@ export type AppConfig = {
18
18
  };
19
19
  sandbox?: ExecCommandSanboxConfig;
20
20
  tools?: {
21
- tavily?: {
22
- apiKey?: string;
23
- };
24
21
  /**
25
22
  * - Vertex AI: requires baseURL and account
26
23
  * - AI Studio: requires apiKey
@@ -32,6 +29,9 @@ export type AppConfig = {
32
29
  apiKey?: string;
33
30
  model?: string;
34
31
  };
32
+ searchWeb?: {
33
+ tavilyApiKey?: string;
34
+ };
35
35
  };
36
36
  mcpServers?: Record<string, MCPServerConfig>;
37
37
  notifyCmd?: string;
package/src/config.mjs CHANGED
@@ -16,9 +16,12 @@ import {
16
16
  import { evalJSONConfig } from "./utils/evalJSONConfig.mjs";
17
17
 
18
18
  /**
19
+ * @param {Object} [options]
20
+ * @param {boolean} [options.skipTrustCheck] - Skip trust check for config files
19
21
  * @returns {Promise<{appConfig: AppConfig, loadedConfigPath: string[]}>}
20
22
  */
21
- export async function loadAppConfig() {
23
+ export async function loadAppConfig(options = {}) {
24
+ const { skipTrustCheck = false } = options;
22
25
  const paths = [
23
26
  `${AGENT_ROOT}/.config/config.predefined.json`,
24
27
  `${AGENT_USER_CONFIG_DIR}/config.json`,
@@ -33,7 +36,7 @@ export async function loadAppConfig() {
33
36
  let merged = {};
34
37
 
35
38
  for (const filePath of paths) {
36
- const config = await loadConfigFile(path.resolve(filePath));
39
+ const config = await loadConfigFile(path.resolve(filePath), skipTrustCheck);
37
40
  if (Object.keys(config).length) {
38
41
  loadedConfigPath.push(filePath);
39
42
  }
@@ -55,9 +58,9 @@ export async function loadAppConfig() {
55
58
  },
56
59
  sandbox: config.sandbox ?? merged.sandbox,
57
60
  tools: {
58
- tavily: {
59
- ...(merged.tools?.tavily ?? {}),
60
- ...(config.tools?.tavily ?? {}),
61
+ searchWeb: {
62
+ ...(merged.tools?.searchWeb ?? {}),
63
+ ...(config.tools?.searchWeb ?? {}),
61
64
  },
62
65
  askGoogle: {
63
66
  ...(merged.tools?.askGoogle ?? {}),
@@ -81,9 +84,10 @@ export async function loadAppConfig() {
81
84
 
82
85
  /**
83
86
  * @param {string} filePath
87
+ * @param {boolean} [skipTrustCheck=false]
84
88
  * @returns {Promise<AppConfig>}
85
89
  */
86
- export async function loadConfigFile(filePath) {
90
+ export async function loadConfigFile(filePath, skipTrustCheck = false) {
87
91
  let content;
88
92
  try {
89
93
  content = await fs.readFile(filePath, "utf-8");
@@ -95,7 +99,7 @@ export async function loadConfigFile(filePath) {
95
99
  }
96
100
 
97
101
  const hash = crypto.createHash("sha256").update(content).digest("hex");
98
- const isTrusted = await isConfigHashTrusted(hash);
102
+ const isTrusted = skipTrustCheck || (await isConfigHashTrusted(hash));
99
103
 
100
104
  if (!isTrusted) {
101
105
  if (!process.stdout.isTTY) {
package/src/main.mjs CHANGED
@@ -33,6 +33,21 @@ if (cliArgs.showHelp) {
33
33
  printHelp();
34
34
  }
35
35
 
36
+ if (cliArgs.listModels) {
37
+ const { appConfig } = await loadAppConfig({ skipTrustCheck: true });
38
+ if (!appConfig.models || appConfig.models.length === 0) {
39
+ console.error("No models found in configuration.");
40
+ process.exit(1);
41
+ }
42
+ for (const model of appConfig.models) {
43
+ const platform = model.platform;
44
+ console.log(
45
+ `${model.name}+${model.variant} (platform: ${platform.name}+${platform.variant})`,
46
+ );
47
+ }
48
+ process.exit(0);
49
+ }
50
+
36
51
  (async () => {
37
52
  const startTime = new Date();
38
53
  const sessionId = [
@@ -118,8 +133,8 @@ if (cliArgs.showHelp) {
118
133
  createReportAsSubagentTool(),
119
134
  ];
120
135
 
121
- if (appConfig.tools?.tavily?.apiKey) {
122
- builtinTools.push(createTavilySearchTool(appConfig.tools.tavily));
136
+ if (appConfig.tools?.searchWeb?.tavilyApiKey) {
137
+ builtinTools.push(createTavilySearchTool(appConfig.tools.searchWeb));
123
138
  }
124
139
 
125
140
  if (appConfig.tools?.askGoogle) {
@@ -6,7 +6,7 @@
6
6
  import { noThrow } from "../utils/noThrow.mjs";
7
7
 
8
8
  /**
9
- * @param {{apiKey?: string}} config
9
+ * @param {{tavilyApiKey?: string}} config
10
10
  * @returns {Tool}
11
11
  */
12
12
  export function createTavilySearchTool(config) {
@@ -34,7 +34,7 @@ export function createTavilySearchTool(config) {
34
34
  const response = await fetch("https://api.tavily.com/search", {
35
35
  method: "POST",
36
36
  headers: {
37
- Authorization: `Bearer ${config.apiKey}`,
37
+ Authorization: `Bearer ${config.tavilyApiKey}`,
38
38
  "Content-Type": "application/json",
39
39
  },
40
40
  body: JSON.stringify({