@unlinearity/cli-wechat-bridge 1.0.0 → 1.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.
Files changed (81) hide show
  1. package/bin/_run-entry.mjs +2 -8
  2. package/bin/wechat-bridge-claude.mjs +2 -2
  3. package/bin/wechat-bridge-codex.mjs +2 -2
  4. package/bin/wechat-bridge-opencode.mjs +2 -2
  5. package/bin/wechat-bridge-shell.mjs +2 -2
  6. package/bin/wechat-bridge.mjs +2 -2
  7. package/bin/wechat-check-update.mjs +2 -2
  8. package/bin/wechat-claude-start.mjs +2 -2
  9. package/bin/wechat-claude.mjs +2 -2
  10. package/bin/wechat-codex-start.mjs +2 -2
  11. package/bin/wechat-codex.mjs +2 -2
  12. package/bin/wechat-opencode-start.mjs +2 -2
  13. package/bin/wechat-opencode.mjs +2 -2
  14. package/bin/wechat-setup.mjs +5 -0
  15. package/dist/bridge/bridge-adapters.claude.js +829 -0
  16. package/dist/bridge/bridge-adapters.codex.js +2206 -0
  17. package/dist/bridge/bridge-adapters.core.js +668 -0
  18. package/dist/bridge/bridge-adapters.js +26 -0
  19. package/dist/bridge/bridge-adapters.opencode.js +1777 -0
  20. package/dist/bridge/bridge-adapters.shared.js +1098 -0
  21. package/dist/bridge/bridge-adapters.shell.js +363 -0
  22. package/dist/bridge/bridge-controller.js +44 -0
  23. package/dist/bridge/bridge-final-reply.js +31 -0
  24. package/dist/bridge/bridge-process-reaper.js +348 -0
  25. package/dist/bridge/bridge-state.js +356 -0
  26. package/dist/bridge/bridge-types.js +1 -0
  27. package/dist/bridge/bridge-utils.js +1150 -0
  28. package/dist/bridge/claude-hook.js +82 -0
  29. package/dist/bridge/claude-hooks.js +267 -0
  30. package/dist/bridge/wechat-bridge.js +904 -0
  31. package/dist/commands/check-update.js +30 -0
  32. package/dist/companion/codex-panel-link.js +72 -0
  33. package/dist/companion/codex-panel.js +179 -0
  34. package/dist/companion/codex-remote-client.js +117 -0
  35. package/dist/companion/local-companion-link.js +210 -0
  36. package/dist/companion/local-companion-start.js +372 -0
  37. package/dist/companion/local-companion.js +411 -0
  38. package/dist/media/media-types.js +1 -0
  39. package/dist/runtime/create-runtime-host.js +12 -0
  40. package/dist/runtime/legacy-adapter-runtime.js +40 -0
  41. package/dist/runtime/runtime-types.js +5 -0
  42. package/dist/utils/version-checker.js +161 -0
  43. package/dist/wechat/channel-config.js +103 -0
  44. package/dist/wechat/setup.js +283 -0
  45. package/dist/wechat/standalone-bot.js +356 -0
  46. package/dist/wechat/wechat-channel.js +481 -0
  47. package/dist/wechat/wechat-transport.js +887 -0
  48. package/package.json +6 -2
  49. package/src/bridge/bridge-adapters.claude.ts +0 -1047
  50. package/src/bridge/bridge-adapters.codex.ts +0 -2733
  51. package/src/bridge/bridge-adapters.core.ts +0 -877
  52. package/src/bridge/bridge-adapters.opencode.ts +0 -2390
  53. package/src/bridge/bridge-adapters.shared.ts +0 -1647
  54. package/src/bridge/bridge-adapters.shell.ts +0 -456
  55. package/src/bridge/bridge-adapters.ts +0 -30
  56. package/src/bridge/bridge-controller.ts +0 -58
  57. package/src/bridge/bridge-final-reply.ts +0 -56
  58. package/src/bridge/bridge-process-reaper.ts +0 -417
  59. package/src/bridge/bridge-state.ts +0 -520
  60. package/src/bridge/bridge-types.ts +0 -218
  61. package/src/bridge/bridge-utils.ts +0 -1524
  62. package/src/bridge/claude-hook.ts +0 -92
  63. package/src/bridge/claude-hooks.ts +0 -376
  64. package/src/bridge/wechat-bridge.ts +0 -1305
  65. package/src/commands/check-update.ts +0 -46
  66. package/src/companion/codex-panel-link.ts +0 -151
  67. package/src/companion/codex-panel.ts +0 -225
  68. package/src/companion/codex-remote-client.ts +0 -178
  69. package/src/companion/local-companion-link.ts +0 -338
  70. package/src/companion/local-companion-start.ts +0 -520
  71. package/src/companion/local-companion.ts +0 -532
  72. package/src/media/media-types.ts +0 -11
  73. package/src/runtime/create-runtime-host.ts +0 -16
  74. package/src/runtime/legacy-adapter-runtime.ts +0 -55
  75. package/src/runtime/runtime-types.ts +0 -51
  76. package/src/utils/version-checker.ts +0 -197
  77. package/src/wechat/channel-config.ts +0 -141
  78. package/src/wechat/setup.ts +0 -232
  79. package/src/wechat/standalone-bot.ts +0 -483
  80. package/src/wechat/wechat-channel.ts +0 -608
  81. package/src/wechat/wechat-transport.ts +0 -1290
