tyc-cli 0.3.4 → 0.3.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/dist/config.js CHANGED
@@ -1,17 +1,20 @@
1
1
  // ~/.tyc/config.json 读写
2
2
  //
3
- // 配置文件格式与通用 MCP 客户端保持一致:
4
- // { "url": "https://mcp.tianyancha.com/v1",
5
- // "headers": { "Authorization": "xxxx" } }
3
+ // 配置文件格式:
4
+ // { "url": "https://mcp.tianyancha.com/mcp",
5
+ // "headers": { "Authorization": "xxxx" },
6
+ // "coreUrl": "https://mcp.tianyancha.com/v1/core/tools/call" }
6
7
  //
7
- // 默认端点指向天眼查公网 MCP Server;连接本地或私有环境可通过
8
- // tyc init --url http://localhost:8080/v1 或 TYC_MCP_ENDPOINT 环境变量覆盖。
8
+ // 默认端点指向天眼查公网 MCP Server 的 canonical 入口 /mcp,CLI 实际调用
9
+ // shared core REST 端点;连接本地或私有环境可通过
10
+ // tyc init --url http://localhost:8080/mcp 或 TYC_MCP_ENDPOINT 环境变量覆盖。
9
11
  import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "node:fs";
10
12
  import { join } from "node:path";
11
13
  import { homedir } from "node:os";
14
+ import { exchangeRefreshToken } from "./oauth.js";
12
15
  const CONFIG_DIR = join(homedir(), ".tyc");
13
16
  const CONFIG_FILE = join(CONFIG_DIR, "config.json");
14
- export const DEFAULT_MCP_URL = "https://mcp.tianyancha.com/v1";
17
+ export const DEFAULT_MCP_URL = "https://mcp.tianyancha.com/mcp";
15
18
  export function loadConfig() {
16
19
  if (!existsSync(CONFIG_FILE))
17
20
  return null;
@@ -32,9 +35,75 @@ export function saveConfig(config) {
32
35
  }
33
36
  catch { /* best effort */ }
34
37
  }
