@emqo/claudebridge 0.3.1 → 0.5.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/ctl.js CHANGED
@@ -101,11 +101,40 @@ else if (category === "auto") {
101
101
  if (action === "add") {
102
102
  const [userId, platform, chatId, ...descParts] = rest;
103
103
  if (!userId || !platform || !chatId || !descParts.length)
104
- fail("Usage: auto add <user_id> <platform> <chat_id> <description>");
104
+ fail("Usage: auto add <user_id> <platform> <chat_id> <description> [--parent <id>]");
105
+ // Parse optional --parent flag
106
+ let parentId = null;
107
+ const parentIdx = descParts.indexOf("--parent");
108
+ if (parentIdx !== -1 && descParts[parentIdx + 1]) {
109
+ parentId = parseInt(descParts[parentIdx + 1]);
110
+ descParts.splice(parentIdx, 2);
111
+ }
105
112
  const desc = descParts.join(" ");
106
- const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, created_at) VALUES (?, ?, ?, ?, 'auto', ?)").run(userId, platform, chatId, desc, Date.now());
113
+ const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, created_at) VALUES (?, ?, ?, ?, 'auto', ?, ?)").run(userId, platform, chatId, desc, parentId, Date.now());
107
114
  output({ ok: true, id: Number(r.lastInsertRowid), message: "Auto task queued" });
108
115
  }
116
+ else if (action === "add-approval") {
117
+ const [userId, platform, chatId, ...descParts] = rest;
118
+ if (!userId || !platform || !chatId || !descParts.length)
119
+ fail("Usage: auto add-approval <user_id> <platform> <chat_id> <description> [--parent <id>]");
120
+ let parentId = null;
121
+ const parentIdx = descParts.indexOf("--parent");
122
+ if (parentIdx !== -1 && descParts[parentIdx + 1]) {
123
+ parentId = parseInt(descParts[parentIdx + 1]);
124
+ descParts.splice(parentIdx, 2);
125
+ }
126
+ const desc = descParts.join(" ");
127
+ const r = db.prepare("INSERT INTO tasks (user_id, platform, chat_id, description, status, parent_id, created_at) VALUES (?, ?, ?, ?, 'approval_pending', ?, ?)").run(userId, platform, chatId, desc, parentId, Date.now());
128
+ output({ ok: true, id: Number(r.lastInsertRowid), message: "Auto task queued for approval" });
129
+ }
130
+ else if (action === "result") {
131
+ const [taskId, ...resultParts] = rest;
132
+ if (!taskId || !resultParts.length)
133
+ fail("Usage: auto result <task_id> <result_text>");
134
+ const resultText = resultParts.join(" ");
135
+ db.prepare("UPDATE tasks SET result = ? WHERE id = ?").run(resultText, parseInt(taskId));
136
+ output({ ok: true, message: "Task result saved" });
137
+ }
109
138
  else if (action === "list") {
110
139
  const [userId] = rest;
111
140
  if (!userId)
@@ -121,7 +150,7 @@ else if (category === "auto") {
121
150
  output({ ok: true, message: "Auto task cancelled" });
122
151
  }
123
152
  else {
124
- fail("Usage: auto <add|list|cancel> ...");
153
+ fail("Usage: auto <add|add-approval|result|list|cancel> ...");
125
154
  }
126
155
  }
