@robota-sdk/agent-cli 3.0.0-beta.9 → 3.0.0-beta.90

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.
Files changed (141) hide show
  1. package/CHANGELOG.md +2678 -0
  2. package/LICENSE +661 -21
  3. package/README.md +521 -170
  4. package/bin/agent.cjs +39 -0
  5. package/dist/node/bin.js +38 -9
  6. package/dist/node/bounded-output-BdWv9SX3-28em7iHq.js +2 -0
  7. package/dist/node/bounded-output-BdWv9SX3-28em7iHq.js.map +1 -0
  8. package/dist/node/bounded-output-BdWv9SX3-C4ucXwLR.js +2 -0
  9. package/dist/node/cli-core-YR-wNxJu.js +2918 -0
  10. package/dist/node/command-executor-D_UBZHxh-CVZipoOV.js +2 -0
  11. package/dist/node/command-executor-D_UBZHxh-CVZipoOV.js.map +1 -0
  12. package/dist/node/command-executor-D_UBZHxh-D7UrdAlN.js +2 -0
  13. package/dist/node/headless.js +4 -0
  14. package/dist/node/http-executor-ADn4locH-D-8crzF8.js +2 -0
  15. package/dist/node/http-executor-ADn4locH-D-8crzF8.js.map +1 -0
  16. package/dist/node/http-executor-ADn4locH-uz5KV6N-.js +2 -0
  17. package/dist/node/index.d.ts +5950 -36
  18. package/dist/node/index.d.ts.map +1 -0
  19. package/dist/node/index.js +2953 -13
  20. package/dist/node/index.js.map +1 -0
  21. package/dist/node/node-B9M9cLfF.js +165 -0
  22. package/dist/node/node-BUERQysz.js +15 -0
  23. package/dist/node/node-BWNobqRD.js +15 -0
  24. package/dist/node/node-BWNobqRD.js.map +1 -0
  25. package/dist/node/node-C1z7m4gZ.js +165 -0
  26. package/dist/node/node-C1z7m4gZ.js.map +1 -0
  27. package/dist/node/node-C4_FOvnk.js +2 -0
  28. package/dist/node/node-C4_FOvnk.js.map +1 -0
  29. package/dist/node/node-CHbqiARC.js +2 -0
  30. package/dist/node/node-DENLEmMT.js +2 -0
  31. package/dist/node/node-DENLEmMT.js.map +1 -0
  32. package/dist/node/rolldown-runtime-C9xYM0wc.js +1 -0
  33. package/dist/node/rolldown-runtime-DK3Fl9T5-BeX3Bzvn.js +2 -0
  34. package/dist/node/rolldown-runtime-DK3Fl9T5-DK3Fl9T5.js +2 -0
  35. package/dist/node/rolldown-runtime-DK3Fl9T5-DK3Fl9T5.js.map +1 -0
  36. package/dist/node/rolldown-runtime-rs7quPpn.js +2 -0
  37. package/dist/node/verdict-decoder-Dz_rjoIZ-DA8BKB83.js +2 -0
  38. package/dist/node/verdict-decoder-Dz_rjoIZ-DA8BKB83.js.map +1 -0
  39. package/dist/node/verdict-decoder-Dz_rjoIZ-qM0jJVsB.js +2 -0
  40. package/dist/web/assets/PretendardVariable.subset.0-BHUkWNFR.woff2 +0 -0
  41. package/dist/web/assets/PretendardVariable.subset.1-C-__qv6_.woff2 +0 -0
  42. package/dist/web/assets/PretendardVariable.subset.10-DzSWztS8.woff2 +0 -0
  43. package/dist/web/assets/PretendardVariable.subset.11-CqVmlKJn.woff2 +0 -0
  44. package/dist/web/assets/PretendardVariable.subset.12-BHuZSgT0.woff2 +0 -0
  45. package/dist/web/assets/PretendardVariable.subset.13-C42mj_j2.woff2 +0 -0
  46. package/dist/web/assets/PretendardVariable.subset.14-Bl512uUX.woff2 +0 -0
  47. package/dist/web/assets/PretendardVariable.subset.15-D04iXIE3.woff2 +0 -0
  48. package/dist/web/assets/PretendardVariable.subset.16-BQUnS2GX.woff2 +0 -0
  49. package/dist/web/assets/PretendardVariable.subset.17-BfZSA-Xc.woff2 +0 -0
  50. package/dist/web/assets/PretendardVariable.subset.18-CwAxMC3C.woff2 +0 -0
  51. package/dist/web/assets/PretendardVariable.subset.19-CJu4Zcdo.woff2 +0 -0
  52. package/dist/web/assets/PretendardVariable.subset.2-dCZkyKLw.woff2 +0 -0
  53. package/dist/web/assets/PretendardVariable.subset.20-Ig1-z3n5.woff2 +0 -0
  54. package/dist/web/assets/PretendardVariable.subset.21-yKPEdLXC.woff2 +0 -0
  55. package/dist/web/assets/PretendardVariable.subset.22-CSqxKoOs.woff2 +0 -0
  56. package/dist/web/assets/PretendardVariable.subset.23-DK80wi0t.woff2 +0 -0
  57. package/dist/web/assets/PretendardVariable.subset.24-CmkE8Q8D.woff2 +0 -0
  58. package/dist/web/assets/PretendardVariable.subset.25-CsoWBIZB.woff2 +0 -0
  59. package/dist/web/assets/PretendardVariable.subset.26-Sozl8dw8.woff2 +0 -0
  60. package/dist/web/assets/PretendardVariable.subset.27-CT6nuW9L.woff2 +0 -0
  61. package/dist/web/assets/PretendardVariable.subset.28-CpO0Y96p.woff2 +0 -0
  62. package/dist/web/assets/PretendardVariable.subset.29-D6hjrUWm.woff2 +0 -0
  63. package/dist/web/assets/PretendardVariable.subset.3-Dqw33sf4.woff2 +0 -0
  64. package/dist/web/assets/PretendardVariable.subset.30-CWDM1c0J.woff2 +0 -0
  65. package/dist/web/assets/PretendardVariable.subset.31-CdmyZ5mm.woff2 +0 -0
  66. package/dist/web/assets/PretendardVariable.subset.32-CGnFWD2i.woff2 +0 -0
  67. package/dist/web/assets/PretendardVariable.subset.33--0OT__YQ.woff2 +0 -0
  68. package/dist/web/assets/PretendardVariable.subset.34-CaCS33Md.woff2 +0 -0
  69. package/dist/web/assets/PretendardVariable.subset.35-DWFYRGLp.woff2 +0 -0
  70. package/dist/web/assets/PretendardVariable.subset.36-Dn5IBRQB.woff2 +0 -0
  71. package/dist/web/assets/PretendardVariable.subset.37-BD6FyOtY.woff2 +0 -0
  72. package/dist/web/assets/PretendardVariable.subset.38-D4hu443z.woff2 +0 -0
  73. package/dist/web/assets/PretendardVariable.subset.39-B_7wfth9.woff2 +0 -0
  74. package/dist/web/assets/PretendardVariable.subset.4-Bvh2YGoc.woff2 +0 -0
  75. package/dist/web/assets/PretendardVariable.subset.40-BDaOfdUe.woff2 +0 -0
  76. package/dist/web/assets/PretendardVariable.subset.41-BUACvzZC.woff2 +0 -0
  77. package/dist/web/assets/PretendardVariable.subset.42-Dp-5mnyL.woff2 +0 -0
  78. package/dist/web/assets/PretendardVariable.subset.43-DHdpry7N.woff2 +0 -0
  79. package/dist/web/assets/PretendardVariable.subset.44-qHopVhdd.woff2 +0 -0
  80. package/dist/web/assets/PretendardVariable.subset.45-BniyRFfm.woff2 +0 -0
  81. package/dist/web/assets/PretendardVariable.subset.46-BMRq7xC-.woff2 +0 -0
  82. package/dist/web/assets/PretendardVariable.subset.47-B-cWO2pw.woff2 +0 -0
  83. package/dist/web/assets/PretendardVariable.subset.48-Ct-fWrPO.woff2 +0 -0
  84. package/dist/web/assets/PretendardVariable.subset.49-BblQVys9.woff2 +0 -0
  85. package/dist/web/assets/PretendardVariable.subset.5-K_MNGNCe.woff2 +0 -0
  86. package/dist/web/assets/PretendardVariable.subset.50-C8IyFH7L.woff2 +0 -0
  87. package/dist/web/assets/PretendardVariable.subset.51-Bxd0gTAs.woff2 +0 -0
  88. package/dist/web/assets/PretendardVariable.subset.52-CNgqKOOJ.woff2 +0 -0
  89. package/dist/web/assets/PretendardVariable.subset.53-BSRnyb-u.woff2 +0 -0
  90. package/dist/web/assets/PretendardVariable.subset.54-Dt2-cQkx.woff2 +0 -0
  91. package/dist/web/assets/PretendardVariable.subset.55-jFgflYjX.woff2 +0 -0
  92. package/dist/web/assets/PretendardVariable.subset.56-BwZdvJZQ.woff2 +0 -0
  93. package/dist/web/assets/PretendardVariable.subset.57-BwFDg-Fs.woff2 +0 -0
  94. package/dist/web/assets/PretendardVariable.subset.58-DlucQts_.woff2 +0 -0
  95. package/dist/web/assets/PretendardVariable.subset.59-CMkWjhdo.woff2 +0 -0
  96. package/dist/web/assets/PretendardVariable.subset.6-Bxhohlcm.woff2 +0 -0
  97. package/dist/web/assets/PretendardVariable.subset.60-CeHezjjf.woff2 +0 -0
  98. package/dist/web/assets/PretendardVariable.subset.61-PUuTnod4.woff2 +0 -0
  99. package/dist/web/assets/PretendardVariable.subset.62-DGSAWCfb.woff2 +0 -0
  100. package/dist/web/assets/PretendardVariable.subset.63-B35xsm4O.woff2 +0 -0
  101. package/dist/web/assets/PretendardVariable.subset.64-CTbrgYF9.woff2 +0 -0
  102. package/dist/web/assets/PretendardVariable.subset.65-B66rjuyf.woff2 +0 -0
  103. package/dist/web/assets/PretendardVariable.subset.66-C3HqaDeY.woff2 +0 -0
  104. package/dist/web/assets/PretendardVariable.subset.67-BmuXdlDy.woff2 +0 -0
  105. package/dist/web/assets/PretendardVariable.subset.68-DS9B48d0.woff2 +0 -0
  106. package/dist/web/assets/PretendardVariable.subset.69-YT16ymcp.woff2 +0 -0
  107. package/dist/web/assets/PretendardVariable.subset.7-E2HaA55t.woff2 +0 -0
  108. package/dist/web/assets/PretendardVariable.subset.70-BUXiAGMT.woff2 +0 -0
  109. package/dist/web/assets/PretendardVariable.subset.71-DuPZj8us.woff2 +0 -0
  110. package/dist/web/assets/PretendardVariable.subset.72-pYYGrEQR.woff2 +0 -0
  111. package/dist/web/assets/PretendardVariable.subset.73-DMrK970F.woff2 +0 -0
  112. package/dist/web/assets/PretendardVariable.subset.74-D4tQnymK.woff2 +0 -0
  113. package/dist/web/assets/PretendardVariable.subset.75-CxKdrRNf.woff2 +0 -0
  114. package/dist/web/assets/PretendardVariable.subset.76-DhPm2b_q.woff2 +0 -0
  115. package/dist/web/assets/PretendardVariable.subset.77-DwaxqOC8.woff2 +0 -0
  116. package/dist/web/assets/PretendardVariable.subset.78-DhqRbBzT.woff2 +0 -0
  117. package/dist/web/assets/PretendardVariable.subset.79-XpoyPP38.woff2 +0 -0
  118. package/dist/web/assets/PretendardVariable.subset.8-CRbJhhyA.woff2 +0 -0
  119. package/dist/web/assets/PretendardVariable.subset.80-DsV9Qp_h.woff2 +0 -0
  120. package/dist/web/assets/PretendardVariable.subset.81-BZzF9Hb3.woff2 +0 -0
  121. package/dist/web/assets/PretendardVariable.subset.82-BgAHe30u.woff2 +0 -0
  122. package/dist/web/assets/PretendardVariable.subset.83-DF-zBLLe.woff2 +0 -0
  123. package/dist/web/assets/PretendardVariable.subset.84-Brb8EsYQ.woff2 +0 -0
  124. package/dist/web/assets/PretendardVariable.subset.85-Byo_x2hf.woff2 +0 -0
  125. package/dist/web/assets/PretendardVariable.subset.86-XG7lTN_6.woff2 +0 -0
  126. package/dist/web/assets/PretendardVariable.subset.87-Lzui2vbK.woff2 +0 -0
  127. package/dist/web/assets/PretendardVariable.subset.88-CqX6JSgh.woff2 +0 -0
  128. package/dist/web/assets/PretendardVariable.subset.89-DOzqWPpX.woff2 +0 -0
  129. package/dist/web/assets/PretendardVariable.subset.9-Btb3bmS6.woff2 +0 -0
  130. package/dist/web/assets/PretendardVariable.subset.90-BF7RiZjm.woff2 +0 -0
  131. package/dist/web/assets/PretendardVariable.subset.91-Csm0YNoH.woff2 +0 -0
  132. package/dist/web/assets/index-BSxTg2Py.css +1 -0
  133. package/dist/web/assets/index-BpCxNRj_.js +309 -0
  134. package/dist/web/index.html +15 -0
  135. package/package.json +165 -34
  136. package/dist/node/bin.cjs +0 -1348
  137. package/dist/node/bin.d.cts +0 -1
  138. package/dist/node/bin.d.ts +0 -1
  139. package/dist/node/chunk-CYMQY2IR.js +0 -1327
  140. package/dist/node/index.cjs +0 -1365
  141. package/dist/node/index.d.cts +0 -49
