@4onstudios/iris-agent 0.1.0 → 0.2.1

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
@@ -142,15 +142,52 @@ standard error. Each ACP process is bound to the workspace supplied at startup.
142
142
  To switch workspaces, close the process and respawn `iris-agent` with the new
143
143
  `--workspace` path.
144
144
 
145
+ When the current directory is the target workspace, `--workspace` is optional:
146
+
147
+ ```sh
148
+ OPENROUTER_API_KEY=... npx @4onstudios/iris-agent@latest --acp
149
+ ```
150
+
151
+ If the selected model's credentials are missing, Iris Agent stops before opening
152
+ the ACP connection and prints the required environment variable, a terminal
153
+ command, and a ready-to-paste VS Code ACP Client configuration. Configure API
154
+ keys in `acp.agents.<name>.env`; do not put them in the argument list:
155
+
156
+ ```json
157
+ {
158
+ "acp.agents": {
159
+ "Iris Agent": {
160
+ "command": "npx",
161
+ "args": ["@4onstudios/iris-agent@latest", "--acp"],
162
+ "env": {
163
+ "OPENROUTER_API_KEY": "your-openrouter-api-key"
164
+ }
165
+ }
166
+ }
167
+ }
168
+ ```
169
+
170
+ Use `--modelId` to select a direct provider: `openai/gpt-4o` with
171
+ `OPENAI_API_KEY`, `anthropic/claude-sonnet-4-5` with `ANTHROPIC_API_KEY`,
172
+ `google/gemini-2.5-pro` with `GOOGLE_GENERATIVE_AI_API_KEY`, or
173
+ `huggingface/...` with `HF_TOKEN`. Local `ollama/<model>` sessions do not need
174
+ a cloud API key.
175
+
176
+ Iris Agent advertises ACP session loading and listing, so
177
+ [VS Code ACP Client](https://github.com/formulahendry/vscode-acp) can display
178
+ and restore persisted conversations for the current workspace. It also returns
179
+ the per-session model configuration option required by the extension's composer
180
+ controls.
181
+
145
182
  ## CLI Usage
146
183
 
147
184
  ```sh
148
- iris-agent --workspace <path> [--acp | --chat] [--modelId <model>]
185
+ iris-agent [--workspace <path>] [--acp | --chat] [--modelId <model>]
149
186
  ```
150
187
 
151
188
  **Options:**
152
189
 
153
- - `--workspace` (required, `-w`) - Path to the workspace/project root
190
+ - `--workspace` (`-w`) - Path to the workspace/project root; defaults to the current working directory
154
191
  - `--acp` (`-a`) - Start ACP protocol server (stdio-based)
155
192
  - `--chat` (`-c`) - Start interactive chat mode
156
193
  - `--modelId` - Model identifier used for chat/ACP sessions (default: `openrouter/openai/gpt-4o` or `MODEL_ID` / `OPENROUTER_MODEL` env vars)
@@ -169,6 +206,9 @@ npm run cli -- --workspace . --chat --modelId openrouter/anthropic/claude-3.7-so
169
206
  # ACP server for IDE integration
170
207
  npm run cli -- --workspace . --acp
171
208
 
209
+ # ACP server from the current workspace
210
+ npx @4onstudios/iris-agent@latest --acp
211
+
172
212
  # ACP server with custom default model
173
213
  npm run cli -- --workspace . --acp --modelId openrouter/openai/gpt-4o
174
214
 
@@ -666,14 +706,9 @@ The project logo is available at
666
706
  community references. Keep the logo unchanged when using it as the project
667
707
  mark.
668
708
 
669
- ## Package publishing
709
+ ## Package
670
710
 
671
- The package is configured for public npm publication:
672
-
673
- ```sh
674
- npm login
675
- npm publish
676
- ```
711
+ The published package is available on [npm](https://www.npmjs.com/package/@4onstudios/iris-agent).
677
712
 
678
713
  Yarn users can install the published package with:
679
714
 
@@ -681,10 +716,4 @@ Yarn users can install the published package with:
681
716
  yarn global add @4onstudios/iris-agent
682
717
  ```
683
718
 
684
- Publishing requires access to the `@4onstudios` npm scope. The package is
685
- configured with public access, but npm credentials and organization
686
- permissions must be supplied by the publisher.
687
-
688
- See [RELEASING.md](./RELEASING.md) for npm publication and versioning steps.
689
-
690
719
  This project is released under the [MIT License](LICENSE).
@@ -9,7 +9,18 @@ import { executeCommand } from "../core/agent/tools/executeCommand.js";
9
9
  import { getSlashCommandDescriptors, isSlashCommandsFeatureEnabled, } from "../helpers/slashCommands.js";
10
10
  import { extractTokenUsageFromChunkPayload, mergeTokenUsage, } from "../helpers/tokenUsage.js";
11
11
  const DEFAULT_MAX_STEPS = 50;
12
+ const MAX_CONCURRENT_SESSION_LOADS = 16;
12
13
  const CHAT_SESSIONS_DIR = path.join(os.homedir(), ".iris", "chat-sessions");
14
+ const hasPersistedSessionCwd = (persisted) => typeof persisted?.cwd === "string" && persisted.cwd.length > 0;
15
+ const getPersistedSessionTitle = (persisted) => typeof persisted?.title === "string" ? persisted.title : undefined;
16
+ const getPersistedSessionUpdatedAt = (persisted) => {
17
+ if (typeof persisted.updatedAt !== "number" &&
18
+ typeof persisted.updatedAt !== "string") {
19
+ return undefined;
20
+ }
21
+ const timestamp = new Date(persisted.updatedAt);
22
+ return Number.isNaN(timestamp.getTime()) ? undefined : timestamp.toISOString();
23
+ };
13
24
  const isSafeChatSessionId = (sessionId) => /^[A-Za-z0-9._:-]+$/.test(sessionId);
14
25
  const getChatSessionPath = (sessionId) => path.join(CHAT_SESSIONS_DIR, `${sessionId}.json`);
15
26
  const loadPersistedChatSession = async (sessionId) => {
@@ -26,13 +37,33 @@ const loadPersistedChatSession = async (sessionId) => {
26
37
  return undefined;
27
38
  }
28
39
  };
29
- const savePersistedChatSession = async (sessionId, messages, existing) => {
40
+ const loadPersistedChatSessions = async (sessionIds) => {
41
+ const loaded = new Array(sessionIds.length);
42
+ let nextIndex = 0;
43
+ const worker = async () => {
44
+ while (nextIndex < sessionIds.length) {
45
+ const index = nextIndex;
46
+ nextIndex += 1;
47
+ const sessionId = sessionIds[index];
48
+ if (sessionId) {
49
+ loaded[index] = await loadPersistedChatSession(sessionId);
50
+ }
51
+ }
52
+ };
53
+ await Promise.all(Array.from({ length: Math.min(MAX_CONCURRENT_SESSION_LOADS, sessionIds.length) }, worker));
54
+ return loaded.flatMap((persisted, index) => {
55
+ const sessionId = sessionIds[index];
56
+ return persisted && sessionId ? [{ sessionId, persisted }] : [];
57
+ });
58
+ };
59
+ const savePersistedChatSession = async (sessionId, messages, session, existing) => {
30
60
  if (!isSafeChatSessionId(sessionId))
31
61
  return;
32
62
  const timestamp = Date.now();
33
63
  const payload = {
34
64
  id: sessionId,
35
- title: existing?.title,
65
+ cwd: path.resolve(session.cwd),
66
+ title: session.title ?? getPersistedSessionTitle(existing),
36
67
  createdAt: existing?.createdAt ?? timestamp,
37
68
  updatedAt: timestamp,
38
69
  messages,
@@ -92,6 +123,18 @@ export function assertAcpWorkspace(params, boundWorkspaceRoot) {
92
123
  throw new Error(`This ACP process is bound to '${path.resolve(boundWorkspaceRoot)}'. Close it and respawn iris-agent with --workspace '${path.resolve(requestedWorkspace)}' to switch workspaces.`);
93
124
  }
94
125
  }
126
+ const resolveAcpWorkspace = (params, boundWorkspaceRoot) => {
127
+ if (boundWorkspaceRoot) {
128
+ assertAcpWorkspace(params, boundWorkspaceRoot);
129
+ return boundWorkspaceRoot;
130
+ }
131
+ const requestedWorkspace = typeof params?.workspaceRoot === "string"
132
+ ? params.workspaceRoot
133
+ : typeof params?.cwd === "string"
134
+ ? params.cwd
135
+ : undefined;
136
+ return path.resolve(requestedWorkspace || process.cwd());
137
+ };
95
138
  const toPromptText = (prompt) => prompt
96
139
  .map((block) => {
97
140
  if (block.type === "text")
@@ -184,23 +227,34 @@ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
184
227
  })
185
228
  .catch(() => undefined);
186
229
  };
230
+ const getConfigOptions = (modelId) => {
231
+ const currentModel = modelId || "default";
232
+ return [
233
+ {
234
+ id: "model",
235
+ name: "Model",
236
+ type: "select",
237
+ category: "model",
238
+ currentValue: currentModel,
239
+ options: [{ value: currentModel, name: currentModel }],
240
+ },
241
+ ];
242
+ };
187
243
  return acp
188
244
  .agent({ name: "iris-agent" })
189
245
  .onRequest(acp.methods.agent.initialize, async () => ({
190
246
  protocolVersion: acp.PROTOCOL_VERSION,
191
247
  agentCapabilities: {
192
248
  loadSession: true,
193
- sessionCapabilities: { close: {} },
249
+ sessionCapabilities: { close: {}, list: {} },
194
250
  },
195
251
  agentInfo: {
196
252
  name: "iris-agent",
197
- version: "0.1.0",
253
+ version: "0.2.0",
198
254
  },
199
255
  }))
200
256
  .onRequest(acp.methods.agent.session.new, async (ctx) => {
201
- if (boundWorkspaceRoot) {
202
- assertAcpWorkspace({ cwd: ctx.params.cwd }, boundWorkspaceRoot);
203
- }
257
+ const sessionWorkspace = resolveAcpWorkspace(ctx.params, boundWorkspaceRoot);
204
258
  const irisMeta = readIrisMeta(ctx.params._meta);
205
259
  const requestedModel = irisMeta.modelId ||
206
260
  (typeof ctx.params.modelId === "string"
@@ -210,18 +264,19 @@ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
210
264
  ? irisMeta.chatSessionId
211
265
  : randomUUID();
212
266
  sessions.set(sessionId, {
213
- cwd: boundWorkspaceRoot || ctx.params.cwd,
267
+ cwd: sessionWorkspace,
214
268
  modelId: requestedModel,
215
269
  history: [],
216
270
  });
217
271
  await notifyAvailableCommands(ctx, sessionId);
218
- return { sessionId };
272
+ return {
273
+ sessionId,
274
+ configOptions: getConfigOptions(requestedModel),
275
+ };
219
276
  })
220
277
  .onRequest(acp.methods.agent.session.load, async (ctx) => {
221
278
  const sessionId = ctx.params.sessionId;
222
- if (boundWorkspaceRoot) {
223
- assertAcpWorkspace({ cwd: ctx.params.cwd }, boundWorkspaceRoot);
224
- }
279
+ const requestedWorkspace = resolveAcpWorkspace(ctx.params, boundWorkspaceRoot);
225
280
  const irisMeta = readIrisMeta(ctx.params._meta);
226
281
  const requestedModel = irisMeta.modelId ||
227
282
  (typeof ctx.params.modelId === "string"
@@ -231,10 +286,18 @@ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
231
286
  if (!persisted) {
232
287
  throw new Error(`No persisted chat session found for '${sessionId}'`);
233
288
  }
289
+ const sessionWorkspace = hasPersistedSessionCwd(persisted)
290
+ ? path.resolve(persisted.cwd)
291
+ : requestedWorkspace;
292
+ if (boundWorkspaceRoot &&
293
+ path.resolve(sessionWorkspace) !== path.resolve(boundWorkspaceRoot)) {
294
+ throw new Error(`Persisted ACP session '${sessionId}' belongs to '${sessionWorkspace}', not this process's bound workspace '${boundWorkspaceRoot}'.`);
295
+ }
234
296
  const history = persisted.messages.slice();
235
297
  sessions.set(sessionId, {
236
- cwd: boundWorkspaceRoot || ctx.params.cwd,
298
+ cwd: sessionWorkspace,
237
299
  modelId: requestedModel,
300
+ title: getPersistedSessionTitle(persisted),
238
301
  history,
239
302
  });
240
303
  for (const message of history) {
@@ -247,7 +310,37 @@ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
247
310
  });
