@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 +48 -17
- package/dist/node/bin.js +4 -2
- package/dist/node/chunk-2JAZDNYT.js +246 -0
- package/dist/node/chunk-H3NRW5FW.js +4372 -0
- package/dist/node/index.cjs +2578 -680
- package/dist/node/index.d.cts +55 -3
- package/dist/node/index.d.ts +55 -3
- package/dist/node/index.js +12 -1
- package/dist/node/subagents/child-process-subagent-worker.d.ts +2 -0
- package/dist/node/subagents/child-process-subagent-worker.js +123 -0
- package/package.json +17 -8
- package/dist/node/chunk-CXBD4JNS.js +0 -2640
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
|
|
30
|
-
| ------------------- |
|
|
31
|
-
| `ANTHROPIC_API_KEY` | Anthropic API key |
|
|
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
|
|
272
|
+
Settings are merged in this order, from lowest to highest priority:
|
|
271
273
|
|
|
272
|
-
1.
|
|
273
|
-
2.
|
|
274
|
-
3. `.
|
|
275
|
-
4.
|
|
276
|
-
5.
|
|
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
|
-
"
|
|
283
|
-
|
|
284
|
-
"
|
|
285
|
-
|
|
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
|
|
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-
|
|
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
|
+
};
|