@wu529778790/open-im 1.10.9-beta.21 → 1.10.9-beta.23
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/dist/adapters/opencode-adapter.d.ts +10 -0
- package/dist/adapters/opencode-adapter.js +45 -0
- package/dist/adapters/registry.js +5 -0
- package/dist/commands/handler.js +3 -2
- package/dist/config/types.d.ts +6 -1
- package/dist/config-web.js +6 -0
- package/dist/config.js +3 -0
- package/dist/opencode/cli-runner.d.ts +32 -0
- package/dist/opencode/cli-runner.js +161 -0
- package/dist/session/session-manager.d.ts +1 -1
- package/dist/setup.js +2 -10
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Adapter — run tasks through OpenCode CLI (`opencode run`)
|
|
3
|
+
*/
|
|
4
|
+
import type { RunCallbacks, RunHandle, RunOptions, ToolAdapter } from './tool-adapter.interface.js';
|
|
5
|
+
export declare class OpenCodeAdapter implements ToolAdapter {
|
|
6
|
+
private cliPath;
|
|
7
|
+
readonly toolId = "opencode";
|
|
8
|
+
constructor(cliPath: string);
|
|
9
|
+
run(prompt: string, sessionId: string | undefined, workDir: string, callbacks: RunCallbacks, options?: RunOptions): RunHandle;
|
|
10
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode Adapter — run tasks through OpenCode CLI (`opencode run`)
|
|
3
|
+
*/
|
|
4
|
+
import { runOpenCode } from '../opencode/cli-runner.js';
|
|
5
|
+
export class OpenCodeAdapter {
|
|
6
|
+
cliPath;
|
|
7
|
+
toolId = 'opencode';
|
|
8
|
+
constructor(cliPath) {
|
|
9
|
+
this.cliPath = cliPath;
|
|
10
|
+
}
|
|
11
|
+
run(prompt, sessionId, workDir, callbacks, options) {
|
|
12
|
+
return runOpenCode(this.cliPath, prompt, sessionId, workDir, {
|
|
13
|
+
onText: callbacks.onText,
|
|
14
|
+
onThinking: callbacks.onThinking,
|
|
15
|
+
onToolUse: callbacks.onToolUse,
|
|
16
|
+
onComplete: (raw) => {
|
|
17
|
+
const result = {
|
|
18
|
+
success: raw.success,
|
|
19
|
+
result: raw.result,
|
|
20
|
+
accumulated: raw.accumulated,
|
|
21
|
+
cost: raw.cost,
|
|
22
|
+
durationMs: raw.durationMs,
|
|
23
|
+
model: raw.model,
|
|
24
|
+
numTurns: raw.numTurns,
|
|
25
|
+
toolStats: raw.toolStats,
|
|
26
|
+
};
|
|
27
|
+
callbacks.onComplete(result);
|
|
28
|
+
},
|
|
29
|
+
onError: (err) => {
|
|
30
|
+
const msg = typeof err === 'string' ? err : String(err);
|
|
31
|
+
const friendly = msg.includes('session not found') ||
|
|
32
|
+
msg.includes('Session not found') ||
|
|
33
|
+
msg.includes('no sessions found')
|
|
34
|
+
? 'OpenCode 会话已失效,旧 session 已清理。请直接重试当前请求。'
|
|
35
|
+
: msg;
|
|
36
|
+
callbacks.onError(friendly);
|
|
37
|
+
},
|
|
38
|
+
onSessionId: callbacks.onSessionId,
|
|
39
|
+
onSessionInvalid: callbacks.onSessionInvalid,
|
|
40
|
+
}, {
|
|
41
|
+
skipPermissions: options?.skipPermissions,
|
|
42
|
+
model: options?.model,
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -2,6 +2,7 @@ import { getConfiguredAiCommands } from '../config.js';
|
|
|
2
2
|
import { ClaudeSDKAdapter, configureClaudeSdkSessionIdle } from './claude-sdk-adapter.js';
|
|
3
3
|
import { CodexAdapter } from './codex-adapter.js';
|
|
4
4
|
import { CodeBuddyAdapter } from './codebuddy-adapter.js';
|
|
5
|
+
import { OpenCodeAdapter } from './opencode-adapter.js';
|
|
5
6
|
import { createLogger } from '../logger.js';
|
|
6
7
|
import { destroyAllLiveChildren } from '../shared/process-kill.js';
|
|
7
8
|
const log = createLogger('Registry');
|
|
@@ -24,6 +25,10 @@ export function initAdapters(config) {
|
|
|
24
25
|
log.info('CodeBuddy CLI adapter enabled');
|
|
25
26
|
adapters.set('codebuddy', new CodeBuddyAdapter(config.codebuddyCliPath));
|
|
26
27
|
}
|
|
28
|
+
if (aiCommand === 'opencode') {
|
|
29
|
+
log.info('OpenCode CLI adapter enabled');
|
|
30
|
+
adapters.set('opencode', new OpenCodeAdapter(config.opencodeCliPath));
|
|
31
|
+
}
|
|
27
32
|
}
|
|
28
33
|
}
|
|
29
34
|
export function getAdapter(aiCommand) {
|
package/dist/commands/handler.js
CHANGED
|
@@ -189,12 +189,13 @@ export class CommandHandler {
|
|
|
189
189
|
}
|
|
190
190
|
getAiVersion(aiCommand) {
|
|
191
191
|
if (aiCommand === 'claude') {
|
|
192
|
-
// Claude 使用 SDK,返回 SDK 版本
|
|
193
192
|
return Promise.resolve('SDK Mode');
|
|
194
193
|
}
|
|
195
194
|
const cmd = aiCommand === 'codex'
|
|
196
195
|
? this.deps.config.codexCliPath
|
|
197
|
-
:
|
|
196
|
+
: aiCommand === 'opencode'
|
|
197
|
+
? this.deps.config.opencodeCliPath
|
|
198
|
+
: this.deps.config.codebuddyCliPath;
|
|
198
199
|
return new Promise((resolve) => {
|
|
199
200
|
execFile(cmd, ['--version'], { timeout: 5000 }, (err, stdout) => {
|
|
200
201
|
resolve(err ? '未知' : (stdout?.toString().trim() || '未知'));
|
package/dist/config/types.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LogLevel } from '../logger.js';
|
|
2
2
|
export type Platform = 'clawbot' | 'dingtalk' | 'feishu' | 'qq' | 'telegram' | 'wework' | 'workbuddy';
|
|
3
|
-
export type AiCommand = 'claude' | 'codex' | 'codebuddy';
|
|
3
|
+
export type AiCommand = 'claude' | 'codex' | 'codebuddy' | 'opencode';
|
|
4
4
|
export interface Config {
|
|
5
5
|
enabledPlatforms: Platform[];
|
|
6
6
|
telegramBotToken?: string;
|
|
@@ -24,6 +24,7 @@ export interface Config {
|
|
|
24
24
|
clawbotAllowedUserIds: string[];
|
|
25
25
|
codexCliPath: string;
|
|
26
26
|
codebuddyCliPath: string;
|
|
27
|
+
opencodeCliPath: string;
|
|
27
28
|
/** Claude 访问 API 的代理(如 http://127.0.0.1:7890) */
|
|
28
29
|
claudeProxy?: string;
|
|
29
30
|
/** Codex 访问 chatgpt.com 的代理(如 http://127.0.0.1:7890) */
|
|
@@ -182,6 +183,9 @@ export interface FileToolCodex {
|
|
|
182
183
|
export interface FileToolCodeBuddy {
|
|
183
184
|
cliPath?: string;
|
|
184
185
|
}
|
|
186
|
+
export interface FileToolOpenCode {
|
|
187
|
+
cliPath?: string;
|
|
188
|
+
}
|
|
185
189
|
export interface FileConfig {
|
|
186
190
|
telegramBotToken?: string;
|
|
187
191
|
feishuAppId?: string;
|
|
@@ -203,6 +207,7 @@ export interface FileConfig {
|
|
|
203
207
|
claude?: FileToolClaude;
|
|
204
208
|
codex?: FileToolCodex;
|
|
205
209
|
codebuddy?: FileToolCodeBuddy;
|
|
210
|
+
opencode?: FileToolOpenCode;
|
|
206
211
|
};
|
|
207
212
|
logDir?: string;
|
|
208
213
|
logLevel?: LogLevel;
|
package/dist/config-web.js
CHANGED
|
@@ -351,6 +351,7 @@ function buildInitialPayload(file) {
|
|
|
351
351
|
claudeProxy: file.tools?.claude?.proxy ?? "",
|
|
352
352
|
codexCliPath: file.tools?.codex?.cliPath ?? "codex",
|
|
353
353
|
codebuddyCliPath: file.tools?.codebuddy?.cliPath ?? "codebuddy",
|
|
354
|
+
opencodeCliPath: file.tools?.opencode?.cliPath ?? "opencode",
|
|
354
355
|
codexProxy: file.tools?.codex?.proxy ?? "",
|
|
355
356
|
codexApiKey: (() => {
|
|
356
357
|
if (process.env.OPENAI_API_KEY)
|
|
@@ -505,6 +506,7 @@ function createProbeConfig(values) {
|
|
|
505
506
|
logLevel: "INFO",
|
|
506
507
|
telemetry: { enabled: true },
|
|
507
508
|
codebuddyCliPath: "codebuddy",
|
|
509
|
+
opencodeCliPath: "opencode",
|
|
508
510
|
platforms: {},
|
|
509
511
|
...values,
|
|
510
512
|
};
|
|
@@ -704,6 +706,10 @@ function toFileConfig(payload, existing) {
|
|
|
704
706
|
...existing.tools?.codebuddy,
|
|
705
707
|
cliPath: clean(payload.ai.codebuddyCliPath) ?? "codebuddy",
|
|
706
708
|
},
|
|
709
|
+
opencode: {
|
|
710
|
+
...existing.tools?.opencode,
|
|
711
|
+
cliPath: clean(payload.ai.opencodeCliPath) ?? "opencode",
|
|
712
|
+
},
|
|
707
713
|
},
|
|
708
714
|
platforms: {
|
|
709
715
|
...existing.platforms,
|
package/dist/config.js
CHANGED
|
@@ -231,6 +231,8 @@ export function loadConfig() {
|
|
|
231
231
|
}
|
|
232
232
|
}
|
|
233
233
|
}
|
|
234
|
+
const topencode = file.tools?.opencode ?? {};
|
|
235
|
+
const opencodeCliPath = process.env.OPENCODE_CLI_PATH ?? topencode.cliPath ?? 'opencode';
|
|
234
236
|
const claudeWorkDir = process.env.CLAUDE_WORK_DIR ?? tc.workDir ?? process.cwd();
|
|
235
237
|
const skipPermissions = process.env.OPEN_IM_SKIP_PERMISSIONS === 'false'
|
|
236
238
|
? false
|
|
@@ -563,6 +565,7 @@ export function loadConfig() {
|
|
|
563
565
|
clawbotAllowedUserIds,
|
|
564
566
|
codexCliPath,
|
|
565
567
|
codebuddyCliPath,
|
|
568
|
+
opencodeCliPath,
|
|
566
569
|
claudeProxy,
|
|
567
570
|
codexProxy,
|
|
568
571
|
claudeWorkDir,
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode CLI runner — spawns `opencode run` in non-interactive mode.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode outputs plain text to stdout. Session continuation uses `-s <id>`.
|
|
5
|
+
*/
|
|
6
|
+
export interface OpenCodeRunCallbacks {
|
|
7
|
+
onText: (accumulated: string) => void;
|
|
8
|
+
onThinking?: (accumulated: string) => void;
|
|
9
|
+
onToolUse?: (toolName: string, toolInput?: Record<string, unknown>) => void;
|
|
10
|
+
onComplete: (result: {
|
|
11
|
+
success: boolean;
|
|
12
|
+
result: string;
|
|
13
|
+
accumulated: string;
|
|
14
|
+
cost: number;
|
|
15
|
+
durationMs: number;
|
|
16
|
+
model?: string;
|
|
17
|
+
numTurns: number;
|
|
18
|
+
toolStats: Record<string, number>;
|
|
19
|
+
}) => void;
|
|
20
|
+
onError: (error: string) => void;
|
|
21
|
+
onSessionId?: (sessionId: string) => void;
|
|
22
|
+
onSessionInvalid?: () => void;
|
|
23
|
+
}
|
|
24
|
+
export interface OpenCodeRunOptions {
|
|
25
|
+
skipPermissions?: boolean;
|
|
26
|
+
model?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface OpenCodeRunHandle {
|
|
29
|
+
abort: () => void;
|
|
30
|
+
}
|
|
31
|
+
export declare function buildOpenCodeArgs(prompt: string, sessionId: string | undefined, workDir: string, options?: OpenCodeRunOptions): string[];
|
|
32
|
+
export declare function runOpenCode(cliPath: string, prompt: string, sessionId: string | undefined, workDir: string, callbacks: OpenCodeRunCallbacks, options?: OpenCodeRunOptions): OpenCodeRunHandle;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenCode CLI runner — spawns `opencode run` in non-interactive mode.
|
|
3
|
+
*
|
|
4
|
+
* OpenCode outputs plain text to stdout. Session continuation uses `-s <id>`.
|
|
5
|
+
*/
|
|
6
|
+
import { spawn } from 'node:child_process';
|
|
7
|
+
import { createInterface } from 'node:readline';
|
|
8
|
+
import { createLogger } from '../logger.js';
|
|
9
|
+
import { processEnvForNonClaudeCliChild } from '../config/file-io.js';
|
|
10
|
+
import { killProcessTree, trackChild } from '../shared/process-kill.js';
|
|
11
|
+
const log = createLogger('OpenCodeCli');
|
|
12
|
+
export function buildOpenCodeArgs(prompt, sessionId, workDir, options) {
|
|
13
|
+
const args = ['run'];
|
|
14
|
+
// Working directory
|
|
15
|
+
args.push('--dir', workDir);
|
|
16
|
+
// Session continuation
|
|
17
|
+
if (sessionId) {
|
|
18
|
+
args.push('--session', sessionId);
|
|
19
|
+
}
|
|
20
|
+
// Model override
|
|
21
|
+
if (options?.model) {
|
|
22
|
+
args.push('--model', options.model);
|
|
23
|
+
}
|
|
24
|
+
// Skip permissions
|
|
25
|
+
if (options?.skipPermissions) {
|
|
26
|
+
args.push('--dangerously-skip-permissions');
|
|
27
|
+
}
|
|
28
|
+
// Prompt as positional argument
|
|
29
|
+
args.push(prompt);
|
|
30
|
+
return args;
|
|
31
|
+
}
|
|
32
|
+
export function runOpenCode(cliPath, prompt, sessionId, workDir, callbacks, options) {
|
|
33
|
+
const args = buildOpenCodeArgs(prompt, sessionId, workDir, options);
|
|
34
|
+
const env = processEnvForNonClaudeCliChild();
|
|
35
|
+
log.info(`Spawning OpenCode: path=${cliPath}, cwd=${workDir}, session=${sessionId ?? 'new'}`);
|
|
36
|
+
const child = spawn(cliPath, args, {
|
|
37
|
+
cwd: workDir,
|
|
38
|
+
detached: process.platform !== 'win32',
|
|
39
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
40
|
+
env,
|
|
41
|
+
windowsHide: process.platform === 'win32',
|
|
42
|
+
});
|
|
43
|
+
trackChild(child);
|
|
44
|
+
// Close stdin — prompt is passed as argument
|
|
45
|
+
child.stdin?.end();
|
|
46
|
+
let accumulated = '';
|
|
47
|
+
let completed = false;
|
|
48
|
+
const toolStats = {};
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
const rl = createInterface({ input: child.stdout });
|
|
51
|
+
const MAX_STDERR_HEAD = 4 * 1024;
|
|
52
|
+
const MAX_STDERR_TAIL = 6 * 1024;
|
|
53
|
+
let stderrHead = '';
|
|
54
|
+
let stderrTail = '';
|
|
55
|
+
let stderrTotal = 0;
|
|
56
|
+
let stderrHeadFull = false;
|
|
57
|
+
child.stderr?.on('data', (chunk) => {
|
|
58
|
+
const text = chunk.toString();
|
|
59
|
+
stderrTotal += text.length;
|
|
60
|
+
if (!stderrHeadFull) {
|
|
61
|
+
const room = MAX_STDERR_HEAD - stderrHead.length;
|
|
62
|
+
if (room > 0) {
|
|
63
|
+
stderrHead += text.slice(0, room);
|
|
64
|
+
if (stderrHead.length >= MAX_STDERR_HEAD)
|
|
65
|
+
stderrHeadFull = true;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
stderrTail += text;
|
|
69
|
+
if (stderrTail.length > MAX_STDERR_TAIL) {
|
|
70
|
+
stderrTail = stderrTail.slice(-MAX_STDERR_TAIL);
|
|
71
|
+
}
|
|
72
|
+
log.debug(`[stderr] ${text.trimEnd()}`);
|
|
73
|
+
});
|
|
74
|
+
rl.on('line', (line) => {
|
|
75
|
+
if (completed)
|
|
76
|
+
return;
|
|
77
|
+
// OpenCode outputs plain text — accumulate each line
|
|
78
|
+
const trimmed = line.trim();
|
|
79
|
+
if (!trimmed)
|
|
80
|
+
return;
|
|
81
|
+
// Skip spinner lines (ANSI escape sequences or common spinner chars)
|
|
82
|
+
if (/^[⠀-⣿\s⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏▪●◆⬤◉⬤⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏]+$/.test(trimmed))
|
|
83
|
+
return;
|
|
84
|
+
accumulated += (accumulated ? '\n' : '') + line;
|
|
85
|
+
callbacks.onText(accumulated);
|
|
86
|
+
});
|
|
87
|
+
// Also capture stdout as a raw stream for binary-like streaming
|
|
88
|
+
child.stdout?.on('data', (chunk) => {
|
|
89
|
+
// The readline interface handles line-by-line, but we also
|
|
90
|
+
// want to capture any partial data for streaming updates
|
|
91
|
+
const text = chunk.toString();
|
|
92
|
+
if (!completed && text.trim()) {
|
|
93
|
+
log.debug(`[stdout chunk] ${text.substring(0, 200)}`);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
let exitCode = null;
|
|
97
|
+
let rlClosed = false;
|
|
98
|
+
let childClosed = false;
|
|
99
|
+
const finalize = () => {
|
|
100
|
+
if (!rlClosed || !childClosed)
|
|
101
|
+
return;
|
|
102
|
+
if (completed)
|
|
103
|
+
return;
|
|
104
|
+
if (exitCode !== null && exitCode !== 0) {
|
|
105
|
+
let errMsg = '';
|
|
106
|
+
if (stderrTotal > 0) {
|
|
107
|
+
if (!stderrHeadFull) {
|
|
108
|
+
errMsg = stderrHead;
|
|
109
|
+
}
|
|
110
|
+
else if (stderrTotal <= MAX_STDERR_HEAD + MAX_STDERR_TAIL) {
|
|
111
|
+
errMsg = stderrHead + stderrTail.slice(stderrTail.length - (stderrTotal - MAX_STDERR_HEAD));
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
errMsg =
|
|
115
|
+
stderrHead +
|
|
116
|
+
`\n\n... (omitted ${stderrTotal - MAX_STDERR_HEAD - MAX_STDERR_TAIL} bytes) ...\n\n` +
|
|
117
|
+
stderrTail;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (sessionId &&
|
|
121
|
+
(errMsg.includes('session not found') ||
|
|
122
|
+
errMsg.includes('Session not found') ||
|
|
123
|
+
errMsg.includes('no sessions found'))) {
|
|
124
|
+
callbacks.onSessionInvalid?.();
|
|
125
|
+
}
|
|
126
|
+
callbacks.onError(errMsg || `OpenCode exited with code ${exitCode}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// Exit code 0 but no onComplete fired yet — treat accumulated text as result
|
|
130
|
+
if (!completed) {
|
|
131
|
+
completed = true;
|
|
132
|
+
callbacks.onComplete({
|
|
133
|
+
success: true,
|
|
134
|
+
result: accumulated,
|
|
135
|
+
accumulated,
|
|
136
|
+
cost: 0,
|
|
137
|
+
durationMs: Date.now() - startTime,
|
|
138
|
+
numTurns: 1,
|
|
139
|
+
toolStats,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
rl.on('close', () => {
|
|
144
|
+
rlClosed = true;
|
|
145
|
+
finalize();
|
|
146
|
+
});
|
|
147
|
+
child.on('close', (code) => {
|
|
148
|
+
exitCode = code;
|
|
149
|
+
childClosed = true;
|
|
150
|
+
finalize();
|
|
151
|
+
});
|
|
152
|
+
child.on('error', (err) => {
|
|
153
|
+
log.error('OpenCode spawn error:', err);
|
|
154
|
+
callbacks.onError(`Failed to start OpenCode: ${err.message}`);
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
abort: () => {
|
|
158
|
+
killProcessTree(child);
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
package/dist/setup.js
CHANGED
|
@@ -121,25 +121,17 @@ function printManualInstructions(configPath) {
|
|
|
121
121
|
"cardTemplateId": "你的钉钉 AI 卡片模板 ID(可选,配置后启用单条流式)",
|
|
122
122
|
"allowedUserIds": ["允许访问的钉钉用户 ID(可选)"]
|
|
123
123
|
},
|
|
124
|
-
"wechat": {
|
|
125
|
-
"enabled": false,
|
|
126
|
-
"aiCommand": "claude",
|
|
127
|
-
"workbuddyAccessToken": "(由 open-im init 在浏览器完成 WorkBuddy 登录后自动写入)",
|
|
128
|
-
"workbuddyRefreshToken": "",
|
|
129
|
-
"userId": "",
|
|
130
|
-
"allowedUserIds": ["允许访问的微信用户 ID(可选)"]
|
|
131
|
-
},
|
|
132
124
|
"clawbot": {
|
|
133
125
|
"enabled": false,
|
|
134
126
|
"aiCommand": "claude",
|
|
135
|
-
"apiUrl": "
|
|
127
|
+
"apiUrl": "https://ilinkai.weixin.qq.com",
|
|
136
128
|
"apiToken": "你的 ClawBot Bearer Token(可选)",
|
|
137
129
|
"allowedUserIds": ["允许访问的微信用户 ID(可选)"]
|
|
138
130
|
}
|
|
139
131
|
}
|
|
140
132
|
}`);
|
|
141
133
|
console.log("");
|
|
142
|
-
console.log("提示:至少需要配置 Telegram、Feishu、QQ、
|
|
134
|
+
console.log("提示:至少需要配置 Telegram、Feishu、QQ、WeWork、DingTalk、WorkBuddy 或 ClawBot 其中一个平台");
|
|
143
135
|
console.log("或设置环境变量: TELEGRAM_BOT_TOKEN=xxx、FEISHU_APP_ID=xxx、QQ_BOT_APPID=xxx、WECHAT_WORKBUDDY_ACCESS_TOKEN=xxx、WEWORK_CORP_ID=xxx、DINGTALK_CLIENT_ID=xxx 或 CLAWBOT_API_TOKEN=xxx 后再运行");
|
|
144
136
|
console.log("");
|
|
145
137
|
}
|