cueclaw 0.0.4 → 0.1.1

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.
@@ -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-LSL3FRCU.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
+ };