@robota-sdk/agent-cli 3.0.0-beta.46 → 3.0.0-beta.48

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
@@ -78,10 +78,33 @@ robota --model <model> # Model override (e.g., claude-sonnet-4-6)
78
78
  robota --language <lang> # Response language (ko, en, ja, zh)
79
79
  robota --permission-mode <mode> # plan | default | acceptEdits | bypassPermissions
80
80
  robota --max-turns <n> # Limit agentic turns per interaction
81
+ robota --output-format <fmt> # text | json | stream-json (print mode)
82
+ robota --system-prompt <text> # Replace system prompt (print mode)
83
+ robota --append-system-prompt <text> # Append to system prompt (print mode)
81
84
  robota --reset # Delete user settings and exit
82
85
  robota --version # Show version
83
86
  ```
84
87
 
88
+ ### Print Mode Output Formats
89
+
90
+ Print mode (`-p`) supports three output formats via `--output-format`:
91
+
92
+ | Format | Description |
93
+ | ------------- | ------------------------------------------------------------------ |
94
+ | `text` | Plain text response to stdout (default) |
95
+ | `json` | Single JSON object: `{ type, result, session_id, subtype }` |
96
+ | `stream-json` | Newline-delimited JSON with `content_block_delta` streaming events |
97
+
98
+ ### Stdin Pipe
99
+
100
+ When `-p` is used without a positional argument and stdin is piped, the CLI reads from stdin:
101
+
102
+ ```bash
103
+ echo "Explain this error" | robota -p
104
+ cat file.ts | robota -p "Review this code" --output-format json
105
+ git diff | robota -p "Summarize changes" --output-format stream-json
106
+ ```
107
+
85
108
  ## First-Run Setup
86
109
 
87
110
  When no settings file exists, the CLI prompts for:
@@ -227,7 +250,7 @@ When a session has a name, it appears in three places:
227
250
  | `/rename <name>` | Rename the current session |
228
251
  | `/exit` | Exit CLI |
229
252
 
230
- Typing `/` triggers an autocomplete popup with arrow-key navigation, Tab completion, and Esc to dismiss. Commands with subcommands (e.g., `/mode`, `/model`) show a nested submenu. Skill commands discovered from `.agents/skills/` and `.claude/commands/` appear alongside built-in commands.
253
+ Typing `/` triggers an autocomplete popup with arrow-key navigation and Esc to dismiss. Tab inserts the highlighted command into the input field without executing — continue typing args or press Enter to execute. Enter selects and executes immediately. Commands with subcommands (e.g., `/mode`, `/model`) show a nested submenu. Skill commands discovered from `.agents/skills/` and `.claude/commands/` appear alongside built-in commands.
231
254
 
232
255
  ## Plugin Management
233
256
 
@@ -317,18 +340,19 @@ bin.ts → cli.ts (arg parsing)
317
340
 
318
341
  ## Dependencies
319
342
 
320
- | Package | Purpose |
321
- | --------------------------- | ------------------------------------------ |
322
- | `@robota-sdk/agent-sdk` | Session factory, query, config, context |
323
- | `@robota-sdk/agent-core` | Types (TPermissionMode, TToolArgs) |
324
- | `ink`, `react` | TUI rendering |
325
- | `ink-select-input` | Arrow-key selection (permission prompt) |
326
- | `ink-spinner` | Loading spinner |
327
- | `chalk` | Terminal colors |
328
- | `ink-text-input` | Base text input (extended by CjkTextInput) |
329
- | `marked`, `marked-terminal` | Markdown parsing and terminal rendering |
330
- | `cli-highlight` | Syntax highlighting for code blocks |
331
- | `string-width` | Unicode-aware string width (CJK support) |
343
+ | Package | Purpose |
344
+ | -------------------------------------- | ------------------------------------------ |
345
+ | `@robota-sdk/agent-sdk` | Session factory, query, config, context |
346
+ | `@robota-sdk/agent-core` | Types (TPermissionMode, TToolArgs) |
347
+ | `@robota-sdk/agent-transport-headless` | Headless runner for print mode (`-p`) |
348
+ | `ink`, `react` | TUI rendering |
349
+ | `ink-select-input` | Arrow-key selection (permission prompt) |
350
+ | `ink-spinner` | Loading spinner |
351
+ | `chalk` | Terminal colors |
352
+ | `ink-text-input` | Base text input (extended by CjkTextInput) |
353
+ | `marked`, `marked-terminal` | Markdown parsing and terminal rendering |
354
+ | `cli-highlight` | Syntax highlighting for code blocks |
355
+ | `string-width` | Unicode-aware string width (CJK support) |
332
356
 
333
357
  ## Documentation
334
358
 
package/dist/node/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startCli
4
- } from "./chunk-L5G6WFOL.js";
4
+ } from "./chunk-QNBFMSI5.js";
5
5
 
6
6
  // src/bin.ts
7
7
  process.on("uncaughtException", (err) => {
@@ -40,6 +40,9 @@ function parseCliArgs() {
40
40
  "max-turns": { type: "string" },
41
41
  "fork-session": { type: "boolean", default: false },
42
42
  name: { type: "string", short: "n" },
43
+ "output-format": { type: "string" },
44
+ "system-prompt": { type: "string" },
45
+ "append-system-prompt": { type: "string" },
43
46
  version: { type: "boolean", default: false },
44
47
  reset: { type: "boolean", default: false }
45
48
  }
@@ -55,6 +58,9 @@ function parseCliArgs() {
55
58
  maxTurns: parseMaxTurns(values["max-turns"]),
56
59
  forkSession: values["fork-session"] ?? false,
57
60
  sessionName: values["name"],
61
+ outputFormat: values["output-format"],
62
+ systemPrompt: values["system-prompt"],
63
+ appendSystemPrompt: values["append-system-prompt"],
58
64
  version: values["version"] ?? false,
59
65
  reset: values["reset"] ?? false
60
66
  };
@@ -134,6 +140,9 @@ function createProviderFromSettings(cwd, modelOverride) {
134
140
  }
135
141
  }
136
142
 
143
+ // src/cli.ts
144
+ import { createHeadlessTransport } from "@robota-sdk/agent-transport-headless";
145
+
137
146
  // src/ui/render.tsx
138
147
  import { render } from "ink";
139
148
 
@@ -187,9 +196,12 @@ var TuiStateManager = class {
187
196
  this.notify();
188
197
  };
189
198
  onToolEnd = (state) => {
190
- this.activeTools = this.activeTools.map(
191
- (t) => t.toolName === state.toolName && t.isRunning ? state : t
192
- );
199
+ const idx = this.activeTools.findIndex((t) => t.toolName === state.toolName && t.isRunning);
200
+ if (idx !== -1) {
201
+ const updated = [...this.activeTools];
202
+ updated[idx] = state;
203
+ this.activeTools = updated;
204
+ }
193
205
  this.notify();
194
206
  };
195
207
  onThinking = (thinking) => {
@@ -782,6 +794,9 @@ function EntryItem({ entry }) {
782
794
  if (entry.type === "tool-summary") {
783
795
  return /* @__PURE__ */ jsx(ToolSummaryEntry, { entry });
784
796
  }
797
+ if (entry.type === "tool-start" || entry.type === "tool-end") {
798
+ return /* @__PURE__ */ jsx(Fragment, {});
799
+ }
785
800
  return /* @__PURE__ */ jsx(EventEntry, { entry });
786
801
  }
787
802
  function MessageList({ history }) {
@@ -911,9 +926,7 @@ function CjkTextInput({
911
926
  const pasteBufferRef = useRef2("");
912
927
  if (value !== valueRef.current) {
913
928
  valueRef.current = value;
914
- if (cursorRef.current > value.length) {
915
- cursorRef.current = value.length;
916
- }
929
+ cursorRef.current = value.length;
917
930
  }
918
931
  useInput(
919
932
  (input, key) => {
@@ -1194,23 +1207,23 @@ function InputArea({
1194
1207
  const label = `[Pasted text #${id} +${lineCount} lines]`;
1195
1208
  setValue((prev) => prev ? `${prev} ${label}` : label);
1196
1209
  }, []);
