cueclaw 0.0.3 → 0.1.0
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/dist/app-DIUXV4XR.js +1953 -0
- package/dist/chunk-BVQG3WYO.js +29 -0
- package/dist/chunk-DVQFSFIZ.js +14 -0
- package/dist/{chunk-GMHDL4CG.js → chunk-F3EQAFH4.js} +59 -5
- package/dist/{chunk-K4PGB2DU.js → chunk-G43R5ASK.js} +46 -2
- package/dist/chunk-KRNAXOQ4.js +46 -0
- package/dist/{service-BHFOM6E2.js → chunk-PZZ6FBGB.js} +25 -7
- package/dist/chunk-QBOYMF4A.js +42 -0
- package/dist/chunk-RSKXBXSJ.js +247 -0
- package/dist/{chunk-D77G7ABJ.js → chunk-SEYPA5M2.js} +36 -318
- package/dist/{daemon-TWVEMRCU.js → chunk-W274JWEK.js} +3 -82
- package/dist/chunk-WE5J7GMR.js +409 -0
- package/dist/chunk-ZCK3IFLC.js +59 -0
- package/dist/cli.js +300 -46
- package/dist/config-6NWFKNLW.js +21 -0
- package/dist/daemon-R3LU23PR.js +95 -0
- package/dist/env-2ZIW4OD7.js +16 -0
- package/dist/executor-LS3Y4DO5.js +14 -0
- package/dist/logger-HD23RPWS.js +12 -0
- package/dist/planner-UU4T5IEN.js +28 -0
- package/dist/router-KEZ3YQXC.js +14 -0
- package/dist/service-VTUYSAAZ.js +15 -0
- package/dist/{setup-QZUEJUIN.js → setup-NWBKTQCO.js} +11 -45
- package/dist/{telegram-BTTWEETO.js → telegram-P6DBJ7WZ.js} +2 -2
- package/dist/{whatsapp-36XWDSJ5.js → whatsapp-HFMOFSFI.js} +1 -1
- package/package.json +4 -1
- package/dist/app-UJ4M3TPW.js +0 -448
- package/dist/chunk-E7BP6DMO.js +0 -10
- package/dist/chunk-JRHM3Z4C.js +0 -158
- package/dist/config-HMHM7UAZ.js +0 -12
- package/dist/router-36O66FDW.js +0 -10
|
@@ -0,0 +1,1953 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TriggerLoop
|
|
3
|
+
} from "./chunk-W274JWEK.js";
|
|
4
|
+
import {
|
|
5
|
+
getServiceStatus
|
|
6
|
+
} from "./chunk-PZZ6FBGB.js";
|
|
7
|
+
import {
|
|
8
|
+
checkEnvironment,
|
|
9
|
+
validateAuth
|
|
10
|
+
} from "./chunk-KRNAXOQ4.js";
|
|
11
|
+
import {
|
|
12
|
+
MessageRouter
|
|
13
|
+
} from "./chunk-F3EQAFH4.js";
|
|
14
|
+
import {
|
|
15
|
+
askQuestionTool,
|
|
16
|
+
buildPlannerSystemPrompt,
|
|
17
|
+
confirmPlan,
|
|
18
|
+
parsePlannerToolResponse,
|
|
19
|
+
plannerTool,
|
|
20
|
+
rejectPlan,
|
|
21
|
+
setSecretTool
|
|
22
|
+
} from "./chunk-WE5J7GMR.js";
|
|
23
|
+
import {
|
|
24
|
+
createAnthropicClient
|
|
25
|
+
} from "./chunk-DVQFSFIZ.js";
|
|
26
|
+
import {
|
|
27
|
+
isDev,
|
|
28
|
+
writeEnvVar
|
|
29
|
+
} from "./chunk-ZCK3IFLC.js";
|
|
30
|
+
import {
|
|
31
|
+
executeWorkflow
|
|
32
|
+
} from "./chunk-SEYPA5M2.js";
|
|
33
|
+
import {
|
|
34
|
+
deleteWorkflow,
|
|
35
|
+
getStepRunsByRunId,
|
|
36
|
+
getWorkflow,
|
|
37
|
+
getWorkflowRunsByWorkflowId,
|
|
38
|
+
initDb,
|
|
39
|
+
insertWorkflow,
|
|
40
|
+
listWorkflows,
|
|
41
|
+
updateWorkflowPhase
|
|
42
|
+
} from "./chunk-G43R5ASK.js";
|
|
43
|
+
import {
|
|
44
|
+
cueclawHome,
|
|
45
|
+
loadConfig,
|
|
46
|
+
loadExistingConfig,
|
|
47
|
+
validateConfig,
|
|
48
|
+
writeConfig
|
|
49
|
+
} from "./chunk-RSKXBXSJ.js";
|
|
50
|
+
import "./chunk-BVQG3WYO.js";
|
|
51
|
+
import {
|
|
52
|
+
logger,
|
|
53
|
+
onLogLine
|
|
54
|
+
} from "./chunk-QBOYMF4A.js";
|
|
55
|
+
|
|
56
|
+
// src/tui/app.tsx
|
|
57
|
+
import React2, { useReducer as useReducer2, useCallback as useCallback2, useMemo as useMemo3, useState as useState3, useEffect as useEffect2, useRef as useRef2 } from "react";
|
|
58
|
+
import { Box as Box6, Text as Text6, Static, useInput as useInput4, useApp, useStdout as useStdout3 } from "ink";
|
|
59
|
+
import { ThemeProvider, ConfirmInput as ConfirmInput2 } from "@inkjs/ui";
|
|
60
|
+
|
|
61
|
+
// src/tui/theme.ts
|
|
62
|
+
import { extendTheme, defaultTheme } from "@inkjs/ui";
|
|
63
|
+
var cueclawTheme = extendTheme(defaultTheme, {
|
|
64
|
+
components: {
|
|
65
|
+
Header: {
|
|
66
|
+
styles: {
|
|
67
|
+
hints: () => ({ color: "white", dimColor: true })
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
PlanView: {
|
|
71
|
+
styles: {
|
|
72
|
+
title: () => ({ color: "cyan", bold: true }),
|
|
73
|
+
stepPending: () => ({ color: "gray" }),
|
|
74
|
+
stepRunning: () => ({ color: "yellow" }),
|
|
75
|
+
stepDone: () => ({ color: "green" }),
|
|
76
|
+
stepFailed: () => ({ color: "red" }),
|
|
77
|
+
border: () => ({ borderColor: "gray" })
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
StatusDashboard: {
|
|
81
|
+
styles: {
|
|
82
|
+
executing: () => ({ color: "yellow" }),
|
|
83
|
+
completed: () => ({ color: "green" }),
|
|
84
|
+
failed: () => ({ color: "red" }),
|
|
85
|
+
paused: () => ({ color: "gray", dimColor: true })
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
Chat: {
|
|
89
|
+
styles: {
|
|
90
|
+
userMessage: () => ({ color: "white", bold: true }),
|
|
91
|
+
systemMessage: () => ({ color: "cyan" }),
|
|
92
|
+
assistantMessage: () => ({ color: "white" }),
|
|
93
|
+
prompt: () => ({ color: "green" })
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// src/tui/chat.tsx
|
|
100
|
+
import { useState, useMemo, useReducer, useEffect, useRef } from "react";
|
|
101
|
+
import { Box, Text, useStdout, useInput } from "ink";
|
|
102
|
+
import { Spinner, useComponentTheme } from "@inkjs/ui";
|
|
103
|
+
|
|
104
|
+
// src/tui/version.ts
|
|
105
|
+
import { createRequire } from "module";
|
|
106
|
+
import { fileURLToPath } from "url";
|
|
107
|
+
var require2 = createRequire(import.meta.url);
|
|
108
|
+
var pkg = require2("../../package.json");
|
|
109
|
+
var isDev2 = !fileURLToPath(import.meta.url).includes("/dist/");
|
|
110
|
+
var appVersion = isDev2 ? "dev" : pkg.version;
|
|
111
|
+
|
|
112
|
+
// src/tui/commands.ts
|
|
113
|
+
var commands = [];
|
|
114
|
+
function registerCommand(cmd) {
|
|
115
|
+
commands.push(cmd);
|
|
116
|
+
}
|
|
117
|
+
function getCommands() {
|
|
118
|
+
return commands;
|
|
119
|
+
}
|
|
120
|
+
function findCommand(name) {
|
|
121
|
+
const lower = name.toLowerCase();
|
|
122
|
+
return commands.find((c) => c.name === lower || c.aliases.includes(lower));
|
|
123
|
+
}
|
|
124
|
+
function parseSlashCommand(input) {
|
|
125
|
+
const trimmed = input.trim();
|
|
126
|
+
if (!trimmed.startsWith("/")) return null;
|
|
127
|
+
const spaceIdx = trimmed.indexOf(" ");
|
|
128
|
+
if (spaceIdx === -1) {
|
|
129
|
+
return { name: trimmed.slice(1), args: "" };
|
|
130
|
+
}
|
|
131
|
+
return { name: trimmed.slice(1, spaceIdx), args: trimmed.slice(spaceIdx + 1).trim() };
|
|
132
|
+
}
|
|
133
|
+
registerCommand({
|
|
134
|
+
name: "help",
|
|
135
|
+
aliases: ["h", "?"],
|
|
136
|
+
description: "Show available commands",
|
|
137
|
+
usage: "/help",
|
|
138
|
+
execute(_args, ctx) {
|
|
139
|
+
const lines = commands.map((c) => {
|
|
140
|
+
const aliases = c.aliases.length > 0 ? ` (${c.aliases.map((a) => "/" + a).join(", ")})` : "";
|
|
141
|
+
return ` /${c.name}${aliases} \u2014 ${c.description}`;
|
|
142
|
+
});
|
|
143
|
+
ctx.addMessage({ role: "assistant", text: "Available commands:\n" + lines.join("\n") });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
registerCommand({
|
|
147
|
+
name: "list",
|
|
148
|
+
aliases: ["ls"],
|
|
149
|
+
description: "List all workflows",
|
|
150
|
+
usage: "/list",
|
|
151
|
+
execute(_args, ctx) {
|
|
152
|
+
const workflows = listWorkflows(ctx.db);
|
|
153
|
+
if (workflows.length === 0) {
|
|
154
|
+
ctx.addMessage({ role: "assistant", text: "No workflows found." });
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const header = `${"ID".padEnd(14)} ${"Name".padEnd(28)} ${"Phase".padEnd(14)} Trigger`;
|
|
158
|
+
const rows = workflows.map((wf) => {
|
|
159
|
+
const trigger = wf.trigger.type === "poll" ? `poll (${wf.trigger.interval_seconds}s)` : wf.trigger.type === "cron" ? `cron (${wf.trigger.expression})` : "manual";
|
|
160
|
+
return `${wf.id.slice(0, 12).padEnd(14)} ${wf.name.slice(0, 26).padEnd(28)} ${wf.phase.padEnd(14)} ${trigger}`;
|
|
161
|
+
});
|
|
162
|
+
ctx.addMessage({ role: "assistant", text: `Workflows (${workflows.length}):
|
|
163
|
+
${header}
|
|
164
|
+
${rows.join("\n")}` });
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
registerCommand({
|
|
168
|
+
name: "status",
|
|
169
|
+
aliases: ["st"],
|
|
170
|
+
description: "View workflow status",
|
|
171
|
+
usage: "/status [id]",
|
|
172
|
+
execute(args, ctx) {
|
|
173
|
+
if (!args) {
|
|
174
|
+
findCommand("list").execute("", ctx);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const wf = getWorkflow(ctx.db, args) ?? findWorkflowByPrefix(ctx.db, args);
|
|
178
|
+
if (!wf) {
|
|
179
|
+
ctx.addMessage({ role: "assistant", text: `Workflow not found: ${args}` });
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const trigger = wf.trigger.type === "poll" ? `poll (${wf.trigger.interval_seconds}s)` : wf.trigger.type === "cron" ? `cron (${wf.trigger.expression})` : "manual";
|
|
183
|
+
let detail = `Workflow: ${wf.name}
|
|
184
|
+
ID: ${wf.id}
|
|
185
|
+
Phase: ${wf.phase}
|
|
186
|
+
Trigger: ${trigger}
|
|
187
|
+
Created: ${wf.created_at}
|
|
188
|
+
|
|
189
|
+
Steps:`;
|
|
190
|
+
for (const step of wf.steps) {
|
|
191
|
+
const deps = step.depends_on.length > 0 ? ` (after: ${step.depends_on.join(", ")})` : "";
|
|
192
|
+
detail += `
|
|
193
|
+
- ${step.id}: ${step.description.slice(0, 80)}${deps}`;
|
|
194
|
+
}
|
|
195
|
+
const runs = getWorkflowRunsByWorkflowId(ctx.db, wf.id);
|
|
196
|
+
if (runs.length > 0) {
|
|
197
|
+
const latest = runs[0];
|
|
198
|
+
detail += `
|
|
199
|
+
|
|
200
|
+
Latest Run: ${latest.status}`;
|
|
201
|
+
if (latest.error) detail += ` \u2014 ${latest.error}`;
|
|
202
|
+
const stepRuns = getStepRunsByRunId(ctx.db, latest.id);
|
|
203
|
+
if (stepRuns.length > 0) {
|
|
204
|
+
detail += "\nStep Results:";
|
|
205
|
+
for (const sr of stepRuns) {
|
|
206
|
+
const output = sr.output_json ? ` \u2014 ${sr.output_json.slice(0, 60)}` : "";
|
|
207
|
+
detail += `
|
|
208
|
+
${sr.step_id}: ${sr.status}${output}`;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
ctx.addMessage({ role: "assistant", text: detail });
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
registerCommand({
|
|
216
|
+
name: "pause",
|
|
217
|
+
aliases: [],
|
|
218
|
+
description: "Pause a workflow",
|
|
219
|
+
usage: "/pause <id>",
|
|
220
|
+
execute(args, ctx) {
|
|
221
|
+
if (!args) {
|
|
222
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /pause <workflow-id>" });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
const wf = getWorkflow(ctx.db, args) ?? findWorkflowByPrefix(ctx.db, args);
|
|
226
|
+
if (!wf) {
|
|
227
|
+
ctx.addMessage({ role: "assistant", text: `Workflow not found: ${args}` });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (wf.phase !== "active") {
|
|
231
|
+
ctx.addMessage({ role: "assistant", text: `Cannot pause workflow in phase "${wf.phase}" (must be "active")` });
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
updateWorkflowPhase(ctx.db, wf.id, "paused");
|
|
235
|
+
ctx.addMessage({ role: "assistant", text: `Paused workflow "${wf.name}" (${wf.id})` });
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
registerCommand({
|
|
239
|
+
name: "resume",
|
|
240
|
+
aliases: [],
|
|
241
|
+
description: "Resume a paused workflow",
|
|
242
|
+
usage: "/resume <id>",
|
|
243
|
+
execute(args, ctx) {
|
|
244
|
+
if (!args) {
|
|
245
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /resume <workflow-id>" });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
const wf = getWorkflow(ctx.db, args) ?? findWorkflowByPrefix(ctx.db, args);
|
|
249
|
+
if (!wf) {
|
|
250
|
+
ctx.addMessage({ role: "assistant", text: `Workflow not found: ${args}` });
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
if (wf.phase !== "paused") {
|
|
254
|
+
ctx.addMessage({ role: "assistant", text: `Cannot resume workflow in phase "${wf.phase}" (must be "paused")` });
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const nextPhase = wf.trigger.type === "manual" ? "executing" : "active";
|
|
258
|
+
updateWorkflowPhase(ctx.db, wf.id, nextPhase);
|
|
259
|
+
ctx.addMessage({ role: "assistant", text: `Resumed workflow "${wf.name}" \u2014 phase: ${nextPhase}` });
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
registerCommand({
|
|
263
|
+
name: "delete",
|
|
264
|
+
aliases: ["rm"],
|
|
265
|
+
description: "Delete a workflow",
|
|
266
|
+
usage: "/delete <id>",
|
|
267
|
+
execute(args, ctx) {
|
|
268
|
+
if (!args) {
|
|
269
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /delete <workflow-id>" });
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const wf = getWorkflow(ctx.db, args) ?? findWorkflowByPrefix(ctx.db, args);
|
|
273
|
+
if (!wf) {
|
|
274
|
+
ctx.addMessage({ role: "assistant", text: `Workflow not found: ${args}` });
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (wf.phase === "executing") {
|
|
278
|
+
ctx.addMessage({ role: "assistant", text: "Cannot delete workflow while it is executing." });
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
deleteWorkflow(ctx.db, wf.id);
|
|
282
|
+
ctx.addMessage({ role: "assistant", text: `Deleted workflow "${wf.name}" (${wf.id})` });
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
registerCommand({
|
|
286
|
+
name: "config",
|
|
287
|
+
aliases: ["cfg"],
|
|
288
|
+
description: "View or set configuration",
|
|
289
|
+
usage: "/config get [key] | /config set <key> <value>",
|
|
290
|
+
execute(args, ctx) {
|
|
291
|
+
const parts = args.split(/\s+/);
|
|
292
|
+
const subcommand = parts[0]?.toLowerCase();
|
|
293
|
+
if (!subcommand || subcommand === "get") {
|
|
294
|
+
const key = parts[1];
|
|
295
|
+
try {
|
|
296
|
+
const config = loadConfig();
|
|
297
|
+
if (!key) {
|
|
298
|
+
ctx.addMessage({ role: "assistant", text: "Configuration:\n" + JSON.stringify(config, null, 2) });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const keyParts = key.split(".");
|
|
302
|
+
let value = config;
|
|
303
|
+
for (const p of keyParts) {
|
|
304
|
+
if (value === null || value === void 0) break;
|
|
305
|
+
value = value[p];
|
|
306
|
+
}
|
|
307
|
+
ctx.addMessage({ role: "assistant", text: value !== void 0 ? `${key} = ${JSON.stringify(value, null, 2)}` : `Key not found: ${key}` });
|
|
308
|
+
} catch (err) {
|
|
309
|
+
ctx.addMessage({ role: "assistant", text: `Error loading config: ${err instanceof Error ? err.message : String(err)}` });
|
|
310
|
+
}
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (subcommand === "set") {
|
|
314
|
+
const key = parts[1];
|
|
315
|
+
const value = parts.slice(2).join(" ");
|
|
316
|
+
if (!key || !value) {
|
|
317
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /config set <key> <value>" });
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
let parsed = value;
|
|
322
|
+
if (value === "true") parsed = true;
|
|
323
|
+
else if (value === "false") parsed = false;
|
|
324
|
+
else if (/^\d+$/.test(value)) parsed = Number(value);
|
|
325
|
+
const keyParts = key.split(".");
|
|
326
|
+
const update = {};
|
|
327
|
+
let target = update;
|
|
328
|
+
for (let i = 0; i < keyParts.length - 1; i++) {
|
|
329
|
+
target[keyParts[i]] = {};
|
|
330
|
+
target = target[keyParts[i]];
|
|
331
|
+
}
|
|
332
|
+
target[keyParts[keyParts.length - 1]] = parsed;
|
|
333
|
+
writeConfig(update);
|
|
334
|
+
const newConfig = loadConfig();
|
|
335
|
+
ctx.setConfig(newConfig);
|
|
336
|
+
ctx.addMessage({ role: "assistant", text: `Set ${key} = ${JSON.stringify(parsed)}` });
|
|
337
|
+
} catch (err) {
|
|
338
|
+
ctx.addMessage({ role: "assistant", text: `Error setting config: ${err instanceof Error ? err.message : String(err)}` });
|
|
339
|
+
}
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /config get [key] | /config set <key> <value>" });
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
registerCommand({
|
|
346
|
+
name: "daemon",
|
|
347
|
+
aliases: [],
|
|
348
|
+
description: "View daemon status",
|
|
349
|
+
usage: "/daemon status|start|stop",
|
|
350
|
+
execute(args, ctx) {
|
|
351
|
+
const subcommand = args.trim().toLowerCase() || "status";
|
|
352
|
+
if (subcommand === "status") {
|
|
353
|
+
const status = getServiceStatus();
|
|
354
|
+
const bridgeStatus = ctx.bridge ? ctx.bridge.isExternal ? "external service" : "in-process" : "not connected";
|
|
355
|
+
ctx.addMessage({ role: "assistant", text: `Daemon status: ${status}
|
|
356
|
+
Bridge: ${bridgeStatus}` });
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
if (subcommand === "start" || subcommand === "stop") {
|
|
360
|
+
ctx.addMessage({ role: "assistant", text: `Use the CLI for daemon ${subcommand}: cueclaw daemon ${subcommand}` });
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /daemon status|start|stop" });
|
|
364
|
+
}
|
|
365
|
+
});
|
|
366
|
+
registerCommand({
|
|
367
|
+
name: "info",
|
|
368
|
+
aliases: [],
|
|
369
|
+
description: "Show system information",
|
|
370
|
+
usage: "/info",
|
|
371
|
+
execute(_args, ctx) {
|
|
372
|
+
const config = ctx.config;
|
|
373
|
+
const lines = [
|
|
374
|
+
`CueClaw ${appVersion === "dev" ? "dev" : `v${appVersion}`}`,
|
|
375
|
+
`Working directory: ${ctx.cwd}`,
|
|
376
|
+
`Config directory: ${cueclawHome()}`,
|
|
377
|
+
""
|
|
378
|
+
];
|
|
379
|
+
if (config) {
|
|
380
|
+
lines.push(`Planner model: ${config.claude.planner.model}`);
|
|
381
|
+
lines.push(`Executor model: ${config.claude.executor.model}`);
|
|
382
|
+
lines.push(`Base URL: ${config.claude.base_url}`);
|
|
383
|
+
if (config.telegram?.enabled) lines.push("Telegram: enabled");
|
|
384
|
+
if (config.whatsapp?.enabled) lines.push("WhatsApp: enabled");
|
|
385
|
+
if (config.container?.enabled) lines.push("Container isolation: enabled");
|
|
386
|
+
}
|
|
387
|
+
ctx.addMessage({ role: "assistant", text: lines.join("\n") });
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
registerCommand({
|
|
391
|
+
name: "clear",
|
|
392
|
+
aliases: ["cls"],
|
|
393
|
+
description: "Clear chat messages",
|
|
394
|
+
usage: "/clear",
|
|
395
|
+
execute(_args, ctx) {
|
|
396
|
+
ctx.clearMessages();
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
registerCommand({
|
|
400
|
+
name: "new",
|
|
401
|
+
aliases: [],
|
|
402
|
+
description: "Generate a plan directly (skip conversation)",
|
|
403
|
+
usage: "/new <description>",
|
|
404
|
+
execute(args, ctx) {
|
|
405
|
+
if (!args) {
|
|
406
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /new <workflow description>" });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
ctx.addMessage({ role: "assistant", text: "Generating plan..." });
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
registerCommand({
|
|
413
|
+
name: "cancel",
|
|
414
|
+
aliases: [],
|
|
415
|
+
description: "Cancel current conversation",
|
|
416
|
+
usage: "/cancel",
|
|
417
|
+
execute(_args, ctx) {
|
|
418
|
+
ctx.addMessage({ role: "assistant", text: "Conversation cancelled." });
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
registerCommand({
|
|
422
|
+
name: "bot",
|
|
423
|
+
aliases: [],
|
|
424
|
+
description: "Manage bot channels",
|
|
425
|
+
usage: "/bot start|status",
|
|
426
|
+
execute(args, ctx) {
|
|
427
|
+
const subcommand = args.trim().toLowerCase() || "status";
|
|
428
|
+
if (subcommand === "status") {
|
|
429
|
+
const config = ctx.config;
|
|
430
|
+
const tg = config?.telegram?.enabled ? "enabled" : "disabled";
|
|
431
|
+
const wa = config?.whatsapp?.enabled ? "enabled" : "disabled";
|
|
432
|
+
ctx.addMessage({ role: "assistant", text: `Telegram: ${tg}
|
|
433
|
+
WhatsApp: ${wa}` });
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
if (subcommand === "start") {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
ctx.addMessage({ role: "assistant", text: "Usage: /bot start|status" });
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
registerCommand({
|
|
443
|
+
name: "setup",
|
|
444
|
+
aliases: [],
|
|
445
|
+
description: "Re-run configuration setup",
|
|
446
|
+
usage: "/setup",
|
|
447
|
+
execute(_args, ctx) {
|
|
448
|
+
ctx.addMessage({ role: "assistant", text: "Starting setup wizard..." });
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
function findWorkflowByPrefix(db, prefix) {
|
|
452
|
+
const all = listWorkflows(db);
|
|
453
|
+
return all.find((wf) => wf.id.startsWith(prefix));
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/tui/chat.tsx
|
|
457
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
458
|
+
function inputReducer(state, action) {
|
|
459
|
+
switch (action.type) {
|
|
460
|
+
case "insert": {
|
|
461
|
+
const value = state.value.slice(0, state.cursor) + action.text + state.value.slice(state.cursor);
|
|
462
|
+
return { value, prevValue: state.value, cursor: state.cursor + action.text.length };
|
|
463
|
+
}
|
|
464
|
+
case "delete": {
|
|
465
|
+
if (state.cursor === 0) return state;
|
|
466
|
+
const value = state.value.slice(0, state.cursor - 1) + state.value.slice(state.cursor);
|
|
467
|
+
return { value, prevValue: state.value, cursor: state.cursor - 1 };
|
|
468
|
+
}
|
|
469
|
+
case "left":
|
|
470
|
+
return { ...state, cursor: Math.max(0, state.cursor - 1) };
|
|
471
|
+
case "right":
|
|
472
|
+
return { ...state, cursor: Math.min(state.value.length, state.cursor + 1) };
|
|
473
|
+
case "reset":
|
|
474
|
+
return { value: "", prevValue: state.value, cursor: 0 };
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
function ResettableInput({ placeholder, onSubmit, onChange, isDisabled }) {
|
|
478
|
+
const [state, dispatch] = useReducer(inputReducer, { value: "", prevValue: "", cursor: 0 });
|
|
479
|
+
const submitRef = useRef(null);
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
if (state.value !== state.prevValue) {
|
|
482
|
+
onChange?.(state.value);
|
|
483
|
+
}
|
|
484
|
+
}, [state.value, state.prevValue, onChange]);
|
|
485
|
+
useEffect(() => {
|
|
486
|
+
if (submitRef.current !== null) {
|
|
487
|
+
const value = submitRef.current;
|
|
488
|
+
submitRef.current = null;
|
|
489
|
+
onSubmit(value);
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
useInput((input, key) => {
|
|
493
|
+
if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) return;
|
|
494
|
+
if (key.return) {
|
|
495
|
+
submitRef.current = state.value;
|
|
496
|
+
dispatch({ type: "reset" });
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
if (key.leftArrow) {
|
|
500
|
+
dispatch({ type: "left" });
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
if (key.rightArrow) {
|
|
504
|
+
dispatch({ type: "right" });
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (key.backspace || key.delete) {
|
|
508
|
+
dispatch({ type: "delete" });
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
dispatch({ type: "insert", text: input });
|
|
512
|
+
}, { isActive: !isDisabled });
|
|
513
|
+
if (state.value.length === 0) {
|
|
514
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
515
|
+
/* @__PURE__ */ jsx(Text, { inverse: true, children: placeholder?.[0] ?? " " }),
|
|
516
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder?.slice(1) ?? "" })
|
|
517
|
+
] });
|
|
518
|
+
}
|
|
519
|
+
const before = state.value.slice(0, state.cursor);
|
|
520
|
+
const cursorChar = state.value[state.cursor] ?? " ";
|
|
521
|
+
const after = state.value.slice(state.cursor + 1);
|
|
522
|
+
return /* @__PURE__ */ jsxs(Text, { children: [
|
|
523
|
+
before,
|
|
524
|
+
/* @__PURE__ */ jsx(Text, { inverse: true, children: cursorChar }),
|
|
525
|
+
after
|
|
526
|
+
] });
|
|
527
|
+
}
|
|
528
|
+
function Chat({ messages, isGenerating, onSubmit, footerExtra, footerHints, streamingText }) {
|
|
529
|
+
const { styles } = useComponentTheme("Chat");
|
|
530
|
+
const { styles: headerStyles } = useComponentTheme("Header");
|
|
531
|
+
const userStyle = styles?.userMessage?.() ?? { color: "white", bold: true };
|
|
532
|
+
const systemStyle = styles?.systemMessage?.() ?? { color: "cyan" };
|
|
533
|
+
const assistantStyle = styles?.assistantMessage?.() ?? { color: "white" };
|
|
534
|
+
const promptStyle = styles?.prompt?.() ?? { color: "green" };
|
|
535
|
+
const hintsStyle = headerStyles?.hints?.() ?? { color: "white", dimColor: true };
|
|
536
|
+
const { stdout } = useStdout();
|
|
537
|
+
const cols = stdout?.columns ?? 80;
|
|
538
|
+
const [currentInput, setCurrentInput] = useState("");
|
|
539
|
+
const allCommands = useMemo(() => getCommands(), []);
|
|
540
|
+
const matchingCommands = useMemo(() => {
|
|
541
|
+
if (!currentInput.startsWith("/")) return [];
|
|
542
|
+
const prefix = currentInput.toLowerCase();
|
|
543
|
+
return allCommands.filter((c) => {
|
|
544
|
+
const full = `/${c.name}`;
|
|
545
|
+
return full.startsWith(prefix) || c.aliases.some((a) => `/${a}`.startsWith(prefix));
|
|
546
|
+
});
|
|
547
|
+
}, [currentInput, allCommands]);
|
|
548
|
+
const showCommandHints = currentInput.startsWith("/") && matchingCommands.length > 0 && currentInput !== "/" + matchingCommands[0]?.name;
|
|
549
|
+
const defaultHints = "Enter send \xB7 /help commands \xB7 Ctrl+C exit";
|
|
550
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, children: [
|
|
551
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingX: 1, marginTop: 1, children: [
|
|
552
|
+
messages.map((msg, i) => /* @__PURE__ */ jsx(Box, { marginBottom: 1, flexDirection: "column", children: msg.role === "user" ? /* @__PURE__ */ jsxs(Text, { ...userStyle, children: [
|
|
553
|
+
"You: ",
|
|
554
|
+
msg.text
|
|
555
|
+
] }) : msg.role === "assistant" ? msg.content ? /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
556
|
+
/* @__PURE__ */ jsx(Text, { ...assistantStyle, bold: true, children: "CueClaw:" }),
|
|
557
|
+
msg.content
|
|
558
|
+
] }) : /* @__PURE__ */ jsxs(Text, { ...assistantStyle, children: [
|
|
559
|
+
"CueClaw: ",
|
|
560
|
+
msg.text
|
|
561
|
+
] }) : /* @__PURE__ */ jsx(Text, { ...systemStyle, children: msg.text }) }, i)),
|
|
562
|
+
streamingText && /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { ...assistantStyle, children: [
|
|
563
|
+
"CueClaw: ",
|
|
564
|
+
streamingText
|
|
565
|
+
] }) }),
|
|
566
|
+
isGenerating && !streamingText && /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsx(Spinner, { label: "Thinking..." }) })
|
|
567
|
+
] }),
|
|
568
|
+
!isGenerating && /* @__PURE__ */ jsxs(Box, { paddingX: 1, children: [
|
|
569
|
+
/* @__PURE__ */ jsx(Text, { ...promptStyle, children: "> " }),
|
|
570
|
+
/* @__PURE__ */ jsx(
|
|
571
|
+
ResettableInput,
|
|
572
|
+
{
|
|
573
|
+
placeholder: "Describe a workflow or type /help",
|
|
574
|
+
onChange: setCurrentInput,
|
|
575
|
+
onSubmit: (value) => {
|
|
576
|
+
const trimmed = value.trim();
|
|
577
|
+
if (trimmed) {
|
|
578
|
+
setCurrentInput("");
|
|
579
|
+
onSubmit(trimmed);
|
|
580
|
+
}
|
|
581
|
+
},
|
|
582
|
+
isDisabled: isGenerating
|
|
583
|
+
}
|
|
584
|
+
)
|
|
585
|
+
] }),
|
|
586
|
+
!isGenerating && showCommandHints && /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 2, children: matchingCommands.slice(0, 6).map((cmd) => /* @__PURE__ */ jsxs(Box, { gap: 1, children: [
|
|
587
|
+
/* @__PURE__ */ jsxs(Text, { color: "cyan", children: [
|
|
588
|
+
"/",
|
|
589
|
+
cmd.name
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
592
|
+
"\u2014 ",
|
|
593
|
+
cmd.description
|
|
594
|
+
] })
|
|
595
|
+
] }, cmd.name)) }),
|
|
596
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.max(0, cols - 2)) }) }),
|
|
597
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: /* @__PURE__ */ jsxs(Text, { ...hintsStyle, children: [
|
|
598
|
+
footerHints ?? defaultHints,
|
|
599
|
+
footerExtra ?? ""
|
|
600
|
+
] }) })
|
|
601
|
+
] });
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/tui/plan-view.tsx
|
|
605
|
+
import { Box as Box2, Text as Text2, useInput as useInput2 } from "ink";
|
|
606
|
+
import { useComponentTheme as useComponentTheme2 } from "@inkjs/ui";
|
|
607
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
608
|
+
function PlanView({ workflow, onConfirm, onModify, onCancel }) {
|
|
609
|
+
const { styles } = useComponentTheme2("PlanView");
|
|
610
|
+
const titleStyle = styles?.title?.() ?? { color: "cyan", bold: true };
|
|
611
|
+
const pendingStyle = styles?.stepPending?.() ?? { color: "gray" };
|
|
612
|
+
const borderStyle = styles?.border?.() ?? { borderColor: "gray" };
|
|
613
|
+
useInput2((input) => {
|
|
614
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
615
|
+
if (input === "m" || input === "M") onModify();
|
|
616
|
+
if (input === "n" || input === "N") onCancel();
|
|
617
|
+
});
|
|
618
|
+
const trigger = workflow.trigger;
|
|
619
|
+
const triggerLabel = trigger.type === "manual" ? "manual" : trigger.type === "cron" ? `cron (${trigger.expression})` : `poll (${trigger.interval_seconds}s)`;
|
|
620
|
+
const failureDesc = workflow.failure_policy.on_step_failure;
|
|
621
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
622
|
+
/* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", flexGrow: 1, borderStyle: "round", ...borderStyle, children: [
|
|
623
|
+
/* @__PURE__ */ jsxs2(Text2, { ...titleStyle, children: [
|
|
624
|
+
"Plan: ",
|
|
625
|
+
workflow.name
|
|
626
|
+
] }),
|
|
627
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
628
|
+
"Trigger: ",
|
|
629
|
+
triggerLabel
|
|
630
|
+
] }),
|
|
631
|
+
/* @__PURE__ */ jsx2(Text2, { children: "" }),
|
|
632
|
+
workflow.steps.map((step, i) => /* @__PURE__ */ jsx2(StepLine, { step, index: i + 1, style: pendingStyle }, step.id)),
|
|
633
|
+
/* @__PURE__ */ jsx2(Text2, { children: "" }),
|
|
634
|
+
/* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
635
|
+
"Failure policy: ",
|
|
636
|
+
failureDesc
|
|
637
|
+
] })
|
|
638
|
+
] }),
|
|
639
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
640
|
+
/* @__PURE__ */ jsx2(Text2, { color: "green", children: "[Y] Confirm" }),
|
|
641
|
+
" ",
|
|
642
|
+
/* @__PURE__ */ jsx2(Text2, { color: "yellow", children: "[M] Modify" }),
|
|
643
|
+
" ",
|
|
644
|
+
/* @__PURE__ */ jsx2(Text2, { color: "red", children: "[N] Cancel" })
|
|
645
|
+
] }) })
|
|
646
|
+
] });
|
|
647
|
+
}
|
|
648
|
+
function StepLine({ step, index, style }) {
|
|
649
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
650
|
+
/* @__PURE__ */ jsxs2(Text2, { ...style, children: [
|
|
651
|
+
index,
|
|
652
|
+
". ",
|
|
653
|
+
step.description
|
|
654
|
+
] }),
|
|
655
|
+
step.depends_on && step.depends_on.length > 0 && /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
656
|
+
" \u2514\u2500 depends on: ",
|
|
657
|
+
step.depends_on.join(", ")
|
|
658
|
+
] })
|
|
659
|
+
] });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/tui/execution-view.tsx
|
|
663
|
+
import { Box as Box3, Text as Text3, useInput as useInput3 } from "ink";
|
|
664
|
+
import { Spinner as Spinner2, useComponentTheme as useComponentTheme3 } from "@inkjs/ui";
|
|
665
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
666
|
+
function ExecutionView({ workflow, stepProgress, output, onBack, onAbort }) {
|
|
667
|
+
const { styles } = useComponentTheme3("PlanView");
|
|
668
|
+
const titleStyle = styles?.title?.() ?? { color: "cyan", bold: true };
|
|
669
|
+
const isRunning = Array.from(stepProgress.values()).some((s) => s.status === "running");
|
|
670
|
+
useInput3((input, key) => {
|
|
671
|
+
if (isRunning && onAbort && input === "x") {
|
|
672
|
+
onAbort();
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
if (!isRunning && onBack && (key.return || input === "q" || key.escape)) {
|
|
676
|
+
onBack();
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
680
|
+
/* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", flexGrow: 1, children: [
|
|
681
|
+
/* @__PURE__ */ jsxs3(Box3, { justifyContent: "space-between", children: [
|
|
682
|
+
/* @__PURE__ */ jsxs3(Text3, { ...titleStyle, children: [
|
|
683
|
+
"Workflow: ",
|
|
684
|
+
workflow.name
|
|
685
|
+
] }),
|
|
686
|
+
/* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
687
|
+
"Status: ",
|
|
688
|
+
isRunning ? "Running" : "Complete"
|
|
689
|
+
] })
|
|
690
|
+
] }),
|
|
691
|
+
/* @__PURE__ */ jsx3(Text3, { children: "" }),
|
|
692
|
+
/* @__PURE__ */ jsx3(Text3, { bold: true, children: "Steps:" }),
|
|
693
|
+
workflow.steps.map((step, i) => {
|
|
694
|
+
const progress = stepProgress.get(step.id);
|
|
695
|
+
const status = progress?.status ?? "pending";
|
|
696
|
+
const icon = statusIcon(status);
|
|
697
|
+
const durationText = progress?.duration ? ` (${formatDuration(progress.duration)})` : "";
|
|
698
|
+
return /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
699
|
+
/* @__PURE__ */ jsxs3(Text3, { color: statusColor(status), children: [
|
|
700
|
+
icon,
|
|
701
|
+
" ",
|
|
702
|
+
i + 1,
|
|
703
|
+
". ",
|
|
704
|
+
step.description,
|
|
705
|
+
durationText
|
|
706
|
+
] }),
|
|
707
|
+
status === "running" && /* @__PURE__ */ jsx3(Spinner2, {})
|
|
708
|
+
] }, step.id);
|
|
709
|
+
}),
|
|
710
|
+
output.length > 0 && /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
|
|
711
|
+
/* @__PURE__ */ jsx3(Text3, { dimColor: true, children: "\u2500\u2500 Live Output \u2500\u2500" }),
|
|
712
|
+
output.slice(-10).map((line, i) => /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: line }, i))
|
|
713
|
+
] })
|
|
714
|
+
] }),
|
|
715
|
+
/* @__PURE__ */ jsx3(Box3, { marginTop: 1, children: /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: isRunning ? "Press [X] to cancel" : "Press Enter, Q, or Esc to return to chat" }) })
|
|
716
|
+
] });
|
|
717
|
+
}
|
|
718
|
+
function statusIcon(status) {
|
|
719
|
+
switch (status) {
|
|
720
|
+
case "succeeded":
|
|
721
|
+
return "\u2713";
|
|
722
|
+
case "running":
|
|
723
|
+
return "\u25CF";
|
|
724
|
+
case "failed":
|
|
725
|
+
return "\u2717";
|
|
726
|
+
case "skipped":
|
|
727
|
+
return "\u25CB";
|
|
728
|
+
default:
|
|
729
|
+
return " ";
|
|
730
|
+
}
|
|
731
|
+
}
|
|
732
|
+
function statusColor(status) {
|
|
733
|
+
switch (status) {
|
|
734
|
+
case "succeeded":
|
|
735
|
+
return "green";
|
|
736
|
+
case "running":
|
|
737
|
+
return "yellow";
|
|
738
|
+
case "failed":
|
|
739
|
+
return "red";
|
|
740
|
+
case "skipped":
|
|
741
|
+
return "gray";
|
|
742
|
+
default:
|
|
743
|
+
return "gray";
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
function formatDuration(ms) {
|
|
747
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
748
|
+
return `${Math.round(ms / 1e3)}s`;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// src/tui/onboarding.tsx
|
|
752
|
+
import { useState as useState2, useCallback, useMemo as useMemo2 } from "react";
|
|
753
|
+
import { Box as Box4, Text as Text4, useStdout as useStdout2 } from "ink";
|
|
754
|
+
import { TextInput, PasswordInput, ConfirmInput, Spinner as Spinner3, StatusMessage } from "@inkjs/ui";
|
|
755
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
756
|
+
function maskKey(key) {
|
|
757
|
+
if (key.length <= 8) return "****";
|
|
758
|
+
return key.slice(0, 4) + "****" + key.slice(-4);
|
|
759
|
+
}
|
|
760
|
+
function Onboarding({ onComplete, issues }) {
|
|
761
|
+
const { stdout } = useStdout2();
|
|
762
|
+
const cols = stdout?.columns ?? 80;
|
|
763
|
+
const existing = useMemo2(() => loadExistingConfig(), []);
|
|
764
|
+
const initialStep = useMemo2(() => {
|
|
765
|
+
if (!issues || issues.length === 0) return "welcome";
|
|
766
|
+
const errorFields = new Set(issues.filter((i) => i.severity === "error").map((i) => i.field));
|
|
767
|
+
if (errorFields.size === 1 && errorFields.has("claude.api_key")) {
|
|
768
|
+
return "api_key";
|
|
769
|
+
}
|
|
770
|
+
return "welcome";
|
|
771
|
+
}, [issues]);
|
|
772
|
+
const [step, setStep] = useState2(initialStep);
|
|
773
|
+
const [state, setState] = useState2({
|
|
774
|
+
apiKey: existing.apiKey ?? "",
|
|
775
|
+
baseUrl: existing.baseUrl ?? "",
|
|
776
|
+
containerEnabled: existing.containerEnabled ?? false,
|
|
777
|
+
telegramEnabled: existing.telegramEnabled ?? false,
|
|
778
|
+
telegramToken: existing.telegramToken ?? "",
|
|
779
|
+
whatsappEnabled: existing.whatsappEnabled ?? false
|
|
780
|
+
});
|
|
781
|
+
const env = checkEnvironment();
|
|
782
|
+
const gotoApiKey = useCallback(() => {
|
|
783
|
+
setStep(existing.apiKey ? "api_key_existing" : "api_key");
|
|
784
|
+
}, [existing]);
|
|
785
|
+
const gotoBaseUrl = useCallback(() => {
|
|
786
|
+
if (!isDev) return;
|
|
787
|
+
setStep(existing.baseUrl ? "base_url_existing" : "base_url");
|
|
788
|
+
}, [existing]);
|
|
789
|
+
const gotoContainer = useCallback(() => {
|
|
790
|
+
if (!(env.docker && env.dockerRunning)) {
|
|
791
|
+
gotoTelegram();
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
setStep(existing.containerEnabled !== void 0 ? "container_existing" : "container");
|
|
795
|
+
}, [env, existing]);
|
|
796
|
+
const gotoTelegram = useCallback(() => {
|
|
797
|
+
setStep(existing.telegramEnabled !== void 0 ? "telegram_existing" : "telegram");
|
|
798
|
+
}, [existing]);
|
|
799
|
+
const gotoTelegramToken = useCallback(() => {
|
|
800
|
+
setStep(existing.telegramToken ? "telegram_token_existing" : "telegram_token");
|
|
801
|
+
}, [existing]);
|
|
802
|
+
const gotoWhatsApp = useCallback(() => {
|
|
803
|
+
setStep(existing.whatsappEnabled !== void 0 ? "whatsapp_existing" : "whatsapp");
|
|
804
|
+
}, [existing]);
|
|
805
|
+
const afterValidation = useCallback(() => {
|
|
806
|
+
if (env.docker && env.dockerRunning) {
|
|
807
|
+
gotoContainer();
|
|
808
|
+
} else {
|
|
809
|
+
gotoTelegram();
|
|
810
|
+
}
|
|
811
|
+
}, [env, gotoContainer, gotoTelegram]);
|
|
812
|
+
const handleApiKeySubmit = useCallback((value) => {
|
|
813
|
+
const key = value.trim();
|
|
814
|
+
if (!key) return;
|
|
815
|
+
setState((s) => ({ ...s, apiKey: key }));
|
|
816
|
+
if (isDev) {
|
|
817
|
+
gotoBaseUrl();
|
|
818
|
+
} else {
|
|
819
|
+
setStep("validating");
|
|
820
|
+
doValidation(key, "");
|
|
821
|
+
}
|
|
822
|
+
}, [gotoBaseUrl]);
|
|
823
|
+
const handleBaseUrlSubmit = useCallback((value) => {
|
|
824
|
+
const url = value.trim();
|
|
825
|
+
setState((s) => ({ ...s, baseUrl: url }));
|
|
826
|
+
setStep("validating");
|
|
827
|
+
doValidation(state.apiKey, url);
|
|
828
|
+
}, [state.apiKey]);
|
|
829
|
+
const doValidation = useCallback(async (apiKey, baseUrl) => {
|
|
830
|
+
const prevKey = process.env["ANTHROPIC_API_KEY"];
|
|
831
|
+
const prevUrl = process.env["ANTHROPIC_BASE_URL"];
|
|
832
|
+
process.env["ANTHROPIC_API_KEY"] = apiKey;
|
|
833
|
+
if (baseUrl) {
|
|
834
|
+
process.env["ANTHROPIC_BASE_URL"] = baseUrl;
|
|
835
|
+
} else {
|
|
836
|
+
delete process.env["ANTHROPIC_BASE_URL"];
|
|
837
|
+
}
|
|
838
|
+
try {
|
|
839
|
+
const tempConfig = loadConfig();
|
|
840
|
+
const result = await validateAuth(tempConfig);
|
|
841
|
+
if (result.valid) {
|
|
842
|
+
setState((s) => ({ ...s, validationError: void 0 }));
|
|
843
|
+
afterValidation();
|
|
844
|
+
} else {
|
|
845
|
+
setState((s) => ({ ...s, validationError: result.error }));
|
|
846
|
+
setStep("api_key");
|
|
847
|
+
}
|
|
848
|
+
} catch {
|
|
849
|
+
setState((s) => ({ ...s, validationError: "Failed to validate API key" }));
|
|
850
|
+
setStep("api_key");
|
|
851
|
+
} finally {
|
|
852
|
+
if (prevKey !== void 0) process.env["ANTHROPIC_API_KEY"] = prevKey;
|
|
853
|
+
else delete process.env["ANTHROPIC_API_KEY"];
|
|
854
|
+
if (prevUrl !== void 0) process.env["ANTHROPIC_BASE_URL"] = prevUrl;
|
|
855
|
+
else delete process.env["ANTHROPIC_BASE_URL"];
|
|
856
|
+
}
|
|
857
|
+
}, [afterValidation]);
|
|
858
|
+
const handleContainerYes = useCallback(() => {
|
|
859
|
+
setState((s) => ({ ...s, containerEnabled: true }));
|
|
860
|
+
gotoTelegram();
|
|
861
|
+
}, [gotoTelegram]);
|
|
862
|
+
const handleContainerNo = useCallback(() => {
|
|
863
|
+
setState((s) => ({ ...s, containerEnabled: false }));
|
|
864
|
+
gotoTelegram();
|
|
865
|
+
}, [gotoTelegram]);
|
|
866
|
+
const handleTelegramYes = useCallback(() => {
|
|
867
|
+
setState((s) => ({ ...s, telegramEnabled: true }));
|
|
868
|
+
gotoTelegramToken();
|
|
869
|
+
}, [gotoTelegramToken]);
|
|
870
|
+
const handleTelegramNo = useCallback(() => {
|
|
871
|
+
setState((s) => ({ ...s, telegramEnabled: false }));
|
|
872
|
+
gotoWhatsApp();
|
|
873
|
+
}, [gotoWhatsApp]);
|
|
874
|
+
const handleTelegramTokenSubmit = useCallback((value) => {
|
|
875
|
+
setState((s) => ({ ...s, telegramToken: value.trim() }));
|
|
876
|
+
gotoWhatsApp();
|
|
877
|
+
}, [gotoWhatsApp]);
|
|
878
|
+
const finishWithWhatsApp = useCallback((enabled) => {
|
|
879
|
+
const next = { ...state, whatsappEnabled: enabled };
|
|
880
|
+
setState((s) => ({ ...s, whatsappEnabled: enabled }));
|
|
881
|
+
setStep("saving");
|
|
882
|
+
doSaveConfig(next);
|
|
883
|
+
}, [state]);
|
|
884
|
+
const handleWhatsAppYes = useCallback(() => finishWithWhatsApp(true), [finishWithWhatsApp]);
|
|
885
|
+
const handleWhatsAppNo = useCallback(() => finishWithWhatsApp(false), [finishWithWhatsApp]);
|
|
886
|
+
const doSaveConfig = useCallback((finalState) => {
|
|
887
|
+
if (isDev) {
|
|
888
|
+
writeEnvVar("ANTHROPIC_API_KEY", finalState.apiKey);
|
|
889
|
+
if (finalState.baseUrl) {
|
|
890
|
+
writeEnvVar("ANTHROPIC_BASE_URL", finalState.baseUrl);
|
|
891
|
+
}
|
|
892
|
+
} else {
|
|
893
|
+
const configUpdates = {
|
|
894
|
+
claude: {
|
|
895
|
+
api_key: finalState.apiKey,
|
|
896
|
+
...finalState.baseUrl ? { base_url: finalState.baseUrl } : {}
|
|
897
|
+
}
|
|
898
|
+
};
|
|
899
|
+
if (finalState.containerEnabled) {
|
|
900
|
+
configUpdates.container = { enabled: true };
|
|
901
|
+
}
|
|
902
|
+
if (finalState.telegramEnabled && finalState.telegramToken) {
|
|
903
|
+
configUpdates.telegram = {
|
|
904
|
+
enabled: true,
|
|
905
|
+
token: finalState.telegramToken
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
if (finalState.whatsappEnabled) {
|
|
909
|
+
configUpdates.whatsapp = { enabled: true };
|
|
910
|
+
}
|
|
911
|
+
writeConfig(configUpdates);
|
|
912
|
+
}
|
|
913
|
+
const config = loadConfig();
|
|
914
|
+
setStep("done");
|
|
915
|
+
setTimeout(() => onComplete(config), 1500);
|
|
916
|
+
}, [onComplete]);
|
|
917
|
+
const StepLayout = ({ children, input }) => /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, children: [
|
|
918
|
+
/* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, children }),
|
|
919
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: input })
|
|
920
|
+
] });
|
|
921
|
+
return /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
922
|
+
step === "welcome" && /* @__PURE__ */ jsxs4(
|
|
923
|
+
StepLayout,
|
|
924
|
+
{
|
|
925
|
+
input: /* @__PURE__ */ jsx4(
|
|
926
|
+
TextInput,
|
|
927
|
+
{
|
|
928
|
+
placeholder: "Press Enter to continue...",
|
|
929
|
+
onSubmit: () => gotoApiKey()
|
|
930
|
+
}
|
|
931
|
+
),
|
|
932
|
+
children: [
|
|
933
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "cyan", children: "Welcome to CueClaw" }),
|
|
934
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "First time? Let's set up CueClaw." }),
|
|
935
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "\u2500".repeat(Math.max(0, cols - 2)) }),
|
|
936
|
+
/* @__PURE__ */ jsxs4(Box4, { marginTop: 1, children: [
|
|
937
|
+
/* @__PURE__ */ jsx4(Text4, { children: "Press " }),
|
|
938
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, color: "green", children: "Enter" }),
|
|
939
|
+
/* @__PURE__ */ jsx4(Text4, { children: " to begin setup" })
|
|
940
|
+
] })
|
|
941
|
+
]
|
|
942
|
+
}
|
|
943
|
+
),
|
|
944
|
+
step === "api_key_existing" && /* @__PURE__ */ jsxs4(
|
|
945
|
+
StepLayout,
|
|
946
|
+
{
|
|
947
|
+
input: /* @__PURE__ */ jsx4(
|
|
948
|
+
ConfirmInput,
|
|
949
|
+
{
|
|
950
|
+
onConfirm: () => {
|
|
951
|
+
if (isDev) gotoBaseUrl();
|
|
952
|
+
else {
|
|
953
|
+
setStep("validating");
|
|
954
|
+
doValidation(state.apiKey, state.baseUrl);
|
|
955
|
+
}
|
|
956
|
+
},
|
|
957
|
+
onCancel: () => setStep("api_key")
|
|
958
|
+
}
|
|
959
|
+
),
|
|
960
|
+
children: [
|
|
961
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 1: API Key" }),
|
|
962
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
963
|
+
"Found existing API key: ",
|
|
964
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: maskKey(existing.apiKey) })
|
|
965
|
+
] }),
|
|
966
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keep this key?" })
|
|
967
|
+
]
|
|
968
|
+
}
|
|
969
|
+
),
|
|
970
|
+
step === "api_key" && /* @__PURE__ */ jsxs4(
|
|
971
|
+
StepLayout,
|
|
972
|
+
{
|
|
973
|
+
input: /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
974
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "> " }),
|
|
975
|
+
/* @__PURE__ */ jsx4(PasswordInput, { placeholder: "sk-ant-...", onSubmit: handleApiKeySubmit })
|
|
976
|
+
] }),
|
|
977
|
+
children: [
|
|
978
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 1: API Key" }),
|
|
979
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Enter your Anthropic API key (or compatible provider key)" }),
|
|
980
|
+
state.validationError && /* @__PURE__ */ jsx4(Box4, { marginY: 1, children: /* @__PURE__ */ jsx4(StatusMessage, { variant: "error", children: state.validationError }) })
|
|
981
|
+
]
|
|
982
|
+
}
|
|
983
|
+
),
|
|
984
|
+
step === "base_url_existing" && /* @__PURE__ */ jsxs4(
|
|
985
|
+
StepLayout,
|
|
986
|
+
{
|
|
987
|
+
input: /* @__PURE__ */ jsx4(
|
|
988
|
+
ConfirmInput,
|
|
989
|
+
{
|
|
990
|
+
onConfirm: () => {
|
|
991
|
+
setStep("validating");
|
|
992
|
+
doValidation(state.apiKey, state.baseUrl);
|
|
993
|
+
},
|
|
994
|
+
onCancel: () => setStep("base_url")
|
|
995
|
+
}
|
|
996
|
+
),
|
|
997
|
+
children: [
|
|
998
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 1b: Base URL" }),
|
|
999
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1000
|
+
"Found existing base URL: ",
|
|
1001
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: existing.baseUrl })
|
|
1002
|
+
] }),
|
|
1003
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keep this URL?" })
|
|
1004
|
+
]
|
|
1005
|
+
}
|
|
1006
|
+
),
|
|
1007
|
+
step === "base_url" && /* @__PURE__ */ jsxs4(
|
|
1008
|
+
StepLayout,
|
|
1009
|
+
{
|
|
1010
|
+
input: /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
1011
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "> " }),
|
|
1012
|
+
/* @__PURE__ */ jsx4(TextInput, { placeholder: "https://api.anthropic.com", onSubmit: handleBaseUrlSubmit })
|
|
1013
|
+
] }),
|
|
1014
|
+
children: [
|
|
1015
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 1b: Base URL (optional)" }),
|
|
1016
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Custom API base URL (leave empty for api.anthropic.com)" })
|
|
1017
|
+
]
|
|
1018
|
+
}
|
|
1019
|
+
),
|
|
1020
|
+
step === "validating" && /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx4(Spinner3, { label: "Validating API key..." }) }),
|
|
1021
|
+
step === "container_existing" && /* @__PURE__ */ jsxs4(
|
|
1022
|
+
StepLayout,
|
|
1023
|
+
{
|
|
1024
|
+
input: /* @__PURE__ */ jsx4(
|
|
1025
|
+
ConfirmInput,
|
|
1026
|
+
{
|
|
1027
|
+
onConfirm: () => gotoTelegram(),
|
|
1028
|
+
onCancel: () => setStep("container")
|
|
1029
|
+
}
|
|
1030
|
+
),
|
|
1031
|
+
children: [
|
|
1032
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 2: Container Isolation" }),
|
|
1033
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1034
|
+
"Container isolation is already ",
|
|
1035
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: existing.containerEnabled ? "enabled" : "disabled" }),
|
|
1036
|
+
"."
|
|
1037
|
+
] }),
|
|
1038
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keep this setting?" })
|
|
1039
|
+
]
|
|
1040
|
+
}
|
|
1041
|
+
),
|
|
1042
|
+
step === "container" && /* @__PURE__ */ jsxs4(
|
|
1043
|
+
StepLayout,
|
|
1044
|
+
{
|
|
1045
|
+
input: /* @__PURE__ */ jsx4(ConfirmInput, { onConfirm: handleContainerYes, onCancel: handleContainerNo }),
|
|
1046
|
+
children: [
|
|
1047
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 2: Container Isolation" }),
|
|
1048
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Docker detected. Enable container isolation for safer execution?" })
|
|
1049
|
+
]
|
|
1050
|
+
}
|
|
1051
|
+
),
|
|
1052
|
+
step === "telegram_existing" && /* @__PURE__ */ jsxs4(
|
|
1053
|
+
StepLayout,
|
|
1054
|
+
{
|
|
1055
|
+
input: /* @__PURE__ */ jsx4(
|
|
1056
|
+
ConfirmInput,
|
|
1057
|
+
{
|
|
1058
|
+
onConfirm: () => {
|
|
1059
|
+
if (state.telegramEnabled) gotoTelegramToken();
|
|
1060
|
+
else gotoWhatsApp();
|
|
1061
|
+
},
|
|
1062
|
+
onCancel: () => setStep("telegram")
|
|
1063
|
+
}
|
|
1064
|
+
),
|
|
1065
|
+
children: [
|
|
1066
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 3: Telegram Bot" }),
|
|
1067
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1068
|
+
"Telegram bot is already ",
|
|
1069
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: existing.telegramEnabled ? "enabled" : "disabled" }),
|
|
1070
|
+
"."
|
|
1071
|
+
] }),
|
|
1072
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keep this setting?" })
|
|
1073
|
+
]
|
|
1074
|
+
}
|
|
1075
|
+
),
|
|
1076
|
+
step === "telegram" && /* @__PURE__ */ jsxs4(
|
|
1077
|
+
StepLayout,
|
|
1078
|
+
{
|
|
1079
|
+
input: /* @__PURE__ */ jsx4(ConfirmInput, { onConfirm: handleTelegramYes, onCancel: handleTelegramNo }),
|
|
1080
|
+
children: [
|
|
1081
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 3: Telegram Bot" }),
|
|
1082
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Set up a Telegram bot for remote workflow management?" })
|
|
1083
|
+
]
|
|
1084
|
+
}
|
|
1085
|
+
),
|
|
1086
|
+
step === "telegram_token_existing" && /* @__PURE__ */ jsxs4(
|
|
1087
|
+
StepLayout,
|
|
1088
|
+
{
|
|
1089
|
+
input: /* @__PURE__ */ jsx4(
|
|
1090
|
+
ConfirmInput,
|
|
1091
|
+
{
|
|
1092
|
+
onConfirm: () => gotoWhatsApp(),
|
|
1093
|
+
onCancel: () => setStep("telegram_token")
|
|
1094
|
+
}
|
|
1095
|
+
),
|
|
1096
|
+
children: [
|
|
1097
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Telegram Bot Token" }),
|
|
1098
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1099
|
+
"Found existing token: ",
|
|
1100
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: maskKey(existing.telegramToken) })
|
|
1101
|
+
] }),
|
|
1102
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keep this token?" })
|
|
1103
|
+
]
|
|
1104
|
+
}
|
|
1105
|
+
),
|
|
1106
|
+
step === "telegram_token" && /* @__PURE__ */ jsxs4(
|
|
1107
|
+
StepLayout,
|
|
1108
|
+
{
|
|
1109
|
+
input: /* @__PURE__ */ jsxs4(Box4, { children: [
|
|
1110
|
+
/* @__PURE__ */ jsx4(Text4, { color: "green", children: "> " }),
|
|
1111
|
+
/* @__PURE__ */ jsx4(PasswordInput, { placeholder: "123456:ABC...", onSubmit: handleTelegramTokenSubmit })
|
|
1112
|
+
] }),
|
|
1113
|
+
children: [
|
|
1114
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Telegram Bot Token" }),
|
|
1115
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Paste the token from @BotFather" })
|
|
1116
|
+
]
|
|
1117
|
+
}
|
|
1118
|
+
),
|
|
1119
|
+
step === "whatsapp_existing" && /* @__PURE__ */ jsxs4(
|
|
1120
|
+
StepLayout,
|
|
1121
|
+
{
|
|
1122
|
+
input: /* @__PURE__ */ jsx4(
|
|
1123
|
+
ConfirmInput,
|
|
1124
|
+
{
|
|
1125
|
+
onConfirm: () => finishWithWhatsApp(state.whatsappEnabled),
|
|
1126
|
+
onCancel: () => setStep("whatsapp")
|
|
1127
|
+
}
|
|
1128
|
+
),
|
|
1129
|
+
children: [
|
|
1130
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 4: WhatsApp" }),
|
|
1131
|
+
/* @__PURE__ */ jsxs4(Text4, { children: [
|
|
1132
|
+
"WhatsApp is already ",
|
|
1133
|
+
/* @__PURE__ */ jsx4(Text4, { color: "yellow", children: existing.whatsappEnabled ? "enabled" : "disabled" }),
|
|
1134
|
+
"."
|
|
1135
|
+
] }),
|
|
1136
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Keep this setting?" })
|
|
1137
|
+
]
|
|
1138
|
+
}
|
|
1139
|
+
),
|
|
1140
|
+
step === "whatsapp" && /* @__PURE__ */ jsxs4(
|
|
1141
|
+
StepLayout,
|
|
1142
|
+
{
|
|
1143
|
+
input: /* @__PURE__ */ jsx4(ConfirmInput, { onConfirm: handleWhatsAppYes, onCancel: handleWhatsAppNo }),
|
|
1144
|
+
children: [
|
|
1145
|
+
/* @__PURE__ */ jsx4(Text4, { bold: true, children: "Step 4: WhatsApp" }),
|
|
1146
|
+
/* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Enable WhatsApp channel? (Requires QR scan on daemon start)" })
|
|
1147
|
+
]
|
|
1148
|
+
}
|
|
1149
|
+
),
|
|
1150
|
+
step === "saving" && /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", flexGrow: 1, children: /* @__PURE__ */ jsx4(Spinner3, { label: "Saving configuration..." }) }),
|
|
1151
|
+
step === "done" && /* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", flexGrow: 1, children: [
|
|
1152
|
+
/* @__PURE__ */ jsx4(StatusMessage, { variant: "success", children: "Configuration saved!" }),
|
|
1153
|
+
/* @__PURE__ */ jsxs4(Box4, { flexDirection: "column", marginTop: 1, marginLeft: 2, children: [
|
|
1154
|
+
/* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1155
|
+
"API Key: ****",
|
|
1156
|
+
state.apiKey.slice(-4)
|
|
1157
|
+
] }),
|
|
1158
|
+
state.baseUrl && /* @__PURE__ */ jsxs4(Text4, { dimColor: true, children: [
|
|
1159
|
+
"Base URL: ",
|
|
1160
|
+
state.baseUrl
|
|
1161
|
+
] }),
|
|
1162
|
+
state.containerEnabled && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Container isolation: enabled" }),
|
|
1163
|
+
state.telegramEnabled && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "Telegram bot: configured" }),
|
|
1164
|
+
state.whatsappEnabled && /* @__PURE__ */ jsx4(Text4, { dimColor: true, children: "WhatsApp: enabled" })
|
|
1165
|
+
] }),
|
|
1166
|
+
/* @__PURE__ */ jsx4(Box4, { marginTop: 1, children: /* @__PURE__ */ jsx4(Text4, { children: "Starting CueClaw..." }) })
|
|
1167
|
+
] })
|
|
1168
|
+
] });
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
// src/tui/renderers.tsx
|
|
1172
|
+
import { Box as Box5, Text as Text5 } from "ink";
|
|
1173
|
+
import { Fragment, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1174
|
+
function phaseColor(phase) {
|
|
1175
|
+
switch (phase) {
|
|
1176
|
+
case "executing":
|
|
1177
|
+
return "yellow";
|
|
1178
|
+
case "active":
|
|
1179
|
+
return "green";
|
|
1180
|
+
case "completed":
|
|
1181
|
+
return "green";
|
|
1182
|
+
case "failed":
|
|
1183
|
+
return "red";
|
|
1184
|
+
case "paused":
|
|
1185
|
+
return "gray";
|
|
1186
|
+
default:
|
|
1187
|
+
return "white";
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function WorkflowTable({ workflows }) {
|
|
1191
|
+
if (workflows.length === 0) {
|
|
1192
|
+
return /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: "No workflows found." });
|
|
1193
|
+
}
|
|
1194
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1195
|
+
/* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1196
|
+
/* @__PURE__ */ jsx5(Box5, { width: 14, children: /* @__PURE__ */ jsx5(Text5, { bold: true, dimColor: true, children: "ID" }) }),
|
|
1197
|
+
/* @__PURE__ */ jsx5(Box5, { width: 28, children: /* @__PURE__ */ jsx5(Text5, { bold: true, dimColor: true, children: "Name" }) }),
|
|
1198
|
+
/* @__PURE__ */ jsx5(Box5, { width: 14, children: /* @__PURE__ */ jsx5(Text5, { bold: true, dimColor: true, children: "Phase" }) }),
|
|
1199
|
+
/* @__PURE__ */ jsx5(Box5, { width: 16, children: /* @__PURE__ */ jsx5(Text5, { bold: true, dimColor: true, children: "Trigger" }) })
|
|
1200
|
+
] }),
|
|
1201
|
+
workflows.map((wf) => {
|
|
1202
|
+
const trigger = wf.trigger.type === "poll" ? `poll (${wf.trigger.interval_seconds}s)` : wf.trigger.type === "cron" ? `cron` : "manual";
|
|
1203
|
+
return /* @__PURE__ */ jsxs5(Box5, { children: [
|
|
1204
|
+
/* @__PURE__ */ jsx5(Box5, { width: 14, children: /* @__PURE__ */ jsx5(Text5, { children: wf.id.slice(0, 12) }) }),
|
|
1205
|
+
/* @__PURE__ */ jsx5(Box5, { width: 28, children: /* @__PURE__ */ jsx5(Text5, { children: wf.name.slice(0, 26) }) }),
|
|
1206
|
+
/* @__PURE__ */ jsx5(Box5, { width: 14, children: /* @__PURE__ */ jsx5(Text5, { color: phaseColor(wf.phase), children: wf.phase }) }),
|
|
1207
|
+
/* @__PURE__ */ jsx5(Box5, { width: 16, children: /* @__PURE__ */ jsx5(Text5, { dimColor: true, children: trigger }) })
|
|
1208
|
+
] }, wf.id);
|
|
1209
|
+
}),
|
|
1210
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1211
|
+
"\n",
|
|
1212
|
+
"Use /status ",
|
|
1213
|
+
"<id>",
|
|
1214
|
+
" to view details, /pause /resume /delete to manage."
|
|
1215
|
+
] })
|
|
1216
|
+
] });
|
|
1217
|
+
}
|
|
1218
|
+
function WorkflowDetail({ workflow, latestRun, stepRuns }) {
|
|
1219
|
+
const trigger = workflow.trigger.type === "poll" ? `poll (${workflow.trigger.interval_seconds}s)` : workflow.trigger.type === "cron" ? `cron (${workflow.trigger.expression})` : "manual";
|
|
1220
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", children: [
|
|
1221
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: workflow.name }),
|
|
1222
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1223
|
+
"ID: ",
|
|
1224
|
+
workflow.id
|
|
1225
|
+
] }),
|
|
1226
|
+
/* @__PURE__ */ jsxs5(Text5, { children: [
|
|
1227
|
+
"Phase: ",
|
|
1228
|
+
/* @__PURE__ */ jsx5(Text5, { color: phaseColor(workflow.phase), children: workflow.phase })
|
|
1229
|
+
] }),
|
|
1230
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1231
|
+
"Trigger: ",
|
|
1232
|
+
trigger
|
|
1233
|
+
] }),
|
|
1234
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1235
|
+
"Created: ",
|
|
1236
|
+
workflow.created_at
|
|
1237
|
+
] }),
|
|
1238
|
+
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
1239
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Steps:" }),
|
|
1240
|
+
workflow.steps.map((step, i) => {
|
|
1241
|
+
const deps = step.depends_on.length > 0 ? ` (after: ${step.depends_on.join(", ")})` : "";
|
|
1242
|
+
return /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1243
|
+
" ",
|
|
1244
|
+
i + 1,
|
|
1245
|
+
". ",
|
|
1246
|
+
step.id,
|
|
1247
|
+
": ",
|
|
1248
|
+
step.description.slice(0, 60),
|
|
1249
|
+
deps
|
|
1250
|
+
] }, step.id);
|
|
1251
|
+
}),
|
|
1252
|
+
latestRun && /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1253
|
+
/* @__PURE__ */ jsx5(Text5, { children: "" }),
|
|
1254
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, children: "Latest Run:" }),
|
|
1255
|
+
/* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1256
|
+
" Status: ",
|
|
1257
|
+
latestRun.status
|
|
1258
|
+
] }),
|
|
1259
|
+
latestRun.error && /* @__PURE__ */ jsxs5(Text5, { color: "red", children: [
|
|
1260
|
+
" Error: ",
|
|
1261
|
+
latestRun.error
|
|
1262
|
+
] }),
|
|
1263
|
+
stepRuns && stepRuns.length > 0 && /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1264
|
+
/* @__PURE__ */ jsx5(Text5, { dimColor: true, children: " Step results:" }),
|
|
1265
|
+
stepRuns.map((sr) => /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
|
|
1266
|
+
" ",
|
|
1267
|
+
sr.step_id,
|
|
1268
|
+
": ",
|
|
1269
|
+
sr.status,
|
|
1270
|
+
sr.output_json ? ` \u2014 ${sr.output_json.slice(0, 50)}` : ""
|
|
1271
|
+
] }, sr.id))
|
|
1272
|
+
] })
|
|
1273
|
+
] })
|
|
1274
|
+
] });
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// src/tui/daemon-bridge.ts
|
|
1278
|
+
async function initDaemonBridge(db, config, cwd, options) {
|
|
1279
|
+
const status = getServiceStatus();
|
|
1280
|
+
if (status === "running") {
|
|
1281
|
+
logger.info("External daemon detected, TUI will operate as frontend only");
|
|
1282
|
+
return {
|
|
1283
|
+
triggerLoop: null,
|
|
1284
|
+
router: null,
|
|
1285
|
+
botChannels: [],
|
|
1286
|
+
isExternal: true
|
|
1287
|
+
};
|
|
1288
|
+
}
|
|
1289
|
+
const router = new MessageRouter(db, config, cwd);
|
|
1290
|
+
const botChannels = [];
|
|
1291
|
+
if (!options?.skipBots) {
|
|
1292
|
+
await connectBotChannels(config, router, botChannels);
|
|
1293
|
+
}
|
|
1294
|
+
const triggerLoop = new TriggerLoop(db, router, cwd, 5);
|
|
1295
|
+
triggerLoop.start();
|
|
1296
|
+
router.start();
|
|
1297
|
+
logger.info("In-process daemon bridge started");
|
|
1298
|
+
return {
|
|
1299
|
+
triggerLoop,
|
|
1300
|
+
router,
|
|
1301
|
+
botChannels,
|
|
1302
|
+
isExternal: false
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
async function startBotChannels(bridge, config) {
|
|
1306
|
+
if (bridge.isExternal || !bridge.router) return;
|
|
1307
|
+
await connectBotChannels(config, bridge.router, bridge.botChannels);
|
|
1308
|
+
}
|
|
1309
|
+
async function connectBotChannels(config, router, botChannels) {
|
|
1310
|
+
if (config.telegram?.enabled && config.telegram.token) {
|
|
1311
|
+
try {
|
|
1312
|
+
const { TelegramChannel } = await import("./telegram-P6DBJ7WZ.js");
|
|
1313
|
+
const tg = new TelegramChannel(
|
|
1314
|
+
config.telegram.token,
|
|
1315
|
+
config.telegram.allowed_users ?? [],
|
|
1316
|
+
(jid, msg) => router.handleInbound("telegram", jid, msg)
|
|
1317
|
+
);
|
|
1318
|
+
router.registerChannel(tg);
|
|
1319
|
+
await tg.connect();
|
|
1320
|
+
tg.onCallback((wfId, action, chatId) => router.handleCallbackAction("telegram", chatId, wfId, action));
|
|
1321
|
+
botChannels.push(tg);
|
|
1322
|
+
logger.info("Telegram channel started (in-process)");
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
logger.error({ err }, "Failed to start Telegram channel");
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
if (config.whatsapp?.enabled) {
|
|
1328
|
+
try {
|
|
1329
|
+
const { WhatsAppChannel } = await import("./whatsapp-HFMOFSFI.js");
|
|
1330
|
+
const wa = new WhatsAppChannel(
|
|
1331
|
+
config.whatsapp.auth_dir ?? `${process.env["HOME"]}/.cueclaw/auth/whatsapp`,
|
|
1332
|
+
config.whatsapp.allowed_jids ?? [],
|
|
1333
|
+
(jid, msg) => router.handleInbound("whatsapp", jid, msg)
|
|
1334
|
+
);
|
|
1335
|
+
router.registerChannel(wa);
|
|
1336
|
+
await wa.connect();
|
|
1337
|
+
botChannels.push(wa);
|
|
1338
|
+
logger.info("WhatsApp channel started (in-process)");
|
|
1339
|
+
} catch (err) {
|
|
1340
|
+
logger.error({ err }, "Failed to start WhatsApp channel");
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
async function stopDaemonBridge(bridge) {
|
|
1345
|
+
if (bridge.isExternal) return;
|
|
1346
|
+
bridge.triggerLoop?.stop();
|
|
1347
|
+
bridge.router?.stop();
|
|
1348
|
+
for (const channel of bridge.botChannels) {
|
|
1349
|
+
try {
|
|
1350
|
+
await channel.disconnect();
|
|
1351
|
+
} catch (err) {
|
|
1352
|
+
logger.error({ err, channel: channel.name }, "Failed to disconnect channel");
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
logger.info("Daemon bridge stopped");
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/planner-session.ts
|
|
1359
|
+
import { nanoid } from "nanoid";
|
|
1360
|
+
async function startPlannerSession(userMessage, config, callbacks) {
|
|
1361
|
+
const session = {
|
|
1362
|
+
id: `ps_${nanoid()}`,
|
|
1363
|
+
messages: [{ role: "user", content: userMessage }],
|
|
1364
|
+
status: "conversing",
|
|
1365
|
+
workflow: null
|
|
1366
|
+
};
|
|
1367
|
+
const turn = await runPlannerTurn(session, config, callbacks);
|
|
1368
|
+
return { session, turn };
|
|
1369
|
+
}
|
|
1370
|
+
async function continuePlannerSession(session, userMessage, config, callbacks) {
|
|
1371
|
+
session.messages.push({ role: "user", content: userMessage });
|
|
1372
|
+
const turn = await runPlannerTurn(session, config, callbacks);
|
|
1373
|
+
return { session, turn };
|
|
1374
|
+
}
|
|
1375
|
+
function cancelPlannerSession(session) {
|
|
1376
|
+
session.status = "cancelled";
|
|
1377
|
+
}
|
|
1378
|
+
async function runPlannerTurn(session, config, callbacks) {
|
|
1379
|
+
const anthropic = createAnthropicClient(config);
|
|
1380
|
+
const systemPrompt = buildPlannerSystemPrompt(config) + `
|
|
1381
|
+
|
|
1382
|
+
## Conversation Mode
|
|
1383
|
+
|
|
1384
|
+
You are in multi-turn conversation mode. You have three tools:
|
|
1385
|
+
|
|
1386
|
+
1. **ask_question** \u2014 Ask the user clarifying questions when more information is needed.
|
|
1387
|
+
Use this when the user's description is vague, missing key details (trigger type, frequency, target repos, filters, output format, etc.), or could be interpreted multiple ways.
|
|
1388
|
+
Also use this to ask the user for missing credentials \u2014 e.g., "This workflow needs a GITHUB_TOKEN. Could you provide one?"
|
|
1389
|
+
|
|
1390
|
+
2. **set_secret** \u2014 Store a credential the user provides (e.g., API token, webhook URL).
|
|
1391
|
+
Only call this AFTER the user explicitly provides the secret value. Never guess or fabricate values.
|
|
1392
|
+
|
|
1393
|
+
3. **create_workflow** \u2014 Generate the final workflow plan when you have sufficient information.
|
|
1394
|
+
Only use this when you are confident you understand the user's requirements.
|
|
1395
|
+
|
|
1396
|
+
Guidelines:
|
|
1397
|
+
- For simple, clear requests, you may generate the plan immediately.
|
|
1398
|
+
- For complex or ambiguous requests, ask 1-3 focused questions first.
|
|
1399
|
+
- If a workflow requires credentials not listed in Available Credentials, ask the user for them before creating the workflow.
|
|
1400
|
+
- Be concise and helpful in your questions.
|
|
1401
|
+
- Respond in the same language the user uses.`;
|
|
1402
|
+
let response;
|
|
1403
|
+
try {
|
|
1404
|
+
if (callbacks?.onToken) {
|
|
1405
|
+
const stream = anthropic.messages.stream({
|
|
1406
|
+
model: config.claude.planner.model,
|
|
1407
|
+
max_tokens: 4096,
|
|
1408
|
+
system: systemPrompt,
|
|
1409
|
+
messages: session.messages,
|
|
1410
|
+
tools: [askQuestionTool, setSecretTool, plannerTool]
|
|
1411
|
+
});
|
|
1412
|
+
stream.on("text", (text) => {
|
|
1413
|
+
callbacks.onToken?.(text);
|
|
1414
|
+
});
|
|
1415
|
+
response = await stream.finalMessage();
|
|
1416
|
+
} else {
|
|
1417
|
+
response = await anthropic.messages.create({
|
|
1418
|
+
model: config.claude.planner.model,
|
|
1419
|
+
max_tokens: 4096,
|
|
1420
|
+
system: systemPrompt,
|
|
1421
|
+
messages: session.messages,
|
|
1422
|
+
tools: [askQuestionTool, setSecretTool, plannerTool]
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1425
|
+
} catch (err) {
|
|
1426
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1427
|
+
logger.error({ err }, "Planner session API request failed");
|
|
1428
|
+
return { type: "error", content: `API request failed: ${detail}` };
|
|
1429
|
+
}
|
|
1430
|
+
const rawResponse = response;
|
|
1431
|
+
if (rawResponse.type === "error" || rawResponse.error) {
|
|
1432
|
+
const errMsg = rawResponse.error?.message ?? JSON.stringify(rawResponse.error ?? rawResponse);
|
|
1433
|
+
return { type: "error", content: `API error: ${errMsg}` };
|
|
1434
|
+
}
|
|
1435
|
+
const result = parsePlannerToolResponse(response);
|
|
1436
|
+
session.messages.push({ role: "assistant", content: response.content });
|
|
1437
|
+
switch (result.type) {
|
|
1438
|
+
case "question": {
|
|
1439
|
+
const toolBlock = response.content.find((b) => b.type === "tool_use" && b.name === "ask_question");
|
|
1440
|
+
if (toolBlock && toolBlock.type === "tool_use") {
|
|
1441
|
+
session.messages.push({
|
|
1442
|
+
role: "user",
|
|
1443
|
+
content: [{ type: "tool_result", tool_use_id: toolBlock.id, content: "Question delivered to user. Waiting for response." }]
|
|
1444
|
+
});
|
|
1445
|
+
}
|
|
1446
|
+
return { type: "question", content: result.question };
|
|
1447
|
+
}
|
|
1448
|
+
case "set_secret": {
|
|
1449
|
+
if (isDev) {
|
|
1450
|
+
writeEnvVar(result.key, result.value);
|
|
1451
|
+
} else {
|
|
1452
|
+
process.env[result.key] = result.value;
|
|
1453
|
+
}
|
|
1454
|
+
logger.info({ key: result.key }, "Secret stored via planner");
|
|
1455
|
+
const secretToolBlock = response.content.find((b) => b.type === "tool_use" && b.name === "set_secret");
|
|
1456
|
+
if (secretToolBlock && secretToolBlock.type === "tool_use") {
|
|
1457
|
+
session.messages.push({
|
|
1458
|
+
role: "user",
|
|
1459
|
+
content: [{ type: "tool_result", tool_use_id: secretToolBlock.id, content: `Secret ${result.key} saved successfully.` }]
|
|
1460
|
+
});
|
|
1461
|
+
}
|
|
1462
|
+
return runPlannerTurn(session, config, callbacks);
|
|
1463
|
+
}
|
|
1464
|
+
case "plan": {
|
|
1465
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1466
|
+
const workflow = {
|
|
1467
|
+
...result.plannerOutput,
|
|
1468
|
+
schema_version: "1.0",
|
|
1469
|
+
id: `wf_${nanoid()}`,
|
|
1470
|
+
phase: "awaiting_confirmation",
|
|
1471
|
+
created_at: now,
|
|
1472
|
+
updated_at: now
|
|
1473
|
+
};
|
|
1474
|
+
session.workflow = workflow;
|
|
1475
|
+
session.status = "plan_ready";
|
|
1476
|
+
return { type: "plan", content: `Generated plan: "${workflow.name}"`, workflow };
|
|
1477
|
+
}
|
|
1478
|
+
case "text":
|
|
1479
|
+
return { type: "text", content: result.text };
|
|
1480
|
+
case "error":
|
|
1481
|
+
return { type: "error", content: result.error };
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
// src/tui/app.tsx
|
|
1486
|
+
import { Fragment as Fragment2, jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1487
|
+
function appReducer(state, action) {
|
|
1488
|
+
switch (action.type) {
|
|
1489
|
+
case "SHOW_CHAT":
|
|
1490
|
+
return { ...state, view: "chat" };
|
|
1491
|
+
case "SHOW_ONBOARDING":
|
|
1492
|
+
return { ...state, view: "onboarding" };
|
|
1493
|
+
case "SHOW_PLAN":
|
|
1494
|
+
return { ...state, view: "plan", workflow: action.workflow };
|
|
1495
|
+
case "SHOW_EXECUTION":
|
|
1496
|
+
return { ...state, view: "execution", workflow: action.workflow, stepProgress: /* @__PURE__ */ new Map(), executionOutput: [] };
|
|
1497
|
+
case "SHOW_EXIT_PROMPT":
|
|
1498
|
+
return { ...state, view: "exit_prompt" };
|
|
1499
|
+
case "ADD_MESSAGE":
|
|
1500
|
+
return { ...state, messages: [...state.messages, action.message] };
|
|
1501
|
+
case "SET_MESSAGES":
|
|
1502
|
+
return { ...state, messages: action.messages };
|
|
1503
|
+
case "SET_GENERATING":
|
|
1504
|
+
return { ...state, isGenerating: action.value };
|
|
1505
|
+
case "SET_STREAMING_TEXT":
|
|
1506
|
+
return { ...state, streamingText: action.text };
|
|
1507
|
+
case "UPDATE_STEP":
|
|
1508
|
+
return { ...state, stepProgress: new Map(state.stepProgress).set(action.stepId, action.progress) };
|
|
1509
|
+
case "ADD_OUTPUT":
|
|
1510
|
+
return { ...state, executionOutput: [...state.executionOutput, action.line] };
|
|
1511
|
+
default:
|
|
1512
|
+
return state;
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
function App({ cwd, skipOnboarding }) {
|
|
1516
|
+
const { exit } = useApp();
|
|
1517
|
+
const validation = useMemo3(() => validateConfig(), []);
|
|
1518
|
+
const needsSetup = !skipOnboarding && !validation.valid;
|
|
1519
|
+
const configIssues = validation.issues.filter((i) => i.severity === "error");
|
|
1520
|
+
const [config, setConfig] = useState3(() => {
|
|
1521
|
+
if (needsSetup) return null;
|
|
1522
|
+
try {
|
|
1523
|
+
return loadConfig();
|
|
1524
|
+
} catch {
|
|
1525
|
+
return null;
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
const db = useMemo3(() => initDb(), []);
|
|
1529
|
+
const bridgeRef = useRef2(null);
|
|
1530
|
+
const abortRef = useRef2(null);
|
|
1531
|
+
const abortMapRef = useRef2(/* @__PURE__ */ new Map());
|
|
1532
|
+
const [isExecuting, setIsExecuting] = useState3(false);
|
|
1533
|
+
const [daemonStatus, setDaemonStatus] = useState3("none");
|
|
1534
|
+
const plannerSessionRef = useRef2(null);
|
|
1535
|
+
useEffect2(() => {
|
|
1536
|
+
return onLogLine((line) => {
|
|
1537
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: line } });
|
|
1538
|
+
});
|
|
1539
|
+
}, []);
|
|
1540
|
+
const initialState = {
|
|
1541
|
+
view: needsSetup ? "onboarding" : "chat",
|
|
1542
|
+
messages: [],
|
|
1543
|
+
workflow: null,
|
|
1544
|
+
isGenerating: false,
|
|
1545
|
+
stepProgress: /* @__PURE__ */ new Map(),
|
|
1546
|
+
executionOutput: [],
|
|
1547
|
+
streamingText: ""
|
|
1548
|
+
};
|
|
1549
|
+
const [state, dispatch] = useReducer2(appReducer, initialState);
|
|
1550
|
+
const hasConfiguredBots = !!(config?.telegram?.enabled && config?.telegram?.token || config?.whatsapp?.enabled);
|
|
1551
|
+
useEffect2(() => {
|
|
1552
|
+
if (!config) return;
|
|
1553
|
+
let cancelled = false;
|
|
1554
|
+
setDaemonStatus("starting");
|
|
1555
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: "Starting daemon..." } });
|
|
1556
|
+
initDaemonBridge(db, config, cwd, { skipBots: true }).then((bridge) => {
|
|
1557
|
+
if (cancelled) {
|
|
1558
|
+
stopDaemonBridge(bridge);
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1561
|
+
bridgeRef.current = bridge;
|
|
1562
|
+
setDaemonStatus(bridge.isExternal ? "external" : "running");
|
|
1563
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: bridge.isExternal ? "Background service detected." : "Daemon started." } });
|
|
1564
|
+
if (!bridge.isExternal && hasConfiguredBots) {
|
|
1565
|
+
const botList = [];
|
|
1566
|
+
if (config.telegram?.enabled && config.telegram?.token) botList.push("Telegram");
|
|
1567
|
+
if (config.whatsapp?.enabled) botList.push("WhatsApp");
|
|
1568
|
+
dispatch({
|
|
1569
|
+
type: "ADD_MESSAGE",
|
|
1570
|
+
message: { role: "assistant", text: `${botList.join(" and ")} bot${botList.length > 1 ? "s are" : " is"} configured. Type /bot start to launch.` }
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
}).catch((err) => {
|
|
1574
|
+
logger.error({ err }, "Failed to start daemon bridge");
|
|
1575
|
+
setDaemonStatus("none");
|
|
1576
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: "Failed to start daemon." } });
|
|
1577
|
+
});
|
|
1578
|
+
return () => {
|
|
1579
|
+
cancelled = true;
|
|
1580
|
+
if (bridgeRef.current) {
|
|
1581
|
+
stopDaemonBridge(bridgeRef.current);
|
|
1582
|
+
bridgeRef.current = null;
|
|
1583
|
+
}
|
|
1584
|
+
};
|
|
1585
|
+
}, [config, db, cwd]);
|
|
1586
|
+
useInput4((input, key) => {
|
|
1587
|
+
if (input === "c" && key.ctrl) {
|
|
1588
|
+
const bridge = bridgeRef.current;
|
|
1589
|
+
const hasRunning = isExecuting || bridge && !bridge.isExternal;
|
|
1590
|
+
if (hasRunning && state.view !== "exit_prompt") {
|
|
1591
|
+
dispatch({ type: "SHOW_EXIT_PROMPT" });
|
|
1592
|
+
return;
|
|
1593
|
+
}
|
|
1594
|
+
if (bridge) {
|
|
1595
|
+
stopDaemonBridge(bridge).finally(() => {
|
|
1596
|
+
exit();
|
|
1597
|
+
process.exit(0);
|
|
1598
|
+
});
|
|
1599
|
+
} else {
|
|
1600
|
+
exit();
|
|
1601
|
+
process.exit(0);
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
if (state.view === "onboarding" || state.view === "exit_prompt") return;
|
|
1605
|
+
if (input === "d" && key.ctrl) {
|
|
1606
|
+
const workflows = listWorkflows(db);
|
|
1607
|
+
dispatch({
|
|
1608
|
+
type: "ADD_MESSAGE",
|
|
1609
|
+
message: {
|
|
1610
|
+
role: "assistant",
|
|
1611
|
+
content: React2.createElement(WorkflowTable, { workflows })
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
}
|
|
1615
|
+
});
|
|
1616
|
+
const commandCtx = useMemo3(() => ({
|
|
1617
|
+
db,
|
|
1618
|
+
config,
|
|
1619
|
+
cwd,
|
|
1620
|
+
bridge: bridgeRef.current,
|
|
1621
|
+
addMessage: (msg) => dispatch({ type: "ADD_MESSAGE", message: msg }),
|
|
1622
|
+
clearMessages: () => dispatch({ type: "SET_MESSAGES", messages: [] }),
|
|
1623
|
+
setConfig
|
|
1624
|
+
}), [db, config, cwd]);
|
|
1625
|
+
const handleOnboardingComplete = useCallback2((newConfig) => {
|
|
1626
|
+
setConfig(newConfig);
|
|
1627
|
+
dispatch({ type: "SHOW_CHAT" });
|
|
1628
|
+
}, []);
|
|
1629
|
+
const handleChatSubmit = useCallback2(async (text) => {
|
|
1630
|
+
if (!config) return;
|
|
1631
|
+
const parsed = parseSlashCommand(text);
|
|
1632
|
+
if (parsed) {
|
|
1633
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "user", text } });
|
|
1634
|
+
if (parsed.name === "cancel") {
|
|
1635
|
+
if (plannerSessionRef.current) {
|
|
1636
|
+
cancelPlannerSession(plannerSessionRef.current);
|
|
1637
|
+
plannerSessionRef.current = null;
|
|
1638
|
+
}
|
|
1639
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: "Conversation cancelled." } });
|
|
1640
|
+
return;
|
|
1641
|
+
}
|
|
1642
|
+
if (parsed.name === "new" && !parsed.args) {
|
|
1643
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: "Usage: /new <workflow description>" } });
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (parsed.name === "new" && parsed.args) {
|
|
1647
|
+
plannerSessionRef.current = null;
|
|
1648
|
+
dispatch({ type: "SET_GENERATING", value: true });
|
|
1649
|
+
dispatch({ type: "SET_STREAMING_TEXT", text: "" });
|
|
1650
|
+
try {
|
|
1651
|
+
const { generatePlan } = await import("./planner-UU4T5IEN.js");
|
|
1652
|
+
const workflow = await generatePlan(parsed.args, config);
|
|
1653
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: `Generated plan: "${workflow.name}"` } });
|
|
1654
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1655
|
+
dispatch({ type: "SHOW_PLAN", workflow });
|
|
1656
|
+
} catch (err) {
|
|
1657
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1658
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: `Error: ${msg}` } });
|
|
1659
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1660
|
+
}
|
|
1661
|
+
return;
|
|
1662
|
+
}
|
|
1663
|
+
if (parsed.name === "list" || parsed.name === "ls") {
|
|
1664
|
+
const workflows = listWorkflows(db);
|
|
1665
|
+
dispatch({
|
|
1666
|
+
type: "ADD_MESSAGE",
|
|
1667
|
+
message: {
|
|
1668
|
+
role: "assistant",
|
|
1669
|
+
content: React2.createElement(WorkflowTable, { workflows })
|
|
1670
|
+
}
|
|
1671
|
+
});
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
if ((parsed.name === "status" || parsed.name === "st") && parsed.args) {
|
|
1675
|
+
const wf = getWorkflow(db, parsed.args) ?? listWorkflows(db).find((w) => w.id.startsWith(parsed.args));
|
|
1676
|
+
if (wf) {
|
|
1677
|
+
const runs = getWorkflowRunsByWorkflowId(db, wf.id);
|
|
1678
|
+
const latestRun = runs[0];
|
|
1679
|
+
const stepRuns = latestRun ? getStepRunsByRunId(db, latestRun.id) : void 0;
|
|
1680
|
+
dispatch({
|
|
1681
|
+
type: "ADD_MESSAGE",
|
|
1682
|
+
message: {
|
|
1683
|
+
role: "assistant",
|
|
1684
|
+
content: React2.createElement(WorkflowDetail, { workflow: wf, latestRun, stepRuns })
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: `Workflow not found: ${parsed.args}` } });
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
if (parsed.name === "clear" || parsed.name === "cls") {
|
|
1693
|
+
dispatch({ type: "SET_MESSAGES", messages: [] });
|
|
1694
|
+
return;
|
|
1695
|
+
}
|
|
1696
|
+
if (parsed.name === "bot" && parsed.args.trim().toLowerCase() === "start") {
|
|
1697
|
+
const bridge = bridgeRef.current;
|
|
1698
|
+
if (bridge && config) {
|
|
1699
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: "Starting bot channels..." } });
|
|
1700
|
+
try {
|
|
1701
|
+
await startBotChannels(bridge, config);
|
|
1702
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: "Bot channels started." } });
|
|
1703
|
+
} catch (err) {
|
|
1704
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: `Failed to start bots: ${err instanceof Error ? err.message : String(err)}` } });
|
|
1705
|
+
}
|
|
1706
|
+
} else {
|
|
1707
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: "Daemon not running. Cannot start bots." } });
|
|
1708
|
+
}
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (parsed.name === "setup") {
|
|
1712
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: "Starting setup wizard..." } });
|
|
1713
|
+
setConfig(null);
|
|
1714
|
+
dispatch({ type: "SET_MESSAGES", messages: [] });
|
|
1715
|
+
dispatch({ type: "SHOW_ONBOARDING" });
|
|
1716
|
+
return;
|
|
1717
|
+
}
|
|
1718
|
+
const cmd = findCommand(parsed.name);
|
|
1719
|
+
if (cmd) {
|
|
1720
|
+
commandCtx.bridge = bridgeRef.current;
|
|
1721
|
+
commandCtx.config = config;
|
|
1722
|
+
await cmd.execute(parsed.args, commandCtx);
|
|
1723
|
+
} else {
|
|
1724
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: `Unknown command: /${parsed.name}. Type /help for available commands.` } });
|
|
1725
|
+
}
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "user", text } });
|
|
1729
|
+
dispatch({ type: "SET_GENERATING", value: true });
|
|
1730
|
+
dispatch({ type: "SET_STREAMING_TEXT", text: "" });
|
|
1731
|
+
try {
|
|
1732
|
+
let result;
|
|
1733
|
+
if (plannerSessionRef.current && plannerSessionRef.current.status === "conversing") {
|
|
1734
|
+
result = await continuePlannerSession(
|
|
1735
|
+
plannerSessionRef.current,
|
|
1736
|
+
text,
|
|
1737
|
+
config,
|
|
1738
|
+
{
|
|
1739
|
+
onToken: (token) => {
|
|
1740
|
+
dispatch({ type: "SET_STREAMING_TEXT", text: (state.streamingText || "") + token });
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
);
|
|
1744
|
+
} else {
|
|
1745
|
+
result = await startPlannerSession(
|
|
1746
|
+
text,
|
|
1747
|
+
config,
|
|
1748
|
+
{
|
|
1749
|
+
onToken: (token) => {
|
|
1750
|
+
dispatch({ type: "SET_STREAMING_TEXT", text: (state.streamingText || "") + token });
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
);
|
|
1754
|
+
}
|
|
1755
|
+
plannerSessionRef.current = result.session;
|
|
1756
|
+
dispatch({ type: "SET_STREAMING_TEXT", text: "" });
|
|
1757
|
+
switch (result.turn.type) {
|
|
1758
|
+
case "question":
|
|
1759
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: result.turn.content } });
|
|
1760
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1761
|
+
break;
|
|
1762
|
+
case "plan":
|
|
1763
|
+
if (result.turn.workflow) {
|
|
1764
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: `Generated plan: "${result.turn.workflow.name}"` } });
|
|
1765
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1766
|
+
dispatch({ type: "SHOW_PLAN", workflow: result.turn.workflow });
|
|
1767
|
+
}
|
|
1768
|
+
break;
|
|
1769
|
+
case "text":
|
|
1770
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: result.turn.content } });
|
|
1771
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1772
|
+
break;
|
|
1773
|
+
case "error":
|
|
1774
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: `Error: ${result.turn.content}` } });
|
|
1775
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1776
|
+
plannerSessionRef.current = null;
|
|
1777
|
+
break;
|
|
1778
|
+
}
|
|
1779
|
+
} catch (err) {
|
|
1780
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1781
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: `Error: ${msg}` } });
|
|
1782
|
+
dispatch({ type: "SET_GENERATING", value: false });
|
|
1783
|
+
dispatch({ type: "SET_STREAMING_TEXT", text: "" });
|
|
1784
|
+
plannerSessionRef.current = null;
|
|
1785
|
+
logger.error({ err }, "Planner session failed");
|
|
1786
|
+
}
|
|
1787
|
+
}, [config, db, commandCtx, state.streamingText]);
|
|
1788
|
+
const handleConfirm = useCallback2(async () => {
|
|
1789
|
+
if (!state.workflow) return;
|
|
1790
|
+
const confirmed = confirmPlan(state.workflow);
|
|
1791
|
+
try {
|
|
1792
|
+
insertWorkflow(db, confirmed);
|
|
1793
|
+
} catch (err) {
|
|
1794
|
+
logger.error({ err }, "Failed to persist workflow");
|
|
1795
|
+
}
|
|
1796
|
+
const controller = new AbortController();
|
|
1797
|
+
abortRef.current = controller;
|
|
1798
|
+
abortMapRef.current.set(confirmed.id, controller);
|
|
1799
|
+
setIsExecuting(true);
|
|
1800
|
+
plannerSessionRef.current = null;
|
|
1801
|
+
dispatch({ type: "SHOW_EXECUTION", workflow: confirmed });
|
|
1802
|
+
try {
|
|
1803
|
+
await executeWorkflow({
|
|
1804
|
+
workflow: confirmed,
|
|
1805
|
+
triggerData: null,
|
|
1806
|
+
db,
|
|
1807
|
+
cwd,
|
|
1808
|
+
signal: controller.signal,
|
|
1809
|
+
onProgress: (stepId, msg) => {
|
|
1810
|
+
if (typeof msg === "object" && msg !== null && "status" in msg) {
|
|
1811
|
+
dispatch({ type: "UPDATE_STEP", stepId, progress: { stepId, status: msg.status } });
|
|
1812
|
+
} else {
|
|
1813
|
+
dispatch({ type: "UPDATE_STEP", stepId, progress: { stepId, status: "running" } });
|
|
1814
|
+
if (typeof msg === "string") {
|
|
1815
|
+
dispatch({ type: "ADD_OUTPUT", line: msg });
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1819
|
+
});
|
|
1820
|
+
const trigger = confirmed.trigger;
|
|
1821
|
+
if (trigger.type === "poll") {
|
|
1822
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: `Workflow completed first run. It will run every ${trigger.interval_seconds}s.` } });
|
|
1823
|
+
const bridge = bridgeRef.current;
|
|
1824
|
+
if (bridge?.triggerLoop && !bridge.isExternal) {
|
|
1825
|
+
bridge.triggerLoop.registerTrigger(confirmed);
|
|
1826
|
+
}
|
|
1827
|
+
} else if (trigger.type === "cron") {
|
|
1828
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: `Workflow completed first run. Scheduled: ${trigger.expression}` } });
|
|
1829
|
+
const bridge = bridgeRef.current;
|
|
1830
|
+
if (bridge?.triggerLoop && !bridge.isExternal) {
|
|
1831
|
+
bridge.triggerLoop.registerTrigger(confirmed);
|
|
1832
|
+
}
|
|
1833
|
+
} else {
|
|
1834
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: "Workflow execution completed." } });
|
|
1835
|
+
}
|
|
1836
|
+
} catch (err) {
|
|
1837
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1838
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "system", text: `Execution failed: ${msg}` } });
|
|
1839
|
+
logger.error({ err }, "Workflow execution failed");
|
|
1840
|
+
} finally {
|
|
1841
|
+
abortMapRef.current.delete(confirmed.id);
|
|
1842
|
+
setIsExecuting(abortMapRef.current.size > 0);
|
|
1843
|
+
}
|
|
1844
|
+
}, [state.workflow, db, cwd]);
|
|
1845
|
+
const handleModify = useCallback2(() => {
|
|
1846
|
+
dispatch({ type: "SHOW_CHAT" });
|
|
1847
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: "Describe your modifications:" } });
|
|
1848
|
+
}, []);
|
|
1849
|
+
const handleCancel = useCallback2(() => {
|
|
1850
|
+
if (state.workflow) {
|
|
1851
|
+
rejectPlan(state.workflow);
|
|
1852
|
+
}
|
|
1853
|
+
plannerSessionRef.current = null;
|
|
1854
|
+
dispatch({ type: "SHOW_CHAT" });
|
|
1855
|
+
dispatch({ type: "ADD_MESSAGE", message: { role: "assistant", text: "Plan cancelled." } });
|
|
1856
|
+
}, [state.workflow]);
|
|
1857
|
+
const handleExecutionAbort = useCallback2(() => {
|
|
1858
|
+
abortRef.current?.abort();
|
|
1859
|
+
}, []);
|
|
1860
|
+
const handleExecutionBack = useCallback2(() => {
|
|
1861
|
+
dispatch({ type: "SHOW_CHAT" });
|
|
1862
|
+
}, []);
|
|
1863
|
+
const handleExitInstall = useCallback2(async () => {
|
|
1864
|
+
try {
|
|
1865
|
+
const { installService } = await import("./service-VTUYSAAZ.js");
|
|
1866
|
+
const result = installService();
|
|
1867
|
+
if (result.success) {
|
|
1868
|
+
logger.info("System service installed");
|
|
1869
|
+
}
|
|
1870
|
+
} catch (err) {
|
|
1871
|
+
logger.error({ err }, "Failed to install service");
|
|
1872
|
+
}
|
|
1873
|
+
const bridge = bridgeRef.current;
|
|
1874
|
+
if (bridge) await stopDaemonBridge(bridge);
|
|
1875
|
+
exit();
|
|
1876
|
+
process.exit(0);
|
|
1877
|
+
}, [exit]);
|
|
1878
|
+
const handleExitNoInstall = useCallback2(async () => {
|
|
1879
|
+
for (const controller of abortMapRef.current.values()) {
|
|
1880
|
+
controller.abort();
|
|
1881
|
+
}
|
|
1882
|
+
const bridge = bridgeRef.current;
|
|
1883
|
+
if (bridge) await stopDaemonBridge(bridge);
|
|
1884
|
+
exit();
|
|
1885
|
+
process.exit(0);
|
|
1886
|
+
}, [exit]);
|
|
1887
|
+
const { stdout } = useStdout3();
|
|
1888
|
+
const footerExtra = daemonStatus === "external" ? " | Background service detected" : daemonStatus === "running" ? " | Daemon active" : "";
|
|
1889
|
+
const footerHints = plannerSessionRef.current?.status === "conversing" ? "Enter send \xB7 /cancel abort \xB7 /help commands" : void 0;
|
|
1890
|
+
const displayPath = cwd ? cwd.replace(process.env["HOME"] ?? "", "~") : "";
|
|
1891
|
+
const versionLabel = appVersion === "dev" ? "dev" : `v${appVersion}`;
|
|
1892
|
+
const rows = stdout?.rows ?? 24;
|
|
1893
|
+
return /* @__PURE__ */ jsx6(ThemeProvider, { theme: cueclawTheme, children: /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", height: rows, children: [
|
|
1894
|
+
/* @__PURE__ */ jsx6(Static, { items: state.view !== "onboarding" ? ["banner"] : [], children: (item) => /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, paddingY: 1, children: [
|
|
1895
|
+
/* @__PURE__ */ jsx6(Text6, { color: "cyan", bold: true, children: "CueClaw" }),
|
|
1896
|
+
/* @__PURE__ */ jsxs6(Text6, { dimColor: true, children: [
|
|
1897
|
+
versionLabel,
|
|
1898
|
+
" \xB7 ",
|
|
1899
|
+
displayPath
|
|
1900
|
+
] })
|
|
1901
|
+
] }, item) }),
|
|
1902
|
+
state.view === "onboarding" && /* @__PURE__ */ jsx6(Onboarding, { onComplete: handleOnboardingComplete, issues: configIssues }),
|
|
1903
|
+
state.view === "chat" && /* @__PURE__ */ jsx6(
|
|
1904
|
+
Chat,
|
|
1905
|
+
{
|
|
1906
|
+
messages: state.messages,
|
|
1907
|
+
isGenerating: state.isGenerating,
|
|
1908
|
+
onSubmit: handleChatSubmit,
|
|
1909
|
+
footerExtra,
|
|
1910
|
+
footerHints,
|
|
1911
|
+
streamingText: state.streamingText || void 0
|
|
1912
|
+
}
|
|
1913
|
+
),
|
|
1914
|
+
state.view === "plan" && state.workflow && /* @__PURE__ */ jsx6(
|
|
1915
|
+
PlanView,
|
|
1916
|
+
{
|
|
1917
|
+
workflow: state.workflow,
|
|
1918
|
+
onConfirm: handleConfirm,
|
|
1919
|
+
onModify: handleModify,
|
|
1920
|
+
onCancel: handleCancel
|
|
1921
|
+
}
|
|
1922
|
+
),
|
|
1923
|
+
state.view === "execution" && state.workflow && /* @__PURE__ */ jsx6(
|
|
1924
|
+
ExecutionView,
|
|
1925
|
+
{
|
|
1926
|
+
workflow: state.workflow,
|
|
1927
|
+
stepProgress: state.stepProgress,
|
|
1928
|
+
output: state.executionOutput,
|
|
1929
|
+
onBack: handleExecutionBack,
|
|
1930
|
+
onAbort: handleExecutionAbort
|
|
1931
|
+
}
|
|
1932
|
+
),
|
|
1933
|
+
state.view === "exit_prompt" && /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
1934
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", flexGrow: 1, children: [
|
|
1935
|
+
isExecuting && /* @__PURE__ */ jsx6(Text6, { bold: true, color: "yellow", children: "A workflow is currently running. It will be cancelled if you exit." }),
|
|
1936
|
+
bridgeRef.current && !bridgeRef.current.isExternal ? /* @__PURE__ */ jsxs6(Fragment2, { children: [
|
|
1937
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: "Install as background service?" }),
|
|
1938
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "This lets poll/cron workflows keep running after you exit." })
|
|
1939
|
+
] }) : /* @__PURE__ */ jsx6(Text6, { bold: true, children: "Are you sure you want to exit?" })
|
|
1940
|
+
] }),
|
|
1941
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: bridgeRef.current && !bridgeRef.current.isExternal ? /* @__PURE__ */ jsx6(ConfirmInput2, { onConfirm: handleExitInstall, onCancel: handleExitNoInstall }) : /* @__PURE__ */ jsx6(
|
|
1942
|
+
ConfirmInput2,
|
|
1943
|
+
{
|
|
1944
|
+
onConfirm: handleExitNoInstall,
|
|
1945
|
+
onCancel: () => dispatch({ type: "SHOW_CHAT" })
|
|
1946
|
+
}
|
|
1947
|
+
) })
|
|
1948
|
+
] })
|
|
1949
|
+
] }) });
|
|
1950
|
+
}
|
|
1951
|
+
export {
|
|
1952
|
+
App
|
|
1953
|
+
};
|