tyc-cli 0.3.8 → 0.3.9

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.
@@ -25,8 +25,7 @@ export function registerCompanyCapabilitiesCommand(companyCmd, program) {
25
25
  }
26
26
  catch (err) {
27
27
  const msg = err instanceof Error ? err.message : String(err);
28
- console.error(`请求失败: ${msg}`);
29
- process.exit(1);
28
+ throw new Error(`请求失败: ${msg}`, { cause: err });
30
29
  }
31
30
  });
32
31
  }
@@ -36,7 +36,7 @@ function bindMethod(catCmd, tool, program) {
36
36
  // 都能直观看到分层信息;徽章不影响 Agent 对 description 的语义理解。
37
37
  let methodCmd = catCmd
38
38
  .command(tool.cliMethod)
39
- .description(`[${tool.layer}] ${tool.description}`);
39
+ .description(`[${tool.layer}] ${tool.description} 调用额度:成功且返回有效结果计 1 次;报错、全部无结果和系统异常计 0 次。`);
40
40
  if (positional) {
41
41
  methodCmd = methodCmd.argument(`<${positional.name}>`, positional.description);
42
42
  }
@@ -63,8 +63,7 @@ function bindMethod(catCmd, tool, program) {
63
63
  }
64
64
  catch (err) {
65
65
  const msg = err instanceof Error ? err.message : String(err);
66
- console.error(`请求失败: ${msg}`);
67
- process.exit(1);
66
+ throw new Error(`请求失败: ${msg}`, { cause: err });
68
67
  }
69
68
  });
70
69
  }
@@ -91,9 +91,7 @@ export function registerInitCommand(program) {
91
91
  }
92
92
  catch (err) {
93
93
  const msg = err instanceof Error ? err.message : String(err);
94
- console.error(`配置校验失败:${msg}`);
95
- console.error("配置已保存;确认 --url 可达且 Authorization 有效后重新运行 tyc init,或加 --no-verify 跳过校验。");
96
- process.exit(1);
94
+ throw new Error(`配置校验失败:${msg}\n配置已保存;确认 --url 可达且 Authorization 有效后重新运行 tyc init,或加 --no-verify 跳过校验。`, { cause: err });
97
95
  }
98
96
  });
99
97
  }
@@ -275,17 +275,15 @@ export function registerLoginCommand(program) {
275
275
  state,
276
276
  timeoutMs: 5 * 60 * 1000,
277
277
  })).redirectUri;
