@agentguardx/helper 0.3.95 → 0.3.97

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.
Files changed (2) hide show
  1. package/lib/wizard.cjs +96 -11
  2. package/package.json +7 -7
package/lib/wizard.cjs CHANGED
@@ -98,18 +98,52 @@ function supportedTools() {
98
98
  }));
99
99
  const apps = (mac, win) => process.platform === 'darwin' ? ['/Applications', path.join(home, 'Applications')].some(dir => fs.existsSync(path.join(dir, mac))) : windows && !!process.env.LOCALAPPDATA && fs.existsSync(path.join(process.env.LOCALAPPDATA, win));
100
100
  return [
101
- { label: 'OpenCode(模型与远程 MCP)', value: 'opencode', present: command('opencode'), install: 'npm i -g opencode-ai' },
101
+ { label: 'OpenCode(模型+远程 MCP+本机安全工具)', value: 'opencode', present: command('opencode'), install: 'npm i -g opencode-ai' },
102
102
  // WorkBuddy:自定义模型写入 ~/.workbuddy/models.json、远程 MCP 写入 mcp.json(WORKBUDDY_CONFIG_DIR 可改目录)。
103
103
  // 以应用包或该配置目录存在为准;Windows 安装目录按 Electron 默认位置查找,未经 Windows 实机核对。
104
- { label: 'WorkBuddy(模型与远程 MCP)', value: 'workbuddy', present: apps('WorkBuddy.app', 'Programs/WorkBuddy/WorkBuddy.exe') || fs.existsSync(process.env.WORKBUDDY_CONFIG_DIR || path.join(home, '.workbuddy')), install: '从 WorkBuddy 官网下载安装并启动一次' },
104
+ { label: 'WorkBuddy(模型+远程 MCP+本机安全工具)', value: 'workbuddy', present: apps('WorkBuddy.app', 'Programs/WorkBuddy/WorkBuddy.exe') || fs.existsSync(process.env.WORKBUDDY_CONFIG_DIR || path.join(home, '.workbuddy')), install: '从 WorkBuddy 官网下载安装并启动一次' },
105
105
  { label: 'OpenClaw(模型)', value: 'openclaw', present: command('openclaw'), install: '按 OpenClaw 官方说明安装' },
106
- { label: 'Cursor(远程 MCP)', value: 'cursor', present: command('cursor') || apps('Cursor.app', 'Programs/Cursor/Cursor.exe'), install: '从 cursor.com 下载安装' },
106
+ { label: 'Cursor(远程 MCP+本机安全工具,需 launch)', value: 'cursor', present: command('cursor') || apps('Cursor.app', 'Programs/Cursor/Cursor.exe'), install: '从 cursor.com 下载安装' },
107
107
  { label: 'Cherry Studio(手动填写说明)', value: 'cherry-studio', present: apps('Cherry Studio.app', 'Programs/Cherry Studio/Cherry Studio.exe'), install: '从 cherry-ai.com 下载安装' },
108
108
  ];
109
109
  }
110
110
  function detectedTools() {
111
111
  return supportedTools().filter(item => item.present);
112
112
  }