248
311
  }
249
312
  await notifyAvailableCommands(ctx, sessionId);
250
- return {};
313
+ return { configOptions: getConfigOptions(requestedModel) };
314
+ })
315
+ .onRequest(acp.methods.agent.session.list, async (ctx) => {
316
+ const requestedCwd = resolveAcpWorkspace(ctx.params, boundWorkspaceRoot);
317
+ let entries;
318
+ try {
319
+ entries = await fs.readdir(CHAT_SESSIONS_DIR, {
320
+ withFileTypes: true,
321
+ });
322
+ }
323
+ catch (error) {
324
+ if (error.code !== "ENOENT") {
325
+ throw error;
326
+ }
327
+ return { sessions: [] };
328
+ }
329
+ const persistedSessions = (await loadPersistedChatSessions(entries
330
+ .filter((entry) => entry.isFile() && entry.name.endsWith(".json"))
331
+ .map((entry) => entry.name.slice(0, -".json".length))))
332
+ .filter(({ persisted }) => !hasPersistedSessionCwd(persisted) ||
333
+ path.resolve(persisted.cwd) === requestedCwd)
334
+ .map(({ sessionId, persisted }) => ({
335
+ sessionId,
336
+ cwd: hasPersistedSessionCwd(persisted)
337
+ ? path.resolve(persisted.cwd)
338
+ : requestedCwd,
339
+ title: getPersistedSessionTitle(persisted),
340
+ updatedAt: getPersistedSessionUpdatedAt(persisted),
341
+ }));
342
+ persistedSessions.sort((left, right) => (right.updatedAt || "").localeCompare(left.updatedAt || ""));
343
+ return { sessions: persistedSessions };
251
344
  })