@@ -1,1327 +0,0 @@
1
- // src/cli.ts
2
- import { parseArgs } from "util";
3
- import { readFileSync as readFileSync2, existsSync as existsSync2, mkdirSync, writeFileSync, unlinkSync } from "fs";
4
- import { join as join2, dirname } from "path";
5
- import { fileURLToPath } from "url";
6
- import * as readline from "readline";
7
- import {
8
- loadConfig,
9
- loadContext,
10
- detectProject,
11
- createSession as createSession2,
12
- SessionStore,
13
- FileSessionLogger as FileSessionLogger2,
14
- projectPaths as projectPaths2
15
- } from "@robota-sdk/agent-sdk";
16
-
17
- // src/permissions/permission-prompt.ts
18
- import chalk from "chalk";
19
- var PERMISSION_OPTIONS = ["Allow", "Deny"];
20
- var ALLOW_INDEX = 0;
21
- function formatArgs(toolArgs) {
22
- const entries = Object.entries(toolArgs);
23
- if (entries.length === 0) {
24
- return "(no arguments)";
25
- }
26
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
27
- }
28
- async function promptForApproval(terminal, toolName, toolArgs) {
29
- terminal.writeLine("");
30
- terminal.writeLine(chalk.yellow(`[Permission Required] Tool: ${toolName}`));
31
- terminal.writeLine(chalk.dim(` ${formatArgs(toolArgs)}`));
32
- terminal.writeLine("");
33
- const selected = await terminal.select(PERMISSION_OPTIONS, ALLOW_INDEX);
34
- return selected === ALLOW_INDEX;
35
- }
36
-
37
- // src/ui/render.tsx
38
- import { render } from "ink";
39
-
40
- // src/ui/App.tsx
41
- import { useState as useState4, useCallback as useCallback2, useRef as useRef2 } from "react";
42
- import { Box as Box6, Text as Text8, useApp, useInput as useInput4 } from "ink";
43
- import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
44
-
45
- // src/commands/command-registry.ts
46
- var CommandRegistry = class {
47
- sources = [];
48
- addSource(source) {
49
- this.sources.push(source);
50
- }
51
- /** Get all commands, optionally filtered by prefix */
52
- getCommands(filter) {
53
- const all = [];
54
- for (const source of this.sources) {
55
- all.push(...source.getCommands());
56
- }
57
- if (!filter) return all;
58
- const lower = filter.toLowerCase();
59
- return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
60
- }
61
- /** Get subcommands for a specific command */
62
- getSubcommands(commandName) {
63
- const lower = commandName.toLowerCase();
64
- for (const source of this.sources) {
65
- for (const cmd of source.getCommands()) {
66
- if (cmd.name.toLowerCase() === lower && cmd.subcommands) {
67
- return cmd.subcommands;
68
- }
69
- }
70
- }
71
- return [];
72
- }
73
- };
74
-
75
- // src/commands/builtin-source.ts
76
- function createBuiltinCommands() {
77
- return [
78
- { name: "help", description: "Show available commands", source: "builtin" },
79
- { name: "clear", description: "Clear conversation history", source: "builtin" },
80
- {
81
- name: "mode",
82
- description: "Permission mode",
83
- source: "builtin",
84
- subcommands: [
85
- { name: "plan", description: "Plan only, no execution", source: "builtin" },
86
- { name: "default", description: "Ask before risky actions", source: "builtin" },
87
- { name: "acceptEdits", description: "Auto-approve file edits", source: "builtin" },
88
- { name: "bypassPermissions", description: "Skip all permission checks", source: "builtin" }
89
- ]
90
- },
91
- {
92
- name: "model",
93
- description: "Select AI model",
94
- source: "builtin",
95
- subcommands: [
96
- { name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
97
- { name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
98
- { name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
99
- ]
100
- },
101
- { name: "compact", description: "Compress context window", source: "builtin" },
102
- { name: "cost", description: "Show session info", source: "builtin" },
103
- { name: "context", description: "Context window info", source: "builtin" },
104
- { name: "permissions", description: "Permission rules", source: "builtin" },
105
- { name: "reset", description: "Delete settings and exit", source: "builtin" },
106
- { name: "exit", description: "Exit CLI", source: "builtin" }
107
- ];
108
- }
109
- var BuiltinCommandSource = class {
110
- name = "builtin";
111
- commands;
112
- constructor() {
113
- this.commands = createBuiltinCommands();
114
- }
115
- getCommands() {
116
- return this.commands;
117
- }
118
- };
119
-
120
- // src/commands/skill-source.ts
121
- import { readdirSync, readFileSync, existsSync } from "fs";
122
- import { join } from "path";
123
- import { homedir } from "os";
124
- function parseFrontmatter(content) {
125
- const lines = content.split("\n");
126
- if (lines[0]?.trim() !== "---") return null;
127
- let name = "";
128
- let description = "";
129
- for (let i = 1; i < lines.length; i++) {
130
- const line = lines[i];
131
- if (line.trim() === "---") break;
132
- const nameMatch = line.match(/^name:\s*(.+)/);
133
- if (nameMatch) {
134
- name = nameMatch[1].trim();
135
- continue;
136
- }
137
- const descMatch = line.match(/^description:\s*(.+)/);
138
- if (descMatch) {
139
- description = descMatch[1].trim();
140
- }
141
- }
142
- return name ? { name, description } : null;
143
- }
144
- function scanSkillsDir(skillsDir) {
145
- if (!existsSync(skillsDir)) return [];
146
- const commands = [];
147
- const entries = readdirSync(skillsDir, { withFileTypes: true });
148
- for (const entry of entries) {
149
- if (!entry.isDirectory()) continue;
150
- const skillFile = join(skillsDir, entry.name, "SKILL.md");
151
- if (!existsSync(skillFile)) continue;
152
- const content = readFileSync(skillFile, "utf-8");
153
- const frontmatter = parseFrontmatter(content);
154
- commands.push({
155
- name: frontmatter?.name ?? entry.name,
156
- description: frontmatter?.description ?? `Skill: ${entry.name}`,
157
- source: "skill",
158
- skillContent: content
159
- });
160
- }
161
- return commands;
162
- }
163
- var SkillCommandSource = class {
164
- name = "skill";
165
- cwd;
166
- cachedCommands = null;
167
- constructor(cwd) {
168
- this.cwd = cwd;
169
- }
170
- getCommands() {
171
- if (this.cachedCommands) return this.cachedCommands;
172
- const projectSkills = scanSkillsDir(join(this.cwd, ".agents", "skills"));
173
- const userSkills = scanSkillsDir(join(homedir(), ".claude", "skills"));
174
- const seen = new Set(projectSkills.map((cmd) => cmd.name));
175
- const merged = [...projectSkills];
176
- for (const cmd of userSkills) {
177
- if (!seen.has(cmd.name)) {
178
- merged.push(cmd);
179
- }
180
- }
181
- this.cachedCommands = merged;
182
- return this.cachedCommands;
183
- }
184
- };
185
-
186
- // src/ui/MessageList.tsx
187
- import { Box, Text } from "ink";
188
-
189
- // src/ui/render-markdown.ts
190
- import { marked } from "marked";
191
- import TerminalRenderer from "marked-terminal";
192
- marked.setOptions({
193
- renderer: new TerminalRenderer()
194
- });
195
- function renderMarkdown(md) {
196
- const result = marked.parse(md);
197
- return typeof result === "string" ? result.trimEnd() : md;
198
- }
199
-
200
- // src/ui/MessageList.tsx
201
- import { jsx, jsxs } from "react/jsx-runtime";
202
- function RoleLabel({ role }) {
203
- switch (role) {
204
- case "user":
205
- return /* @__PURE__ */ jsxs(Text, { color: "green", bold: true, children: [
206
- "You:",
207
- " "
208
- ] });
209
- case "assistant":
210
- return /* @__PURE__ */ jsxs(Text, { color: "cyan", bold: true, children: [
211
- "Robota:",
212
- " "
213
- ] });
214
- case "system":
215
- return /* @__PURE__ */ jsxs(Text, { color: "yellow", bold: true, children: [
216
- "System:",
217
- " "
218
- ] });
219
- case "tool":
220
- return /* @__PURE__ */ jsxs(Text, { color: "magenta", bold: true, children: [
221
- "Tool:",
222
- " "
223
- ] });
224
- }
225
- }
226
- function MessageItem({ message }) {
227
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [
228
- /* @__PURE__ */ jsxs(Box, { children: [
229
- /* @__PURE__ */ jsx(RoleLabel, { role: message.role }),
230
- message.toolName && /* @__PURE__ */ jsxs(Text, { color: "magenta", dimColor: true, children: [
231
- "[",
232
- message.toolName,
233
- "]",
234
- " "
235
- ] })
236
- ] }),
237
- /* @__PURE__ */ jsx(Text, { children: " " }),
238
- /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
239
- ] });
240
- }
241
- function MessageList({ messages }) {
242
- return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ jsx(MessageItem, { message: msg }, msg.id)) });
243
- }
244
-
245
- // src/ui/StatusBar.tsx
246
- import { Box as Box2, Text as Text2 } from "ink";
247
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
248
- var CONTEXT_YELLOW_THRESHOLD = 70;
249
- var CONTEXT_RED_THRESHOLD = 90;
250
- function getContextColor(percentage) {
251
- if (percentage >= CONTEXT_RED_THRESHOLD) return "red";
252
- if (percentage >= CONTEXT_YELLOW_THRESHOLD) return "yellow";
253
- return "green";
254
- }
255
- function StatusBar({
256
- permissionMode,
257
- modelName,
258
- sessionId: _sessionId,
259
- messageCount,
260
- isThinking,
261
- contextPercentage,
262
- contextUsedTokens,
263
- contextMaxTokens
264
- }) {
265
- const contextColor = getContextColor(contextPercentage);
266
- return /* @__PURE__ */ jsxs2(
267
- Box2,
268
- {
269
- borderStyle: "single",
270
- borderColor: "gray",
271
- paddingLeft: 1,
272
- paddingRight: 1,
273
- justifyContent: "space-between",
274
- children: [
275
- /* @__PURE__ */ jsxs2(Text2, { children: [
276
- /* @__PURE__ */ jsx2(Text2, { color: "cyan", bold: true, children: "Mode:" }),
277
- " ",
278
- /* @__PURE__ */ jsx2(Text2, { children: permissionMode }),
279
- " | ",
280
- /* @__PURE__ */ jsx2(Text2, { dimColor: true, children: modelName }),
281
- " | ",
282
- /* @__PURE__ */ jsxs2(Text2, { color: contextColor, children: [
283
- "Context: ",
284
- Math.round(contextPercentage),
285
- "% (",
286
- (contextUsedTokens / 1e3).toFixed(1),
287
- "k/",
288
- (contextMaxTokens / 1e3).toFixed(0),
289
- "k)"
290
- ] })
291
- ] }),
292
- /* @__PURE__ */ jsxs2(Text2, { children: [
293
- isThinking && /* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "Thinking... " }),
294
- /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
295
- "msgs: ",
296
- messageCount
297
- ] })
298
- ] })
299
- ]
300
- }
301
- );
302
- }
303
-
304
- // src/ui/InputArea.tsx
305
- import React3, { useState as useState3, useCallback, useMemo } from "react";
306
- import { Box as Box4, Text as Text6, useInput as useInput2 } from "ink";
307
-
308
- // src/ui/CjkTextInput.tsx
309
- import { useRef, useState } from "react";
310
- import { Text as Text3, useInput, useCursor } from "ink";
311
- import stringWidth from "string-width";
312
- import chalk2 from "chalk";
313
- import { jsx as jsx3 } from "react/jsx-runtime";
314
- function CjkTextInput({
315
- value,
316
- onChange,
317
- onSubmit,
318
- placeholder = "",
319
- focus = true,
320
- showCursor = true
321
- }) {
322
- const valueRef = useRef(value);
323
- const cursorRef = useRef(value.length);
324
- const [, forceRender] = useState(0);
325
- const { setCursorPosition } = useCursor();
326
- if (value !== valueRef.current) {
327
- valueRef.current = value;
328
- if (cursorRef.current > value.length) {
329
- cursorRef.current = value.length;
330
- }
331
- }
332
- useInput(
333
- (input, key) => {
334
- if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
335
- return;
336
- }
337
- if (key.return) {
338
- onSubmit?.(valueRef.current);
339
- return;
340
- }
341
- if (key.leftArrow) {
342
- if (cursorRef.current > 0) {
343
- cursorRef.current -= 1;
344
- forceRender((n) => n + 1);
345
- }
346
- return;
347
- }
348
- if (key.rightArrow) {
349
- if (cursorRef.current < valueRef.current.length) {
350
- cursorRef.current += 1;
351
- forceRender((n) => n + 1);
352
- }
353
- return;
354
- }
355
- if (key.backspace || key.delete) {
356
- if (cursorRef.current > 0) {
357
- const v2 = valueRef.current;
358
- const next2 = v2.slice(0, cursorRef.current - 1) + v2.slice(cursorRef.current);
359
- cursorRef.current -= 1;
360
- valueRef.current = next2;
361
- onChange(next2);
362
- }
363
- return;
364
- }
365
- const v = valueRef.current;
366
- const c = cursorRef.current;
367
- const next = v.slice(0, c) + input + v.slice(c);
368
- cursorRef.current = c + input.length;
369
- valueRef.current = next;
370
- onChange(next);
371
- },
372
- { isActive: focus }
373
- );
374
- if (showCursor && focus) {
375
- const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
376
- const cursorX = 4 + stringWidth(textBeforeCursor);
377
- setCursorPosition({ x: cursorX, y: 0 });
378
- }
379
- return /* @__PURE__ */ jsx3(Text3, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
380
- }
381
- function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
382
- if (!showCursor) {
383
- return value.length > 0 ? value : placeholder ? chalk2.gray(placeholder) : "";
384
- }
385
- if (value.length === 0) {
386
- if (placeholder.length > 0) {
387
- return chalk2.inverse(placeholder[0]) + chalk2.gray(placeholder.slice(1));
388
- }
389
- return chalk2.inverse(" ");
390
- }
391
- const chars = [...value];
392
- let rendered = "";
393
- for (let i = 0; i < chars.length; i++) {
394
- const char = chars[i] ?? "";
395
- rendered += i === cursorOffset ? chalk2.inverse(char) : char;
396
- }
397
- if (cursorOffset >= chars.length) {
398
- rendered += chalk2.inverse(" ");
399
- }
400
- return rendered;
401
- }
402
-
403
- // src/ui/WaveText.tsx
404
- import { useState as useState2, useEffect } from "react";
405
- import { Text as Text4 } from "ink";
406
- import { jsx as jsx4 } from "react/jsx-runtime";
407
- var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
408
- var INTERVAL_MS = 400;
409
- var CHARS_PER_GROUP = 4;
410
- function WaveText({ text }) {
411
- const [tick, setTick] = useState2(0);
412
- useEffect(() => {
413
- const timer = setInterval(() => {
414
- setTick((prev) => prev + 1);
415
- }, INTERVAL_MS);
416
- return () => clearInterval(timer);
417
- }, []);
418
- const chars = [...text];
419
- return /* @__PURE__ */ jsx4(Text4, { children: chars.map((char, i) => {
420
- const group = Math.floor(i / CHARS_PER_GROUP);
421
- const colorIndex = (tick + group) % WAVE_COLORS.length;
422
- return /* @__PURE__ */ jsx4(Text4, { color: WAVE_COLORS[colorIndex], children: char }, i);
423
- }) });
424
- }
425
-
426
- // src/ui/SlashAutocomplete.tsx
427
- import { Box as Box3, Text as Text5 } from "ink";
428
- import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
429
- var MAX_VISIBLE = 8;
430
- function CommandRow(props) {
431
- const { cmd, isSelected, showSlash } = props;
432
- const prefix = showSlash ? "/" : "";
433
- const indicator = isSelected ? "\u25B8 " : " ";
434
- const nameColor = isSelected ? "cyan" : void 0;
435
- const dimmed = !isSelected;
436
- return /* @__PURE__ */ jsxs3(Box3, { children: [
437
- /* @__PURE__ */ jsxs3(Text5, { color: nameColor, dimColor: dimmed, children: [
438
- indicator,
439
- prefix,
440
- cmd.name
441
- ] }),
442
- /* @__PURE__ */ jsx5(Text5, { dimColor: dimmed, children: " " }),
443
- /* @__PURE__ */ jsx5(Text5, { color: nameColor, dimColor: dimmed, children: cmd.description })
444
- ] });
445
- }
446
- function SlashAutocomplete({
447
- commands,
448
- selectedIndex,
449
- visible,
450
- isSubcommandMode
451
- }) {
452
- if (!visible || commands.length === 0) return null;
453
- const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
454
- const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
455
- return /* @__PURE__ */ jsx5(Box3, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ jsx5(
456
- CommandRow,
457
- {
458
- cmd,
459
- isSelected: scrollOffset + i === selectedIndex,
460
- showSlash: !isSubcommandMode
461
- },
462
- cmd.name
463
- )) });
464
- }
465
- function computeScrollOffset(selectedIndex, total) {
466
- if (total <= MAX_VISIBLE) return 0;
467
- if (selectedIndex < MAX_VISIBLE) return 0;
468
- const maxOffset = total - MAX_VISIBLE;
469
- return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
470
- }
471
-
472
- // src/ui/InputArea.tsx
473
- import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
474
- function parseSlashInput(value) {
475
- if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
476
- const afterSlash = value.slice(1);
477
- const spaceIndex = afterSlash.indexOf(" ");
478
- if (spaceIndex === -1) return { isSlash: true, parentCommand: "", filter: afterSlash };
479
- const parent = afterSlash.slice(0, spaceIndex);
480
- const rest = afterSlash.slice(spaceIndex + 1);
481
- return { isSlash: true, parentCommand: parent, filter: rest };
482
- }
483
- function useAutocomplete(value, registry) {
484
- const [selectedIndex, setSelectedIndex] = useState3(0);
485
- const [dismissed, setDismissed] = useState3(false);
486
- const prevValueRef = React3.useRef(value);
487
- if (prevValueRef.current !== value) {
488
- prevValueRef.current = value;
489
- if (dismissed) setDismissed(false);
490
- }
491
- const parsed = parseSlashInput(value);
492
- const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
493
- const filteredCommands = useMemo(() => {
494
- if (!registry || !parsed.isSlash || dismissed) return [];
495
- if (isSubcommandMode) {
496
- const subs = registry.getSubcommands(parsed.parentCommand);
497
- if (subs.length === 0) return [];
498
- if (!parsed.filter) return subs;
499
- const lower = parsed.filter.toLowerCase();
500
- return subs.filter((c) => c.name.toLowerCase().startsWith(lower));
501
- }
502
- return registry.getCommands(parsed.filter);
503
- }, [registry, parsed.isSlash, parsed.parentCommand, parsed.filter, dismissed, isSubcommandMode]);
504
- const showPopup = parsed.isSlash && filteredCommands.length > 0 && !dismissed;
505
- if (selectedIndex >= filteredCommands.length && filteredCommands.length > 0) {
506
- setSelectedIndex(filteredCommands.length - 1);
507
- }
508
- return {
509
- showPopup,
510
- filteredCommands,
511
- selectedIndex,
512
- setSelectedIndex,
513
- isSubcommandMode,
514
- setShowPopup: (val) => {
515
- if (typeof val === "function") {
516
- setDismissed((prev) => {
517
- const nextVal = val(!prev);
518
- return !nextVal;
519
- });
520
- } else {
521
- setDismissed(!val);
522
- }
523
- }
524
- };
525
- }
526
- function InputArea({ onSubmit, isDisabled, registry }) {
527
- const [value, setValue] = useState3("");
528
- const {
529
- showPopup,
530
- filteredCommands,
531
- selectedIndex,
532
- setSelectedIndex,
533
- isSubcommandMode,
534
- setShowPopup
535
- } = useAutocomplete(value, registry);
536
- const handleSubmit = useCallback(
537
- (text) => {
538
- const trimmed = text.trim();
539
- if (trimmed.length === 0) return;
540
- if (showPopup && filteredCommands[selectedIndex]) {
541
- selectCommand(filteredCommands[selectedIndex]);
542
- return;
543
- }
544
- setValue("");
545
- onSubmit(trimmed);
546
- },
547
- [showPopup, filteredCommands, selectedIndex, onSubmit]
548
- );
549
- const selectCommand = useCallback(
550
- (cmd) => {
551
- const parsed = parseSlashInput(value);
552
- if (parsed.parentCommand) {
553
- const fullCommand = `/${parsed.parentCommand} ${cmd.name}`;
554
- setValue("");
555
- onSubmit(fullCommand);
556
- return;
557
- }
558
- if (cmd.subcommands && cmd.subcommands.length > 0) {
559
- setValue(`/${cmd.name} `);
560
- setSelectedIndex(0);
561
- return;
562
- }
563
- setValue("");
564
- onSubmit(`/${cmd.name}`);
565
- },
566
- [value, onSubmit, setSelectedIndex]
567
- );
568
- useInput2(
569
- (_input, key) => {
570
- if (!showPopup) return;
571
- if (key.upArrow) {
572
- setSelectedIndex((prev) => prev > 0 ? prev - 1 : filteredCommands.length - 1);
573
- } else if (key.downArrow) {
574
- setSelectedIndex((prev) => prev < filteredCommands.length - 1 ? prev + 1 : 0);
575
- } else if (key.escape) {
576
- setShowPopup(false);
577
- } else if (key.tab) {
578
- const cmd = filteredCommands[selectedIndex];
579
- if (cmd) selectCommand(cmd);
580
- }
581
- },
582
- { isActive: showPopup && !isDisabled }
583
- );
584
- return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", children: [
585
- showPopup && /* @__PURE__ */ jsx6(
586
- SlashAutocomplete,
587
- {
588
- commands: filteredCommands,
589
- selectedIndex,
590
- visible: showPopup,
591
- isSubcommandMode
592
- }
593
- ),
594
- /* @__PURE__ */ jsx6(Box4, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ jsx6(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ jsxs4(Box4, { children: [
595
- /* @__PURE__ */ jsx6(Text6, { color: "green", bold: true, children: "> " }),
596
- /* @__PURE__ */ jsx6(
597
- CjkTextInput,
598
- {
599
- value,
600
- onChange: setValue,
601
- onSubmit: handleSubmit,
602
- placeholder: "Type a message or /help"
603
- }
604
- )
605
- ] }) })
606
- ] });
607
- }
608
-
609
- // src/ui/PermissionPrompt.tsx
610
- import React4 from "react";
611
- import { Box as Box5, Text as Text7, useInput as useInput3 } from "ink";
612
- import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
613
- var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
614
- function formatArgs2(args) {
615
- const entries = Object.entries(args);
616
- if (entries.length === 0) return "(no arguments)";
617
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
618
- }
619
- function PermissionPrompt({ request }) {
620
- const [selected, setSelected] = React4.useState(0);
621
- const resolvedRef = React4.useRef(false);
622
- const prevRequestRef = React4.useRef(request);
623
- if (prevRequestRef.current !== request) {
624
- prevRequestRef.current = request;
625
- resolvedRef.current = false;
626
- setSelected(0);
627
- }
628
- const doResolve = React4.useCallback(
629
- (index) => {
630
- if (resolvedRef.current) return;
631
- resolvedRef.current = true;
632
- if (index === 0) request.resolve(true);
633
- else if (index === 1) request.resolve("allow-session");
634
- else request.resolve(false);
635
- },
636
- [request]
637
- );
638
- useInput3((input, key) => {
639
- if (resolvedRef.current) return;
640
- if (key.upArrow || key.leftArrow) {
641
- setSelected((prev) => prev > 0 ? prev - 1 : prev);
642
- } else if (key.downArrow || key.rightArrow) {
643
- setSelected((prev) => prev < OPTIONS.length - 1 ? prev + 1 : prev);
644
- } else if (key.return) {
645
- doResolve(selected);
646
- } else if (input === "y" || input === "1") {
647
- doResolve(0);
648
- } else if (input === "a" || input === "2") {
649
- doResolve(1);
650
- } else if (input === "n" || input === "d" || input === "3") {
651
- doResolve(2);
652
- }
653
- });
654
- return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
655
- /* @__PURE__ */ jsx7(Text7, { color: "yellow", bold: true, children: "[Permission Required]" }),
656
- /* @__PURE__ */ jsxs5(Text7, { children: [
657
- "Tool:",
658
- " ",
659
- /* @__PURE__ */ jsx7(Text7, { color: "cyan", bold: true, children: request.toolName })
660
- ] }),
661
- /* @__PURE__ */ jsxs5(Text7, { dimColor: true, children: [
662
- " ",
663
- formatArgs2(request.toolArgs)
664
- ] }),
665
- /* @__PURE__ */ jsx7(Box5, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsx7(Box5, { marginRight: 2, children: /* @__PURE__ */ jsxs5(Text7, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
666
- i === selected ? "> " : " ",
667
- opt
668
- ] }) }, opt)) }),
669
- /* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " left/right to select, Enter to confirm" })
670
- ] });
671
- }
672
-
673
- // src/ui/App.tsx
674
- import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
675
- var msgIdCounter = 0;
676
- function nextId() {
677
- msgIdCounter += 1;
678
- return `msg_${msgIdCounter}`;
679
- }
680
- var NOOP_TERMINAL = {
681
- write: () => {
682
- },
683
- writeLine: () => {
684
- },
685
- writeMarkdown: () => {
686
- },
687
- writeError: () => {
688
- },
689
- prompt: () => Promise.resolve(""),
690
- select: () => Promise.resolve(0),
691
- spinner: () => ({ stop: () => {
692
- }, update: () => {
693
- } })
694
- };
695
- function useSession(props) {
696
- const [permissionRequest, setPermissionRequest] = useState4(null);
697
- const [streamingText, setStreamingText] = useState4("");
698
- const permissionQueueRef = useRef2([]);
699
- const processingRef = useRef2(false);
700
- const processNextPermission = useCallback2(() => {
701
- if (processingRef.current) return;
702
- const next = permissionQueueRef.current[0];
703
- if (!next) {
704
- setPermissionRequest(null);
705
- return;
706
- }
707
- processingRef.current = true;
708
- setPermissionRequest({
709
- toolName: next.toolName,
710
- toolArgs: next.toolArgs,
711
- resolve: (result) => {
712
- permissionQueueRef.current.shift();
713
- processingRef.current = false;
714
- setPermissionRequest(null);
715
- next.resolve(result);
716
- setTimeout(() => processNextPermission(), 0);
717
- }
718
- });
719
- }, []);
720
- const sessionRef = useRef2(null);
721
- if (sessionRef.current === null) {
722
- const permissionHandler = (toolName, toolArgs) => {
723
- return new Promise((resolve) => {
724
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
725
- processNextPermission();
726
- });
727
- };
728
- const onTextDelta = (delta) => {
729
- setStreamingText((prev) => prev + delta);
730
- };
731
- const paths = projectPaths(props.cwd ?? process.cwd());
732
- sessionRef.current = createSession({
733
- config: props.config,
734
- context: props.context,
735
- terminal: NOOP_TERMINAL,
736
- sessionLogger: new FileSessionLogger(paths.logs),
737
- projectInfo: props.projectInfo,
738
- sessionStore: props.sessionStore,
739
- permissionMode: props.permissionMode,
740
- maxTurns: props.maxTurns,
741
- permissionHandler,
742
- onTextDelta
743
- });
744
- }
745
- const clearStreamingText = useCallback2(() => setStreamingText(""), []);
746
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
747
- }
748
- function useMessages() {
749
- const [messages, setMessages] = useState4([]);
750
- const addMessage = useCallback2((msg) => {
751
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
752
- }, []);
753
- return { messages, setMessages, addMessage };
754
- }
755
- var HELP_TEXT = [
756
- "Available commands:",
757
- " /help \u2014 Show this help",
758
- " /clear \u2014 Clear conversation",
759
- " /compact [instr] \u2014 Compact context (optional focus instructions)",
760
- " /mode [m] \u2014 Show/change permission mode",
761
- " /cost \u2014 Show session info",
762
- " /reset \u2014 Delete settings and exit",
763
- " /exit \u2014 Exit CLI"
764
- ].join("\n");
765
- function handleModeCommand(arg, session, addMessage) {
766
- const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
767
- if (!arg) {
768
- addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
769
- } else if (validModes.includes(arg)) {
770
- session.setPermissionMode(arg);
771
- addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
772
- } else {
773
- addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
774
- }
775
- return true;
776
- }
777
- async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
778
- switch (cmd) {
779
- case "help":
780
- addMessage({ role: "system", content: HELP_TEXT });
781
- return true;
782
- case "clear":
783
- setMessages([]);
784
- session.clearHistory();
785
- addMessage({ role: "system", content: "Conversation cleared." });
786
- return true;
787
- case "compact": {
788
- const instructions = parts.slice(1).join(" ").trim() || void 0;
789
- const before = session.getContextState().usedPercentage;
790
- addMessage({ role: "system", content: "Compacting context..." });
791
- await session.compact(instructions);
792
- const after = session.getContextState().usedPercentage;
793
- addMessage({
794
- role: "system",
795
- content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
796
- });
797
- return true;
798
- }
799
- case "mode":
800
- return handleModeCommand(parts[1], session, addMessage);
801
- case "cost":
802
- addMessage({
803
- role: "system",
804
- content: `Session: ${session.getSessionId()}
805
- Messages: ${session.getMessageCount()}`
806
- });
807
- return true;
808
- case "permissions": {
809
- const mode = session.getPermissionMode();
810
- const sessionAllowed = session.getSessionAllowedTools();
811
- const lines = [`Permission mode: ${mode}`];
812
- if (sessionAllowed.length > 0) {
813
- lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
814
- } else {
815
- lines.push("No session-approved tools.");
816
- }
817
- addMessage({ role: "system", content: lines.join("\n") });
818
- return true;
819
- }
820
- case "context": {
821
- const ctx = session.getContextState();
822
- addMessage({
823
- role: "system",
824
- content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
825
- });
826
- return true;
827
- }
828
- case "reset": {
829
- const { existsSync: exists, unlinkSync: unlink } = await import("fs");
830
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
831
- const settingsPath = `${home}/.robota/settings.json`;
832
- if (exists(settingsPath)) {
833
- unlink(settingsPath);
834
- addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
835
- } else {
836
- addMessage({ role: "system", content: "No user settings found." });
837
- }
838
- setTimeout(() => exit(), 500);
839
- return true;
840
- }
841
- case "exit":
842
- exit();
843
- return true;
844
- default: {
845
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
846
- if (skillCmd) {
847
- addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
848
- return false;
849
- }
850
- addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
851
- return true;
852
- }
853
- }
854
- }
855
- function useSlashCommands(session, addMessage, setMessages, exit, registry) {
856
- return useCallback2(
857
- async (input) => {
858
- const parts = input.slice(1).split(/\s+/);
859
- const cmd = parts[0]?.toLowerCase() ?? "";
860
- return executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry);
861
- },
862
- [session, addMessage, setMessages, exit, registry]
863
- );
864
- }
865
- function StreamingIndicator({ text }) {
866
- if (text) {
867
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
868
- /* @__PURE__ */ jsxs6(Text8, { color: "cyan", bold: true, children: [
869
- "Robota:",
870
- " "
871
- ] }),
872
- /* @__PURE__ */ jsx8(Text8, { children: " " }),
873
- /* @__PURE__ */ jsx8(Box6, { marginLeft: 2, children: /* @__PURE__ */ jsx8(Text8, { wrap: "wrap", children: renderMarkdown(text) }) })
874
- ] });
875
- }
876
- return /* @__PURE__ */ jsx8(Text8, { color: "yellow", children: "Thinking..." });
877
- }
878
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
879
- setIsThinking(true);
880
- clearStreamingText();
881
- const historyBefore = session.getHistory().length;
882
- try {
883
- const response = await session.run(prompt);
884
- clearStreamingText();
885
- const history = session.getHistory();
886
- const toolLines = [];
887
- for (let i = historyBefore; i < history.length; i++) {
888
- const msg = history[i];
889
- if (msg.role === "assistant" && msg.toolCalls) {
890
- for (const tc of msg.toolCalls) {
891
- let value = "";
892
- try {
893
- const parsed = JSON.parse(tc.function.arguments);
894
- const firstVal = Object.values(parsed)[0];
895
- value = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
896
- } catch {
897
- value = tc.function.arguments;
898
- }
899
- const truncated = value.length > 80 ? value.slice(0, 77) + "..." : value;
900
- toolLines.push(`${tc.function.name}(${truncated})`);
901
- }
902
- }
903
- }
904
- if (toolLines.length > 0) {
905
- addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
906
- }
907
- addMessage({ role: "assistant", content: response || "(empty response)" });
908
- setContextPercentage(session.getContextState().usedPercentage);
909
- } catch (err) {
910
- clearStreamingText();
911
- if (err instanceof DOMException && err.name === "AbortError") {
912
- addMessage({ role: "system", content: "Cancelled." });
913
- } else {
914
- const errMsg = err instanceof Error ? err.message : String(err);
915
- addMessage({ role: "system", content: `Error: ${errMsg}` });
916
- }
917
- } finally {
918
- setIsThinking(false);
919
- }
920
- }
921
- function buildSkillPrompt(input, registry) {
922
- const parts = input.slice(1).split(/\s+/);
923
- const cmd = parts[0]?.toLowerCase() ?? "";
924
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
925
- if (!skillCmd) return null;
926
- const args = parts.slice(1).join(" ").trim();
927
- const userInstruction = args || skillCmd.description;
928
- if (skillCmd.skillContent) {
929
- return `<skill name="${cmd}">
930
- ${skillCmd.skillContent}
931
- </skill>
932
-
933
- Execute the "${cmd}" skill: ${userInstruction}`;
934
- }
935
- return `Use the "${cmd}" skill: ${userInstruction}`;
936
- }
937
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
938
- return useCallback2(
939
- async (input) => {
940
- if (input.startsWith("/")) {
941
- const handled = await handleSlashCommand(input);
942
- if (handled) {
943
- setContextPercentage(session.getContextState().usedPercentage);
944
- return;
945
- }
946
- const prompt = buildSkillPrompt(input, registry);
947
- if (!prompt) return;
948
- return runSessionPrompt(
949
- prompt,
950
- session,
951
- addMessage,
952
- clearStreamingText,
953
- setIsThinking,
954
- setContextPercentage
955
- );
956
- }
957
- addMessage({ role: "user", content: input });
958
- return runSessionPrompt(
959
- input,
960
- session,
961
- addMessage,
962
- clearStreamingText,
963
- setIsThinking,
964
- setContextPercentage
965
- );
966
- },
967
- [
968
- session,
969
- addMessage,
970
- handleSlashCommand,
971
- clearStreamingText,
972
- setIsThinking,
973
- setContextPercentage,
974
- registry
975
- ]
976
- );
977
- }
978
- function useCommandRegistry(cwd) {
979
- const registryRef = useRef2(null);
980
- if (registryRef.current === null) {
981
- const registry = new CommandRegistry();
982
- registry.addSource(new BuiltinCommandSource());
983
- registry.addSource(new SkillCommandSource(cwd));
984
- registryRef.current = registry;
985
- }
986
- return registryRef.current;
987
- }
988
- function App(props) {
989
- const { exit } = useApp();
990
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
991
- const { messages, setMessages, addMessage } = useMessages();
992
- const [isThinking, setIsThinking] = useState4(false);
993
- const [contextPercentage, setContextPercentage] = useState4(0);
994
- const registry = useCommandRegistry(props.cwd ?? process.cwd());
995
- const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry);
996
- const handleSubmit = useSubmitHandler(
997
- session,
998
- addMessage,
999
- handleSlashCommand,
1000
- clearStreamingText,
1001
- setIsThinking,
1002
- setContextPercentage,
1003
- registry
1004
- );
1005
- useInput4(
1006
- (_input, key) => {
1007
- if (key.ctrl && _input === "c") exit();
1008
- if (key.escape && isThinking) session.abort();
1009
- },
1010
- { isActive: !permissionRequest }
1011
- );
1012
- return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
1013
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1014
- /* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: true, children: `
1015
- ____ ___ ____ ___ _____ _
1016
- | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1017
- | |_) | | | | _ \\| | | || | / _ \\
1018
- | _ <| |_| | |_) | |_| || |/ ___ \\
1019
- |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1020
- ` }),
1021
- /* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
1022
- " v",
1023
- props.version ?? "0.0.0"
1024
- ] })
1025
- ] }),
1026
- /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1027
- /* @__PURE__ */ jsx8(MessageList, { messages }),
1028
- isThinking && /* @__PURE__ */ jsx8(Box6, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx8(StreamingIndicator, { text: streamingText }) })
1029
- ] }),
1030
- permissionRequest && /* @__PURE__ */ jsx8(PermissionPrompt, { request: permissionRequest }),
1031
- /* @__PURE__ */ jsx8(
1032
- StatusBar,
1033
- {
1034
- permissionMode: session.getPermissionMode(),
1035
- modelName: props.config.provider.model,
1036
- sessionId: session.getSessionId(),
1037
- messageCount: messages.length,
1038
- isThinking,
1039
- contextPercentage,
1040
- contextUsedTokens: session.getContextState().usedTokens,
1041
- contextMaxTokens: session.getContextState().maxTokens
1042
- }
1043
- ),
1044
- /* @__PURE__ */ jsx8(
1045
- InputArea,
1046
- {
1047
- onSubmit: handleSubmit,
1048
- isDisabled: isThinking || !!permissionRequest,
1049
- registry
1050
- }
1051
- )
1052
- ] });
1053
- }
1054
-
1055
- // src/ui/render.tsx
1056
- import { jsx as jsx9 } from "react/jsx-runtime";
1057
- function renderApp(options) {
1058
- process.on("unhandledRejection", (reason) => {
1059
- process.stderr.write(`
1060
- [UNHANDLED REJECTION] ${reason}
1061
- `);
1062
- if (reason instanceof Error) {
1063
- process.stderr.write(`${reason.stack}
1064
- `);
1065
- }
1066
- });
1067
- const instance = render(/* @__PURE__ */ jsx9(App, { ...options }), {
1068
- exitOnCtrlC: true
1069
- });
1070
- instance.waitUntilExit().catch((err) => {
1071
- if (err) {
1072
- process.stderr.write(`
1073
- [EXIT ERROR] ${err}
1074
- `);
1075
- }
1076
- });
1077
- }
1078
-
1079
- // src/cli.ts
1080
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
1081
- function readVersion() {
1082
- try {
1083
- const thisFile = fileURLToPath(import.meta.url);
1084
- const dir = dirname(thisFile);
1085
- const candidates = [join2(dir, "..", "..", "package.json"), join2(dir, "..", "package.json")];
1086
- for (const pkgPath of candidates) {
1087
- try {
1088
- const raw = readFileSync2(pkgPath, "utf-8");
1089
- const pkg = JSON.parse(raw);
1090
- if (pkg.version !== void 0 && pkg.name !== void 0) {
1091
- return pkg.version;
1092
- }
1093
- } catch {
1094
- }
1095
- }
1096
- return "0.0.0";
1097
- } catch {
1098
- return "0.0.0";
1099
- }
1100
- }
1101
- function parsePermissionMode(raw) {
1102
- if (raw === void 0) return void 0;
1103
- if (!VALID_MODES.includes(raw)) {
1104
- process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
1105
- `);
1106
- process.exit(1);
1107
- }
1108
- return raw;
1109
- }
1110
- function parseMaxTurns(raw) {
1111
- if (raw === void 0) return void 0;
1112
- const n = parseInt(raw, 10);
1113
- if (isNaN(n) || n <= 0) {
1114
- process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
1115
- `);
1116
- process.exit(1);
1117
- }
1118
- return n;
1119
- }
1120
- function parseCliArgs() {
1121
- const { values, positionals } = parseArgs({
1122
- allowPositionals: true,
1123
- options: {
1124
- p: { type: "boolean", short: "p", default: false },
1125
- c: { type: "boolean", short: "c", default: false },
1126
- r: { type: "string", short: "r" },
1127
- model: { type: "string" },
1128
- "permission-mode": { type: "string" },
1129
- "max-turns": { type: "string" },
1130
- version: { type: "boolean", default: false },
1131
- reset: { type: "boolean", default: false }
1132
- }
1133
- });
1134
- return {
1135
- positional: positionals,
1136
- printMode: values["p"] ?? false,
1137
- continueMode: values["c"] ?? false,
1138
- resumeId: values["r"],
1139
- model: values["model"],
1140
- permissionMode: parsePermissionMode(values["permission-mode"]),
1141
- maxTurns: parseMaxTurns(values["max-turns"]),
1142
- version: values["version"] ?? false,
1143
- reset: values["reset"] ?? false
1144
- };
1145
- }
1146
- var PrintTerminal = class {
1147
- write(text) {
1148
- process.stdout.write(text);
1149
- }
1150
- writeLine(text) {
1151
- process.stdout.write(text + "\n");
1152
- }
1153
- writeMarkdown(md) {
1154
- process.stdout.write(md);
1155
- }
1156
- writeError(text) {
1157
- process.stderr.write(text + "\n");
1158
- }
1159
- prompt(question) {
1160
- return new Promise((resolve) => {
1161
- const rl = readline.createInterface({
1162
- input: process.stdin,
1163
- output: process.stdout,
1164
- terminal: false,
1165
- historySize: 0
1166
- });
1167
- rl.question(question, (answer) => {
1168
- rl.close();
1169
- resolve(answer);
1170
- });
1171
- });
1172
- }
1173
- async select(options, initialIndex = 0) {
1174
- for (let i = 0; i < options.length; i++) {
1175
- const marker = i === initialIndex ? ">" : " ";
1176
- process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
1177
- `);
1178
- }
1179
- const answer = await this.prompt(
1180
- ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
1181
- );
1182
- const trimmed = answer.trim().toLowerCase();
1183
- if (trimmed === "") return initialIndex;
1184
- const num = parseInt(trimmed, 10);
1185
- if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
1186
- return initialIndex;
1187
- }
1188
- spinner(_message) {
1189
- return { stop() {
1190
- }, update() {
1191
- } };
1192
- }
1193
- };
1194
- function getUserSettingsPath() {
1195
- const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
1196
- return join2(home, ".robota", "settings.json");
1197
- }
1198
- async function ensureConfig(cwd) {
1199
- const userPath = getUserSettingsPath();
1200
- const projectPath = join2(cwd, ".robota", "settings.json");
1201
- const localPath = join2(cwd, ".robota", "settings.local.json");
1202
- if (existsSync2(userPath) || existsSync2(projectPath) || existsSync2(localPath)) {
1203
- return;
1204
- }
1205
- process.stdout.write("\n");
1206
- process.stdout.write(" Welcome to Robota CLI!\n");
1207
- process.stdout.write(" No configuration found. Let's set up your API key.\n");
1208
- process.stdout.write("\n");
1209
- const apiKey = await new Promise((resolve) => {
1210
- process.stdout.write(" Anthropic API key: ");
1211
- let input = "";
1212
- const stdin = process.stdin;
1213
- const wasRaw = stdin.isRaw;
1214
- stdin.setRawMode(true);
1215
- stdin.resume();
1216
- stdin.setEncoding("utf8");
1217
- const onData = (data) => {
1218
- for (const ch of data) {
1219
- if (ch === "\r" || ch === "\n") {
1220
- stdin.removeListener("data", onData);
1221
- stdin.setRawMode(wasRaw ?? false);
1222
- stdin.pause();
1223
- process.stdout.write("\n");
1224
- resolve(input.trim());
1225
- return;
1226
- } else if (ch === "\x7F" || ch === "\b") {
1227
- if (input.length > 0) {
1228
- input = input.slice(0, -1);
1229
- process.stdout.write("\b \b");
1230
- }
1231
- } else if (ch === "") {
1232
- process.stdout.write("\n");
1233
- process.exit(0);
1234
- } else if (ch.charCodeAt(0) >= 32) {
1235
- input += ch;
1236
- process.stdout.write("*");
1237
- }
1238
- }
1239
- };
1240
- stdin.on("data", onData);
1241
- });
1242
- if (!apiKey) {
1243
- process.stderr.write("\n No API key provided. Exiting.\n");
1244
- process.exit(1);
1245
- }
1246
- const settingsDir = dirname(userPath);
1247
- mkdirSync(settingsDir, { recursive: true });
1248
- const settings = {
1249
- provider: {
1250
- name: "anthropic",
1251
- model: "claude-sonnet-4-6",
1252
- apiKey
1253
- }
1254
- };
1255
- writeFileSync(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
1256
- process.stdout.write(`
1257
- Config saved to ${userPath}
1258
-
1259
- `);
1260
- }
1261
- function resetConfig() {
1262
- const userPath = getUserSettingsPath();
1263
- if (existsSync2(userPath)) {
1264
- unlinkSync(userPath);
1265
- process.stdout.write(`Deleted ${userPath}
1266
- `);
1267
- } else {
1268
- process.stdout.write("No user settings found.\n");
1269
- }
1270
- }
1271
- async function startCli() {
1272
- const args = parseCliArgs();
1273
- if (args.version) {
1274
- process.stdout.write(`robota ${readVersion()}
1275
- `);
1276
- return;
1277
- }
1278
- if (args.reset) {
1279
- resetConfig();
1280
- return;
1281
- }
1282
- const cwd = process.cwd();
1283
- await ensureConfig(cwd);
1284
- const [config, context, projectInfo] = await Promise.all([
1285
- loadConfig(cwd),
1286
- loadContext(cwd),
1287
- detectProject(cwd)
1288
- ]);
1289
- if (args.model !== void 0) {
1290
- config.provider.model = args.model;
1291
- }
1292
- const sessionStore = new SessionStore();
1293
- if (args.printMode) {
1294
- const prompt = args.positional.join(" ").trim();
1295
- if (prompt.length === 0) {
1296
- process.stderr.write("Print mode (-p) requires a prompt argument.\n");
1297
- process.exit(1);
1298
- }
1299
- const terminal = new PrintTerminal();
1300
- const paths = projectPaths2(cwd);
1301
- const session = createSession2({
1302
- config,
1303
- context,
1304
- terminal,
1305
- sessionLogger: new FileSessionLogger2(paths.logs),
1306
- projectInfo,
1307
- permissionMode: args.permissionMode,
1308
- promptForApproval
1309
- });
1310
- const response = await session.run(prompt);
1311
- process.stdout.write(response + "\n");
1312
- return;
1313
- }
1314
- renderApp({
1315
- config,
1316
- context,
1317
- projectInfo,
1318
- sessionStore,
1319
- permissionMode: args.permissionMode,
1320
- maxTurns: args.maxTurns,
1321
- version: readVersion()
1322
- });
1323
- }
1324
-
1325
- export {
1326
- startCli
1327
- };