linco-connect 1.0.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/README.md +426 -0
- package/bin/linco.js +465 -0
- package/package.json +25 -0
- package/public/index.html +1457 -0
- package/server.js +17 -0
- package/src/agentRunner.js +37 -0
- package/src/agents/claude.js +1 -0
- package/src/agents/codex.js +869 -0
- package/src/attachmentHandler.js +258 -0
- package/src/claudeRunner.js +564 -0
- package/src/config.js +371 -0
- package/src/danger.js +21 -0
- package/src/httpStatic.js +166 -0
- package/src/imConnector.js +488 -0
- package/src/imageHandler.js +38 -0
- package/src/lincoProtocol.js +209 -0
- package/src/localAuth.js +46 -0
- package/src/logger.js +137 -0
- package/src/outgoingAttachmentHandler.js +204 -0
- package/src/protocol.js +17 -0
- package/src/serverApp.js +61 -0
- package/src/session.js +359 -0
- package/src/slashCommands.js +349 -0
- package/src/streamBuffer.js +73 -0
- package/src/wsServer.js +293 -0
package/bin/linco.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execFileSync, spawn } = require('child_process');
|
|
6
|
+
const { startServer } = require('../src/serverApp');
|
|
7
|
+
const {
|
|
8
|
+
ensureDir,
|
|
9
|
+
findGitBash,
|
|
10
|
+
getConfigFile,
|
|
11
|
+
loadConfig,
|
|
12
|
+
parseToken,
|
|
13
|
+
readUserConfig,
|
|
14
|
+
resolveCommand,
|
|
15
|
+
saveUserConfig,
|
|
16
|
+
} = require('../src/config');
|
|
17
|
+
const { ensureLocalToken, localUrlWithToken } = require('../src/localAuth');
|
|
18
|
+
const pkg = require('../package.json');
|
|
19
|
+
|
|
20
|
+
const rootDir = path.resolve(__dirname, '..');
|
|
21
|
+
|
|
22
|
+
main().catch((err) => {
|
|
23
|
+
console.error(`❌ ${err.message}`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function main() {
|
|
28
|
+
const argv = process.argv.slice(2);
|
|
29
|
+
const command = argv[0] && !argv[0].startsWith('-') ? argv.shift() : 'help';
|
|
30
|
+
const options = parseArgs(argv);
|
|
31
|
+
|
|
32
|
+
if (options.version || command === 'version') {
|
|
33
|
+
console.log(pkg.version);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (options.help || command === 'help') {
|
|
38
|
+
printHelp();
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
switch (command) {
|
|
43
|
+
case 'init':
|
|
44
|
+
initCommand(options);
|
|
45
|
+
break;
|
|
46
|
+
case 'start':
|
|
47
|
+
await startCommand(options);
|
|
48
|
+
break;
|
|
49
|
+
case 'stop':
|
|
50
|
+
await stopCommand();
|
|
51
|
+
break;
|
|
52
|
+
case 'doctor':
|
|
53
|
+
doctorCommand();
|
|
54
|
+
break;
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`未知命令: ${command}\n运行 linco --help 查看用法。`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function parseArgs(args) {
|
|
61
|
+
const options = {};
|
|
62
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
63
|
+
const arg = args[i];
|
|
64
|
+
if (!arg.startsWith('--')) continue;
|
|
65
|
+
|
|
66
|
+
const eq = arg.indexOf('=');
|
|
67
|
+
const key = eq > 2 ? arg.slice(2, eq) : arg.slice(2);
|
|
68
|
+
const value = eq > 2 ? arg.slice(eq + 1) : args[i + 1];
|
|
69
|
+
|
|
70
|
+
if (['force', 'help', 'version', 'daemon', 'daemon-child', 'local-im', 'mock-im'].includes(key)) {
|
|
71
|
+
options[key] = true;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (value == null || String(value).startsWith('--')) {
|
|
76
|
+
throw new Error(`参数 --${key} 缺少值`);
|
|
77
|
+
}
|
|
78
|
+
options[key] = value;
|
|
79
|
+
if (eq < 0) i += 1;
|
|
80
|
+
}
|
|
81
|
+
return options;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function initCommand(options) {
|
|
85
|
+
const tokenValue = options.token || process.env.LINCO_TOKEN || '';
|
|
86
|
+
const token = parseToken(tokenValue);
|
|
87
|
+
const appId = options['app-id'] || token.appId;
|
|
88
|
+
const appSecret = options['app-secret'] || token.appSecret;
|
|
89
|
+
|
|
90
|
+
if (!appId || !appSecret) {
|
|
91
|
+
throw new Error('请提供 --token "appId:appSecret",或同时提供 --app-id 与 --app-secret。');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const account = options.account || 'default';
|
|
95
|
+
const configFile = getConfigFile();
|
|
96
|
+
const current = readUserConfig(configFile);
|
|
97
|
+
|
|
98
|
+
if (fs.existsSync(configFile) && !options.force) {
|
|
99
|
+
const existingAccount = current.channels?.linco?.accounts?.[account];
|
|
100
|
+
if (existingAccount?.appId || existingAccount?.appSecret || existingAccount?.token) {
|
|
101
|
+
throw new Error(`配置已存在,如需覆盖请添加 --force。配置文件: ${configFile}`);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const agentType = options.agent ? options.agent.trim().toLowerCase() : null;
|
|
106
|
+
|
|
107
|
+
if (agentType) {
|
|
108
|
+
const agentBin = resolveCommand(agentType);
|
|
109
|
+
if (!agentBin || !commandExists(agentType)) {
|
|
110
|
+
throw new Error(`未检测到 ${agentType} CLI,请先安装 ${agentType} 再初始化。`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const next = mergeInitConfig(current, { appId, appSecret, account, agentType });
|
|
115
|
+
saveUserConfig(next, configFile);
|
|
116
|
+
|
|
117
|
+
const config = loadConfig(rootDir);
|
|
118
|
+
ensureLocalToken(config);
|
|
119
|
+
|
|
120
|
+
console.log('✅ Linco Connect 初始化完成');
|
|
121
|
+
console.log(` 配置文件: ${config.configFile}`);
|
|
122
|
+
console.log(` 账号: ${account}`);
|
|
123
|
+
if (agentType) {
|
|
124
|
+
console.log(` Agent 类型: ${agentType}`);
|
|
125
|
+
}
|
|
126
|
+
console.log(' 下一步: linco doctor && linco start');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function commandExists(command) {
|
|
130
|
+
try {
|
|
131
|
+
const detector = process.platform === 'win32' ? 'where.exe' : 'which';
|
|
132
|
+
const args = [command];
|
|
133
|
+
execFileSync(detector, args, { stdio: 'ignore', windowsHide: true });
|
|
134
|
+
return true;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function mergeInitConfig(current, values) {
|
|
141
|
+
const accountEntry = {
|
|
142
|
+
appId: values.appId,
|
|
143
|
+
appSecret: values.appSecret,
|
|
144
|
+
enabled: true,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
...current,
|
|
149
|
+
defaultChannel: 'linco',
|
|
150
|
+
channels: {
|
|
151
|
+
...(current.channels || {}),
|
|
152
|
+
linco: {
|
|
153
|
+
...(current.channels?.linco || {}),
|
|
154
|
+
agents: {
|
|
155
|
+
...(current.channels?.linco?.agents || {}),
|
|
156
|
+
[values.agentType]: {
|
|
157
|
+
...(current.channels?.linco?.agents?.[values.agentType] || {}),
|
|
158
|
+
defaultAccount: values.account,
|
|
159
|
+
accounts: {
|
|
160
|
+
...(current.channels?.linco?.agents?.[values.agentType]?.accounts || {}),
|
|
161
|
+
[values.account]: {
|
|
162
|
+
...(current.channels?.linco?.agents?.[values.agentType]?.accounts?.[values.account] || {}),
|
|
163
|
+
...accountEntry,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
localWeb: {
|
|
171
|
+
...(current.localWeb || {}),
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function startCommand(options = {}) {
|
|
177
|
+
if (options['local-im'] || options['mock-im']) {
|
|
178
|
+
process.env.LINCO_LOCAL_IM_ENABLED = '1';
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const config = loadConfig(rootDir);
|
|
182
|
+
ensureLocalToken(config);
|
|
183
|
+
|
|
184
|
+
if (options.daemon && options['daemon-child']) {
|
|
185
|
+
throw new Error('--daemon 与 --daemon-child 不能同时使用');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (options.daemon) {
|
|
189
|
+
await startDaemon(config);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (options['daemon-child']) {
|
|
194
|
+
startDaemonChild(config);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
startServer(rootDir, { config });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
async function startDaemon(config) {
|
|
202
|
+
ensureDir(config.lincoHome);
|
|
203
|
+
ensureDir(config.logsDir);
|
|
204
|
+
|
|
205
|
+
const pidFile = daemonPidFile(config);
|
|
206
|
+
const existing = readDaemonPid(pidFile);
|
|
207
|
+
if (existing?.pid) {
|
|
208
|
+
if (isOwnDaemon(existing) && isProcessRunning(existing.pid)) {
|
|
209
|
+
throw new Error(`Linco Connect 已在后台运行,PID: ${existing.pid}`);
|
|
210
|
+
}
|
|
211
|
+
removeDaemonPid(pidFile);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const logs = daemonLogFiles(config);
|
|
215
|
+
const outFd = fs.openSync(logs.stdoutLog, 'a');
|
|
216
|
+
const errFd = fs.openSync(logs.stderrLog, 'a');
|
|
217
|
+
const childArgs = [__filename, 'start', '--daemon-child'];
|
|
218
|
+
if (config.localWeb?.imEnabled) childArgs.push('--local-im');
|
|
219
|
+
const child = spawn(process.execPath, childArgs, {
|
|
220
|
+
cwd: rootDir,
|
|
221
|
+
detached: true,
|
|
222
|
+
env: process.env,
|
|
223
|
+
stdio: ['ignore', outFd, errFd],
|
|
224
|
+
windowsHide: true,
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
child.unref();
|
|
228
|
+
fs.closeSync(outFd);
|
|
229
|
+
fs.closeSync(errFd);
|
|
230
|
+
|
|
231
|
+
await waitForDaemonStart(child.pid, pidFile, 5000);
|
|
232
|
+
|
|
233
|
+
console.log('✅ Linco Connect 已在后台启动');
|
|
234
|
+
console.log(` PID: ${child.pid}`);
|
|
235
|
+
console.log(` PID 文件: ${pidFile}`);
|
|
236
|
+
console.log(` 标准输出日志: ${logs.stdoutLog}`);
|
|
237
|
+
console.log(` 错误日志: ${logs.stderrLog}`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function startDaemonChild(config) {
|
|
241
|
+
const pidFile = daemonPidFile(config);
|
|
242
|
+
let cleaned = false;
|
|
243
|
+
const cleanup = () => {
|
|
244
|
+
if (cleaned) return;
|
|
245
|
+
cleaned = true;
|
|
246
|
+
removeDaemonPid(pidFile);
|
|
247
|
+
};
|
|
248
|
+
const shutdown = () => {
|
|
249
|
+
cleanup();
|
|
250
|
+
process.exit(0);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
process.once('SIGINT', shutdown);
|
|
254
|
+
process.once('SIGTERM', shutdown);
|
|
255
|
+
process.once('exit', cleanup);
|
|
256
|
+
|
|
257
|
+
startServer(rootDir, {
|
|
258
|
+
config,
|
|
259
|
+
onListening: () => writeDaemonPid(config),
|
|
260
|
+
onClose: cleanup,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
async function stopCommand() {
|
|
265
|
+
const config = loadConfig(rootDir);
|
|
266
|
+
const pidFile = daemonPidFile(config);
|
|
267
|
+
const metadata = readDaemonPid(pidFile);
|
|
268
|
+
|
|
269
|
+
if (!metadata?.pid) {
|
|
270
|
+
removeDaemonPid(pidFile);
|
|
271
|
+
console.log('Linco Connect 未在后台运行。');
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (!isOwnDaemon(metadata)) {
|
|
276
|
+
removeDaemonPid(pidFile);
|
|
277
|
+
console.log(`已清理无效的 PID 文件: ${pidFile}`);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (!isProcessRunning(metadata.pid)) {
|
|
282
|
+
removeDaemonPid(pidFile);
|
|
283
|
+
console.log(`已清理失效的 PID 文件: ${pidFile}`);
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
process.kill(metadata.pid, 'SIGTERM');
|
|
288
|
+
const stopped = await waitForProcessExit(metadata.pid, 5000);
|
|
289
|
+
if (!stopped && isProcessRunning(metadata.pid)) {
|
|
290
|
+
process.kill(metadata.pid, 'SIGKILL');
|
|
291
|
+
const killed = await waitForProcessExit(metadata.pid, 2000);
|
|
292
|
+
if (!killed && isProcessRunning(metadata.pid)) {
|
|
293
|
+
throw new Error(`停止失败,进程仍在运行,PID: ${metadata.pid}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
removeDaemonPid(pidFile);
|
|
298
|
+
console.log(`✅ Linco Connect 已停止,PID: ${metadata.pid}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
function daemonPidFile(config) {
|
|
302
|
+
return path.join(config.lincoHome, 'linco.pid');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function daemonLogFiles(config) {
|
|
306
|
+
ensureDir(config.logsDir);
|
|
307
|
+
return {
|
|
308
|
+
stdoutLog: path.join(config.logsDir, 'daemon.out.log'),
|
|
309
|
+
stderrLog: path.join(config.logsDir, 'daemon.err.log'),
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function readDaemonPid(pidFile) {
|
|
314
|
+
try {
|
|
315
|
+
return JSON.parse(fs.readFileSync(pidFile, 'utf8'));
|
|
316
|
+
} catch {
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function writeDaemonPid(config) {
|
|
322
|
+
ensureDir(config.lincoHome);
|
|
323
|
+
const logs = daemonLogFiles(config);
|
|
324
|
+
fs.writeFileSync(daemonPidFile(config), `${JSON.stringify({
|
|
325
|
+
app: pkg.name,
|
|
326
|
+
cli: __filename,
|
|
327
|
+
cwd: rootDir,
|
|
328
|
+
pid: process.pid,
|
|
329
|
+
startedAt: new Date().toISOString(),
|
|
330
|
+
host: config.host,
|
|
331
|
+
port: config.port,
|
|
332
|
+
...logs,
|
|
333
|
+
}, null, 2)}\n`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function isOwnDaemon(metadata) {
|
|
337
|
+
return metadata?.app === pkg.name && metadata?.cli === __filename;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function removeDaemonPid(pidFile) {
|
|
341
|
+
try {
|
|
342
|
+
fs.rmSync(pidFile, { force: true });
|
|
343
|
+
} catch {}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function isProcessRunning(pid) {
|
|
347
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
348
|
+
try {
|
|
349
|
+
process.kill(pid, 0);
|
|
350
|
+
return true;
|
|
351
|
+
} catch {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function waitForProcessExit(pid, timeoutMs) {
|
|
357
|
+
const startedAt = Date.now();
|
|
358
|
+
return new Promise((resolve) => {
|
|
359
|
+
const timer = setInterval(() => {
|
|
360
|
+
if (!isProcessRunning(pid)) {
|
|
361
|
+
clearInterval(timer);
|
|
362
|
+
resolve(true);
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
366
|
+
clearInterval(timer);
|
|
367
|
+
resolve(false);
|
|
368
|
+
}
|
|
369
|
+
}, 100);
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function waitForDaemonStart(pid, pidFile, timeoutMs) {
|
|
374
|
+
const startedAt = Date.now();
|
|
375
|
+
return new Promise((resolve, reject) => {
|
|
376
|
+
const timer = setInterval(() => {
|
|
377
|
+
const metadata = readDaemonPid(pidFile);
|
|
378
|
+
if (metadata?.pid === pid && isProcessRunning(pid)) {
|
|
379
|
+
clearInterval(timer);
|
|
380
|
+
resolve();
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (!isProcessRunning(pid)) {
|
|
384
|
+
clearInterval(timer);
|
|
385
|
+
reject(new Error(`后台进程启动失败,请查看日志。PID 文件: ${pidFile}`));
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
389
|
+
clearInterval(timer);
|
|
390
|
+
reject(new Error(`后台进程启动超时,请查看日志。PID 文件: ${pidFile}`));
|
|
391
|
+
}
|
|
392
|
+
}, 100);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function doctorCommand() {
|
|
397
|
+
const checks = [];
|
|
398
|
+
const config = loadConfig(rootDir);
|
|
399
|
+
ensureLocalToken(config);
|
|
400
|
+
|
|
401
|
+
checks.push(['Node.js', Number(process.versions.node.split('.')[0]) >= 18, process.version]);
|
|
402
|
+
checks.push(['配置文件', fs.existsSync(config.configFile), config.configFile]);
|
|
403
|
+
checks.push(['IM token', Boolean(config.im.appId && config.im.appSecret), config.im.appId ? `appId=${config.im.appId}` : '未配置']);
|
|
404
|
+
checks.push(['远端 IM 连接', true, config.im.enabled ? '已启用' : '未启用']);
|
|
405
|
+
checks.push(['远端 IM 地址', Boolean(config.im.wsUrl), safeUrlForDisplay(config.im.wsUrl)]);
|
|
406
|
+
checks.push(['IM 账号', true, `${config.im.channel}/${config.im.account}`]);
|
|
407
|
+
checks.push(['IM Agent', true, config.im.agentId]);
|
|
408
|
+
checks.push(['本地测试 token', Boolean(config.localWeb?.token), '已生成']);
|
|
409
|
+
for (const [agentType, agent] of Object.entries(config.agents || {})) {
|
|
410
|
+
checks.push([`${agentType} Agent`, true, agent.enabled ? `已启用 ${safeUrlForDisplay(agent.wsUrl)}` : `未启用 ${safeUrlForDisplay(agent.wsUrl)}`]);
|
|
411
|
+
checks.push([`${agentType} CLI`, !agent.enabled || (path.isAbsolute(agent.bin) ? fs.existsSync(agent.bin) : commandExists(agent.bin)), agent.bin]);
|
|
412
|
+
}
|
|
413
|
+
checks.push(['Git Bash', process.platform !== 'win32' || Boolean(findGitBash(readUserConfig(config.configFile))), process.platform === 'win32' ? (config.gitBashEnv || '未找到') : '非 Windows 不需要']);
|
|
414
|
+
checks.push(['Linco Home', canEnsureWritable(config.lincoHome), config.lincoHome]);
|
|
415
|
+
checks.push(['会话目录', canEnsureWritable(config.sessionsDir), config.sessionsDir]);
|
|
416
|
+
|
|
417
|
+
console.log('Linco Connect Doctor\n');
|
|
418
|
+
let ok = true;
|
|
419
|
+
for (const [name, passed, detail] of checks) {
|
|
420
|
+
ok = ok && passed;
|
|
421
|
+
console.log(`${passed ? '✅' : '❌'} ${name}: ${detail}`);
|
|
422
|
+
}
|
|
423
|
+
console.log(`\n本地测试页: ${localUrlWithToken(config)}`);
|
|
424
|
+
|
|
425
|
+
if (!ok) process.exitCode = 1;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function safeUrlForDisplay(value) {
|
|
429
|
+
try {
|
|
430
|
+
const url = new URL(value);
|
|
431
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
432
|
+
} catch {
|
|
433
|
+
return value || '未配置';
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function canEnsureWritable(dir) {
|
|
438
|
+
try {
|
|
439
|
+
ensureDir(dir);
|
|
440
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
441
|
+
return true;
|
|
442
|
+
} catch {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function printHelp() {
|
|
448
|
+
console.log(`Linco Connect ${pkg.version}
|
|
449
|
+
|
|
450
|
+
用法:
|
|
451
|
+
linco init --token "appId:appSecret" --agent claude [--account default] [--force]
|
|
452
|
+
linco start [--daemon] [--local-im|--mock-im]
|
|
453
|
+
linco stop
|
|
454
|
+
linco doctor
|
|
455
|
+
|
|
456
|
+
说明:
|
|
457
|
+
init 初始化本地配置,不需要填写 wsUrl
|
|
458
|
+
start 启动本机 Agent 连接器和本地测试页
|
|
459
|
+
stop 停止后台运行的 Linco Connect
|
|
460
|
+
doctor 检查本地运行环境
|
|
461
|
+
|
|
462
|
+
Agent:
|
|
463
|
+
--agent 指定 Agent 类型,如 claude 或 codex
|
|
464
|
+
`);
|
|
465
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "linco-connect",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "自研 IM 桥接多 Agent 服务",
|
|
5
|
+
"main": "server.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"linco": "./bin/linco.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"public",
|
|
13
|
+
"server.js",
|
|
14
|
+
"CLAUDE.md"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"start": "node server.js"
|
|
18
|
+
},
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"ws": "^8.16.0"
|
|
24
|
+
}
|
|
25
|
+
}
|