@@ -1,92 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import crypto from "node:crypto";
4
- import net from "node:net";
5
-
6
- async function readStdin(): Promise<string> {
7
- return await new Promise<string>((resolve) => {
8
- const chunks: Buffer[] = [];
9
- process.stdin.on("data", (chunk) => {
10
- chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
11
- });
12
- process.stdin.on("end", () => {
13
- resolve(Buffer.concat(chunks).toString("utf8"));
14
- });
15
- process.stdin.resume();
16
- });
17
- }
18
-
19
- async function main(): Promise<void> {
20
- const portText = process.env.CLAUDE_WECHAT_HOOK_PORT;
21
- const token = process.env.CLAUDE_WECHAT_HOOK_TOKEN;
22
- const port = portText ? Number.parseInt(portText, 10) : Number.NaN;
23
-
24
- if (!token || !Number.isInteger(port) || port <= 0) {
25
- return;
26
- }
27
-
28
- const payload = await readStdin();
29
- if (!payload.trim()) {
30
- return;
31
- }
32
- const requestId = crypto.randomUUID();
33
- let stdout = "";
34
-
35
- await new Promise<void>((resolve) => {
36
- const socket = net.connect({ host: "127.0.0.1", port });
37
- let buffer = "";
38
- const finish = () => {
39
- try {
40
- socket.destroy();
41
- } catch {
42
- // Best effort cleanup.
43
- }
44
- resolve();
45
- };
46
-
47
- socket.once("connect", () => {
48
- try {
49
- socket.write(`${JSON.stringify({ token, requestId, payload })}\n`);
50
- } catch {
51
- finish();
52
- }
53
- });
54
-
55
- socket.setEncoding("utf8");
56
- socket.on("data", (chunk) => {
57
- buffer += chunk;
58
- while (true) {
59
- const newlineIndex = buffer.indexOf("\n");
60
- if (newlineIndex < 0) {
61
- break;
62
- }
63
- const line = buffer.slice(0, newlineIndex).trim();
64
- buffer = buffer.slice(newlineIndex + 1);
65
- if (!line) {
66
- continue;
67
- }
68
- try {
69
- const response = JSON.parse(line) as {
70
- requestId?: string;
71
- stdout?: string;
72
- };
73
- if (response.requestId === requestId && typeof response.stdout === "string") {
74
- stdout = response.stdout;
75
- }
76
- } catch {
77
- // Ignore malformed hook responses.
78
- }
79
- }
80
- });
81
- socket.once("close", finish);
82
- socket.once("error", finish);
83
- });
84
-
85
- if (stdout) {
86
- process.stdout.write(stdout);
87
- }
88
- }
89
-
90
- main().catch(() => {
91
- process.exit(0);
92
- });
@@ -1,376 +0,0 @@
1
- import type { ApprovalRequest } from "./bridge-types.ts";
2
- import { normalizeOutput, truncatePreview } from "./bridge-utils.ts";
3
-
4
- export type ClaudeHookEventName =
5
- | "SessionStart"
6
- | "UserPromptSubmit"
7
- | "PermissionRequest"
8
- | "Notification"
9
- | "Stop"
10
- | "StopFailure";
11
-
12
- export type ClaudeHookPayload = {
13
- session_id?: string;
14
- transcript_path?: string;
15
- cwd?: string;
16
- hook_event_name?: ClaudeHookEventName | string;
17
- source?: string;
18
- prompt?: string;
19
- permission_mode?: string;
20
- tool_name?: string;
21
- tool_input?: Record<string, unknown>;
22
- permission_suggestions?: unknown[];
23
- notification_type?: string;
24
- message?: string;
25
- title?: string;
26
- last_assistant_message?: string;
27
- error?: string;
28
- error_details?: string;
29
- stop_hook_active?: boolean;
30
- };
31
-
32
- export type PendingInjectedClaudePrompt = {
33
- normalizedText: string;
34
- createdAtMs: number;
35
- };
36
-
37
- export type ClaudePermissionDecisionAction = "confirm" | "deny";
38
-
39
- type ClaudeTranscriptAssistantEntry = {
40
- type?: string;
41
- message?: {
42
- role?: string;
43
- content?: unknown;
44
- stop_reason?: string | null;
45
- };
46
- };
47
-
48
- type ClaudeHookScriptParams = {
49
- platform?: NodeJS.Platform;
50
- runtimeExecPath: string;
51
- hookEntryPath: string;
52
- hookPort: number;
53
- hookToken: string;
54
- };
55
-
56
- function quoteWindowsCommandArg(value: string): string {
57
- return `"${value.replace(/"/g, '""')}"`;
58
- }
59
-
60
- function quotePosixCommandArg(value: string): string {
61
- return `'${value.replace(/'/g, `'\\''`)}'`;
62
- }
63
-
64
- export function parseClaudeHookPayload(raw: string): ClaudeHookPayload | null {
65
- const trimmed = raw.trim();
66
- if (!trimmed) {
67
- return null;
68
- }
69
-
70
- try {
71
- const parsed = JSON.parse(trimmed) as ClaudeHookPayload;
72
- return parsed && typeof parsed === "object" ? parsed : null;
73
- } catch {
74
- return null;
75
- }
76
- }
77
-
78
- export function extractClaudeResumeConversationId(
79
- transcriptPath: string | undefined,
80
- ): string | null {
81
- if (typeof transcriptPath !== "string") {
82
- return null;
83
- }
84
-
85
- const trimmed = transcriptPath.trim();
86
- if (!trimmed) {
87
- return null;
88
- }
89
-
90
- const segments = trimmed.split(/[\\/]+/);
91
- const fileName = segments[segments.length - 1] ?? "";
92
- if (!fileName.toLowerCase().endsWith(".jsonl")) {
93
- return null;
94
- }
95
-
96
- const conversationId = fileName.slice(0, -".jsonl".length).trim();
97
- return conversationId || null;
98
- }
99
-
100
- export function buildClaudeHookSettings(command: string): Record<string, unknown> {
101
- const hook = {
102
- hooks: [
103
- {
104
- type: "command",
105
- command,
106
- },
107
- ],
108
- };
109
-
110
- return {
111
- hooks: {
112
- SessionStart: [hook],
113
- UserPromptSubmit: [hook],
114
- PermissionRequest: [hook],
115
- Notification: [
116
- {
117
- matcher: "permission_prompt",
118
- hooks: hook.hooks,
119
- },
120
- ],
121
- Stop: [hook],
122
- StopFailure: [hook],
123
- },
124
- };
125
- }
126
-
127
- export function buildClaudeHookScript(params: ClaudeHookScriptParams): string {
128
- if (params.platform === "win32") {
129
- return [
130
- "@echo off",
131
- "setlocal",
132
- `set "CLAUDE_WECHAT_HOOK_PORT=${params.hookPort}"`,
133
- `set "CLAUDE_WECHAT_HOOK_TOKEN=${params.hookToken}"`,
134
- // Claude reads hook decisions from stdout, so only stderr can be discarded here.
135
- `${quoteWindowsCommandArg(params.runtimeExecPath)} --no-warnings --experimental-strip-types ${quoteWindowsCommandArg(params.hookEntryPath)} 2>nul`,
136
- "exit /b 0",
137
- ].join("\r\n");
138
- }
139
-
140
- return [
141
- "#!/bin/sh",
142
- `export CLAUDE_WECHAT_HOOK_PORT=${quotePosixCommandArg(String(params.hookPort))}`,
143
- `export CLAUDE_WECHAT_HOOK_TOKEN=${quotePosixCommandArg(params.hookToken)}`,
144
- // Claude reads hook decisions from stdout, so only stderr can be discarded here.
145
- `${quotePosixCommandArg(params.runtimeExecPath)} --no-warnings --experimental-strip-types ${quotePosixCommandArg(params.hookEntryPath)} 2>/dev/null || true`,
146
- "exit 0",
147
- ].join("\n");
148
- }
149
-
150
- function summarizeClaudePlan(plan: string): string {
151
- const lines = normalizeOutput(plan)
152
- .split("\n")
153
- .map((line) => line.trim())
154
- .filter(Boolean);
155
- if (lines.length === 0) {
156
- return "(empty plan)";
157
- }
158
-
159
- const heading = lines
160
- .find((line) => /^#+\s+/.test(line))
161
- ?.replace(/^#+\s+/, "")
162
- .trim();
163
- const description = lines.find(
164
- (line) =>
165
- !/^#+\s+/.test(line) &&
166
- !/^[-*]\s+/.test(line) &&
167
- !/^\d+\.\s+/.test(line),
168
- );
169
-
170
- return truncatePreview([heading, description].filter(Boolean).join(" - ") || lines[0], 180);
171
- }
172
-
173
- function summarizeClaudeToolInput(
174
- toolName: string,
175
- toolInput: Record<string, unknown> | undefined,
176
- ): {
177
- detailLabel: string;
178
- detailPreview: string;
179
- } {
180
- if (!toolInput) {
181
- return {
182
- detailLabel: "details",
183
- detailPreview: "(no input)",
184
- };
185
- }
186
-
187
- if (toolName === "ExitPlanMode" && typeof toolInput.plan === "string") {
188
- return {
189
- detailLabel: "plan",
190
- detailPreview: summarizeClaudePlan(toolInput.plan),
191
- };
192
- }
193
-
194
- if (typeof toolInput.command === "string" && toolInput.command.trim()) {
195
- return {
196
- detailLabel: "command",
197
- detailPreview: toolInput.command.trim(),
198
- };
199
- }
200
-
201
- if (typeof toolInput.file_path === "string" && toolInput.file_path.trim()) {
202
- return {
203
- detailLabel: "path",
204
- detailPreview: toolInput.file_path.trim(),
205
- };
206
- }
207
-
208
- if (typeof toolInput.pattern === "string" && toolInput.pattern.trim()) {
209
- return {
210
- detailLabel: "pattern",
211
- detailPreview: toolInput.pattern.trim(),
212
- };
213
- }
214
-
215
- if (typeof toolInput.url === "string" && toolInput.url.trim()) {
216
- return {
217
- detailLabel: "url",
218
- detailPreview: toolInput.url.trim(),
219
- };
220
- }
221
-
222
- return {
223
- detailLabel: "details",
224
- detailPreview: truncatePreview(JSON.stringify(toolInput), 180),
225
- };
226
- }
227
-
228
- export function buildClaudePermissionApprovalRequest(
229
- payload: ClaudeHookPayload,
230
- ): ApprovalRequest {
231
- const toolName =
232
- typeof payload.tool_name === "string" && payload.tool_name.trim()
233
- ? payload.tool_name.trim()
234
- : "Tool";
235
- const { detailLabel, detailPreview } = summarizeClaudeToolInput(toolName, payload.tool_input);
236
-
237
- return {
238
- source: "cli",
239
- summary: `Claude permission is required for ${toolName}.`,
240
- commandPreview: `${toolName}: ${detailPreview}`,
241
- toolName,
242
- detailLabel,
243
- detailPreview,
244
- };
245
- }
246
-
247
- export function buildClaudePermissionDecisionHookOutput(
248
- action: ClaudePermissionDecisionAction,
249
- ): string {
250
- const decision =
251
- action === "confirm"
252
- ? {
253
- behavior: "allow",
254
- }
255
- : {
256
- behavior: "deny",
257
- message: "Permission denied from WeChat bridge.",
258
- interrupt: false,
259
- };
260
-
261
- return JSON.stringify({
262
- hookSpecificOutput: {
263
- hookEventName: "PermissionRequest",
264
- decision,
265
- },
266
- });
267
- }
268
-
269
- export function extractClaudeAssistantMessageText(payload: ClaudeHookPayload): string {
270
- return typeof payload.last_assistant_message === "string"
271
- ? normalizeOutput(payload.last_assistant_message).trim()
272
- : "";
273
- }
274
-
275
- function extractClaudeAssistantContentText(content: unknown): string {
276
- if (!Array.isArray(content)) {
277
- return "";
278
- }
279
-
280
- const parts = content
281
- .flatMap((item) => {
282
- if (!item || typeof item !== "object") {
283
- return [];
284
- }
285
-
286
- const candidate = item as {
287
- type?: string;
288
- text?: string;
289
- };
290
- if (candidate.type !== "text" || typeof candidate.text !== "string") {
291
- return [];
292
- }
293
-
294
- const text = normalizeOutput(candidate.text).trim();
295
- return text ? [text] : [];
296
- });
297
-
298
- return parts.join("\n\n").trim();
299
- }
300
-
301
- export function extractClaudeTranscriptFinalReply(rawTranscript: string): string | null {
302
- const lines = rawTranscript.split(/\r?\n/);
303
- let fallbackText: string | null = null;
304
-
305
- for (let index = lines.length - 1; index >= 0; index -= 1) {
306
- const line = lines[index]?.trim();
307
- if (!line) {
308
- continue;
309
- }
310
-
311
- let parsed: ClaudeTranscriptAssistantEntry | null = null;
312
- try {
313
- parsed = JSON.parse(line) as ClaudeTranscriptAssistantEntry;
314
- } catch {
315
- continue;
316
- }
317
-
318
- if (!parsed || typeof parsed !== "object") {
319
- continue;
320
- }
321
-
322
- if (parsed.type !== "assistant" || !parsed.message || parsed.message.role !== "assistant") {
323
- continue;
324
- }
325
-
326
- const text = extractClaudeAssistantContentText(parsed.message.content);
327
- if (!text) {
328
- continue;
329
- }
330
-
331
- if (parsed.message.stop_reason === "end_turn") {
332
- return text;
333
- }
334
-
335
- fallbackText ??= text;
336
- }
337
-
338
- return fallbackText;
339
- }
340
-
341
- export function normalizeClaudeAssistantMessage(payload: ClaudeHookPayload): string {
342
- return extractClaudeAssistantMessageText(payload) || "(no final reply)";
343
- }
344
-
345
- export function buildClaudeFailureMessage(payload: ClaudeHookPayload): string {
346
- const details = [
347
- typeof payload.last_assistant_message === "string"
348
- ? normalizeOutput(payload.last_assistant_message).trim()
349
- : "",
350
- typeof payload.error_details === "string"
351
- ? normalizeOutput(payload.error_details).trim()
352
- : "",
353
- typeof payload.error === "string" ? payload.error.trim() : "",
354
- ].filter(Boolean);
355
-
356
- return truncatePreview(details.join(" | ") || "Claude reported an unknown error.", 500);
357
- }
358
-
359
- export function findInjectedClaudePromptIndex(
360
- prompt: string,
361
- pendingInputs: PendingInjectedClaudePrompt[],
362
- nowMs = Date.now(),
363
- maxAgeMs = 15_000,
364
- ): number {
365
- const normalizedPrompt = normalizeOutput(prompt).trim();
366
- if (!normalizedPrompt) {
367
- return -1;
368
- }
369
-
370
- return pendingInputs.findIndex((candidate) => {
371
- if (nowMs - candidate.createdAtMs > maxAgeMs) {
372
- return false;
373
- }
374
- return candidate.normalizedText === normalizedPrompt;
375
- });
376
- }