@robota-sdk/agent-cli 3.0.0-beta.5 → 3.0.0-beta.50
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 +195 -86
- package/dist/node/bin.js +11 -1
- package/dist/node/chunk-QNBFMSI5.js +2523 -0
- package/dist/node/index.cjs +2023 -710
- package/dist/node/index.d.cts +10 -23
- package/dist/node/index.d.ts +10 -23
- package/dist/node/index.js +1 -8
- package/package.json +16 -4
- package/dist/node/bin.cjs +0 -1217
- package/dist/node/bin.d.cts +0 -1
- package/dist/node/chunk-4DYVT4WI.js +0 -1196
package/dist/node/index.cjs
CHANGED
|
@@ -30,192 +30,599 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/index.ts
|
|
31
31
|
var index_exports = {};
|
|
32
32
|
__export(index_exports, {
|
|
33
|
-
Session: () => import_agent_sdk3.Session,
|
|
34
|
-
SessionStore: () => import_agent_sdk3.SessionStore,
|
|
35
|
-
TRUST_TO_MODE: () => import_agent_sdk3.TRUST_TO_MODE,
|
|
36
|
-
query: () => import_agent_sdk3.query,
|
|
37
33
|
startCli: () => startCli
|
|
38
34
|
});
|
|
39
35
|
module.exports = __toCommonJS(index_exports);
|
|
40
|
-
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
41
36
|
|
|
42
37
|
// src/cli.ts
|
|
38
|
+
var import_node_fs3 = require("fs");
|
|
39
|
+
var import_node_path5 = require("path");
|
|
40
|
+
var import_node_url = require("url");
|
|
41
|
+
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
42
|
+
var import_agent_sessions = require("@robota-sdk/agent-sessions");
|
|
43
|
+
|
|
44
|
+
// src/utils/cli-args.ts
|
|
43
45
|
var import_node_util = require("util");
|
|
46
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
47
|
+
function parsePermissionMode(raw) {
|
|
48
|
+
if (raw === void 0) return void 0;
|
|
49
|
+
if (!VALID_MODES.includes(raw)) {
|
|
50
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
51
|
+
`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
return raw;
|
|
55
|
+
}
|
|
56
|
+
function parseMaxTurns(raw) {
|
|
57
|
+
if (raw === void 0) return void 0;
|
|
58
|
+
const n = parseInt(raw, 10);
|
|
59
|
+
if (isNaN(n) || n <= 0) {
|
|
60
|
+
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
61
|
+
`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
return n;
|
|
65
|
+
}
|
|
66
|
+
function parseCliArgs() {
|
|
67
|
+
const { values, positionals } = (0, import_node_util.parseArgs)({
|
|
68
|
+
allowPositionals: true,
|
|
69
|
+
options: {
|
|
70
|
+
p: { type: "boolean", short: "p", default: false },
|
|
71
|
+
continue: { type: "boolean", short: "c", default: false },
|
|
72
|
+
resume: { type: "string", short: "r" },
|
|
73
|
+
model: { type: "string" },
|
|
74
|
+
language: { type: "string" },
|
|
75
|
+
"permission-mode": { type: "string" },
|
|
76
|
+
"max-turns": { type: "string" },
|
|
77
|
+
"fork-session": { type: "boolean", default: false },
|
|
78
|
+
name: { type: "string", short: "n" },
|
|
79
|
+
"output-format": { type: "string" },
|
|
80
|
+
"system-prompt": { type: "string" },
|
|
81
|
+
"append-system-prompt": { type: "string" },
|
|
82
|
+
version: { type: "boolean", default: false },
|
|
83
|
+
reset: { type: "boolean", default: false }
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
positional: positionals,
|
|
88
|
+
printMode: values["p"] ?? false,
|
|
89
|
+
continueMode: values["continue"] ?? false,
|
|
90
|
+
resumeId: values["resume"],
|
|
91
|
+
model: values["model"],
|
|
92
|
+
language: values["language"],
|
|
93
|
+
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
94
|
+
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
95
|
+
forkSession: values["fork-session"] ?? false,
|
|
96
|
+
sessionName: values["name"],
|
|
97
|
+
outputFormat: values["output-format"],
|
|
98
|
+
systemPrompt: values["system-prompt"],
|
|
99
|
+
appendSystemPrompt: values["append-system-prompt"],
|
|
100
|
+
version: values["version"] ?? false,
|
|
101
|
+
reset: values["reset"] ?? false
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/utils/settings-io.ts
|
|
106
|
+
var import_node_fs = require("fs");
|
|
107
|
+
var import_node_path = require("path");
|
|
108
|
+
function getUserSettingsPath() {
|
|
109
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
110
|
+
return (0, import_node_path.join)(home, ".robota", "settings.json");
|
|
111
|
+
}
|
|
112
|
+
function readSettings(path) {
|
|
113
|
+
if (!(0, import_node_fs.existsSync)(path)) return {};
|
|
114
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
115
|
+
}
|
|
116
|
+
function writeSettings(path, settings) {
|
|
117
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
118
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
119
|
+
}
|
|
120
|
+
function updateModelInSettings(settingsPath, modelId) {
|
|
121
|
+
const settings = readSettings(settingsPath);
|
|
122
|
+
const provider = settings.provider ?? {};
|
|
123
|
+
provider.model = modelId;
|
|
124
|
+
settings.provider = provider;
|
|
125
|
+
writeSettings(settingsPath, settings);
|
|
126
|
+
}
|
|
127
|
+
function deleteSettings(path) {
|
|
128
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
129
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/utils/provider-factory.ts
|
|
44
136
|
var import_node_fs2 = require("fs");
|
|
45
137
|
var import_node_path2 = require("path");
|
|
46
|
-
var
|
|
47
|
-
var
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
138
|
+
var import_node_os = require("os");
|
|
139
|
+
var import_agent_provider_anthropic = require("@robota-sdk/agent-provider-anthropic");
|
|
140
|
+
function readProviderSettings(cwd) {
|
|
141
|
+
const paths = [
|
|
142
|
+
(0, import_node_path2.join)(cwd, ".robota", "settings.local.json"),
|
|
143
|
+
(0, import_node_path2.join)(cwd, ".robota", "settings.json"),
|
|
144
|
+
(0, import_node_path2.join)(cwd, ".claude", "settings.local.json"),
|
|
145
|
+
(0, import_node_path2.join)(cwd, ".claude", "settings.json"),
|
|
146
|
+
(0, import_node_path2.join)((0, import_node_os.homedir)(), ".robota", "settings.json"),
|
|
147
|
+
(0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "settings.json")
|
|
148
|
+
];
|
|
149
|
+
for (const filePath of paths) {
|
|
150
|
+
if (!(0, import_node_fs2.existsSync)(filePath)) continue;
|
|
151
|
+
try {
|
|
152
|
+
const raw = (0, import_node_fs2.readFileSync)(filePath, "utf8");
|
|
153
|
+
const parsed = JSON.parse(raw);
|
|
154
|
+
const provider = parsed.provider;
|
|
155
|
+
if (provider?.apiKey && provider?.name) {
|
|
156
|
+
return {
|
|
157
|
+
name: provider.name,
|
|
158
|
+
model: provider.model ?? "claude-sonnet-4-6",
|
|
159
|
+
apiKey: provider.apiKey
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
} catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
58
165
|
}
|
|
59
|
-
|
|
166
|
+
throw new Error("No provider configuration found. Run `robota` to set up.");
|
|
60
167
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
168
|
+
function createProviderFromSettings(cwd, modelOverride) {
|
|
169
|
+
const settings = readProviderSettings(cwd);
|
|
170
|
+
const model = modelOverride ?? settings.model;
|
|
171
|
+
switch (settings.name) {
|
|
172
|
+
case "anthropic":
|
|
173
|
+
return new import_agent_provider_anthropic.AnthropicProvider({ apiKey: settings.apiKey, defaultModel: model });
|
|
174
|
+
default:
|
|
175
|
+
throw new Error(`Unknown provider: ${settings.name}. Currently supported: anthropic`);
|
|
176
|
+
}
|
|
68
177
|
}
|
|
69
178
|
|
|
179
|
+
// src/cli.ts
|
|
180
|
+
var import_agent_transport_headless = require("@robota-sdk/agent-transport-headless");
|
|
181
|
+
|
|
70
182
|
// src/ui/render.tsx
|
|
71
|
-
var
|
|
183
|
+
var import_ink15 = require("ink");
|
|
72
184
|
|
|
73
185
|
// src/ui/App.tsx
|
|
74
|
-
var
|
|
75
|
-
var
|
|
186
|
+
var import_react13 = require("react");
|
|
187
|
+
var import_ink14 = require("ink");
|
|
188
|
+
var import_agent_core4 = require("@robota-sdk/agent-core");
|
|
189
|
+
|
|
190
|
+
// src/ui/hooks/useInteractiveSession.ts
|
|
191
|
+
var import_react = require("react");
|
|
192
|
+
var import_node_os2 = require("os");
|
|
193
|
+
var import_node_path3 = require("path");
|
|
76
194
|
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
195
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
196
|
+
var import_node_crypto = require("crypto");
|
|
77
197
|
|
|
78
|
-
// src/
|
|
79
|
-
var
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
198
|
+
// src/ui/tui-state-manager.ts
|
|
199
|
+
var MAX_RENDERED_MESSAGES = 100;
|
|
200
|
+
var TuiStateManager = class {
|
|
201
|
+
// ── Rendering state ───────────────────────────────────────────
|
|
202
|
+
history = [];
|
|
203
|
+
streamingText = "";
|
|
204
|
+
activeTools = [];
|
|
205
|
+
isThinking = false;
|
|
206
|
+
isAborting = false;
|
|
207
|
+
pendingPrompt = null;
|
|
208
|
+
contextState = { percentage: 0, usedTokens: 0, maxTokens: 0 };
|
|
209
|
+
/** Called after any state change. React hook sets this to trigger re-render. */
|
|
210
|
+
onChange = null;
|
|
211
|
+
// ── Internal ──────────────────────────────────────────────────
|
|
212
|
+
streamBuf = "";
|
|
213
|
+
notify() {
|
|
214
|
+
this.onChange?.();
|
|
83
215
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
216
|
+
// ── Event handlers (InteractiveSession → state) ───────────────
|
|
217
|
+
onTextDelta = (delta) => {
|
|
218
|
+
this.streamBuf += delta;
|
|
219
|
+
this.streamingText = this.streamBuf;
|
|
220
|
+
this.notify();
|
|
221
|
+
};
|
|
222
|
+
onToolStart = (state) => {
|
|
223
|
+
this.activeTools = [...this.activeTools, state];
|
|
224
|
+
this.notify();
|
|
225
|
+
};
|
|
226
|
+
onToolEnd = (state) => {
|
|
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;
|
|
89
232
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return cmd.subcommands;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
233
|
+
this.notify();
|
|
234
|
+
};
|
|
235
|
+
onThinking = (thinking) => {
|
|
236
|
+
this.isThinking = thinking;
|
|
237
|
+
if (thinking) {
|
|
238
|
+
this.streamBuf = "";
|
|
239
|
+
this.streamingText = "";
|
|
240
|
+
this.activeTools = [];
|
|
241
|
+
} else {
|
|
242
|
+
this.isAborting = false;
|
|
103
243
|
}
|
|
104
|
-
|
|
244
|
+
this.notify();
|
|
245
|
+
};
|
|
246
|
+
onComplete = (result) => {
|
|
247
|
+
this.streamBuf = "";
|
|
248
|
+
this.streamingText = "";
|
|
249
|
+
this.activeTools = [];
|
|
250
|
+
this.contextState = {
|
|
251
|
+
percentage: result.contextState.usedPercentage,
|
|
252
|
+
usedTokens: result.contextState.usedTokens,
|
|
253
|
+
maxTokens: result.contextState.maxTokens
|
|
254
|
+
};
|
|
255
|
+
this.notify();
|
|
256
|
+
};
|
|
257
|
+
onInterrupted = () => {
|
|
258
|
+
this.streamBuf = "";
|
|
259
|
+
this.streamingText = "";
|
|
260
|
+
this.activeTools = [];
|
|
261
|
+
this.notify();
|
|
262
|
+
};
|
|
263
|
+
onError = () => {
|
|
264
|
+
this.streamBuf = "";
|
|
265
|
+
this.streamingText = "";
|
|
266
|
+
this.activeTools = [];
|
|
267
|
+
this.notify();
|
|
268
|
+
};
|
|
269
|
+
// ── State updates from external sources ───────────────────────
|
|
270
|
+
/** Sync history from InteractiveSession */
|
|
271
|
+
syncHistory(entries) {
|
|
272
|
+
if (entries.length === 0) return;
|
|
273
|
+
this.history = entries.length > MAX_RENDERED_MESSAGES ? entries.slice(-MAX_RENDERED_MESSAGES) : [...entries];
|
|
274
|
+
this.notify();
|
|
105
275
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
112
|
-
{ name: "clear", description: "Clear conversation history", source: "builtin" },
|
|
113
|
-
{
|
|
114
|
-
name: "mode",
|
|
115
|
-
description: "Permission mode",
|
|
116
|
-
source: "builtin",
|
|
117
|
-
subcommands: [
|
|
118
|
-
{ name: "plan", description: "Plan only, no execution", source: "builtin" },
|
|
119
|
-
{ name: "default", description: "Ask before risky actions", source: "builtin" },
|
|
120
|
-
{ name: "acceptEdits", description: "Auto-approve file edits", source: "builtin" },
|
|
121
|
-
{ name: "bypassPermissions", description: "Skip all permission checks", source: "builtin" }
|
|
122
|
-
]
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: "model",
|
|
126
|
-
description: "Select AI model",
|
|
127
|
-
source: "builtin",
|
|
128
|
-
subcommands: [
|
|
129
|
-
{ name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
|
|
130
|
-
{ name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
|
|
131
|
-
{ name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
|
|
132
|
-
]
|
|
133
|
-
},
|
|
134
|
-
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
135
|
-
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
136
|
-
{ name: "context", description: "Context window info", source: "builtin" },
|
|
137
|
-
{ name: "permissions", description: "Permission rules", source: "builtin" },
|
|
138
|
-
{ name: "exit", description: "Exit CLI", source: "builtin" }
|
|
139
|
-
];
|
|
140
|
-
}
|
|
141
|
-
var BuiltinCommandSource = class {
|
|
142
|
-
name = "builtin";
|
|
143
|
-
commands;
|
|
144
|
-
constructor() {
|
|
145
|
-
this.commands = createBuiltinCommands();
|
|
276
|
+
/** Add a single history entry */
|
|
277
|
+
addEntry(entry) {
|
|
278
|
+
const updated = [...this.history, entry];
|
|
279
|
+
this.history = updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
|
|
280
|
+
this.notify();
|
|
146
281
|
}
|
|
147
|
-
|
|
148
|
-
|
|
282
|
+
/** Update pending prompt state */
|
|
283
|
+
setPendingPrompt(prompt) {
|
|
284
|
+
this.pendingPrompt = prompt;
|
|
285
|
+
this.notify();
|
|
286
|
+
}
|
|
287
|
+
/** Set aborting flag */
|
|
288
|
+
setAborting(aborting) {
|
|
289
|
+
this.isAborting = aborting;
|
|
290
|
+
this.notify();
|
|
291
|
+
}
|
|
292
|
+
/** Update context state */
|
|
293
|
+
setContextState(state) {
|
|
294
|
+
this.contextState = state;
|
|
295
|
+
this.notify();
|
|
149
296
|
}
|
|
150
297
|
};
|
|
151
298
|
|
|
152
|
-
// src/
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
299
|
+
// src/ui/hooks/useInteractiveSession.ts
|
|
300
|
+
function initializeSession(props, permissionHandler) {
|
|
301
|
+
const interactiveSession = new import_agent_sdk.InteractiveSession({
|
|
302
|
+
cwd: props.cwd,
|
|
303
|
+
provider: props.provider,
|
|
304
|
+
permissionMode: props.permissionMode,
|
|
305
|
+
maxTurns: props.maxTurns,
|
|
306
|
+
permissionHandler,
|
|
307
|
+
sessionStore: props.sessionStore,
|
|
308
|
+
resumeSessionId: props.resumeSessionId,
|
|
309
|
+
forkSession: props.forkSession,
|
|
310
|
+
sessionName: props.sessionName
|
|
311
|
+
});
|
|
312
|
+
const registry = new import_agent_sdk.CommandRegistry();
|
|
313
|
+
registry.addSource(new import_agent_sdk.BuiltinCommandSource());
|
|
314
|
+
registry.addSource(new import_agent_sdk.SkillCommandSource(props.cwd));
|
|
315
|
+
const pluginsDir = (0, import_node_path3.join)((0, import_node_os2.homedir)(), ".robota", "plugins");
|
|
316
|
+
const loader = new import_agent_sdk.BundlePluginLoader(pluginsDir);
|
|
317
|
+
try {
|
|
318
|
+
const plugins = loader.loadPluginsSync();
|
|
319
|
+
if (plugins.length > 0) {
|
|
320
|
+
registry.addSource(new import_agent_sdk.PluginCommandSource(plugins));
|
|
172
321
|
}
|
|
322
|
+
} catch {
|
|
173
323
|
}
|
|
174
|
-
|
|
324
|
+
const manager = new TuiStateManager();
|
|
325
|
+
return { interactiveSession, registry, manager };
|
|
175
326
|
}
|
|
176
|
-
function
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
const
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
327
|
+
function useInteractiveSession(props) {
|
|
328
|
+
const [, forceRender] = (0, import_react.useState)(0);
|
|
329
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
|
|
330
|
+
const permissionQueueRef = (0, import_react.useRef)([]);
|
|
331
|
+
const processingRef = (0, import_react.useRef)(false);
|
|
332
|
+
const processNextPermission = (0, import_react.useCallback)(() => {
|
|
333
|
+
if (processingRef.current) return;
|
|
334
|
+
const next = permissionQueueRef.current[0];
|
|
335
|
+
if (!next) {
|
|
336
|
+
setPermissionRequest(null);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
processingRef.current = true;
|
|
340
|
+
setPermissionRequest({
|
|
341
|
+
toolName: next.toolName,
|
|
342
|
+
toolArgs: next.toolArgs,
|
|
343
|
+
resolve: (result) => {
|
|
344
|
+
permissionQueueRef.current.shift();
|
|
345
|
+
processingRef.current = false;
|
|
346
|
+
setPermissionRequest(null);
|
|
347
|
+
next.resolve(result);
|
|
348
|
+
setTimeout(() => processNextPermission(), 0);
|
|
349
|
+
}
|
|
190
350
|
});
|
|
351
|
+
}, []);
|
|
352
|
+
const permissionHandler = (0, import_react.useCallback)(
|
|
353
|
+
(toolName, toolArgs) => new Promise((resolve) => {
|
|
354
|
+
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
355
|
+
processNextPermission();
|
|
356
|
+
}),
|
|
357
|
+
[processNextPermission]
|
|
358
|
+
);
|
|
359
|
+
const stateRef = (0, import_react.useRef)(null);
|
|
360
|
+
if (stateRef.current === null) {
|
|
361
|
+
stateRef.current = initializeSession(props, permissionHandler);
|
|
191
362
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
this.cwd = cwd;
|
|
363
|
+
const { interactiveSession, registry, manager } = stateRef.current;
|
|
364
|
+
manager.onChange = () => forceRender((n) => n + 1);
|
|
365
|
+
if (manager.history.length === 0) {
|
|
366
|
+
const restored = interactiveSession.getFullHistory();
|
|
367
|
+
if (restored.length > 0) {
|
|
368
|
+
manager.syncHistory(restored);
|
|
369
|
+
}
|
|
200
370
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
371
|
+
(0, import_react.useEffect)(() => {
|
|
372
|
+
interactiveSession.on("text_delta", manager.onTextDelta);
|
|
373
|
+
interactiveSession.on("tool_start", manager.onToolStart);
|
|
374
|
+
interactiveSession.on("tool_end", manager.onToolEnd);
|
|
375
|
+
interactiveSession.on("thinking", manager.onThinking);
|
|
376
|
+
interactiveSession.on("complete", manager.onComplete);
|
|
377
|
+
interactiveSession.on("interrupted", manager.onInterrupted);
|
|
378
|
+
interactiveSession.on("error", manager.onError);
|
|
379
|
+
const initCheck = setInterval(() => {
|
|
380
|
+
try {
|
|
381
|
+
const ctx = interactiveSession.getContextState();
|
|
382
|
+
manager.setContextState({
|
|
383
|
+
percentage: ctx.usedPercentage,
|
|
384
|
+
usedTokens: ctx.usedTokens,
|
|
385
|
+
maxTokens: ctx.maxTokens
|
|
386
|
+
});
|
|
387
|
+
const restored = interactiveSession.getFullHistory();
|
|
388
|
+
if (restored.length > 0) {
|
|
389
|
+
manager.syncHistory(restored);
|
|
390
|
+
}
|
|
391
|
+
clearInterval(initCheck);
|
|
392
|
+
} catch {
|
|
210
393
|
}
|
|
394
|
+
}, 200);
|
|
395
|
+
return () => {
|
|
396
|
+
clearInterval(initCheck);
|
|
397
|
+
interactiveSession.off("text_delta", manager.onTextDelta);
|
|
398
|
+
interactiveSession.off("tool_start", manager.onToolStart);
|
|
399
|
+
interactiveSession.off("tool_end", manager.onToolEnd);
|
|
400
|
+
interactiveSession.off("thinking", manager.onThinking);
|
|
401
|
+
interactiveSession.off("complete", manager.onComplete);
|
|
402
|
+
interactiveSession.off("interrupted", manager.onInterrupted);
|
|
403
|
+
interactiveSession.off("error", manager.onError);
|
|
404
|
+
};
|
|
405
|
+
}, [interactiveSession, manager]);
|
|
406
|
+
(0, import_react.useEffect)(() => {
|
|
407
|
+
manager.syncHistory(interactiveSession.getFullHistory());
|
|
408
|
+
if (!manager.isThinking) {
|
|
409
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
211
410
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
411
|
+
}, [manager.isThinking, interactiveSession, manager]);
|
|
412
|
+
const handleSubmit = (0, import_react.useCallback)(
|
|
413
|
+
async (input) => {
|
|
414
|
+
if (input.startsWith("/")) {
|
|
415
|
+
const parts = input.slice(1).split(/\s+/);
|
|
416
|
+
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
417
|
+
const args = parts.slice(1).join(" ");
|
|
418
|
+
const result = await interactiveSession.executeCommand(cmd, args);
|
|
419
|
+
if (result) {
|
|
420
|
+
manager.addEntry((0, import_agent_core.messageToHistoryEntry)((0, import_agent_core.createSystemMessage)(result.message)));
|
|
421
|
+
const effects = interactiveSession;
|
|
422
|
+
if (result.data?.modelId) {
|
|
423
|
+
effects._pendingModelId = result.data.modelId;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (result.data?.language) {
|
|
427
|
+
effects._pendingLanguage = result.data.language;
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (result.data?.resetRequested) {
|
|
431
|
+
effects._resetRequested = true;
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
if (result.data?.triggerResumePicker) {
|
|
435
|
+
effects._triggerResumePicker = true;
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
if (result.data?.name) {
|
|
439
|
+
effects._sessionName = result.data.name;
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const ctx = interactiveSession.getContextState();
|
|
443
|
+
manager.setContextState({
|
|
444
|
+
percentage: ctx.usedPercentage,
|
|
445
|
+
usedTokens: ctx.usedTokens,
|
|
446
|
+
maxTokens: ctx.maxTokens
|
|
447
|
+
});
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
|
|
451
|
+
if (skillCmd) {
|
|
452
|
+
manager.addEntry({
|
|
453
|
+
id: (0, import_node_crypto.randomUUID)(),
|
|
454
|
+
timestamp: /* @__PURE__ */ new Date(),
|
|
455
|
+
category: "event",
|
|
456
|
+
type: "skill-invocation",
|
|
457
|
+
data: {
|
|
458
|
+
skillName: cmd,
|
|
459
|
+
source: skillCmd.source,
|
|
460
|
+
message: `Invoking ${skillCmd.source}: ${cmd}`
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
const prompt = await (0, import_agent_sdk.buildSkillPrompt)(input, registry);
|
|
464
|
+
if (prompt) {
|
|
465
|
+
const qualifiedName = registry.resolveQualifiedName(cmd);
|
|
466
|
+
const hookInput = qualifiedName ? `/${qualifiedName}${input.slice(1 + cmd.length)}` : input;
|
|
467
|
+
await interactiveSession.submit(prompt, input, hookInput);
|
|
468
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
if (cmd === "exit") {
|
|
473
|
+
interactiveSession._exitRequested = true;
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
if (cmd === "plugin") {
|
|
477
|
+
interactiveSession._triggerPluginTUI = true;
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
manager.addEntry(
|
|
481
|
+
(0, import_agent_core.messageToHistoryEntry)(
|
|
482
|
+
(0, import_agent_core.createSystemMessage)(`Unknown command "/${cmd}". Type /help for help.`)
|
|
483
|
+
)
|
|
484
|
+
);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
await interactiveSession.submit(input);
|
|
488
|
+
manager.setPendingPrompt(interactiveSession.getPendingPrompt());
|
|
489
|
+
},
|
|
490
|
+
[interactiveSession, registry, manager]
|
|
491
|
+
);
|
|
492
|
+
const handleAbort = (0, import_react.useCallback)(() => {
|
|
493
|
+
manager.setAborting(true);
|
|
494
|
+
interactiveSession.abort();
|
|
495
|
+
}, [interactiveSession, manager]);
|
|
496
|
+
const handleCancelQueue = (0, import_react.useCallback)(() => {
|
|
497
|
+
interactiveSession.cancelQueue();
|
|
498
|
+
manager.setPendingPrompt(null);
|
|
499
|
+
}, [interactiveSession, manager]);
|
|
500
|
+
return {
|
|
501
|
+
interactiveSession,
|
|
502
|
+
registry,
|
|
503
|
+
history: manager.history,
|
|
504
|
+
addEntry: (entry) => manager.addEntry(entry),
|
|
505
|
+
streamingText: manager.streamingText,
|
|
506
|
+
activeTools: manager.activeTools,
|
|
507
|
+
isThinking: manager.isThinking,
|
|
508
|
+
isAborting: manager.isAborting,
|
|
509
|
+
pendingPrompt: manager.pendingPrompt,
|
|
510
|
+
permissionRequest,
|
|
511
|
+
contextState: manager.contextState,
|
|
512
|
+
handleSubmit,
|
|
513
|
+
handleAbort,
|
|
514
|
+
handleCancelQueue
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// src/ui/hooks/usePluginCallbacks.ts
|
|
519
|
+
var import_react2 = require("react");
|
|
520
|
+
var import_node_os3 = require("os");
|
|
521
|
+
var import_node_path4 = require("path");
|
|
522
|
+
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
523
|
+
function usePluginCallbacks(cwd) {
|
|
524
|
+
return (0, import_react2.useMemo)(() => {
|
|
525
|
+
const home = (0, import_node_os3.homedir)();
|
|
526
|
+
const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
|
|
527
|
+
const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
|
|
528
|
+
const settingsStore = new import_agent_sdk2.PluginSettingsStore(userSettingsPath);
|
|
529
|
+
const marketplace = new import_agent_sdk2.MarketplaceClient({ pluginsDir });
|
|
530
|
+
const installer = new import_agent_sdk2.BundlePluginInstaller({
|
|
531
|
+
pluginsDir,
|
|
532
|
+
settingsStore,
|
|
533
|
+
marketplaceClient: marketplace
|
|
534
|
+
});
|
|
535
|
+
const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
|
|
536
|
+
return {
|
|
537
|
+
listInstalled: async () => {
|
|
538
|
+
const plugins = await loader.loadAll();
|
|
539
|
+
const enabledMap = settingsStore.getEnabledPlugins();
|
|
540
|
+
return plugins.map((p) => {
|
|
541
|
+
const parts = p.pluginDir.split("/");
|
|
542
|
+
const cacheIdx = parts.indexOf("cache");
|
|
543
|
+
const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
|
|
544
|
+
const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
|
|
545
|
+
return {
|
|
546
|
+
name: fullId,
|
|
547
|
+
description: p.manifest.description,
|
|
548
|
+
enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
|
|
549
|
+
};
|
|
550
|
+
});
|
|
551
|
+
},
|
|
552
|
+
listAvailablePlugins: async (marketplaceName) => {
|
|
553
|
+
let manifest;
|
|
554
|
+
try {
|
|
555
|
+
manifest = marketplace.fetchManifest(marketplaceName);
|
|
556
|
+
} catch {
|
|
557
|
+
return [];
|
|
558
|
+
}
|
|
559
|
+
const installed = installer.getInstalledPlugins();
|
|
560
|
+
const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
|
|
561
|
+
return manifest.plugins.map((p) => ({
|
|
562
|
+
name: p.name,
|
|
563
|
+
description: p.description,
|
|
564
|
+
installed: installedNames.has(p.name)
|
|
565
|
+
}));
|
|
566
|
+
},
|
|
567
|
+
install: async (pluginId, scope) => {
|
|
568
|
+
const [name, marketplaceName] = pluginId.split("@");
|
|
569
|
+
if (!name || !marketplaceName) {
|
|
570
|
+
throw new Error("Plugin ID must be in format: name@marketplace");
|
|
571
|
+
}
|
|
572
|
+
if (scope === "project") {
|
|
573
|
+
const projectPluginsDir = (0, import_node_path4.join)(cwd, ".robota", "plugins");
|
|
574
|
+
const projectInstaller = new import_agent_sdk2.BundlePluginInstaller({
|
|
575
|
+
pluginsDir: projectPluginsDir,
|
|
576
|
+
settingsStore,
|
|
577
|
+
marketplaceClient: marketplace
|
|
578
|
+
});
|
|
579
|
+
await projectInstaller.install(name, marketplaceName);
|
|
580
|
+
} else {
|
|
581
|
+
await installer.install(name, marketplaceName);
|
|
582
|
+
}
|
|
583
|
+
},
|
|
584
|
+
uninstall: async (pluginId) => {
|
|
585
|
+
await installer.uninstall(pluginId);
|
|
586
|
+
},
|
|
587
|
+
enable: async (pluginId) => {
|
|
588
|
+
await installer.enable(pluginId);
|
|
589
|
+
},
|
|
590
|
+
disable: async (pluginId) => {
|
|
591
|
+
await installer.disable(pluginId);
|
|
592
|
+
},
|
|
593
|
+
marketplaceAdd: async (source) => {
|
|
594
|
+
if (source.includes("/") && !source.includes(":")) {
|
|
595
|
+
return marketplace.addMarketplace({ type: "github", repo: source });
|
|
596
|
+
} else {
|
|
597
|
+
return marketplace.addMarketplace({ type: "git", url: source });
|
|
598
|
+
}
|
|
599
|
+
},
|
|
600
|
+
marketplaceRemove: async (name) => {
|
|
601
|
+
const installedFromMarketplace = installer.getPluginsByMarketplace(name);
|
|
602
|
+
for (const record of installedFromMarketplace) {
|
|
603
|
+
await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
|
|
604
|
+
}
|
|
605
|
+
marketplace.removeMarketplace(name);
|
|
606
|
+
},
|
|
607
|
+
marketplaceUpdate: async (name) => {
|
|
608
|
+
marketplace.updateMarketplace(name);
|
|
609
|
+
},
|
|
610
|
+
marketplaceList: async () => {
|
|
611
|
+
return marketplace.listMarketplaces().map((m) => ({
|
|
612
|
+
name: m.name,
|
|
613
|
+
type: m.source.type
|
|
614
|
+
}));
|
|
615
|
+
},
|
|
616
|
+
reloadPlugins: async () => {
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
}, [cwd]);
|
|
620
|
+
}
|
|
216
621
|
|
|
217
622
|
// src/ui/MessageList.tsx
|
|
218
|
-
var
|
|
623
|
+
var import_react3 = __toESM(require("react"), 1);
|
|
624
|
+
var import_ink2 = require("ink");
|
|
625
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
219
626
|
|
|
220
627
|
// src/ui/render-markdown.ts
|
|
221
628
|
var import_marked = require("marked");
|
|
@@ -228,54 +635,201 @@ function renderMarkdown(md) {
|
|
|
228
635
|
return typeof result === "string" ? result.trimEnd() : md;
|
|
229
636
|
}
|
|
230
637
|
|
|
231
|
-
// src/ui/
|
|
638
|
+
// src/ui/DiffBlock.tsx
|
|
639
|
+
var import_ink = require("ink");
|
|
232
640
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
641
|
+
var MAX_DIFF_LINES = 12;
|
|
642
|
+
var TRUNCATED_SHOW = 10;
|
|
643
|
+
function DiffBlock({ file, lines }) {
|
|
644
|
+
const truncated = lines.length > MAX_DIFF_LINES;
|
|
645
|
+
const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
|
|
646
|
+
const remaining = lines.length - TRUNCATED_SHOW;
|
|
647
|
+
const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
|
|
648
|
+
const numWidth = String(maxLineNum).length;
|
|
649
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginLeft: 4, children: [
|
|
650
|
+
file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
651
|
+
"\u2502 ",
|
|
652
|
+
file
|
|
653
|
+
] }),
|
|
654
|
+
visible.map((line, i) => {
|
|
655
|
+
const lineNum = String(line.lineNumber).padStart(numWidth, " ");
|
|
656
|
+
if (line.type === "context") {
|
|
657
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
658
|
+
"\u2502 ",
|
|
659
|
+
lineNum,
|
|
660
|
+
" ",
|
|
661
|
+
line.text
|
|
662
|
+
] }, i);
|
|
663
|
+
}
|
|
664
|
+
const prefix = line.type === "remove" ? "-" : "+";
|
|
665
|
+
const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
|
|
666
|
+
const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
|
|
667
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: fgColor, backgroundColor: bgColor, children: [
|
|
668
|
+
"\u2502 ",
|
|
669
|
+
lineNum,
|
|
670
|
+
" ",
|
|
671
|
+
prefix,
|
|
672
|
+
" ",
|
|
673
|
+
line.text
|
|
674
|
+
] }, i);
|
|
675
|
+
}),
|
|
676
|
+
truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
|
|
677
|
+
"\u2502 ... and ",
|
|
678
|
+
remaining,
|
|
679
|
+
" more lines"
|
|
680
|
+
] })
|
|
681
|
+
] });
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/ui/MessageList.tsx
|
|
685
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
233
686
|
function RoleLabel({ role }) {
|
|
234
687
|
switch (role) {
|
|
235
688
|
case "user":
|
|
236
|
-
return /* @__PURE__ */ (0,
|
|
689
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
|
|
237
690
|
"You:",
|
|
238
691
|
" "
|
|
239
692
|
] });
|
|
240
693
|
case "assistant":
|
|
241
|
-
return /* @__PURE__ */ (0,
|
|
694
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
|
|
242
695
|
"Robota:",
|
|
243
696
|
" "
|
|
244
697
|
] });
|
|
245
698
|
case "system":
|
|
246
|
-
return /* @__PURE__ */ (0,
|
|
699
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
|
|
247
700
|
"System:",
|
|
248
701
|
" "
|
|
249
702
|
] });
|
|
250
703
|
case "tool":
|
|
251
|
-
return /* @__PURE__ */ (0,
|
|
704
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
252
705
|
"Tool:",
|
|
253
706
|
" "
|
|
254
707
|
] });
|
|
255
708
|
}
|
|
256
709
|
}
|
|
257
|
-
function
|
|
258
|
-
|
|
259
|
-
/* @__PURE__ */ (0,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
710
|
+
function ToolMessage({ message }) {
|
|
711
|
+
if (!(0, import_agent_core2.isToolMessage)(message)) {
|
|
712
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
|
|
713
|
+
}
|
|
714
|
+
const toolName = message.name;
|
|
715
|
+
const content = message.content;
|
|
716
|
+
let summaries = null;
|
|
717
|
+
try {
|
|
718
|
+
const parsed = JSON.parse(content);
|
|
719
|
+
if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
|
|
720
|
+
summaries = parsed;
|
|
721
|
+
}
|
|
722
|
+
} catch {
|
|
723
|
+
}
|
|
724
|
+
if (summaries) {
|
|
725
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
726
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
728
|
+
"Tool:",
|
|
729
|
+
" "
|
|
730
|
+
] }),
|
|
731
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
732
|
+
"[",
|
|
733
|
+
toolName,
|
|
734
|
+
"]"
|
|
735
|
+
] })
|
|
736
|
+
] }),
|
|
737
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
738
|
+
summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", children: [
|
|
739
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
740
|
+
" ",
|
|
741
|
+
"\u2713",
|
|
742
|
+
" ",
|
|
743
|
+
s.line
|
|
744
|
+
] }),
|
|
745
|
+
s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiffBlock, { file: s.diffFile, lines: s.diffLines })
|
|
746
|
+
] }, i))
|
|
747
|
+
] });
|
|
748
|
+
}
|
|
749
|
+
const lines = content.split("\n").filter((l) => l.trim());
|
|
750
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
751
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
753
|
+
"Tool:",
|
|
265
754
|
" "
|
|
755
|
+
] }),
|
|
756
|
+
toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
|
|
757
|
+
"[",
|
|
758
|
+
toolName,
|
|
759
|
+
"]"
|
|
266
760
|
] })
|
|
267
761
|
] }),
|
|
268
|
-
/* @__PURE__ */ (0,
|
|
269
|
-
|
|
762
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
763
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
764
|
+
" ",
|
|
765
|
+
"\u2713",
|
|
766
|
+
" ",
|
|
767
|
+
line
|
|
768
|
+
] }, i))
|
|
769
|
+
] });
|
|
770
|
+
}
|
|
771
|
+
var MessageItem = import_react3.default.memo(function MessageItem2({
|
|
772
|
+
message
|
|
773
|
+
}) {
|
|
774
|
+
if ((0, import_agent_core2.isToolMessage)(message)) {
|
|
775
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
|
|
776
|
+
}
|
|
777
|
+
const content = message.content ?? "";
|
|
778
|
+
const isInterrupted = message.state === "interrupted";
|
|
779
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }) }),
|
|
781
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
782
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: (0, import_agent_core2.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
|
|
783
|
+
] });
|
|
784
|
+
});
|
|
785
|
+
function ToolSummaryEntry({ entry }) {
|
|
786
|
+
const data = entry.data;
|
|
787
|
+
const lines = data?.summary?.split("\n") ?? [];
|
|
788
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
789
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
|
|
790
|
+
"Tool:",
|
|
791
|
+
" "
|
|
792
|
+
] }) }),
|
|
793
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
794
|
+
lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
|
|
795
|
+
" ",
|
|
796
|
+
line
|
|
797
|
+
] }, i))
|
|
798
|
+
] });
|
|
799
|
+
}
|
|
800
|
+
function EventEntry({ entry }) {
|
|
801
|
+
const eventData = entry.data;
|
|
802
|
+
const eventMessage = typeof eventData?.message === "string" ? eventData.message : typeof eventData?.content === "string" ? eventData.content : entry.type;
|
|
803
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
804
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
|
|
805
|
+
"System:",
|
|
806
|
+
" "
|
|
807
|
+
] }) }),
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: eventMessage }) })
|
|
270
810
|
] });
|
|
271
811
|
}
|
|
272
|
-
function
|
|
273
|
-
|
|
812
|
+
function EntryItem({ entry }) {
|
|
813
|
+
if (entry.category === "chat") {
|
|
814
|
+
const message = entry.data;
|
|
815
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageItem, { message });
|
|
816
|
+
}
|
|
817
|
+
if (entry.type === "tool-summary") {
|
|
818
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolSummaryEntry, { entry });
|
|
819
|
+
}
|
|
820
|
+
if (entry.type === "tool-start" || entry.type === "tool-end") {
|
|
821
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
|
|
822
|
+
}
|
|
823
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EventEntry, { entry });
|
|
824
|
+
}
|
|
825
|
+
function MessageList({ history }) {
|
|
826
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: history.map((entry) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(EntryItem, { entry }, entry.id)) });
|
|
274
827
|
}
|
|
275
828
|
|
|
276
829
|
// src/ui/StatusBar.tsx
|
|
277
|
-
var
|
|
278
|
-
var
|
|
830
|
+
var import_ink3 = require("ink");
|
|
831
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
832
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
279
833
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
280
834
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
281
835
|
function getContextColor(percentage) {
|
|
@@ -291,11 +845,12 @@ function StatusBar({
|
|
|
291
845
|
isThinking,
|
|
292
846
|
contextPercentage,
|
|
293
847
|
contextUsedTokens,
|
|
294
|
-
contextMaxTokens
|
|
848
|
+
contextMaxTokens,
|
|
849
|
+
sessionName
|
|
295
850
|
}) {
|
|
296
851
|
const contextColor = getContextColor(contextPercentage);
|
|
297
|
-
return /* @__PURE__ */ (0,
|
|
298
|
-
|
|
852
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
853
|
+
import_ink3.Box,
|
|
299
854
|
{
|
|
300
855
|
borderStyle: "single",
|
|
301
856
|
borderColor: "gray",
|
|
@@ -303,26 +858,30 @@ function StatusBar({
|
|
|
303
858
|
paddingRight: 1,
|
|
304
859
|
justifyContent: "space-between",
|
|
305
860
|
children: [
|
|
306
|
-
/* @__PURE__ */ (0,
|
|
307
|
-
/* @__PURE__ */ (0,
|
|
861
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
862
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", bold: true, children: "Mode:" }),
|
|
308
863
|
" ",
|
|
309
|
-
/* @__PURE__ */ (0,
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
|
|
865
|
+
sessionName && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
866
|
+
" | ",
|
|
867
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "magenta", children: sessionName })
|
|
868
|
+
] }),
|
|
310
869
|
" | ",
|
|
311
|
-
/* @__PURE__ */ (0,
|
|
870
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
|
|
312
871
|
" | ",
|
|
313
|
-
/* @__PURE__ */ (0,
|
|
872
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
|
|
314
873
|
"Context: ",
|
|
315
874
|
Math.round(contextPercentage),
|
|
316
875
|
"% (",
|
|
317
|
-
(
|
|
318
|
-
"
|
|
319
|
-
(
|
|
320
|
-
"
|
|
876
|
+
(0, import_agent_core3.formatTokenCount)(contextUsedTokens),
|
|
877
|
+
"/",
|
|
878
|
+
(0, import_agent_core3.formatTokenCount)(contextMaxTokens),
|
|
879
|
+
")"
|
|
321
880
|
] })
|
|
322
881
|
] }),
|
|
323
|
-
/* @__PURE__ */ (0,
|
|
324
|
-
isThinking && /* @__PURE__ */ (0,
|
|
325
|
-
/* @__PURE__ */ (0,
|
|
882
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
|
|
883
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: "Thinking... " }),
|
|
884
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { dimColor: true, children: [
|
|
326
885
|
"msgs: ",
|
|
327
886
|
messageCount
|
|
328
887
|
] })
|
|
@@ -333,146 +892,222 @@ function StatusBar({
|
|
|
333
892
|
}
|
|
334
893
|
|
|
335
894
|
// src/ui/InputArea.tsx
|
|
336
|
-
var
|
|
337
|
-
var
|
|
895
|
+
var import_react6 = __toESM(require("react"), 1);
|
|
896
|
+
var import_ink7 = require("ink");
|
|
338
897
|
|
|
339
898
|
// src/ui/CjkTextInput.tsx
|
|
340
|
-
var
|
|
341
|
-
var
|
|
899
|
+
var import_react4 = require("react");
|
|
900
|
+
var import_ink4 = require("ink");
|
|
901
|
+
var import_chalk = __toESM(require("chalk"), 1);
|
|
342
902
|
var import_string_width = __toESM(require("string-width"), 1);
|
|
343
|
-
var
|
|
344
|
-
var
|
|
903
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
904
|
+
var PASTE_START = "[200~";
|
|
905
|
+
var PASTE_END = "[201~";
|
|
906
|
+
function filterPrintable(input) {
|
|
907
|
+
if (!input || input.length === 0) return "";
|
|
908
|
+
return input.replace(/[\x00-\x1f\x7f]/g, "");
|
|
909
|
+
}
|
|
910
|
+
function insertAtCursor(value, cursor, input) {
|
|
911
|
+
const next = value.slice(0, cursor) + input + value.slice(cursor);
|
|
912
|
+
return { value: next, cursor: cursor + input.length };
|
|
913
|
+
}
|
|
914
|
+
function displayOffset(chars, charIndex, width) {
|
|
915
|
+
let offset = 0;
|
|
916
|
+
for (let i = 0; i < charIndex && i < chars.length; i++) {
|
|
917
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
918
|
+
const col = offset % width;
|
|
919
|
+
if (col + w > width) offset += width - col;
|
|
920
|
+
offset += w;
|
|
921
|
+
}
|
|
922
|
+
return offset;
|
|
923
|
+
}
|
|
924
|
+
function charIndexAtDisplayOffset(chars, target, width) {
|
|
925
|
+
let offset = 0;
|
|
926
|
+
for (let i = 0; i < chars.length; i++) {
|
|
927
|
+
if (offset >= target) return i;
|
|
928
|
+
const w = (0, import_string_width.default)(chars[i]);
|
|
929
|
+
const col = offset % width;
|
|
930
|
+
if (col + w > width) offset += width - col;
|
|
931
|
+
offset += w;
|
|
932
|
+
}
|
|
933
|
+
return chars.length;
|
|
934
|
+
}
|
|
345
935
|
function CjkTextInput({
|
|
346
936
|
value,
|
|
347
937
|
onChange,
|
|
348
938
|
onSubmit,
|
|
939
|
+
onPaste,
|
|
349
940
|
placeholder = "",
|
|
350
941
|
focus = true,
|
|
351
|
-
showCursor = true
|
|
942
|
+
showCursor = true,
|
|
943
|
+
availableWidth
|
|
352
944
|
}) {
|
|
353
|
-
const valueRef = (0,
|
|
354
|
-
const cursorRef = (0,
|
|
355
|
-
const [, forceRender] = (0,
|
|
356
|
-
const
|
|
945
|
+
const valueRef = (0, import_react4.useRef)(value);
|
|
946
|
+
const cursorRef = (0, import_react4.useRef)(value.length);
|
|
947
|
+
const [, forceRender] = (0, import_react4.useState)(0);
|
|
948
|
+
const isPastingRef = (0, import_react4.useRef)(false);
|
|
949
|
+
const pasteBufferRef = (0, import_react4.useRef)("");
|
|
357
950
|
if (value !== valueRef.current) {
|
|
358
951
|
valueRef.current = value;
|
|
359
|
-
|
|
360
|
-
cursorRef.current = value.length;
|
|
361
|
-
}
|
|
952
|
+
cursorRef.current = value.length;
|
|
362
953
|
}
|
|
363
|
-
(0,
|
|
954
|
+
(0, import_ink4.useInput)(
|
|
364
955
|
(input, key) => {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (cursorRef.current > 0) {
|
|
374
|
-
cursorRef.current -= 1;
|
|
375
|
-
forceRender((n) => n + 1);
|
|
956
|
+
try {
|
|
957
|
+
if (input === PASTE_START || input.startsWith(PASTE_START)) {
|
|
958
|
+
isPastingRef.current = true;
|
|
959
|
+
const afterMarker = input.slice(PASTE_START.length);
|
|
960
|
+
if (afterMarker.length > 0) {
|
|
961
|
+
pasteBufferRef.current += afterMarker;
|
|
962
|
+
}
|
|
963
|
+
return;
|
|
376
964
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
965
|
+
if (isPastingRef.current) {
|
|
966
|
+
if (input === PASTE_END || input.includes(PASTE_END)) {
|
|
967
|
+
const beforeMarker = input.split(PASTE_END)[0] ?? "";
|
|
968
|
+
pasteBufferRef.current += beforeMarker;
|
|
969
|
+
const text = pasteBufferRef.current.replace(/\r\n?/g, "\n");
|
|
970
|
+
pasteBufferRef.current = "";
|
|
971
|
+
isPastingRef.current = false;
|
|
972
|
+
if (text.length > 0) {
|
|
973
|
+
if (text.includes("\n") && onPaste) {
|
|
974
|
+
onPaste(text);
|
|
975
|
+
} else {
|
|
976
|
+
const printable2 = filterPrintable(text);
|
|
977
|
+
if (printable2.length > 0) {
|
|
978
|
+
const result2 = insertAtCursor(valueRef.current, cursorRef.current, printable2);
|
|
979
|
+
cursorRef.current = result2.cursor;
|
|
980
|
+
valueRef.current = result2.value;
|
|
981
|
+
onChange(result2.value);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
} else {
|
|
986
|
+
pasteBufferRef.current += input;
|
|
987
|
+
}
|
|
988
|
+
return;
|
|
383
989
|
}
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if (key.backspace || key.delete) {
|
|
387
|
-
if (cursorRef.current > 0) {
|
|
388
|
-
const v2 = valueRef.current;
|
|
389
|
-
const next2 = v2.slice(0, cursorRef.current - 1) + v2.slice(cursorRef.current);
|
|
390
|
-
cursorRef.current -= 1;
|
|
391
|
-
valueRef.current = next2;
|
|
392
|
-
onChange(next2);
|
|
990
|
+
if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
|
|
991
|
+
return;
|
|
393
992
|
}
|
|
394
|
-
|
|
993
|
+
if (key.upArrow || key.downArrow) {
|
|
994
|
+
if (availableWidth && availableWidth > 0) {
|
|
995
|
+
const chars = [...valueRef.current];
|
|
996
|
+
const offset = displayOffset(chars, cursorRef.current, availableWidth);
|
|
997
|
+
const target = key.upArrow ? offset - availableWidth : offset + availableWidth;
|
|
998
|
+
if (target >= 0) {
|
|
999
|
+
const newCursor = charIndexAtDisplayOffset(chars, target, availableWidth);
|
|
1000
|
+
if (newCursor !== cursorRef.current) {
|
|
1001
|
+
cursorRef.current = newCursor;
|
|
1002
|
+
forceRender((n) => n + 1);
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
if (key.return) {
|
|
1009
|
+
onSubmit?.(valueRef.current);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
|
|
1013
|
+
onPaste(input.replace(/\r\n?/g, "\n"));
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
if (key.leftArrow) {
|
|
1017
|
+
if (cursorRef.current > 0) {
|
|
1018
|
+
cursorRef.current -= 1;
|
|
1019
|
+
forceRender((n) => n + 1);
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (key.rightArrow) {
|
|
1024
|
+
if (cursorRef.current < valueRef.current.length) {
|
|
1025
|
+
cursorRef.current += 1;
|
|
1026
|
+
forceRender((n) => n + 1);
|
|
1027
|
+
}
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
if (key.backspace || key.delete) {
|
|
1031
|
+
if (cursorRef.current > 0) {
|
|
1032
|
+
const v = valueRef.current;
|
|
1033
|
+
const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
|
|
1034
|
+
cursorRef.current -= 1;
|
|
1035
|
+
valueRef.current = next;
|
|
1036
|
+
onChange(next);
|
|
1037
|
+
}
|
|
1038
|
+
return;
|
|
1039
|
+
}
|
|
1040
|
+
const printable = filterPrintable(input);
|
|
1041
|
+
if (printable.length === 0) return;
|
|
1042
|
+
const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
|
|
1043
|
+
cursorRef.current = result.cursor;
|
|
1044
|
+
valueRef.current = result.value;
|
|
1045
|
+
onChange(result.value);
|
|
1046
|
+
} catch {
|
|
395
1047
|
}
|
|
396
|
-
const v = valueRef.current;
|
|
397
|
-
const c = cursorRef.current;
|
|
398
|
-
const next = v.slice(0, c) + input + v.slice(c);
|
|
399
|
-
cursorRef.current = c + input.length;
|
|
400
|
-
valueRef.current = next;
|
|
401
|
-
onChange(next);
|
|
402
1048
|
},
|
|
403
1049
|
{ isActive: focus }
|
|
404
1050
|
);
|
|
405
|
-
|
|
406
|
-
const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
|
|
407
|
-
const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
|
|
408
|
-
setCursorPosition({ x: cursorX, y: 0 });
|
|
409
|
-
}
|
|
410
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
1051
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
|
|
411
1052
|
}
|
|
412
1053
|
function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
|
|
413
1054
|
if (!showCursor) {
|
|
414
|
-
return value.length > 0 ? value : placeholder ?
|
|
1055
|
+
return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
|
|
415
1056
|
}
|
|
416
1057
|
if (value.length === 0) {
|
|
417
1058
|
if (placeholder.length > 0) {
|
|
418
|
-
return
|
|
1059
|
+
return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
|
|
419
1060
|
}
|
|
420
|
-
return
|
|
1061
|
+
return import_chalk.default.inverse(" ");
|
|
421
1062
|
}
|
|
422
1063
|
const chars = [...value];
|
|
423
1064
|
let rendered = "";
|
|
424
1065
|
for (let i = 0; i < chars.length; i++) {
|
|
425
1066
|
const char = chars[i] ?? "";
|
|
426
|
-
rendered += i === cursorOffset ?
|
|
1067
|
+
rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
|
|
427
1068
|
}
|
|
428
1069
|
if (cursorOffset >= chars.length) {
|
|
429
|
-
rendered +=
|
|
1070
|
+
rendered += import_chalk.default.inverse(" ");
|
|
430
1071
|
}
|
|
431
1072
|
return rendered;
|
|
432
1073
|
}
|
|
433
1074
|
|
|
434
1075
|
// src/ui/WaveText.tsx
|
|
435
|
-
var
|
|
436
|
-
var
|
|
437
|
-
var
|
|
1076
|
+
var import_react5 = require("react");
|
|
1077
|
+
var import_ink5 = require("ink");
|
|
1078
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
438
1079
|
var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
|
|
439
1080
|
var INTERVAL_MS = 400;
|
|
440
1081
|
var CHARS_PER_GROUP = 4;
|
|
441
1082
|
function WaveText({ text }) {
|
|
442
|
-
const [tick, setTick] = (0,
|
|
443
|
-
(0,
|
|
1083
|
+
const [tick, setTick] = (0, import_react5.useState)(0);
|
|
1084
|
+
(0, import_react5.useEffect)(() => {
|
|
444
1085
|
const timer = setInterval(() => {
|
|
445
1086
|
setTick((prev) => prev + 1);
|
|
446
1087
|
}, INTERVAL_MS);
|
|
447
1088
|
return () => clearInterval(timer);
|
|
448
1089
|
}, []);
|
|
449
1090
|
const chars = [...text];
|
|
450
|
-
return /* @__PURE__ */ (0,
|
|
1091
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
|
|
451
1092
|
const group = Math.floor(i / CHARS_PER_GROUP);
|
|
452
1093
|
const colorIndex = (tick + group) % WAVE_COLORS.length;
|
|
453
|
-
return /* @__PURE__ */ (0,
|
|
1094
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
|
|
454
1095
|
}) });
|
|
455
1096
|
}
|
|
456
1097
|
|
|
457
1098
|
// src/ui/SlashAutocomplete.tsx
|
|
458
|
-
var
|
|
459
|
-
var
|
|
1099
|
+
var import_ink6 = require("ink");
|
|
1100
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
460
1101
|
var MAX_VISIBLE = 8;
|
|
461
1102
|
function CommandRow(props) {
|
|
462
1103
|
const { cmd, isSelected, showSlash } = props;
|
|
463
|
-
const prefix = showSlash ? "/" : "";
|
|
464
1104
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
465
1105
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
466
1106
|
const dimmed = !isSelected;
|
|
467
|
-
return /* @__PURE__ */ (0,
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
cmd.name
|
|
472
|
-
] }),
|
|
473
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
|
|
474
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
475
|
-
] });
|
|
1107
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: nameColor, dimColor: dimmed, children: [
|
|
1108
|
+
indicator,
|
|
1109
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
1110
|
+
] }) });
|
|
476
1111
|
}
|
|
477
1112
|
function SlashAutocomplete({
|
|
478
1113
|
commands,
|
|
@@ -483,7 +1118,7 @@ function SlashAutocomplete({
|
|
|
483
1118
|
if (!visible || commands.length === 0) return null;
|
|
484
1119
|
const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
|
|
485
1120
|
const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
|
|
486
|
-
return /* @__PURE__ */ (0,
|
|
1121
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
487
1122
|
CommandRow,
|
|
488
1123
|
{
|
|
489
1124
|
cmd,
|
|
@@ -500,8 +1135,14 @@ function computeScrollOffset(selectedIndex, total) {
|
|
|
500
1135
|
return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
|
|
501
1136
|
}
|
|
502
1137
|
|
|
1138
|
+
// src/utils/paste-labels.ts
|
|
1139
|
+
var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
|
|
1140
|
+
function expandPasteLabels(text, store) {
|
|
1141
|
+
return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
|
|
1142
|
+
}
|
|
1143
|
+
|
|
503
1144
|
// src/ui/InputArea.tsx
|
|
504
|
-
var
|
|
1145
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
505
1146
|
function parseSlashInput(value) {
|
|
506
1147
|
if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
|
|
507
1148
|
const afterSlash = value.slice(1);
|
|
@@ -512,16 +1153,16 @@ function parseSlashInput(value) {
|
|
|
512
1153
|
return { isSlash: true, parentCommand: parent, filter: rest };
|
|
513
1154
|
}
|
|
514
1155
|
function useAutocomplete(value, registry) {
|
|
515
|
-
const [selectedIndex, setSelectedIndex] = (0,
|
|
516
|
-
const [dismissed, setDismissed] = (0,
|
|
517
|
-
const prevValueRef =
|
|
1156
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react6.useState)(0);
|
|
1157
|
+
const [dismissed, setDismissed] = (0, import_react6.useState)(false);
|
|
1158
|
+
const prevValueRef = import_react6.default.useRef(value);
|
|
518
1159
|
if (prevValueRef.current !== value) {
|
|
519
1160
|
prevValueRef.current = value;
|
|
520
1161
|
if (dismissed) setDismissed(false);
|
|
521
1162
|
}
|
|
522
1163
|
const parsed = parseSlashInput(value);
|
|
523
1164
|
const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
|
|
524
|
-
const filteredCommands = (0,
|
|
1165
|
+
const filteredCommands = (0, import_react6.useMemo)(() => {
|
|
525
1166
|
if (!registry || !parsed.isSlash || dismissed) return [];
|
|
526
1167
|
if (isSubcommandMode) {
|
|
527
1168
|
const subs = registry.getSubcommands(parsed.parentCommand);
|
|
@@ -554,8 +1195,25 @@ function useAutocomplete(value, registry) {
|
|
|
554
1195
|
}
|
|
555
1196
|
};
|
|
556
1197
|
}
|
|
557
|
-
|
|
558
|
-
|
|
1198
|
+
var BORDER_HORIZONTAL = 2;
|
|
1199
|
+
var PADDING_LEFT = 1;
|
|
1200
|
+
var PROMPT_WIDTH = 2;
|
|
1201
|
+
var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
|
|
1202
|
+
function InputArea({
|
|
1203
|
+
onSubmit,
|
|
1204
|
+
onCancelQueue,
|
|
1205
|
+
isDisabled,
|
|
1206
|
+
isAborting,
|
|
1207
|
+
pendingPrompt,
|
|
1208
|
+
registry,
|
|
1209
|
+
sessionName
|
|
1210
|
+
}) {
|
|
1211
|
+
const [value, setValue] = (0, import_react6.useState)("");
|
|
1212
|
+
const pasteStore = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
|
|
1213
|
+
const { stdout } = (0, import_ink7.useStdout)();
|
|
1214
|
+
const terminalColumns = stdout?.columns ?? 80;
|
|
1215
|
+
const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
|
|
1216
|
+
const pasteIdRef = (0, import_react6.useRef)(0);
|
|
559
1217
|
const {
|
|
560
1218
|
showPopup,
|
|
561
1219
|
filteredCommands,
|
|
@@ -564,20 +1222,31 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
564
1222
|
isSubcommandMode,
|
|
565
1223
|
setShowPopup
|
|
566
1224
|
} = useAutocomplete(value, registry);
|
|
567
|
-
const
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
1225
|
+
const handlePaste = (0, import_react6.useCallback)((text) => {
|
|
1226
|
+
pasteIdRef.current += 1;
|
|
1227
|
+
const id = pasteIdRef.current;
|
|
1228
|
+
pasteStore.current.set(id, text);
|
|
1229
|
+
const lineCount = text.split("\n").length;
|
|
1230
|
+
const label = `[Pasted text #${id} +${lineCount} lines]`;
|
|
1231
|
+
setValue((prev) => prev ? `${prev} ${label}` : label);
|
|
1232
|
+
}, []);
|
|
1233
|
+
const tabCompleteCommand = (0, import_react6.useCallback)(
|
|
1234
|
+
(cmd) => {
|
|
1235
|
+
const parsed = parseSlashInput(value);
|
|
1236
|
+
if (parsed.parentCommand) {
|
|
1237
|
+
setValue(`/${parsed.parentCommand} ${cmd.name} `);
|
|
573
1238
|
return;
|
|
574
1239
|
}
|
|
575
|
-
|
|
576
|
-
|
|
1240
|
+
if (cmd.subcommands && cmd.subcommands.length > 0) {
|
|
1241
|
+
setValue(`/${cmd.name} `);
|
|
1242
|
+
setSelectedIndex(0);
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
setValue(`/${cmd.name} `);
|
|
577
1246
|
},
|
|
578
|
-
[
|
|
1247
|
+
[value, setSelectedIndex]
|
|
579
1248
|
);
|
|
580
|
-
const
|
|
1249
|
+
const enterSelectCommand = (0, import_react6.useCallback)(
|
|
581
1250
|
(cmd) => {
|
|
582
1251
|
const parsed = parseSlashInput(value);
|
|
583
1252
|
if (parsed.parentCommand) {
|
|
@@ -596,7 +1265,23 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
596
1265
|
},
|
|
597
1266
|
[value, onSubmit, setSelectedIndex]
|
|
598
1267
|
);
|
|
599
|
-
(0,
|
|
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
|
+
);
|
|
1284
|
+
(0, import_ink7.useInput)(
|
|
600
1285
|
(_input, key) => {
|
|
601
1286
|
if (!showPopup) return;
|
|
602
1287
|
if (key.upArrow) {
|
|
@@ -607,13 +1292,32 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
607
1292
|
setShowPopup(false);
|
|
608
1293
|
} else if (key.tab) {
|
|
609
1294
|
const cmd = filteredCommands[selectedIndex];
|
|
610
|
-
if (cmd)
|
|
1295
|
+
if (cmd) tabCompleteCommand(cmd);
|
|
611
1296
|
}
|
|
612
1297
|
},
|
|
613
1298
|
{ isActive: showPopup && !isDisabled }
|
|
614
1299
|
);
|
|
615
|
-
|
|
616
|
-
|
|
1300
|
+
(0, import_ink7.useInput)(
|
|
1301
|
+
(_input, key) => {
|
|
1302
|
+
if ((key.backspace || key.delete) && pendingPrompt) {
|
|
1303
|
+
onCancelQueue?.();
|
|
1304
|
+
}
|
|
1305
|
+
},
|
|
1306
|
+
{ isActive: !!pendingPrompt }
|
|
1307
|
+
);
|
|
1308
|
+
const borderColor = isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green";
|
|
1309
|
+
const innerWidth = Math.max(1, terminalColumns - BORDER_HORIZONTAL);
|
|
1310
|
+
const topBorder = (() => {
|
|
1311
|
+
if (sessionName) {
|
|
1312
|
+
const label = ` "${sessionName}" `;
|
|
1313
|
+
const rightPad = 2;
|
|
1314
|
+
const leftLen = Math.max(0, innerWidth - label.length - rightPad);
|
|
1315
|
+
return { left: "\u250C" + "\u2500".repeat(leftLen), label, right: "\u2500".repeat(rightPad) + "\u2510" };
|
|
1316
|
+
}
|
|
1317
|
+
return { left: "\u250C" + "\u2500".repeat(innerWidth), label: "", right: "\u2510" };
|
|
1318
|
+
})();
|
|
1319
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
|
|
1320
|
+
showPopup && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
617
1321
|
SlashAutocomplete,
|
|
618
1322
|
{
|
|
619
1323
|
commands: filteredCommands,
|
|
@@ -622,41 +1326,97 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
622
1326
|
isSubcommandMode
|
|
623
1327
|
}
|
|
624
1328
|
),
|
|
625
|
-
/* @__PURE__ */ (0,
|
|
626
|
-
|
|
627
|
-
/* @__PURE__ */ (0,
|
|
1329
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: borderColor, children: [
|
|
1330
|
+
topBorder.left,
|
|
1331
|
+
topBorder.label ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { backgroundColor: borderColor, color: "black", bold: true, children: topBorder.label }) : null,
|
|
1332
|
+
topBorder.right
|
|
1333
|
+
] }),
|
|
1334
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { borderStyle: "single", borderTop: false, borderColor, paddingLeft: 1, children: isAborting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: "cyan", children: [
|
|
1335
|
+
" ",
|
|
1336
|
+
"Queued: ",
|
|
1337
|
+
pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
|
|
1338
|
+
" ",
|
|
1339
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: "(Backspace to cancel)" })
|
|
1340
|
+
] }) : isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
|
|
1341
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "green", bold: true, children: "> " }),
|
|
1342
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
628
1343
|
CjkTextInput,
|
|
629
1344
|
{
|
|
630
1345
|
value,
|
|
631
1346
|
onChange: setValue,
|
|
632
1347
|
onSubmit: handleSubmit,
|
|
633
|
-
|
|
1348
|
+
onPaste: handlePaste,
|
|
1349
|
+
placeholder: "Type a message or /help",
|
|
1350
|
+
availableWidth
|
|
634
1351
|
}
|
|
635
1352
|
)
|
|
636
1353
|
] }) })
|
|
637
1354
|
] });
|
|
638
1355
|
}
|
|
639
1356
|
|
|
1357
|
+
// src/ui/ConfirmPrompt.tsx
|
|
1358
|
+
var import_react7 = require("react");
|
|
1359
|
+
var import_ink8 = require("ink");
|
|
1360
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1361
|
+
function ConfirmPrompt({
|
|
1362
|
+
message,
|
|
1363
|
+
options = ["Yes", "No"],
|
|
1364
|
+
onSelect
|
|
1365
|
+
}) {
|
|
1366
|
+
const [selected, setSelected] = (0, import_react7.useState)(0);
|
|
1367
|
+
const resolvedRef = (0, import_react7.useRef)(false);
|
|
1368
|
+
const doSelect = (0, import_react7.useCallback)(
|
|
1369
|
+
(index) => {
|
|
1370
|
+
if (resolvedRef.current) return;
|
|
1371
|
+
resolvedRef.current = true;
|
|
1372
|
+
onSelect(index);
|
|
1373
|
+
},
|
|
1374
|
+
[onSelect]
|
|
1375
|
+
);
|
|
1376
|
+
(0, import_ink8.useInput)((input, key) => {
|
|
1377
|
+
if (resolvedRef.current) return;
|
|
1378
|
+
if (key.leftArrow || key.upArrow) {
|
|
1379
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
1380
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
1381
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
1382
|
+
} else if (key.return) {
|
|
1383
|
+
doSelect(selected);
|
|
1384
|
+
} else if (input === "y" && options.length === 2) {
|
|
1385
|
+
doSelect(0);
|
|
1386
|
+
} else if (input === "n" && options.length === 2) {
|
|
1387
|
+
doSelect(1);
|
|
1388
|
+
}
|
|
1389
|
+
});
|
|
1390
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1391
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: message }),
|
|
1392
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
1393
|
+
i === selected ? "> " : " ",
|
|
1394
|
+
opt
|
|
1395
|
+
] }) }, opt)) }),
|
|
1396
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
1397
|
+
] });
|
|
1398
|
+
}
|
|
1399
|
+
|
|
640
1400
|
// src/ui/PermissionPrompt.tsx
|
|
641
|
-
var
|
|
642
|
-
var
|
|
643
|
-
var
|
|
1401
|
+
var import_react8 = __toESM(require("react"), 1);
|
|
1402
|
+
var import_ink9 = require("ink");
|
|
1403
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
644
1404
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
645
|
-
function
|
|
1405
|
+
function formatArgs(args) {
|
|
646
1406
|
const entries = Object.entries(args);
|
|
647
1407
|
if (entries.length === 0) return "(no arguments)";
|
|
648
1408
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
649
1409
|
}
|
|
650
1410
|
function PermissionPrompt({ request }) {
|
|
651
|
-
const [selected, setSelected] =
|
|
652
|
-
const resolvedRef =
|
|
653
|
-
const prevRequestRef =
|
|
1411
|
+
const [selected, setSelected] = import_react8.default.useState(0);
|
|
1412
|
+
const resolvedRef = import_react8.default.useRef(false);
|
|
1413
|
+
const prevRequestRef = import_react8.default.useRef(request);
|
|
654
1414
|
if (prevRequestRef.current !== request) {
|
|
655
1415
|
prevRequestRef.current = request;
|
|
656
1416
|
resolvedRef.current = false;
|
|
657
1417
|
setSelected(0);
|
|
658
1418
|
}
|
|
659
|
-
const doResolve =
|
|
1419
|
+
const doResolve = import_react8.default.useCallback(
|
|
660
1420
|
(index) => {
|
|
661
1421
|
if (resolvedRef.current) return;
|
|
662
1422
|
resolvedRef.current = true;
|
|
@@ -666,7 +1426,7 @@ function PermissionPrompt({ request }) {
|
|
|
666
1426
|
},
|
|
667
1427
|
[request]
|
|
668
1428
|
);
|
|
669
|
-
(0,
|
|
1429
|
+
(0, import_ink9.useInput)((input, key) => {
|
|
670
1430
|
if (resolvedRef.current) return;
|
|
671
1431
|
if (key.upArrow || key.leftArrow) {
|
|
672
1432
|
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
@@ -682,364 +1442,857 @@ function PermissionPrompt({ request }) {
|
|
|
682
1442
|
doResolve(2);
|
|
683
1443
|
}
|
|
684
1444
|
});
|
|
685
|
-
return /* @__PURE__ */ (0,
|
|
686
|
-
/* @__PURE__ */ (0,
|
|
687
|
-
/* @__PURE__ */ (0,
|
|
1445
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1446
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
1447
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
|
|
688
1448
|
"Tool:",
|
|
689
1449
|
" ",
|
|
690
|
-
/* @__PURE__ */ (0,
|
|
1450
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
691
1451
|
] }),
|
|
692
|
-
/* @__PURE__ */ (0,
|
|
1452
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
|
|
693
1453
|
" ",
|
|
694
|
-
|
|
1454
|
+
formatArgs(request.toolArgs)
|
|
695
1455
|
] }),
|
|
696
|
-
/* @__PURE__ */ (0,
|
|
1456
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
697
1457
|
i === selected ? "> " : " ",
|
|
698
1458
|
opt
|
|
699
1459
|
] }) }, opt)) }),
|
|
700
|
-
/* @__PURE__ */ (0,
|
|
1460
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
701
1461
|
] });
|
|
702
1462
|
}
|
|
703
1463
|
|
|
704
|
-
// src/ui/
|
|
705
|
-
var
|
|
706
|
-
var
|
|
707
|
-
function
|
|
708
|
-
|
|
709
|
-
return
|
|
1464
|
+
// src/ui/StreamingIndicator.tsx
|
|
1465
|
+
var import_ink10 = require("ink");
|
|
1466
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1467
|
+
function getToolStyle(t) {
|
|
1468
|
+
if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
|
|
1469
|
+
if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
|
|
1470
|
+
if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
|
|
1471
|
+
return { color: "green", icon: "\u2713", strikethrough: false };
|
|
710
1472
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
writeMarkdown: () => {
|
|
717
|
-
},
|
|
718
|
-
writeError: () => {
|
|
719
|
-
},
|
|
720
|
-
prompt: () => Promise.resolve(""),
|
|
721
|
-
select: () => Promise.resolve(0),
|
|
722
|
-
spinner: () => ({ stop: () => {
|
|
723
|
-
}, update: () => {
|
|
724
|
-
} })
|
|
725
|
-
};
|
|
726
|
-
function useSession(props) {
|
|
727
|
-
const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
|
|
728
|
-
const [streamingText, setStreamingText] = (0, import_react5.useState)("");
|
|
729
|
-
const permissionQueueRef = (0, import_react5.useRef)([]);
|
|
730
|
-
const processingRef = (0, import_react5.useRef)(false);
|
|
731
|
-
const processNextPermission = (0, import_react5.useCallback)(() => {
|
|
732
|
-
if (processingRef.current) return;
|
|
733
|
-
const next = permissionQueueRef.current[0];
|
|
734
|
-
if (!next) {
|
|
735
|
-
setPermissionRequest(null);
|
|
736
|
-
return;
|
|
737
|
-
}
|
|
738
|
-
processingRef.current = true;
|
|
739
|
-
setPermissionRequest({
|
|
740
|
-
toolName: next.toolName,
|
|
741
|
-
toolArgs: next.toolArgs,
|
|
742
|
-
resolve: (result) => {
|
|
743
|
-
permissionQueueRef.current.shift();
|
|
744
|
-
processingRef.current = false;
|
|
745
|
-
setPermissionRequest(null);
|
|
746
|
-
next.resolve(result);
|
|
747
|
-
setTimeout(() => processNextPermission(), 0);
|
|
748
|
-
}
|
|
749
|
-
});
|
|
750
|
-
}, []);
|
|
751
|
-
const sessionRef = (0, import_react5.useRef)(null);
|
|
752
|
-
if (sessionRef.current === null) {
|
|
753
|
-
const permissionHandler = (toolName, toolArgs) => {
|
|
754
|
-
return new Promise((resolve) => {
|
|
755
|
-
permissionQueueRef.current.push({ toolName, toolArgs, resolve });
|
|
756
|
-
processNextPermission();
|
|
757
|
-
});
|
|
758
|
-
};
|
|
759
|
-
const onTextDelta = (delta) => {
|
|
760
|
-
setStreamingText((prev) => prev + delta);
|
|
761
|
-
};
|
|
762
|
-
const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
|
|
763
|
-
sessionRef.current = (0, import_agent_sdk.createSession)({
|
|
764
|
-
config: props.config,
|
|
765
|
-
context: props.context,
|
|
766
|
-
terminal: NOOP_TERMINAL,
|
|
767
|
-
sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
|
|
768
|
-
projectInfo: props.projectInfo,
|
|
769
|
-
sessionStore: props.sessionStore,
|
|
770
|
-
permissionMode: props.permissionMode,
|
|
771
|
-
maxTurns: props.maxTurns,
|
|
772
|
-
permissionHandler,
|
|
773
|
-
onTextDelta
|
|
774
|
-
});
|
|
775
|
-
}
|
|
776
|
-
const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
|
|
777
|
-
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
778
|
-
}
|
|
779
|
-
function useMessages() {
|
|
780
|
-
const [messages, setMessages] = (0, import_react5.useState)([]);
|
|
781
|
-
const addMessage = (0, import_react5.useCallback)((msg) => {
|
|
782
|
-
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
783
|
-
}, []);
|
|
784
|
-
return { messages, setMessages, addMessage };
|
|
785
|
-
}
|
|
786
|
-
var HELP_TEXT = [
|
|
787
|
-
"Available commands:",
|
|
788
|
-
" /help \u2014 Show this help",
|
|
789
|
-
" /clear \u2014 Clear conversation",
|
|
790
|
-
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
791
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
792
|
-
" /cost \u2014 Show session info",
|
|
793
|
-
" /exit \u2014 Exit CLI"
|
|
794
|
-
].join("\n");
|
|
795
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
796
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
797
|
-
if (!arg) {
|
|
798
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
799
|
-
} else if (validModes.includes(arg)) {
|
|
800
|
-
session.setPermissionMode(arg);
|
|
801
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
802
|
-
} else {
|
|
803
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
1473
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
1474
|
+
const hasTools = activeTools.length > 0;
|
|
1475
|
+
const hasText = text.length > 0;
|
|
1476
|
+
if (!hasTools && !hasText) {
|
|
1477
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, {});
|
|
804
1478
|
}
|
|
805
|
-
return
|
|
1479
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
1480
|
+
hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1481
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "white", bold: true, children: "Tools:" }),
|
|
1482
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
1483
|
+
activeTools.map((t, i) => {
|
|
1484
|
+
const { color, icon, strikethrough } = getToolStyle(t);
|
|
1485
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
|
|
1486
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color, strikethrough, children: [
|
|
1487
|
+
" ",
|
|
1488
|
+
icon,
|
|
1489
|
+
" ",
|
|
1490
|
+
t.toolName,
|
|
1491
|
+
"(",
|
|
1492
|
+
t.firstArg,
|
|
1493
|
+
")"
|
|
1494
|
+
] }),
|
|
1495
|
+
t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
|
|
1496
|
+
] }, `${t.toolName}-${i}`);
|
|
1497
|
+
})
|
|
1498
|
+
] }),
|
|
1499
|
+
hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
|
|
1500
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: "Robota:" }),
|
|
1501
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
|
|
1502
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
1503
|
+
] })
|
|
1504
|
+
] });
|
|
806
1505
|
}
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
1506
|
+
|
|
1507
|
+
// src/ui/PluginTUI.tsx
|
|
1508
|
+
var import_react11 = require("react");
|
|
1509
|
+
|
|
1510
|
+
// src/ui/MenuSelect.tsx
|
|
1511
|
+
var import_react9 = require("react");
|
|
1512
|
+
var import_ink11 = require("ink");
|
|
1513
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1514
|
+
function MenuSelect({
|
|
1515
|
+
title,
|
|
1516
|
+
items,
|
|
1517
|
+
onSelect,
|
|
1518
|
+
onBack,
|
|
1519
|
+
loading,
|
|
1520
|
+
error
|
|
1521
|
+
}) {
|
|
1522
|
+
const [selected, setSelected] = (0, import_react9.useState)(0);
|
|
1523
|
+
const selectedRef = (0, import_react9.useRef)(0);
|
|
1524
|
+
const resolvedRef = (0, import_react9.useRef)(false);
|
|
1525
|
+
const doSelect = (0, import_react9.useCallback)(
|
|
1526
|
+
(index) => {
|
|
1527
|
+
if (resolvedRef.current || items.length === 0) return;
|
|
1528
|
+
resolvedRef.current = true;
|
|
1529
|
+
onSelect(items[index].value);
|
|
1530
|
+
},
|
|
1531
|
+
[items, onSelect]
|
|
1532
|
+
);
|
|
1533
|
+
(0, import_ink11.useInput)((input, key) => {
|
|
1534
|
+
if (resolvedRef.current) return;
|
|
1535
|
+
if (key.escape) {
|
|
1536
|
+
resolvedRef.current = true;
|
|
1537
|
+
onBack();
|
|
1538
|
+
return;
|
|
828
1539
|
}
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1540
|
+
if (loading || error || items.length === 0) return;
|
|
1541
|
+
if (key.upArrow) {
|
|
1542
|
+
const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
|
|
1543
|
+
selectedRef.current = next;
|
|
1544
|
+
setSelected(next);
|
|
1545
|
+
} else if (key.downArrow) {
|
|
1546
|
+
const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
|
|
1547
|
+
selectedRef.current = next;
|
|
1548
|
+
setSelected(next);
|
|
1549
|
+
} else if (key.return) {
|
|
1550
|
+
doSelect(selectedRef.current);
|
|
1551
|
+
}
|
|
1552
|
+
});
|
|
1553
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1554
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", bold: true, children: title }),
|
|
1555
|
+
loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Loading..." }) }),
|
|
1556
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { marginTop: 1, flexDirection: "column", children: [
|
|
1557
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "red", children: error }),
|
|
1558
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Press Esc to go back" })
|
|
1559
|
+
] }),
|
|
1560
|
+
!loading && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { children: [
|
|
1561
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
1562
|
+
i === selected ? "> " : " ",
|
|
1563
|
+
item.label
|
|
1564
|
+
] }),
|
|
1565
|
+
item.hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
|
|
1566
|
+
" ",
|
|
1567
|
+
item.hint
|
|
1568
|
+
] })
|
|
1569
|
+
] }, item.value)) }),
|
|
1570
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
|
|
1571
|
+
] });
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
// src/ui/TextPrompt.tsx
|
|
1575
|
+
var import_react10 = require("react");
|
|
1576
|
+
var import_ink12 = require("ink");
|
|
1577
|
+
var import_jsx_runtime12 = require("react/jsx-runtime");
|
|
1578
|
+
function TextPrompt({
|
|
1579
|
+
title,
|
|
1580
|
+
placeholder,
|
|
1581
|
+
onSubmit,
|
|
1582
|
+
onCancel,
|
|
1583
|
+
validate
|
|
1584
|
+
}) {
|
|
1585
|
+
const [value, setValue] = (0, import_react10.useState)("");
|
|
1586
|
+
const [error, setError] = (0, import_react10.useState)();
|
|
1587
|
+
const resolvedRef = (0, import_react10.useRef)(false);
|
|
1588
|
+
const valueRef = (0, import_react10.useRef)("");
|
|
1589
|
+
const handleSubmit = (0, import_react10.useCallback)(() => {
|
|
1590
|
+
if (resolvedRef.current) return;
|
|
1591
|
+
const trimmed = valueRef.current.trim();
|
|
1592
|
+
if (!trimmed) return;
|
|
1593
|
+
if (validate) {
|
|
1594
|
+
const err = validate(trimmed);
|
|
1595
|
+
if (err) {
|
|
1596
|
+
setError(err);
|
|
1597
|
+
return;
|
|
846
1598
|
}
|
|
847
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
848
|
-
return true;
|
|
849
1599
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1600
|
+
resolvedRef.current = true;
|
|
1601
|
+
onSubmit(trimmed);
|
|
1602
|
+
}, [validate, onSubmit]);
|
|
1603
|
+
(0, import_ink12.useInput)((input, key) => {
|
|
1604
|
+
if (resolvedRef.current) return;
|
|
1605
|
+
if (key.escape) {
|
|
1606
|
+
resolvedRef.current = true;
|
|
1607
|
+
onCancel();
|
|
1608
|
+
return;
|
|
857
1609
|
}
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
return
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
1610
|
+
if (key.return) {
|
|
1611
|
+
handleSubmit();
|
|
1612
|
+
return;
|
|
1613
|
+
}
|
|
1614
|
+
if (key.backspace || key.delete) {
|
|
1615
|
+
valueRef.current = valueRef.current.slice(0, -1);
|
|
1616
|
+
setValue(valueRef.current);
|
|
1617
|
+
setError(void 0);
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
if (input && !key.ctrl && !key.meta) {
|
|
1621
|
+
valueRef.current = valueRef.current + input;
|
|
1622
|
+
setValue(valueRef.current);
|
|
1623
|
+
setError(void 0);
|
|
869
1624
|
}
|
|
1625
|
+
});
|
|
1626
|
+
return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
1627
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "yellow", bold: true, children: title }),
|
|
1628
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { marginTop: 1, children: [
|
|
1629
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "> " }),
|
|
1630
|
+
value ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: placeholder }) : null,
|
|
1631
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "\u2588" })
|
|
1632
|
+
] }),
|
|
1633
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "red", children: error }),
|
|
1634
|
+
/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
|
|
1635
|
+
] });
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// src/ui/plugin-tui-handlers.ts
|
|
1639
|
+
function handleMainSelect(value, nav) {
|
|
1640
|
+
if (value === "marketplace") {
|
|
1641
|
+
nav.push({ screen: "marketplace-list" });
|
|
1642
|
+
} else if (value === "installed") {
|
|
1643
|
+
nav.push({ screen: "installed-list" });
|
|
870
1644
|
}
|
|
871
1645
|
}
|
|
872
|
-
function
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
},
|
|
879
|
-
[session, addMessage, setMessages, exit, registry]
|
|
880
|
-
);
|
|
1646
|
+
function handleMarketplaceListSelect(value, nav) {
|
|
1647
|
+
if (value === "__add__") {
|
|
1648
|
+
nav.push({ screen: "marketplace-add" });
|
|
1649
|
+
} else {
|
|
1650
|
+
nav.push({ screen: "marketplace-action", context: { marketplace: value } });
|
|
1651
|
+
}
|
|
881
1652
|
}
|
|
882
|
-
function
|
|
883
|
-
if (
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1653
|
+
function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
|
|
1654
|
+
if (value === "browse") {
|
|
1655
|
+
nav.push({ screen: "marketplace-browse", context: { marketplace } });
|
|
1656
|
+
} else if (value === "update") {
|
|
1657
|
+
callbacks.marketplaceUpdate(marketplace).then(() => {
|
|
1658
|
+
nav.notify(`Updated marketplace "${marketplace}".`);
|
|
1659
|
+
nav.pop();
|
|
1660
|
+
}).catch((err) => {
|
|
1661
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1662
|
+
});
|
|
1663
|
+
} else if (value === "remove") {
|
|
1664
|
+
nav.setConfirm({
|
|
1665
|
+
message: `Remove marketplace "${marketplace}" and all its plugins?`,
|
|
1666
|
+
onConfirm: () => {
|
|
1667
|
+
nav.setConfirm(void 0);
|
|
1668
|
+
callbacks.marketplaceRemove(marketplace).then(() => {
|
|
1669
|
+
nav.notify(`Removed marketplace "${marketplace}".`);
|
|
1670
|
+
nav.popN(2);
|
|
1671
|
+
}).catch((err) => {
|
|
1672
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1673
|
+
});
|
|
1674
|
+
},
|
|
1675
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
1676
|
+
});
|
|
892
1677
|
}
|
|
893
|
-
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
|
|
894
1678
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
setContextPercentage(session.getContextState().usedPercentage);
|
|
903
|
-
} catch (err) {
|
|
904
|
-
clearStreamingText();
|
|
905
|
-
if (err instanceof DOMException && err.name === "AbortError") {
|
|
906
|
-
addMessage({ role: "system", content: "Cancelled." });
|
|
907
|
-
} else {
|
|
908
|
-
const errMsg = err instanceof Error ? err.message : String(err);
|
|
909
|
-
addMessage({ role: "system", content: `Error: ${errMsg}` });
|
|
910
|
-
}
|
|
911
|
-
} finally {
|
|
912
|
-
setIsThinking(false);
|
|
1679
|
+
function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
|
|
1680
|
+
const fullId = `${value}@${marketplace}`;
|
|
1681
|
+
const item = items.find((i) => i.value === value);
|
|
1682
|
+
if (item?.hint === "installed") {
|
|
1683
|
+
nav.push({ screen: "installed-action", context: { pluginId: fullId } });
|
|
1684
|
+
} else {
|
|
1685
|
+
nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
|
|
913
1686
|
}
|
|
914
1687
|
}
|
|
915
|
-
function
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
1688
|
+
function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
|
|
1689
|
+
const scope = value;
|
|
1690
|
+
callbacks.install(pluginId, scope).then(() => {
|
|
1691
|
+
nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
|
|
1692
|
+
nav.popN(2);
|
|
1693
|
+
}).catch((err) => {
|
|
1694
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1695
|
+
});
|
|
922
1696
|
}
|
|
923
|
-
function
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
1697
|
+
function handleInstalledListSelect(value, callbacks, nav) {
|
|
1698
|
+
nav.setConfirm({
|
|
1699
|
+
message: `Uninstall plugin "${value}"?`,
|
|
1700
|
+
onConfirm: () => {
|
|
1701
|
+
nav.setConfirm(void 0);
|
|
1702
|
+
callbacks.uninstall(value).then(() => {
|
|
1703
|
+
nav.notify(`Uninstalled plugin "${value}".`);
|
|
1704
|
+
nav.refresh();
|
|
1705
|
+
}).catch((err) => {
|
|
1706
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1707
|
+
});
|
|
1708
|
+
},
|
|
1709
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
1710
|
+
});
|
|
1711
|
+
}
|
|
1712
|
+
function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
|
|
1713
|
+
if (value === "uninstall") {
|
|
1714
|
+
nav.setConfirm({
|
|
1715
|
+
message: `Uninstall plugin "${pluginId}"?`,
|
|
1716
|
+
onConfirm: () => {
|
|
1717
|
+
nav.setConfirm(void 0);
|
|
1718
|
+
callbacks.uninstall(pluginId).then(() => {
|
|
1719
|
+
nav.notify(`Uninstalled plugin "${pluginId}".`);
|
|
1720
|
+
nav.popN(2);
|
|
1721
|
+
}).catch((err) => {
|
|
1722
|
+
nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1723
|
+
});
|
|
1724
|
+
},
|
|
1725
|
+
onCancel: () => nav.setConfirm(void 0)
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
// src/ui/PluginTUI.tsx
|
|
1731
|
+
var import_jsx_runtime13 = require("react/jsx-runtime");
|
|
1732
|
+
function PluginTUI({ callbacks, onClose, addMessage }) {
|
|
1733
|
+
const [stack, setStack] = (0, import_react11.useState)([{ screen: "main" }]);
|
|
1734
|
+
const [items, setItems] = (0, import_react11.useState)([]);
|
|
1735
|
+
const [loading, setLoading] = (0, import_react11.useState)(false);
|
|
1736
|
+
const [error, setError] = (0, import_react11.useState)();
|
|
1737
|
+
const [confirm, setConfirm] = (0, import_react11.useState)();
|
|
1738
|
+
const [refreshCounter, setRefreshCounter] = (0, import_react11.useState)(0);
|
|
1739
|
+
const current = stack[stack.length - 1] ?? { screen: "main" };
|
|
1740
|
+
const push = (0, import_react11.useCallback)((state) => {
|
|
1741
|
+
setStack((prev) => [...prev, state]);
|
|
1742
|
+
setItems([]);
|
|
1743
|
+
setError(void 0);
|
|
1744
|
+
}, []);
|
|
1745
|
+
const pop = (0, import_react11.useCallback)(() => {
|
|
1746
|
+
setStack((prev) => {
|
|
1747
|
+
if (prev.length <= 1) {
|
|
1748
|
+
onClose();
|
|
1749
|
+
return prev;
|
|
1750
|
+
}
|
|
1751
|
+
return prev.slice(0, -1);
|
|
1752
|
+
});
|
|
1753
|
+
setItems([]);
|
|
1754
|
+
setError(void 0);
|
|
1755
|
+
}, [onClose]);
|
|
1756
|
+
const popN = (0, import_react11.useCallback)(
|
|
1757
|
+
(n) => {
|
|
1758
|
+
setStack((prev) => {
|
|
1759
|
+
const next = prev.slice(0, Math.max(1, prev.length - n));
|
|
1760
|
+
if (next.length === 0) {
|
|
1761
|
+
onClose();
|
|
1762
|
+
return prev;
|
|
931
1763
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1764
|
+
return next;
|
|
1765
|
+
});
|
|
1766
|
+
setItems([]);
|
|
1767
|
+
setError(void 0);
|
|
1768
|
+
},
|
|
1769
|
+
[onClose]
|
|
1770
|
+
);
|
|
1771
|
+
const notify = (0, import_react11.useCallback)(
|
|
1772
|
+
(content) => {
|
|
1773
|
+
addMessage?.({ role: "system", content });
|
|
1774
|
+
},
|
|
1775
|
+
[addMessage]
|
|
1776
|
+
);
|
|
1777
|
+
const refresh = (0, import_react11.useCallback)(() => {
|
|
1778
|
+
setItems([]);
|
|
1779
|
+
setRefreshCounter((c) => c + 1);
|
|
1780
|
+
}, []);
|
|
1781
|
+
const nav = { push, pop, popN, notify, setConfirm, refresh };
|
|
1782
|
+
(0, import_react11.useEffect)(() => {
|
|
1783
|
+
const screen2 = current.screen;
|
|
1784
|
+
if (screen2 === "marketplace-list") {
|
|
1785
|
+
setLoading(true);
|
|
1786
|
+
callbacks.marketplaceList().then((sources) => {
|
|
1787
|
+
const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
|
|
1788
|
+
const sourceItems = sources.map((s) => ({
|
|
1789
|
+
label: s.name,
|
|
1790
|
+
value: s.name,
|
|
1791
|
+
hint: s.type
|
|
1792
|
+
}));
|
|
1793
|
+
setItems([...baseItems, ...sourceItems]);
|
|
1794
|
+
setLoading(false);
|
|
1795
|
+
}).catch((err) => {
|
|
1796
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1797
|
+
setLoading(false);
|
|
1798
|
+
});
|
|
1799
|
+
} else if (screen2 === "marketplace-browse") {
|
|
1800
|
+
const marketplace = current.context?.marketplace ?? "";
|
|
1801
|
+
setLoading(true);
|
|
1802
|
+
callbacks.listAvailablePlugins(marketplace).then((plugins) => {
|
|
1803
|
+
setItems(
|
|
1804
|
+
plugins.map((p) => ({
|
|
1805
|
+
label: p.name,
|
|
1806
|
+
value: p.name,
|
|
1807
|
+
hint: p.installed ? "installed" : p.description
|
|
1808
|
+
}))
|
|
941
1809
|
);
|
|
1810
|
+
setLoading(false);
|
|
1811
|
+
}).catch((err) => {
|
|
1812
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1813
|
+
setLoading(false);
|
|
1814
|
+
});
|
|
1815
|
+
} else if (screen2 === "installed-list") {
|
|
1816
|
+
setLoading(true);
|
|
1817
|
+
callbacks.listInstalled().then((plugins) => {
|
|
1818
|
+
setItems(
|
|
1819
|
+
plugins.map((p) => ({
|
|
1820
|
+
label: p.name,
|
|
1821
|
+
value: p.name,
|
|
1822
|
+
hint: p.description
|
|
1823
|
+
}))
|
|
1824
|
+
);
|
|
1825
|
+
setLoading(false);
|
|
1826
|
+
}).catch((err) => {
|
|
1827
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
1828
|
+
setLoading(false);
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
}, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
|
|
1832
|
+
const handleSelect = (0, import_react11.useCallback)(
|
|
1833
|
+
(value) => {
|
|
1834
|
+
const screen2 = current.screen;
|
|
1835
|
+
const ctx = current.context;
|
|
1836
|
+
if (screen2 === "main") handleMainSelect(value, nav);
|
|
1837
|
+
else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
|
|
1838
|
+
else if (screen2 === "marketplace-action")
|
|
1839
|
+
handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
|
|
1840
|
+
else if (screen2 === "marketplace-browse")
|
|
1841
|
+
handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
|
|
1842
|
+
else if (screen2 === "marketplace-install-scope")
|
|
1843
|
+
handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
1844
|
+
else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
|
|
1845
|
+
else if (screen2 === "installed-action")
|
|
1846
|
+
handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
|
|
1847
|
+
},
|
|
1848
|
+
[current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
|
|
1849
|
+
);
|
|
1850
|
+
const handleTextSubmit = (0, import_react11.useCallback)(
|
|
1851
|
+
(value) => {
|
|
1852
|
+
if (current.screen === "marketplace-add") {
|
|
1853
|
+
callbacks.marketplaceAdd(value).then((name) => {
|
|
1854
|
+
notify(`Added marketplace "${name}" from ${value}.`);
|
|
1855
|
+
pop();
|
|
1856
|
+
}).catch((err) => {
|
|
1857
|
+
notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
1858
|
+
pop();
|
|
1859
|
+
});
|
|
942
1860
|
}
|
|
943
|
-
addMessage({ role: "user", content: input });
|
|
944
|
-
return runSessionPrompt(
|
|
945
|
-
input,
|
|
946
|
-
session,
|
|
947
|
-
addMessage,
|
|
948
|
-
clearStreamingText,
|
|
949
|
-
setIsThinking,
|
|
950
|
-
setContextPercentage
|
|
951
|
-
);
|
|
952
1861
|
},
|
|
953
|
-
[
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
1862
|
+
[current.screen, callbacks, notify, pop]
|
|
1863
|
+
);
|
|
1864
|
+
if (confirm) {
|
|
1865
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1866
|
+
ConfirmPrompt,
|
|
1867
|
+
{
|
|
1868
|
+
message: confirm.message,
|
|
1869
|
+
onSelect: (index) => {
|
|
1870
|
+
if (index === 0) confirm.onConfirm();
|
|
1871
|
+
else confirm.onCancel();
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
);
|
|
1875
|
+
}
|
|
1876
|
+
const screen = current.screen;
|
|
1877
|
+
if (screen === "marketplace-add") {
|
|
1878
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1879
|
+
TextPrompt,
|
|
1880
|
+
{
|
|
1881
|
+
title: "Add Marketplace Source",
|
|
1882
|
+
placeholder: "owner/repo or git URL",
|
|
1883
|
+
onSubmit: handleTextSubmit,
|
|
1884
|
+
onCancel: pop,
|
|
1885
|
+
validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
|
|
1886
|
+
}
|
|
1887
|
+
);
|
|
1888
|
+
}
|
|
1889
|
+
if (screen === "marketplace-action") {
|
|
1890
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1891
|
+
MenuSelect,
|
|
1892
|
+
{
|
|
1893
|
+
title: `Marketplace: ${current.context?.marketplace ?? ""}`,
|
|
1894
|
+
items: [
|
|
1895
|
+
{ label: "Browse plugins", value: "browse" },
|
|
1896
|
+
{ label: "Update", value: "update" },
|
|
1897
|
+
{ label: "Remove", value: "remove" }
|
|
1898
|
+
],
|
|
1899
|
+
onSelect: handleSelect,
|
|
1900
|
+
onBack: pop
|
|
1901
|
+
},
|
|
1902
|
+
stack.length
|
|
1903
|
+
);
|
|
1904
|
+
}
|
|
1905
|
+
if (screen === "marketplace-install-scope") {
|
|
1906
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1907
|
+
MenuSelect,
|
|
1908
|
+
{
|
|
1909
|
+
title: `Install scope for "${current.context?.pluginId ?? ""}"`,
|
|
1910
|
+
items: [
|
|
1911
|
+
{ label: "User scope", value: "user" },
|
|
1912
|
+
{ label: "Project scope", value: "project" }
|
|
1913
|
+
],
|
|
1914
|
+
onSelect: handleSelect,
|
|
1915
|
+
onBack: pop
|
|
1916
|
+
},
|
|
1917
|
+
stack.length
|
|
1918
|
+
);
|
|
1919
|
+
}
|
|
1920
|
+
if (screen === "installed-action") {
|
|
1921
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1922
|
+
MenuSelect,
|
|
1923
|
+
{
|
|
1924
|
+
title: `Plugin: ${current.context?.pluginId ?? ""}`,
|
|
1925
|
+
items: [{ label: "Uninstall", value: "uninstall" }],
|
|
1926
|
+
onSelect: handleSelect,
|
|
1927
|
+
onBack: pop
|
|
1928
|
+
},
|
|
1929
|
+
stack.length
|
|
1930
|
+
);
|
|
1931
|
+
}
|
|
1932
|
+
const titleMap = {
|
|
1933
|
+
main: "Plugin Management",
|
|
1934
|
+
"marketplace-list": "Marketplace",
|
|
1935
|
+
"marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
|
|
1936
|
+
"installed-list": "Installed Plugins"
|
|
1937
|
+
};
|
|
1938
|
+
const staticItemsMap = {
|
|
1939
|
+
main: [
|
|
1940
|
+
{ label: "Marketplace", value: "marketplace" },
|
|
1941
|
+
{ label: "Installed Plugins", value: "installed" }
|
|
961
1942
|
]
|
|
1943
|
+
};
|
|
1944
|
+
return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
|
|
1945
|
+
MenuSelect,
|
|
1946
|
+
{
|
|
1947
|
+
title: titleMap[screen] ?? "Plugin Management",
|
|
1948
|
+
items: staticItemsMap[screen] ?? items,
|
|
1949
|
+
onSelect: handleSelect,
|
|
1950
|
+
onBack: pop,
|
|
1951
|
+
loading,
|
|
1952
|
+
error
|
|
1953
|
+
},
|
|
1954
|
+
`${screen}-${stack.length}-${refreshCounter}`
|
|
962
1955
|
);
|
|
963
1956
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1957
|
+
|
|
1958
|
+
// src/ui/ListPicker.tsx
|
|
1959
|
+
var import_react12 = require("react");
|
|
1960
|
+
var import_ink13 = require("ink");
|
|
1961
|
+
var import_jsx_runtime14 = require("react/jsx-runtime");
|
|
1962
|
+
var DEFAULT_MAX_VISIBLE = 3;
|
|
1963
|
+
function ListPicker({
|
|
1964
|
+
items,
|
|
1965
|
+
renderItem,
|
|
1966
|
+
onSelect,
|
|
1967
|
+
onCancel,
|
|
1968
|
+
maxVisible = DEFAULT_MAX_VISIBLE
|
|
1969
|
+
}) {
|
|
1970
|
+
const [selectedIndex, setSelectedIndex] = (0, import_react12.useState)(0);
|
|
1971
|
+
const [scrollOffset, setScrollOffset] = (0, import_react12.useState)(0);
|
|
1972
|
+
const selectedRef = (0, import_react12.useRef)(0);
|
|
1973
|
+
const resolvedRef = (0, import_react12.useRef)(false);
|
|
1974
|
+
(0, import_ink13.useInput)((_input, key) => {
|
|
1975
|
+
if (resolvedRef.current) return;
|
|
1976
|
+
if (key.escape) {
|
|
1977
|
+
resolvedRef.current = true;
|
|
1978
|
+
onCancel();
|
|
1979
|
+
return;
|
|
1980
|
+
}
|
|
1981
|
+
if (items.length === 0) return;
|
|
1982
|
+
if (key.upArrow) {
|
|
1983
|
+
const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
|
|
1984
|
+
selectedRef.current = next;
|
|
1985
|
+
setSelectedIndex(next);
|
|
1986
|
+
if (next < scrollOffset) {
|
|
1987
|
+
setScrollOffset(next);
|
|
1988
|
+
}
|
|
1989
|
+
} else if (key.downArrow) {
|
|
1990
|
+
const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
|
|
1991
|
+
selectedRef.current = next;
|
|
1992
|
+
setSelectedIndex(next);
|
|
1993
|
+
if (next >= scrollOffset + maxVisible) {
|
|
1994
|
+
setScrollOffset(next - maxVisible + 1);
|
|
1995
|
+
}
|
|
1996
|
+
} else if (key.return) {
|
|
1997
|
+
const item = items[selectedRef.current];
|
|
1998
|
+
if (item !== void 0) {
|
|
1999
|
+
resolvedRef.current = true;
|
|
2000
|
+
onSelect(item);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
});
|
|
2004
|
+
if (items.length === 0) {
|
|
2005
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Box, {});
|
|
971
2006
|
}
|
|
972
|
-
|
|
2007
|
+
const visibleItems = items.slice(scrollOffset, scrollOffset + maxVisible);
|
|
2008
|
+
const hasMore = scrollOffset + maxVisible < items.length;
|
|
2009
|
+
const hasLess = scrollOffset > 0;
|
|
2010
|
+
return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", children: [
|
|
2011
|
+
hasLess && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
|
|
2012
|
+
" \u2191 ",
|
|
2013
|
+
scrollOffset,
|
|
2014
|
+
" more above"
|
|
2015
|
+
] }),
|
|
2016
|
+
visibleItems.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Box, { marginBottom: 1, children: renderItem(item, scrollOffset + index === selectedIndex) }, scrollOffset + index)),
|
|
2017
|
+
hasMore && /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
|
|
2018
|
+
" \u2193 ",
|
|
2019
|
+
items.length - scrollOffset - maxVisible,
|
|
2020
|
+
" more below"
|
|
2021
|
+
] })
|
|
2022
|
+
] });
|
|
973
2023
|
}
|
|
2024
|
+
|
|
2025
|
+
// src/ui/App.tsx
|
|
2026
|
+
var import_jsx_runtime15 = require("react/jsx-runtime");
|
|
2027
|
+
var EXIT_DELAY_MS = 500;
|
|
2028
|
+
var SESSION_ID_DISPLAY_LENGTH = 8;
|
|
974
2029
|
function App(props) {
|
|
975
|
-
const
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
addMessage,
|
|
985
|
-
handleSlashCommand,
|
|
986
|
-
clearStreamingText,
|
|
987
|
-
setIsThinking,
|
|
988
|
-
setContextPercentage,
|
|
989
|
-
registry
|
|
2030
|
+
const [activeSessionId, setActiveSessionId] = (0, import_react13.useState)(props.resumeSessionId);
|
|
2031
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2032
|
+
AppInner,
|
|
2033
|
+
{
|
|
2034
|
+
...props,
|
|
2035
|
+
resumeSessionId: activeSessionId,
|
|
2036
|
+
onSessionSwitch: (sessionId) => setActiveSessionId(sessionId)
|
|
2037
|
+
},
|
|
2038
|
+
activeSessionId ?? "__new__"
|
|
990
2039
|
);
|
|
991
|
-
|
|
2040
|
+
}
|
|
2041
|
+
function AppInner(props) {
|
|
2042
|
+
const { exit } = (0, import_ink14.useApp)();
|
|
2043
|
+
const cwd = props.cwd;
|
|
2044
|
+
const {
|
|
2045
|
+
interactiveSession,
|
|
2046
|
+
registry,
|
|
2047
|
+
history,
|
|
2048
|
+
addEntry,
|
|
2049
|
+
streamingText,
|
|
2050
|
+
activeTools,
|
|
2051
|
+
isThinking,
|
|
2052
|
+
isAborting,
|
|
2053
|
+
pendingPrompt,
|
|
2054
|
+
permissionRequest,
|
|
2055
|
+
contextState,
|
|
2056
|
+
handleSubmit: baseHandleSubmit,
|
|
2057
|
+
handleAbort,
|
|
2058
|
+
handleCancelQueue
|
|
2059
|
+
} = useInteractiveSession({
|
|
2060
|
+
cwd,
|
|
2061
|
+
provider: props.provider,
|
|
2062
|
+
permissionMode: props.permissionMode,
|
|
2063
|
+
maxTurns: props.maxTurns,
|
|
2064
|
+
sessionStore: props.sessionStore,
|
|
2065
|
+
resumeSessionId: props.resumeSessionId,
|
|
2066
|
+
forkSession: props.forkSession,
|
|
2067
|
+
sessionName: props.sessionName
|
|
2068
|
+
});
|
|
2069
|
+
const pluginCallbacks = usePluginCallbacks(cwd);
|
|
2070
|
+
const [pendingModelId, setPendingModelId] = (0, import_react13.useState)(null);
|
|
2071
|
+
const pendingModelChangeRef = (0, import_react13.useRef)(null);
|
|
2072
|
+
const [showPluginTUI, setShowPluginTUI] = (0, import_react13.useState)(false);
|
|
2073
|
+
const [showSessionPicker, setShowSessionPicker] = (0, import_react13.useState)(
|
|
2074
|
+
props.resumeSessionId === "__picker__"
|
|
2075
|
+
);
|
|
2076
|
+
const [sessionName, setSessionName] = (0, import_react13.useState)(props.sessionName);
|
|
2077
|
+
(0, import_react13.useEffect)(() => {
|
|
2078
|
+
const name = interactiveSession?.getName?.();
|
|
2079
|
+
if (name && !sessionName) setSessionName(name);
|
|
2080
|
+
}, [interactiveSession, sessionName]);
|
|
2081
|
+
(0, import_react13.useEffect)(() => {
|
|
2082
|
+
const title = sessionName ? `Robota \u2014 ${sessionName}` : "Robota";
|
|
2083
|
+
process.stdout.write(`\x1B]0;${title}\x07`);
|
|
2084
|
+
}, [sessionName]);
|
|
2085
|
+
const handleSubmit = async (input) => {
|
|
2086
|
+
await baseHandleSubmit(input);
|
|
2087
|
+
const sideEffects = interactiveSession;
|
|
2088
|
+
if (sideEffects._pendingModelId) {
|
|
2089
|
+
const modelId = sideEffects._pendingModelId;
|
|
2090
|
+
delete sideEffects._pendingModelId;
|
|
2091
|
+
pendingModelChangeRef.current = modelId;
|
|
2092
|
+
setPendingModelId(modelId);
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
if (sideEffects._pendingLanguage) {
|
|
2096
|
+
const lang = sideEffects._pendingLanguage;
|
|
2097
|
+
delete sideEffects._pendingLanguage;
|
|
2098
|
+
const settingsPath = getUserSettingsPath();
|
|
2099
|
+
const settings = readSettings(settingsPath);
|
|
2100
|
+
settings.language = lang;
|
|
2101
|
+
writeSettings(settingsPath, settings);
|
|
2102
|
+
addEntry(
|
|
2103
|
+
(0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)(`Language set to "${lang}". Restarting...`))
|
|
2104
|
+
);
|
|
2105
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
2106
|
+
return;
|
|
2107
|
+
}
|
|
2108
|
+
if (sideEffects._resetRequested) {
|
|
2109
|
+
delete sideEffects._resetRequested;
|
|
2110
|
+
const settingsPath = getUserSettingsPath();
|
|
2111
|
+
if (deleteSettings(settingsPath)) {
|
|
2112
|
+
addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)(`Deleted ${settingsPath}. Exiting...`)));
|
|
2113
|
+
} else {
|
|
2114
|
+
addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("No user settings found.")));
|
|
2115
|
+
}
|
|
2116
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
if (sideEffects._exitRequested) {
|
|
2120
|
+
delete sideEffects._exitRequested;
|
|
2121
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
2122
|
+
return;
|
|
2123
|
+
}
|
|
2124
|
+
if (sideEffects._triggerPluginTUI) {
|
|
2125
|
+
delete sideEffects._triggerPluginTUI;
|
|
2126
|
+
setShowPluginTUI(true);
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
if (sideEffects._triggerResumePicker) {
|
|
2130
|
+
delete sideEffects._triggerResumePicker;
|
|
2131
|
+
setShowSessionPicker(true);
|
|
2132
|
+
return;
|
|
2133
|
+
}
|
|
2134
|
+
if (sideEffects._sessionName) {
|
|
2135
|
+
const name = sideEffects._sessionName;
|
|
2136
|
+
delete sideEffects._sessionName;
|
|
2137
|
+
interactiveSession.setName(name);
|
|
2138
|
+
setSessionName(name);
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
};
|
|
2142
|
+
(0, import_ink14.useInput)(
|
|
992
2143
|
(_input, key) => {
|
|
993
|
-
if (key.
|
|
994
|
-
|
|
2144
|
+
if (key.escape && isThinking) {
|
|
2145
|
+
handleAbort();
|
|
2146
|
+
}
|
|
995
2147
|
},
|
|
996
|
-
{ isActive: !permissionRequest }
|
|
2148
|
+
{ isActive: !permissionRequest && !showPluginTUI && !showSessionPicker }
|
|
997
2149
|
);
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
2150
|
+
let permissionMode = props.permissionMode ?? "default";
|
|
2151
|
+
let sessionId = "";
|
|
2152
|
+
try {
|
|
2153
|
+
const session = interactiveSession.getSession();
|
|
2154
|
+
permissionMode = session.getPermissionMode();
|
|
2155
|
+
sessionId = session.getSessionId();
|
|
2156
|
+
} catch {
|
|
2157
|
+
}
|
|
2158
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Box, { flexDirection: "column", children: [
|
|
2159
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
2160
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { color: "cyan", bold: true, children: `
|
|
1001
2161
|
____ ___ ____ ___ _____ _
|
|
1002
2162
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
1003
2163
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
1004
2164
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
1005
2165
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
1006
2166
|
` }),
|
|
1007
|
-
/* @__PURE__ */ (0,
|
|
2167
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Text, { dimColor: true, children: [
|
|
1008
2168
|
" v",
|
|
1009
2169
|
props.version ?? "0.0.0"
|
|
1010
2170
|
] })
|
|
1011
2171
|
] }),
|
|
1012
|
-
/* @__PURE__ */ (0,
|
|
1013
|
-
/* @__PURE__ */ (0,
|
|
1014
|
-
isThinking && /* @__PURE__ */ (0,
|
|
2172
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
2173
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MessageList, { history }),
|
|
2174
|
+
(isThinking || activeTools.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
1015
2175
|
] }),
|
|
1016
|
-
permissionRequest && /* @__PURE__ */ (0,
|
|
1017
|
-
/* @__PURE__ */ (0,
|
|
2176
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
2177
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2178
|
+
ConfirmPrompt,
|
|
2179
|
+
{
|
|
2180
|
+
message: `Change model to ${(0, import_agent_core4.getModelName)(pendingModelId)}? This will restart the session.`,
|
|
2181
|
+
onSelect: (index) => {
|
|
2182
|
+
setPendingModelId(null);
|
|
2183
|
+
pendingModelChangeRef.current = null;
|
|
2184
|
+
if (index === 0) {
|
|
2185
|
+
try {
|
|
2186
|
+
const settingsPath = getUserSettingsPath();
|
|
2187
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
2188
|
+
addEntry(
|
|
2189
|
+
(0, import_agent_core4.messageToHistoryEntry)(
|
|
2190
|
+
(0, import_agent_core4.createSystemMessage)(
|
|
2191
|
+
`Model changed to ${(0, import_agent_core4.getModelName)(pendingModelId)}. Restarting...`
|
|
2192
|
+
)
|
|
2193
|
+
)
|
|
2194
|
+
);
|
|
2195
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
2196
|
+
} catch (err) {
|
|
2197
|
+
addEntry(
|
|
2198
|
+
(0, import_agent_core4.messageToHistoryEntry)(
|
|
2199
|
+
(0, import_agent_core4.createSystemMessage)(
|
|
2200
|
+
`Failed: ${err instanceof Error ? err.message : String(err)}`
|
|
2201
|
+
)
|
|
2202
|
+
)
|
|
2203
|
+
);
|
|
2204
|
+
}
|
|
2205
|
+
} else {
|
|
2206
|
+
addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Model change cancelled.")));
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
),
|
|
2211
|
+
showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2212
|
+
PluginTUI,
|
|
2213
|
+
{
|
|
2214
|
+
callbacks: pluginCallbacks,
|
|
2215
|
+
onClose: () => setShowPluginTUI(false),
|
|
2216
|
+
addMessage: (msg) => addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)(msg.content)))
|
|
2217
|
+
}
|
|
2218
|
+
),
|
|
2219
|
+
showSessionPicker && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
2220
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { bold: true, color: "cyan", children: "Select a session to resume (ESC to cancel):" }),
|
|
2221
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
2222
|
+
ListPicker,
|
|
2223
|
+
{
|
|
2224
|
+
items: (props.sessionStore?.list() ?? []).filter((s) => s.cwd === props.cwd),
|
|
2225
|
+
renderItem: (session, isSelected) => {
|
|
2226
|
+
const lastMsg = session.messages.slice().reverse().find((m) => {
|
|
2227
|
+
const msg = m;
|
|
2228
|
+
return msg.role === "assistant" && msg.content;
|
|
2229
|
+
});
|
|
2230
|
+
const rawPreview = lastMsg?.content?.replace(/[\n\r]+/g, " ").trim() ?? "";
|
|
2231
|
+
const preview = rawPreview ? rawPreview.slice(0, 60) + (rawPreview.length > 60 ? "..." : "") : "";
|
|
2232
|
+
return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Text, { children: [
|
|
2233
|
+
isSelected ? "> " : " ",
|
|
2234
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { bold: true, children: session.name ?? session.id.slice(0, SESSION_ID_DISPLAY_LENGTH) }),
|
|
2235
|
+
" ",
|
|
2236
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { dimColor: true, children: new Date(session.updatedAt).toLocaleString(void 0, {
|
|
2237
|
+
month: "short",
|
|
2238
|
+
day: "numeric",
|
|
2239
|
+
hour: "2-digit",
|
|
2240
|
+
minute: "2-digit"
|
|
2241
|
+
}) }),
|
|
2242
|
+
" ",
|
|
2243
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_ink14.Text, { dimColor: true, children: [
|
|
2244
|
+
"msgs: ",
|
|
2245
|
+
session.messages.length
|
|
2246
|
+
] }),
|
|
2247
|
+
preview ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
|
|
2248
|
+
"\n ",
|
|
2249
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { color: "gray", children: preview })
|
|
2250
|
+
] }) : null
|
|
2251
|
+
] });
|
|
2252
|
+
},
|
|
2253
|
+
onSelect: (session) => {
|
|
2254
|
+
setShowSessionPicker(false);
|
|
2255
|
+
props.onSessionSwitch(session.id);
|
|
2256
|
+
},
|
|
2257
|
+
onCancel: () => {
|
|
2258
|
+
setShowSessionPicker(false);
|
|
2259
|
+
addEntry((0, import_agent_core4.messageToHistoryEntry)((0, import_agent_core4.createSystemMessage)("Session resume cancelled.")));
|
|
2260
|
+
}
|
|
2261
|
+
}
|
|
2262
|
+
)
|
|
2263
|
+
] }),
|
|
2264
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1018
2265
|
StatusBar,
|
|
1019
2266
|
{
|
|
1020
|
-
permissionMode
|
|
1021
|
-
modelName: props.
|
|
1022
|
-
sessionId
|
|
1023
|
-
messageCount:
|
|
2267
|
+
permissionMode,
|
|
2268
|
+
modelName: props.modelId ? (0, import_agent_core4.getModelName)(props.modelId) : "",
|
|
2269
|
+
sessionId,
|
|
2270
|
+
messageCount: history.length,
|
|
1024
2271
|
isThinking,
|
|
1025
|
-
contextPercentage,
|
|
1026
|
-
contextUsedTokens:
|
|
1027
|
-
contextMaxTokens:
|
|
2272
|
+
contextPercentage: contextState.percentage,
|
|
2273
|
+
contextUsedTokens: contextState.usedTokens,
|
|
2274
|
+
contextMaxTokens: contextState.maxTokens,
|
|
2275
|
+
sessionName
|
|
1028
2276
|
}
|
|
1029
2277
|
),
|
|
1030
|
-
/* @__PURE__ */ (0,
|
|
2278
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
|
|
1031
2279
|
InputArea,
|
|
1032
2280
|
{
|
|
1033
2281
|
onSubmit: handleSubmit,
|
|
1034
|
-
|
|
1035
|
-
|
|
2282
|
+
onCancelQueue: handleCancelQueue,
|
|
2283
|
+
isDisabled: !!permissionRequest || showPluginTUI || showSessionPicker || isThinking && !!pendingPrompt,
|
|
2284
|
+
isAborting,
|
|
2285
|
+
pendingPrompt,
|
|
2286
|
+
registry,
|
|
2287
|
+
sessionName
|
|
1036
2288
|
}
|
|
1037
|
-
)
|
|
2289
|
+
),
|
|
2290
|
+
/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_ink14.Text, { children: " " })
|
|
1038
2291
|
] });
|
|
1039
2292
|
}
|
|
1040
2293
|
|
|
1041
2294
|
// src/ui/render.tsx
|
|
1042
|
-
var
|
|
2295
|
+
var import_jsx_runtime16 = require("react/jsx-runtime");
|
|
1043
2296
|
function renderApp(options) {
|
|
1044
2297
|
process.on("unhandledRejection", (reason) => {
|
|
1045
2298
|
process.stderr.write(`
|
|
@@ -1050,29 +2303,50 @@ function renderApp(options) {
|
|
|
1050
2303
|
`);
|
|
1051
2304
|
}
|
|
1052
2305
|
});
|
|
1053
|
-
|
|
2306
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
2307
|
+
process.stdout.write("\x1B[?2004h");
|
|
2308
|
+
}
|
|
2309
|
+
const instance = (0, import_ink15.render)(/* @__PURE__ */ (0, import_jsx_runtime16.jsx)(App, { ...options }), {
|
|
1054
2310
|
exitOnCtrlC: true
|
|
1055
2311
|
});
|
|
1056
|
-
instance.waitUntilExit().
|
|
2312
|
+
instance.waitUntilExit().then(() => {
|
|
2313
|
+
if (process.stdout.isTTY) {
|
|
2314
|
+
process.stdout.write("\x1B[?2004l");
|
|
2315
|
+
}
|
|
2316
|
+
process.exit(0);
|
|
2317
|
+
}).catch((err) => {
|
|
1057
2318
|
if (err) {
|
|
1058
2319
|
process.stderr.write(`
|
|
1059
2320
|
[EXIT ERROR] ${err}
|
|
1060
2321
|
`);
|
|
1061
2322
|
}
|
|
2323
|
+
process.exit(1);
|
|
1062
2324
|
});
|
|
1063
2325
|
}
|
|
1064
2326
|
|
|
1065
2327
|
// src/cli.ts
|
|
1066
2328
|
var import_meta = {};
|
|
1067
|
-
|
|
2329
|
+
function checkSettingsFile(filePath) {
|
|
2330
|
+
if (!(0, import_node_fs3.existsSync)(filePath)) return "missing";
|
|
2331
|
+
try {
|
|
2332
|
+
const raw = (0, import_node_fs3.readFileSync)(filePath, "utf8").trim();
|
|
2333
|
+
if (raw.length === 0) return "incomplete";
|
|
2334
|
+
const parsed = JSON.parse(raw);
|
|
2335
|
+
const provider = parsed.provider;
|
|
2336
|
+
if (!provider?.apiKey) return "incomplete";
|
|
2337
|
+
return "valid";
|
|
2338
|
+
} catch {
|
|
2339
|
+
return "corrupt";
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
1068
2342
|
function readVersion() {
|
|
1069
2343
|
try {
|
|
1070
2344
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1071
|
-
const dir = (0,
|
|
1072
|
-
const candidates = [(0,
|
|
2345
|
+
const dir = (0, import_node_path5.dirname)(thisFile);
|
|
2346
|
+
const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
|
|
1073
2347
|
for (const pkgPath of candidates) {
|
|
1074
2348
|
try {
|
|
1075
|
-
const raw = (0,
|
|
2349
|
+
const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
|
|
1076
2350
|
const pkg = JSON.parse(raw);
|
|
1077
2351
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1078
2352
|
return pkg.version;
|
|
@@ -1085,97 +2359,107 @@ function readVersion() {
|
|
|
1085
2359
|
return "0.0.0";
|
|
1086
2360
|
}
|
|
1087
2361
|
}
|
|
1088
|
-
function
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
2362
|
+
function promptInput(label, masked = false) {
|
|
2363
|
+
return new Promise((resolve) => {
|
|
2364
|
+
process.stdout.write(label);
|
|
2365
|
+
let input = "";
|
|
2366
|
+
const stdin = process.stdin;
|
|
2367
|
+
const wasRaw = stdin.isRaw;
|
|
2368
|
+
stdin.setRawMode(true);
|
|
2369
|
+
stdin.resume();
|
|
2370
|
+
stdin.setEncoding("utf8");
|
|
2371
|
+
const onData = (data) => {
|
|
2372
|
+
for (const ch of data) {
|
|
2373
|
+
if (ch === "\r" || ch === "\n") {
|
|
2374
|
+
stdin.removeListener("data", onData);
|
|
2375
|
+
stdin.setRawMode(wasRaw ?? false);
|
|
2376
|
+
stdin.pause();
|
|
2377
|
+
process.stdout.write("\n");
|
|
2378
|
+
resolve(input.trim());
|
|
2379
|
+
return;
|
|
2380
|
+
} else if (ch === "\x7F" || ch === "\b") {
|
|
2381
|
+
if (input.length > 0) {
|
|
2382
|
+
input = input.slice(0, -1);
|
|
2383
|
+
process.stdout.write("\b \b");
|
|
2384
|
+
}
|
|
2385
|
+
} else if (ch === "") {
|
|
2386
|
+
process.stdout.write("\n");
|
|
2387
|
+
process.exit(0);
|
|
2388
|
+
} else if (ch.charCodeAt(0) >= 32) {
|
|
2389
|
+
input += ch;
|
|
2390
|
+
process.stdout.write(masked ? "*" : ch);
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
};
|
|
2394
|
+
stdin.on("data", onData);
|
|
1119
2395
|
});
|
|
1120
|
-
return {
|
|
1121
|
-
positional: positionals,
|
|
1122
|
-
printMode: values["p"] ?? false,
|
|
1123
|
-
continueMode: values["c"] ?? false,
|
|
1124
|
-
resumeId: values["r"],
|
|
1125
|
-
model: values["model"],
|
|
1126
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1127
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1128
|
-
version: values["version"] ?? false
|
|
1129
|
-
};
|
|
1130
2396
|
}
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
2397
|
+
async function ensureConfig(cwd) {
|
|
2398
|
+
const userPath = getUserSettingsPath();
|
|
2399
|
+
const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
|
|
2400
|
+
const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
|
|
2401
|
+
const paths = [userPath, projectPath, localPath];
|
|
2402
|
+
const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
|
|
2403
|
+
if (checks.some((c) => c.status === "valid")) {
|
|
2404
|
+
return;
|
|
1134
2405
|
}
|
|
1135
|
-
|
|
1136
|
-
|
|
2406
|
+
const corrupt = checks.filter((c) => c.status === "corrupt");
|
|
2407
|
+
const incomplete = checks.filter((c) => c.status === "incomplete");
|
|
2408
|
+
process.stdout.write("\n");
|
|
2409
|
+
if (corrupt.length > 0) {
|
|
2410
|
+
for (const c of corrupt) {
|
|
2411
|
+
process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
|
|
2412
|
+
`);
|
|
2413
|
+
}
|
|
2414
|
+
process.stdout.write("\n");
|
|
1137
2415
|
}
|
|
1138
|
-
|
|
1139
|
-
|
|
2416
|
+
if (incomplete.length > 0) {
|
|
2417
|
+
for (const c of incomplete) {
|
|
2418
|
+
process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
|
|
2419
|
+
`);
|
|
2420
|
+
}
|
|
2421
|
+
process.stdout.write("\n");
|
|
1140
2422
|
}
|
|
1141
|
-
|
|
1142
|
-
process.
|
|
2423
|
+
if (corrupt.length === 0 && incomplete.length === 0) {
|
|
2424
|
+
process.stdout.write(" Welcome to Robota CLI!\n");
|
|
2425
|
+
process.stdout.write(" No configuration found. Let's set up.\n");
|
|
2426
|
+
} else {
|
|
2427
|
+
process.stdout.write(" Reconfiguring...\n");
|
|
1143
2428
|
}
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
terminal: false,
|
|
1150
|
-
historySize: 0
|
|
1151
|
-
});
|
|
1152
|
-
rl.question(question, (answer) => {
|
|
1153
|
-
rl.close();
|
|
1154
|
-
resolve(answer);
|
|
1155
|
-
});
|
|
1156
|
-
});
|
|
2429
|
+
process.stdout.write("\n");
|
|
2430
|
+
const apiKey = await promptInput(" Anthropic API key: ", true);
|
|
2431
|
+
if (!apiKey) {
|
|
2432
|
+
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
2433
|
+
process.exit(1);
|
|
1157
2434
|
}
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
2435
|
+
const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
|
|
2436
|
+
const settingsDir = (0, import_node_path5.dirname)(userPath);
|
|
2437
|
+
(0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
|
|
2438
|
+
const settings = {
|
|
2439
|
+
provider: {
|
|
2440
|
+
name: "anthropic",
|
|
2441
|
+
model: "claude-sonnet-4-6",
|
|
2442
|
+
apiKey
|
|
1163
2443
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1168
|
-
if (trimmed === "") return initialIndex;
|
|
1169
|
-
const num = parseInt(trimmed, 10);
|
|
1170
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1171
|
-
return initialIndex;
|
|
2444
|
+
};
|
|
2445
|
+
if (language) {
|
|
2446
|
+
settings.language = language;
|
|
1172
2447
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
2448
|
+
(0, import_node_fs3.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
2449
|
+
process.stdout.write(`
|
|
2450
|
+
Config saved to ${userPath}
|
|
2451
|
+
|
|
2452
|
+
`);
|
|
2453
|
+
}
|
|
2454
|
+
function resetConfig() {
|
|
2455
|
+
const userPath = getUserSettingsPath();
|
|
2456
|
+
if (deleteSettings(userPath)) {
|
|
2457
|
+
process.stdout.write(`Deleted ${userPath}
|
|
2458
|
+
`);
|
|
2459
|
+
} else {
|
|
2460
|
+
process.stdout.write("No user settings found.\n");
|
|
1177
2461
|
}
|
|
1178
|
-
}
|
|
2462
|
+
}
|
|
1179
2463
|
async function startCli() {
|
|
1180
2464
|
const args = parseCliArgs();
|
|
1181
2465
|
if (args.version) {
|
|
@@ -1183,52 +2467,81 @@ async function startCli() {
|
|
|
1183
2467
|
`);
|
|
1184
2468
|
return;
|
|
1185
2469
|
}
|
|
2470
|
+
if (args.reset) {
|
|
2471
|
+
resetConfig();
|
|
2472
|
+
return;
|
|
2473
|
+
}
|
|
1186
2474
|
const cwd = process.cwd();
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
2475
|
+
await ensureConfig(cwd);
|
|
2476
|
+
const providerSettings = readProviderSettings(cwd);
|
|
2477
|
+
const modelId = args.model ?? providerSettings.model;
|
|
2478
|
+
const provider = createProviderFromSettings(cwd, args.model);
|
|
2479
|
+
const sessionStore = new import_agent_sessions.SessionStore();
|
|
2480
|
+
let resumeSessionId;
|
|
2481
|
+
if (args.continueMode) {
|
|
2482
|
+
const sessions = sessionStore.list().filter((s) => s.cwd === cwd);
|
|
2483
|
+
if (sessions.length > 0) {
|
|
2484
|
+
resumeSessionId = sessions[0].id;
|
|
2485
|
+
}
|
|
2486
|
+
} else if (args.resumeId !== void 0) {
|
|
2487
|
+
if (args.resumeId === "") {
|
|
2488
|
+
resumeSessionId = "__picker__";
|
|
2489
|
+
} else {
|
|
2490
|
+
const sessions = sessionStore.list();
|
|
2491
|
+
const match = sessions.find((s) => s.id === args.resumeId || s.name === args.resumeId);
|
|
2492
|
+
if (match) {
|
|
2493
|
+
resumeSessionId = match.id;
|
|
2494
|
+
} else {
|
|
2495
|
+
process.stderr.write(`Session not found: ${args.resumeId}
|
|
2496
|
+
`);
|
|
2497
|
+
process.exit(1);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
1194
2500
|
}
|
|
1195
|
-
const sessionStore = new import_agent_sdk2.SessionStore();
|
|
1196
2501
|
if (args.printMode) {
|
|
1197
|
-
|
|
1198
|
-
if (prompt
|
|
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) {
|
|
1199
2511
|
process.stderr.write("Print mode (-p) requires a prompt argument.\n");
|
|
1200
2512
|
process.exit(1);
|
|
1201
2513
|
}
|
|
1202
|
-
const
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
projectInfo,
|
|
1210
|
-
permissionMode: args.permissionMode,
|
|
1211
|
-
promptForApproval
|
|
2514
|
+
const session = new import_agent_sdk3.InteractiveSession({
|
|
2515
|
+
cwd,
|
|
2516
|
+
provider,
|
|
2517
|
+
permissionMode: args.permissionMode ?? "bypassPermissions",
|
|
2518
|
+
maxTurns: args.maxTurns,
|
|
2519
|
+
sessionStore,
|
|
2520
|
+
sessionName: args.sessionName
|
|
1212
2521
|
});
|
|
1213
|
-
const
|
|
1214
|
-
|
|
1215
|
-
|
|
2522
|
+
const transport = (0, import_agent_transport_headless.createHeadlessTransport)({
|
|
2523
|
+
outputFormat: args.outputFormat ?? "text",
|
|
2524
|
+
prompt
|
|
2525
|
+
});
|
|
2526
|
+
session.attachTransport(transport);
|
|
2527
|
+
await transport.start();
|
|
2528
|
+
process.exit(transport.getExitCode());
|
|
1216
2529
|
}
|
|
1217
2530
|
renderApp({
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
2531
|
+
cwd,
|
|
2532
|
+
provider,
|
|
2533
|
+
modelId,
|
|
2534
|
+
language: args.language,
|
|
1222
2535
|
permissionMode: args.permissionMode,
|
|
1223
2536
|
maxTurns: args.maxTurns,
|
|
1224
|
-
version: readVersion()
|
|
2537
|
+
version: readVersion(),
|
|
2538
|
+
sessionStore,
|
|
2539
|
+
resumeSessionId,
|
|
2540
|
+
forkSession: args.forkSession,
|
|
2541
|
+
sessionName: args.sessionName
|
|
1225
2542
|
});
|
|
1226
2543
|
}
|
|
1227
2544
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1228
2545
|
0 && (module.exports = {
|
|
1229
|
-
Session,
|
|
1230
|
-
SessionStore,
|
|
1231
|
-
TRUST_TO_MODE,
|
|
1232
|
-
query,
|
|
1233
2546
|
startCli
|
|
1234
2547
|
});
|