weclaude 0.0.4

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.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -0
  3. package/cli/wrc.sh +168 -0
  4. package/commands/wrc.md +7 -0
  5. package/config.example.jsonc +75 -0
  6. package/dist/cli/init.js +216 -0
  7. package/dist/cli/init.js.map +1 -0
  8. package/dist/cli/sync.js +130 -0
  9. package/dist/cli/sync.js.map +1 -0
  10. package/dist/daemon/approval.js +366 -0
  11. package/dist/daemon/approval.js.map +1 -0
  12. package/dist/daemon/ask.js +97 -0
  13. package/dist/daemon/ask.js.map +1 -0
  14. package/dist/daemon/cc-bridge.js +173 -0
  15. package/dist/daemon/cc-bridge.js.map +1 -0
  16. package/dist/daemon/claim.js +76 -0
  17. package/dist/daemon/claim.js.map +1 -0
  18. package/dist/daemon/http.js +82 -0
  19. package/dist/daemon/http.js.map +1 -0
  20. package/dist/daemon/inbound.js +145 -0
  21. package/dist/daemon/inbound.js.map +1 -0
  22. package/dist/daemon/index.js +85 -0
  23. package/dist/daemon/index.js.map +1 -0
  24. package/dist/daemon/mirror-bridge.js +539 -0
  25. package/dist/daemon/mirror-bridge.js.map +1 -0
  26. package/dist/daemon/outbound.js +33 -0
  27. package/dist/daemon/outbound.js.map +1 -0
  28. package/dist/daemon/pending.js +27 -0
  29. package/dist/daemon/pending.js.map +1 -0
  30. package/dist/daemon/redact.js +27 -0
  31. package/dist/daemon/redact.js.map +1 -0
  32. package/dist/daemon/session-cache.js +66 -0
  33. package/dist/daemon/session-cache.js.map +1 -0
  34. package/dist/daemon/sessions.js +35 -0
  35. package/dist/daemon/sessions.js.map +1 -0
  36. package/dist/daemon/ws.js +67 -0
  37. package/dist/daemon/ws.js.map +1 -0
  38. package/dist/mcp/server.js +82 -0
  39. package/dist/mcp/server.js.map +1 -0
  40. package/dist/shared/config-writer.js +39 -0
  41. package/dist/shared/config-writer.js.map +1 -0
  42. package/dist/shared/config.js +139 -0
  43. package/dist/shared/config.js.map +1 -0
  44. package/dist/shared/log.js +18 -0
  45. package/dist/shared/log.js.map +1 -0
  46. package/dist/shared/paths.js +6 -0
  47. package/dist/shared/paths.js.map +1 -0
  48. package/docs/DESIGN-INIT.md +125 -0
  49. package/docs/ONBOARDING.md +118 -0
  50. package/hooks/hooks.json +16 -0
  51. package/hooks/pre-tool-use.sh +81 -0
  52. package/launchd/com.cc-wecom.daemon.plist.template +45 -0
  53. package/package.json +77 -0
  54. package/scripts/install.sh +50 -0
  55. package/scripts/uninstall.sh +26 -0
  56. package/systemd/cc-wecom.service.template +17 -0
