cueclaw 0.0.3 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app-DIUXV4XR.js +1953 -0
- package/dist/chunk-BVQG3WYO.js +29 -0
- package/dist/chunk-DVQFSFIZ.js +14 -0
- package/dist/{chunk-GMHDL4CG.js → chunk-F3EQAFH4.js} +59 -5
- package/dist/{chunk-K4PGB2DU.js → chunk-G43R5ASK.js} +46 -2
- package/dist/chunk-KRNAXOQ4.js +46 -0
- package/dist/{service-BHFOM6E2.js → chunk-PZZ6FBGB.js} +25 -7
- package/dist/chunk-QBOYMF4A.js +42 -0
- package/dist/chunk-RSKXBXSJ.js +247 -0
- package/dist/{chunk-D77G7ABJ.js → chunk-SEYPA5M2.js} +36 -318
- package/dist/{daemon-TWVEMRCU.js → chunk-W274JWEK.js} +3 -82
- package/dist/chunk-WE5J7GMR.js +409 -0
- package/dist/chunk-ZCK3IFLC.js +59 -0
- package/dist/cli.js +300 -46
- package/dist/config-6NWFKNLW.js +21 -0
- package/dist/daemon-R3LU23PR.js +95 -0
- package/dist/env-2ZIW4OD7.js +16 -0
- package/dist/executor-LS3Y4DO5.js +14 -0
- package/dist/logger-HD23RPWS.js +12 -0
- package/dist/planner-UU4T5IEN.js +28 -0
- package/dist/router-KEZ3YQXC.js +14 -0
- package/dist/service-VTUYSAAZ.js +15 -0
- package/dist/{setup-QZUEJUIN.js → setup-NWBKTQCO.js} +11 -45
- package/dist/{telegram-BTTWEETO.js → telegram-P6DBJ7WZ.js} +2 -2
- package/dist/{whatsapp-36XWDSJ5.js → whatsapp-HFMOFSFI.js} +1 -1
- package/package.json +4 -1
- package/dist/app-UJ4M3TPW.js +0 -448
- package/dist/chunk-E7BP6DMO.js +0 -10
- package/dist/chunk-JRHM3Z4C.js +0 -158
- package/dist/config-HMHM7UAZ.js +0 -12
- package/dist/router-36O66FDW.js +0 -10
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/types.ts
|
|
2
|
+
var CueclawError = class extends Error {
|
|
3
|
+
constructor(message, code) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = "CueclawError";
|
|
7
|
+
}
|
|
8
|
+
};
|
|
9
|
+
var PlannerError = class extends CueclawError {
|
|
10
|
+
constructor(message) {
|
|
11
|
+
super(message, "PLANNER_ERROR");
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var ExecutorError = class extends CueclawError {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message, "EXECUTOR_ERROR");
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
var ConfigError = class extends CueclawError {
|
|
20
|
+
constructor(message) {
|
|
21
|
+
super(message, "CONFIG_ERROR");
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
PlannerError,
|
|
27
|
+
ExecutorError,
|
|
28
|
+
ConfigError
|
|
29
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// src/anthropic-client.ts
|
|
2
|
+
import Anthropic from "@anthropic-ai/sdk";
|
|
3
|
+
var ANTHROPIC_OFFICIAL = "https://api.anthropic.com";
|
|
4
|
+
function createAnthropicClient(config) {
|
|
5
|
+
const isThirdParty = config.claude.base_url !== ANTHROPIC_OFFICIAL;
|
|
6
|
+
return new Anthropic({
|
|
7
|
+
...isThirdParty ? { authToken: config.claude.api_key, apiKey: "" } : { apiKey: config.claude.api_key },
|
|
8
|
+
baseURL: config.claude.base_url
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export {
|
|
13
|
+
createAnthropicClient
|
|
14
|
+
};
|
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import {
|
|
2
2
|
confirmPlan,
|
|
3
|
-
executeWorkflow,
|
|
4
3
|
generatePlan,
|
|
5
4
|
modifyPlan,
|
|
6
5
|
rejectPlan
|
|
7
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WE5J7GMR.js";
|
|
7
|
+
import {
|
|
8
|
+
createAnthropicClient
|
|
9
|
+
} from "./chunk-DVQFSFIZ.js";
|
|
10
|
+
import {
|
|
11
|
+
executeWorkflow
|
|
12
|
+
} from "./chunk-SEYPA5M2.js";
|
|
8
13
|
import {
|
|
9
14
|
getWorkflow,
|
|
10
15
|
listWorkflows,
|
|
11
16
|
updateWorkflowPhase
|
|
12
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-G43R5ASK.js";
|
|
13
18
|
import {
|
|
14
19
|
logger
|
|
15
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-QBOYMF4A.js";
|
|
16
21
|
|
|
17
22
|
// src/router.ts
|
|
18
23
|
var CONFIRMATION_TIMEOUT = 10 * 6e4;
|
|
@@ -67,7 +72,7 @@ var MessageRouter = class {
|
|
|
67
72
|
} else if (this.hasPendingConfirmation(chatJid)) {
|
|
68
73
|
await this.handleConfirmation(channel, chatJid, text);
|
|
69
74
|
} else {
|
|
70
|
-
await this.
|
|
75
|
+
await this.classifyAndRoute(channel, chatJid, text);
|
|
71
76
|
}
|
|
72
77
|
}
|
|
73
78
|
async handleCommand(channel, chatJid, text) {
|
|
@@ -142,6 +147,55 @@ ${steps}`);
|
|
|
142
147
|
await channel.sendMessage(chatJid, `Unknown command: ${command}. Type /help for available commands.`);
|
|
143
148
|
}
|
|
144
149
|
}
|
|
150
|
+
async handleCallbackAction(channelName, chatId, _workflowId, action) {
|
|
151
|
+
const actionMap = {
|
|
152
|
+
confirm: "yes",
|
|
153
|
+
modify: "modify",
|
|
154
|
+
cancel: "no"
|
|
155
|
+
};
|
|
156
|
+
const mappedText = actionMap[action] ?? action;
|
|
157
|
+
await this.handleInbound(channelName, chatId, { text: mappedText, sender: chatId });
|
|
158
|
+
}
|
|
159
|
+
async classifyAndRoute(channel, chatJid, text) {
|
|
160
|
+
try {
|
|
161
|
+
const client = createAnthropicClient(this.config);
|
|
162
|
+
const response = await client.messages.create({
|
|
163
|
+
model: this.config.claude.planner.model,
|
|
164
|
+
max_tokens: 512,
|
|
165
|
+
system: "You are a router for CueClaw, a workflow automation tool. Classify the user message and call the appropriate tool. Use create_workflow_request if the user wants to automate a task, set up a workflow, schedule something, or perform an action that requires multiple steps. Use chat_reply for greetings, questions about capabilities, casual conversation, or anything that is not a workflow request.",
|
|
166
|
+
messages: [{ role: "user", content: text }],
|
|
167
|
+
tools: [
|
|
168
|
+
{
|
|
169
|
+
name: "create_workflow_request",
|
|
170
|
+
description: "The user wants to create an automation workflow",
|
|
171
|
+
input_schema: { type: "object", properties: {} }
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "chat_reply",
|
|
175
|
+
description: "Reply to casual conversation or questions",
|
|
176
|
+
input_schema: {
|
|
177
|
+
type: "object",
|
|
178
|
+
properties: {
|
|
179
|
+
message: { type: "string", description: "The reply to send to the user" }
|
|
180
|
+
},
|
|
181
|
+
required: ["message"]
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
tool_choice: { type: "any" }
|
|
186
|
+
});
|
|
187
|
+
const toolUse = response.content.find((b) => b.type === "tool_use");
|
|
188
|
+
if (toolUse && toolUse.type === "tool_use" && toolUse.name === "chat_reply") {
|
|
189
|
+
const input = toolUse.input;
|
|
190
|
+
await channel.sendMessage(chatJid, input.message ?? "I'm CueClaw, a workflow automation tool. Send me a task description and I'll create a plan for you!");
|
|
191
|
+
} else {
|
|
192
|
+
await this.handleNewWorkflow(channel, chatJid, text);
|
|
193
|
+
}
|
|
194
|
+
} catch (err) {
|
|
195
|
+
logger.error({ err, chatJid }, "Classification failed, falling back to workflow");
|
|
196
|
+
await this.handleNewWorkflow(channel, chatJid, text);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
145
199
|
async handleNewWorkflow(channel, chatJid, text) {
|
|
146
200
|
await channel.sendMessage(chatJid, "Generating execution plan...");
|
|
147
201
|
channel.setTyping?.(chatJid, true);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cueclawHome
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RSKXBXSJ.js";
|
|
4
4
|
|
|
5
5
|
// src/db.ts
|
|
6
6
|
import Database from "better-sqlite3";
|
|
@@ -95,6 +95,24 @@ function rowToWorkflow(row) {
|
|
|
95
95
|
updated_at: row.updated_at
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
+
function insertWorkflow(db, workflow) {
|
|
99
|
+
db.prepare(`
|
|
100
|
+
INSERT INTO workflows (id, name, description, trigger_json, steps_json, failure_policy_json, phase, schema_version, metadata_json, created_at, updated_at)
|
|
101
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
102
|
+
`).run(
|
|
103
|
+
workflow.id,
|
|
104
|
+
workflow.name,
|
|
105
|
+
workflow.description,
|
|
106
|
+
JSON.stringify(workflow.trigger),
|
|
107
|
+
JSON.stringify(workflow.steps),
|
|
108
|
+
JSON.stringify(workflow.failure_policy),
|
|
109
|
+
workflow.phase,
|
|
110
|
+
workflow.schema_version,
|
|
111
|
+
workflow.metadata ? JSON.stringify(workflow.metadata) : null,
|
|
112
|
+
workflow.created_at,
|
|
113
|
+
workflow.updated_at
|
|
114
|
+
);
|
|
115
|
+
}
|
|
98
116
|
function getWorkflow(db, id) {
|
|
99
117
|
const row = db.prepare("SELECT * FROM workflows WHERE id = ?").get(id);
|
|
100
118
|
return row ? rowToWorkflow(row) : void 0;
|
|
@@ -107,6 +125,22 @@ function listWorkflows(db, phase) {
|
|
|
107
125
|
function updateWorkflowPhase(db, id, phase) {
|
|
108
126
|
db.prepare("UPDATE workflows SET phase = ?, updated_at = ? WHERE id = ?").run(phase, (/* @__PURE__ */ new Date()).toISOString(), id);
|
|
109
127
|
}
|
|
128
|
+
function deleteWorkflow(db, id) {
|
|
129
|
+
const del = db.transaction(() => {
|
|
130
|
+
const runIds = db.prepare("SELECT id FROM workflow_runs WHERE workflow_id = ?").all(id);
|
|
131
|
+
for (const run of runIds) {
|
|
132
|
+
const stepRunIds = db.prepare("SELECT id FROM step_runs WHERE run_id = ?").all(run.id);
|
|
133
|
+
for (const sr of stepRunIds) {
|
|
134
|
+
db.prepare("DELETE FROM sessions WHERE step_run_id = ?").run(sr.id);
|
|
135
|
+
}
|
|
136
|
+
db.prepare("DELETE FROM step_runs WHERE run_id = ?").run(run.id);
|
|
137
|
+
}
|
|
138
|
+
db.prepare("DELETE FROM workflow_runs WHERE workflow_id = ?").run(id);
|
|
139
|
+
db.prepare("DELETE FROM trigger_state WHERE workflow_id = ?").run(id);
|
|
140
|
+
db.prepare("DELETE FROM workflows WHERE id = ?").run(id);
|
|
141
|
+
});
|
|
142
|
+
del();
|
|
143
|
+
}
|
|
110
144
|
function insertWorkflowRun(db, run) {
|
|
111
145
|
db.prepare(`
|
|
112
146
|
INSERT INTO workflow_runs (id, workflow_id, trigger_data, status, started_at, completed_at, error, duration_ms)
|
|
@@ -127,14 +161,24 @@ function updateStepRunStatus(db, id, status, output, error) {
|
|
|
127
161
|
const completedAt = status === "succeeded" || status === "failed" || status === "skipped" ? (/* @__PURE__ */ new Date()).toISOString() : null;
|
|
128
162
|
db.prepare("UPDATE step_runs SET status = ?, output_json = ?, error = ?, completed_at = ? WHERE id = ?").run(status, output ?? null, error ?? null, completedAt, id);
|
|
129
163
|
}
|
|
164
|
+
function getStepRunsByRunId(db, runId) {
|
|
165
|
+
return db.prepare("SELECT * FROM step_runs WHERE run_id = ?").all(runId);
|
|
166
|
+
}
|
|
167
|
+
function getWorkflowRunsByWorkflowId(db, workflowId) {
|
|
168
|
+
return db.prepare("SELECT * FROM workflow_runs WHERE workflow_id = ? ORDER BY started_at DESC").all(workflowId);
|
|
169
|
+
}
|
|
130
170
|
|
|
131
171
|
export {
|
|
132
172
|
initDb,
|
|
173
|
+
insertWorkflow,
|
|
133
174
|
getWorkflow,
|
|
134
175
|
listWorkflows,
|
|
135
176
|
updateWorkflowPhase,
|
|
177
|
+
deleteWorkflow,
|
|
136
178
|
insertWorkflowRun,
|
|
137
179
|
updateWorkflowRunStatus,
|
|
138
180
|
insertStepRun,
|
|
139
|
-
updateStepRunStatus
|
|
181
|
+
updateStepRunStatus,
|
|
182
|
+
getStepRunsByRunId,
|
|
183
|
+
getWorkflowRunsByWorkflowId
|
|
140
184
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAnthropicClient
|
|
3
|
+
} from "./chunk-DVQFSFIZ.js";
|
|
4
|
+
|
|
5
|
+
// src/setup-environment.ts
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
7
|
+
function checkEnvironment() {
|
|
8
|
+
const nodeVersion = process.version;
|
|
9
|
+
let docker = false;
|
|
10
|
+
let dockerVersion;
|
|
11
|
+
let dockerRunning = false;
|
|
12
|
+
try {
|
|
13
|
+
const version = execFileSync("docker", ["--version"], { encoding: "utf-8" }).trim();
|
|
14
|
+
docker = true;
|
|
15
|
+
dockerVersion = version;
|
|
16
|
+
} catch {
|
|
17
|
+
}
|
|
18
|
+
if (docker) {
|
|
19
|
+
try {
|
|
20
|
+
execFileSync("docker", ["info"], { encoding: "utf-8", stdio: "pipe" });
|
|
21
|
+
dockerRunning = true;
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return { docker, dockerVersion, dockerRunning, nodeVersion };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/setup-auth.ts
|
|
29
|
+
async function validateAuth(config) {
|
|
30
|
+
try {
|
|
31
|
+
const client = createAnthropicClient(config);
|
|
32
|
+
await client.messages.create({
|
|
33
|
+
model: "claude-haiku-4-5-20251001",
|
|
34
|
+
max_tokens: 10,
|
|
35
|
+
messages: [{ role: "user", content: "ping" }]
|
|
36
|
+
});
|
|
37
|
+
return { valid: true };
|
|
38
|
+
} catch (err) {
|
|
39
|
+
return { valid: false, error: err instanceof Error ? err.message : String(err) };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
checkEnvironment,
|
|
45
|
+
validateAuth
|
|
46
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cueclawHome
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-RSKXBXSJ.js";
|
|
4
4
|
import {
|
|
5
5
|
logger
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QBOYMF4A.js";
|
|
7
7
|
|
|
8
8
|
// src/service.ts
|
|
9
9
|
import { writeFileSync, existsSync, unlinkSync, mkdirSync } from "fs";
|
|
@@ -38,14 +38,30 @@ function uninstallService() {
|
|
|
38
38
|
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
function stopService() {
|
|
42
|
+
const platform = process.platform;
|
|
43
|
+
try {
|
|
44
|
+
if (platform === "darwin") {
|
|
45
|
+
execFileSync("launchctl", ["stop", SERVICE_LABEL], { stdio: "pipe" });
|
|
46
|
+
return { success: true };
|
|
47
|
+
} else if (platform === "linux") {
|
|
48
|
+
execFileSync("systemctl", ["--user", "stop", "cueclaw"], { stdio: "pipe" });
|
|
49
|
+
return { success: true };
|
|
50
|
+
} else {
|
|
51
|
+
return { success: false, error: `Unsupported platform: ${platform}` };
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
41
57
|
function getServiceStatus() {
|
|
42
58
|
const platform = process.platform;
|
|
43
59
|
try {
|
|
44
60
|
if (platform === "darwin") {
|
|
45
|
-
const output = execFileSync("launchctl", ["list", SERVICE_LABEL], { encoding: "utf-8" });
|
|
46
|
-
return output.includes(
|
|
61
|
+
const output = execFileSync("launchctl", ["list", SERVICE_LABEL], { encoding: "utf-8", stdio: "pipe" });
|
|
62
|
+
return output.includes('"PID"') ? "running" : "stopped";
|
|
47
63
|
} else if (platform === "linux") {
|
|
48
|
-
const output = execFileSync("systemctl", ["--user", "is-active", "cueclaw"], { encoding: "utf-8" }).trim();
|
|
64
|
+
const output = execFileSync("systemctl", ["--user", "is-active", "cueclaw"], { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
49
65
|
return output === "active" ? "running" : "stopped";
|
|
50
66
|
}
|
|
51
67
|
} catch {
|
|
@@ -146,8 +162,10 @@ function uninstallSystemd() {
|
|
|
146
162
|
logger.info("Uninstalled systemd service");
|
|
147
163
|
return { success: true };
|
|
148
164
|
}
|
|
165
|
+
|
|
149
166
|
export {
|
|
150
|
-
getServiceStatus,
|
|
151
167
|
installService,
|
|
152
|
-
uninstallService
|
|
168
|
+
uninstallService,
|
|
169
|
+
stopService,
|
|
170
|
+
getServiceStatus
|
|
153
171
|
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// src/logger.ts
|
|
2
|
+
import pino from "pino";
|
|
3
|
+
import { PassThrough } from "stream";
|
|
4
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
var tuiStream = new PassThrough();
|
|
6
|
+
tuiStream.on("data", (chunk) => {
|
|
7
|
+
for (const raw of chunk.toString().split("\n")) {
|
|
8
|
+
if (!raw.trim()) continue;
|
|
9
|
+
try {
|
|
10
|
+
const obj = JSON.parse(raw);
|
|
11
|
+
const lvl = (pino.levels.labels[obj.level] ?? "INFO").toUpperCase();
|
|
12
|
+
const mod = obj.module ? ` [${obj.module}]` : "";
|
|
13
|
+
const msg = obj.msg ?? "";
|
|
14
|
+
for (const fn of listeners) fn(`${lvl}${mod} ${msg}`);
|
|
15
|
+
} catch {
|
|
16
|
+
for (const fn of listeners) fn(raw);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
var logger = pino({
|
|
21
|
+
level: process.env["LOG_LEVEL"] ?? "info",
|
|
22
|
+
transport: process.env["NODE_ENV"] === "production" ? void 0 : { target: "pino-pretty", options: { colorize: true } }
|
|
23
|
+
});
|
|
24
|
+
function enableTuiLogging() {
|
|
25
|
+
logger = pino({ level: process.env["LOG_LEVEL"] ?? "info" }, tuiStream);
|
|
26
|
+
}
|
|
27
|
+
function onLogLine(fn) {
|
|
28
|
+
listeners.add(fn);
|
|
29
|
+
return () => {
|
|
30
|
+
listeners.delete(fn);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function createChildLogger(bindings) {
|
|
34
|
+
return logger.child(bindings);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export {
|
|
38
|
+
logger,
|
|
39
|
+
enableTuiLogging,
|
|
40
|
+
onLogLine,
|
|
41
|
+
createChildLogger
|
|
42
|
+
};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ConfigError
|
|
3
|
+
} from "./chunk-BVQG3WYO.js";
|
|
4
|
+
|
|
5
|
+
// src/config.ts
|
|
6
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from "fs";
|
|
7
|
+
import { join } from "path";
|
|
8
|
+
import { homedir } from "os";
|
|
9
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
10
|
+
import { z } from "zod/v4";
|
|
11
|
+
var ConfigSchema = z.object({
|
|
12
|
+
claude: z.object({
|
|
13
|
+
api_key: z.string(),
|
|
14
|
+
base_url: z.url().default("https://api.anthropic.com"),
|
|
15
|
+
planner: z.object({ model: z.string().default("claude-sonnet-4-6") }).default({ model: "claude-sonnet-4-6" }),
|
|
16
|
+
executor: z.object({
|
|
17
|
+
model: z.string().default("claude-sonnet-4-6"),
|
|
18
|
+
api_key: z.string().optional(),
|
|
19
|
+
base_url: z.url().optional(),
|
|
20
|
+
skip_permissions: z.boolean().default(false)
|
|
21
|
+
}).default({ model: "claude-sonnet-4-6", skip_permissions: false })
|
|
22
|
+
}),
|
|
23
|
+
identity: z.object({ name: z.string() }).optional(),
|
|
24
|
+
whatsapp: z.object({
|
|
25
|
+
enabled: z.boolean().default(false),
|
|
26
|
+
auth_dir: z.string().default("~/.cueclaw/auth/whatsapp"),
|
|
27
|
+
allowed_jids: z.array(z.string()).default([])
|
|
28
|
+
}).optional(),
|
|
29
|
+
telegram: z.object({
|
|
30
|
+
enabled: z.boolean().default(false),
|
|
31
|
+
token: z.string().optional(),
|
|
32
|
+
allowed_users: z.array(z.string()).default([])
|
|
33
|
+
}).optional(),
|
|
34
|
+
logging: z.object({
|
|
35
|
+
level: z.enum(["debug", "info", "warn", "error"]).default("info"),
|
|
36
|
+
dir: z.string().default("~/.cueclaw/logs")
|
|
37
|
+
}).optional(),
|
|
38
|
+
container: z.object({
|
|
39
|
+
enabled: z.boolean().default(false),
|
|
40
|
+
image: z.string().default("cueclaw-agent:latest"),
|
|
41
|
+
timeout: z.number().default(18e5),
|
|
42
|
+
max_output_size: z.number().default(10485760),
|
|
43
|
+
idle_timeout: z.number().default(18e5),
|
|
44
|
+
network: z.enum(["none", "host", "bridge"]).default("none")
|
|
45
|
+
}).optional()
|
|
46
|
+
});
|
|
47
|
+
function cueclawHome() {
|
|
48
|
+
return join(homedir(), ".cueclaw");
|
|
49
|
+
}
|
|
50
|
+
function ensureCueclawHome() {
|
|
51
|
+
const home = cueclawHome();
|
|
52
|
+
const dirs = [home, join(home, "db"), join(home, "logs"), join(home, "auth")];
|
|
53
|
+
for (const dir of dirs) {
|
|
54
|
+
mkdirSync(dir, { recursive: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function interpolateEnvVars(value) {
|
|
58
|
+
return value.replace(/\$\{(\w+)\}/g, (_match, name) => {
|
|
59
|
+
return process.env[name] ?? "";
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function interpolateObject(obj) {
|
|
63
|
+
if (typeof obj === "string") return interpolateEnvVars(obj);
|
|
64
|
+
if (Array.isArray(obj)) return obj.map(interpolateObject);
|
|
65
|
+
if (obj !== null && typeof obj === "object") {
|
|
66
|
+
const result = {};
|
|
67
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
68
|
+
result[key] = interpolateObject(value);
|
|
69
|
+
}
|
|
70
|
+
return result;
|
|
71
|
+
}
|
|
72
|
+
return obj;
|
|
73
|
+
}
|
|
74
|
+
function loadYamlFile(path) {
|
|
75
|
+
if (!existsSync(path)) return null;
|
|
76
|
+
const content = readFileSync(path, "utf-8");
|
|
77
|
+
const parsed = parseYaml(content);
|
|
78
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
79
|
+
}
|
|
80
|
+
function deepMerge(target, source) {
|
|
81
|
+
const result = { ...target };
|
|
82
|
+
for (const [key, value] of Object.entries(source)) {
|
|
83
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value) && result[key] !== null && typeof result[key] === "object" && !Array.isArray(result[key])) {
|
|
84
|
+
result[key] = deepMerge(result[key], value);
|
|
85
|
+
} else {
|
|
86
|
+
result[key] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
function loadConfig() {
|
|
92
|
+
const globalPath = join(cueclawHome(), "config.yaml");
|
|
93
|
+
const localPath = join(process.cwd(), ".cueclaw", "config.yaml");
|
|
94
|
+
let merged = {};
|
|
95
|
+
const globalConfig = loadYamlFile(globalPath);
|
|
96
|
+
if (globalConfig) merged = deepMerge(merged, globalConfig);
|
|
97
|
+
const localConfig = loadYamlFile(localPath);
|
|
98
|
+
if (localConfig) merged = deepMerge(merged, localConfig);
|
|
99
|
+
if (process.env["ANTHROPIC_API_KEY"]) {
|
|
100
|
+
merged.claude = merged.claude ?? {};
|
|
101
|
+
merged.claude.api_key = process.env["ANTHROPIC_API_KEY"];
|
|
102
|
+
}
|
|
103
|
+
if (process.env["ANTHROPIC_BASE_URL"]) {
|
|
104
|
+
merged.claude = merged.claude ?? {};
|
|
105
|
+
merged.claude.base_url = process.env["ANTHROPIC_BASE_URL"];
|
|
106
|
+
}
|
|
107
|
+
if (process.env["TELEGRAM_BOT_TOKEN"]) {
|
|
108
|
+
merged.telegram = merged.telegram ?? {};
|
|
109
|
+
merged.telegram.token = process.env["TELEGRAM_BOT_TOKEN"];
|
|
110
|
+
merged.telegram.enabled = true;
|
|
111
|
+
}
|
|
112
|
+
merged = interpolateObject(merged);
|
|
113
|
+
const result = ConfigSchema.safeParse(merged);
|
|
114
|
+
if (!result.success) {
|
|
115
|
+
const issues = result.error.issues.map((i) => ` ${i.path.join(".")}: ${i.message}`).join("\n");
|
|
116
|
+
throw new ConfigError(`Invalid configuration:
|
|
117
|
+
${issues}`);
|
|
118
|
+
}
|
|
119
|
+
return result.data;
|
|
120
|
+
}
|
|
121
|
+
var DEFAULT_CONFIG = `# CueClaw Configuration
|
|
122
|
+
# See docs/config.md for all options
|
|
123
|
+
|
|
124
|
+
claude:
|
|
125
|
+
api_key: \${ANTHROPIC_API_KEY}
|
|
126
|
+
planner:
|
|
127
|
+
model: claude-sonnet-4-6
|
|
128
|
+
executor:
|
|
129
|
+
model: claude-sonnet-4-6
|
|
130
|
+
|
|
131
|
+
logging:
|
|
132
|
+
level: info
|
|
133
|
+
`;
|
|
134
|
+
function createDefaultConfig() {
|
|
135
|
+
const configPath = join(cueclawHome(), "config.yaml");
|
|
136
|
+
if (!existsSync(configPath)) {
|
|
137
|
+
ensureCueclawHome();
|
|
138
|
+
writeFileSync(configPath, DEFAULT_CONFIG, "utf-8");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
function needsOnboarding() {
|
|
142
|
+
const configPath = join(cueclawHome(), "config.yaml");
|
|
143
|
+
if (!existsSync(configPath)) return true;
|
|
144
|
+
const raw = loadYamlFile(configPath);
|
|
145
|
+
if (!raw) return true;
|
|
146
|
+
const apiKey = raw.claude?.api_key;
|
|
147
|
+
if (!apiKey || typeof apiKey !== "string") {
|
|
148
|
+
return !process.env["ANTHROPIC_API_KEY"];
|
|
149
|
+
}
|
|
150
|
+
const resolved = interpolateEnvVars(apiKey);
|
|
151
|
+
return !resolved;
|
|
152
|
+
}
|
|
153
|
+
function validateConfig() {
|
|
154
|
+
const issues = [];
|
|
155
|
+
const configPath = join(cueclawHome(), "config.yaml");
|
|
156
|
+
if (!existsSync(configPath)) {
|
|
157
|
+
issues.push({ field: "config", severity: "error", message: "Config file not found. Run setup to create one." });
|
|
158
|
+
if (!process.env["ANTHROPIC_API_KEY"]) {
|
|
159
|
+
return { valid: false, issues };
|
|
160
|
+
}
|
|
161
|
+
issues.length = 0;
|
|
162
|
+
return { valid: true, issues };
|
|
163
|
+
}
|
|
164
|
+
const raw = loadYamlFile(configPath);
|
|
165
|
+
if (!raw) {
|
|
166
|
+
issues.push({ field: "config", severity: "error", message: "Config file is empty or invalid YAML." });
|
|
167
|
+
if (!process.env["ANTHROPIC_API_KEY"]) {
|
|
168
|
+
return { valid: false, issues };
|
|
169
|
+
}
|
|
170
|
+
issues.length = 0;
|
|
171
|
+
return { valid: true, issues };
|
|
172
|
+
}
|
|
173
|
+
const claude = raw.claude;
|
|
174
|
+
const rawApiKey = claude?.api_key;
|
|
175
|
+
const resolvedKey = rawApiKey ? interpolateEnvVars(rawApiKey) : process.env["ANTHROPIC_API_KEY"];
|
|
176
|
+
if (!resolvedKey) {
|
|
177
|
+
issues.push({ field: "claude.api_key", severity: "error", message: "API key is missing. Set it in config or ANTHROPIC_API_KEY env var." });
|
|
178
|
+
}
|
|
179
|
+
const baseUrl = claude?.base_url;
|
|
180
|
+
if (baseUrl && baseUrl !== "https://api.anthropic.com") {
|
|
181
|
+
try {
|
|
182
|
+
new URL(baseUrl);
|
|
183
|
+
} catch {
|
|
184
|
+
issues.push({ field: "claude.base_url", severity: "error", message: `Invalid base URL: "${baseUrl}"` });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const telegram = raw.telegram;
|
|
188
|
+
if (telegram?.enabled === true) {
|
|
189
|
+
const token = telegram.token ?? process.env["TELEGRAM_BOT_TOKEN"];
|
|
190
|
+
if (!token) {
|
|
191
|
+
issues.push({ field: "telegram.token", severity: "warning", message: "Telegram is enabled but no bot token is configured." });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const whatsapp = raw.whatsapp;
|
|
195
|
+
if (whatsapp?.enabled === true) {
|
|
196
|
+
const authDir = whatsapp.auth_dir;
|
|
197
|
+
if (authDir && !existsSync(authDir.replace("~", homedir()))) {
|
|
198
|
+
issues.push({ field: "whatsapp.auth_dir", severity: "warning", message: `WhatsApp auth directory does not exist: ${authDir}` });
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const hasErrors = issues.some((i) => i.severity === "error");
|
|
202
|
+
return { valid: !hasErrors, issues };
|
|
203
|
+
}
|
|
204
|
+
function loadExistingConfig() {
|
|
205
|
+
const configPath = join(cueclawHome(), "config.yaml");
|
|
206
|
+
const raw = loadYamlFile(configPath);
|
|
207
|
+
if (!raw) return {};
|
|
208
|
+
const claude = raw.claude;
|
|
209
|
+
const container = raw.container;
|
|
210
|
+
const telegram = raw.telegram;
|
|
211
|
+
const whatsapp = raw.whatsapp;
|
|
212
|
+
const rawKey = claude?.api_key;
|
|
213
|
+
const resolvedKey = rawKey ? interpolateEnvVars(rawKey) : void 0;
|
|
214
|
+
const envTelegramToken = process.env["TELEGRAM_BOT_TOKEN"];
|
|
215
|
+
const telegramToken = telegram?.token ?? envTelegramToken;
|
|
216
|
+
const telegramEnabled = telegram?.enabled === true || !!envTelegramToken || void 0;
|
|
217
|
+
return {
|
|
218
|
+
apiKey: resolvedKey || void 0,
|
|
219
|
+
baseUrl: claude?.base_url && claude.base_url !== "https://api.anthropic.com" ? claude.base_url : void 0,
|
|
220
|
+
containerEnabled: container?.enabled === true ? true : void 0,
|
|
221
|
+
telegramEnabled,
|
|
222
|
+
telegramToken: telegramToken || void 0,
|
|
223
|
+
whatsappEnabled: whatsapp?.enabled === true ? true : void 0
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
function writeConfig(updates) {
|
|
227
|
+
ensureCueclawHome();
|
|
228
|
+
const configPath = join(cueclawHome(), "config.yaml");
|
|
229
|
+
let existing = {};
|
|
230
|
+
if (existsSync(configPath)) {
|
|
231
|
+
const loaded = loadYamlFile(configPath);
|
|
232
|
+
if (loaded) existing = loaded;
|
|
233
|
+
}
|
|
234
|
+
const merged = deepMerge(existing, updates);
|
|
235
|
+
writeFileSync(configPath, stringifyYaml(merged), "utf-8");
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export {
|
|
239
|
+
cueclawHome,
|
|
240
|
+
ensureCueclawHome,
|
|
241
|
+
loadConfig,
|
|
242
|
+
createDefaultConfig,
|
|
243
|
+
needsOnboarding,
|
|
244
|
+
validateConfig,
|
|
245
|
+
loadExistingConfig,
|
|
246
|
+
writeConfig
|
|
247
|
+
};
|