@robota-sdk/agent-cli 3.0.0-beta.53 → 3.0.0-beta.55

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
@@ -6,6 +6,8 @@ AI coding assistant CLI built on Robota SDK. Loads AGENTS.md/CLAUDE.md for proje
6
6
 
7
7
  ## Installation
8
8
 
9
+ Requires Node.js 22+.
10
+
9
11
  ```bash
10
12
  # Global install
11
13
  npm install -g @robota-sdk/agent-cli
@@ -26,9 +28,9 @@ robota -p "List all files" # Print mode (one-shot, exit after response)
26
28
 
27
29
  ### Environment Variables
28
30
 
29
- | Variable | Description | Required |
30
- | ------------------- | ----------------- | -------- |
31
- | `ANTHROPIC_API_KEY` | Anthropic API key | Yes |
31
+ | Variable | Description | Required |
32
+ | ------------------- | ---------------------------------------------- | -------------- |
33
+ | `ANTHROPIC_API_KEY` | Anthropic API key for the `anthropic` provider | Anthropic only |
32
34
 
33
35
  Set your key before running:
34
36
 
@@ -107,12 +109,12 @@ git diff | robota -p "Summarize changes" --output-format stream-json
107
109
 
108
110
  ## First-Run Setup
109
111
 
110
- When no settings file exists, the CLI prompts for:
112
+ When no usable settings file exists, the CLI prompts for:
111
113
 
112
114
  1. **Anthropic API key** (input masked with asterisks)
113
115
  2. **Response language** (ko/en/ja/zh, default: en)
114
116
 
115
- Creates `~/.robota/settings.json`. Use `robota --reset` to return to first-run state.
117
+ Creates `~/.robota/settings.json`. Use `robota --reset` to return to first-run state. OpenAI-compatible local profiles, such as LM Studio, can be configured manually without using the first-run Anthropic prompt.
116
118
 
117
119
  ## Built-in Tools
118
120
 
@@ -267,22 +269,37 @@ The `/plugin` command opens an interactive TUI for managing bundle plugins:
267
269
 
268
270
  ## Configuration
269
271
 
270
- Settings are loaded from (highest priority first):
272
+ Settings are merged in this order, from lowest to highest priority:
271
273
 
272
- 1. `.robota/settings.local.json` (local, gitignored)
273
- 2. `.robota/settings.json` (project, shared)
274
- 3. `.claude/settings.json` (project, Claude Code compatible)
275
- 4. `~/.robota/settings.json` (user global)
276
- 5. `~/.claude/settings.json` (user global, Claude Code compatible)
274
+ 1. `~/.robota/settings.json` (user global)
275
+ 2. `~/.claude/settings.json` (user global, Claude Code compatible)
276
+ 3. `.robota/settings.json` (project, shared)
277
+ 4. `.robota/settings.local.json` (local, gitignored)
278
+ 5. `.claude/settings.json` (project, Claude Code compatible)
279
+ 6. `.claude/settings.local.json` (local, gitignored, Claude Code compatible)
277
280
 
278
281
  ```json
