cueclaw 0.0.1 → 0.0.2

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/cli.js CHANGED
@@ -0,0 +1,225 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ initDb,
4
+ listWorkflows
5
+ } from "./chunk-K4PGB2DU.js";
6
+ import {
7
+ createDefaultConfig,
8
+ ensureCueclawHome,
9
+ loadConfig
10
+ } from "./chunk-JRHM3Z4C.js";
11
+ import {
12
+ logger
13
+ } from "./chunk-E7BP6DMO.js";
14
+
15
+ // src/cli.ts
16
+ import { Command } from "commander";
17
+
18
+ // src/env.ts
19
+ import { readFileSync, existsSync } from "fs";
20
+ import { parse } from "dotenv";
21
+ var secrets = {};
22
+ function loadSecrets(envPath = ".env") {
23
+ if (!existsSync(envPath)) return;
24
+ const content = readFileSync(envPath, "utf-8");
25
+ secrets = parse(content);
26
+ }
27
+
28
+ // src/cli.ts
29
+ var program = new Command().name("cueclaw").description("Orchestrate agent workflows with natural language").version("0.0.1");
30
+ program.command("info").description("Show loaded config, paths, versions").action(() => {
31
+ try {
32
+ const config = loadConfig();
33
+ logger.info({
34
+ model_planner: config.claude.planner.model,
35
+ model_executor: config.claude.executor.model,
36
+ whatsapp: config.whatsapp?.enabled ?? false,
37
+ telegram: config.telegram?.enabled ?? false,
38
+ container: config.container?.enabled ?? false,
39
+ log_level: config.logging?.level ?? "info"
40
+ }, "CueClaw configuration");
41
+ } catch (err) {
42
+ logger.error({ err }, "Failed to load config");
43
+ process.exit(1);
44
+ }
45
+ });
46
+ var configCmd = program.command("config").description("Manage configuration");
47
+ configCmd.command("get").argument("[key]", "Config key (dot notation)").description("Get config value").action((key) => {
48
+ try {
49
+ const config = loadConfig();
50
+ if (!key) {
51
+ console.log(JSON.stringify(config, null, 2));
52
+ return;
53
+ }
54
+ const parts = key.split(".");
55
+ let value = config;
56
+ for (const part of parts) {
57
+ if (value === null || value === void 0) break;
58
+ value = value[part];
59
+ }
60
+ console.log(value !== void 0 ? JSON.stringify(value, null, 2) : "Key not found");
61
+ } catch (err) {
62
+ logger.error({ err }, "Failed to load config");
63
+ process.exit(1);
64
+ }
65
+ });
66
+ program.command("new").description("Create a new workflow interactively").action(() => {
67
+ console.log("Workflow creation coming in Phase 1");
68
+ });
69
+ program.command("list").description("List all workflows").action(() => {
70
+ try {
71
+ ensureCueclawHome();
72
+ const db = initDb();
73
+ const workflows = listWorkflows(db);
74
+ if (workflows.length === 0) {
75
+ console.log("No workflows found.");
76
+ return;
77
+ }
78
+ for (const wf of workflows) {
79
+ console.log(` ${wf.id} ${wf.phase.padEnd(22)} ${wf.name}`);
80
+ }
81
+ db.close();
82
+ } catch (err) {
83
+ logger.error({ err }, "Failed to list workflows");
84
+ process.exit(1);
85
+ }
86
+ });
87
+ program.command("status").argument("[workflow-id]", "Workflow ID").description("View workflow status").action(() => {
88
+ console.log("Status view coming in Phase 1");
89
+ });
90
+ program.command("pause").argument("<workflow-id>", "Workflow ID").description("Pause a workflow").action(() => {
91
+ console.log("Pause coming in Phase 1");
92
+ });
93
+ program.command("resume").argument("<workflow-id>", "Workflow ID").description("Resume a paused workflow").action(() => {
94
+ console.log("Resume coming in Phase 1");
95
+ });
96
+ program.command("delete").argument("<workflow-id>", "Workflow ID").description("Delete a workflow").action(() => {
97
+ console.log("Delete coming in Phase 1");
98
+ });
99
+ var daemonCmd = program.command("daemon").description("Manage background daemon");
100
+ daemonCmd.command("start").option("--detach", "Run in background").description("Start the daemon").action(async () => {
101
+ try {
102
+ const { startDaemon } = await import("./daemon-TWVEMRCU.js");
103
+ await startDaemon();
104
+ } catch (err) {
105
+ logger.error({ err }, "Daemon failed");
106
+ process.exit(1);
107
+ }
108
+ });
109
+ daemonCmd.command("stop").description("Stop the daemon").action(() => {
110
+ console.log("Use launchctl unload (macOS) or systemctl --user stop cueclaw (Linux)");
111
+ });
112
+ daemonCmd.command("install").description("Install system service").action(async () => {
113
+ const { installService } = await import("./service-BHFOM6E2.js");
114
+ const result = installService();
115
+ if (result.success) {
116
+ console.log("Service installed successfully.");
117
+ } else {
118
+ console.log(`Install failed: ${result.error}`);
119
+ process.exit(1);
120
+ }
121
+ });
122
+ daemonCmd.command("uninstall").description("Remove system service").action(async () => {
123
+ const { uninstallService } = await import("./service-BHFOM6E2.js");
124
+ const result = uninstallService();
125
+ if (result.success) {
126
+ console.log("Service uninstalled successfully.");
127
+ } else {
128
+ console.log(`Uninstall failed: ${result.error}`);
129
+ process.exit(1);
130
+ }
131
+ });
132
+ daemonCmd.command("status").description("View daemon status").action(async () => {
133
+ const { getServiceStatus } = await import("./service-BHFOM6E2.js");
134
+ const status = getServiceStatus();
135
+ console.log(`Daemon status: ${status}`);
136
+ });
137
+ daemonCmd.command("logs").description("View daemon logs").action(async () => {
138
+ const { join } = await import("path");
139
+ const { cueclawHome } = await import("./config-HMHM7UAZ.js");
140
+ const logPath = join(cueclawHome(), "logs", "daemon.log");
141
+ const { existsSync: existsSync2 } = await import("fs");
142
+ if (!existsSync2(logPath)) {
143
+ console.log("No daemon log file found.");
144
+ return;
145
+ }
146
+ const { spawn } = await import("child_process");
147
+ spawn("tail", ["-f", logPath], { stdio: "inherit" });
148
+ });
149
+ var botCmd = program.command("bot").description("Manage bot channels");
150
+ botCmd.command("start").description("Start all configured bot channels").action(async () => {
151
+ try {
152
+ const config = loadConfig();
153
+ const db = initDb();
154
+ const { MessageRouter } = await import("./router-36O66FDW.js");
155
+ const router = new MessageRouter(db, config, process.cwd());
156
+ if (config.whatsapp?.enabled) {
157
+ const { WhatsAppChannel } = await import("./whatsapp-36XWDSJ5.js");
158
+ const wa = new WhatsAppChannel(
159
+ config.whatsapp.auth_dir ?? `${process.env["HOME"]}/.cueclaw/auth/whatsapp`,
160
+ config.whatsapp.allowed_jids ?? [],
161
+ (jid, msg) => router.handleInbound("whatsapp", jid, msg)
162
+ );
163
+ router.registerChannel(wa);
164
+ await wa.connect();
165
+ logger.info("WhatsApp channel started");
166
+ }
167
+ if (config.telegram?.enabled) {
168
+ const { TelegramChannel } = await import("./telegram-BTTWEETO.js");
169
+ const tg = new TelegramChannel(
170
+ config.telegram.token,
171
+ config.telegram.allowed_users ?? [],
172
+ (jid, msg) => router.handleInbound("telegram", jid, msg)
173
+ );
174
+ router.registerChannel(tg);
175
+ await tg.connect();
176
+ logger.info("Telegram channel started");
177
+ }
178
+ router.start();
179
+ console.log("Bot channels started. Press Ctrl+C to stop.");
180
+ } catch (err) {
181
+ logger.error({ err }, "Failed to start bot channels");
182
+ process.exit(1);
183
+ }
184
+ });
185
+ botCmd.command("status").description("View channel connection status").action(() => {
186
+ const config = loadConfig();
187
+ console.log(`WhatsApp: ${config.whatsapp?.enabled ? "enabled" : "disabled"}`);
188
+ console.log(`Telegram: ${config.telegram?.enabled ? "enabled" : "disabled"}`);
189
+ });
190
+ program.command("setup").description("First-run setup: validate Docker, build container, smoke test").action(async () => {
191
+ try {
192
+ const config = loadConfig();
193
+ const { runSetup } = await import("./setup-QZUEJUIN.js");
194
+ await runSetup(config, process.cwd());
195
+ } catch (err) {
196
+ logger.error({ err }, "Setup failed");
197
+ process.exit(1);
198
+ }
199
+ });
200
+ program.command("tui").description("Start interactive TUI").option("--no-banner", "Skip the startup banner").action(async (opts) => {
201
+ try {
202
+ const React = await import("react");
203
+ const { render } = await import("ink");
204
+ const { App } = await import("./app-UJ4M3TPW.js");
205
+ render(React.createElement(App, { noBanner: opts.banner === false, cwd: process.cwd() }));
206
+ } catch (err) {
207
+ logger.error({ err }, "Failed to start TUI");
208
+ process.exit(1);
209
+ }
210
+ });
211
+ program.action(async () => {
212
+ try {
213
+ const React = await import("react");
214
+ const { render } = await import("ink");
215
+ const { App } = await import("./app-UJ4M3TPW.js");
216
+ render(React.createElement(App, { cwd: process.cwd() }));
217
+ } catch (err) {
218
+ logger.error({ err }, "Failed to start TUI");
219
+ process.exit(1);
220
+ }
221
+ });
222
+ loadSecrets();
223
+ ensureCueclawHome();
224
+ createDefaultConfig();
225
+ program.parse();
@@ -0,0 +1,12 @@
1
+ import {
2
+ createDefaultConfig,
3
+ cueclawHome,
4
+ ensureCueclawHome,
5
+ loadConfig
6
+ } from "./chunk-JRHM3Z4C.js";
7
+ export {
8
+ createDefaultConfig,
9
+ cueclawHome,
10
+ ensureCueclawHome,
11
+ loadConfig
12
+ };
@@ -0,0 +1,308 @@
1
+ import {
2
+ MessageRouter
3
+ } from "./chunk-GMHDL4CG.js";
4
+ import {
5
+ executeWorkflow
6
+ } from "./chunk-D77G7ABJ.js";
7
+ import {
8
+ initDb
9
+ } from "./chunk-K4PGB2DU.js";
10
+ import {
11
+ loadConfig
12
+ } from "./chunk-JRHM3Z4C.js";
13
+ import {
14
+ logger
15
+ } from "./chunk-E7BP6DMO.js";
16
+
17
+ // src/trigger-loop.ts
18
+ import { CronExpressionParser } from "cron-parser";
19
+
20
+ // src/trigger.ts
21
+ import { execFileSync } from "child_process";
22
+ var CHECK_SCRIPT_TIMEOUT = 3e4;
23
+ function evaluatePollTrigger(workflow, trigger, db) {
24
+ let stdout;
25
+ try {
26
+ stdout = execFileSync("sh", ["-c", trigger.check_script], {
27
+ timeout: CHECK_SCRIPT_TIMEOUT,
28
+ encoding: "utf-8"
29
+ }).trim();
30
+ } catch (err) {
31
+ logger.error({ workflowId: workflow.id, err }, "Poll check_script failed");
32
+ db.prepare(
33
+ "INSERT OR REPLACE INTO trigger_state (workflow_id, last_error, last_check_at) VALUES (?, ?, ?)"
34
+ ).run(workflow.id, err instanceof Error ? err.message : String(err), (/* @__PURE__ */ new Date()).toISOString());
35
+ return null;
36
+ }
37
+ const state = db.prepare(
38
+ "SELECT last_result FROM trigger_state WHERE workflow_id = ?"
39
+ ).get(workflow.id);
40
+ let triggerData = null;
41
+ if (trigger.diff_mode === "new_items") {
42
+ const newItems = diffNewItems(state?.last_result ?? null, stdout);
43
+ if (newItems.length > 0) triggerData = newItems.join("\n");
44
+ } else {
45
+ if (state?.last_result !== stdout) triggerData = stdout;
46
+ }
47
+ db.prepare(
48
+ "INSERT OR REPLACE INTO trigger_state (workflow_id, last_result, last_check_at) VALUES (?, ?, ?)"
49
+ ).run(workflow.id, stdout, (/* @__PURE__ */ new Date()).toISOString());
50
+ if (!triggerData) return null;
51
+ logger.info({ workflowId: workflow.id }, "Poll trigger fired");
52
+ return { workflowId: workflow.id, data: triggerData };
53
+ }
54
+ function diffNewItems(oldOutput, newOutput) {
55
+ if (!oldOutput) return newOutput ? newOutput.split("\n").filter(Boolean) : [];
56
+ const oldLines = new Set(oldOutput.split("\n").filter(Boolean));
57
+ return newOutput.split("\n").filter((line) => line && !oldLines.has(line));
58
+ }
59
+
60
+ // src/group-queue.ts
61
+ var GroupQueue = class {
62
+ constructor(maxConcurrent = 5) {
63
+ this.maxConcurrent = maxConcurrent;
64
+ }
65
+ running = 0;
66
+ runningByWorkflow = /* @__PURE__ */ new Set();
67
+ queue = [];
68
+ async enqueue(workflowId, task) {
69
+ return new Promise((resolve, reject) => {
70
+ const wrappedTask = async () => {
71
+ try {
72
+ await task();
73
+ resolve();
74
+ } catch (e) {
75
+ reject(e);
76
+ }
77
+ };
78
+ if (this.running >= this.maxConcurrent || this.runningByWorkflow.has(workflowId)) {
79
+ this.queue.push({ workflowId, task: wrappedTask });
80
+ logger.debug({ workflowId, queueLength: this.queue.length }, "Task queued");
81
+ } else {
82
+ this.running++;
83
+ this.runningByWorkflow.add(workflowId);
84
+ wrappedTask().finally(() => {
85
+ this.running--;
86
+ this.runningByWorkflow.delete(workflowId);
87
+ this.processNext();
88
+ });
89
+ }
90
+ });
91
+ }
92
+ processNext() {
93
+ const idx = this.queue.findIndex((item) => !this.runningByWorkflow.has(item.workflowId));
94
+ if (idx === -1 || this.running >= this.maxConcurrent) return;
95
+ const next = this.queue.splice(idx, 1)[0];
96
+ this.running++;
97
+ this.runningByWorkflow.add(next.workflowId);
98
+ next.task().finally(() => {
99
+ this.running--;
100
+ this.runningByWorkflow.delete(next.workflowId);
101
+ this.processNext();
102
+ });
103
+ }
104
+ get pendingCount() {
105
+ return this.queue.length;
106
+ }
107
+ get activeCount() {
108
+ return this.running;
109
+ }
110
+ };
111
+
112
+ // src/trigger-loop.ts
113
+ var TriggerLoop = class {
114
+ constructor(db, router, cwd, maxConcurrent = 5) {
115
+ this.db = db;
116
+ this.router = router;
117
+ this.cwd = cwd;
118
+ this.queue = new GroupQueue(maxConcurrent);
119
+ }
120
+ intervals = /* @__PURE__ */ new Map();
121
+ queue;
122
+ log = logger.child({ module: "trigger-loop" });
123
+ start() {
124
+ const rows = this.db.prepare(
125
+ "SELECT * FROM workflows WHERE phase = 'active'"
126
+ ).all();
127
+ for (const row of rows) {
128
+ const workflow = this.rowToWorkflow(row);
129
+ this.registerTrigger(workflow);
130
+ }
131
+ this.log.info({ count: rows.length }, "Trigger loop started");
132
+ }
133
+ registerTrigger(workflow) {
134
+ this.unregisterTrigger(workflow.id);
135
+ const trigger = workflow.trigger;
136
+ if (trigger.type === "poll") {
137
+ const interval = setInterval(() => {
138
+ try {
139
+ const result = evaluatePollTrigger(workflow, trigger, this.db);
140
+ if (result) {
141
+ this.executeTrigger(workflow, result.data);
142
+ }
143
+ } catch (err) {
144
+ this.log.error({ workflowId: workflow.id, err }, "Poll trigger error");
145
+ }
146
+ }, trigger.interval_seconds * 1e3);
147
+ this.intervals.set(workflow.id, interval);
148
+ this.log.info({ workflowId: workflow.id, intervalSeconds: trigger.interval_seconds }, "Registered poll trigger");
149
+ }
150
+ if (trigger.type === "cron") {
151
+ const interval = setInterval(() => {
152
+ try {
153
+ const expr = CronExpressionParser.parse(trigger.expression, {
154
+ tz: trigger.timezone ?? "UTC"
155
+ });
156
+ const prev = expr.prev().toDate();
157
+ const now = /* @__PURE__ */ new Date();
158
+ if (now.getTime() - prev.getTime() < 6e4) {
159
+ const state = this.db.prepare(
160
+ "SELECT last_fire_at FROM trigger_state WHERE workflow_id = ?"
161
+ ).get(workflow.id);
162
+ const lastFire = state?.last_fire_at ? new Date(state.last_fire_at).getTime() : 0;
163
+ if (prev.getTime() <= lastFire) return;
164
+ this.db.prepare(
165
+ "INSERT OR REPLACE INTO trigger_state (workflow_id, last_fire_at) VALUES (?, ?)"
166
+ ).run(workflow.id, prev.toISOString());
167
+ this.executeTrigger(workflow, (/* @__PURE__ */ new Date()).toISOString());
168
+ }
169
+ } catch (err) {
170
+ this.log.error({ workflowId: workflow.id, err }, "Cron evaluation failed");
171
+ }
172
+ }, 6e4);
173
+ this.intervals.set(workflow.id, interval);
174
+ this.log.info({ workflowId: workflow.id, expression: trigger.expression }, "Registered cron trigger");
175
+ }
176
+ }
177
+ unregisterTrigger(workflowId) {
178
+ const interval = this.intervals.get(workflowId);
179
+ if (interval) {
180
+ clearInterval(interval);
181
+ this.intervals.delete(workflowId);
182
+ }
183
+ }
184
+ executeTrigger(workflow, triggerData) {
185
+ this.queue.enqueue(workflow.id, async () => {
186
+ this.log.info({ workflowId: workflow.id }, "Executing triggered workflow");
187
+ try {
188
+ await executeWorkflow({
189
+ workflow,
190
+ triggerData,
191
+ db: this.db,
192
+ cwd: this.cwd,
193
+ onProgress: (_stepId, msg) => {
194
+ if (typeof msg === "object" && msg?.type === "step_complete") {
195
+ this.router.broadcastNotification(`Step completed: ${_stepId} (${workflow.name})`);
196
+ }
197
+ }
198
+ });
199
+ this.router.broadcastNotification(`Workflow complete: ${workflow.name}`);
200
+ } catch (err) {
201
+ const errMsg = err instanceof Error ? err.message : String(err);
202
+ this.log.error({ workflowId: workflow.id, err }, "Triggered execution failed");
203
+ this.router.broadcastNotification(`Workflow failed: ${workflow.name} \u2014 ${errMsg}`);
204
+ }
205
+ }).catch((err) => {
206
+ this.log.error({ workflowId: workflow.id, err }, "Queue execution error");
207
+ });
208
+ }
209
+ stop() {
210
+ for (const interval of this.intervals.values()) {
211
+ clearInterval(interval);
212
+ }
213
+ this.intervals.clear();
214
+ this.log.info("Trigger loop stopped");
215
+ }
216
+ get registeredCount() {
217
+ return this.intervals.size;
218
+ }
219
+ rowToWorkflow(row) {
220
+ return {
221
+ id: row.id,
222
+ name: row.name,
223
+ description: row.description,
224
+ steps: JSON.parse(row.steps_json),
225
+ trigger: JSON.parse(row.trigger_json),
226
+ failure_policy: JSON.parse(row.failure_policy_json),
227
+ phase: row.phase,
228
+ schema_version: "1.0",
229
+ created_at: row.created_at,
230
+ updated_at: row.updated_at,
231
+ metadata: row.metadata_json ? JSON.parse(row.metadata_json) : void 0
232
+ };
233
+ }
234
+ };
235
+
236
+ // src/daemon.ts
237
+ async function startDaemon() {
238
+ const config = loadConfig();
239
+ const db = initDb();
240
+ const cwd = process.cwd();
241
+ const router = new MessageRouter(db, config, cwd);
242
+ if (config.whatsapp?.enabled) {
243
+ try {
244
+ const { WhatsAppChannel } = await import("./whatsapp-36XWDSJ5.js");
245
+ const wa = new WhatsAppChannel(
246
+ config.whatsapp.auth_dir ?? `${process.env["HOME"]}/.cueclaw/auth/whatsapp`,
247
+ config.whatsapp.allowed_jids ?? [],
248
+ (jid, msg) => router.handleInbound("whatsapp", jid, msg)
249
+ );
250
+ router.registerChannel(wa);
251
+ await wa.connect();
252
+ logger.info("WhatsApp channel started");
253
+ } catch (err) {
254
+ logger.error({ err }, "Failed to start WhatsApp channel");
255
+ }
256
+ }
257
+ if (config.telegram?.enabled) {
258
+ try {
259
+ const { TelegramChannel } = await import("./telegram-BTTWEETO.js");
260
+ const tg = new TelegramChannel(
261
+ config.telegram.token,
262
+ config.telegram.allowed_users ?? [],
263
+ (jid, msg) => router.handleInbound("telegram", jid, msg)
264
+ );
265
+ router.registerChannel(tg);
266
+ await tg.connect();
267
+ logger.info("Telegram channel started");
268
+ } catch (err) {
269
+ logger.error({ err }, "Failed to start Telegram channel");
270
+ }
271
+ }
272
+ await recoverRunningWorkflows(db, router);
273
+ const maxConcurrent = 5;
274
+ const triggerLoop = new TriggerLoop(db, router, cwd, maxConcurrent);
275
+ triggerLoop.start();
276
+ router.start();
277
+ logger.info("Daemon started");
278
+ const shutdown = () => {
279
+ logger.info("Shutting down daemon...");
280
+ triggerLoop.stop();
281
+ router.stop();
282
+ db.close();
283
+ process.exit(0);
284
+ };
285
+ process.on("SIGTERM", shutdown);
286
+ process.on("SIGINT", shutdown);
287
+ }
288
+ async function recoverRunningWorkflows(db, router) {
289
+ const interruptedRuns = db.prepare(
290
+ "SELECT id, workflow_id FROM workflow_runs WHERE status = 'running'"
291
+ ).all();
292
+ if (interruptedRuns.length === 0) return;
293
+ logger.warn({ count: interruptedRuns.length }, "Recovering interrupted workflow runs");
294
+ for (const run of interruptedRuns) {
295
+ db.prepare(
296
+ "UPDATE workflow_runs SET status = 'failed', error = ?, completed_at = ? WHERE id = ?"
297
+ ).run("Daemon restarted during execution", (/* @__PURE__ */ new Date()).toISOString(), run.id);
298
+ db.prepare(
299
+ "UPDATE step_runs SET status = 'failed', error = ? WHERE run_id = ? AND status = 'running'"
300
+ ).run("Daemon restarted during execution", run.id);
301
+ router.broadcastNotification(
302
+ `Workflow ${run.workflow_id} was interrupted by daemon restart (run ${run.id})`
303
+ );
304
+ }
305
+ }
306
+ export {
307
+ startDaemon
308
+ };
@@ -0,0 +1,10 @@
1
+ import {
2
+ MessageRouter
3
+ } from "./chunk-GMHDL4CG.js";
4
+ import "./chunk-D77G7ABJ.js";
5
+ import "./chunk-K4PGB2DU.js";
6
+ import "./chunk-JRHM3Z4C.js";
7
+ import "./chunk-E7BP6DMO.js";
8
+ export {
9
+ MessageRouter
10
+ };