@cjhyy/code-shell-chat 0.8.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.
Files changed (73) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +416 -0
  3. package/dist/adapter-factory.d.ts +30 -0
  4. package/dist/adapter-factory.js +198 -0
  5. package/dist/attachments.d.ts +13 -0
  6. package/dist/attachments.js +74 -0
  7. package/dist/channel.d.ts +112 -0
  8. package/dist/channel.js +60 -0
  9. package/dist/chat-gateway.d.ts +58 -0
  10. package/dist/chat-gateway.js +232 -0
  11. package/dist/cli.d.ts +2 -0
  12. package/dist/cli.js +259 -0
  13. package/dist/codeshell.d.ts +6 -0
  14. package/dist/codeshell.js +6 -0
  15. package/dist/config.d.ts +125 -0
  16. package/dist/config.js +469 -0
  17. package/dist/delivery-queue.d.ts +93 -0
  18. package/dist/delivery-queue.js +401 -0
  19. package/dist/desktop-control-client.d.ts +51 -0
  20. package/dist/desktop-control-client.js +309 -0
  21. package/dist/dingtalk.d.ts +19 -0
  22. package/dist/dingtalk.js +93 -0
  23. package/dist/discord.d.ts +23 -0
  24. package/dist/discord.js +134 -0
  25. package/dist/gateway.d.ts +34 -0
  26. package/dist/gateway.js +287 -0
  27. package/dist/index.d.ts +13 -0
  28. package/dist/index.js +13 -0
  29. package/dist/ingress.d.ts +8 -0
  30. package/dist/ingress.js +61 -0
  31. package/dist/instance-lock.d.ts +24 -0
  32. package/dist/instance-lock.js +100 -0
  33. package/dist/lark.d.ts +20 -0
  34. package/dist/lark.js +184 -0
  35. package/dist/lifecycle.d.ts +7 -0
  36. package/dist/lifecycle.js +16 -0
  37. package/dist/line.d.ts +17 -0
  38. package/dist/line.js +123 -0
  39. package/dist/matrix.d.ts +21 -0
  40. package/dist/matrix.js +208 -0
  41. package/dist/mattermost.d.ts +18 -0
  42. package/dist/mattermost.js +170 -0
  43. package/dist/media.d.ts +39 -0
  44. package/dist/media.js +223 -0
  45. package/dist/notification-progress.d.ts +46 -0
  46. package/dist/notification-progress.js +299 -0
  47. package/dist/notification-relay.d.ts +43 -0
  48. package/dist/notification-relay.js +285 -0
  49. package/dist/platform-canary.d.ts +22 -0
  50. package/dist/platform-canary.js +55 -0
  51. package/dist/protocol.d.ts +102 -0
  52. package/dist/protocol.js +1 -0
  53. package/dist/service-manager.d.ts +47 -0
  54. package/dist/service-manager.js +222 -0
  55. package/dist/slack.d.ts +17 -0
  56. package/dist/slack.js +136 -0
  57. package/dist/teams.d.ts +26 -0
  58. package/dist/teams.js +193 -0
  59. package/dist/telegram.d.ts +33 -0
  60. package/dist/telegram.js +263 -0
  61. package/dist/webhook.d.ts +26 -0
  62. package/dist/webhook.js +91 -0
  63. package/dist/wechat-login.d.ts +21 -0
  64. package/dist/wechat-login.js +97 -0
  65. package/dist/wechat-storage.d.ts +61 -0
  66. package/dist/wechat-storage.js +251 -0
  67. package/dist/wechat.d.ts +129 -0
  68. package/dist/wechat.js +1082 -0
  69. package/dist/wecom.d.ts +16 -0
  70. package/dist/wecom.js +149 -0
  71. package/dist/whatsapp.d.ts +27 -0
  72. package/dist/whatsapp.js +218 -0
  73. package/package.json +123 -0
