dingtalk-ask-mcp-server 0.1.3 → 0.1.5
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 +2 -0
- package/dist/daemonClient.js +24 -4
- package/dist/installCli.js +63 -10
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,8 @@ dingtalk-ask install --client-id <AppKey> --client-secret <AppSecret> \
|
|
|
60
60
|
[--timeout 1440000] [--host cc|codex|both]
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
> **不想记参数?** 在终端直接跑 `dingtalk-ask install`(不带凭证)会进入**交互式向导**,一步步问你 AppKey / AppSecret / robotCode / userId / 宿主(有默认值回车即用)。
|
|
64
|
+
|
|
63
65
|
它会自动改这些(合并保留既有内容、幂等):
|
|
64
66
|
|
|
65
67
|
| 宿主 | 改动的文件 | 写入内容 |
|
package/dist/daemonClient.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { request } from 'node:http';
|
|
8
8
|
import { spawn } from 'node:child_process';
|
|
9
|
-
import { openSync, existsSync } from 'node:fs';
|
|
9
|
+
import { openSync, closeSync, existsSync } from 'node:fs';
|
|
10
10
|
import { tmpdir } from 'node:os';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
@@ -152,11 +152,22 @@ function resolveDaemonScript() {
|
|
|
152
152
|
*/
|
|
153
153
|
function spawnDaemon(config) {
|
|
154
154
|
const daemonScript = resolveDaemonScript();
|
|
155
|
-
|
|
155
|
+
// 日志重定向是可选的调试输出:文件被占用/无权限(EPERM)时退回不记日志,绝不因此阻断守护进程拉起。
|
|
156
|
+
let stdio = ['ignore', 'ignore', 'ignore'];
|
|
157
|
+
let logFd;
|
|
158
|
+
let logNote = '无日志(文件不可写)';
|
|
159
|
+
try {
|
|
160
|
+
logFd = openSync(daemonLogPath(), 'a');
|
|
161
|
+
stdio = ['ignore', logFd, logFd];
|
|
162
|
+
logNote = `日志 ${daemonLogPath()}`;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
/* 日志打不开:退回 ignore,仍拉起守护进程 */
|
|
166
|
+
}
|
|
156
167
|
const child = spawn(process.execPath, [daemonScript], {
|
|
157
168
|
detached: true,
|
|
158
169
|
windowsHide: true,
|
|
159
|
-
stdio
|
|
170
|
+
stdio,
|
|
160
171
|
env: {
|
|
161
172
|
...process.env,
|
|
162
173
|
// 注入解析后的凭证,覆盖可能缺失/过期的 env(支持 CLI 参数来源)。
|
|
@@ -168,7 +179,16 @@ function spawnDaemon(config) {
|
|
|
168
179
|
},
|
|
169
180
|
});
|
|
170
181
|
child.unref();
|
|
171
|
-
|
|
182
|
+
// 关闭父进程持有的日志 fd 副本:子进程已继承自己的句柄;不关会泄漏、长期锁住日志文件。
|
|
183
|
+
if (logFd !== undefined) {
|
|
184
|
+
try {
|
|
185
|
+
closeSync(logFd);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
/* 忽略 */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
console.error(`已拉起钉钉守护进程(pid=${child.pid ?? '?'},脚本 ${daemonScript},${logNote})。`);
|
|
172
192
|
}
|
|
173
193
|
/**
|
|
174
194
|
* 确保守护进程在跑并就绪,返回其锁(port + token)。
|
package/dist/installCli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, unlin
|
|
|
14
14
|
import { homedir, tmpdir } from 'node:os';
|
|
15
15
|
import { join, dirname } from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import { createInterface } from 'node:readline/promises';
|
|
17
18
|
import { resolveDingtalkConfig } from './dingtalk/config.js';
|
|
18
19
|
import { mergeArgsIntoEnv } from './cliArgs.js';
|
|
19
20
|
import { packageVersion } from './version.js';
|
|
@@ -200,7 +201,7 @@ function main() {
|
|
|
200
201
|
usage();
|
|
201
202
|
}
|
|
202
203
|
if (cmd === 'install') {
|
|
203
|
-
runInstall(rest);
|
|
204
|
+
void runInstall(rest);
|
|
204
205
|
}
|
|
205
206
|
else if (cmd === 'uninstall') {
|
|
206
207
|
runUninstall(rest);
|
|
@@ -328,23 +329,75 @@ function runUninstall(rest) {
|
|
|
328
329
|
cleanRuntime();
|
|
329
330
|
console.log('\n✅ 已卸载。重启 Claude Code / 重开 Codex 会话后彻底生效。');
|
|
330
331
|
}
|
|
331
|
-
/**
|
|
332
|
-
function
|
|
332
|
+
/** 交互式逐步收集凭证(缺配置时的向导)。返回解析后的 config 与可选 host。 */
|
|
333
|
+
async function promptConfig(env, cwd) {
|
|
334
|
+
const existing = inspectMcpJson(tolerantJson(join(cwd, '.mcp.json')));
|
|
335
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
336
|
+
try {
|
|
337
|
+
console.log('未检测到完整凭证,进入交互式配置(直接回车用【中括号】内默认值):\n');
|
|
338
|
+
const ask = async (label, def) => {
|
|
339
|
+
const a = (await rl.question(` ${label}${def ? ` [${def}]` : ''}: `)).trim();
|
|
340
|
+
return a || def || '';
|
|
341
|
+
};
|
|
342
|
+
const askSecret = async (label, realDef) => {
|
|
343
|
+
const a = (await rl.question(` ${label}${realDef ? ` [${mask(realDef)}]` : ''}: `)).trim();
|
|
344
|
+
return a || realDef || '';
|
|
345
|
+
};
|
|
346
|
+
const clientId = await ask('AppKey (client-id)', env.DINGTALK_CLIENT_ID);
|
|
347
|
+
const clientSecret = await askSecret('AppSecret (client-secret)', env.DINGTALK_CLIENT_SECRET);
|
|
348
|
+
const robotCode = await ask('机器人 robotCode(一般同 AppKey)', env.DINGTALK_ROBOT_CODE ?? clientId);
|
|
349
|
+
const userId = await ask('你的钉钉 userId', env.DINGTALK_USER_ID ?? existing.userId);
|
|
350
|
+
const defaultHost = existsSync(join(homedir(), '.codex')) ? 'both' : 'cc';
|
|
351
|
+
const hostRaw = await ask('装到哪个宿主 cc/codex/both', defaultHost);
|
|
352
|
+
const host = hostRaw === 'cc' || hostRaw === 'codex' || hostRaw === 'both' ? hostRaw : undefined;
|
|
353
|
+
const merged = {
|
|
354
|
+
...env,
|
|
355
|
+
DINGTALK_CLIENT_ID: clientId,
|
|
356
|
+
DINGTALK_CLIENT_SECRET: clientSecret,
|
|
357
|
+
DINGTALK_ROBOT_CODE: robotCode || clientId,
|
|
358
|
+
DINGTALK_USER_ID: userId,
|
|
359
|
+
};
|
|
360
|
+
let config;
|
|
361
|
+
try {
|
|
362
|
+
config = resolveDingtalkConfig(merged);
|
|
363
|
+
}
|
|
364
|
+
catch (error) {
|
|
365
|
+
console.error(`\n凭证不完整:${error instanceof Error ? error.message : String(error)}`);
|
|
366
|
+
process.exit(1);
|
|
367
|
+
}
|
|
368
|
+
return { config, host };
|
|
369
|
+
}
|
|
370
|
+
finally {
|
|
371
|
+
rl.close();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
/** install 子命令:装 MCP + 钩子(缺凭证时进交互式向导)。 */
|
|
375
|
+
async function runInstall(rest) {
|
|
376
|
+
const cwd = process.cwd();
|
|
377
|
+
const env = mergeArgsIntoEnv(process.env, rest);
|
|
333
378
|
let config;
|
|
379
|
+
let interactiveHost;
|
|
334
380
|
try {
|
|
335
|
-
config = resolveDingtalkConfig(
|
|
381
|
+
config = resolveDingtalkConfig(env);
|
|
336
382
|
}
|
|
337
|
-
catch
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
383
|
+
catch {
|
|
384
|
+
// 缺凭证:交互式终端 → 逐步引导输入;非交互(管道/CI)→ 报错提示用参数。
|
|
385
|
+
if (process.stdin.isTTY !== true) {
|
|
386
|
+
console.error('缺少钉钉凭证。请用 --client-id / --client-secret / --robot-code / --user-id 提供,');
|
|
387
|
+
console.error('或在交互式终端直接运行 `dingtalk-ask install` 逐步输入。');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
const r = await promptConfig(env, cwd);
|
|
391
|
+
config = r.config;
|
|
392
|
+
interactiveHost = r.host;
|
|
341
393
|
}
|
|
342
394
|
const nodePath = process.execPath;
|
|
343
395
|
const indexPath = fileURLToPath(new URL('./index.js', import.meta.url));
|
|
344
|
-
// 目标宿主:显式 --host
|
|
345
|
-
const host = parseHost(rest);
|
|
396
|
+
// 目标宿主:显式 --host 优先,其次交互式选择;否则 CC 必装、Codex 存在则一并装。
|
|
397
|
+
const host = parseHost(rest) ?? interactiveHost;
|
|
346
398
|
const doCc = host === undefined || host === 'cc' || host === 'both';
|
|
347
399
|
const doCodex = host === 'codex' || host === 'both' || (host === undefined && existsSync(join(homedir(), '.codex')));
|
|
400
|
+
console.log('');
|
|
348
401
|
if (doCc) {
|
|
349
402
|
installClaudeCode(config, rest, nodePath, indexPath);
|
|
350
403
|
}
|