dingtalk-ask-mcp-server 0.1.1 → 0.1.2
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 +1 -0
- package/dist/installCli.js +114 -31
- package/dist/installConfig.js +26 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -138,6 +138,7 @@ Codex 若 `approval_policy="never"` 本就不弹命令审批,故只提供 `ask_h
|
|
|
138
138
|
|
|
139
139
|
```bash
|
|
140
140
|
dingtalk-ask install … # 一键接入(见上)
|
|
141
|
+
dingtalk-ask info # 查看配置与进程状态(守护进程/离开模式/各宿主是否配好)
|
|
141
142
|
dingtalk-ask daemon status|stop # 查看/停止共享守护进程
|
|
142
143
|
dingtalk-ask away on|off|status # 本地切换离开模式
|
|
143
144
|
dingtalk-ask clean # 清空运行时状态(守护进程/锁/flag/就绪标记/日志),不动配置文件
|
package/dist/installCli.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* 写入均为宿主本地配置(.mcp.json / .claude/settings.json / ~/.codex/config.toml,皆不入库),
|
|
11
11
|
* 合并保留既有内容、幂等。纯逻辑在 installConfig.ts / installHookCore.ts。
|
|
12
12
|
*/
|
|
13
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, unlinkSync, readdirSync } from 'node:fs';
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, unlinkSync, readdirSync, statSync } from 'node:fs';
|
|
14
14
|
import { homedir, tmpdir } from 'node:os';
|
|
15
15
|
import { join, dirname } from 'node:path';
|
|
16
16
|
import { fileURLToPath } from 'node:url';
|
|
@@ -21,7 +21,7 @@ import { mergeHook, permissionHookPath, resolveSettingsPath } from './installHoo
|
|
|
21
21
|
import { daemonLockPath, readDaemonLock, removeDaemonLock } from './daemon/lock.js';
|
|
22
22
|
import { probeHealth } from './daemonClient.js';
|
|
23
23
|
import { isAway, setAway, awayFlagPath } from './awayState.js';
|
|
24
|
-
import { buildMcpEntry, mergeMcpServers, ensureMcpToolTimeout, codexHasDingtalk, buildCodexBlock, MCP_SERVER_NAME, } from './installConfig.js';
|
|
24
|
+
import { buildMcpEntry, mergeMcpServers, ensureMcpToolTimeout, codexHasDingtalk, buildCodexBlock, inspectMcpJson, inspectSettingsHook, MCP_SERVER_NAME, } from './installConfig.js';
|
|
25
25
|
/** 解析目标宿主。默认 CC;Codex 见 resolveCodexTarget。 */
|
|
26
26
|
function parseHost(argv) {
|
|
27
27
|
const i = argv.indexOf('--host');
|
|
@@ -101,12 +101,123 @@ function usage() {
|
|
|
101
101
|
console.log(' [--timeout <ms>] [--host cc|codex|both] # 一键装 MCP+钩子');
|
|
102
102
|
console.log(' dingtalk-ask daemon <status|stop> # 查看/停止共享守护进程');
|
|
103
103
|
console.log(' dingtalk-ask away <on|off|status> # 本地切换离开模式');
|
|
104
|
+
console.log(' dingtalk-ask info # 查看配置与进程状态');
|
|
104
105
|
console.log(' dingtalk-ask clean # 清空运行时状态(守护进程/锁/flag/标记/日志)');
|
|
105
106
|
console.log(' dingtalk-ask -v | --version # 版本');
|
|
106
107
|
console.log(' dingtalk-ask -h | --help # 帮助');
|
|
107
108
|
process.exit(1);
|
|
108
109
|
}
|
|
109
|
-
/**
|
|
110
|
+
/** 宽松读 JSON:文件缺失/损坏都返回空对象(info 用,不抛错)。 */
|
|
111
|
+
function tolerantJson(path) {
|
|
112
|
+
try {
|
|
113
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/** 轻度掩码(展示 userId 等,不完全暴露)。 */
|
|
120
|
+
function mask(v) {
|
|
121
|
+
if (v === undefined || v === '') {
|
|
122
|
+
return '(未知)';
|
|
123
|
+
}
|
|
124
|
+
return v.length <= 4 ? '***' : `${v.slice(0, 3)}***${v.slice(-2)}`;
|
|
125
|
+
}
|
|
126
|
+
/** info 子命令:一屏看配置与进程状态。 */
|
|
127
|
+
async function runInfo() {
|
|
128
|
+
const tmp = tmpdir();
|
|
129
|
+
const cwd = process.cwd();
|
|
130
|
+
console.log(`dingtalk-ask v${packageVersion()}`);
|
|
131
|
+
console.log('\n【进程】');
|
|
132
|
+
const lock = readDaemonLock(daemonLockPath());
|
|
133
|
+
if (lock === undefined) {
|
|
134
|
+
console.log(' 守护进程:未运行');
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
const probe = await probeHealth(lock);
|
|
138
|
+
console.log(` 守护进程:${probe.ok ? '在跑' : '锁存在但无应答'} port=${lock.port} pid=${lock.pid} 就绪=${probe.ready}`);
|
|
139
|
+
}
|
|
140
|
+
console.log(` 离开模式:${isAway() ? '开(推钉钉)' : '关(只在终端)'}`);
|
|
141
|
+
let greeted = [];
|
|
142
|
+
try {
|
|
143
|
+
greeted = readdirSync(tmp)
|
|
144
|
+
.filter((f) => f.startsWith('dingtalk-greeted-'))
|
|
145
|
+
.map((f) => f.replace(/^dingtalk-greeted-/, '').replace(/\.flag$/, ''));
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
/* 忽略 */
|
|
149
|
+
}
|
|
150
|
+
console.log(` 已发就绪提示的机器人:${greeted.length > 0 ? greeted.join('、') : '无'}`);
|
|
151
|
+
const logPath = join(tmp, 'dingtalk-daemon.log');
|
|
152
|
+
try {
|
|
153
|
+
const st = statSync(logPath);
|
|
154
|
+
console.log(` 守护进程日志:${logPath}(${Math.round(st.size / 1024)}KB)`);
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
console.log(' 守护进程日志:无');
|
|
158
|
+
}
|
|
159
|
+
console.log('\n【配置 · Claude Code】');
|
|
160
|
+
const mcp = inspectMcpJson(tolerantJson(join(cwd, '.mcp.json')));
|
|
161
|
+
console.log(` .mcp.json (${cwd}):${mcp.configured ? `✅ 已配(command=${mcp.command ?? '?'}, userId=${mask(mcp.userId)})` : '❌ 未配'}`);
|
|
162
|
+
const projSettings = inspectSettingsHook(tolerantJson(join(cwd, '.claude', 'settings.json')));
|
|
163
|
+
// MCP_TOOL_TIMEOUT 可能在 settings.json 或 settings.local.json,两处都看。
|
|
164
|
+
const localTimeout = inspectSettingsHook(tolerantJson(join(cwd, '.claude', 'settings.local.json'))).mcpToolTimeout;
|
|
165
|
+
const mcpToolTimeout = projSettings.mcpToolTimeout ?? localTimeout;
|
|
166
|
+
console.log(` 项目 settings.json:命令审批钩子 ${projSettings.hook ? '✅' : '❌'}` +
|
|
167
|
+
`,MCP_TOOL_TIMEOUT=${mcpToolTimeout ?? '未设'}`);
|
|
168
|
+
const userSettings = inspectSettingsHook(tolerantJson(join(homedir(), '.claude', 'settings.json')));
|
|
169
|
+
console.log(` 用户 settings.json (~):命令审批钩子 ${userSettings.hook ? '✅' : '❌'}`);
|
|
170
|
+
console.log('\n【配置 · Codex】');
|
|
171
|
+
const codexPath = join(homedir(), '.codex', 'config.toml');
|
|
172
|
+
let codexContent = '';
|
|
173
|
+
try {
|
|
174
|
+
codexContent = readFileSync(codexPath, 'utf8');
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
codexContent = '';
|
|
178
|
+
}
|
|
179
|
+
if (codexContent === '') {
|
|
180
|
+
console.log(` ${codexPath}:无文件(未用 Codex?)`);
|
|
181
|
+
}
|
|
182
|
+
else if (codexHasDingtalk(codexContent)) {
|
|
183
|
+
const m = /\[mcp_servers\.dingtalk-ask\][\s\S]*?tool_timeout_sec\s*=\s*(\d+)/.exec(codexContent);
|
|
184
|
+
console.log(` ${codexPath}:✅ 已配(tool_timeout_sec=${m ? m[1] : '?'})`);
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
console.log(` ${codexPath}:❌ 未配 dingtalk-ask`);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function main() {
|
|
191
|
+
const args = process.argv.slice(2);
|
|
192
|
+
const cmd = args[0];
|
|
193
|
+
const rest = args.slice(1);
|
|
194
|
+
if (cmd === '-v' || cmd === '--version') {
|
|
195
|
+
console.log(packageVersion());
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (cmd === '-h' || cmd === '--help') {
|
|
199
|
+
usage();
|
|
200
|
+
}
|
|
201
|
+
if (cmd === 'install') {
|
|
202
|
+
runInstall(rest);
|
|
203
|
+
}
|
|
204
|
+
else if (cmd === 'daemon') {
|
|
205
|
+
void runDaemon(rest);
|
|
206
|
+
}
|
|
207
|
+
else if (cmd === 'away') {
|
|
208
|
+
runAway(rest);
|
|
209
|
+
}
|
|
210
|
+
else if (cmd === 'info') {
|
|
211
|
+
void runInfo();
|
|
212
|
+
}
|
|
213
|
+
else if (cmd === 'clean') {
|
|
214
|
+
runClean();
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
usage();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
main();
|
|
110
221
|
function runClean() {
|
|
111
222
|
const tmp = tmpdir();
|
|
112
223
|
// 1. 停守护进程 + 删锁。
|
|
@@ -160,34 +271,6 @@ function runClean() {
|
|
|
160
271
|
}
|
|
161
272
|
console.log('✅ 已清空运行时状态。配置文件(.mcp.json / settings.json / config.toml)未改动。');
|
|
162
273
|
}
|
|
163
|
-
function main() {
|
|
164
|
-
const args = process.argv.slice(2);
|
|
165
|
-
const cmd = args[0];
|
|
166
|
-
const rest = args.slice(1);
|
|
167
|
-
if (cmd === '-v' || cmd === '--version') {
|
|
168
|
-
console.log(packageVersion());
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
if (cmd === '-h' || cmd === '--help') {
|
|
172
|
-
usage();
|
|
173
|
-
}
|
|
174
|
-
if (cmd === 'install') {
|
|
175
|
-
runInstall(rest);
|
|
176
|
-
}
|
|
177
|
-
else if (cmd === 'daemon') {
|
|
178
|
-
void runDaemon(rest);
|
|
179
|
-
}
|
|
180
|
-
else if (cmd === 'away') {
|
|
181
|
-
runAway(rest);
|
|
182
|
-
}
|
|
183
|
-
else if (cmd === 'clean') {
|
|
184
|
-
runClean();
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
usage();
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
main();
|
|
191
274
|
/** install 子命令:装 MCP + 钩子。 */
|
|
192
275
|
function runInstall(rest) {
|
|
193
276
|
let config;
|
package/dist/installConfig.js
CHANGED
|
@@ -54,6 +54,32 @@ export function ensureMcpToolTimeout(settings, ms) {
|
|
|
54
54
|
export function codexHasDingtalk(content) {
|
|
55
55
|
return content.includes(`[mcp_servers.${MCP_SERVER_NAME}]`);
|
|
56
56
|
}
|
|
57
|
+
/** 检查 .mcp.json 是否配了 dingtalk-ask,并抽取 command / userId 供展示(纯函数)。 */
|
|
58
|
+
export function inspectMcpJson(obj) {
|
|
59
|
+
const servers = obj.mcpServers;
|
|
60
|
+
const s = servers?.[MCP_SERVER_NAME];
|
|
61
|
+
if (s === undefined) {
|
|
62
|
+
return { configured: false };
|
|
63
|
+
}
|
|
64
|
+
let userId = s.env?.DINGTALK_USER_ID;
|
|
65
|
+
if (userId === undefined && Array.isArray(s.args)) {
|
|
66
|
+
const i = s.args.indexOf('--user-id');
|
|
67
|
+
if (i >= 0) {
|
|
68
|
+
userId = s.args[i + 1];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return { configured: true, command: s.command, userId };
|
|
72
|
+
}
|
|
73
|
+
/** 检查 settings.json 是否装了命令审批钩子、以及 MCP_TOOL_TIMEOUT(纯函数)。 */
|
|
74
|
+
export function inspectSettingsHook(obj) {
|
|
75
|
+
const hooks = obj.hooks;
|
|
76
|
+
const list = hooks?.PermissionRequest;
|
|
77
|
+
const hook = Array.isArray(list) &&
|
|
78
|
+
list.some((e) => Array.isArray(e.hooks) && e.hooks.some((h) => typeof h.command === 'string' && h.command.includes('permissionHook.js')));
|
|
79
|
+
const env = obj.env;
|
|
80
|
+
const t = env?.MCP_TOOL_TIMEOUT;
|
|
81
|
+
return { hook, mcpToolTimeout: typeof t === 'string' ? t : undefined };
|
|
82
|
+
}
|
|
57
83
|
/**
|
|
58
84
|
* 构造 Codex config.toml 追加块(TOML 基本字符串转义,无需 TOML 库)。
|
|
59
85
|
*
|