38
+ const OAUTH_REFRESH_SKEW_MS = 60 * 1000;
39
+ function normalizeTokenType(raw) {
40
+ if (!raw)
41
+ return "Bearer";
42
+ return raw.toLowerCase() === "bearer" ? "Bearer" : raw;
43
+ }
44
+ function jwtExpiresAt(accessToken) {
45
+ const parts = accessToken.split(".");
46
+ if (parts.length < 2)
47
+ return undefined;
48
+ try {
49
+ const normalized = parts[1].replace(/-/g, "+").replace(/_/g, "/");
50
+ const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
51
+ const payload = JSON.parse(Buffer.from(padded, "base64").toString("utf-8"));
52
+ if (typeof payload.exp === "number" && Number.isFinite(payload.exp)) {
53
+ return payload.exp * 1000;
54
+ }
55
+ }
56
+ catch {
57
+ // Non-JWT access tokens are valid OAuth responses too; expires_in remains authoritative.
58
+ }
59
+ return undefined;
60
+ }
61
+ export function inferAccessTokenExpiresAt(token, now = Date.now()) {
62
+ if (typeof token.expires_in === "number" && Number.isFinite(token.expires_in) && token.expires_in > 0) {
63
+ return now + token.expires_in * 1000;
64
+ }
65
+ return jwtExpiresAt(token.access_token);
66
+ }
67
+ export function applyOAuthTokenToConfig(config, token, opts) {
68
+ const now = opts.now ?? Date.now();
69
+ const tokenType = normalizeTokenType(token.token_type);
70
+ const refreshToken = token.refresh_token || opts.refreshToken;
71
+ if (!config.headers)
72
+ config.headers = {};
73
+ config.headers.Authorization = `${tokenType} ${token.access_token}`;
74
+ if (!refreshToken) {
75
+ delete config.oauth;
76
+ return;
77
+ }
78
+ config.oauth = {
79
+ tokenEndpoint: opts.tokenEndpoint,
80
+ clientId: opts.clientId,
81
+ clientSecret: opts.clientSecret,
82
+ refreshToken,
83
+ resource: opts.resource,
84
+ scope: token.scope || opts.scope,
85
+ tokenType,
86
+ accessTokenExpiresAt: inferAccessTokenExpiresAt(token, now),
87
+ updatedAt: now,
88
+ };
89
+ }
90
+ export function defaultCoreURL(mcpURL) {
91
+ const u = new URL(mcpURL);
92
+ const path = u.pathname.replace(/\/+$/, "");
93
+ if (path === "" || path === "/v1" || path === "/mcp") {
94
+ u.pathname = "/v1/core/tools/call";
95
+ }
96
+ else if (!path.endsWith("/core/tools/call")) {
97
+ u.pathname = `${path}/core/tools/call`;
98
+ }
99
+ u.search = "";
100
+ u.hash = "";
101
+ return u.toString();
102
+ }
35
103
  export function resolveConfig() {
36
104
  const cfg = loadConfig() || {};
37
105
  const url = process.env.TYC_MCP_ENDPOINT || cfg.url || DEFAULT_MCP_URL;
106
+ const coreUrl = process.env.TYC_CORE_ENDPOINT || cfg.coreUrl || defaultCoreURL(url);
38
107
  const headers = { ...(cfg.headers || {}) };
39
108
  if (!headers.Authorization && process.env.TYC_AUTHORIZATION) {
40
109
  headers.Authorization = process.env.TYC_AUTHORIZATION;
@@ -43,5 +112,60 @@ export function resolveConfig() {
43
112
  console.error("未配置 Authorization。请先运行:tyc init --authorization YOUR_API_KEY");
44
113
  process.exit(1);
45
114
  }
46
- return { url, headers };
115
+ return { url, coreUrl, headers, oauth: cfg.oauth ? { ...cfg.oauth } : undefined };
116
+ }
117
+ function hasBearerAuthorization(config) {
118
+ return /^Bearer\s+\S+/i.test(config.headers.Authorization || "");
119
+ }
120
+ function shouldRefreshOAuth(oauth, opts) {
121
+ if (opts.force)
122
+ return true;
123
+ if (!oauth.accessTokenExpiresAt)
124
+ return false;
125
+ const skewMs = opts.skewMs ?? OAUTH_REFRESH_SKEW_MS;
126
+ return oauth.accessTokenExpiresAt <= Date.now() + skewMs;
127
+ }
128
+ export async function refreshResolvedConfigOAuth(resolved, opts = {}) {
129
+ const stored = loadConfig() || {};
130
+ const oauth = stored.oauth;
131
+ if (!oauth?.refreshToken || !oauth.tokenEndpoint || !oauth.clientId) {
132
+ return { config: resolved, refreshed: false };
133
+ }
134
+ if (!hasBearerAuthorization(resolved)) {
135
+ return { config: resolved, refreshed: false };
136
+ }
137
+ if (!shouldRefreshOAuth(oauth, opts)) {
138
+ return { config: resolved, refreshed: false };
139
+ }
140
+ let token;
141
+ try {
142
+ token = await exchangeRefreshToken({
143
+ tokenEndpoint: oauth.tokenEndpoint,
144
+ clientId: oauth.clientId,
145
+ clientSecret: oauth.clientSecret,
146
+ refreshToken: oauth.refreshToken,
147
+ resource: oauth.resource,
148
+ });
149
+ }
150
+ catch (err) {
151
+ const msg = err instanceof Error ? err.message : String(err);
152
+ throw new Error(`OAuth refresh token 刷新失败,请重新运行 tyc login:${msg}`);
153
+ }
154
+ applyOAuthTokenToConfig(stored, token, {
155
+ tokenEndpoint: oauth.tokenEndpoint,
156
+ clientId: oauth.clientId,
157
+ clientSecret: oauth.clientSecret,
158
+ refreshToken: oauth.refreshToken,
159
+ resource: oauth.resource,
160
+ scope: oauth.scope,
161
+ });
162
+ saveConfig(stored);
163
+ if (opts.verbose) {
164
+ console.error("> OAuth access token refreshed");
165
+ }
166
+ return { config: resolveConfig(), refreshed: true };
167
+ }
168
+ export async function resolveConfigWithFreshOAuth(opts = {}) {
169
+ const resolved = resolveConfig();
170
+ return refreshResolvedConfigOAuth(resolved, opts);
47
171
  }
@@ -0,0 +1,121 @@
1
+ function maskToken(v) {
2
+ if (!v)
3
+ return "";
4
+ if (v.length <= 8)
5
+ return "****";
6
+ return v.slice(0, 4) + "****" + v.slice(-4);
7
+ }
8
+ function coreSiblingURL(coreUrl, siblingPath) {
9
+ const u = new URL(coreUrl);
10
+ const path = u.pathname.replace(/\/+$/, "");
11
+ if (path.endsWith("/tools/call")) {
12
+ u.pathname = path.slice(0, -"/tools/call".length) + siblingPath;
13
+ }
14
+ else {
15
+ u.pathname = path + siblingPath;
16
+ }
17
+ u.search = "";
18
+ u.hash = "";
19
+ return u.toString();
20
+ }
21
+ function readyURL(coreUrl) {
22
+ return coreSiblingURL(coreUrl, "/ready");
23
+ }
24
+ function authReadyURL(coreUrl) {
25
+ return coreSiblingURL(coreUrl, "/auth/ready");
26
+ }
27
+ export async function verifyCoreEndpoint(cfg, verbose = false) {
28
+ const url = readyURL(cfg.coreUrl);
29
+ if (verbose)
30
+ console.error(`> GET ${url}`);
31
+ const resp = await fetch(url, {
32
+ method: "GET",
33
+ headers: { Accept: "text/plain" },
34
+ });
35
+ const text = await resp.text();
36
+ if (verbose) {
37
+ console.error(`< ${resp.status} ${resp.statusText}`);
38
+ console.error(`< body: ${text.slice(0, 500)}`);
39
+ }
40
+ if (resp.status < 200 || resp.status >= 300) {
41
+ throw new Error(`core endpoint 校验失败:HTTP ${resp.status}\n${text.slice(0, 500)}`);
42
+ }
43
+ }
44
+ export async function verifyCoreAuthEndpoint(cfg, verbose = false) {
45
+ const url = authReadyURL(cfg.coreUrl);
46
+ if (verbose)
47
+ console.error(`> GET ${url}`);
48
+ const headers = {
49
+ Accept: "application/json, text/plain",
50
+ ...cfg.headers,
51
+ };
52
+ const resp = await fetch(url, {
53
+ method: "GET",
54
+ headers,
55
+ });
56
+ const text = await resp.text();
57
+ if (verbose) {
58
+ console.error(`< ${resp.status} ${resp.statusText}`);
59
+ console.error(`< body: ${text.slice(0, 500)}`);
60
+ }
61
+ if (resp.status < 200 || resp.status >= 300) {
62
+ let desc = text.slice(0, 500);
63
+ try {
64
+ const parsed = JSON.parse(text);
65
+ desc = String(parsed.error_description || parsed.error || desc);
66
+ }
67
+ catch {
68
+ // keep raw text
69
+ }
70
+ throw new Error(`core/auth/ready HTTP ${resp.status}\n${desc}`);
71
+ }
72
+ }
73
+ export async function callCoreTool(cfg, name, args, opts) {
74
+ const verbose = !!opts?.verbose;
75
+ const format = opts?.format || "json";
76
+ const headers = {
77
+ "Content-Type": "application/json",
78
+ Accept: "application/json",
79
+ ...cfg.headers,
80
+ };
81
+ const body = {
82
+ tool_name: name,
83
+ arguments: args,
84
+ format,
85
+ };
86
+ if (verbose) {
87
+ const masked = {};
88
+ for (const [k, v] of Object.entries(headers)) {
89
+ masked[k] = k.toLowerCase() === "authorization" ? maskToken(v) : v;
90
+ }
91
+ console.error(`> POST ${cfg.coreUrl}`);
92
+ console.error(`> headers: ${JSON.stringify(masked)}`);
93
+ console.error(`> body: ${JSON.stringify(body)}`);
94
+ }
95
+ const resp = await fetch(cfg.coreUrl, {
96
+ method: "POST",
97
+ headers,
98
+ body: JSON.stringify(body),
99
+ });
100
+ const text = await resp.text();
101
+ if (verbose) {
102
+ console.error(`< ${resp.status} ${resp.statusText}`);
103
+ console.error(`< body: ${text.slice(0, 2000)}`);
104
+ }
105
+ let parsed;
106
+ try {
107
+ parsed = JSON.parse(text);
108
+ }
109
+ catch {
110
+ throw new Error(`core 响应不是 JSON:HTTP ${resp.status}\n${text.slice(0, 500)}`);
111
+ }
112
+ if (resp.status < 200 || resp.status >= 300) {
113
+ const desc = parsed.error_description || parsed.error || text.slice(0, 500);
114
+ throw new Error(`core/tools/call HTTP ${resp.status}\n${desc}`);
115
+ }
116
+ const content = parsed.content;
117
+ const contentText = typeof content === "string" ? content : JSON.stringify(content ?? {});
118
+ return {
119
+ content: [{ type: "text", text: contentText }],
120
+ };
121
+ }
package/dist/index.js CHANGED
@@ -1,99 +1,77 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
3
  import { registerInitCommand } from "./commands/init.js";
4
+ import { registerLoginCommand } from "./commands/login.js";
4
5
  import { registerCategoryCommands } from "./commands/category.js";
5
6
  import { registerLayerCommands } from "./commands/layers.js";
6
7
  import { getCategories, getLayerCount, getPriorityTools, getTotalCount, } from "./registry.js";
7
8
  import { VERSION } from "./version.js";
8
- // 顶层 help:把"最懂 AI 的 CLI"落在 4 层架构 + 调用顺序 + 可发现性上。
9
- // Agent 看 tyc --help 时应立刻读懂"先 L0 锚定企业,再 L1 概览,再 L2 下钻,最后 L3 详情"这条主线。
9
+ // 顶层 help 保持为快速入口:讲清分层、调用顺序、发现命令和输出控制;
10
+ // 更完整的架构说明放在 `tyc layers`。
10
11
  const helpText = `
11
- ARCHITECTURE — Layered Tool Discovery for LLM Agents
12
-
13
- Cognitive research shows LLM tool-selection accuracy collapses above
14
- 30 tools (>95% at 10, ~70% at 30, <50% at 100+). tyc counters this with
15
- a 4-layer progressive-disclosure architecture, led by a dedicated
16
- entity-resolution layer:
17
-
18
- ┌─ L0 Resolve ${pad(getLayerCount("L0"), 3)} tool entity resolution · 简称/曾用名/模糊名 → 精确企业
19
- ├─ L1 Overview ${pad(getLayerCount("L1"), 3)} tools one captain per facet · returns _summary for routing
20
- ├─ L2 Drill-down ${pad(getLayerCount("L2"), 3)} tools one-hop into a specific data dimension (★ = L2 优先, see below)
21
- └─ L3 Specialized ${pad(getLayerCount("L3"), 3)} tools id-based detail · search_* · vertical SKILLs
22
-
23
- Recommended call order:
24
-
25
- 0. Anchor at L0. Feed the user's raw company name (可能是简称/曾用名/模糊
26
- 指代) into \`search_companies\`; lock onto one entity
27
- with a USCC + official name before anything else.
28
- 1. Ascend to L1. Pick ONE of the ${getLayerCount("L1")} overview tools with the
29
- anchored USCC. Read _summary, then use layer lists or
30
- MCP get_company_capabilities for the next tool.
31
- 2. Descend to L2. Pick a specific dimension
32
- (shareholders / litigation / patents / annual reports …).
33
- If unsure which L2, default to that facet's L2★ 优先 (below).
34
- 3. Land on L3. When L2 returns a list item with an \`id\`, a \`search_*\`
35
- need arises, or a vertical SKILL is activated.
36
-
37
- Rule of thumb: the smaller the layer number, the higher the information
38
- density per token. L0 is MANDATORY — skipping it (without a USCC already
39
- in hand) wastes downstream calls on the wrong entity.
40
- Default LLM surface = L0 + L1 = ${getLayerCount("L0") + getLayerCount("L1")} tools (≤ 15-tool exposure limit).
41
-
42
- L2 优先 ★ — \`L2★ 队副\`(${getPriorityTools().length} 个,每 facet 1 个)
12
+ 工具分层
13
+
14
+ tyc 采用 4 层渐进发现,默认只把 L0 + L1 的 ${getLayerCount("L0") + getLayerCount("L1")} 个高密度工具暴露给 Agent,再按需下钻到 L2/L3。
15
+
16
+ ┌─ L0 实体锚定 ${pad(getLayerCount("L0"), 3)} 个工具 简称 / 曾用名 / 模糊名 → 精确企业
17
+ ├─ L1 概要总览 ${pad(getLayerCount("L1"), 3)} 个工具 每个 facet 1 个总览工具,返回 _summary
18
+ ├─ L2 维度下钻 ${pad(getLayerCount("L2"), 3)} 个工具 股东、诉讼、商标、招投标、年报等明细维度
19
+ └─ L3 专项工具 ${pad(getLayerCount("L3"), 3)} 个工具 id 详情、search_*、垂直场景和专业查询
20
+
21
+ 推荐调用顺序
22
+
23
+ 0. 先做 L0:用用户给出的原始企业名调用 \`search_companies\`,确认唯一企业、
24
+ 统一社会信用代码和企业全名。
25
+ 1. 再看 L1:按问题选择 1 个概要工具,读取 _summary 判断下一步。
26
+ 2. 下钻 L2:选择具体数据维度;不确定时优先用对应 facet 的 L2★ 工具。
27
+ 3. 进入 L3:需要记录 id 详情、跨主体 search_* 或垂直 SKILL 时再使用。
28
+
29
+ L2 优先 ★(${getPriorityTools().length} 个,每个 facet 1 个)
43
30
 
44
31
  Agent 拿到 L1 _summary 后若不确定走哪个 L2,对应 facet 的"L2 优先"是默认下钻:
45
32
 
46
33
  ${renderPriorityLines()}
47
34
 
48
- Detail: tyc layers / tyc L2 list (★ marks priority rows)
49
-
50
- DISCOVER BY LAYER
35
+ 按层发现
51
36
 
52
- tyc layers one-screen architecture map (含 L2 优先映射)
53
- tyc L0 list list the ${getLayerCount("L0")} L0 tool (entity-resolve layer)
54
- tyc L1 list list all ${getLayerCount("L1")} L1 tools (overview, grouped)
55
- tyc L2 list list all ${getLayerCount("L2")} L2 tools (drill-down; ★ priority pinned to top)
56
- tyc L3 list list all ${getLayerCount("L3")} L3 tools (specialized, grouped)
57
- tyc L0 list --md Markdown tables (agent on-screen)
58
- tyc L0 list --json machine-readable JSON (id · cli · params · description · priority)
37
+ tyc layers 查看一屏架构图和推荐调用顺序
38
+ tyc L0 list 列出 ${getLayerCount("L0")} 个 L0 工具
39
+ tyc L1 list 列出 ${getLayerCount("L1")} 个 L1 工具
40
+ tyc L2 list 列出 ${getLayerCount("L2")} 个 L2 工具(★ 优先工具置顶)
41
+ tyc L3 list 列出 ${getLayerCount("L3")} 个 L3 工具
42
+ tyc L0 list --md 输出 Markdown 表格
43
+ tyc L0 list --json 输出 JSON(含工具名、CLI、参数、描述、优先级)
44
+ tyc company capabilities <company_id> --company-name <name>
45
+ 根据 L0 返回的 company_id 打印可直接执行的工具清单
59
46
 
60
- DISCOVER BY CATEGORY (orthogonal to layers, 6 groups)
47
+ 按分类发现(与层级正交,共 6 组)
61
48
 
62
49
  ${renderCategoryLines()}
63
50
 
64
- OUTPUT FORMATS (mutually exclusive, priority: --md > --compact > --pretty / default)
65
-
66
- (default) indented JSON (pretty) — readable for both humans and agents
67
- --pretty same as default (kept for backward compatibility / explicit intent)
68
- --compact compact single-line JSON (pipe / jq friendly; old default behavior)
69
- --md Markdown tables (human + agent on-screen)
70
- --verbose also print MCP request details to stderr (orthogonal to above)
71
-
72
- OUTPUT TRUNCATION & DUMP (orthogonal — apply to any sub-command)
73
-
74
- --head [N] print only the first N lines (N defaults to 50 if flag given alone)
75
- --tail [M] print only the last M lines (M defaults to 20 if flag given alone)
76
- · --head + --tail together: head + "... omitted ..." + tail
77
- · either flag alone: that side only
78
- --full force-print the FULL rendered content (no truncation; highest priority)
79
- --threshold <BYTES> master switch for byte-truncation. Without --threshold there is
80
- NO automatic truncation, regardless of output size.
81
- With --threshold N: when rendered output > N bytes, byte-truncate
82
- from the head; --head / --tail are IGNORED in this mode.
83
- --output-file <PATH> write the FULL rendered content to PATH. **No file is written
84
- unless --output-file is explicitly passed** (no auto-dump).
85
- Combine with --head/--tail/--threshold to keep stdout terse
86
- while preserving the full result on disk.
87
-
88
- SETUP
89
-
90
- tyc init --authorization <KEY> default https://mcp.tianyancha.com/v1
91
- tyc init --url <MCP_URL> --authorization <KEY> local / self-hosted
92
-
93
- Every tool returns tyc OpenAPI native English keys verbatim. The MCP Server
94
- handles multi-source merge, Asia/Shanghai timestamp formatting, _summary
95
- injection, and empty-result normalization — the CLI just tells the Agent
96
- where the data is and what it looks like.
51
+ 全局输出选项(所有工具命令均可使用)
52
+
53
+ 以下选项可用于所有工具调用;具体工具 help 不重复展开完整列表。
54
+
55
+ 默认 缩进 JSON,兼顾人类阅读和 Agent 解析
56
+ --pretty 同默认输出,保留用于显式声明意图
57
+ --compact 单行紧凑 JSON,适合管道和 jq
58
+ --md Markdown 表格,适合终端阅读或粘贴给 Agent
59
+ --verbose 额外把 shared core 请求详情打印到 stderr
60
+
61
+ --head [N] 只输出前 N 行;只写 flag 时默认 50 行
62
+ --tail [M] 只输出后 M 行;只写 flag 时默认 20 行
63
+ --full 强制输出完整内容,优先级最高
64
+ --threshold <BYTES> 超过指定字节数时从头截断;不传则不自动截断
65
+ --output-file <PATH> 把完整结果写入文件;必须显式指定才会落盘
66
+
67
+ 初始化与登录
68
+
69
+ tyc login OAuth 浏览器登录
70
+ tyc login --no-open --no-block 打印 OAuth URL 后立即退出,手动完成
71
+ tyc login --callback-token <CODE_OR_CALLBACK_URL> 继续非阻塞 OAuth 登录
72
+ tyc login --url <MCP_URL> 登录本地、预发或自托管 MCP
73
+ tyc init --authorization <KEY> 使用 API Key 兼容路径
74
+ tyc init --url <MCP_URL> --authorization <KEY> 配置本地或自托管服务
97
75
  `;
98
76
  function pad(n, width) {
99
77
  const s = String(n);
@@ -102,7 +80,7 @@ function pad(n, width) {
102
80
  function renderCategoryLines() {
103
81
  const lines = [];
104
82
  for (const c of getCategories()) {
105
- lines.push(` tyc ${c.group.padEnd(22)} ${c.name_zh} (${c.tool_count} tools)`);
83
+ lines.push(` tyc ${c.group.padEnd(22)} ${c.name_zh} (${c.tool_count} 个工具)`);
106
84
  }
107
85
  return lines.join("\n");
108
86
  }
@@ -115,12 +93,26 @@ function renderPriorityLines() {
115
93
  }
116
94
  const program = new Command()
117
95
  .name("tyc")
118
- .description(`Tianyan AI CLI — ${getTotalCount()} commercial-data tools · L0=${getLayerCount("L0")} L1=${getLayerCount("L1")} L2=${getLayerCount("L2")} L3=${getLayerCount("L3")} · the AI-native gateway (pair with the TA MCP Server)`)
119
- .version(VERSION)
96
+ .description(`天眼查 AI CLI:${getTotalCount()} 个商业数据工具 · L0=${getLayerCount("L0")} L1=${getLayerCount("L1")} L2=${getLayerCount("L2")} L3=${getLayerCount("L3")} · 面向 Agent 的 MCP 工具网关`)
97
+ .version(VERSION, "-V, --version", "输出版本号")
98
+ .helpOption("-h, --help", "显示命令帮助")
99
+ .addHelpCommand("help [command]", "显示命令帮助")
100
+ .configureHelp({
101
+ styleTitle(title) {
102
+ const titleMap = {
103
+ "Usage:": "用法:",
104
+ "Arguments:": "参数:",
105
+ "Options:": "选项:",
106
+ "Global Options:": "全局选项:",
107
+ "Commands:": "命令:",
108
+ };
109
+ return titleMap[title] || title;
110
+ },
111
+ })
120
112
  .option("--pretty", "缩进 JSON 输出(默认行为,flag 保留以保持向后兼容)")
121
113
  .option("--md", "Markdown 表格化输出(适合人类阅读 / Agent 上屏)")
122
114
  .option("--compact", "紧凑单行 JSON(管道 / jq 场景;为旧默认行为)")
123
- .option("--verbose", "打印 MCP 请求详情到 stderr")
115
+ .option("--verbose", "打印 shared core 请求详情到 stderr")
124
116
  .option("--head [n]", "仅输出前 N 行(不传值默认 50;与 --tail 同时给则同时输出两端)")
125
117
  .option("--tail [m]", "仅输出后 M 行(不传值默认 20;与 --head 同时给则同时输出两端)")
126
118
  .option("--full", "强制输出完整内容(最高优先级,覆盖 --head/--tail/--threshold)")
@@ -128,6 +120,7 @@ const program = new Command()
128
120
  .option("--threshold <bytes>", "字节截断主开关:超过该字节数则从头按字节截断;不传则永不截断")
129
121
  .addHelpText("after", helpText);
130
122
  registerInitCommand(program);
123
+ registerLoginCommand(program);
131
124
  registerLayerCommands(program);
132
125
  registerCategoryCommands(program);
133
126
  program.parseAsync(process.argv).catch((err) => {
package/dist/logo.js ADDED
@@ -0,0 +1,24 @@
1
+ export const TY_LOGO = `\x1b[38;2;22;135;245m ▄▄▄████████▄▄▄
2
+ ▄▄█████████▀▀▀▀▀▀▀▀▀
3
+ ▄████████▀▀▄▄▄▄██████▄▄▄▄
4
+ ▄████████████████████████████▄
5
+ ▄███████████████████▀▀▀▀▀▀▀████▄
6
+ ▄██████████████▀▀ ███
7
+ ████████████▀▀ ▄▄▀ █▀
8
+ ███████████▀ ▄▄██▀
9
+ █████████▀ ▄█████ ▄
10
+ ███████▀ ▄██████▀ ▄█
11
+ ██████▀ ▄███████ █ ▄███
12
+ ████ █████████ █▄ ▄▄▄▄▄▄▄▄▄█████
13
+ ▀██ ██████████ ██▄ ▀▀██████████▀
14
+ ▀ ███████████▄ ████▄ ▀▀▀▀▀▀▀▀
15
+ █████████████ ██████▄▄▄
16
+ ▀███████████▄ ▀██████████▀
17
+ ▀▀█████████▄ ▀██████▀▀
18
+ ▀▀▀██████ ▀▀▀\x1b[0m`;
19
+ export const LOGIN_SUCCESS_BANNER = `登录成功
20
+ ${TY_LOGO}
21
+ 公平看清世界`;
22
+ export function printLoginSuccessBanner() {
23
+ console.log(LOGIN_SUCCESS_BANNER);
24
+ }