113
+ function ownedConnections(root) {
114
+ const directory = path.resolve(root || process.env.AGENTGUARD_CONFIG_DIR || path.join(os.homedir(), '.agentguard'));
115
+ const file = path.join(directory, 'connections.json');
116
+ let stat; try { stat = fs.lstatSync(file); } catch (error) { if (error.code === 'ENOENT') return {}; throw new Error('connection_metadata_unavailable'); }
117
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1048576 || process.platform !== 'win32' && (stat.mode & 0o077)) throw new Error('connection_metadata_invalid');
118
+ const state = JSON.parse(fs.readFileSync(file, 'utf8'));
119
+ if (state.schema_version !== 1 || !state.clients || typeof state.clients !== 'object') throw new Error('connection_metadata_invalid');
120
+ return state.clients;
121
+ }
122
+ function workspacePath(raw, cwd = process.cwd()) {
123
+ const value = fs.realpathSync(path.resolve(cwd, raw.trim() || '.'));
124
+ if (!fs.statSync(value).isDirectory() || value === path.parse(value).root || value === fs.realpathSync(os.homedir()) || /[\x00-\x1f\x7f]/.test(value)) throw new Error('local_workspace_invalid');
125
+ return value;
126
+ }
127
+ async function inDirectory(directory, action) {
128
+ const previous = process.cwd();
129
+ if (directory) process.chdir(directory);
130
+ try { return await action(); } finally { if (directory) process.chdir(previous); }
131
+ }
132
+ function clientSummary(tool, record, verification, doctorPassed, commandCode) {
133
+ const labels = { opencode: 'OpenCode', workbuddy: 'WorkBuddy', openclaw: 'OpenClaw', cursor: 'Cursor', 'cherry-studio': 'Cherry Studio' };
134
+ const lines = [`${labels[tool] || tool}:${commandCode ? '接入命令失败,未自动重试' : verification?.configuration_verified ? '配置已核验' : '配置尚未确认'}`];
135
+ if (record) {
136
+ let origin; try { origin = serverOrigin(record.gateway_url); } catch { lines.push(' 地址记录无效,需修复后再使用。'); return lines; }
137
+ const verified = verification?.configuration_verified ? '已核验' : '记录值,尚未核验';
138
+ if (['opencode', 'workbuddy', 'openclaw'].includes(tool)) lines.push(` 模型地址(${verified}):${origin}${tool === 'workbuddy' ? '/v1/chat/completions' : '/v1'}`);
139
+ if (['opencode', 'workbuddy', 'cursor'].includes(tool)) lines.push(` 远程 MCP(${verified}):${origin}/v1/mcp`);
140
+ if (tool === 'workbuddy') lines.push(` 本机 MCP:agentguard_local;固定受控工作根:${record.workbuddy?.local_workspace || '未确认'}`);
141
+ else if (['opencode', 'cursor'].includes(tool)) lines.push(' 本机 MCP:agentguard_local;工作根为客户端启动目录,实际运行时仍须核对。');
142
+ }
143
+ lines.push(` doctor:${doctorPassed ? '本轮检查通过' : '未全部通过,详见上方实际逐项结果'}`);
144
+ lines.push(' 应用内信任、所选 AgentGuard 模型与真实调用结果仍需确认。');
145
+ return lines;
146
+ }
113
147
  async function currentServer(runtime, prefix, flagServer) {
114
148
  // Priority --server > AGENTGUARD_SERVER > saved configuration is resolved by the Go core.
115
149
  try { return await queryJSON(runtime, [...prefix, ...(flagServer ? ['--server', flagServer] : []), 'server', 'show']); }
@@ -130,6 +164,7 @@ async function init(runtime, args) {
130
164
  }
131
165
  const prefix = root ? ['--config-dir', root] : [];
132
166
  const ask = terminal();
167
+ try {
133
168
  process.stdout.write('AgentGuard 企业接入助手\n');
134
169
  const state = await currentServer(runtime, prefix, flagServer);
135
170
  const fallback = typeof state.server === 'string' ? state.server : '';
@@ -189,7 +224,7 @@ async function init(runtime, args) {
189
224
  process.stdout.write('本机已登录该服务器,沿用现有登录。如需换人,请先执行 agentguard logout。\n');
190
225
  loggedInHere = true;
191
226
  }
192
- if ((process.env.AGENTGUARD_SERVER || '') && process.env.AGENTGUARD_SERVER.replace(/\/+$/, '') !== server) process.stdout.write(`提示:环境变量 AGENTGUARD_SERVER 指向 ${process.env.AGENTGUARD_SERVER},与本次所选服务器不同;后续命令请取消该变量或用 --server 指定。\n`);
227
+ if ((process.env.AGENTGUARD_SERVER || '') && process.env.AGENTGUARD_SERVER.replace(/\/+$/, '') !== server) process.stdout.write(`提示:环境变量 AGENTGUARD_SERVER 指向 ${(() => { try { return serverOrigin(process.env.AGENTGUARD_SERVER); } catch { return '无效地址(已隐藏)'; } })()},与本次所选服务器不同;后续命令请取消该变量或用 --server 指定。\n`);
193
228
  const method = loggedInHere ? 'existing' : await choose(ask, '选择登录方式', choices);
194
229
  if (method === 'existing') code = 0;
195
230
  else if (method === 'token') {
@@ -204,14 +239,64 @@ async function init(runtime, args) {
204
239
  process.stdout.write('以下支持的工具在本机未检测到,安装后运行 npx @agentguardx/helper connect <工具名> 接入:\n');
205
240
  for (const item of missing) process.stdout.write(` - ${item.label}:${item.install}(工具名 ${item.value})\n`);
206
241
  }
242
+ // Persist and revalidate the chosen root through the verified core. Explicit
243
+ // target flags keep a stale shell environment from selecting another server.
244
+ code = await run(runtime, [...prefix, 'server', 'set', server, ...httpFlags], '');
245
+ if (code) { process.exitCode = code; return; }
246
+ const target = [...prefix, '--server', server, ...httpFlags];
247
+ let beforeState;
248
+ try { beforeState = await queryJSON(runtime, [...target, 'status']); }
249
+ catch { process.stdout.write('当前登录未能在线核验,未继续写客户端配置;请先运行 doctor,按实际原因恢复。\n'); process.exitCode = 1; return; }
250
+ if (beforeState.logged_in !== true) { process.stdout.write('当前登录无效,未继续写客户端配置;请正常重新登录。\n'); process.exitCode = 1; return; }
251
+ const beforeClients = new Map((beforeState.clients || []).map(row => [row.client, row]));
252
+ const outcomes = [];
253
+ let selected = [];
207
254
  if (!tools.length) process.stdout.write('本机暂未检测到支持的工具。\n');
208
255
  else {
209
- const selected = await choose(ask, '选择要接入的已检测工具', tools, true);
210
- for (const tool of selected) { code = await run(runtime, [...prefix, 'connect', tool], ''); if (code) { process.exitCode = code; return; } }
211
- if (selected.includes('workbuddy')) process.stdout.write('WorkBuddy 已写入 AgentGuard 模型与远程 MCP:请在 WorkBuddy 的模型选择里选“AgentGuard / …”,首次使用 AgentGuard 连接器时按提示点“信任”。只有选用 AgentGuard 模型的对话和该连接器的调用经过 AgentGuard,WorkBuddy 自带模型、连接器与本机工具不经过 AgentGuard,除非企业在 WorkBuddy 侧关闭。\n');
212
- if (selected.includes('opencode')) process.stdout.write('OpenCode 已设为默认走 AgentGuard(原默认模型已记录,断开时恢复):直接运行 opencode 即可,也可用 launch 启动。接入包含本机安全 MCP 配置,使用 ag_* 工具;实际连接与授权结果以诊断和服务端记录为准。\n');
256
+ selected = await choose(ask, '选择要接入的已检测工具', tools, true);
257
+ for (const tool of selected) {
258
+ if (tool === 'cherry-studio') {
259
+ process.stdout.write(`Cherry Studio 需要在应用内手动填写模型地址 ${server}/v1 和本人 PAT;助手不会显示令牌,尚未确认接入完成。\n`);
260
+ outcomes.push({ tool, code: 1, manual: true }); continue;
261
+ }
262
+ let workspace = '';
263
+ try {
264
+ if (tool === 'workbuddy') {
265
+ const saved = ownedConnections(root).workbuddy?.workbuddy?.local_workspace;
266
+ const current = fs.realpathSync(process.cwd());
267
+ if (saved) { workspace = workspacePath(saved); process.stdout.write(`WorkBuddy 已固定受控工作根:${saved}\n当前目录:${current};本次保留原根。要更换,请先正常 disconnect,再从目标目录接入。\n`); }
268
+ else {
269
+ const typed = await question(ask, `WorkBuddy 本机受控工作目录(回车使用 ${current},请勿选择主目录或系统根):`);
270
+ workspace = workspacePath(typed, current);
271
+ process.stdout.write(`WorkBuddy 将固定受控工作根:${workspace}\n`);
272
+ }
273
+ }
274
+ if (beforeClients.get(tool)?.code === 'local_mcp_reconnect_required') {
275
+ process.stdout.write(`${tool} 的本机桥接需要按当前签名入口重新接入;先按原归属恢复,再配置,用户修改冲突会停止。\n`);
276
+ const restored = await run(runtime, [...target, 'disconnect', tool], '');
277
+ if (restored) { outcomes.push({ tool, code: restored }); continue; }
278
+ }
279
+ const result = await inDirectory(workspace, () => run(runtime, [...target, 'connect', tool], ''));
280
+ outcomes.push({ tool, code: result });
281
+ if (result) process.stdout.write(`${tool} 接入未成功;保留原归属保护,不自动重试,继续汇总其它选中客户端。\n`);
282
+ } catch (error) {
283
+ const reason = /^[a-z0-9_]+$/.test(error.message) ? error.message : 'client_setup_failed';
284
+ outcomes.push({ tool, code: 1 }); process.stdout.write(`${tool} 未完成:${reason}。\n`);
285
+ }
286
+ }
287
+ if (selected.includes('workbuddy')) process.stdout.write('WorkBuddy:在应用内显式选择“AgentGuard / …”,分别信任远程 agentguard 和本机 agentguard_local;若仍缓存旧地址,结束任务后正常重启或重载连接器。宿主自带模型/工具不会因此全部受控。\n');
288
+ if (selected.includes('opencode')) process.stdout.write('OpenCode:直接启动可沿用已配置的默认 AgentGuard 模型;项目或会话覆盖仍需核对,也可按文档使用 launch。\n');
213
289
  }
214
- process.stdout.write('运行实际诊断:\n'); code = await run(runtime, [...prefix, 'doctor'], ''); process.exitCode = code;
215
- process.stdout.write(code === 0 ? '初始化完成。用 npx @agentguardx/helper launch <工具名> 启动;全局安装后也可用 agentguard launch <工具名>。\n' : '诊断未全部通过,请按上述实际结果处理。\n');
290
+ process.stdout.write('运行实际诊断:\n'); const doctorCode = await run(runtime, [...target, 'doctor'], '');
291
+ let verification = {}, records = {};
292
+ try { verification = await queryJSON(runtime, [...target, 'status']); records = ownedConnections(root); }
293
+ catch { process.stdout.write('无法读回完整配置核验状态,不能确认全部接入完成。\n'); }
294
+ const clients = new Map((verification.clients || []).map(row => [row.client, row]));
295
+ process.stdout.write('本次客户端接入结果:\n');
296
+ for (const result of outcomes) for (const line of clientSummary(result.tool, records[result.tool], clients.get(result.tool), doctorCode === 0, result.code)) process.stdout.write(line + '\n');
297
+ const complete = selected.length > 0 && doctorCode === 0 && outcomes.every(row => row.code === 0 && clients.get(row.tool)?.configuration_verified === true);
298
+ process.exitCode = complete ? 0 : doctorCode || (selected.length ? 1 : 0);
299
+ process.stdout.write(complete ? '选中客户端的配置与本轮诊断已通过;请在应用中完成信任和一次真实允许/拒绝调用验证。\n' : selected.length ? '接入尚未全部确认完成,请按每端结果处理;没有自动重试失败操作。\n' : '未选择客户端;已保留本次登录及诊断结果。\n');
300
+ } finally { process.stdin.pause(); if (typeof process.stdin.unref === 'function') process.stdin.unref(); }
216
301
  }
217
- module.exports = { init, serverOrigin, detectedTools };
302
+ module.exports = { init, serverOrigin, detectedTools, supportedTools, workspacePath, clientSummary, ownedConnections };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentguardx/helper",
3
- "version": "0.3.95",
3
+ "version": "0.3.97",
4
4
  "description": "AgentGuard \u5458\u5de5\u63a5\u5165\u52a9\u624b",
5
5
  "license": "UNLICENSED",
6
6
  "engines": {
@@ -19,12 +19,12 @@
19
19
  "preuninstall": "node lib/preuninstall.cjs"
20
20
  },
21
21
  "optionalDependencies": {
22
- "@agentguardx/helper-darwin-x64": "0.3.95",
23
- "@agentguardx/helper-darwin-arm64": "0.3.95",
24
- "@agentguardx/helper-linux-x64": "0.3.95",
25
- "@agentguardx/helper-linux-arm64": "0.3.95",
26
- "@agentguardx/helper-win32-x64": "0.3.95",
27
- "@agentguardx/helper-win32-arm64": "0.3.95"
22
+ "@agentguardx/helper-darwin-x64": "0.3.97",
23
+ "@agentguardx/helper-darwin-arm64": "0.3.97",
24
+ "@agentguardx/helper-linux-x64": "0.3.97",
25
+ "@agentguardx/helper-linux-arm64": "0.3.97",
26
+ "@agentguardx/helper-win32-x64": "0.3.97",
27
+ "@agentguardx/helper-win32-arm64": "0.3.97"
28
28
  },
29
29
  "publishConfig": {
30
30
  "access": "public"