252
345
  .onRequest(acp.methods.agent.session.setConfigOption, async (ctx) => {
253
346
  const sessionId = ctx.params.sessionId;
@@ -265,20 +358,7 @@ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
265
358
  session.modelId = undefined;
266
359
  }
267
360
  }
268
- const currentModel = session.modelId || "default";
269
- const configOptions = [
270
- {
271
- id: "model",
272
- name: "Model",
273
- type: "select",
274
- category: "model",
275
- currentValue: currentModel,
276
- options: [
277
- { value: currentModel, name: currentModel },
278
- ],
279
- },
280
- ];
281
- return { configOptions };
361
+ return { configOptions: getConfigOptions(session.modelId) };
282
362
  })
283
363
  .onRequest(acp.methods.agent.session.prompt, async (ctx) => {
284
364
  const sessionId = ctx.params.sessionId;
@@ -305,10 +385,13 @@ export const createAcpAgentApp = (agentProvider, workspaceRoot) => {
305
385
  session.history.push({ role: "user", content: promptText });
306
386
  }
307
387
  const persistTurn = async (assistantText) => {
388
+ if (promptText && !session.title) {
389
+ session.title = promptText.replace(/\s+/g, " ").slice(0, 120);
390
+ }
308
391
  if (assistantText) {
309
392
  session.history.push({ role: "assistant", content: assistantText });
310
393
  }
311
- await savePersistedChatSession(sessionId, session.history, await loadPersistedChatSession(sessionId));
394
+ await savePersistedChatSession(sessionId, session.history, session, await loadPersistedChatSession(sessionId));
312
395
  };
313
396
  const turnSignal = activeTurn.abortController.signal;
314
397
  if (sessions.get(sessionId) !== session || turnSignal.aborted) {
@@ -809,6 +892,6 @@ export async function startAcpServer(runtimeAgentOrFactory, workspaceRoot) {
809
892
  const output = Writable.toWeb(process.stdout);
810
893
  const input = Readable.toWeb(process.stdin);
811
894
  const connection = createAcpAgentApp(runtimeAgentOrFactory, workspaceRoot).connect(acp.ndJsonStream(output, input));
812
- console.error("ACP agent ready: iris-agent@0.1.0 (stdio)");
895
+ console.error("ACP agent ready: iris-agent@0.2.0 (stdio)");
813
896
  await connection.closed;
814
897
  }
@@ -0,0 +1,3 @@
1
+ type Environment = Record<string, string | undefined>;
2
+ export declare const getMissingProviderSetup: (modelId: string, environment?: Environment) => string | undefined;
3
+ export {};
@@ -0,0 +1,134 @@
1
+ const getConfiguredKey = (environment, keys) => keys.some((key) => Boolean(environment[key]?.trim()));
2
+ const getRequiredProvider = (modelId, environment) => {
3
+ const normalizedModelId = modelId.endsWith("-other")
4
+ ? modelId.slice(0, -"-other".length)
5
+ : modelId;
6
+ if (normalizedModelId.startsWith("ollama/") ||
7
+ normalizedModelId.startsWith("local/")) {
8
+ return undefined;
9
+ }
10
+ if (normalizedModelId.startsWith("huggingface/") ||
11
+ normalizedModelId.endsWith(":fireworks-ai")) {
12
+ return getConfiguredKey(environment, ["HF_TOKEN"])
13
+ ? undefined
14
+ : {
15
+ credential: "HF_TOKEN",
16
+ modelExample: "huggingface/Qwen/Qwen2.5-Coder-32B-Instruct",
17
+ description: "a Hugging Face Inference Providers token",
18
+ };
19
+ }
20
+ if (normalizedModelId.startsWith("openrouter/")) {
21
+ return getConfiguredKey(environment, ["OPENROUTER_API_KEY"])
22
+ ? undefined
23
+ : {
24
+ credential: "OPENROUTER_API_KEY",
25
+ modelExample: "openrouter/openai/gpt-4o",
26
+ description: "an OpenRouter API key",
27
+ };
28
+ }
29
+ if (normalizedModelId.startsWith("openai/")) {
30
+ return getConfiguredKey(environment, ["OPENAI_API_KEY"])
31
+ ? undefined
32
+ : {
33
+ credential: "OPENAI_API_KEY",
34
+ modelExample: "openai/gpt-4o",
35
+ description: "an OpenAI API key",
36
+ };
37
+ }
38
+ if (normalizedModelId.startsWith("anthropic/")) {
39
+ return getConfiguredKey(environment, ["ANTHROPIC_API_KEY"])
40
+ ? undefined
41
+ : {
42
+ credential: "ANTHROPIC_API_KEY",
43
+ modelExample: "anthropic/claude-sonnet-4-5",
44
+ description: "an Anthropic API key",
45
+ };
46
+ }
47
+ if (normalizedModelId.startsWith("google/") ||
48
+ normalizedModelId.startsWith("gemini")) {
49
+ if (getConfiguredKey(environment, [
50
+ "GOOGLE_GENERATIVE_AI_API_KEY",
51
+ "GEMINI_API_KEY",
52
+ ])) {
53
+ return undefined;
54
+ }
55
+ return {
56
+ credential: "GOOGLE_GENERATIVE_AI_API_KEY",
57
+ modelExample: "google/gemini-2.5-pro",
58
+ description: "a Google AI API key",
59
+ };
60
+ }
61
+ if (normalizedModelId.includes("/")) {
62
+ return getConfiguredKey(environment, ["OPENROUTER_API_KEY"])
63
+ ? undefined
64
+ : {
65
+ credential: "OPENROUTER_API_KEY",
66
+ modelExample: normalizedModelId,
67
+ description: "an OpenRouter API key",
68
+ };
69
+ }
70
+ if (normalizedModelId.startsWith("claude")) {
71
+ if (getConfiguredKey(environment, [
72
+ "ANTHROPIC_API_KEY",
73
+ "OPENROUTER_API_KEY",
74
+ ])) {
75
+ return undefined;
76
+ }
77
+ return {
78
+ credential: "ANTHROPIC_API_KEY",
79
+ modelExample: "anthropic/claude-sonnet-4-5",
80
+ description: "an Anthropic API key",
81
+ };
82
+ }
83
+ if (normalizedModelId.startsWith("gpt") ||
84
+ normalizedModelId.startsWith("o1") ||
85
+ normalizedModelId.startsWith("o3")) {
86
+ if (getConfiguredKey(environment, ["OPENAI_API_KEY", "OPENROUTER_API_KEY"])) {
87
+ return undefined;
88
+ }
89
+ return {
90
+ credential: "OPENAI_API_KEY",
91
+ modelExample: "openai/gpt-4o",
92
+ description: "an OpenAI API key",
93
+ };
94
+ }
95
+ return getConfiguredKey(environment, ["OPENAI_API_KEY", "OPENROUTER_API_KEY"])
96
+ ? undefined
97
+ : {
98
+ credential: "OPENROUTER_API_KEY",
99
+ modelExample: `openrouter/${normalizedModelId}`,
100
+ description: "an OpenRouter API key",
101
+ };
102
+ };
103
+ export const getMissingProviderSetup = (modelId, environment = process.env) => {
104
+ const requiredProvider = getRequiredProvider(modelId, environment);
105
+ if (!requiredProvider)
106
+ return undefined;
107
+ const { credential, description, modelExample } = requiredProvider;
108
+ return [
109
+ `Iris Agent cannot start model '${modelId}' because ${credential} is not configured.`,
110
+ "",
111
+ `Provide ${description} before starting the agent:`,
112
+ ` ${credential}=<your-api-key> npx @4onstudios/iris-agent@latest --acp --modelId ${modelId}`,
113
+ "",
114
+ "For VS Code ACP Client, add the key to the agent's environment in settings.json:",
115
+ JSON.stringify({
116
+ "acp.agents": {
117
+ "Iris Agent": {
118
+ command: "npx",
119
+ args: ["@4onstudios/iris-agent@latest", "--acp", "--modelId", modelId],
120
+ env: { [credential]: "<your-api-key>" },
121
+ },
122
+ },
123
+ }, null, 2),
124
+ "",
125
+ "Supported provider options:",
126
+ " OpenRouter: OPENROUTER_API_KEY with --modelId openrouter/openai/gpt-4o",
127
+ " OpenAI: OPENAI_API_KEY with --modelId openai/gpt-4o",
128
+ " Anthropic: ANTHROPIC_API_KEY with --modelId anthropic/claude-sonnet-4-5",
129
+ " Google: GOOGLE_GENERATIVE_AI_API_KEY with --modelId google/gemini-2.5-pro",
130
+ " Hugging Face: HF_TOKEN with --modelId huggingface/Qwen/Qwen2.5-Coder-32B-Instruct",
131
+ " Local Ollama: no cloud key; use --modelId ollama/<model> (optionally set OLLAMA_BASE_URL).",
132
+ `Example selected-model identifier: ${modelExample}`,
133
+ ].join("\n");
134
+ };
package/dist/api/agent.js CHANGED
@@ -465,7 +465,7 @@ const createGeneratedAgentRuntimeAdapter = (agent) => {
465
465
  descriptor: {
466
466
  id: "generated-agent-runtime",
467
467
  name: "Generated Agent Runtime",
468
- version: "0.1.0",
468
+ version: "0.2.0",
469
469
  source: "external",
470
470
  },
471
471
  async startSession(context) {
@@ -51,13 +51,13 @@ export declare const listDirectoryTool: {
51
51
  includeHidden: z.ZodDefault<z.ZodBoolean>;
52
52
  maxDepth: z.ZodDefault<z.ZodNumber>;
53
53
  }, "strip", z.ZodTypeAny, {
54
- maxDepth?: number;
55
54
  recursive?: boolean;
55
+ maxDepth?: number;
56
56
  dirPath?: string;
57
57
  includeHidden?: boolean;
58
58
  }, {
59
- maxDepth?: number;
60
59
  recursive?: boolean;
60
+ maxDepth?: number;
61
61
  dirPath?: string;
62
62
  includeHidden?: boolean;
63
63
  }>;
package/dist/cli.d.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  /**
3
3
  * iris-agent CLI with ACP (Agent Client Protocol) support
4
4
  * Usage:
5
- * iris-agent --workspace /path/to/workspace --modelId openrouter/openai/gpt-4o --acp
5
+ * iris-agent --acp
6
6
  * iris-agent --workspace /path/to/workspace --modelId openrouter/openai/gpt-4o --chat
7
7
  */
8
8
  export {};
package/dist/cli.js CHANGED
@@ -2,13 +2,14 @@
2
2
  /**
3
3
  * iris-agent CLI with ACP (Agent Client Protocol) support
4
4
  * Usage:
5
- * iris-agent --workspace /path/to/workspace --modelId openrouter/openai/gpt-4o --acp
5
+ * iris-agent --acp
6
6
  * iris-agent --workspace /path/to/workspace --modelId openrouter/openai/gpt-4o --chat
7
7
  */
8
8
  import yargs from "yargs";
9
9
  import { hideBin } from "yargs/helpers";
10
10
  import { createCodingAgent } from "./api/core/agent/index.js";
11
11
  import { startAcpServer } from "./api/acp/acpServer.js";
12
+ import { getMissingProviderSetup } from "./api/acp/providerSetup.js";
12
13
  const defaultModelId = process.env.MODEL_ID ||
13
14
  process.env.OPENROUTER_MODEL ||
14
15
  "openrouter/openai/gpt-4o";
@@ -17,7 +18,7 @@ const argv = yargs(hideBin(process.argv))
17
18
  alias: "w",
18
19
  type: "string",
19
20
  description: "Workspace root path",
20
- required: true,
21
+ default: process.cwd(),
21
22
  })
22
23
  .option("acp", {
23
24
  alias: "a",
@@ -58,6 +59,12 @@ async function main() {
58
59
  console.log(`📁 Workspace: ${workspaceRoot}`);
59
60
  console.log(`🤖 Model: ${modelId}`);
60
61
  if (argv.acp) {
62
+ const missingProviderSetup = getMissingProviderSetup(modelId);
63
+ if (missingProviderSetup) {
64
+ console.error(missingProviderSetup);
65
+ process.exitCode = 1;
66
+ return;
67
+ }
61
68
  console.log("🔗 Starting ACP server over stdio...");
62
69
  await startAcpServer((requestedModelId, targetWorkspace) => createCodingAgent(requestedModelId || modelId, targetWorkspace || workspaceRoot), workspaceRoot);
63
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@4onstudios/iris-agent",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "private": false,
5
5
  "description": "Standalone HTTP coding-agent service and CLI with ACP (Agent Client Protocol) support.",
6
6
  "type": "module",