@@ -0,0 +1,27 @@
1
+ // Sensitive value redaction for tool_input before sending to IM.
2
+ // Keep it boring: regex on JSON keys/values that look like credentials.
3
+ const KEY_RE = /(password|passwd|pwd|secret|token|api[_-]?key|access[_-]?key|authorization|cookie)/i;
4
+ const VALUE_RE = /^(eyJ[A-Za-z0-9_-]{10,}|sk-[A-Za-z0-9]{16,}|[A-Fa-f0-9]{32,}|ghp_[A-Za-z0-9]{20,})$/;
5
+ const redactString = (s) => (VALUE_RE.test(s) ? "***" : s);
6
+ export const redact = (input) => {
7
+ if (input === null || input === undefined)
8
+ return input;
9
+ if (typeof input === "string")
10
+ return redactString(input);
11
+ if (Array.isArray(input))
12
+ return input.map(redact);
13
+ if (typeof input === "object") {
14
+ const out = {};
15
+ for (const [k, v] of Object.entries(input)) {
16
+ if (KEY_RE.test(k) && typeof v === "string") {
17
+ out[k] = "***";
18
+ }
19
+ else {
20
+ out[k] = redact(v);
21
+ }
22
+ }
23
+ return out;
24
+ }
25
+ return input;
26
+ };
27
+ //# sourceMappingURL=redact.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.js","sourceRoot":"","sources":["../../daemon/redact.ts"],"names":[],"mappings":"AAAA,iEAAiE;AACjE,wEAAwE;AACxE,MAAM,MAAM,GAAG,qFAAqF,CAAC;AACrG,MAAM,QAAQ,GAAG,qFAAqF,CAAC;AAEvG,MAAM,YAAY,GAAG,CAAC,CAAS,EAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAE3E,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAc,EAAW,EAAE;IAChD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;IAC1D,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACnD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACtE,IAAI,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAE,CAAC;gBAC5C,GAAG,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;YACrB,CAAC;QACH,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC"}
@@ -0,0 +1,66 @@
1
+ // Session cache for "Allow for session" decisions.
2
+ // Key = (sessionId, toolName, hash(cmd_prefix)). In-memory, TTL via approval.sessionCacheMinutes.
3
+ import { createHash } from "node:crypto";
4
+ const cache = new Map();
5
+ const norm = (s) => s.trim().replace(/\s+/g, " ");
6
+ const cmdPrefix = (toolName, toolInput) => {
7
+ // Bash: first token of command. Others: stable JSON.
8
+ if (toolName === "Bash" && toolInput && typeof toolInput === "object") {
9
+ const cmd = toolInput.command;
10
+ if (typeof cmd === "string") {
11
+ const first = norm(cmd).split(" ")[0] ?? "";
12
+ return first;
13
+ }
14
+ }
15
+ try {
16
+ return JSON.stringify(toolInput);
17
+ }
18
+ catch {
19
+ return String(toolInput);
20
+ }
21
+ };
22
+ export const cacheKey = (sessionId, toolName, toolInput) => {
23
+ const h = createHash("sha1").update(cmdPrefix(toolName, toolInput)).digest("hex").slice(0, 12);
24
+ return `${sessionId}::${toolName}::${h}`;
25
+ };
26
+ export const cacheGet = (key) => {
27
+ const e = cache.get(key);
28
+ if (!e)
29
+ return undefined;
30
+ if (Date.now() > e.expiresAt) {
31
+ cache.delete(key);
32
+ return undefined;
33
+ }
34
+ return e.decision;
35
+ };
36
+ export const cachePut = (key, decision, ttlMs) => {
37
+ cache.set(key, { decision, expiresAt: Date.now() + ttlMs });
38
+ };
39
+ export const cacheClear = () => cache.clear();
40
+ // ── Per-session auto-approve window ────────────────────────────────────
41
+ // Set by `allow_window` clicks; while active, approval requests within the
42
+ // SAME sessionId bypass the card flow and return allow immediately.
43
+ const autoApproveUntilBySession = new Map();
44
+ export const setAutoWindow = (sessionId, ttlMs) => {
45
+ const until = Date.now() + ttlMs;
46
+ autoApproveUntilBySession.set(sessionId, until);
47
+ return until;
48
+ };
49
+ export const autoWindowRemainingMs = (sessionId) => Math.max(0, (autoApproveUntilBySession.get(sessionId) ?? 0) - Date.now());
50
+ export const isAutoWindowActive = (sessionId) => {
51
+ const until = autoApproveUntilBySession.get(sessionId);
52
+ if (until === undefined)
53
+ return false;
54
+ if (Date.now() >= until) {
55
+ autoApproveUntilBySession.delete(sessionId);
56
+ return false;
57
+ }
58
+ return true;
59
+ };
60
+ export const clearAutoWindow = (sessionId) => {
61
+ if (sessionId === undefined)
62
+ autoApproveUntilBySession.clear();
63
+ else
64
+ autoApproveUntilBySession.delete(sessionId);
65
+ };
66
+ //# sourceMappingURL=session-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-cache.js","sourceRoot":"","sources":["../../daemon/session-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,35 @@
1
+ // Persisted principal → claude session_id map. Single JSON file, write-through.
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { expandHome } from "../shared/paths.js";
5
+ export const loadSessionStore = (filePath) => {
6
+ const abs = expandHome(filePath);
7
+ let map = {};
8
+ if (existsSync(abs)) {
9
+ try {
10
+ map = JSON.parse(readFileSync(abs, "utf8"));
11
+ }
12
+ catch {
13
+ map = {};
14
+ }
15
+ }
16
+ else {
17
+ mkdirSync(dirname(abs), { recursive: true });
18
+ }
19
+ const persist = () => {
20
+ writeFileSync(abs, JSON.stringify(map, null, 2), "utf8");
21
+ };
22
+ return {
23
+ get: (p) => map[p],
24
+ set: (p, sid) => {
25
+ map[p] = sid;
26
+ persist();
27
+ },
28
+ drop: (p) => {
29
+ delete map[p];
30
+ persist();
31
+ },
32
+ all: () => ({ ...map }),
33
+ };
34
+ };
35
+ //# sourceMappingURL=sessions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sessions.js","sourceRoot":"","sources":["../../daemon/sessions.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAShD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAgB,EAAE;IACjE,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,GAAG,GAA2B,EAAE,CAAC;IACrC,IAAI,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAA2B,CAAC;QACxE,CAAC;QAAC,MAAM,CAAC;YACP,GAAG,GAAG,EAAE,CAAC;QACX,CAAC;IACH,CAAC;SAAM,CAAC;QACN,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3D,CAAC,CAAC;IAEF,OAAO;QACL,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAClB,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;YACd,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC;YACb,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE;YACV,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC;YACd,OAAO,EAAE,CAAC;QACZ,CAAC;QACD,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,GAAG,GAAG,EAAE,CAAC;KACxB,CAAC;AACJ,CAAC,CAAC"}
@@ -0,0 +1,67 @@
1
+ // Wraps @wecom/aibot-node-sdk WSClient. Pure construction + event wiring;
2
+ // outbound helpers (text/card) live in `outbound.ts`.
3
+ import { WSClient, WSAuthFailureError, WSReconnectExhaustedError, } from "@wecom/aibot-node-sdk";
4
+ // SCENE is a numeric channel tag assigned by WeCom for telemetry;
5
+ // 0 = generic / unbranded.
6
+ const SCENE = 0;
7
+ const PLUG_VERSION = "0.0.1";
8
+ const HEARTBEAT_MS = 30_000;
9
+ const MAX_RECONNECT = 10;
10
+ const MAX_AUTH_FAIL = 5;
11
+ const sdkLogger = (log) => ({
12
+ debug: (msg, ...a) => log.debug({ a }, String(msg)),
13
+ info: (msg, ...a) => log.info({ a }, String(msg)),
14
+ warn: (msg, ...a) => log.warn({ a }, String(msg)),
15
+ error: (msg, ...a) => log.error({ a }, String(msg)),
16
+ });
17
+ export const startWs = (cfg, log) => {
18
+ const { bot } = cfg;
19
+ log.info({ botId: bot.botId, ws: bot.websocketUrl }, "WS init");
20
+ const client = new WSClient({
21
+ botId: bot.botId,
22
+ secret: bot.secret,
23
+ wsUrl: bot.websocketUrl,
24
+ logger: sdkLogger(log),
25
+ heartbeatInterval: HEARTBEAT_MS,
26
+ maxReconnectAttempts: MAX_RECONNECT,
27
+ maxAuthFailureAttempts: MAX_AUTH_FAIL,
28
+ scene: SCENE,
29
+ plug_version: PLUG_VERSION,
30
+ });
31
+ let resolveReady;
32
+ let rejectReady;
33
+ const ready = new Promise((res, rej) => {
34
+ resolveReady = res;
35
+ rejectReady = rej;
36
+ });
37
+ client.on("connected", () => log.info("WS connected"));
38
+ client.on("authenticated", () => {
39
+ log.info("WS authenticated");
40
+ resolveReady();
41
+ });
42
+ client.on("disconnected", (reason) => log.warn({ reason }, "WS disconnected"));
43
+ client.on("reconnecting", (attempt) => log.info({ attempt }, "WS reconnecting"));
44
+ client.on("error", (err) => {
45
+ log.error({ err: err.message, kind: err.constructor.name }, "WS error");
46
+ if (err instanceof WSAuthFailureError || err instanceof WSReconnectExhaustedError) {
47
+ rejectReady(err);
48
+ }
49
+ });
50
+ client.on("event.disconnected_event", () => {
51
+ log.error("WS kicked by server (new connection elsewhere); auto-restart suppressed");
52
+ client.disconnect();
53
+ });
54
+ const shutdown = async () => {
55
+ log.info("WS shutdown");
56
+ try {
57
+ client.disconnect();
58
+ }
59
+ catch (e) {
60
+ log.warn({ err: e.message }, "disconnect threw");
61
+ }
62
+ };
63
+ // SDK 构造不自动连接,需显式调用。
64
+ client.connect();
65
+ return { client, ready, shutdown };
66
+ };
67
+ //# sourceMappingURL=ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","sourceRoot":"","sources":["../../daemon/ws.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ // MCP server `wecom`. Stdio transport. Talks to local cc-wecom daemon over HTTP
2
+ // for outbound, so this server is stateless w.r.t. WS — daemon owns the socket.
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import { z } from "zod";
6
+ const DAEMON_BASE = process.env.CC_WECOM_DAEMON_BASE ?? "http://127.0.0.1:17890";
7
+ const post = async (path, body) => {
8
+ const r = await fetch(`${DAEMON_BASE}${path}`, {
9
+ method: "POST",
10
+ headers: { "content-type": "application/json" },
11
+ body: JSON.stringify(body),
12
+ });
13
+ return (await r.json().catch(() => ({})));
14
+ };
15
+ const ok = (data) => ({
16
+ content: [{ type: "text", text: JSON.stringify(data) }],
17
+ });
18
+ const fail = (msg) => ({
19
+ isError: true,
20
+ content: [{ type: "text", text: msg }],
21
+ });
22
+ const server = new McpServer({ name: "wecom", version: "0.0.1" }, { capabilities: { tools: {} } });
23
+ server.registerTool("send_markdown", {
24
+ title: "Send WeCom markdown",
25
+ description: "Proactively send a markdown message to a WeCom chat (user or group).",
26
+ inputSchema: {
27
+ chat: z
28
+ .string()
29
+ .describe('Target chat: "user:<userid>" for DM, "chat:<chatid>" for group, or raw id.'),
30
+ content: z.string().describe("Markdown content."),
31
+ },
32
+ }, async ({ chat, content }) => {
33
+ const r = await post("/message", { chat, markdown: content });
34
+ return r.ok ? ok({ ok: true }) : fail(`send failed: ${r.error ?? "unknown"}`);
35
+ });
36
+ server.registerTool("send_card", {
37
+ title: "Send WeCom template card",
38
+ description: "Send a structured template card (button_interaction / text_notice / news_notice / vote_interaction / multiple_interaction). Pass the full TemplateCard object as `card`.",
39
+ inputSchema: {
40
+ chat: z.string(),
41
+ card: z.record(z.string(), z.unknown()).describe("WeCom TemplateCard object."),
42
+ },
43
+ }, async ({ chat, card }) => {
44
+ const r = await post("/card", { chat, card });
45
+ return r.ok ? ok({ ok: true, reqId: r.reqId }) : fail(`send card failed: ${r.error ?? "unknown"}`);
46
+ });
47
+ server.registerTool("ask_user", {
48
+ title: "Ask user via WeCom button card",
49
+ description: "Push a yes/no/options button card to the user, block until they click, return the selected key. Useful for in-task confirmation that needs human input.",
50
+ inputSchema: {
51
+ chat: z.string(),
52
+ title: z.string(),
53
+ detail: z.string().optional(),
54
+ options: z
55
+ .array(z.object({ key: z.string(), label: z.string(), style: z.number().int().optional() }))
56
+ .min(1)
57
+ .max(6),
58
+ timeoutSec: z.number().int().positive().default(600),
59
+ },
60
+ }, async ({ chat, title, detail, options, timeoutSec }) => {
61
+ const r = await post("/ask", { chat, title, detail, options, timeoutSec });
62
+ if (r.ok)
63
+ return ok({ choice: r.choice });
64
+ return fail(`ask failed: ${r.error ?? "unknown"}`);
65
+ });
66
+ server.registerTool("daemon_status", {
67
+ title: "WeCom daemon status",
68
+ description: "Return health / connection state of the local cc-wecom daemon.",
69
+ inputSchema: {},
70
+ }, async () => {
71
+ try {
72
+ const r = await fetch(`${DAEMON_BASE}/status`);
73
+ const j = await r.json();
74
+ return ok(j);
75
+ }
76
+ catch (e) {
77
+ return fail(`daemon unreachable: ${e.message}`);
78
+ }
79
+ });
80
+ const transport = new StdioServerTransport();
81
+ await server.connect(transport);
82
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ // Surgical edits to ~/.cc-wecom/config.jsonc and secrets.json — preserves
2
+ // comments + formatting via jsonc-parser's `modify` patches.
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { dirname } from "node:path";
5
+ import { applyEdits, modify, parse as parseJsonc } from "jsonc-parser";
6
+ import { expandHome } from "./paths.js";
7
+ const FORMAT = { tabSize: 2, insertSpaces: true, eol: "\n" };
8
+ const readText = (abs) => existsSync(abs) ? readFileSync(abs, "utf8") : "{}\n";
9
+ const writeText = (abs, txt) => {
10
+ mkdirSync(dirname(abs), { recursive: true });
11
+ writeFileSync(abs, txt.endsWith("\n") ? txt : `${txt}\n`, "utf8");
12
+ };
13
+ /** Apply a sequence of (path, value) patches to a JSONC file. value=undefined deletes. */
14
+ export const patchJsonc = (filePath, patches) => {
15
+ const abs = expandHome(filePath);
16
+ let txt = readText(abs);
17
+ for (const { path, value } of patches) {
18
+ const edits = modify(txt, path, value, { formattingOptions: FORMAT });
19
+ txt = applyEdits(txt, edits);
20
+ }
21
+ writeText(abs, txt);
22
+ };
23
+ /** Append `value` into a string array at `path`, deduped. No-op if already present. */
24
+ export const appendUnique = (filePath, path, value) => {
25
+ const abs = expandHome(filePath);
26
+ const txt = readText(abs);
27
+ const tree = (parseJsonc(txt) ?? {});
28
+ const cur = path.reduce((acc, key) => {
29
+ if (acc && typeof acc === "object")
30
+ return acc[String(key)];
31
+ return undefined;
32
+ }, tree);
33
+ const arr = Array.isArray(cur) ? cur.slice() : [];
34
+ if (arr.includes(value))
35
+ return;
36
+ arr.push(value);
37
+ patchJsonc(filePath, [{ path, value: arr }]);
38
+ };
39
+ //# sourceMappingURL=config-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-writer.js","sourceRoot":"","sources":["../../shared/config-writer.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6DAA6D;AAC7D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,KAAK,IAAI,UAAU,EAAiB,MAAM,cAAc,CAAC;AACtF,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,MAAM,GAAG,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC;AAE7D,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAU,EAAE,CACvC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AAEvD,MAAM,SAAS,GAAG,CAAC,GAAW,EAAE,GAAW,EAAQ,EAAE;IACnD,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,EAAE,MAAM,CAAC,CAAC;AACpE,CAAC,CAAC;AAEF,0FAA0F;AAC1F,MAAM,CAAC,MAAM,UAAU,GAAG,CACxB,QAAgB,EAChB,OAAkD,EAC5C,EAAE;IACR,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjC,IAAI,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IACxB,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,OAAO,EAAE,CAAC;QACtC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,iBAAiB,EAAE,MAAM,EAAE,CAAC,CAAC;QACtE,GAAG,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACtB,CAAC,CAAC;AAEF,uFAAuF;AACvF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,QAAgB,EAAE,IAAc,EAAE,KAAa,EAAQ,EAAE;IACpF,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IACjC,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC;IAC1B,MAAM,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,CAA4B,CAAC;IAChE,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,CAAU,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;QAC5C,IAAI,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAQ,GAA+B,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QACzF,OAAO,SAAS,CAAC;IACnB,CAAC,EAAE,IAAI,CAAC,CAAC;IACT,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAE,GAAgB,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAChE,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC;QAAE,OAAO;IAChC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChB,UAAU,CAAC,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC;AAC/C,CAAC,CAAC"}
@@ -0,0 +1,139 @@
1
+ // Declarative config: schema (zod) + loader. Pure transforms, file IO at boundary.
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { parse as parseJsonc } from "jsonc-parser";
4
+ import { z } from "zod";
5
+ import { expandHome } from "./paths.js";
6
+ // ── Schema ──────────────────────────────────────────────────────────
7
+ const Bot = z.object({
8
+ botId: z.string().min(1),
9
+ secret: z.string().min(1),
10
+ websocketUrl: z.string().url().default("wss://openws.work.weixin.qq.com"),
11
+ });
12
+ const Daemon = z.object({
13
+ host: z.string().default("127.0.0.1"),
14
+ port: z.number().int().min(1).max(65535).default(17890),
15
+ stateDir: z.string().default("~/.cc-wecom/state"),
16
+ logFile: z.string().default("~/.cc-wecom/daemon.log"),
17
+ logLevel: z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
18
+ });
19
+ const Mirror = z.object({
20
+ // Where Claude Code writes per-project transcripts. Forks like `claude-internal`
21
+ // use a parallel dir (e.g. `~/.claude-internal/projects`). The mirror tails
22
+ // `<projectsDir>/<encoded(wrc.cwd)>/<sid>.jsonl`.
23
+ projectsDir: z.string().default("~/.claude/projects"),
24
+ // Pin a specific Claude session to mirror. Empty → auto-pick latest .jsonl
25
+ // under `<projectsDir>/<encoded(wrc.cwd)>/` by mtime.
26
+ sessionId: z.string().default(""),
27
+ // Inbound (WeCom→CLI) inject target. Empty → spawn `claude --resume -p`
28
+ // (response lands in jsonl but NOT in the live TTY). Set to a tmux target
29
+ // (e.g. "_cc-wecom" or "_cc-wecom:0.0") to paste into the live interactive
30
+ // claude — the only way to make WeCom messages visible in the CLI window.
31
+ tmuxTarget: z.string().default(""),
32
+ // Where to push live assistant output. Empty → fall back to defaultChat.
33
+ pushChat: z.string().default(""),
34
+ // Cap a single push payload (WeCom markdown ~2048 limit). Long replies are split.
35
+ chunkChars: z.number().int().positive().default(1800),
36
+ // Mirror the user's CLI prompts (type:"user" with string content).
37
+ includeUser: z.boolean().default(true),
38
+ // Mirror assistant tool_use blocks (Bash/Edit/Read/...).
39
+ includeTools: z.boolean().default(true),
40
+ // Mirror tool_result blocks. Off by default — usually noisy.
41
+ includeToolResults: z.boolean().default(false),
42
+ // Truncate each tool_result body to this many chars.
43
+ toolResultMaxChars: z.number().int().positive().default(400),
44
+ // Where inbound images/files from WeCom get saved before being pasted into
45
+ // the live TTY. Files persist — claude reads them by absolute path.
46
+ inboxDir: z.string().default("~/.cc-wecom/inbox"),
47
+ });
48
+ const Wrc = z.object({
49
+ allowFrom: z.array(z.string()).default([]),
50
+ mode: z.enum(["headless", "mirror"]).default("headless"),
51
+ claudeBin: z.string().default("claude"),
52
+ cwd: z.string().default("~"),
53
+ sessionMapFile: z.string().default("~/.cc-wecom/sessions.json"),
54
+ extraArgs: z.array(z.string()).default([]),
55
+ mirror: Mirror.default({}),
56
+ });
57
+ const Approval = z.object({
58
+ enabled: z.boolean().default(true),
59
+ matcher: z.string().default(".*"),
60
+ approvers: z.array(z.string()).default([]),
61
+ hookTimeoutSec: z.number().int().positive().default(1810),
62
+ longPollSec: z.number().int().positive().default(1800),
63
+ sessionCacheMinutes: z.number().int().nonnegative().default(30),
64
+ windowMinutes: z.number().int().nonnegative().default(10),
65
+ sensitiveArgRedact: z.boolean().default(true),
66
+ fallbackOnError: z.enum(["ask", "allow", "deny"]).default("ask"),
67
+ });
68
+ const SyncTarget = z.object({
69
+ kind: z.literal("claude-internal"),
70
+ settingsPath: z.string(),
71
+ scope: z.enum(["user", "project", "local"]).default("user"),
72
+ });
73
+ const Sync = z.object({
74
+ targets: z.array(SyncTarget).default([]),
75
+ });
76
+ export const ConfigSchema = z.object({
77
+ bot: Bot,
78
+ defaultChat: z.string().default(""),
79
+ daemon: Daemon.default({}),
80
+ wrc: Wrc.default({}),
81
+ approval: Approval.default({}),
82
+ sync: Sync.default({ targets: [] }),
83
+ });
84
+ // ── Loader ──────────────────────────────────────────────────────────
85
+ const DEFAULT_CONFIG_PATHS = [
86
+ "~/.cc-wecom/config.jsonc",
87
+ "~/.cc-wecom/config.json",
88
+ ];
89
+ const SECRETS_PATH = "~/.cc-wecom/secrets.json";
90
+ const readJsoncIfExists = (p) => {
91
+ const abs = expandHome(p);
92
+ if (!existsSync(abs))
93
+ return undefined;
94
+ const text = readFileSync(abs, "utf8");
95
+ return parseJsonc(text);
96
+ };
97
+ const deepMerge = (a, b) => {
98
+ const out = { ...a };
99
+ for (const [k, v] of Object.entries(b ?? {})) {
100
+ const av = out[k];
101
+ if (v && typeof v === "object" && !Array.isArray(v) && av && typeof av === "object" && !Array.isArray(av)) {
102
+ out[k] = deepMerge(av, v);
103
+ }
104
+ else if (v !== undefined && v !== "") {
105
+ out[k] = v;
106
+ }
107
+ }
108
+ return out;
109
+ };
110
+ /** Resolve config path: explicit > $CC_WECOM_CONFIG > defaults. */
111
+ const resolveConfigPath = (explicit) => {
112
+ if (explicit)
113
+ return explicit;
114
+ if (process.env.CC_WECOM_CONFIG)
115
+ return process.env.CC_WECOM_CONFIG;
116
+ for (const p of DEFAULT_CONFIG_PATHS) {
117
+ if (existsSync(expandHome(p)))
118
+ return p;
119
+ }
120
+ return undefined;
121
+ };
122
+ export const loadConfig = (explicitPath) => {
123
+ const sourcePath = resolveConfigPath(explicitPath);
124
+ if (!sourcePath) {
125
+ throw new Error(`cc-wecom config not found. Create ~/.cc-wecom/config.jsonc (see config.example.jsonc) or set $CC_WECOM_CONFIG.`);
126
+ }
127
+ const base = (readJsoncIfExists(sourcePath) ?? {});
128
+ const secrets = (readJsoncIfExists(SECRETS_PATH) ?? {});
129
+ const merged = deepMerge(base, secrets);
130
+ const parsed = ConfigSchema.safeParse(merged);
131
+ if (!parsed.success) {
132
+ const issues = parsed.error.issues
133
+ .map((i) => ` - ${i.path.join(".") || "<root>"}: ${i.message}`)
134
+ .join("\n");
135
+ throw new Error(`cc-wecom config invalid (${sourcePath}):\n${issues}`);
136
+ }
137
+ return { config: parsed.data, sourcePath: expandHome(sourcePath) };
138
+ };
139
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../shared/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ // Logger: pino, dual sink (stdout pretty in TTY, file always).
2
+ import { existsSync, mkdirSync, createWriteStream } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import pino from "pino";
5
+ import { expandHome } from "./paths.js";
6
+ const ensureDir = (filePath) => {
7
+ const d = dirname(filePath);
8
+ if (!existsSync(d))
9
+ mkdirSync(d, { recursive: true });
10
+ };
11
+ export const makeLogger = ({ logFile, logLevel, name }) => {
12
+ const abs = expandHome(logFile);
13
+ ensureDir(abs);
14
+ const fileStream = createWriteStream(abs, { flags: "a" });
15
+ const opts = { name, level: logLevel, base: { pid: process.pid } };
16
+ return pino(opts, fileStream);
17
+ };
18
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../shared/log.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,IAAyC,MAAM,MAAM,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,SAAS,GAAG,CAAC,QAAgB,EAAQ,EAAE;IAC3C,MAAM,CAAC,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5B,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAAE,SAAS,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AACxD,CAAC,CAAC;AAQF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAkB,EAAU,EAAE;IAChF,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC;IAChC,SAAS,CAAC,GAAG,CAAC,CAAC;IACf,MAAM,UAAU,GAAG,iBAAiB,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC1D,MAAM,IAAI,GAAkB,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC;IAClF,OAAO,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;AAChC,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ // Path utilities — kept tiny and pure.
2
+ import { homedir } from "node:os";
3
+ import { resolve } from "node:path";
4
+ /** Expand leading `~` and normalize. */
5
+ export const expandHome = (p) => p.startsWith("~") ? resolve(homedir(), p.slice(p.startsWith("~/") ? 2 : 1)) : resolve(p);
6
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../shared/paths.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,wCAAwC;AACxC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,CAAS,EAAU,EAAE,CAC9C,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC"}