1197
- const handleSubmit = useCallback2(
1198
- (text) => {
1199
- const trimmed = text.trim();
1200
- if (trimmed.length === 0) return;
1201
- if (showPopup && filteredCommands[selectedIndex]) {
1202
- selectCommand(filteredCommands[selectedIndex]);
1210
+ const tabCompleteCommand = useCallback2(
1211
+ (cmd) => {
1212
+ const parsed = parseSlashInput(value);
1213
+ if (parsed.parentCommand) {
1214
+ setValue(`/${parsed.parentCommand} ${cmd.name} `);
1203
1215
  return;
1204
1216
  }
1205
- const expanded = expandPasteLabels(trimmed, pasteStore.current);
1206
- setValue("");
1207
- pasteStore.current.clear();
1208
- pasteIdRef.current = 0;
1209
- onSubmit(expanded);
1217
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
1218
+ setValue(`/${cmd.name} `);
1219
+ setSelectedIndex(0);
1220
+ return;
1221
+ }
1222
+ setValue(`/${cmd.name} `);
1210
1223
  },
1211
- [showPopup, filteredCommands, selectedIndex, onSubmit]
1224
+ [value, setSelectedIndex]
1212
1225
  );
1213
- const selectCommand = useCallback2(
1226
+ const enterSelectCommand = useCallback2(
1214
1227
  (cmd) => {
1215
1228
  const parsed = parseSlashInput(value);
1216
1229
  if (parsed.parentCommand) {
@@ -1229,6 +1242,22 @@ function InputArea({
1229
1242
  },
1230
1243
  [value, onSubmit, setSelectedIndex]
1231
1244
  );
1245
+ const handleSubmit = useCallback2(
1246
+ (text) => {
1247
+ const trimmed = text.trim();
1248
+ if (trimmed.length === 0) return;
1249
+ if (showPopup && filteredCommands[selectedIndex]) {
1250
+ enterSelectCommand(filteredCommands[selectedIndex]);
1251
+ return;
1252
+ }
1253
+ const expanded = expandPasteLabels(trimmed, pasteStore.current);
1254
+ setValue("");
1255
+ pasteStore.current.clear();
1256
+ pasteIdRef.current = 0;
1257
+ onSubmit(expanded);
1258
+ },
1259
+ [showPopup, filteredCommands, selectedIndex, onSubmit, enterSelectCommand]
1260
+ );
1232
1261
  useInput2(
1233
1262
  (_input, key) => {
1234
1263
  if (!showPopup) return;
@@ -1240,7 +1269,7 @@ function InputArea({
1240
1269
  setShowPopup(false);
1241
1270
  } else if (key.tab) {
1242
1271
  const cmd = filteredCommands[selectedIndex];
1243
- if (cmd) selectCommand(cmd);
1272
+ if (cmd) tabCompleteCommand(cmd);
1244
1273
  }
1245
1274
  },
1246
1275
  { isActive: showPopup && !isDisabled }
@@ -2446,8 +2475,15 @@ async function startCli() {
2446
2475
  }
2447
2476
  }
2448
2477
  if (args.printMode) {
2449
- const prompt = args.positional.join(" ").trim();
2450
- if (prompt.length === 0) {
2478
+ let prompt = args.positional.join(" ").trim();
2479
+ if (!prompt && !process.stdin.isTTY) {
2480
+ const chunks = [];
2481
+ for await (const chunk of process.stdin) {
2482
+ chunks.push(chunk);
2483
+ }
2484
+ prompt = Buffer.concat(chunks).toString("utf-8").trim();
2485
+ }
2486
+ if (!prompt) {
2451
2487
  process.stderr.write("Print mode (-p) requires a prompt argument.\n");
2452
2488
  process.exit(1);
2453
2489
  }
@@ -2459,19 +2495,13 @@ async function startCli() {
2459
2495
  sessionStore,
2460
2496
  sessionName: args.sessionName
2461
2497
  });
2462
- await new Promise((resolve, reject) => {
2463
- session.on("complete", (result) => {
2464
- process.stdout.write(result.response + "\n");
2465
- resolve();
2466
- });
2467
- session.on("interrupted", (result) => {
2468
- if (result.response) process.stdout.write(result.response + "\n");
2469
- resolve();
2470
- });
2471
- session.on("error", (err) => reject(err));
2472
- session.submit(prompt).catch(reject);
2498
+ const transport = createHeadlessTransport({
2499
+ outputFormat: args.outputFormat ?? "text",
2500
+ prompt
2473
2501
  });
2474
- return;
2502
+ session.attachTransport(transport);
2503
+ await transport.start();
2504
+ process.exit(transport.getExitCode());
2475
2505
  }
2476
2506
  renderApp({
2477
2507
  cwd,
@@ -76,6 +76,9 @@ function parseCliArgs() {
76
76
  "max-turns": { type: "string" },
77
77
  "fork-session": { type: "boolean", default: false },
78
78
  name: { type: "string", short: "n" },
79
+ "output-format": { type: "string" },
80
+ "system-prompt": { type: "string" },
81
+ "append-system-prompt": { type: "string" },
79
82
  version: { type: "boolean", default: false },
80
83
  reset: { type: "boolean", default: false }
81
84
  }
@@ -91,6 +94,9 @@ function parseCliArgs() {
91
94
  maxTurns: parseMaxTurns(values["max-turns"]),
92
95
  forkSession: values["fork-session"] ?? false,
93
96
  sessionName: values["name"],
97
+ outputFormat: values["output-format"],
98
+ systemPrompt: values["system-prompt"],
99
+ appendSystemPrompt: values["append-system-prompt"],
94
100
  version: values["version"] ?? false,
95
101
  reset: values["reset"] ?? false
96
102
  };
@@ -170,6 +176,9 @@ function createProviderFromSettings(cwd, modelOverride) {
170
176
  }
171
177
  }
172
178
 
179
+ // src/cli.ts
180
+ var import_agent_transport_headless = require("@robota-sdk/agent-transport-headless");
181
+
173
182
  // src/ui/render.tsx
174
183
  var import_ink15 = require("ink");
175
184
 
@@ -215,9 +224,12 @@ var TuiStateManager = class {
215
224
  this.notify();
216
225
  };
217
226
  onToolEnd = (state) => {
218
- this.activeTools = this.activeTools.map(
219
- (t) => t.toolName === state.toolName && t.isRunning ? state : t
220
- );
227
+ const idx = this.activeTools.findIndex((t) => t.toolName === state.toolName && t.isRunning);
228
+ if (idx !== -1) {
229
+ const updated = [...this.activeTools];
230
+ updated[idx] = state;
231
+ this.activeTools = updated;
232
+ }
221
233
  this.notify();
222
234
  };
223
235
  onThinking = (thinking) => {
@@ -805,6 +817,9 @@ function EntryItem({ entry }) {
805
817
  if (entry.type === "tool-summary") {
806
818
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolSummaryEntry, { entry });
807
819
  }
820
+ if (entry.type === "tool-start" || entry.type === "tool-end") {
821
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
822
+ }
808
823
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EventEntry, { entry });
809
824
  }
810
825
  function MessageList({ history }) {
@@ -934,9 +949,7 @@ function CjkTextInput({
934
949
  const pasteBufferRef = (0, import_react4.useRef)("");
935
950
  if (value !== valueRef.current) {
936
951
  valueRef.current = value;
937
- if (cursorRef.current > value.length) {
938
- cursorRef.current = value.length;
939
- }
952
+ cursorRef.current = value.length;
940
953
  }
941
954
  (0, import_ink4.useInput)(
942
955
  (input, key) => {
@@ -1217,23 +1230,23 @@ function InputArea({
1217
1230
  const label = `[Pasted text #${id} +${lineCount} lines]`;
1218
1231
  setValue((prev) => prev ? `${prev} ${label}` : label);
1219
1232
  }, []);
1220
- const handleSubmit = (0, import_react6.useCallback)(
1221
- (text) => {
1222
- const trimmed = text.trim();
1223
- if (trimmed.length === 0) return;
1224
- if (showPopup && filteredCommands[selectedIndex]) {
1225
- selectCommand(filteredCommands[selectedIndex]);
1233
+ const tabCompleteCommand = (0, import_react6.useCallback)(
1234
+ (cmd) => {
1235
+ const parsed = parseSlashInput(value);
1236
+ if (parsed.parentCommand) {
1237
+ setValue(`/${parsed.parentCommand} ${cmd.name} `);
1226
1238
  return;
1227
1239
  }
1228
- const expanded = expandPasteLabels(trimmed, pasteStore.current);
1229
- setValue("");
1230
- pasteStore.current.clear();
1231
- pasteIdRef.current = 0;
1232
- onSubmit(expanded);
1240
+ if (cmd.subcommands && cmd.subcommands.length > 0) {
1241
+ setValue(`/${cmd.name} `);
1242
+ setSelectedIndex(0);
1243
+ return;
1244
+ }
1245
+ setValue(`/${cmd.name} `);
1233
1246
  },
1234
- [showPopup, filteredCommands, selectedIndex, onSubmit]
1247
+ [value, setSelectedIndex]
1235
1248
  );
1236
- const selectCommand = (0, import_react6.useCallback)(
1249
+ const enterSelectCommand = (0, import_react6.useCallback)(
1237
1250
  (cmd) => {
1238
1251
  const parsed = parseSlashInput(value);
1239
1252
  if (parsed.parentCommand) {
@@ -1252,6 +1265,22 @@ function InputArea({
1252
1265
  },
1253
1266
  [value, onSubmit, setSelectedIndex]
1254
1267
  );
1268
+ const handleSubmit = (0, import_react6.useCallback)(
1269
+ (text) => {
1270
+ const trimmed = text.trim();
1271
+ if (trimmed.length === 0) return;
1272
+ if (showPopup && filteredCommands[selectedIndex]) {
1273
+ enterSelectCommand(filteredCommands[selectedIndex]);
1274
+ return;
1275
+ }
1276
+ const expanded = expandPasteLabels(trimmed, pasteStore.current);
1277
+ setValue("");
1278
+ pasteStore.current.clear();
1279
+ pasteIdRef.current = 0;
1280
+ onSubmit(expanded);
1281
+ },
1282
+ [showPopup, filteredCommands, selectedIndex, onSubmit, enterSelectCommand]
1283
+ );
1255
1284
  (0, import_ink7.useInput)(
1256
1285
  (_input, key) => {
1257
1286
  if (!showPopup) return;
@@ -1263,7 +1292,7 @@ function InputArea({
1263
1292
  setShowPopup(false);
1264
1293
  } else if (key.tab) {
1265
1294
  const cmd = filteredCommands[selectedIndex];
1266
- if (cmd) selectCommand(cmd);
1295
+ if (cmd) tabCompleteCommand(cmd);
1267
1296
  }
1268
1297
  },
1269
1298
  { isActive: showPopup && !isDisabled }
@@ -2470,8 +2499,15 @@ async function startCli() {
2470
2499
  }
2471
2500
  }
2472
2501
  if (args.printMode) {
2473
- const prompt = args.positional.join(" ").trim();
2474
- if (prompt.length === 0) {
2502
+ let prompt = args.positional.join(" ").trim();
2503
+ if (!prompt && !process.stdin.isTTY) {
2504
+ const chunks = [];
2505
+ for await (const chunk of process.stdin) {
2506
+ chunks.push(chunk);
2507
+ }
2508
+ prompt = Buffer.concat(chunks).toString("utf-8").trim();
2509
+ }
2510
+ if (!prompt) {
2475
2511
  process.stderr.write("Print mode (-p) requires a prompt argument.\n");
2476
2512
  process.exit(1);
2477
2513
  }
@@ -2483,19 +2519,13 @@ async function startCli() {
2483
2519
  sessionStore,
2484
2520
  sessionName: args.sessionName
2485
2521
  });
2486
- await new Promise((resolve, reject) => {
2487
- session.on("complete", (result) => {
2488
- process.stdout.write(result.response + "\n");
2489
- resolve();
2490
- });
2491
- session.on("interrupted", (result) => {
2492
- if (result.response) process.stdout.write(result.response + "\n");
2493
- resolve();
2494
- });
2495
- session.on("error", (err) => reject(err));
2496
- session.submit(prompt).catch(reject);
2522
+ const transport = (0, import_agent_transport_headless.createHeadlessTransport)({
2523
+ outputFormat: args.outputFormat ?? "text",
2524
+ prompt
2497
2525
  });
2498
- return;
2526
+ session.attachTransport(transport);
2527
+ await transport.start();
2528
+ process.exit(transport.getExitCode());
2499
2529
  }
2500
2530
  renderApp({
2501
2531
  cwd,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  startCli
3
- } from "./chunk-L5G6WFOL.js";
3
+ } from "./chunk-QNBFMSI5.js";
4
4
  export {
5
5
  startCli
6
6
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robota-sdk/agent-cli",
3
- "version": "3.0.0-beta.46",
3
+ "version": "3.0.0-beta.48",
4
4
  "description": "AI coding assistant CLI built on Robota SDK",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "dist"
26
26
  ],
27
27
  "dependencies": {
28
- "@robota-sdk/agent-sessions": "3.0.0-beta.46",
28
+ "@robota-sdk/agent-sessions": "3.0.0-beta.48",
29
29
  "chalk": "^5.3.0",
30
30
  "cli-highlight": "^2.1.0",
31
31
  "ink": "^6.8.0",
@@ -36,9 +36,10 @@
36
36
  "marked-terminal": "^7.3.0",
37
37
  "react": "19.2.4",
38
38
  "string-width": "^8.2.0",
39
- "@robota-sdk/agent-core": "3.0.0-beta.46",
40
- "@robota-sdk/agent-sdk": "3.0.0-beta.46",
41
- "@robota-sdk/agent-provider-anthropic": "3.0.0-beta.46"
39
+ "@robota-sdk/agent-core": "3.0.0-beta.48",
40
+ "@robota-sdk/agent-provider-anthropic": "3.0.0-beta.48",
41
+ "@robota-sdk/agent-transport-headless": "3.0.0-beta.48",
42
+ "@robota-sdk/agent-sdk": "3.0.0-beta.48"
42
43
  },
43
44
  "devDependencies": {
44
45
  "@types/marked": "^6.0.0",