@ekay/claude-im 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/LICENSE +21 -0
- package/README.md +119 -0
- package/dist/index.js +1710 -0
- package/package.json +48 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1710 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { readFileSync, existsSync, unlinkSync, writeFileSync, readdirSync, mkdirSync, chmodSync } from 'fs';
|
|
4
|
+
import { dirname, resolve, basename } from 'path';
|
|
5
|
+
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto';
|
|
6
|
+
import { load, dump } from 'js-yaml';
|
|
7
|
+
import readline from 'readline';
|
|
8
|
+
import { query } from '@anthropic-ai/claude-agent-sdk';
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
import { fileURLToPath } from 'url';
|
|
11
|
+
import * as Lark from '@larksuiteoapi/node-sdk';
|
|
12
|
+
|
|
13
|
+
var __defProp = Object.defineProperty;
|
|
14
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
15
|
+
var __esm = (fn, res) => function __init() {
|
|
16
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
17
|
+
};
|
|
18
|
+
var __export = (target, all) => {
|
|
19
|
+
for (var name in all)
|
|
20
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/core/registry.ts
|
|
24
|
+
function registerAdapter(platform, ctor) {
|
|
25
|
+
registry.set(platform, ctor);
|
|
26
|
+
}
|
|
27
|
+
function createAdapter(config) {
|
|
28
|
+
const Ctor = registry.get(config.platform);
|
|
29
|
+
if (!Ctor) throw new Error(`\u672A\u627E\u5230\u5E73\u53F0 "${config.platform}" \u7684\u9002\u914D\u5668\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E`);
|
|
30
|
+
return new Ctor(config);
|
|
31
|
+
}
|
|
32
|
+
function registeredPlatforms() {
|
|
33
|
+
return [...registry.keys()];
|
|
34
|
+
}
|
|
35
|
+
var registry;
|
|
36
|
+
var init_registry = __esm({
|
|
37
|
+
"src/core/registry.ts"() {
|
|
38
|
+
registry = /* @__PURE__ */ new Map();
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
var ts, logger;
|
|
42
|
+
var init_logger = __esm({
|
|
43
|
+
"src/logger/index.ts"() {
|
|
44
|
+
ts = () => chalk.blue((/* @__PURE__ */ new Date()).toLocaleTimeString());
|
|
45
|
+
logger = {
|
|
46
|
+
/** 普通信息日志 */
|
|
47
|
+
info: (msg) => console.log(`${ts()} ${chalk.cyan("\u2139")} ${msg}`),
|
|
48
|
+
/** 成功日志 */
|
|
49
|
+
success: (msg) => console.log(`${ts()} ${chalk.green("\u2705")} ${msg}`),
|
|
50
|
+
/** 警告日志 */
|
|
51
|
+
warn: (msg) => console.log(`${ts()} ${chalk.yellow("\u26A0")} ${msg}`),
|
|
52
|
+
/** 错误日志 */
|
|
53
|
+
error: (msg) => console.log(`${ts()} ${chalk.red("\u274C")} ${msg}`),
|
|
54
|
+
/** 收到消息 - 仅记录脱敏标识,不记录内容 */
|
|
55
|
+
msgReceived: (msgId, chatType) => console.log(`${ts()} ${chalk.blue("\u{1F4AC}")} ${chalk.gray("msg:" + msgId.slice(0, 8))} [${chatType}]`),
|
|
56
|
+
/** 发送回复 */
|
|
57
|
+
reply: (msgId) => console.log(`${ts()} ${chalk.green("\u21A9")} replied to ${chalk.gray(msgId.slice(0, 8))}`),
|
|
58
|
+
/** 工具调用 - 仅记录工具名,不记录参数 */
|
|
59
|
+
tool: (name) => console.log(`${ts()} ${chalk.magenta("\u{1F527}")} ${chalk.bold(name)}`),
|
|
60
|
+
/** 灰色次要信息日志 */
|
|
61
|
+
dim: (msg) => console.log(`${ts()} ${chalk.gray(msg)}`)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// src/utils/lock.ts
|
|
67
|
+
var lock_exports = {};
|
|
68
|
+
__export(lock_exports, {
|
|
69
|
+
acquireLock: () => acquireLock,
|
|
70
|
+
killByLock: () => killByLock,
|
|
71
|
+
listLocks: () => listLocks,
|
|
72
|
+
releaseLock: () => releaseLock
|
|
73
|
+
});
|
|
74
|
+
function getLockDir() {
|
|
75
|
+
const home = process.env.CLAUDE_IM_HOME || resolve(process.env.HOME || "~", ".claude-im");
|
|
76
|
+
return home;
|
|
77
|
+
}
|
|
78
|
+
function acquireLock(profile, cwd) {
|
|
79
|
+
const lockPath = resolve(getLockDir(), `lock-${profile}.pid`);
|
|
80
|
+
if (existsSync(lockPath)) {
|
|
81
|
+
const existing = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
82
|
+
try {
|
|
83
|
+
process.kill(existing.pid, 0);
|
|
84
|
+
throw new Error(`Profile "${profile}" is already running (PID ${existing.pid})`);
|
|
85
|
+
} catch (e) {
|
|
86
|
+
if (e.code === "ESRCH") {
|
|
87
|
+
unlinkSync(lockPath);
|
|
88
|
+
} else {
|
|
89
|
+
throw e;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const data = { pid: process.pid, profile, cwd, startedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
94
|
+
writeFileSync(lockPath, JSON.stringify(data), { mode: 384 });
|
|
95
|
+
return data;
|
|
96
|
+
}
|
|
97
|
+
function releaseLock(profile) {
|
|
98
|
+
const lockPath = resolve(getLockDir(), `lock-${profile}.pid`);
|
|
99
|
+
try {
|
|
100
|
+
unlinkSync(lockPath);
|
|
101
|
+
} catch {
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function listLocks() {
|
|
105
|
+
const dir = getLockDir();
|
|
106
|
+
const result = [];
|
|
107
|
+
let files;
|
|
108
|
+
try {
|
|
109
|
+
files = readdirSync(dir);
|
|
110
|
+
} catch {
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
for (const f of files) {
|
|
114
|
+
if (!f.startsWith("lock-") || !f.endsWith(".pid")) continue;
|
|
115
|
+
try {
|
|
116
|
+
const data = JSON.parse(readFileSync(resolve(dir, f), "utf8"));
|
|
117
|
+
try {
|
|
118
|
+
process.kill(data.pid, 0);
|
|
119
|
+
result.push(data);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
function killByLock(profile) {
|
|
128
|
+
const lockPath = resolve(getLockDir(), `lock-${profile}.pid`);
|
|
129
|
+
if (!existsSync(lockPath)) return false;
|
|
130
|
+
const data = JSON.parse(readFileSync(lockPath, "utf8"));
|
|
131
|
+
try {
|
|
132
|
+
process.kill(data.pid, "SIGTERM");
|
|
133
|
+
return true;
|
|
134
|
+
} catch {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
var init_lock = __esm({
|
|
139
|
+
"src/utils/lock.ts"() {
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// src/config/defaults.ts
|
|
144
|
+
var DEFAULT_CLAUDE, DEFAULT_OVERFLOW, DEFAULT_FILE, DEFAULT_EXEC_SECURITY, DEFAULT_STREAMING, DEFAULT_PROFILE;
|
|
145
|
+
var init_defaults = __esm({
|
|
146
|
+
"src/config/defaults.ts"() {
|
|
147
|
+
DEFAULT_CLAUDE = {
|
|
148
|
+
permission_mode: "acceptEdits",
|
|
149
|
+
allowed_tools: ["Read", "Write", "Edit", "Bash", "Glob", "Grep"],
|
|
150
|
+
thinking: { type: "adaptive" }
|
|
151
|
+
};
|
|
152
|
+
DEFAULT_OVERFLOW = {
|
|
153
|
+
mode: "document",
|
|
154
|
+
chunk: { threshold: 2800 },
|
|
155
|
+
document: { threshold: 2800, title_template: "Claude \u56DE\u590D {datetime}", cleanup: { max_docs: 50 } }
|
|
156
|
+
};
|
|
157
|
+
DEFAULT_FILE = {
|
|
158
|
+
enabled: true,
|
|
159
|
+
size_limit: 31457280,
|
|
160
|
+
temp_dir: "",
|
|
161
|
+
prompt: "\u5206\u6790\u6587\u4EF6\u5185\u5BB9\uFF0C\u7ED9\u51FA\u56DE\u5E94",
|
|
162
|
+
multifile_prompt: "\u5206\u6790\u4EE5\u4E0B {count} \u4E2A\u6587\u4EF6\u7684\u5185\u5BB9\uFF0C\u7ED9\u51FA\u56DE\u5E94",
|
|
163
|
+
multifile_timeout: 300
|
|
164
|
+
};
|
|
165
|
+
DEFAULT_EXEC_SECURITY = {
|
|
166
|
+
enabled: true,
|
|
167
|
+
blacklist: ["rm -rf", "sudo", "dd if=", "shutdown", "reboot"],
|
|
168
|
+
confirm_on_warning: true
|
|
169
|
+
};
|
|
170
|
+
DEFAULT_STREAMING = {
|
|
171
|
+
enabled: true,
|
|
172
|
+
flush_interval_ms: 200,
|
|
173
|
+
thinking_enabled: true,
|
|
174
|
+
fallback_on_error: true
|
|
175
|
+
};
|
|
176
|
+
DEFAULT_PROFILE = {
|
|
177
|
+
platform: "feishu",
|
|
178
|
+
platform_config: { app_id: "", owner_open_id: "" },
|
|
179
|
+
claude: DEFAULT_CLAUDE,
|
|
180
|
+
overflow: DEFAULT_OVERFLOW,
|
|
181
|
+
file: DEFAULT_FILE,
|
|
182
|
+
commands: {},
|
|
183
|
+
exec_commands: {},
|
|
184
|
+
exec_security: DEFAULT_EXEC_SECURITY,
|
|
185
|
+
streaming: DEFAULT_STREAMING,
|
|
186
|
+
processing_timeout_ms: 36e5,
|
|
187
|
+
card_title: "Claude",
|
|
188
|
+
image_prompt: "\u5206\u6790\u56FE\u7247\uFF0C\u7ED9\u51FA\u56DE\u5E94"
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
function getEnvSecret(platform, profile) {
|
|
193
|
+
const pu = platform.toUpperCase();
|
|
194
|
+
const key = profile.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
195
|
+
if (process.env[`${pu}_${key}_APP_SECRET`]) return process.env[`${pu}_${key}_APP_SECRET`];
|
|
196
|
+
if (process.env[`${pu}_APP_SECRET`]) return process.env[`${pu}_APP_SECRET`];
|
|
197
|
+
if (platform === "feishu" && process.env.LARK_APP_SECRET) return process.env.LARK_APP_SECRET;
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
function getEnvAppId(platform, profile) {
|
|
201
|
+
const pu = platform.toUpperCase();
|
|
202
|
+
const key = profile.toUpperCase().replace(/[^A-Z0-9]/g, "_");
|
|
203
|
+
if (process.env[`${pu}_${key}_APP_ID`]) return process.env[`${pu}_${key}_APP_ID`];
|
|
204
|
+
if (process.env[`${pu}_APP_ID`]) return process.env[`${pu}_APP_ID`];
|
|
205
|
+
if (platform === "feishu" && process.env.LARK_APP_ID) return process.env.LARK_APP_ID;
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
var init_env = __esm({
|
|
209
|
+
"src/utils/env.ts"() {
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
function getKeyPath() {
|
|
213
|
+
const home = process.env.CLAUDE_IM_HOME || resolve(process.env.HOME || "~", ".claude-im");
|
|
214
|
+
return resolve(home, ".key");
|
|
215
|
+
}
|
|
216
|
+
function loadOrCreateKey() {
|
|
217
|
+
const keyPath = getKeyPath();
|
|
218
|
+
try {
|
|
219
|
+
return readFileSync(keyPath);
|
|
220
|
+
} catch {
|
|
221
|
+
const key = randomBytes(32);
|
|
222
|
+
writeFileSync(keyPath, key, { mode: 384 });
|
|
223
|
+
chmodSync(keyPath, 384);
|
|
224
|
+
return key;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function encrypt(plaintext) {
|
|
228
|
+
const key = loadOrCreateKey();
|
|
229
|
+
const iv = randomBytes(IV_LEN);
|
|
230
|
+
const cipher = createCipheriv(ALGO, key, iv);
|
|
231
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
232
|
+
const authTag = cipher.getAuthTag();
|
|
233
|
+
return Buffer.concat([iv, authTag, encrypted]).toString("base64");
|
|
234
|
+
}
|
|
235
|
+
function decrypt(ciphertext) {
|
|
236
|
+
const key = loadOrCreateKey();
|
|
237
|
+
const buf = Buffer.from(ciphertext, "base64");
|
|
238
|
+
const iv = buf.subarray(0, IV_LEN);
|
|
239
|
+
const authTag = buf.subarray(IV_LEN, IV_LEN + AUTH_TAG_LEN);
|
|
240
|
+
const encrypted = buf.subarray(IV_LEN + AUTH_TAG_LEN);
|
|
241
|
+
const decipher = createDecipheriv(ALGO, key, iv);
|
|
242
|
+
decipher.setAuthTag(authTag);
|
|
243
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString("utf8");
|
|
244
|
+
}
|
|
245
|
+
var ALGO, IV_LEN, AUTH_TAG_LEN;
|
|
246
|
+
var init_crypto = __esm({
|
|
247
|
+
"src/utils/crypto.ts"() {
|
|
248
|
+
ALGO = "aes-256-gcm";
|
|
249
|
+
IV_LEN = 12;
|
|
250
|
+
AUTH_TAG_LEN = 16;
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
function deepMerge(base, override) {
|
|
254
|
+
if (!override) return base;
|
|
255
|
+
const result = { ...base };
|
|
256
|
+
for (const [key, val] of Object.entries(override)) {
|
|
257
|
+
if (val !== void 0 && val !== null) {
|
|
258
|
+
if (typeof val === "object" && !Array.isArray(val) && typeof result[key] === "object") {
|
|
259
|
+
result[key] = deepMerge(result[key], val);
|
|
260
|
+
} else {
|
|
261
|
+
result[key] = val;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
267
|
+
function loadRawConfig() {
|
|
268
|
+
try {
|
|
269
|
+
if (existsSync(GLOBAL_CONFIG_PATH)) return load(readFileSync(GLOBAL_CONFIG_PATH, "utf8"));
|
|
270
|
+
} catch (e) {
|
|
271
|
+
logger.warn(`\u914D\u7F6E\u6587\u4EF6\u89E3\u6790\u5931\u8D25: ${GLOBAL_CONFIG_PATH} \u2014 ${e.message.slice(0, 60)}`);
|
|
272
|
+
}
|
|
273
|
+
return {};
|
|
274
|
+
}
|
|
275
|
+
function loadProjectConfig(cwd) {
|
|
276
|
+
const path = resolve(cwd, ".claude-im.yml");
|
|
277
|
+
try {
|
|
278
|
+
if (existsSync(path)) return load(readFileSync(path, "utf8"));
|
|
279
|
+
} catch (e) {
|
|
280
|
+
logger.warn(`\u9879\u76EE\u914D\u7F6E\u89E3\u6790\u5931\u8D25: ${path} \u2014 ${e.message.slice(0, 60)}`);
|
|
281
|
+
}
|
|
282
|
+
return {};
|
|
283
|
+
}
|
|
284
|
+
function detectPlatform(raw) {
|
|
285
|
+
if (raw.platform) {
|
|
286
|
+
const pc = raw[raw.platform];
|
|
287
|
+
return { platform: raw.platform, platformConfig: pc ?? {} };
|
|
288
|
+
}
|
|
289
|
+
if (raw.feishu?.app_id) {
|
|
290
|
+
const { app_secret: _, ...rest } = raw.feishu;
|
|
291
|
+
return { platform: "feishu", platformConfig: rest };
|
|
292
|
+
}
|
|
293
|
+
return { platform: "feishu", platformConfig: {} };
|
|
294
|
+
}
|
|
295
|
+
function resolveSecret(platform, profile, rawConfig) {
|
|
296
|
+
const envSecret = getEnvSecret(platform, profile);
|
|
297
|
+
if (envSecret) return { secret: envSecret, source: "env" };
|
|
298
|
+
if (!existsSync(VAULT_DIR)) mkdirSync(VAULT_DIR, { recursive: true, mode: 448 });
|
|
299
|
+
const vaultPath = resolve(VAULT_DIR, profile === "default" ? "default" : profile);
|
|
300
|
+
if (existsSync(vaultPath)) {
|
|
301
|
+
try {
|
|
302
|
+
return { secret: decrypt(readFileSync(vaultPath, "utf8")), source: "vault" };
|
|
303
|
+
} catch {
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const configSecret = rawConfig.app_secret;
|
|
307
|
+
if (configSecret) {
|
|
308
|
+
logger.warn(`profile "${profile}" \u7684\u5BC6\u94A5\u660E\u6587\u5B58\u5728\u914D\u7F6E\u6587\u4EF6\u4E2D\uFF0C\u5EFA\u8BAE\u4F7F\u7528\u73AF\u5883\u53D8\u91CF\u6216 vault \u52A0\u5BC6\u5B58\u50A8`);
|
|
309
|
+
return { secret: configSecret, source: "config" };
|
|
310
|
+
}
|
|
311
|
+
throw new Error(`\u672A\u627E\u5230 profile "${profile}" (\u5E73\u53F0: ${platform}) \u7684\u5BC6\u94A5\u3002\u8BF7\u8BBE\u7F6E\u73AF\u5883\u53D8\u91CF\u6216\u8FD0\u884C --setup`);
|
|
312
|
+
}
|
|
313
|
+
function saveSecretToVault(profile, secret) {
|
|
314
|
+
if (!existsSync(VAULT_DIR)) mkdirSync(VAULT_DIR, { recursive: true, mode: 448 });
|
|
315
|
+
writeFileSync(resolve(VAULT_DIR, profile === "default" ? "default" : profile), encrypt(secret), { mode: 384 });
|
|
316
|
+
}
|
|
317
|
+
function saveConfig(raw) {
|
|
318
|
+
const dir = dirname(GLOBAL_CONFIG_PATH);
|
|
319
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
320
|
+
const clean = structuredClone(raw);
|
|
321
|
+
if (clean.feishu) delete clean.feishu.app_secret;
|
|
322
|
+
if (clean.profiles) {
|
|
323
|
+
for (const p of Object.values(clean.profiles)) {
|
|
324
|
+
if (p?.feishu) delete p.feishu.app_secret;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
writeFileSync(GLOBAL_CONFIG_PATH, `# claude-im \u914D\u7F6E\u6587\u4EF6
|
|
328
|
+
# \u901A\u8FC7\u73AF\u5883\u53D8\u91CF\u6216 vault \u7BA1\u7406\u5BC6\u94A5
|
|
329
|
+
|
|
330
|
+
${dump(clean, { indent: 2 })}`, { mode: 384 });
|
|
331
|
+
}
|
|
332
|
+
function resolveConfig(profile, cwd) {
|
|
333
|
+
const raw = loadRawConfig();
|
|
334
|
+
const projectRaw = loadProjectConfig(cwd);
|
|
335
|
+
let profileRaw = {};
|
|
336
|
+
if (profile === "default") {
|
|
337
|
+
profileRaw = raw;
|
|
338
|
+
} else {
|
|
339
|
+
profileRaw = deepMerge({ ...raw }, raw.profiles?.[profile]);
|
|
340
|
+
}
|
|
341
|
+
const { platform, platformConfig } = detectPlatform(profileRaw);
|
|
342
|
+
const envAppId = getEnvAppId(platform, profile);
|
|
343
|
+
if (envAppId) platformConfig.app_id = envAppId;
|
|
344
|
+
if (!platformConfig.app_id) platformConfig.app_id = "";
|
|
345
|
+
const { secret, source } = resolveSecret(platform, profile, platformConfig);
|
|
346
|
+
const base = deepMerge(DEFAULT_PROFILE, raw);
|
|
347
|
+
const merged = deepMerge(base, profileRaw);
|
|
348
|
+
const final = deepMerge(merged, projectRaw);
|
|
349
|
+
final.platform = platform;
|
|
350
|
+
final.platform_config = platformConfig;
|
|
351
|
+
return {
|
|
352
|
+
...final,
|
|
353
|
+
profile,
|
|
354
|
+
platform_secret: secret,
|
|
355
|
+
platform_secret_source: source
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
var HOME, GLOBAL_CONFIG_PATH, VAULT_DIR;
|
|
359
|
+
var init_loader = __esm({
|
|
360
|
+
"src/config/loader.ts"() {
|
|
361
|
+
init_defaults();
|
|
362
|
+
init_env();
|
|
363
|
+
init_crypto();
|
|
364
|
+
init_logger();
|
|
365
|
+
HOME = process.env.CLAUDE_IM_HOME || resolve(process.env.HOME || "~", ".claude-im");
|
|
366
|
+
GLOBAL_CONFIG_PATH = resolve(HOME, "config.yml");
|
|
367
|
+
VAULT_DIR = resolve(HOME, "vault");
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
// src/setup/interactive.ts
|
|
372
|
+
var interactive_exports = {};
|
|
373
|
+
__export(interactive_exports, {
|
|
374
|
+
runNewProfile: () => runNewProfile,
|
|
375
|
+
runSetup: () => runSetup
|
|
376
|
+
});
|
|
377
|
+
function prompt(rl, question) {
|
|
378
|
+
return new Promise((res) => {
|
|
379
|
+
rl.question(question, (a) => res(a.trim()));
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
function loadRaw() {
|
|
383
|
+
try {
|
|
384
|
+
if (existsSync(GLOBAL_CONFIG_PATH2)) return load(readFileSync(GLOBAL_CONFIG_PATH2, "utf8"));
|
|
385
|
+
} catch {
|
|
386
|
+
}
|
|
387
|
+
return {};
|
|
388
|
+
}
|
|
389
|
+
function saveRaw(raw) {
|
|
390
|
+
const dir = dirname(GLOBAL_CONFIG_PATH2);
|
|
391
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
392
|
+
writeFileSync(GLOBAL_CONFIG_PATH2, dump(raw, { indent: 2 }), { mode: 384 });
|
|
393
|
+
}
|
|
394
|
+
async function choosePlatform(rl) {
|
|
395
|
+
const platforms = registeredPlatforms();
|
|
396
|
+
console.log("\n \u652F\u6301\u7684 IM \u5E73\u53F0:");
|
|
397
|
+
platforms.forEach((p, i) => console.log(` ${i + 1}. ${p}`));
|
|
398
|
+
const choice = await prompt(rl, ` \u9009\u62E9\u5E73\u53F0 [${platforms.join("/")}] (\u9ED8\u8BA4 feishu): `);
|
|
399
|
+
if (!choice) return "feishu";
|
|
400
|
+
const idx = parseInt(choice);
|
|
401
|
+
if (idx >= 1 && idx <= platforms.length) return platforms[idx - 1];
|
|
402
|
+
return platforms.includes(choice) ? choice : "feishu";
|
|
403
|
+
}
|
|
404
|
+
async function runSetup(profile) {
|
|
405
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
406
|
+
const isDefault = !profile || profile === "default";
|
|
407
|
+
const label = isDefault ? "\u9ED8\u8BA4 profile" : `profile "${profile}"`;
|
|
408
|
+
console.log(`
|
|
409
|
+
\u{1F6E0} claude-im setup \u2014 \u914D\u7F6E ${label}
|
|
410
|
+
`);
|
|
411
|
+
const platform = await choosePlatform(rl);
|
|
412
|
+
const appId = await prompt(rl, ` ${platform.toUpperCase()} App ID : `);
|
|
413
|
+
const appSecret = await prompt(rl, ` ${platform.toUpperCase()} App Secret : `);
|
|
414
|
+
rl.close();
|
|
415
|
+
const profileName = isDefault ? "default" : profile;
|
|
416
|
+
saveSecretToVault(profileName, appSecret);
|
|
417
|
+
const raw = loadRaw();
|
|
418
|
+
if (isDefault) {
|
|
419
|
+
raw.platform = platform;
|
|
420
|
+
raw[platform] = { ...raw[platform] ?? {}, app_id: appId };
|
|
421
|
+
} else {
|
|
422
|
+
raw.profiles = raw.profiles ?? {};
|
|
423
|
+
raw.profiles[profileName] = {
|
|
424
|
+
platform,
|
|
425
|
+
[platform]: { app_id: appId }
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
saveRaw(raw);
|
|
429
|
+
logger.success(`\u914D\u7F6E\u5DF2\u4FDD\u5B58: ${GLOBAL_CONFIG_PATH2}`);
|
|
430
|
+
logger.dim(`\u5BC6\u94A5\u5DF2\u52A0\u5BC6\u5B58\u50A8\u5728 vault/${profileName}`);
|
|
431
|
+
logger.dim("\u5411 bot \u53D1\u9001\u4EFB\u610F\u6D88\u606F\u5373\u53EF\u81EA\u52A8\u83B7\u53D6 owner_open_id\n");
|
|
432
|
+
}
|
|
433
|
+
async function runNewProfile() {
|
|
434
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
435
|
+
console.log("\n\u{1F6E0} claude-im \u2014 \u6DFB\u52A0\u65B0 profile\n");
|
|
436
|
+
const platform = await choosePlatform(rl);
|
|
437
|
+
const appId = await prompt(rl, ` ${platform.toUpperCase()} App ID : `);
|
|
438
|
+
const appSecret = await prompt(rl, ` ${platform.toUpperCase()} App Secret : `);
|
|
439
|
+
const name = await prompt(rl, " Profile \u540D\u79F0 : ");
|
|
440
|
+
rl.close();
|
|
441
|
+
const profileName = name || `bot-${Math.random().toString(36).slice(2, 6)}`;
|
|
442
|
+
console.log(` \u2192 \u4F7F\u7528\u540D\u79F0: ${profileName}`);
|
|
443
|
+
saveSecretToVault(profileName, appSecret);
|
|
444
|
+
const raw = loadRaw();
|
|
445
|
+
raw.profiles = raw.profiles ?? {};
|
|
446
|
+
raw.profiles[profileName] = {
|
|
447
|
+
platform,
|
|
448
|
+
[platform]: { app_id: appId }
|
|
449
|
+
};
|
|
450
|
+
saveRaw(raw);
|
|
451
|
+
logger.success(`Profile "${profileName}" (${platform}) \u5DF2\u4FDD\u5B58`);
|
|
452
|
+
return profileName;
|
|
453
|
+
}
|
|
454
|
+
var HOME2, GLOBAL_CONFIG_PATH2;
|
|
455
|
+
var init_interactive = __esm({
|
|
456
|
+
"src/setup/interactive.ts"() {
|
|
457
|
+
init_loader();
|
|
458
|
+
init_logger();
|
|
459
|
+
init_registry();
|
|
460
|
+
HOME2 = process.env.CLAUDE_IM_HOME || resolve(process.env.HOME || "~", ".claude-im");
|
|
461
|
+
GLOBAL_CONFIG_PATH2 = resolve(HOME2, "config.yml");
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
// src/config/index.ts
|
|
466
|
+
var config_exports = {};
|
|
467
|
+
__export(config_exports, {
|
|
468
|
+
DEFAULT_PROFILE: () => DEFAULT_PROFILE,
|
|
469
|
+
resolveConfig: () => resolveConfig,
|
|
470
|
+
saveConfig: () => saveConfig,
|
|
471
|
+
saveSecretToVault: () => saveSecretToVault
|
|
472
|
+
});
|
|
473
|
+
var init_config = __esm({
|
|
474
|
+
"src/config/index.ts"() {
|
|
475
|
+
init_loader();
|
|
476
|
+
init_defaults();
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
function getStateDir() {
|
|
480
|
+
const home = process.env.CLAUDE_IM_HOME || resolve(process.env.HOME || "~", ".claude-im");
|
|
481
|
+
return resolve(home, "state");
|
|
482
|
+
}
|
|
483
|
+
function getStatePath(profile) {
|
|
484
|
+
const dir = getStateDir();
|
|
485
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true, mode: 448 });
|
|
486
|
+
return resolve(dir, `${profile}.json.enc`);
|
|
487
|
+
}
|
|
488
|
+
function saveState(profile, data) {
|
|
489
|
+
const path = getStatePath(profile);
|
|
490
|
+
writeFileSync(path, encrypt(JSON.stringify(data)), { mode: 384 });
|
|
491
|
+
}
|
|
492
|
+
function loadState(profile) {
|
|
493
|
+
const path = getStatePath(profile);
|
|
494
|
+
if (!existsSync(path)) return null;
|
|
495
|
+
try {
|
|
496
|
+
return JSON.parse(decrypt(readFileSync(path, "utf8")));
|
|
497
|
+
} catch {
|
|
498
|
+
return null;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
function deleteState(profile) {
|
|
502
|
+
const path = getStatePath(profile);
|
|
503
|
+
try {
|
|
504
|
+
if (existsSync(path)) {
|
|
505
|
+
unlinkSync(path);
|
|
506
|
+
}
|
|
507
|
+
} catch {
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
function hasState(profile) {
|
|
511
|
+
return existsSync(getStatePath(profile));
|
|
512
|
+
}
|
|
513
|
+
var init_vault = __esm({
|
|
514
|
+
"src/session/vault.ts"() {
|
|
515
|
+
init_crypto();
|
|
516
|
+
}
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
// src/session/manager.ts
|
|
520
|
+
function getSession(profile) {
|
|
521
|
+
const cached = memoryCache.get(profile);
|
|
522
|
+
if (cached) return cached;
|
|
523
|
+
const data = loadState(profile);
|
|
524
|
+
if (!data) return null;
|
|
525
|
+
const result = {
|
|
526
|
+
sessionId: data.session_id || "",
|
|
527
|
+
chatId: data.chat_id || ""
|
|
528
|
+
};
|
|
529
|
+
if (result.sessionId) memoryCache.set(profile, result);
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
function setSession(profile, sessionId, chatId) {
|
|
533
|
+
const existing = loadState(profile) ?? {};
|
|
534
|
+
existing.session_id = sessionId;
|
|
535
|
+
if (chatId) existing.chat_id = chatId;
|
|
536
|
+
saveState(profile, existing);
|
|
537
|
+
memoryCache.set(profile, { sessionId, chatId: chatId ?? existing.chat_id ?? "" });
|
|
538
|
+
logger.dim(`session saved: ${sessionId.slice(0, 8)}`);
|
|
539
|
+
}
|
|
540
|
+
function clearSession(profile) {
|
|
541
|
+
deleteState(profile);
|
|
542
|
+
memoryCache.delete(profile);
|
|
543
|
+
}
|
|
544
|
+
var memoryCache;
|
|
545
|
+
var init_manager = __esm({
|
|
546
|
+
"src/session/manager.ts"() {
|
|
547
|
+
init_vault();
|
|
548
|
+
init_logger();
|
|
549
|
+
memoryCache = /* @__PURE__ */ new Map();
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
// src/session/index.ts
|
|
554
|
+
var session_exports = {};
|
|
555
|
+
__export(session_exports, {
|
|
556
|
+
clearSession: () => clearSession,
|
|
557
|
+
deleteState: () => deleteState,
|
|
558
|
+
getSession: () => getSession,
|
|
559
|
+
hasState: () => hasState,
|
|
560
|
+
loadState: () => loadState,
|
|
561
|
+
saveState: () => saveState,
|
|
562
|
+
setSession: () => setSession
|
|
563
|
+
});
|
|
564
|
+
var init_session = __esm({
|
|
565
|
+
"src/session/index.ts"() {
|
|
566
|
+
init_manager();
|
|
567
|
+
init_vault();
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
// src/claude/events.ts
|
|
572
|
+
function createEventState() {
|
|
573
|
+
return {
|
|
574
|
+
thinkingBuffer: [],
|
|
575
|
+
thinkingLength: 0,
|
|
576
|
+
textBuffer: "",
|
|
577
|
+
toolCallCount: 0,
|
|
578
|
+
currentToolName: "",
|
|
579
|
+
sessionId: "",
|
|
580
|
+
error: null,
|
|
581
|
+
isComplete: false
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function processEvent(state, event) {
|
|
585
|
+
if (event.type === "assistant" && "message" in event) {
|
|
586
|
+
const msg = event.message;
|
|
587
|
+
if (msg.content) {
|
|
588
|
+
for (const block of Array.isArray(msg.content) ? msg.content : [msg.content]) {
|
|
589
|
+
if (block.type === "text") {
|
|
590
|
+
state.textBuffer += block.text ?? "";
|
|
591
|
+
} else if (block.type === "tool_use") {
|
|
592
|
+
state.currentToolName = block.name ?? "";
|
|
593
|
+
state.toolCallCount++;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (event.type === "assistant" && "thinking" in event) {
|
|
599
|
+
const thinking = event.thinking;
|
|
600
|
+
if (thinking?.text) {
|
|
601
|
+
state.thinkingBuffer.push(thinking.text);
|
|
602
|
+
state.thinkingLength += thinking.text.length;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (event.type === "result") {
|
|
606
|
+
const result = event;
|
|
607
|
+
state.sessionId = result.session_id ?? "";
|
|
608
|
+
state.error = result.error ?? null;
|
|
609
|
+
state.isComplete = true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
var init_events = __esm({
|
|
613
|
+
"src/claude/events.ts"() {
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
async function runAgent(input, onEvent) {
|
|
617
|
+
const state = createEventState();
|
|
618
|
+
const q = query({
|
|
619
|
+
prompt: input.prompt,
|
|
620
|
+
options: {
|
|
621
|
+
permissionMode: input.permissionMode ?? input.config.claude.permission_mode ?? "acceptEdits",
|
|
622
|
+
allowedTools: input.config.claude.allowed_tools ?? [],
|
|
623
|
+
cwd: input.cwd,
|
|
624
|
+
model: "claude-sonnet-4-6",
|
|
625
|
+
...input.resume ? { resume: input.resume } : {}
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
logger.dim(`Claude \u67E5\u8BE2, prompt length: ${input.prompt.length}`);
|
|
629
|
+
for await (const event of q) {
|
|
630
|
+
processEvent(state, event);
|
|
631
|
+
onEvent?.(state, event);
|
|
632
|
+
}
|
|
633
|
+
logger.dim(`Claude \u5B8C\u6210, session: ${state.sessionId.slice(0, 8)}, tools: ${state.toolCallCount}`);
|
|
634
|
+
return {
|
|
635
|
+
text: state.textBuffer,
|
|
636
|
+
sessionId: state.sessionId,
|
|
637
|
+
thinkingLength: state.thinkingLength,
|
|
638
|
+
toolCallCount: state.toolCallCount,
|
|
639
|
+
error: state.error
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
var init_agent = __esm({
|
|
643
|
+
"src/claude/agent.ts"() {
|
|
644
|
+
init_logger();
|
|
645
|
+
init_events();
|
|
646
|
+
}
|
|
647
|
+
});
|
|
648
|
+
|
|
649
|
+
// src/claude/context.ts
|
|
650
|
+
var init_context = __esm({
|
|
651
|
+
"src/claude/context.ts"() {
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
// src/claude/index.ts
|
|
656
|
+
var init_claude = __esm({
|
|
657
|
+
"src/claude/index.ts"() {
|
|
658
|
+
init_agent();
|
|
659
|
+
init_events();
|
|
660
|
+
init_context();
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
|
|
664
|
+
// src/command/parser.ts
|
|
665
|
+
function parseTemplate(template, variables) {
|
|
666
|
+
return template.replace(/\{(\w+)\}/g, (_, key) => variables[key] ?? `{${key}}`);
|
|
667
|
+
}
|
|
668
|
+
function isCommand(text) {
|
|
669
|
+
return text.startsWith("/");
|
|
670
|
+
}
|
|
671
|
+
function parseCommand(text) {
|
|
672
|
+
const spaceIdx = text.indexOf(" ");
|
|
673
|
+
if (spaceIdx === -1) return { cmd: text.slice(1), args: "" };
|
|
674
|
+
return { cmd: text.slice(1, spaceIdx), args: text.slice(spaceIdx + 1) };
|
|
675
|
+
}
|
|
676
|
+
var init_parser = __esm({
|
|
677
|
+
"src/command/parser.ts"() {
|
|
678
|
+
}
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// src/command/builtins.ts
|
|
682
|
+
function getHelpText() {
|
|
683
|
+
const lines = ["**\u53EF\u7528\u547D\u4EE4:**"];
|
|
684
|
+
for (const [cmd, desc] of Object.entries(BUILTIN_COMMANDS)) {
|
|
685
|
+
lines.push(`- **/${cmd}** \u2014 ${desc}`);
|
|
686
|
+
}
|
|
687
|
+
return lines.join("\n");
|
|
688
|
+
}
|
|
689
|
+
var BUILTIN_COMMANDS;
|
|
690
|
+
var init_builtins = __esm({
|
|
691
|
+
"src/command/builtins.ts"() {
|
|
692
|
+
BUILTIN_COMMANDS = {
|
|
693
|
+
help: "\u663E\u793A\u53EF\u7528\u547D\u4EE4\u5217\u8868",
|
|
694
|
+
stop: "\u505C\u6B62\u5F53\u524D\u5904\u7406",
|
|
695
|
+
new: "\u5F00\u59CB\u65B0\u4F1A\u8BDD",
|
|
696
|
+
status: "\u67E5\u770B\u5F53\u524D\u4F1A\u8BDD\u72B6\u6001",
|
|
697
|
+
model: "\u67E5\u770B\u5F53\u524D\u4F7F\u7528\u7684\u6A21\u578B"
|
|
698
|
+
};
|
|
699
|
+
}
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
// src/command/index.ts
|
|
703
|
+
var command_exports = {};
|
|
704
|
+
__export(command_exports, {
|
|
705
|
+
BUILTIN_COMMANDS: () => BUILTIN_COMMANDS,
|
|
706
|
+
getHelpText: () => getHelpText,
|
|
707
|
+
isCommand: () => isCommand,
|
|
708
|
+
parseCommand: () => parseCommand,
|
|
709
|
+
parseTemplate: () => parseTemplate
|
|
710
|
+
});
|
|
711
|
+
var init_command = __esm({
|
|
712
|
+
"src/command/index.ts"() {
|
|
713
|
+
init_parser();
|
|
714
|
+
init_builtins();
|
|
715
|
+
}
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
// src/core/handler.ts
|
|
719
|
+
var handler_exports = {};
|
|
720
|
+
__export(handler_exports, {
|
|
721
|
+
createMessageHandler: () => createMessageHandler
|
|
722
|
+
});
|
|
723
|
+
function isDuplicate(messageId) {
|
|
724
|
+
const now = Date.now();
|
|
725
|
+
const last = dedupMap.get(messageId);
|
|
726
|
+
if (last && now - last < 3e4) return true;
|
|
727
|
+
dedupMap.set(messageId, now);
|
|
728
|
+
if (dedupMap.size > 500) {
|
|
729
|
+
for (const [key, ts2] of dedupMap) {
|
|
730
|
+
if (now - ts2 > 6e4) dedupMap.delete(key);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
return false;
|
|
734
|
+
}
|
|
735
|
+
function createMessageHandler(config, adapter) {
|
|
736
|
+
let processing = false;
|
|
737
|
+
return async (msg) => {
|
|
738
|
+
if (isDuplicate(msg.id)) return;
|
|
739
|
+
logger.msgReceived(msg.id, msg.chatType);
|
|
740
|
+
if (processing) {
|
|
741
|
+
await adapter.replyText(msg.id, "\u6B63\u5728\u5904\u7406\u4E0A\u4E00\u6761\u6D88\u606F\uFF0C\u8BF7\u7A0D\u5019...");
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
processing = true;
|
|
745
|
+
try {
|
|
746
|
+
await adapter.setReaction(msg.id, "processing");
|
|
747
|
+
if (msg.type === "text" && msg.text) {
|
|
748
|
+
await handleText(config, adapter, msg);
|
|
749
|
+
} else if (msg.type === "image") {
|
|
750
|
+
await adapter.replyText(msg.id, "\u56FE\u7247\u5206\u6790\u529F\u80FD\u5C06\u5728\u540E\u7EED\u7248\u672C\u4E2D\u652F\u6301");
|
|
751
|
+
} else if (msg.type === "file") {
|
|
752
|
+
await adapter.replyText(msg.id, "\u6587\u4EF6\u5206\u6790\u529F\u80FD\u5C06\u5728\u540E\u7EED\u7248\u672C\u4E2D\u652F\u6301");
|
|
753
|
+
} else {
|
|
754
|
+
await adapter.replyText(msg.id, `\u6682\u4E0D\u652F\u6301: ${msg.type}`);
|
|
755
|
+
}
|
|
756
|
+
await adapter.setReaction(msg.id, "done");
|
|
757
|
+
} catch (e) {
|
|
758
|
+
logger.error(`\u5904\u7406\u5931\u8D25: ${e.message.slice(0, 200)}`);
|
|
759
|
+
await adapter.setReaction(msg.id, "error");
|
|
760
|
+
await adapter.replyText(msg.id, "\u5904\u7406\u5931\u8D25\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
|
|
761
|
+
} finally {
|
|
762
|
+
processing = false;
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
async function handleText(config, adapter, msg) {
|
|
767
|
+
const text = msg.text ?? "";
|
|
768
|
+
if (isCommand(text)) {
|
|
769
|
+
const { cmd, args } = parseCommand(text);
|
|
770
|
+
switch (cmd) {
|
|
771
|
+
case "help":
|
|
772
|
+
await adapter.replyCard(msg.id, { title: config.card_title, content: getHelpText() });
|
|
773
|
+
return;
|
|
774
|
+
case "new":
|
|
775
|
+
clearSession(config.profile);
|
|
776
|
+
await adapter.replyText(msg.id, "\u2705 \u5DF2\u5F00\u59CB\u65B0\u4F1A\u8BDD");
|
|
777
|
+
return;
|
|
778
|
+
case "stop":
|
|
779
|
+
await adapter.replyText(msg.id, "\u23F9 \u5DF2\u505C\u6B62");
|
|
780
|
+
return;
|
|
781
|
+
case "status": {
|
|
782
|
+
const sess = getSession(config.profile);
|
|
783
|
+
await adapter.replyText(msg.id, sess ? `\u4F1A\u8BDD: ${sess.sessionId.slice(0, 8)}` : "\u65E0\u6D3B\u8DC3\u4F1A\u8BDD");
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
default: {
|
|
787
|
+
const customCmd = config.commands[cmd];
|
|
788
|
+
if (customCmd) {
|
|
789
|
+
const { parseTemplate: parseTemplate2 } = await Promise.resolve().then(() => (init_command(), command_exports));
|
|
790
|
+
return await runClaudeReply(config, adapter, msg, parseTemplate2(customCmd, { input: args }));
|
|
791
|
+
}
|
|
792
|
+
await adapter.replyText(msg.id, `\u672A\u77E5\u547D\u4EE4: /${cmd}\u3002\u8F93\u5165 /help \u67E5\u770B`);
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
await runClaudeReply(config, adapter, msg, text);
|
|
798
|
+
}
|
|
799
|
+
async function runClaudeReply(config, adapter, msg, prompt2) {
|
|
800
|
+
const t0 = Date.now();
|
|
801
|
+
const sess = getSession(config.profile);
|
|
802
|
+
const ctrl = adapter.createStreamController(config.card_title, config.streaming.flush_interval_ms);
|
|
803
|
+
const replyId = await ctrl.init(msg.id, msg.chatId);
|
|
804
|
+
if (!replyId) {
|
|
805
|
+
await adapter.replyText(msg.id, "\u53D1\u9001\u5931\u8D25");
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const result = await runAgent(
|
|
809
|
+
{ prompt: prompt2, config, resume: sess?.sessionId, cwd: process.cwd() },
|
|
810
|
+
(state) => ctrl.append(state.textBuffer || "\u601D\u8003\u4E2D...")
|
|
811
|
+
);
|
|
812
|
+
await ctrl.complete(result.text || "(\u7A7A\u56DE\u590D)", {
|
|
813
|
+
elapsed: Date.now() - t0,
|
|
814
|
+
tools: result.toolCallCount,
|
|
815
|
+
thinking: result.thinkingLength
|
|
816
|
+
});
|
|
817
|
+
if (result.sessionId) setSession(config.profile, result.sessionId, msg.chatId);
|
|
818
|
+
}
|
|
819
|
+
var dedupMap;
|
|
820
|
+
var init_handler = __esm({
|
|
821
|
+
"src/core/handler.ts"() {
|
|
822
|
+
init_logger();
|
|
823
|
+
init_claude();
|
|
824
|
+
init_session();
|
|
825
|
+
init_command();
|
|
826
|
+
dedupMap = /* @__PURE__ */ new Map();
|
|
827
|
+
}
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
// src/core/lifecycle.ts
|
|
831
|
+
var lifecycle_exports = {};
|
|
832
|
+
__export(lifecycle_exports, {
|
|
833
|
+
startApp: () => startApp
|
|
834
|
+
});
|
|
835
|
+
async function startApp(config, opts) {
|
|
836
|
+
acquireLock(config.profile, process.cwd());
|
|
837
|
+
const adapter = createAdapter(config);
|
|
838
|
+
const { createMessageHandler: createMessageHandler2 } = await Promise.resolve().then(() => (init_handler(), handler_exports));
|
|
839
|
+
const handleMessage = createMessageHandler2(config, adapter);
|
|
840
|
+
adapter.onMessage(handleMessage);
|
|
841
|
+
await adapter.connect();
|
|
842
|
+
const { getSession: getSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
843
|
+
const sess = getSession2(config.profile);
|
|
844
|
+
if (sess?.chatId) {
|
|
845
|
+
try {
|
|
846
|
+
await adapter.sendCard(sess.chatId, {
|
|
847
|
+
title: config.card_title,
|
|
848
|
+
content: `\u5DF2\u4E0A\u7EBF
|
|
849
|
+
\u5DE5\u4F5C\u76EE\u5F55: \`${process.cwd()}\``,
|
|
850
|
+
status: "success"
|
|
851
|
+
});
|
|
852
|
+
logger.dim("\u5DF2\u53D1\u9001\u4E0A\u7EBF\u901A\u77E5");
|
|
853
|
+
} catch (e) {
|
|
854
|
+
logger.warn(`\u4E0A\u7EBF\u901A\u77E5\u53D1\u9001\u5931\u8D25: ${e.message.slice(0, 80)}`);
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
logger.dim("\u65E0\u5386\u53F2\u4F1A\u8BDD\uFF0C\u8DF3\u8FC7\u4E0A\u7EBF\u901A\u77E5");
|
|
858
|
+
}
|
|
859
|
+
logger.success(`${config.card_title} [${config.profile}] \u5DF2\u542F\u52A8 (platform: ${config.platform})`);
|
|
860
|
+
if (opts.daemon) {
|
|
861
|
+
logger.dim(`\u5B88\u62A4\u8FDB\u7A0B\u6A21\u5F0F\uFF0CPID: ${process.pid}`);
|
|
862
|
+
process.stdin.destroy();
|
|
863
|
+
}
|
|
864
|
+
let shuttingDown = false;
|
|
865
|
+
const cleanup = async () => {
|
|
866
|
+
if (shuttingDown) return;
|
|
867
|
+
shuttingDown = true;
|
|
868
|
+
logger.info("\u6B63\u5728\u5173\u95ED...");
|
|
869
|
+
const cur = getSession2(config.profile);
|
|
870
|
+
if (cur?.chatId) {
|
|
871
|
+
try {
|
|
872
|
+
await adapter.sendCard(cur.chatId, {
|
|
873
|
+
title: config.card_title,
|
|
874
|
+
content: "\u5DF2\u79BB\u7EBF",
|
|
875
|
+
status: "neutral"
|
|
876
|
+
});
|
|
877
|
+
} catch {
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
try {
|
|
881
|
+
await adapter.disconnect();
|
|
882
|
+
} catch {
|
|
883
|
+
}
|
|
884
|
+
releaseLock(config.profile);
|
|
885
|
+
logger.info(`${config.profile} \u5DF2\u505C\u6B62`);
|
|
886
|
+
process.exit(0);
|
|
887
|
+
};
|
|
888
|
+
process.on("SIGINT", cleanup);
|
|
889
|
+
process.on("SIGTERM", cleanup);
|
|
890
|
+
}
|
|
891
|
+
var init_lifecycle = __esm({
|
|
892
|
+
"src/core/lifecycle.ts"() {
|
|
893
|
+
init_registry();
|
|
894
|
+
init_lock();
|
|
895
|
+
init_logger();
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
|
|
899
|
+
// src/adapters/feishu/index.ts
|
|
900
|
+
init_registry();
|
|
901
|
+
|
|
902
|
+
// src/adapters/feishu/types.ts
|
|
903
|
+
var DEFAULT_REACTION = {
|
|
904
|
+
/** 处理中:Typing(正在输入) */
|
|
905
|
+
processing: "Typing",
|
|
906
|
+
/** 完成:DONE(完成勾) */
|
|
907
|
+
done: "DONE",
|
|
908
|
+
/** 错误:OnIt(处理中带感叹号) */
|
|
909
|
+
error: "OnIt",
|
|
910
|
+
/** 超时:Clock(时钟) */
|
|
911
|
+
timeout: "Clock"
|
|
912
|
+
};
|
|
913
|
+
function createLarkClient(config) {
|
|
914
|
+
return new Lark.Client({
|
|
915
|
+
appId: config.platform_config.app_id,
|
|
916
|
+
appSecret: config.platform_secret
|
|
917
|
+
});
|
|
918
|
+
}
|
|
919
|
+
function createWSClient(config) {
|
|
920
|
+
return new Lark.WSClient({
|
|
921
|
+
appId: config.platform_config.app_id,
|
|
922
|
+
appSecret: config.platform_secret,
|
|
923
|
+
loggerLevel: Lark.LoggerLevel.info
|
|
924
|
+
// 不传 appSecret 到 logger,SDK 内部会处理
|
|
925
|
+
});
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// src/adapters/feishu/message.ts
|
|
929
|
+
async function sendText(client, chatId, text) {
|
|
930
|
+
await client.im.message.create({
|
|
931
|
+
params: { receive_id_type: "chat_id" },
|
|
932
|
+
data: {
|
|
933
|
+
receive_id: chatId,
|
|
934
|
+
content: JSON.stringify({ text }),
|
|
935
|
+
msg_type: "text"
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
}
|
|
939
|
+
async function replyText(client, messageId, text) {
|
|
940
|
+
await client.im.message.reply({
|
|
941
|
+
path: { message_id: messageId },
|
|
942
|
+
data: {
|
|
943
|
+
content: JSON.stringify({ text }),
|
|
944
|
+
msg_type: "text"
|
|
945
|
+
}
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
async function sendCard(client, chatId, card) {
|
|
949
|
+
const res = await client.im.message.create({
|
|
950
|
+
params: { receive_id_type: "chat_id" },
|
|
951
|
+
data: {
|
|
952
|
+
receive_id: chatId,
|
|
953
|
+
content: JSON.stringify(card),
|
|
954
|
+
msg_type: "interactive"
|
|
955
|
+
}
|
|
956
|
+
});
|
|
957
|
+
return res?.data?.message_id ?? "";
|
|
958
|
+
}
|
|
959
|
+
async function replyCard(client, messageId, card) {
|
|
960
|
+
const res = await client.im.message.reply({
|
|
961
|
+
path: { message_id: messageId },
|
|
962
|
+
data: {
|
|
963
|
+
content: JSON.stringify(card),
|
|
964
|
+
msg_type: "interactive"
|
|
965
|
+
}
|
|
966
|
+
});
|
|
967
|
+
return res?.data?.message_id ?? "";
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// src/adapters/feishu/reaction.ts
|
|
971
|
+
init_logger();
|
|
972
|
+
async function setReaction(client, messageId, reactionType) {
|
|
973
|
+
try {
|
|
974
|
+
await client.im.messageReaction.create({
|
|
975
|
+
path: { message_id: messageId },
|
|
976
|
+
data: { reaction_type: { emoji_type: reactionType } }
|
|
977
|
+
});
|
|
978
|
+
} catch (e) {
|
|
979
|
+
logger.dim(`reaction failed: ${e.message.slice(0, 50)}`);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
// src/adapters/feishu/card.ts
|
|
984
|
+
function buildMarkdownCard(title, content) {
|
|
985
|
+
return {
|
|
986
|
+
config: { wide_screen_mode: true },
|
|
987
|
+
header: {
|
|
988
|
+
template: "blue",
|
|
989
|
+
title: { content: title, tag: "plain_text" }
|
|
990
|
+
},
|
|
991
|
+
elements: [
|
|
992
|
+
{ tag: "markdown", content: content || "(\u7A7A\u56DE\u590D)" }
|
|
993
|
+
]
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
function buildStatusCard(title, status, detail) {
|
|
997
|
+
const statusIcon = {
|
|
998
|
+
online: "\u{1F7E2}",
|
|
999
|
+
offline: "\u26AB",
|
|
1000
|
+
error: "\u{1F534}"
|
|
1001
|
+
};
|
|
1002
|
+
return {
|
|
1003
|
+
config: { wide_screen_mode: true },
|
|
1004
|
+
header: {
|
|
1005
|
+
template: status === "error" ? "red" : status === "online" ? "green" : "grey",
|
|
1006
|
+
title: { content: `${statusIcon[status]} ${title}`, tag: "plain_text" }
|
|
1007
|
+
},
|
|
1008
|
+
elements: [
|
|
1009
|
+
{ tag: "markdown", content: detail }
|
|
1010
|
+
]
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// src/core/flush.ts
|
|
1015
|
+
var FlushController = class {
|
|
1016
|
+
buffer = "";
|
|
1017
|
+
lastFlush = 0;
|
|
1018
|
+
timer = null;
|
|
1019
|
+
flushing = false;
|
|
1020
|
+
minInterval;
|
|
1021
|
+
heartbeatInterval;
|
|
1022
|
+
lastHeartbeat;
|
|
1023
|
+
onFlush;
|
|
1024
|
+
/**
|
|
1025
|
+
* 创建流式缓冲控制器
|
|
1026
|
+
*
|
|
1027
|
+
* @param onFlush - 实际执行刷新的回调函数
|
|
1028
|
+
* @param minInterval - 最小刷新间隔(毫秒,默认 200ms)
|
|
1029
|
+
* @param heartbeatInterval - 心跳保活间隔(毫秒,默认 15s),
|
|
1030
|
+
* 超过此间隔未刷新时即使内容较少也会强制刷新
|
|
1031
|
+
*/
|
|
1032
|
+
constructor(onFlush, minInterval = 200, heartbeatInterval = 15e3) {
|
|
1033
|
+
this.onFlush = onFlush;
|
|
1034
|
+
this.minInterval = minInterval;
|
|
1035
|
+
this.heartbeatInterval = heartbeatInterval;
|
|
1036
|
+
this.lastHeartbeat = Date.now();
|
|
1037
|
+
this.lastFlush = 0;
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* 追加文本到缓冲区
|
|
1041
|
+
*
|
|
1042
|
+
* 将文本累加到内部缓冲区,并调度一次刷新检查。
|
|
1043
|
+
*
|
|
1044
|
+
* @param text - 追加的文本内容
|
|
1045
|
+
*/
|
|
1046
|
+
append(text) {
|
|
1047
|
+
this.buffer += text;
|
|
1048
|
+
this.scheduleFlush();
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* 立即刷新所有缓冲内容
|
|
1052
|
+
*
|
|
1053
|
+
* 取消等待中的定时器,强制立即执行一次刷新。
|
|
1054
|
+
* 通常在流式输出结束时调用。
|
|
1055
|
+
*/
|
|
1056
|
+
async flushAll() {
|
|
1057
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1058
|
+
await this.doFlush();
|
|
1059
|
+
}
|
|
1060
|
+
/**
|
|
1061
|
+
* 调度刷新检查
|
|
1062
|
+
*
|
|
1063
|
+
* 根据距上次刷新的时间计算合适的延迟,确保不超过最小刷新间隔。
|
|
1064
|
+
*/
|
|
1065
|
+
scheduleFlush() {
|
|
1066
|
+
if (this.timer) clearTimeout(this.timer);
|
|
1067
|
+
const elapsed = Date.now() - this.lastFlush;
|
|
1068
|
+
const delay = Math.max(0, this.minInterval - elapsed);
|
|
1069
|
+
this.timer = setTimeout(() => this.checkAndFlush(), delay);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* 检查条件并执行刷新
|
|
1073
|
+
*
|
|
1074
|
+
* 同时满足以下条件时执行刷新:
|
|
1075
|
+
* 1. 缓冲区有内容
|
|
1076
|
+
* 2. 距上次刷新超过最小间隔,或距上次心跳超过心跳间隔
|
|
1077
|
+
* 否则重新调度等待。
|
|
1078
|
+
*/
|
|
1079
|
+
async checkAndFlush() {
|
|
1080
|
+
const now = Date.now();
|
|
1081
|
+
const sinceLast = now - this.lastFlush;
|
|
1082
|
+
if (this.buffer && (sinceLast >= this.minInterval || now - this.lastHeartbeat >= this.heartbeatInterval)) {
|
|
1083
|
+
await this.doFlush();
|
|
1084
|
+
this.lastHeartbeat = now;
|
|
1085
|
+
} else if (this.buffer) {
|
|
1086
|
+
this.scheduleFlush();
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* 执行实际的刷新操作
|
|
1091
|
+
*
|
|
1092
|
+
* 将当前缓冲区内容发送给 onFlush 回调。
|
|
1093
|
+
* 如果刷新失败,将内容放回缓冲区头部等待下次重试。
|
|
1094
|
+
*/
|
|
1095
|
+
async doFlush() {
|
|
1096
|
+
if (this.flushing || !this.buffer) return;
|
|
1097
|
+
this.flushing = true;
|
|
1098
|
+
const text = this.buffer;
|
|
1099
|
+
this.buffer = "";
|
|
1100
|
+
try {
|
|
1101
|
+
await this.onFlush(text);
|
|
1102
|
+
this.lastFlush = Date.now();
|
|
1103
|
+
} catch {
|
|
1104
|
+
this.buffer = text + this.buffer;
|
|
1105
|
+
} finally {
|
|
1106
|
+
this.flushing = false;
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1110
|
+
|
|
1111
|
+
// src/adapters/feishu/stream.ts
|
|
1112
|
+
var CardKitStreamController = class {
|
|
1113
|
+
/** 当前控制器状态 */
|
|
1114
|
+
state = "idle";
|
|
1115
|
+
/** 已发送卡片的飞书消息 ID */
|
|
1116
|
+
_messageId = "";
|
|
1117
|
+
/** 飞书 REST API 客户端 */
|
|
1118
|
+
client;
|
|
1119
|
+
/** 流式卡片的标题 */
|
|
1120
|
+
cardTitle;
|
|
1121
|
+
/** 定时刷新控制器,按间隔批量提交累积文本 */
|
|
1122
|
+
flushCtrl;
|
|
1123
|
+
/** 已累积的显示缓冲区内容 */
|
|
1124
|
+
displayBuffer = "";
|
|
1125
|
+
/**
|
|
1126
|
+
* 创建 CardKit 流式卡片控制器。
|
|
1127
|
+
*
|
|
1128
|
+
* @param client - 飞书 REST API 客户端
|
|
1129
|
+
* @param cardTitle - 卡片标题(显示在 header 中)
|
|
1130
|
+
* @param flushIntervalMs - 刷新间隔(毫秒),默认 200ms,控制文本更新的频率
|
|
1131
|
+
*/
|
|
1132
|
+
constructor(client, cardTitle, flushIntervalMs = 200) {
|
|
1133
|
+
this.client = client;
|
|
1134
|
+
this.cardTitle = cardTitle;
|
|
1135
|
+
this.flushCtrl = new FlushController(async (text) => {
|
|
1136
|
+
await this.performFlush(text);
|
|
1137
|
+
}, flushIntervalMs);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* 获取当前卡片消息的 ID。
|
|
1141
|
+
* 在 init() 成功调用后才有值。
|
|
1142
|
+
*/
|
|
1143
|
+
get messageId() {
|
|
1144
|
+
return this._messageId;
|
|
1145
|
+
}
|
|
1146
|
+
/**
|
|
1147
|
+
* 初始化流式卡片。
|
|
1148
|
+
*
|
|
1149
|
+
* 以回复消息的形式向飞书会话发送一张初始卡片(蓝色 header,显示"思考中..."),
|
|
1150
|
+
* 记录返回的 message_id 用于后续更新。调用后将状态切换为 streaming。
|
|
1151
|
+
*
|
|
1152
|
+
* @param replyTo - 被回复消息的 ID
|
|
1153
|
+
* @param chatId - 飞书会话 ID(当前未使用,保留接口兼容)
|
|
1154
|
+
* @returns 创建的卡片消息 ID,失败返回 null
|
|
1155
|
+
*/
|
|
1156
|
+
async init(replyTo, chatId) {
|
|
1157
|
+
const res = await this.client.im.message.reply({
|
|
1158
|
+
path: { message_id: replyTo },
|
|
1159
|
+
data: {
|
|
1160
|
+
msg_type: "interactive",
|
|
1161
|
+
content: JSON.stringify({
|
|
1162
|
+
config: { wide_screen_mode: true },
|
|
1163
|
+
header: {
|
|
1164
|
+
template: "blue",
|
|
1165
|
+
title: { content: `${this.cardTitle} \u2014 \u601D\u8003\u4E2D...`, tag: "plain_text" }
|
|
1166
|
+
},
|
|
1167
|
+
elements: [{ tag: "markdown", content: "..." }]
|
|
1168
|
+
})
|
|
1169
|
+
}
|
|
1170
|
+
});
|
|
1171
|
+
this._messageId = res?.data?.message_id ?? "";
|
|
1172
|
+
this.state = "streaming";
|
|
1173
|
+
return this._messageId || null;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* 追加文本到流式输出缓冲区。
|
|
1177
|
+
*
|
|
1178
|
+
* 文本不会立即发送,而是先进入 FlushController 的缓冲区,
|
|
1179
|
+
* 等待下一个刷新周期统一提交。仅在 streaming 状态下生效。
|
|
1180
|
+
*
|
|
1181
|
+
* @param text - 要追加的文本片段
|
|
1182
|
+
*/
|
|
1183
|
+
append(text) {
|
|
1184
|
+
if (this.state !== "streaming") return;
|
|
1185
|
+
this.flushCtrl.append(text);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* 完成流式输出。
|
|
1189
|
+
*
|
|
1190
|
+
* 先执行最后一次刷新确保所有缓冲文本已发送,然后将状态切换为 completed,
|
|
1191
|
+
* 最后更新卡片为完成态:header 变为绿色,显示"完成"标识和可选的统计信息。
|
|
1192
|
+
*
|
|
1193
|
+
* @param content - 最终完整的回复内容
|
|
1194
|
+
* @param stats - 可选的统计信息(耗时、工具调用次数、思考字符数)
|
|
1195
|
+
*/
|
|
1196
|
+
async complete(content, stats) {
|
|
1197
|
+
await this.flushCtrl.flushAll();
|
|
1198
|
+
this.state = "completed";
|
|
1199
|
+
const statsText = stats ? `
|
|
1200
|
+
|
|
1201
|
+
---
|
|
1202
|
+
\u23F1 ${(stats.elapsed / 1e3).toFixed(1)}s \xB7 \u{1F527} ${stats.tools} tools \xB7 \u{1F4AD} ${stats.thinking} chars` : "";
|
|
1203
|
+
try {
|
|
1204
|
+
await this.client.im.message.patch({
|
|
1205
|
+
path: { message_id: this._messageId },
|
|
1206
|
+
data: {
|
|
1207
|
+
content: JSON.stringify({
|
|
1208
|
+
config: { wide_screen_mode: true },
|
|
1209
|
+
header: { template: "green", title: { content: `${this.cardTitle} \u2014 \u5B8C\u6210`, tag: "plain_text" } },
|
|
1210
|
+
elements: [{ tag: "markdown", content: content + statsText }]
|
|
1211
|
+
})
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
} catch {
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* 中止流式输出。
|
|
1219
|
+
*
|
|
1220
|
+
* 将状态切换为 aborted,立即刷出缓冲区,然后更新卡片为错误态:
|
|
1221
|
+
* header 变为红色,显示"错误"标识和错误详情。
|
|
1222
|
+
*
|
|
1223
|
+
* @param error - 错误描述文本
|
|
1224
|
+
*/
|
|
1225
|
+
async abort(error) {
|
|
1226
|
+
this.state = "aborted";
|
|
1227
|
+
await this.flushCtrl.flushAll();
|
|
1228
|
+
try {
|
|
1229
|
+
await this.client.im.message.patch({
|
|
1230
|
+
path: { message_id: this._messageId },
|
|
1231
|
+
data: {
|
|
1232
|
+
content: JSON.stringify({
|
|
1233
|
+
header: { template: "red", title: { content: `${this.cardTitle} \u2014 \u9519\u8BEF`, tag: "plain_text" } },
|
|
1234
|
+
elements: [{ tag: "markdown", content: error }]
|
|
1235
|
+
})
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
} catch {
|
|
1239
|
+
}
|
|
1240
|
+
}
|
|
1241
|
+
/**
|
|
1242
|
+
* 执行卡片内容的实际刷新操作。
|
|
1243
|
+
*
|
|
1244
|
+
* 将累积的显示缓冲区内容通过飞书 message.patch API 更新到已发送的卡片上。
|
|
1245
|
+
* 仅在 streaming 状态下且有有效的 messageId 时执行。
|
|
1246
|
+
* 每次 patch 都会累加 displayBuffer 中的内容。
|
|
1247
|
+
*
|
|
1248
|
+
* @param text - 本次刷新要追加的文本(实际使用 displayBuffer 进行全量更新)
|
|
1249
|
+
*/
|
|
1250
|
+
async performFlush(text) {
|
|
1251
|
+
if (!this._messageId || this.state !== "streaming") return;
|
|
1252
|
+
this.displayBuffer += text;
|
|
1253
|
+
try {
|
|
1254
|
+
await this.client.im.message.patch({
|
|
1255
|
+
path: { message_id: this._messageId },
|
|
1256
|
+
data: {
|
|
1257
|
+
content: JSON.stringify({
|
|
1258
|
+
config: { wide_screen_mode: true },
|
|
1259
|
+
header: {
|
|
1260
|
+
template: "blue",
|
|
1261
|
+
title: { content: `${this.cardTitle} \u2014 \u56DE\u590D\u4E2D...`, tag: "plain_text" }
|
|
1262
|
+
},
|
|
1263
|
+
elements: [{ tag: "markdown", content: this.displayBuffer }]
|
|
1264
|
+
})
|
|
1265
|
+
}
|
|
1266
|
+
});
|
|
1267
|
+
} catch {
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1272
|
+
// src/adapters/feishu/file.ts
|
|
1273
|
+
init_logger();
|
|
1274
|
+
async function downloadImage(client, messageId, imageKey, tempDir) {
|
|
1275
|
+
try {
|
|
1276
|
+
const res = await client.im.messageResource.get({
|
|
1277
|
+
path: { message_id: messageId, file_key: imageKey },
|
|
1278
|
+
params: { type: "image" }
|
|
1279
|
+
});
|
|
1280
|
+
if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
|
|
1281
|
+
const filePath = resolve(tempDir, `${imageKey}.jpg`);
|
|
1282
|
+
if (res && typeof res.getReadableStream === "function") {
|
|
1283
|
+
const chunks = [];
|
|
1284
|
+
const stream = res.getReadableStream();
|
|
1285
|
+
for await (const chunk of stream) {
|
|
1286
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
1287
|
+
}
|
|
1288
|
+
const buf = Buffer.concat(chunks);
|
|
1289
|
+
writeFileSync(filePath, buf);
|
|
1290
|
+
return { path: filePath, base64: buf.toString("base64") };
|
|
1291
|
+
}
|
|
1292
|
+
return null;
|
|
1293
|
+
} catch (e) {
|
|
1294
|
+
logger.error(`\u4E0B\u8F7D\u56FE\u7247\u5931\u8D25: ${e.message.slice(0, 50)}`);
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
async function downloadFile(client, messageId, fileKey, fileName, tempDir) {
|
|
1299
|
+
try {
|
|
1300
|
+
const res = await client.im.messageResource.get({
|
|
1301
|
+
path: { message_id: messageId, file_key: fileKey },
|
|
1302
|
+
params: { type: "file" }
|
|
1303
|
+
});
|
|
1304
|
+
if (!existsSync(tempDir)) mkdirSync(tempDir, { recursive: true });
|
|
1305
|
+
const safeName = basename(fileName);
|
|
1306
|
+
const filePath = resolve(tempDir, safeName);
|
|
1307
|
+
if (res && typeof res.getReadableStream === "function") {
|
|
1308
|
+
const chunks = [];
|
|
1309
|
+
const stream = res.getReadableStream();
|
|
1310
|
+
for await (const chunk of stream) {
|
|
1311
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
1312
|
+
}
|
|
1313
|
+
const buf = Buffer.concat(chunks);
|
|
1314
|
+
writeFileSync(filePath, buf);
|
|
1315
|
+
return { path: filePath, name: safeName, size: buf.length };
|
|
1316
|
+
}
|
|
1317
|
+
return null;
|
|
1318
|
+
} catch (e) {
|
|
1319
|
+
logger.error(`\u4E0B\u8F7D\u6587\u4EF6\u5931\u8D25: ${e.message.slice(0, 50)}`);
|
|
1320
|
+
return null;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
// src/adapters/feishu/doc.ts
|
|
1325
|
+
init_logger();
|
|
1326
|
+
async function createOverflowDocument(client, title, content) {
|
|
1327
|
+
try {
|
|
1328
|
+
const docRes = await client.docx.document.create({
|
|
1329
|
+
data: { title }
|
|
1330
|
+
});
|
|
1331
|
+
const docId = docRes?.data?.document?.document_id ?? "";
|
|
1332
|
+
if (!docId) return "";
|
|
1333
|
+
try {
|
|
1334
|
+
await client.docx.documentBlock.children.create({
|
|
1335
|
+
path: { document_id: docId, block_id: docId },
|
|
1336
|
+
data: {
|
|
1337
|
+
children: [{
|
|
1338
|
+
block_type: 2,
|
|
1339
|
+
text: {
|
|
1340
|
+
elements: [{ text_run: { content, text_element_style: {} } }],
|
|
1341
|
+
style: {}
|
|
1342
|
+
}
|
|
1343
|
+
}]
|
|
1344
|
+
}
|
|
1345
|
+
});
|
|
1346
|
+
} catch {
|
|
1347
|
+
}
|
|
1348
|
+
const docUrl = `https://bytedance.feishu.cn/docx/${docId}`;
|
|
1349
|
+
logger.dim(`overflow doc: ${docUrl}`);
|
|
1350
|
+
return docUrl;
|
|
1351
|
+
} catch (e) {
|
|
1352
|
+
logger.error(`\u521B\u5EFA\u6587\u6863\u5931\u8D25: ${e.message.slice(0, 50)}`);
|
|
1353
|
+
return "";
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// src/adapters/feishu/adapter.ts
|
|
1358
|
+
function parseMessage(data) {
|
|
1359
|
+
const msg = data.message ?? {};
|
|
1360
|
+
const id = msg.message_id ?? "";
|
|
1361
|
+
const chatId = msg.chat_id ?? "";
|
|
1362
|
+
const chatType = msg.chat_type ?? "p2p";
|
|
1363
|
+
const msgType = msg.message_type ?? "";
|
|
1364
|
+
const content = msg.content ?? "";
|
|
1365
|
+
if (!id || !chatId) return null;
|
|
1366
|
+
let type = "text";
|
|
1367
|
+
let text;
|
|
1368
|
+
let imageKey;
|
|
1369
|
+
let fileKey;
|
|
1370
|
+
let fileName;
|
|
1371
|
+
if (msgType === "image") {
|
|
1372
|
+
type = "image";
|
|
1373
|
+
try {
|
|
1374
|
+
imageKey = JSON.parse(content).image_key;
|
|
1375
|
+
} catch {
|
|
1376
|
+
}
|
|
1377
|
+
} else if (msgType === "file") {
|
|
1378
|
+
type = "file";
|
|
1379
|
+
try {
|
|
1380
|
+
const parsed = JSON.parse(content);
|
|
1381
|
+
fileKey = parsed.file_key;
|
|
1382
|
+
fileName = parsed.file_name;
|
|
1383
|
+
} catch {
|
|
1384
|
+
}
|
|
1385
|
+
} else {
|
|
1386
|
+
type = "text";
|
|
1387
|
+
try {
|
|
1388
|
+
text = JSON.parse(content).text ?? "";
|
|
1389
|
+
} catch {
|
|
1390
|
+
text = content;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return { id, chatId, chatType: chatType === "group" ? "group" : "p2p", type, text, imageKey, fileKey, fileName, raw: data };
|
|
1394
|
+
}
|
|
1395
|
+
var STATUS_TEMPLATE = {
|
|
1396
|
+
success: "online",
|
|
1397
|
+
neutral: "offline",
|
|
1398
|
+
error: "error"
|
|
1399
|
+
};
|
|
1400
|
+
function statusToTemplate(status) {
|
|
1401
|
+
return STATUS_TEMPLATE[status] ?? "offline";
|
|
1402
|
+
}
|
|
1403
|
+
var FeishuAdapter = class {
|
|
1404
|
+
platform = "feishu";
|
|
1405
|
+
/** 飞书 REST API 客户端 */
|
|
1406
|
+
client;
|
|
1407
|
+
/** 飞书 WebSocket 长连接客户端 */
|
|
1408
|
+
wsClient;
|
|
1409
|
+
/** 全局解析后的配置对象 */
|
|
1410
|
+
config;
|
|
1411
|
+
/** 飞书平台专属配置 */
|
|
1412
|
+
feishu;
|
|
1413
|
+
/** 消息处理各阶段对应的表情映射表(支持用户自定义覆盖) */
|
|
1414
|
+
reactionMap;
|
|
1415
|
+
/** 外部注册的消息处理器 */
|
|
1416
|
+
msgHandler = null;
|
|
1417
|
+
/**
|
|
1418
|
+
* 创建飞书适配器实例。
|
|
1419
|
+
*
|
|
1420
|
+
* @param config - 全局解析后的配置对象,从中提取飞书平台配置和密钥
|
|
1421
|
+
*/
|
|
1422
|
+
constructor(config) {
|
|
1423
|
+
this.config = config;
|
|
1424
|
+
this.feishu = config.platform_config;
|
|
1425
|
+
this.reactionMap = { ...DEFAULT_REACTION, ...this.feishu.reaction_emojis };
|
|
1426
|
+
this.client = createLarkClient(config);
|
|
1427
|
+
this.wsClient = createWSClient(config);
|
|
1428
|
+
}
|
|
1429
|
+
/**
|
|
1430
|
+
* 建立与飞书平台的连接。
|
|
1431
|
+
*
|
|
1432
|
+
* 注册飞书 im.message.receive_v1 事件回调,通过 WebSocket 长连接
|
|
1433
|
+
* 接收实时消息推送。必须在调用前先通过 onMessage() 注册消息处理器。
|
|
1434
|
+
*
|
|
1435
|
+
* @throws 若 onMessage() 未在 connect() 之前调用则抛出错误
|
|
1436
|
+
*/
|
|
1437
|
+
async connect() {
|
|
1438
|
+
if (!this.msgHandler) throw new Error("onMessage() \u5FC5\u987B\u5728 connect() \u4E4B\u524D\u8C03\u7528");
|
|
1439
|
+
const handler = this.msgHandler;
|
|
1440
|
+
const dispatcher = new Lark.EventDispatcher({}).register({
|
|
1441
|
+
"im.message.receive_v1": async (data) => {
|
|
1442
|
+
const msg = parseMessage(data);
|
|
1443
|
+
if (msg) await handler(msg);
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
this.wsClient.start({ eventDispatcher: dispatcher });
|
|
1447
|
+
}
|
|
1448
|
+
/**
|
|
1449
|
+
* 断开与飞书平台的连接。
|
|
1450
|
+
*
|
|
1451
|
+
* 当前实现中,WebSocket 客户端会在进程退出时自动清理连接,
|
|
1452
|
+
* 因此无需显式断开。
|
|
1453
|
+
*/
|
|
1454
|
+
async disconnect() {
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* 注册消息处理器。
|
|
1458
|
+
*
|
|
1459
|
+
* 当飞书平台推送新消息时,parseMessage 解析后的 IMessage 对象
|
|
1460
|
+
* 会传递给该处理器。必须在 connect() 之前调用。
|
|
1461
|
+
*
|
|
1462
|
+
* @param handler - 异步消息处理回调函数
|
|
1463
|
+
*/
|
|
1464
|
+
onMessage(handler) {
|
|
1465
|
+
this.msgHandler = handler;
|
|
1466
|
+
}
|
|
1467
|
+
/**
|
|
1468
|
+
* 向指定会话发送纯文本消息。
|
|
1469
|
+
*
|
|
1470
|
+
* @param chatId - 飞书会话 ID(chat_id)
|
|
1471
|
+
* @param text - 要发送的文本内容
|
|
1472
|
+
* @returns 空字符串(飞书 SDK 此处不返回 message_id)
|
|
1473
|
+
*/
|
|
1474
|
+
async sendText(chatId, text) {
|
|
1475
|
+
await sendText(this.client, chatId, text);
|
|
1476
|
+
return "";
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* 回复指定消息的纯文本内容。
|
|
1480
|
+
*
|
|
1481
|
+
* @param messageId - 被回复消息的 ID
|
|
1482
|
+
* @param text - 回复文本内容
|
|
1483
|
+
* @returns 空字符串(飞书 SDK 此处不返回 message_id)
|
|
1484
|
+
*/
|
|
1485
|
+
async replyText(messageId, text) {
|
|
1486
|
+
await replyText(this.client, messageId, text);
|
|
1487
|
+
return "";
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* 向指定会话发送交互式卡片消息。
|
|
1491
|
+
*
|
|
1492
|
+
* 使用 buildMarkdownCard 将 ICard 接口转换为飞书卡片 JSON。
|
|
1493
|
+
*
|
|
1494
|
+
* @param chatId - 飞书会话 ID
|
|
1495
|
+
* @param card - 标准化的卡片数据(标题+内容)
|
|
1496
|
+
* @returns 飞书返回的消息 ID
|
|
1497
|
+
*/
|
|
1498
|
+
async sendCard(chatId, card) {
|
|
1499
|
+
const json = card.status ? buildStatusCard(card.title, statusToTemplate(card.status), card.content) : buildMarkdownCard(card.title, card.content);
|
|
1500
|
+
return await sendCard(this.client, chatId, json);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* 回复指定消息的交互式卡片。
|
|
1504
|
+
*
|
|
1505
|
+
* 使用 buildMarkdownCard 将 ICard 接口转换为飞书卡片 JSON。
|
|
1506
|
+
*
|
|
1507
|
+
* @param messageId - 被回复消息的 ID
|
|
1508
|
+
* @param card - 标准化的卡片数据(标题+内容)
|
|
1509
|
+
* @returns 飞书返回的消息 ID
|
|
1510
|
+
*/
|
|
1511
|
+
async replyCard(messageId, card) {
|
|
1512
|
+
const json = card.status ? buildStatusCard(card.title, statusToTemplate(card.status), card.content) : buildMarkdownCard(card.title, card.content);
|
|
1513
|
+
return await replyCard(this.client, messageId, json);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* 创建流式回复控制器。
|
|
1517
|
+
*
|
|
1518
|
+
* 返回 CardKitStreamController 实例,支持通过飞书卡片实现
|
|
1519
|
+
* 逐段追加内容的流式输出效果。
|
|
1520
|
+
*
|
|
1521
|
+
* @param title - 卡片标题
|
|
1522
|
+
* @param flushMs - 刷新间隔(毫秒),默认 200ms
|
|
1523
|
+
* @returns 流式卡片控制器实例
|
|
1524
|
+
*/
|
|
1525
|
+
createStreamController(title, flushMs) {
|
|
1526
|
+
return new CardKitStreamController(this.client, title, flushMs);
|
|
1527
|
+
}
|
|
1528
|
+
/**
|
|
1529
|
+
* 为指定消息设置表情反应(reaction)。
|
|
1530
|
+
*
|
|
1531
|
+
* 根据处理阶段(processing/done/error/timeout)选择对应表情。
|
|
1532
|
+
* 表情可以从 reaction_emojis 配置中自定义覆盖。
|
|
1533
|
+
*
|
|
1534
|
+
* @param msgId - 消息 ID
|
|
1535
|
+
* @param phase - 处理阶段标识
|
|
1536
|
+
*/
|
|
1537
|
+
async setReaction(msgId, phase) {
|
|
1538
|
+
const emoji = this.reactionMap[phase];
|
|
1539
|
+
if (emoji) await setReaction(this.client, msgId, emoji);
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* 下载消息中的图片到本地临时目录。
|
|
1543
|
+
*
|
|
1544
|
+
* @param msgId - 消息 ID
|
|
1545
|
+
* @param imageKey - 图片资源 key
|
|
1546
|
+
* @returns 下载结果(本地路径和 base64 编码),失败返回 null
|
|
1547
|
+
*/
|
|
1548
|
+
async downloadImage(msgId, imageKey) {
|
|
1549
|
+
const tempDir = this.config.file.temp_dir || "";
|
|
1550
|
+
return downloadImage(this.client, msgId, imageKey, tempDir);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* 下载消息中的文件到本地临时目录。
|
|
1554
|
+
*
|
|
1555
|
+
* @param msgId - 消息 ID
|
|
1556
|
+
* @param fileKey - 文件资源 key
|
|
1557
|
+
* @param fileName - 原始文件名
|
|
1558
|
+
* @returns 下载结果(本地路径、文件名、大小),失败返回 null
|
|
1559
|
+
*/
|
|
1560
|
+
async downloadFile(msgId, fileKey, fileName) {
|
|
1561
|
+
const tempDir = this.config.file.temp_dir || "";
|
|
1562
|
+
return downloadFile(this.client, msgId, fileKey, fileName, tempDir);
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* 创建飞书云文档。
|
|
1566
|
+
*
|
|
1567
|
+
* 当回复内容超长时(超出飞书消息长度限制),将内容写入飞书云文档
|
|
1568
|
+
* 并返回文档链接。
|
|
1569
|
+
*
|
|
1570
|
+
* @param title - 文档标题
|
|
1571
|
+
* @param content - 文档正文内容
|
|
1572
|
+
* @returns 飞书云文档 URL,创建失败返回空字符串
|
|
1573
|
+
*/
|
|
1574
|
+
async createDocument(title, content) {
|
|
1575
|
+
return createOverflowDocument(this.client, title, content);
|
|
1576
|
+
}
|
|
1577
|
+
};
|
|
1578
|
+
|
|
1579
|
+
// src/adapters/feishu/index.ts
|
|
1580
|
+
registerAdapter("feishu", FeishuAdapter);
|
|
1581
|
+
|
|
1582
|
+
// src/adapters/wechat/index.ts
|
|
1583
|
+
init_registry();
|
|
1584
|
+
|
|
1585
|
+
// src/adapters/wechat/adapter.ts
|
|
1586
|
+
init_logger();
|
|
1587
|
+
var WeChatAdapter = class {
|
|
1588
|
+
platform = "wechat";
|
|
1589
|
+
constructor(_config) {
|
|
1590
|
+
logger.warn("\u5FAE\u4FE1\u9002\u914D\u5668\u5C1A\u672A\u5B9E\u73B0");
|
|
1591
|
+
}
|
|
1592
|
+
/** 建立 WebSocket/长连接 */
|
|
1593
|
+
async connect() {
|
|
1594
|
+
throw new Error("\u5FAE\u4FE1\u9002\u914D\u5668\u5C1A\u672A\u5B9E\u73B0");
|
|
1595
|
+
}
|
|
1596
|
+
/** 断开连接 */
|
|
1597
|
+
async disconnect() {
|
|
1598
|
+
}
|
|
1599
|
+
/** 注册消息回调 */
|
|
1600
|
+
onMessage(_handler) {
|
|
1601
|
+
throw new Error("\u5FAE\u4FE1\u9002\u914D\u5668\u5C1A\u672A\u5B9E\u73B0");
|
|
1602
|
+
}
|
|
1603
|
+
/** 发送文本消息 */
|
|
1604
|
+
async sendText(_chatId, _text) {
|
|
1605
|
+
throw new Error("\u672A\u5B9E\u73B0");
|
|
1606
|
+
}
|
|
1607
|
+
/** 回复文本消息 */
|
|
1608
|
+
async replyText(_msgId, _text) {
|
|
1609
|
+
throw new Error("\u672A\u5B9E\u73B0");
|
|
1610
|
+
}
|
|
1611
|
+
/** 发送卡片消息 */
|
|
1612
|
+
async sendCard(_chatId, _card) {
|
|
1613
|
+
throw new Error("\u672A\u5B9E\u73B0");
|
|
1614
|
+
}
|
|
1615
|
+
/** 回复卡片消息 */
|
|
1616
|
+
async replyCard(_msgId, _card) {
|
|
1617
|
+
throw new Error("\u672A\u5B9E\u73B0");
|
|
1618
|
+
}
|
|
1619
|
+
/** 创建流式控制器 */
|
|
1620
|
+
createStreamController(_title, _flushMs) {
|
|
1621
|
+
throw new Error("\u672A\u5B9E\u73B0");
|
|
1622
|
+
}
|
|
1623
|
+
/** 设置消息反应(表情/状态) */
|
|
1624
|
+
async setReaction(_msgId, _phase) {
|
|
1625
|
+
}
|
|
1626
|
+
};
|
|
1627
|
+
|
|
1628
|
+
// src/adapters/wechat/index.ts
|
|
1629
|
+
registerAdapter("wechat", WeChatAdapter);
|
|
1630
|
+
|
|
1631
|
+
// src/index.ts
|
|
1632
|
+
var __dirname$1 = dirname(fileURLToPath(import.meta.url));
|
|
1633
|
+
var PKG = JSON.parse(readFileSync(resolve(__dirname$1, "../package.json"), "utf8"));
|
|
1634
|
+
var program = new Command();
|
|
1635
|
+
program.name("claude-im").description("\u901A\u8FC7 IM \u4E0E Claude Code \u5BF9\u8BDD\u3002WebSocket \u957F\u8FDE\u63A5\uFF0C\u6D41\u5F0F\u5361\u7247\u8F93\u51FA\u3002").version(PKG.version).option("-p, --profile <name>", "\u4F7F\u7528\u6307\u5B9A profile\uFF08\u9ED8\u8BA4 default\uFF09", "default").option("-c, --continue", "\u7EE7\u7EED\u4E0A\u6B21\u672A\u5B8C\u6210\u7684\u4F1A\u8BDD").option("-d, --daemon", "\u4EE5\u540E\u53F0\u5B88\u62A4\u8FDB\u7A0B\u6A21\u5F0F\u8FD0\u884C").option("--setup", "\u4EA4\u4E92\u5F0F\u914D\u7F6E\u5F53\u524D profile").option("--new-profile", "\u6DFB\u52A0\u65B0 profile\uFF08\u591A bot \u573A\u666F\uFF09").option("--list-profiles", "\u5217\u51FA\u6240\u6709\u5DF2\u914D\u7F6E\u7684 profile").option("--reset-session", "\u6E05\u9664\u6307\u5B9A profile \u7684\u4F1A\u8BDD\u72B6\u6001").option("--ps", "\u5217\u51FA\u6240\u6709\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B").option("--kill <target>", "\u6309 profile \u540D\u79F0\u7EC8\u6B62\u5BF9\u5E94\u8FDB\u7A0B").option("--kill-all", "\u7EC8\u6B62\u6240\u6709\u6B63\u5728\u8FD0\u884C\u7684\u8FDB\u7A0B").action(async (opts) => {
|
|
1636
|
+
if (opts.ps) {
|
|
1637
|
+
const { listLocks: listLocks2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
|
|
1638
|
+
const locks = listLocks2();
|
|
1639
|
+
if (locks.length === 0) {
|
|
1640
|
+
console.log("\u6CA1\u6709\u8FD0\u884C\u4E2D\u7684\u8FDB\u7A0B");
|
|
1641
|
+
} else {
|
|
1642
|
+
for (const l of locks) {
|
|
1643
|
+
console.log(` [${l.profile}] PID ${l.pid} cwd: ${l.cwd} since: ${l.startedAt}`);
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
return;
|
|
1647
|
+
}
|
|
1648
|
+
if (opts.kill) {
|
|
1649
|
+
const { killByLock: killByLock2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
|
|
1650
|
+
const ok = killByLock2(opts.kill);
|
|
1651
|
+
console.log(ok ? `\u5DF2\u7EC8\u6B62 profile "${opts.kill}"` : `\u672A\u627E\u5230 "${opts.kill}"`);
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1654
|
+
if (opts.killAll) {
|
|
1655
|
+
const { listLocks: listLocks2, killByLock: killByLock2 } = await Promise.resolve().then(() => (init_lock(), lock_exports));
|
|
1656
|
+
for (const l of listLocks2()) {
|
|
1657
|
+
killByLock2(l.profile);
|
|
1658
|
+
console.log(`\u5DF2\u7EC8\u6B62: ${l.profile} (PID ${l.pid})`);
|
|
1659
|
+
}
|
|
1660
|
+
return;
|
|
1661
|
+
}
|
|
1662
|
+
if (opts.setup) {
|
|
1663
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
1664
|
+
await runSetup2(opts.profile);
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
if (opts.newProfile) {
|
|
1668
|
+
const { runNewProfile: runNewProfile2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
1669
|
+
await runNewProfile2();
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
if (opts.listProfiles) {
|
|
1673
|
+
const { resolveConfig: resolveConfig3 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1674
|
+
try {
|
|
1675
|
+
const cfg2 = resolveConfig3(opts.profile, process.cwd());
|
|
1676
|
+
console.log(`Profile: ${cfg2.profile} platform: ${cfg2.platform} source: ${cfg2.platform_secret_source}`);
|
|
1677
|
+
} catch (e) {
|
|
1678
|
+
console.log(`\u65E0\u6CD5\u52A0\u8F7D\u914D\u7F6E: ${e.message}`);
|
|
1679
|
+
}
|
|
1680
|
+
return;
|
|
1681
|
+
}
|
|
1682
|
+
if (opts.resetSession) {
|
|
1683
|
+
const { clearSession: clearSession2 } = await Promise.resolve().then(() => (init_session(), session_exports));
|
|
1684
|
+
clearSession2(opts.profile);
|
|
1685
|
+
console.log(`\u5DF2\u6E05\u9664 profile "${opts.profile}" \u7684\u4F1A\u8BDD`);
|
|
1686
|
+
return;
|
|
1687
|
+
}
|
|
1688
|
+
const { resolveConfig: resolveConfig2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
1689
|
+
const { startApp: startApp2 } = await Promise.resolve().then(() => (init_lifecycle(), lifecycle_exports));
|
|
1690
|
+
const cwd = process.cwd();
|
|
1691
|
+
let cfg;
|
|
1692
|
+
try {
|
|
1693
|
+
cfg = resolveConfig2(opts.profile, cwd);
|
|
1694
|
+
} catch (e) {
|
|
1695
|
+
if (e.message.includes("\u672A\u627E\u5230")) {
|
|
1696
|
+
console.log(`
|
|
1697
|
+
\u672A\u914D\u7F6E profile "${opts.profile}" \u7684 IM \u901A\u9053\u5BC6\u94A5\uFF0C\u5373\u5C06\u8FDB\u5165\u914D\u7F6E\u5411\u5BFC...
|
|
1698
|
+
`);
|
|
1699
|
+
const { runSetup: runSetup2 } = await Promise.resolve().then(() => (init_interactive(), interactive_exports));
|
|
1700
|
+
await runSetup2(opts.profile);
|
|
1701
|
+
cfg = resolveConfig2(opts.profile, cwd);
|
|
1702
|
+
} else {
|
|
1703
|
+
throw e;
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
console.log(`claude-im v${PKG.version} profile: ${cfg.profile} platform: ${cfg.platform} cwd: ${cwd}`);
|
|
1707
|
+
process.title = `claude-im [${cfg.profile}]`;
|
|
1708
|
+
await startApp2(cfg, { resume: opts.continue, daemon: opts.daemon });
|
|
1709
|
+
});
|
|
1710
|
+
program.parse();
|