279
282
  {
280
283
  "defaultMode": "default",
281
284
  "language": "en",
282
- "provider": {
283
- "name": "anthropic",
284
- "model": "claude-sonnet-4-6",
285
- "apiKey": "$ENV:ANTHROPIC_API_KEY"
285
+ "currentProvider": "gemma",
286
+ "providers": {
287
+ "gemma": {
288
+ "type": "gemma",
289
+ "model": "supergemma4-26b-uncensored-v2",
290
+ "apiKey": "lm-studio",
291
+ "baseURL": "http://localhost:1234/v1"
292
+ },
293
+ "openai": {
294
+ "type": "openai",
295
+ "model": "<openai-compatible-model>",
296
+ "apiKey": "$ENV:OPENAI_API_KEY"
297
+ },
298
+ "anthropic": {
299
+ "type": "anthropic",
300
+ "model": "claude-sonnet-4-6",
301
+ "apiKey": "$ENV:ANTHROPIC_API_KEY"
302
+ }
286
303
  },
287
304
  "permissions": {
288
305
  "allow": ["Bash(pnpm *)"],
@@ -291,6 +308,18 @@ Settings are loaded from (highest priority first):
291
308
  }
292
309
  ```
293
310
 
311
+ `currentProvider` selects a profile from `providers`. Gemma-family LM Studio models use `type: "gemma"` so Robota can apply Gemma-specific channel-marker projection while still talking to the OpenAI-compatible `/v1/chat/completions` API through `baseURL`. Generic OpenAI-compatible profiles use `type: "openai"` and do not apply Gemma filtering. The legacy single-provider shape remains supported:
312
+
313
+ ```json
314
+ {
315
+ "provider": {
316
+ "name": "anthropic",
317
+ "model": "claude-sonnet-4-6",
318
+ "apiKey": "$ENV:ANTHROPIC_API_KEY"
319
+ }
320
+ }
321
+ ```
322
+
294
323
  ## Context Discovery
295
324
 
296
325
  The CLI automatically discovers and loads:
@@ -309,7 +338,9 @@ All context is assembled into the system prompt.
309
338
 
310
339
  ## Session Logging
311
340
 
312
- Session logs are written to `.robota/logs/{sessionId}.jsonl` in JSONL format by default, capturing structured events for diagnostics and replay.
341
+ Session logs are written to `.robota/logs/{sessionId}.jsonl` in JSONL format by default, capturing structured events for diagnostics and replay. Background task lifecycle/progress events are logged there as they happen. Child-process subagents also write append-only transcripts to `.robota/logs/{sessionId}/subagents/{agentId}.jsonl`, including streaming text deltas while the local provider request is still running.
342
+
343
+ Resumable session JSON is written to `.robota/sessions/{sessionId}.json` for the current project and includes messages, UI history, the exact system prompt, registered tool schemas, and background task snapshots. High-frequency streaming chunks stay in JSONL transcript files; the session JSON stores task state and transcript paths.
313
344
 
314
345
  ## Architecture
315
346
 
@@ -345,7 +376,7 @@ bin.ts → cli.ts (arg parsing)
345
376
  | `@robota-sdk/agent-sdk` | Session factory, query, config, context |
346
377
  | `@robota-sdk/agent-core` | Types (TPermissionMode, TToolArgs) |
347
378
  | `@robota-sdk/agent-transport-headless` | Headless runner for print mode (`-p`) |
348
- | `ink`, `react` | TUI rendering |
379
+ | `ink` 7, `react` 19.2+ | TUI rendering |
349
380
  | `ink-select-input` | Arrow-key selection (permission prompt) |
350
381
  | `ink-spinner` | Loading spinner |
351
382
  | `chalk` | Terminal colors |
package/dist/node/bin.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startCli
4
- } from "./chunk-CXBD4JNS.js";
4
+ } from "./chunk-H3NRW5FW.js";
5
+ import "./chunk-2JAZDNYT.js";
5
6
 
6
7
  // src/bin.ts
8
+ import { createAgentCommandModule } from "@robota-sdk/agent-command-agent";
7
9
  process.on("uncaughtException", (err) => {
8
10
  const msg = err.message ?? "";
9
11
  const isLikelyIME = msg.includes("string-width") || msg.includes("setCursorPosition") || msg.includes("getStringWidth") || msg.includes("slice") || msg.includes("charCodeAt");
@@ -14,7 +16,7 @@ process.on("uncaughtException", (err) => {
14
16
  }
15
17
  throw err;
16
18
  });
17
- startCli().catch((err) => {
19
+ startCli({ commandModules: [createAgentCommandModule()] }).catch((err) => {
18
20
  const message = err instanceof Error ? err.message : String(err);
19
21
  process.stderr.write(message + "\n");
20
22
  process.exit(1);
@@ -0,0 +1,246 @@
1
+ // src/utils/provider-factory.ts
2
+ import { readFileSync, existsSync } from "fs";
3
+ import { join } from "path";
4
+ import { homedir } from "os";
5
+
6
+ // src/utils/provider-default-definitions.ts
7
+ import { createAnthropicProviderDefinition } from "@robota-sdk/agent-provider-anthropic";
8
+ import { createGemmaProviderDefinition } from "@robota-sdk/agent-provider-gemma";
9
+ import { createOpenAIProviderDefinition } from "@robota-sdk/agent-provider-openai";
10
+ var DEFAULT_PROVIDER_DEFINITIONS = [
11
+ createAnthropicProviderDefinition(),
12
+ createOpenAIProviderDefinition(),
13
+ createGemmaProviderDefinition()
14
+ ];
15
+
16
+ // src/utils/provider-definition.ts
17
+ import { findProviderDefinition, formatSupportedProviderTypes } from "@robota-sdk/agent-core";
18
+
19
+ // src/utils/provider-factory.ts
20
+ function readProviderSettings(cwd, options = {}) {
21
+ const merged = readMergedProviderSettings(cwd);
22
+ const providerConfig = resolveActiveProvider(
23
+ merged,
24
+ options.providerOverride,
25
+ getProviderDefinitions(options)
26
+ );
27
+ if (providerConfig !== void 0) {
28
+ return providerConfig;
29
+ }
30
+ throw new Error("No provider configuration found. Run `robota` to set up.");
31
+ }
32
+ function readMergedProviderSettings(cwd) {
33
+ const paths = [
34
+ join(homedir(), ".robota", "settings.json"),
35
+ join(homedir(), ".claude", "settings.json"),
36
+ join(cwd, ".robota", "settings.json"),
37
+ join(cwd, ".robota", "settings.local.json"),
38
+ join(cwd, ".claude", "settings.json"),
39
+ join(cwd, ".claude", "settings.local.json")
40
+ ];
41
+ return paths.reduce((settings, filePath) => {
42
+ const parsed = readSettingsFile(filePath);
43
+ if (parsed === void 0) {
44
+ return settings;
45
+ }
46
+ return mergeSettings(settings, parsed);
47
+ }, {});
48
+ }
49
+ function readSettingsFile(filePath) {
50
+ if (!existsSync(filePath)) {
51
+ return void 0;
52
+ }
53
+ try {
54
+ const raw = readFileSync(filePath, "utf8");
55
+ return JSON.parse(raw);
56
+ } catch {
57
+ return void 0;
58
+ }
59
+ }
60
+ function mergeSettings(base, override) {
61
+ return {
62
+ ...base,
63
+ ...override,
64
+ provider: base.provider !== void 0 || override.provider !== void 0 ? { ...base.provider, ...override.provider } : void 0,
65
+ providers: base.providers !== void 0 || override.providers !== void 0 ? mergeProviders(base.providers, override.providers) : void 0
66
+ };
67
+ }
68
+ function mergeProviders(base, override) {
69
+ const result = { ...base ?? {} };
70
+ for (const [name, profile] of Object.entries(override ?? {})) {
71
+ result[name] = { ...result[name], ...profile };
72
+ }
73
+ return result;
74
+ }
75
+ function resolveActiveProvider(settings, providerOverride, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
76
+ const activeProvider = providerOverride ?? settings.currentProvider;
77
+ if (activeProvider !== void 0) {
78
+ const profile = settings.providers?.[activeProvider];
79
+ if (profile === void 0) {
80
+ throw new Error(`Provider profile "${activeProvider}" was not found in providers`);
81
+ }
82
+ if (!profile.type) {
83
+ throw new Error(`Provider profile "${activeProvider}" is missing type`);
84
+ }
85
+ return normalizeProviderConfig(
86
+ {
87
+ name: profile.type,
88
+ model: profile.model,
89
+ apiKey: profile.apiKey,
90
+ baseURL: profile.baseURL,
91
+ timeout: profile.timeout
92
+ },
93
+ providerDefinitions
94
+ );
95
+ }
96
+ const provider = settings.provider;
97
+ if (provider?.name) {
98
+ return normalizeProviderConfig(
99
+ {
100
+ name: provider.name,
101
+ model: provider.model,
102
+ apiKey: provider.apiKey,
103
+ baseURL: provider.baseURL,
104
+ timeout: provider.timeout
105
+ },
106
+ providerDefinitions
107
+ );
108
+ }
109
+ return void 0;
110
+ }
111
+ function normalizeProviderConfig(settings, providerDefinitions) {
112
+ const defaults = findProviderDefinition(providerDefinitions, settings.name)?.defaults ?? {};
113
+ const model = settings.model ?? defaults.model;
114
+ if (!model) {
115
+ throw new Error(`Provider ${settings.name} requires model`);
116
+ }
117
+ return {
118
+ name: settings.name,
119
+ model,
120
+ apiKey: settings.apiKey !== void 0 ? resolveEnvRef(settings.apiKey) : defaults.apiKey,
121
+ baseURL: settings.baseURL ?? defaults.baseURL,
122
+ timeout: settings.timeout
123
+ };
124
+ }
125
+ function resolveEnvRef(value) {
126
+ const envPrefix = "$ENV:";
127
+ if (!value.startsWith(envPrefix)) {
128
+ return value;
129
+ }
130
+ const envName = value.slice(envPrefix.length);
131
+ return process.env[envName] ?? value;
132
+ }
133
+ function resolveProfileApiKey(profile) {
134
+ if (profile.apiKey !== void 0) {
135
+ return profile.apiKey;
136
+ }
137
+ if (profile.apiKeyEnv !== void 0) {
138
+ return process.env[profile.apiKeyEnv];
139
+ }
140
+ return void 0;
141
+ }
142
+ function createProviderFromConfig(settings, providerDefinitions) {
143
+ const definition = findProviderDefinition(providerDefinitions, settings.name);
144
+ if (definition === void 0) {
145
+ throw new Error(
146
+ `Unknown provider: ${settings.name}. Currently supported: ${formatSupportedProviderTypes(providerDefinitions)}`
147
+ );
148
+ }
149
+ if (definition.requiresApiKey === true && !settings.apiKey) {
150
+ throw new Error(`Provider ${settings.name} requires apiKey`);
151
+ }
152
+ return definition.createProvider(settings);
153
+ }
154
+ function createProviderFromSettings(cwd, modelOverride, options = {}) {
155
+ const providerDefinitions = getProviderDefinitions(options);
156
+ const settings = readProviderSettings(cwd, { ...options, providerDefinitions });
157
+ const model = modelOverride ?? settings.model;
158
+ return createProviderFromConfig({ ...settings, model }, providerDefinitions);
159
+ }
160
+ function createProviderFromProfile(profile, modelOverride, providerDefinitions = DEFAULT_PROVIDER_DEFINITIONS) {
161
+ return createProviderFromConfig(
162
+ normalizeProviderConfig(
163
+ {
164
+ name: profile.type,
165
+ model: modelOverride ?? profile.model,
166
+ apiKey: resolveProfileApiKey(profile),
167
+ baseURL: profile.baseURL,
168
+ timeout: profile.timeout
169
+ },
170
+ providerDefinitions
171
+ ),
172
+ providerDefinitions
173
+ );
174
+ }
175
+ function getProviderDefinitions(options) {
176
+ return options.providerDefinitions ?? DEFAULT_PROVIDER_DEFINITIONS;
177
+ }
178
+
179
+ // src/subagents/child-process-subagent-ipc.ts
180
+ function isRecord(value) {
181
+ return typeof value === "object" && value !== null;
182
+ }
183
+ function hasString(value, key) {
184
+ return typeof value[key] === "string";
185
+ }
186
+ function isStartPayload(value) {
187
+ if (!isRecord(value)) return false;
188
+ if (!hasString(value, "jobId")) return false;
189
+ if (!isRecord(value.request)) return false;
190
+ if (!hasString(value.request, "type")) return false;
191
+ if (!hasString(value.request, "prompt")) return false;
192
+ if (!isRecord(value.agentDefinition)) return false;
193
+ if (!hasString(value.agentDefinition, "name")) return false;
194
+ if (!hasString(value.agentDefinition, "systemPrompt")) return false;
195
+ if (!isRecord(value.parentConfig)) return false;
196
+ if (!isRecord(value.parentContext)) return false;
197
+ if (!isRecord(value.providerProfile)) return false;
198
+ if (!hasString(value.providerProfile, "type")) return false;
199
+ return hasString(value.providerProfile, "model");
200
+ }
201
+ function isSubagentWorkerParentMessage(value) {
202
+ if (!isRecord(value) || !hasString(value, "type")) return false;
203
+ switch (value.type) {
204
+ case "start":
205
+ return isStartPayload(value.payload);
206
+ case "send":
207
+ return hasString(value, "prompt");
208
+ case "cancel":
209
+ return value.reason === void 0 || typeof value.reason === "string";
210
+ default:
211
+ return false;
212
+ }
213
+ }
214
+ function isSubagentWorkerChildMessage(value) {
215
+ if (!isRecord(value) || !hasString(value, "type")) return false;
216
+ switch (value.type) {
217
+ case "ready":
218
+ return true;
219
+ case "text_delta":
220
+ return hasString(value, "delta");
221
+ case "tool_start":
222
+ return hasString(value, "toolName");
223
+ case "tool_end":
224
+ return hasString(value, "toolName") && typeof value.success === "boolean";
225
+ case "result":
226
+ return hasString(value, "output");
227
+ case "error":
228
+ return hasString(value, "message");
229
+ case "cancelled":
230
+ return value.reason === void 0 || typeof value.reason === "string";
231
+ default:
232
+ return false;
233
+ }
234
+ }
235
+
236
+ export {
237
+ DEFAULT_PROVIDER_DEFINITIONS,
238
+ findProviderDefinition,
239
+ formatSupportedProviderTypes,
240
+ readProviderSettings,
241
+ readMergedProviderSettings,
242
+ createProviderFromSettings,
243
+ createProviderFromProfile,
244
+ isSubagentWorkerParentMessage,
245
+ isSubagentWorkerChildMessage
246
+ };