package/dist/cli.js ADDED
@@ -0,0 +1,259 @@
1
+ #!/usr/bin/env node
2
+ import { resolve } from "node:path";
3
+ import { createInterface } from "node:readline/promises";
4
+ import { stdin, stdout } from "node:process";
5
+ import qrcode from "qrcode-terminal";
6
+ import { createChannelAdapterAsync } from "./adapter-factory.js";
7
+ import { ChatGateway, createAllowlistMiddleware, createRateLimitMiddleware, } from "./chat-gateway.js";
8
+ import { defaultGatewayConfigPath, loadGatewayConfig } from "./config.js";
9
+ import { DesktopControlClient } from "./desktop-control-client.js";
10
+ import { CODE_SHELL_REMOTE_COMMANDS, createCodeShellRemoteCommands, createMimiPetChat, } from "./gateway.js";
11
+ import { loginCodeShellWechat } from "./wechat-login.js";
12
+ import { defaultWechatDataDirectory } from "./wechat-storage.js";
13
+ import { acquireGatewayInstanceLock } from "./instance-lock.js";
14
+ import { renderWebhookIngress } from "./ingress.js";
15
+ import { GatewayServiceManager } from "./service-manager.js";
16
+ import { runPlatformCanary } from "./platform-canary.js";
17
+ import { createDesktopNotificationHandler } from "./notification-relay.js";
18
+ import { FileNotificationDeliveryProgressStore, notificationDeliveryProgressPath, } from "./notification-progress.js";
19
+ const CHAT_HELP = `用法:
20
+ code-shell-chat [--config /path/config.json]
21
+ code-shell-chat wechat login [--config /path/config.json] [--credentials-dir /path]
22
+ code-shell-chat service install|uninstall|status [--config /path/config.json]
23
+ code-shell-chat ingress print --host chat.example.com [--format caddy|nginx]
24
+ code-shell-chat canary [--config /path/config.json]
25
+
26
+ 选项:
27
+ -h, --help 显示帮助并退出`;
28
+ async function main(args = process.argv.slice(2)) {
29
+ if (args.includes("--help") || args.includes("-h")) {
30
+ console.log(CHAT_HELP);
31
+ return;
32
+ }
33
+ if (args[0] === "wechat" && args[1] === "login") {
34
+ await loginWechat(readWechatLoginOptions(args.slice(2)));
35
+ return;
36
+ }
37
+ if (args[0] === "service") {
38
+ await manageService(args.slice(1));
39
+ return;
40
+ }
41
+ if (args[0] === "ingress" && args[1] === "print") {
42
+ printIngress(args.slice(2));
43
+ return;
44
+ }
45
+ if (args[0] === "canary") {
46
+ await runCanary(args.slice(1));
47
+ return;
48
+ }
49
+ const configPath = readConfigPath(args);
50
+ const config = loadGatewayConfig({ configPath });
51
+ const lease = acquireGatewayInstanceLock(config.runtime.lockPath, "code-shell-chat CLI");
52
+ const shutdown = new AbortController();
53
+ const stop = () => shutdown.abort();
54
+ process.once("SIGINT", stop);
55
+ process.once("SIGTERM", stop);
56
+ try {
57
+ const desktop = new DesktopControlClient(config.desktop);
58
+ const adapters = await Promise.all(config.channels.map((channel) => createChannelAdapterAsync(channel, { discordCommands: CODE_SHELL_REMOTE_COMMANDS })));
59
+ const gateway = new ChatGateway({
60
+ adapters,
61
+ webhook: config.webhook,
62
+ delivery: {
63
+ path: config.runtime.inboxPath,
64
+ maxPending: config.runtime.maxPending,
65
+ maxConcurrent: config.runtime.maxConcurrent,
66
+ maxPerTarget: config.runtime.maxPerTarget,
67
+ },
68
+ adapterRestart: {
69
+ baseMs: config.runtime.adapterRestartBaseMs,
70
+ maxMs: config.runtime.adapterRestartMaxMs,
71
+ },
72
+ });
73
+ gateway.use(createAllowlistMiddleware(Object.fromEntries(config.channels.map((channel) => [
74
+ channel.channel,
75
+ { targetIds: channel.allowedTargetIds, userIds: channel.allowedUserIds },
76
+ ]))));
77
+ gateway.use(createRateLimitMiddleware(config.runtime.maxMessagesPerUserPerMinute));
78
+ gateway.use(createCodeShellRemoteCommands({ desktop }));
79
+ gateway.use(createMimiPetChat({ desktop, channels: adapters }));
80
+ console.log(`[code-shell-chat] gateway 已启动:${config.channels.map(({ channel }) => channel).join(", ")}`);
81
+ const gatewayTask = gateway.run(shutdown.signal);
82
+ const notificationTask = desktop.watchEvents(shutdown.signal, createDesktopNotificationHandler(adapters, config.notifications, {
83
+ progressStore: new FileNotificationDeliveryProgressStore(notificationDeliveryProgressPath(config.runtime.eventCursorPath)),
84
+ authorizeTarget: ({ channel, target }) => {
85
+ const current = loadGatewayConfig({ configPath });
86
+ return current.channels.some((configured) => configured.channel === channel && configured.allowedTargetIds.includes(target));
87
+ },
88
+ }), {
89
+ checkpointPath: config.runtime.eventCursorPath,
90
+ onError: (error) => console.error(`[code-shell-chat] Desktop 通知等待重试:${error instanceof Error ? error.message : String(error)}`),
91
+ });
92
+ await Promise.all([gatewayTask, notificationTask]);
93
+ }
94
+ finally {
95
+ shutdown.abort();
96
+ process.off("SIGINT", stop);
97
+ process.off("SIGTERM", stop);
98
+ lease.release();
99
+ }
100
+ }
101
+ async function runCanary(args) {
102
+ let configPath;
103
+ let timeoutMs = 10 * 60_000;
104
+ for (let index = 0; index < args.length; index += 2) {
105
+ const flag = args[index];
106
+ const value = args[index + 1];
107
+ if (!value)
108
+ throw new Error("canary 参数缺少值");
109
+ if (flag === "--config")
110
+ configPath = value;
111
+ else if (flag === "--timeout-ms" && Number.isSafeInteger(Number(value)) && Number(value) > 0) {
112
+ timeoutMs = Number(value);
113
+ }
114
+ else
115
+ throw new Error(`未知 canary 参数:${flag}`);
116
+ }
117
+ const config = loadGatewayConfig({ configPath });
118
+ const lease = acquireGatewayInstanceLock(config.runtime.lockPath, "code-shell-chat canary");
119
+ try {
120
+ const result = await runPlatformCanary({
121
+ adapters: await Promise.all(config.channels.map((channel) => createChannelAdapterAsync(channel, { discordCommands: CODE_SHELL_REMOTE_COMMANDS }))),
122
+ allowlists: Object.fromEntries(config.channels.map((channel) => [
123
+ channel.channel,
124
+ { targetIds: channel.allowedTargetIds, userIds: channel.allowedUserIds },
125
+ ])),
126
+ webhook: config.webhook,
127
+ delivery: {
128
+ path: config.runtime.inboxPath,
129
+ maxPending: config.runtime.maxPending,
130
+ maxConcurrent: config.runtime.maxConcurrent,
131
+ maxPerTarget: config.runtime.maxPerTarget,
132
+ },
133
+ timeoutMs,
134
+ onReady: (instruction) => console.log(`[code-shell-chat] ${instruction}`),
135
+ });
136
+ console.log(JSON.stringify({ ok: true, ...result }, null, 2));
137
+ }
138
+ finally {
139
+ lease.release();
140
+ }
141
+ }
142
+ async function manageService(args) {
143
+ const action = args[0];
144
+ if (action !== "install" && action !== "uninstall" && action !== "status") {
145
+ throw new Error("用法:code-shell-chat service install|uninstall|status [--config /path/config.json]");
146
+ }
147
+ const configPath = resolve(readOptionalConfigFlag(args.slice(1)) ?? defaultGatewayConfigPath());
148
+ if (action === "install")
149
+ loadGatewayConfig({ configPath });
150
+ const manager = new GatewayServiceManager({ configPath });
151
+ const status = await manager[action]();
152
+ console.log(JSON.stringify({
153
+ action,
154
+ ...status,
155
+ }, null, 2));
156
+ }
157
+ function printIngress(args) {
158
+ let publicHost;
159
+ let upstream;
160
+ let format = "caddy";
161
+ for (let index = 0; index < args.length; index += 2) {
162
+ const flag = args[index];
163
+ const value = args[index + 1];
164
+ if (!value)
165
+ throw new Error("ingress 参数缺少值");
166
+ if (flag === "--host")
167
+ publicHost = value;
168
+ else if (flag === "--upstream")
169
+ upstream = value;
170
+ else if (flag === "--format" && (value === "caddy" || value === "nginx"))
171
+ format = value;
172
+ else
173
+ throw new Error(`未知 ingress 参数:${flag}`);
174
+ }
175
+ if (!publicHost) {
176
+ throw new Error("用法:code-shell-chat ingress print --host chat.example.com [--format caddy|nginx] [--upstream 127.0.0.1:8787]");
177
+ }
178
+ stdout.write(renderWebhookIngress({ format, publicHost, ...(upstream ? { upstream } : {}) }));
179
+ }
180
+ async function loginWechat(options) {
181
+ const shutdown = new AbortController();
182
+ const stop = () => shutdown.abort();
183
+ process.once("SIGINT", stop);
184
+ process.once("SIGTERM", stop);
185
+ let scanned = false;
186
+ console.log("[code-shell-chat] 正在生成个人微信 ClawBot 登录二维码…");
187
+ try {
188
+ const result = await loginCodeShellWechat({
189
+ configPath: options.configPath,
190
+ ...(options.customCredentialsDir ? { credentialsDir: options.credentialsDir } : {}),
191
+ signal: shutdown.signal,
192
+ onQrCode: (url) => {
193
+ qrcode.generate(url, { small: true });
194
+ console.log(`如果二维码无法扫描,请打开:${url}`);
195
+ },
196
+ onStatus: (status) => {
197
+ if (status === "scaned" && !scanned) {
198
+ scanned = true;
199
+ console.log("已扫码,请在手机微信上确认…");
200
+ }
201
+ },
202
+ requestVerificationCode: async () => {
203
+ const readline = createInterface({ input: stdin, output: stdout });
204
+ try {
205
+ return await readline.question("请输入手机微信显示的验证数字:");
206
+ }
207
+ finally {
208
+ readline.close();
209
+ }
210
+ },
211
+ });
212
+ console.log(`✅ 个人微信已连接:${result.accountId}`);
213
+ console.log(`配置已更新:${result.configPath}`);
214
+ console.log("现在可以启动:code-shell-chat");
215
+ }
216
+ finally {
217
+ shutdown.abort();
218
+ process.off("SIGINT", stop);
219
+ process.off("SIGTERM", stop);
220
+ }
221
+ }
222
+ function readWechatLoginOptions(args) {
223
+ let configPath;
224
+ let credentialsDir;
225
+ for (let index = 0; index < args.length; index += 2) {
226
+ const flag = args[index];
227
+ const value = args[index + 1];
228
+ if (!value || (flag !== "--config" && flag !== "--credentials-dir")) {
229
+ throw new Error("用法:code-shell-chat wechat login [--config /path/config.json] [--credentials-dir /path]");
230
+ }
231
+ if (flag === "--config")
232
+ configPath = value;
233
+ else
234
+ credentialsDir = value;
235
+ }
236
+ return {
237
+ configPath: resolve(configPath ?? defaultGatewayConfigPath()),
238
+ credentialsDir: resolve(credentialsDir ?? defaultWechatDataDirectory()),
239
+ customCredentialsDir: credentialsDir !== undefined,
240
+ };
241
+ }
242
+ function readConfigPath(args) {
243
+ if (args.length === 0)
244
+ return undefined;
245
+ if (args[0] === "--config" && args[1] && args.length === 2)
246
+ return args[1];
247
+ throw new Error("用法:code-shell-chat [--config /path/config.json] | code-shell-chat wechat login | code-shell-chat service install|uninstall|status | code-shell-chat ingress print | code-shell-chat canary");
248
+ }
249
+ function readOptionalConfigFlag(args) {
250
+ if (args.length === 0)
251
+ return undefined;
252
+ if (args[0] === "--config" && args[1] && args.length === 2)
253
+ return args[1];
254
+ throw new Error("仅支持 --config /path/config.json");
255
+ }
256
+ main().catch((error) => {
257
+ console.error(`[code-shell-chat] ${error instanceof Error ? error.message : String(error)}`);
258
+ process.exitCode = 1;
259
+ });
@@ -0,0 +1,6 @@
1
+ export * from "./config.js";
2
+ export * from "./desktop-control-client.js";
3
+ export * from "./gateway.js";
4
+ export * from "./protocol.js";
5
+ export * from "./wechat-login.js";
6
+ export { hasWechatStoredContextToken } from "./wechat-storage.js";
@@ -0,0 +1,6 @@
1
+ export * from "./config.js";
2
+ export * from "./desktop-control-client.js";
3
+ export * from "./gateway.js";
4
+ export * from "./protocol.js";
5
+ export * from "./wechat-login.js";
6
+ export { hasWechatStoredContextToken } from "./wechat-storage.js";
@@ -0,0 +1,125 @@
1
+ export interface AllowlistConfig {
2
+ allowedTargetIds: string[];
3
+ allowedUserIds: string[];
4
+ }
5
+ export interface TelegramGatewayConfig extends AllowlistConfig {
6
+ channel: "telegram";
7
+ botToken: string;
8
+ apiBaseUrl: string;
9
+ }
10
+ export interface DiscordGatewayConfig extends AllowlistConfig {
11
+ channel: "discord";
12
+ botToken: string;
13
+ }
14
+ export interface SlackGatewayConfig extends AllowlistConfig {
15
+ channel: "slack";
16
+ botToken: string;
17
+ appToken: string;
18
+ }
19
+ export interface LarkGatewayConfig extends AllowlistConfig {
20
+ channel: "lark";
21
+ appId: string;
22
+ appSecret: string;
23
+ domain?: string;
24
+ }
25
+ export interface DingTalkGatewayConfig extends AllowlistConfig {
26
+ channel: "dingtalk";
27
+ clientId: string;
28
+ clientSecret: string;
29
+ }
30
+ export interface WeComGatewayConfig extends AllowlistConfig {
31
+ channel: "wecom";
32
+ botId: string;
33
+ secret: string;
34
+ }
35
+ export interface WechatGatewayConfig extends AllowlistConfig {
36
+ channel: "wechat";
37
+ accountId: string;
38
+ token: string;
39
+ baseUrl: string;
40
+ botAgent?: string;
41
+ protocolVersion?: string;
42
+ allowUnsafeBaseUrl?: boolean;
43
+ /** Exact extra CDN hostnames trusted for inbound media downloads (HTTPS only). */
44
+ extraCdnDownloadHosts?: string[];
45
+ credentialsDir: string;
46
+ statePath: string;
47
+ }
48
+ export interface MatrixGatewayConfig extends AllowlistConfig {
49
+ channel: "matrix";
50
+ homeserverUrl: string;
51
+ accessToken: string;
52
+ botUserId?: string;
53
+ }
54
+ export interface MattermostGatewayConfig extends AllowlistConfig {
55
+ channel: "mattermost";
56
+ serverUrl: string;
57
+ botToken: string;
58
+ botUserId?: string;
59
+ }
60
+ export interface LineGatewayConfig extends AllowlistConfig {
61
+ channel: "line";
62
+ channelSecret: string;
63
+ channelAccessToken: string;
64
+ }
65
+ export interface WhatsAppGatewayConfig extends AllowlistConfig {
66
+ channel: "whatsapp";
67
+ accessToken: string;
68
+ appSecret: string;
69
+ verifyToken: string;
70
+ phoneNumberId: string;
71
+ apiVersion: string;
72
+ }
73
+ export interface TeamsGatewayConfig extends AllowlistConfig {
74
+ channel: "teams";
75
+ appId: string;
76
+ appPassword: string;
77
+ appType: string;
78
+ tenantId?: string;
79
+ statePath: string;
80
+ }
81
+ export type ConfiguredChannel = TelegramGatewayConfig | DiscordGatewayConfig | SlackGatewayConfig | LarkGatewayConfig | DingTalkGatewayConfig | WeComGatewayConfig | WechatGatewayConfig | MatrixGatewayConfig | MattermostGatewayConfig | LineGatewayConfig | WhatsAppGatewayConfig | TeamsGatewayConfig;
82
+ export interface WebhookGatewayConfig {
83
+ host: string;
84
+ port: number;
85
+ maxBodyBytes: number;
86
+ }
87
+ export interface DesktopGatewayConfig {
88
+ descriptorPath: string;
89
+ autoLaunch: boolean;
90
+ command?: string;
91
+ args: string[];
92
+ startupTimeoutMs: number;
93
+ }
94
+ export interface GatewayConfig {
95
+ channels: ConfiguredChannel[];
96
+ desktop: DesktopGatewayConfig;
97
+ webhook: WebhookGatewayConfig;
98
+ runtime: GatewayRuntimeConfig;
99
+ notifications: GatewayNotificationTarget[];
100
+ }
101
+ export interface GatewayNotificationTarget {
102
+ channel: ConfiguredChannel["channel"];
103
+ target: string;
104
+ }
105
+ export interface GatewayRuntimeConfig {
106
+ lockPath: string;
107
+ inboxPath: string;
108
+ eventCursorPath: string;
109
+ maxPending: number;
110
+ maxConcurrent: number;
111
+ maxPerTarget: number;
112
+ maxMessagesPerUserPerMinute: number;
113
+ adapterRestartBaseMs: number;
114
+ adapterRestartMaxMs: number;
115
+ }
116
+ export interface LoadGatewayConfigOptions {
117
+ configPath?: string;
118
+ env?: NodeJS.ProcessEnv;
119
+ platform?: NodeJS.Platform;
120
+ }
121
+ export declare function defaultGatewayConfigPath(env?: NodeJS.ProcessEnv): string;
122
+ export declare function defaultDesktopControlDescriptorPath(env?: NodeJS.ProcessEnv): string;
123
+ /** Safe, disabled-by-default starter file used by the Desktop Link editor. */
124
+ export declare function gatewayConfigTemplate(): Record<string, unknown>;
125
+ export declare function loadGatewayConfig(opts?: LoadGatewayConfigOptions): GatewayConfig;