127
156
  else {
package/dist/index.js CHANGED
@@ -6,6 +6,7 @@ import { Store } from "./core/store.js";
6
6
  import { AgentEngine } from "./core/agent.js";
7
7
  import { TelegramAdapter } from "./adapters/telegram.js";
8
8
  import { DiscordAdapter } from "./adapters/discord.js";
9
+ import { WebhookServer } from "./webhook.js";
9
10
  async function main() {
10
11
  const _cfgIdx = process.argv.indexOf("--config");
11
12
  const _cfgPath = _cfgIdx !== -1 ? process.argv[_cfgIdx + 1] : undefined;
@@ -13,6 +14,7 @@ async function main() {
13
14
  const store = new Store();
14
15
  const engine = new AgentEngine(config, store);
15
16
  const adapters = [];
17
+ let webhookServer = null;
16
18
  if (config.platforms.telegram.enabled) {
17
19
  if (!config.platforms.telegram.token) {
18
20
  console.error("[fatal] TELEGRAM_BOT_TOKEN not set");
@@ -31,11 +33,18 @@ async function main() {
31
33
  console.error("[fatal] no platform enabled");
32
34
  process.exit(1);
33
35
  }
36
+ // Start webhook server if enabled
37
+ if (config.webhook?.enabled) {
38
+ webhookServer = new WebhookServer(store, config.webhook, config.cron || []);
39
+ webhookServer.start();
40
+ }
34
41
  // --- Register signal handlers and hot-reload BEFORE starting adapters ---
35
42
  const shutdown = () => {
36
43
  console.log("[claudebridge] shutting down...");
37
44
  for (const a of adapters)
38
45
  a.stop();
46
+ if (webhookServer)
47
+ webhookServer.stop();
39
48
  setTimeout(() => process.exit(0), 1000);
40
49
  };
41
50
  process.on("SIGINT", shutdown);
@@ -52,6 +52,29 @@ export function generateSkillDoc(ctx) {
52
52
  ` 2. "优化模块A:[具体描述,包含文件路径和修改内容]"`,
53
53
  ` 3. "优化模块B:[具体描述]"`,
54
54
  ` 4. "运行测试验证所有修改,提交代码,生成优化报告"`,
55
+ ``,
56
+ `### 跨任务记忆传递`,
57
+ `- 每个 auto-task 完成关键分析后,用 memory add 保存结论`,
58
+ `- 下一个 auto-task 会自动加载记忆,可以读取前序任务的成果`,
59
+ `- 示例:分析完成后调用 \`${ctl} memory add ${ctx.userId} "模块A优化点:1.重构API 2.添加缓存 3.修复N+1查询"\``,
60
+ `- 最后一个 auto-task 完成后,清理临时工作记忆(可选)`,
61
+ `- 重要:描述中包含"先用 memory list 查看前序任务的分析结果"可确保链式上下文不断`,
62
+ ``,
63
+ `### 需要审批的自动任务 (Human-in-the-Loop)`,
64
+ `- 关键/破坏性任务用 add-approval: \`${ctl} auto add-approval ${ctx.userId} ${ctx.platform} ${ctx.chatId} "任务描述"\``,
65
+ `- 适用场景:部署、删除、大规模重构、生产环境变更`,
66
+ `- 普通分析/读取任务用 add: \`${ctl} auto add ...\``,
67
+ `- 审批任务会发送给用户确认,批准后才进入执行队列`,
68
+ ``,
69
+ `### 条件分支(侦查模式)`,
70
+ `- 用 --parent <id> 关联子任务: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "子任务描述" --parent 42\``,
71
+ `- 侦查模式:任务1分析 → 存记忆 → 根据结果动态创建后续任务`,
72
+ `- 保存任务结果: \`${ctl} auto result <taskId> "结论摘要"\``,
73
+ `- 示例:`,
74
+ ` 1. 任务#1(侦查): "分析性能瓶颈" → 发现3个问题 → 创建#2,#3,#4 --parent 1`,
75
+ ` 2. 任务#2: "修复N+1查询" (--parent 1)`,
76
+ ` 3. 任务#3: "添加缓存层" (--parent 1)`,
77
+ ` 4. 任务#4: "运行基准测试" (--parent 1)`,
55
78
  ].join("\n");
56
79
  }
57
80
  return [
@@ -100,5 +123,28 @@ export function generateSkillDoc(ctx) {
100
123
  ` 2. "Optimize module A: [specific file paths and changes]"`,
101
124
  ` 3. "Optimize module B: [specific description]"`,
102
125
  ` 4. "Run tests, commit changes, generate optimization report"`,
126
+ ``,
127
+ `### Cross-Task Memory Bridging`,
128
+ `- After completing key analysis in an auto-task, save conclusions via memory add`,
129
+ `- The next auto-task automatically loads memories, accessing prior task findings`,
130
+ `- Example: after analysis, call \`${ctl} memory add ${ctx.userId} "Module A needs: 1.refactor API 2.add cache 3.fix N+1 queries"\``,
131
+ `- Optionally clean up temporary work memories after the final auto-task`,
132
+ `- Tip: include "first run memory list to review prior task findings" in descriptions to ensure chain continuity`,
133
+ ``,
134
+ `### Human-in-the-Loop Auto Tasks`,
135
+ `- For critical/destructive tasks, use add-approval: \`${ctl} auto add-approval ${ctx.userId} ${ctx.platform} ${ctx.chatId} "description"\``,
136
+ `- Use cases: deployments, deletions, large-scale refactors, production changes`,
137
+ `- For analysis/read-only tasks, use regular add: \`${ctl} auto add ...\``,
138
+ `- Approval tasks are sent to the user for confirmation before entering the execution queue`,
139
+ ``,
140
+ `### Conditional Branching (Scout Pattern)`,
141
+ `- Link child tasks with --parent <id>: \`${ctl} auto add ${ctx.userId} ${ctx.platform} ${ctx.chatId} "subtask" --parent 42\``,
142
+ `- Scout pattern: task 1 analyzes → saves memory → dynamically creates follow-up tasks`,
143
+ `- Save task results: \`${ctl} auto result <taskId> "conclusion summary"\``,
144
+ `- Example:`,
145
+ ` 1. Task #1 (scout): "Analyze performance bottlenecks" → finds 3 issues → creates #2,#3,#4 --parent 1`,
146
+ ` 2. Task #2: "Fix N+1 queries" (--parent 1)`,
147
+ ` 3. Task #3: "Add caching layer" (--parent 1)`,
148
+ ` 4. Task #4: "Run benchmarks" (--parent 1)`,
103
149
  ].join("\n");
104
150
  }
@@ -0,0 +1,18 @@
1
+ import { Store } from "./core/store.js";
2
+ import { WebhookConfig, CronEntry } from "./core/config.js";
3
+ export declare class WebhookServer {
4
+ private store;
5
+ private config;
6
+ private cronEntries;
7
+ private server;
8
+ private cronTimers;
9
+ constructor(store: Store, config: WebhookConfig, cronEntries?: CronEntry[]);
10
+ start(): void;
11
+ stop(): void;
12
+ private handleRequest;
13
+ private authenticateBearer;
14
+ private verifyGitHubSignature;
15
+ private buildGitHubDescription;
16
+ private readBody;
17
+ private json;
18
+ }
@@ -0,0 +1,160 @@
1
+ import { createServer } from "node:http";
2
+ import { createHmac, timingSafeEqual } from "node:crypto";
3
+ export class WebhookServer {
4
+ store;
5
+ config;
6
+ cronEntries;
7
+ server = null;
8
+ cronTimers = [];
9
+ constructor(store, config, cronEntries = []) {
10
+ this.store = store;
11
+ this.config = config;
12
+ this.cronEntries = cronEntries;
13
+ }
14
+ start() {
15
+ this.server = createServer((req, res) => this.handleRequest(req, res));
16
+ this.server.listen(this.config.port, () => {
17
+ console.log(`[webhook] HTTP server listening on port ${this.config.port}`);
18
+ });
19
+ // Start cron schedulers
20
+ for (const entry of this.cronEntries) {
21
+ const ms = entry.schedule_minutes * 60000;
22
+ const timer = setInterval(() => {
23
+ try {
24
+ const id = this.store.addTask(entry.user_id, entry.platform, entry.chat_id, entry.description, undefined, true);
25
+ console.log(`[cron] created auto-task #${id}: ${entry.description}`);
26
+ }
27
+ catch (e) {
28
+ console.error(`[cron] failed to create task:`, e);
29
+ }
30
+ }, ms);
31
+ this.cronTimers.push(timer);
32
+ console.log(`[cron] scheduled every ${entry.schedule_minutes}min: ${entry.description}`);
33
+ }
34
+ }
35
+ stop() {
36
+ if (this.server)
37
+ this.server.close();
38
+ for (const timer of this.cronTimers)
39
+ clearInterval(timer);
40
+ this.cronTimers = [];
41
+ }
42
+ async handleRequest(req, res) {
43
+ const url = new URL(req.url || "/", `http://localhost:${this.config.port}`);
44
+ // GET /health
45
+ if (req.method === "GET" && url.pathname === "/health") {
46
+ this.json(res, 200, { ok: true, timestamp: new Date().toISOString() });
47
+ return;
48
+ }
49
+ // POST /api/task — Bearer token auth
50
+ if (req.method === "POST" && url.pathname === "/api/task") {
51
+ if (!this.authenticateBearer(req)) {
52
+ this.json(res, 401, { error: "Unauthorized" });
53
+ return;
54
+ }
55
+ try {
56
+ const body = await this.readBody(req);
57
+ const data = JSON.parse(body);
58
+ if (!data.user_id || !data.platform || !data.chat_id || !data.description) {
59
+ this.json(res, 400, { error: "Missing required fields: user_id, platform, chat_id, description" });
60
+ return;
61
+ }
62
+ let id;
63
+ if (data.approval) {
64
+ id = this.store.addApprovalTask(data.user_id, data.platform, data.chat_id, data.description, data.parent_id);
65
+ }
66
+ else {
67
+ id = this.store.addTask(data.user_id, data.platform, data.chat_id, data.description, undefined, true, data.parent_id);
68
+ }
69
+ this.json(res, 201, { ok: true, id, status: data.approval ? "approval_pending" : "auto" });
70
+ }
71
+ catch (e) {
72
+ this.json(res, 400, { error: e.message || "Invalid JSON" });
73
+ }
74
+ return;
75
+ }
76
+ // POST /webhook/github — GitHub webhook with HMAC verification
77
+ if (req.method === "POST" && url.pathname === "/webhook/github") {
78
+ const userId = url.searchParams.get("user_id");
79
+ const platform = url.searchParams.get("platform") || "telegram";
80
+ const chatId = url.searchParams.get("chat_id");
81
+ if (!userId || !chatId) {
82
+ this.json(res, 400, { error: "Missing user_id or chat_id query params" });
83
+ return;
84
+ }
85
+ const body = await this.readBody(req);
86
+ // Verify GitHub signature if secret is configured
87
+ if (this.config.github_secret) {
88
+ const signature = req.headers["x-hub-signature-256"];
89
+ if (!signature || !this.verifyGitHubSignature(body, signature)) {
90
+ this.json(res, 403, { error: "Invalid signature" });
91
+ return;
92
+ }
93
+ }
94
+ try {
95
+ const payload = JSON.parse(body);
96
+ const event = req.headers["x-github-event"] || "unknown";
97
+ const description = this.buildGitHubDescription(event, payload);
98
+ const id = this.store.addTask(userId, platform, chatId, description, undefined, true);
99
+ console.log(`[webhook] github ${event} → auto-task #${id}`);
100
+ this.json(res, 201, { ok: true, id, event });
101
+ }
102
+ catch (e) {
103
+ this.json(res, 400, { error: e.message || "Invalid payload" });
104
+ }
105
+ return;
106
+ }
107
+ this.json(res, 404, { error: "Not found" });
108
+ }
109
+ authenticateBearer(req) {
110
+ if (!this.config.token)
111
+ return false;
112
+ const auth = req.headers.authorization || "";
113
+ const token = auth.startsWith("Bearer ") ? auth.slice(7) : "";
114
+ return token === this.config.token;
115
+ }
116
+ verifyGitHubSignature(body, signature) {
117
+ const expected = "sha256=" + createHmac("sha256", this.config.github_secret).update(body).digest("hex");
118
+ try {
119
+ return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
125
+ buildGitHubDescription(event, payload) {
126
+ const repo = payload.repository?.full_name || "unknown";
127
+ switch (event) {
128
+ case "push": {
129
+ const branch = (payload.ref || "").replace("refs/heads/", "");
130
+ const count = payload.commits?.length || 0;
131
+ const msg = payload.head_commit?.message?.split("\n")[0] || "";
132
+ return `[GitHub Push] ${repo}:${branch} — ${count} commit(s). Latest: "${msg}". Review changes and summarize impact.`;
133
+ }
134
+ case "pull_request": {
135
+ const pr = payload.pull_request || {};
136
+ const action = payload.action || "opened";
137
+ return `[GitHub PR #${pr.number}] ${repo} — ${action}: "${pr.title}". Review PR description and provide analysis.`;
138
+ }
139
+ case "issues": {
140
+ const issue = payload.issue || {};
141
+ const action = payload.action || "opened";
142
+ return `[GitHub Issue #${issue.number}] ${repo} — ${action}: "${issue.title}". Analyze and suggest next steps.`;
143
+ }
144
+ default:
145
+ return `[GitHub ${event}] ${repo} — Event received. Analyze and summarize.`;
146
+ }
147
+ }
148
+ readBody(req) {
149
+ return new Promise((resolve, reject) => {
150
+ let data = "";
151
+ req.on("data", (chunk) => { data += chunk.toString(); });
152
+ req.on("end", () => resolve(data));
153
+ req.on("error", reject);
154
+ });
155
+ }
156
+ json(res, status, data) {
157
+ res.writeHead(status, { "Content-Type": "application/json" });
158
+ res.end(JSON.stringify(data));
159
+ }
160
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@emqo/claudebridge",
3
- "version": "0.3.1",
4
- "description": "Bridge Claude Code Agent SDK to chat platforms (Telegram, Discord, etc.)",
3
+ "version": "0.5.0",
4
+ "description": "Bridge claude CLI to chat platforms (Telegram, Discord) with HITL approval, conditional branching, webhook triggers, parallel execution, and observability",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
7
7
  "claudebridge": "dist/cli.js",
@@ -17,20 +17,28 @@
17
17
  "claude",
18
18
  "claude-code",
19
19
  "telegram",
20
+ "discord",
20
21
  "bridge",
21
- "agent"
22
+ "agent",
23
+ "chatbot",
24
+ "hitl",
25
+ "webhook",
26
+ "multi-agent"
22
27
  ],
23
28
  "files": [
24
29
  "dist",
25
30
  "config.yaml.example"
26
31
  ],
27
32
  "license": "MIT",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/Emqo/ClaudeBridge.git"
36
+ },
37
+ "homepage": "https://github.com/Emqo/ClaudeBridge#readme",
28
38
  "dependencies": {
29
- "@anthropic-ai/claude-agent-sdk": "^0.2.50",
30
39
  "better-sqlite3": "^12.6.2",
31
40
  "discord.js": "^14.25.1",
32
41
  "dotenv": "^17.3.1",
33
- "grammy": "^1.40.0",
34
42
  "ioredis": "^5.9.3",
35
43
  "yaml": "^2.8.2"
36
44
  },