278
- if (!clientId) {
279
- if (!endpoints.registrationEndpoint) {
280
- callback?.close();
281
- throw new Error("OAuth metadata 未提供 registration_endpoint,请用 --client-id 指定预注册客户端。");
282
- }
283
- if (verbose) {
284
- console.error("> registering OAuth client via Dynamic Client Registration");
285
- }
286
- let registeredClient;
287
- try {
288
- registeredClient = await registerDynamicOAuthClient({
278
+ try {
279
+ if (!clientId) {
280
+ if (!endpoints.registrationEndpoint) {
281
+ throw new Error("OAuth metadata 未提供 registration_endpoint,请用 --client-id 指定预注册客户端。");
282
+ }
283
+ if (verbose) {
284
+ console.error("> registering OAuth client via Dynamic Client Registration");
285
+ }
286
+ const registeredClient = await registerDynamicOAuthClient({
289
287
  registrationEndpoint: endpoints.registrationEndpoint,
290
288
  clientName: "tyc-cli",
291
289
  redirectUris: [redirectUri],
@@ -295,97 +293,96 @@ export function registerLoginCommand(program) {
295
293
  responseTypes: ["code"],
296
294
  tokenEndpointAuthMethod: "none",
297
295
  });
296
+ clientId = registeredClient.clientId;
297
+ clientSecret ||= registeredClient.clientSecret;
298
+ if (verbose) {
299
+ console.error(`> registered client_id: ${clientId}`);
300
+ }
298
301
  }
299
- catch (err) {
300
- callback?.close();
301
- throw err;
302
+ if (noBlock) {
303
+ if (!endpoints.deviceAuthorizationEndpoint) {
304
+ throw new Error("OAuth metadata 未提供 device_authorization_endpoint,请升级授权服务或使用 --device-authorization-endpoint 指定。");
305
+ }
306
+ const device = await requestDeviceAuthorization({
307
+ deviceAuthorizationEndpoint: endpoints.deviceAuthorizationEndpoint,
308
+ clientId,
309
+ clientSecret,
310
+ scope,
311
+ resource,
312
+ });
313
+ savePendingOAuthLogin({
314
+ version: 2,
315
+ flow: "device",
316
+ createdAt: Date.now(),
317
+ mcpURL,
318
+ tokenEndpoint: endpoints.tokenEndpoint,
319
+ clientId,
320
+ clientSecret,
321
+ deviceCode: device.device_code,
322
+ userCode: device.user_code,
323
+ verificationUri: device.verification_uri,
324
+ expiresAt: Date.now() + device.expires_in * 1000,
325
+ interval: device.interval || 5,
326
+ resource,
327
+ scope,
328
+ verify: opts.verify !== false,
329
+ });
330
+ console.error("请在浏览器打开以下 URL 完成登录:");
331
+ console.error(device.verification_uri);
332
+ console.error("");
333
+ console.error(`验证码:${device.user_code}`);
334
+ console.error("");
335
+ console.error("在网页输入 6 位验证码并确认授权后,运行:");
336
+ console.error("tyc login --resume");
337
+ return;
302
338
  }
303
- clientId = registeredClient.clientId;
304
- clientSecret ||= registeredClient.clientSecret;
339
+ const authorizationURL = buildAuthorizationURL({
340
+ authorizationEndpoint: endpoints.authorizationEndpoint,
341
+ clientId,
342
+ redirectUri,
343
+ scope,
344
+ state,
345
+ codeChallenge: pkce.codeChallenge,
346
+ resource,
347
+ });
305
348
  if (verbose) {
306
- console.error(`> registered client_id: ${clientId}`);
349
+ console.error(`> OAuth callback: ${redirectUri}`);
307
350
  }
308
- }
309
- if (noBlock) {
310
- if (!endpoints.deviceAuthorizationEndpoint) {
311
- throw new Error("OAuth metadata 未提供 device_authorization_endpoint,请升级授权服务或使用 --device-authorization-endpoint 指定。");
351
+ if (opts.open === false) {
352
+ console.error("请在浏览器打开以下 URL 完成登录:");
353
+ console.error(authorizationURL);
354
+ }
355
+ else {
356
+ console.error("正在打开浏览器完成 OAuth 登录...");
357
+ openBrowser(authorizationURL);
358
+ }
359
+ if (!callback) {
360
+ throw new Error("OAuth callback server not initialized");
361
+ }
362
+ const code = await callback.waitForCode();
363
+ if (verbose) {
364
+ console.error("> authorization code received; exchanging access token");
312
365
  }
313
- const device = await requestDeviceAuthorization({
314
- deviceAuthorizationEndpoint: endpoints.deviceAuthorizationEndpoint,
366
+ const token = await exchangeAuthorizationCode({
367
+ tokenEndpoint: endpoints.tokenEndpoint,
315
368
  clientId,
316
369
  clientSecret,
317
- scope,
370
+ code,
371
+ redirectUri,
372
+ codeVerifier: pkce.codeVerifier,
318
373
  resource,
319
374
  });
320
- savePendingOAuthLogin({
321
- version: 2,
322
- flow: "device",
323
- createdAt: Date.now(),
324
- mcpURL,
375
+ saveOAuthTokenConfig(mcpURL, token, {
325
376
  tokenEndpoint: endpoints.tokenEndpoint,
326
377
  clientId,
327
378
  clientSecret,
328
- deviceCode: device.device_code,
329
- userCode: device.user_code,
330
- verificationUri: device.verification_uri,
331
- expiresAt: Date.now() + device.expires_in * 1000,
332
- interval: device.interval || 5,
333
379
  resource,
334
- scope,
335
- verify: opts.verify !== false,
336
- });
337
- console.error("请在浏览器打开以下 URL 完成登录:");
338
- console.error(device.verification_uri);
339
- console.error("");
340
- console.error(`验证码:${device.user_code}`);
341
- console.error("");
342
- console.error("在网页输入 6 位验证码并确认授权后,运行:");
343
- console.error("tyc login --resume");
344
- return;
345
- }
346
- const authorizationURL = buildAuthorizationURL({
347
- authorizationEndpoint: endpoints.authorizationEndpoint,
348
- clientId,
349
- redirectUri,
350
- scope,
351
- state,
352
- codeChallenge: pkce.codeChallenge,
353
- resource,
354
- });
355
- if (verbose) {
356
- console.error(`> OAuth callback: ${redirectUri}`);
357
- }
358
- if (opts.open === false) {
359
- console.error("请在浏览器打开以下 URL 完成登录:");
360
- console.error(authorizationURL);
361
- }
362
- else {
363
- console.error("正在打开浏览器完成 OAuth 登录...");
364
- openBrowser(authorizationURL);
380
+ }, verbose);
381
+ clearPendingOAuthLogin(verbose);
382
+ await verifySavedOAuthLogin(opts.verify !== false, verbose);
365
383
  }
366
- if (!callback) {
367
- throw new Error("OAuth callback server not initialized");
384
+ finally {
385
+ callback?.close();
368
386
  }
369
- const code = await callback.waitForCode();
370
- if (verbose) {
371
- console.error("> authorization code received; exchanging access token");
372
- }
373
- const token = await exchangeAuthorizationCode({
374
- tokenEndpoint: endpoints.tokenEndpoint,
375
- clientId,
376
- clientSecret,
377
- code,
378
- redirectUri,
379
- codeVerifier: pkce.codeVerifier,
380
- resource,
381
- });
382
- saveOAuthTokenConfig(mcpURL, token, {
383
- tokenEndpoint: endpoints.tokenEndpoint,
384
- clientId,
385
- clientSecret,
386
- resource,
387
- }, verbose);
388
- clearPendingOAuthLogin(verbose);
389
- await verifySavedOAuthLogin(opts.verify !== false, verbose);
390
387
  });
391
388
  }
@@ -3,13 +3,11 @@ import { applyTruncation, resolveOptionalInt, TRUNCATE_DEFAULTS, } from "../util
3
3
  export function emitToolResult(result, opts, toolName) {
4
4
  if (result.isError) {
5
5
  const errText = extractText(result) || "未知业务错误";
6
- console.error(errText);
7
- process.exit(1);
6
+ throw new Error(errText);
8
7
  }
9
8
  const text = extractText(result);
10
9
  if (text === null) {
11
- console.error("tools/call 返回无可解析内容");
12
- process.exit(1);
10
+ throw new Error("tools/call 返回无可解析内容");
13
11
  }
14
12
  let payload;
15
13
  try {
package/dist/config.js CHANGED
@@ -109,8 +109,7 @@ export function resolveConfig() {
109
109
  headers.Authorization = process.env.TYC_AUTHORIZATION;
110
110
  }
111
111
  if (!headers.Authorization) {
112
- console.error("未配置 Authorization。请先运行:tyc init --authorization YOUR_API_KEY");
113
- process.exit(1);
112
+ throw new Error("未配置 Authorization。请先运行:tyc init --authorization YOUR_API_KEY");
114
113
  }
115
114
  return { url, coreUrl, headers, oauth: cfg.oauth ? { ...cfg.oauth } : undefined };
116
115
  }
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { Command } from "commander";
2
+ import { Command, CommanderError } from "commander";
3
3
  import { registerInitCommand } from "./commands/init.js";
4
4
  import { registerLoginCommand } from "./commands/login.js";
5
5
  import { registerCategoryCommands } from "./commands/category.js";
@@ -93,6 +93,7 @@ function renderPriorityLines() {
93
93
  return lines.join("\n");
94
94
  }
95
95
  const program = new Command()
96
+ .exitOverride()
96
97
  .name("tyc")
97
98
  .description(`天眼查 AI CLI:${getTotalCount()} 个商业数据工具 · L0=${getLayerCount("L0")} L1=${getLayerCount("L1")} L2=${getLayerCount("L2")} L3=${getLayerCount("L3")} · 面向 Agent 的 MCP 工具网关`)
98
99
  .version(VERSION, "-V, --version", "输出版本号")
@@ -125,7 +126,14 @@ registerLoginCommand(program);
125
126
  registerLayerCommands(program);
126
127
  registerCategoryCommands(program);
127
128
  program.parseAsync(process.argv).catch((err) => {
129
+ // Commander has already printed help/version/argument errors. Preserve its
130
+ // exit status without printing twice or interrupting pending stream writes.
131
+ if (err instanceof CommanderError) {
132
+ process.exitCode = err.exitCode;
133
+ return;
134
+ }
128
135
  const msg = err instanceof Error ? err.message : String(err);
129
136
  console.error(msg);
130
- process.exit(1);
137
+ // Let Node finish asynchronous I/O and teardown (including fetch on Windows).
138
+ process.exitCode = 1;
131
139
  });
package/dist/oauth.js CHANGED
@@ -208,13 +208,20 @@ export async function startCallbackServer(opts) {
208
208
  let server;
209
209
  let settled = false;
210
210
  let timer;
211
+ const close = () => {
212
+ settled = true;
213
+ if (timer)
214
+ clearTimeout(timer);
215
+ server?.close();
216
+ server?.closeIdleConnections?.();
217
+ };
211
218
  const codePromise = new Promise((resolve, reject) => {
212
219
  timer = setTimeout(() => {
213
220
  if (settled)
214
221
  return;
215
222
  settled = true;
216
223
  reject(new Error("等待 OAuth 回调超时"));
217
- server?.close();
224
+ close();
218
225
  }, opts.timeoutMs);
219
226
  server = createServer((req, res) => {
220
227
  const url = new URL(req.url || "/", `http://${req.headers.host || opts.host}`);
@@ -237,7 +244,7 @@ export async function startCallbackServer(opts) {
237
244
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
238
245
  res.end(callbackHTML("OAuth 登录失败", "state 校验失败,请回到终端重新运行 tyc login。"));
239
246
  reject(new Error("OAuth state mismatch"));
240
- server?.close();
247
+ close();
241
248
  return;
242
249
  }
243
250
  if (error) {
@@ -245,7 +252,7 @@ export async function startCallbackServer(opts) {
245
252
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
246
253
  res.end(callbackHTML("OAuth 登录失败", errorDescription || error));
247
254
  reject(new Error(`OAuth error: ${errorDescription || error}`));
248
- server?.close();
255
+ close();
249
256
  return;
250
257
  }
251
258
  if (!code) {
@@ -253,49 +260,52 @@ export async function startCallbackServer(opts) {
253
260
  res.writeHead(400, { "Content-Type": "text/html; charset=utf-8" });
254
261
  res.end(callbackHTML("OAuth 登录失败", "授权服务未返回 code。"));
255
262
  reject(new Error("OAuth callback missing code"));
256
- server?.close();
263
+ close();
257
264
  return;
258
265
  }
259
266
  settled = true;
260
267
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
261
268
  res.end(callbackHTML("OAuth 登录成功", "tyc CLI 已收到授权结果,可以关闭这个窗口。"));
262
269
  resolve(code);
263
- server?.close();
270
+ close();
264
271
  });
265
272
  });
266
- await new Promise((resolve, reject) => {
267
- if (!server) {
268
- reject(new Error("callback server not initialized"));
269
- return;
270
- }
271
- const onError = (err) => reject(err);
272
- server.once("error", onError);
273
- server.listen(opts.port, opts.host, () => {
274
- server?.off("error", onError);
275
- resolve();
273
+ // Registration/browser setup happens before waitForCode(). Keep an early
274
+ // callback rejection handled while preserving it for that eventual await.
275
+ void codePromise.catch(() => { });
276
+ try {
277
+ await new Promise((resolve, reject) => {
278
+ if (!server) {
279
+ reject(new Error("callback server not initialized"));
280
+ return;
281
+ }
282
+ const onError = (err) => reject(err);
283
+ server.once("error", onError);
284
+ server.listen(opts.port, opts.host, () => {
285
+ server?.off("error", onError);
286
+ resolve();
287
+ });
276
288
  });
277
- });
289
+ }
290
+ catch (err) {
291
+ close();
292
+ throw err;
293
+ }
278
294
  const address = server?.address();
279
295
  if (!address || typeof address.port !== "number") {
280
- server?.close();
296
+ close();
281
297
  throw new Error("无法启动 OAuth 本地回调服务");
282
298
  }
283
299
  const redirectUri = `http://${opts.host}:${address.port}${opts.path}`;
284
300
  return {
285
301
  redirectUri,
286
- close: () => {
287
- if (timer)
288
- clearTimeout(timer);
289
- server?.close();
290
- },
302
+ close,
291
303
  waitForCode: async () => {
292
304
  try {
293
305
  return await codePromise;
294
306
  }
295
307
  finally {
296
- if (timer)
297
- clearTimeout(timer);
298
- server?.close();
308
+ close();
299
309
  }
300
310
  },
301
311
  };
package/dist/registry.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // 从 src/catalog.json 加载命令树元数据
2
2
  //
3
3
  // catalog.json 由 apimcp 主仓 scripts/gen_cli_catalog 生成;仅包含 CLI
4
- // 需要的字段(name / group / layer / cliMethod / categoryNameZh /
4
+ // 需要的字段(name / group / layer / requiredPlan / cliMethod / categoryNameZh /
5
5
  // description / params)。不含 sources / execution / summary 等服务端
6
6
  // 内部映射,服务端实现细节不泄露。
7
7
  import { readFileSync } from "node:fs";
@@ -0,0 +1,68 @@
1
+ const object = (v) => v !== null && typeof v === "object" && !Array.isArray(v);
2
+ const text = (v) => typeof v === "string" ? v : JSON.stringify(v) ?? "null";
3
+ const cell = (v) => text(v).replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
4
+ export function renderHistoryGuide(value) {
5
+ if (!object(value))
6
+ return [];
7
+ const lines = [`> 查询范围:${cell(value.scope)}`];
8
+ if (Array.isArray(value.nextTools))
9
+ for (const next of value.nextTools) {
10
+ if (object(next))
11
+ lines.push(`> 后续查询:\`${cell(next.toolName)}\` — ${cell(next.purpose)}`);
12
+ }
13
+ if (Array.isArray(value.notes))
14
+ for (const note of value.notes)
15
+ lines.push(`> 历史查询说明:${cell(note)}`);
16
+ return lines;
17
+ }
18
+ export function splitHistoricalPersonnel(obj, toolName) {
19
+ if (toolName !== "get_historical_registration" || !object(obj.sources) || !object(obj.sources.hi))
20
+ return undefined;
21
+ const hi = { ...obj.sources.hi };
22
+ const lines = [];
23
+ for (const [key, title] of [["pastLegalPersonList", "历史法定代表人"], ["pastStafferList", "历史主要人员"]]) {
24
+ if (!(key in hi))
25
+ continue;
26
+ const value = hi[key];
27
+ delete hi[key];
28
+ lines.push(`### ${title}\n`, "> 按来源分组完整展示;组号不代表任职阶段。\n");
29
+ let groups = Array.isArray(value) ? value : [value];
30
+ if (groups.length === 0)
31
+ lines.push("_(空列表)_\n");
32
+ if (groups.length && groups.every(object)) {
33
+ lines.push("单层名单:\n");
34
+ groups = [groups];
35
+ }
36
+ groups.forEach((group, i) => {
37
+ const people = Array.isArray(group) ? group : [group];
38
+ lines.push(`#### 第 ${i + 1} 组(${people.length} 项)\n`);
39
+ people.forEach((person, j) => {
40
+ lines.push(`人员 ${j + 1}:\n`, "| 字段 | 值 |", "|---|---|");
41
+ if (object(person))
42
+ for (const field of Object.keys(person).sort()) {
43
+ const label = field === "name" ? "名称" : field === "time" ? "变更时间" : field;
44
+ lines.push(`| ${cell(label)} | ${cell(person[field])} |`);
45
+ }
46
+ else
47
+ lines.push(`| 原始项 | ${cell(person)} |`);
48
+ lines.push("");
49
+ });
50
+ });
51
+ }
52
+ return lines.length ? { rest: { ...obj, sources: { ...obj.sources, hi } }, markdown: lines.join("\n") } : undefined;
53
+ }
54
+ function changeDate(value) {
55
+ if (!object(value) || typeof value.changeTime !== "string")
56
+ return "";
57
+ const match = /^(\d{4}-\d{2}-\d{2})(?:$|[ T])/.exec(value.changeTime);
58
+ if (!match)
59
+ return "";
60
+ const day = new Date(`${match[1]}T00:00:00Z`);
61
+ return !Number.isNaN(day.getTime()) && day.toISOString().slice(0, 10) === match[1] ? match[1] : "";
62
+ }
63
+ export function changeRecordsForDisplay(records) {
64
+ return [...records].sort((a, b) => {
65
+ const x = changeDate(a), y = changeDate(b);
66
+ return x < y ? 1 : x > y ? -1 : 0;
67
+ });
68
+ }