dingtalk-ask-mcp-server 0.1.4 → 0.1.6
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 +7 -3
- package/dist/installCli.js +84 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,15 +60,19 @@ 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
|
| 宿主 | 改动的文件 | 写入内容 |
|
|
66
68
|
|---|---|---|
|
|
67
|
-
| **Claude Code** | `<当前目录>/.mcp.json` | `mcpServers.dingtalk-ask`(服务 + 凭证 env) |
|
|
68
|
-
| | `<当前目录>/.claude/settings.json`
|
|
69
|
+
| **Claude Code**(项目级) | `<当前目录>/.mcp.json` | `mcpServers.dingtalk-ask`(服务 + 凭证 env) |
|
|
70
|
+
| | `<当前目录>/.claude/settings.json` | `PermissionRequest` 钩子 + `env.MCP_TOOL_TIMEOUT` |
|
|
71
|
+
| **Claude Code**(`--global`·所有项目) | `~/.claude.json`(先备份 `.bak`) | `mcpServers.dingtalk-ask` |
|
|
72
|
+
| | `~/.claude/settings.json` | 钩子 + `MCP_TOOL_TIMEOUT` |
|
|
69
73
|
| **Codex** | `~/.codex/config.toml` | `[mcp_servers.dingtalk-ask]`(含 `tool_timeout_sec=1500` + 凭证,先备份 `.bak`) |
|
|
70
74
|
|
|
71
|
-
默认 **CC 必装,检测到 `~/.codex` 时 Codex
|
|
75
|
+
默认 **CC 必装,检测到 `~/.codex` 时 Codex 一并装**。CC 默认写**项目级**(只对当前项目生效);加 **`--global`** 写**用户级**,免每个项目各装一次(合并保留你其它 MCP server)。装完**重启 Claude Code / 重开 Codex 会话**生效。
|
|
72
76
|
|
|
73
77
|
### 方式 B:手动接入(想自己控制时)
|
|
74
78
|
|
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';
|
|
@@ -54,17 +55,22 @@ function writeJson(path, obj) {
|
|
|
54
55
|
mkdirSync(dirname(path), { recursive: true });
|
|
55
56
|
writeFileSync(path, `${JSON.stringify(obj, null, 2)}\n`, 'utf8');
|
|
56
57
|
}
|
|
57
|
-
/** 装 Claude Code
|
|
58
|
+
/** 装 Claude Code:MCP server(项目 .mcp.json 或 --global 用户级 ~/.claude.json)+ 钩子 + MCP_TOOL_TIMEOUT。 */
|
|
58
59
|
function installClaudeCode(config, argv, nodePath, indexPath) {
|
|
59
60
|
const cwd = process.cwd();
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
const global = argv.includes('--global');
|
|
62
|
+
// 1. MCP server:--global 写 ~/.claude.json(用户级,所有项目可见),否则项目 .mcp.json。
|
|
63
|
+
const mcpPath = global ? join(homedir(), '.claude.json') : join(cwd, '.mcp.json');
|
|
62
64
|
const entry = buildMcpEntry(config, nodePath, indexPath);
|
|
63
|
-
const
|
|
65
|
+
const before = readJson(mcpPath); // 解析失败会抛错中止,绝不覆盖(保护 ~/.claude.json)。
|
|
66
|
+
const { settings: mcpMerged, existed } = mergeMcpServers(before, MCP_SERVER_NAME, entry);
|
|
67
|
+
if (global && existsSync(mcpPath)) {
|
|
68
|
+
copyFileSync(mcpPath, `${mcpPath}.bak`);
|
|
69
|
+
}
|
|
64
70
|
writeJson(mcpPath, mcpMerged);
|
|
65
|
-
console.log(`✅ [CC] ${existed ? '更新' : '写入'} MCP server 到 ${mcpPath}`);
|
|
66
|
-
// 2. .claude/settings.json:钩子 + MCP_TOOL_TIMEOUT(略大于 ask
|
|
67
|
-
const settingsPath = resolveSettingsPath(
|
|
71
|
+
console.log(`✅ [CC] ${existed ? '更新' : '写入'} MCP server 到 ${mcpPath}${global ? '(用户级·所有项目可见,已备份 .bak)' : ''}`);
|
|
72
|
+
// 2. .claude/settings.json:钩子 + MCP_TOOL_TIMEOUT(略大于 ask 超时)。
|
|
73
|
+
const settingsPath = resolveSettingsPath(global ? ['--global'] : [], cwd, homedir());
|
|
68
74
|
const command = `node "${permissionHookPath()}"`;
|
|
69
75
|
const hookMerged = mergeHook(readJson(settingsPath), command);
|
|
70
76
|
const tmMerged = ensureMcpToolTimeout(hookMerged.settings, config.timeoutMs + 60_000);
|
|
@@ -160,6 +166,8 @@ async function runInfo() {
|
|
|
160
166
|
console.log('\n【配置 · Claude Code】');
|
|
161
167
|
const mcp = inspectMcpJson(tolerantJson(join(cwd, '.mcp.json')));
|
|
162
168
|
console.log(` .mcp.json (${cwd}):${mcp.configured ? `✅ 已配(command=${mcp.command ?? '?'}, userId=${mask(mcp.userId)})` : '❌ 未配'}`);
|
|
169
|
+
const userMcp = inspectMcpJson(tolerantJson(join(homedir(), '.claude.json')));
|
|
170
|
+
console.log(` 用户级 ~/.claude.json:${userMcp.configured ? `✅ 已配(所有项目可见,userId=${mask(userMcp.userId)})` : '❌ 未配'}`);
|
|
163
171
|
const projSettings = inspectSettingsHook(tolerantJson(join(cwd, '.claude', 'settings.json')));
|
|
164
172
|
// MCP_TOOL_TIMEOUT 可能在 settings.json 或 settings.local.json,两处都看。
|
|
165
173
|
const localTimeout = inspectSettingsHook(tolerantJson(join(cwd, '.claude', 'settings.local.json'))).mcpToolTimeout;
|
|
@@ -200,7 +208,7 @@ function main() {
|
|
|
200
208
|
usage();
|
|
201
209
|
}
|
|
202
210
|
if (cmd === 'install') {
|
|
203
|
-
runInstall(rest);
|
|
211
|
+
void runInstall(rest);
|
|
204
212
|
}
|
|
205
213
|
else if (cmd === 'uninstall') {
|
|
206
214
|
runUninstall(rest);
|
|
@@ -287,16 +295,20 @@ function runUninstall(rest) {
|
|
|
287
295
|
const doCc = host === undefined || host === 'cc' || host === 'both';
|
|
288
296
|
const doCodex = host === 'codex' || host === 'both' || (host === undefined && existsSync(join(homedir(), '.codex')));
|
|
289
297
|
if (doCc) {
|
|
290
|
-
const
|
|
298
|
+
const global = rest.includes('--global');
|
|
299
|
+
const mcpPath = global ? join(homedir(), '.claude.json') : join(cwd, '.mcp.json');
|
|
291
300
|
const mcp = removeMcpServer(tolerantJson(mcpPath), MCP_SERVER_NAME);
|
|
292
301
|
if (mcp.removed) {
|
|
302
|
+
if (global && existsSync(mcpPath)) {
|
|
303
|
+
copyFileSync(mcpPath, `${mcpPath}.bak`);
|
|
304
|
+
}
|
|
293
305
|
writeJson(mcpPath, mcp.settings);
|
|
294
|
-
console.log(`· [CC] 从 ${mcpPath} 移除 MCP server`);
|
|
306
|
+
console.log(`· [CC] 从 ${mcpPath} 移除 MCP server${global ? '(已备份 .bak)' : ''}`);
|
|
295
307
|
}
|
|
296
308
|
else {
|
|
297
309
|
console.log(`· [CC] ${mcpPath} 无 dingtalk-ask,跳过`);
|
|
298
310
|
}
|
|
299
|
-
const settingsPath = resolveSettingsPath(
|
|
311
|
+
const settingsPath = resolveSettingsPath(global ? ['--global'] : [], cwd, homedir());
|
|
300
312
|
const hookRes = removeHook(tolerantJson(settingsPath));
|
|
301
313
|
if (hookRes.removed) {
|
|
302
314
|
writeJson(settingsPath, hookRes.settings);
|
|
@@ -328,23 +340,75 @@ function runUninstall(rest) {
|
|
|
328
340
|
cleanRuntime();
|
|
329
341
|
console.log('\n✅ 已卸载。重启 Claude Code / 重开 Codex 会话后彻底生效。');
|
|
330
342
|
}
|
|
331
|
-
/**
|
|
332
|
-
function
|
|
343
|
+
/** 交互式逐步收集凭证(缺配置时的向导)。返回解析后的 config 与可选 host。 */
|
|
344
|
+
async function promptConfig(env, cwd) {
|
|
345
|
+
const existing = inspectMcpJson(tolerantJson(join(cwd, '.mcp.json')));
|
|
346
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
347
|
+
try {
|
|
348
|
+
console.log('未检测到完整凭证,进入交互式配置(直接回车用【中括号】内默认值):\n');
|
|
349
|
+
const ask = async (label, def) => {
|
|
350
|
+
const a = (await rl.question(` ${label}${def ? ` [${def}]` : ''}: `)).trim();
|
|
351
|
+
return a || def || '';
|
|
352
|
+
};
|
|
353
|
+
const askSecret = async (label, realDef) => {
|
|
354
|
+
const a = (await rl.question(` ${label}${realDef ? ` [${mask(realDef)}]` : ''}: `)).trim();
|
|
355
|
+
return a || realDef || '';
|
|
356
|
+
};
|
|
357
|
+
const clientId = await ask('AppKey (client-id)', env.DINGTALK_CLIENT_ID);
|
|
358
|
+
const clientSecret = await askSecret('AppSecret (client-secret)', env.DINGTALK_CLIENT_SECRET);
|
|
359
|
+
const robotCode = await ask('机器人 robotCode(一般同 AppKey)', env.DINGTALK_ROBOT_CODE ?? clientId);
|
|
360
|
+
const userId = await ask('你的钉钉 userId', env.DINGTALK_USER_ID ?? existing.userId);
|
|
361
|
+
const defaultHost = existsSync(join(homedir(), '.codex')) ? 'both' : 'cc';
|
|
362
|
+
const hostRaw = await ask('装到哪个宿主 cc/codex/both', defaultHost);
|
|
363
|
+
const host = hostRaw === 'cc' || hostRaw === 'codex' || hostRaw === 'both' ? hostRaw : undefined;
|
|
364
|
+
const merged = {
|
|
365
|
+
...env,
|
|
366
|
+
DINGTALK_CLIENT_ID: clientId,
|
|
367
|
+
DINGTALK_CLIENT_SECRET: clientSecret,
|
|
368
|
+
DINGTALK_ROBOT_CODE: robotCode || clientId,
|
|
369
|
+
DINGTALK_USER_ID: userId,
|
|
370
|
+
};
|
|
371
|
+
let config;
|
|
372
|
+
try {
|
|
373
|
+
config = resolveDingtalkConfig(merged);
|
|
374
|
+
}
|
|
375
|
+
catch (error) {
|
|
376
|
+
console.error(`\n凭证不完整:${error instanceof Error ? error.message : String(error)}`);
|
|
377
|
+
process.exit(1);
|
|
378
|
+
}
|
|
379
|
+
return { config, host };
|
|
380
|
+
}
|
|
381
|
+
finally {
|
|
382
|
+
rl.close();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
/** install 子命令:装 MCP + 钩子(缺凭证时进交互式向导)。 */
|
|
386
|
+
async function runInstall(rest) {
|
|
387
|
+
const cwd = process.cwd();
|
|
388
|
+
const env = mergeArgsIntoEnv(process.env, rest);
|
|
333
389
|
let config;
|
|
390
|
+
let interactiveHost;
|
|
334
391
|
try {
|
|
335
|
-
config = resolveDingtalkConfig(
|
|
392
|
+
config = resolveDingtalkConfig(env);
|
|
336
393
|
}
|
|
337
|
-
catch
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
394
|
+
catch {
|
|
395
|
+
// 缺凭证:交互式终端 → 逐步引导输入;非交互(管道/CI)→ 报错提示用参数。
|
|
396
|
+
if (process.stdin.isTTY !== true) {
|
|
397
|
+
console.error('缺少钉钉凭证。请用 --client-id / --client-secret / --robot-code / --user-id 提供,');
|
|
398
|
+
console.error('或在交互式终端直接运行 `dingtalk-ask install` 逐步输入。');
|
|
399
|
+
process.exit(1);
|
|
400
|
+
}
|
|
401
|
+
const r = await promptConfig(env, cwd);
|
|
402
|
+
config = r.config;
|
|
403
|
+
interactiveHost = r.host;
|
|
341
404
|
}
|
|
342
405
|
const nodePath = process.execPath;
|
|
343
406
|
const indexPath = fileURLToPath(new URL('./index.js', import.meta.url));
|
|
344
|
-
// 目标宿主:显式 --host
|
|
345
|
-
const host = parseHost(rest);
|
|
407
|
+
// 目标宿主:显式 --host 优先,其次交互式选择;否则 CC 必装、Codex 存在则一并装。
|
|
408
|
+
const host = parseHost(rest) ?? interactiveHost;
|
|
346
409
|
const doCc = host === undefined || host === 'cc' || host === 'both';
|
|
347
410
|
const doCodex = host === 'codex' || host === 'both' || (host === undefined && existsSync(join(homedir(), '.codex')));
|
|
411
|
+
console.log('');
|
|
348
412
|
if (doCc) {
|
|
349
413
|
installClaudeCode(config, rest, nodePath, indexPath);
|
|
350
414
|
}
|