tyc-cli 0.3.4 → 0.3.5

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.
@@ -1,5 +1,6 @@
1
1
  import { getCategories, getToolsByGroup } from "../registry.js";
2
2
  import { resolveConfig } from "../config.js";
3
+ import { callCoreTool } from "../coreClient.js";
3
4
  import { callTool } from "../mcpClient.js";
4
5
  import { jsonToMarkdown } from "../utils/jsonToMarkdown.js";
5
6
  import { applyTruncation, resolveOptionalInt, TRUNCATE_DEFAULTS, } from "../utils/truncate.js";
@@ -53,7 +54,9 @@ function bindMethod(catCmd, tool, program) {
53
54
  const opts = program.opts();
54
55
  const verbose = !!opts.verbose;
55
56
  try {
56
- const result = await callTool(cfg, tool.name, args, { verbose });
57
+ const result = cfg.transport === "mcp"
58
+ ? await callTool(cfg, tool.name, args, { verbose })
59
+ : await callCoreTool(cfg, tool.name, args, { verbose });
57
60
  emit(result, opts, tool.name);
58
61
  }
59
62
  catch (err) {
@@ -1,12 +1,23 @@
1
- import { DEFAULT_MCP_URL, loadConfig, resolveConfig, saveConfig } from "../config.js";
1
+ import { DEFAULT_MCP_URL, defaultCoreURL, loadConfig, resolveConfig, saveConfig, } from "../config.js";
2
+ import { verifyCoreEndpoint } from "../coreClient.js";
2
3
  import { clearSession } from "../session.js";
3
4
  import { ensureSession } from "../mcpClient.js";
5
+ function parseTransport(raw) {
6
+ if (!raw)
7
+ return undefined;
8
+ const v = raw.trim().toLowerCase();
9
+ if (v === "core" || v === "mcp")
10
+ return v;
11
+ throw new Error(`invalid --transport: ${raw} (expected core or mcp)`);
12
+ }
4
13
  export function registerInitCommand(program) {
5
14
  program
6
15
  .command("init")
7
16
  .description("配置 MCP endpoint / Authorization(保存到 ~/.tyc/config.json,并校验连通性)")
8
17
  .option("--authorization <token>", "tyc OpenAPI Authorization(写入 headers.Authorization)")
9
18
  .option("--url <url>", `MCP endpoint(默认 ${DEFAULT_MCP_URL})`)
19
+ .option("--core-url <url>", "Shared Core HTTP endpoint(默认从 --url 推导 /v1/core/tools/call)")
20
+ .option("--transport <core|mcp>", "默认调用通道(默认 core;mcp 保留旧 session 链路)")
10
21
  .option("--header <kv...>", "自定义 header,格式 K=V,可重复;留空清除该 key")
11
22
  .option("--clear-session", "仅清除本地 MCP session 缓存(~/.tyc/session.json)")
12
23
  .option("--no-verify", "跳过连通性校验(不发 initialize)")
@@ -18,6 +29,15 @@ export function registerInitCommand(program) {
18
29
  cfg.url = opts.url;
19
30
  if (!cfg.url)
20
31
  cfg.url = DEFAULT_MCP_URL;
32
+ const transport = parseTransport(opts.transport);
33
+ if (transport)
34
+ cfg.transport = transport;
35
+ if (!cfg.transport)
36
+ cfg.transport = "core";
37
+ if (opts.coreUrl)
38
+ cfg.coreUrl = opts.coreUrl;
39
+ if (!cfg.coreUrl)
40
+ cfg.coreUrl = defaultCoreURL(cfg.url);
21
41
  if (opts.authorization)
22
42
  cfg.headers.Authorization = opts.authorization;
23
43
  if (opts.header && opts.header.length > 0) {
@@ -43,20 +63,26 @@ export function registerInitCommand(program) {
43
63
  if (opts.clearSession) {
44
64
  console.log("已清除 ~/.tyc/session.json");
45
65
  }
46
- console.log(`已保存 ~/.tyc/config.json (url=${cfg.url})`);
66
+ console.log(`已保存 ~/.tyc/config.json (transport=${cfg.transport}, url=${cfg.url}, coreUrl=${cfg.coreUrl})`);
47
67
  if (opts.verify === false) {
48
68
  return;
49
69
  }
50
- // 立即发 initialize,拿到 Mcp-Session-Id 落盘;失败则非 0 退出
70
+ // core 模式只探活 stateless endpoint;mcp 模式才 initialize 并缓存 session。
51
71
  try {
52
72
  const resolved = resolveConfig();
53
73
  const verbose = !!program.opts().verbose;
54
- const sess = await ensureSession(resolved, { verbose });
55
- console.log(`已建立 MCP session(sessionId=${sess.sessionId.slice(0, 16)}…)`);
74
+ if (resolved.transport === "mcp") {
75
+ const sess = await ensureSession(resolved, { verbose });
76
+ console.log(`已建立 MCP session(sessionId=${sess.sessionId.slice(0, 16)}…)`);
77
+ }
78
+ else {
79
+ await verifyCoreEndpoint(resolved, verbose);
80
+ console.log("Shared Core endpoint 校验通过");
81
+ }
56
82
  }
57
83
  catch (err) {
58
84
  const msg = err instanceof Error ? err.message : String(err);
59
- console.error(`MCP 连通性校验失败:${msg}`);
85
+ console.error(`连通性校验失败:${msg}`);
60
86
  console.error("配置已保存;确认 --url 可达后重新运行 tyc init,或加 --no-verify 跳过校验。");
61
87
  process.exit(1);
62
88
  }
@@ -0,0 +1,210 @@
1
+ import { DEFAULT_MCP_URL, defaultCoreURL, loadConfig, resolveConfig, saveConfig, } from "../config.js";
2
+ import { verifyCoreAuthEndpoint } from "../coreClient.js";
3
+ import { clearSession } from "../session.js";
4
+ import { ensureSession } from "../mcpClient.js";
5
+ import { buildAuthorizationURL, createPKCE, discoverAuthorizationServer, discoverProtectedResourceMetadata, exchangeAuthorizationCode, knownOAuthDefaultsForMcpURL, openBrowser, randomState, registerDynamicOAuthClient, startCallbackServer, } from "../oauth.js";
6
+ import { printLoginSuccessBanner } from "../logo.js";
7
+ function parsePort(raw) {
8
+ if (!raw)
9
+ return 0;
10
+ const n = Number(raw);
11
+ if (!Number.isInteger(n) || n < 0 || n > 65535) {
12
+ throw new Error(`invalid --redirect-port: ${raw}`);
13
+ }
14
+ return n;
15
+ }
16
+ function normalizeTokenType(raw) {
17
+ if (!raw)
18
+ return "Bearer";
19
+ return raw.toLowerCase() === "bearer" ? "Bearer" : raw;
20
+ }
21
+ export function registerLoginCommand(program) {
22
+ program
23
+ .command("login")
24
+ .description("通过 OAuth 浏览器登录并自动写入 ~/.tyc/config.json")
25
+ .option("--url <url>", `MCP endpoint(默认 ${DEFAULT_MCP_URL},或沿用当前配置)`)
26
+ .option("--issuer <url>", "OAuth authorization server issuer(默认从 MCP metadata 发现)")
27
+ .option("--client-id <id>", "OAuth client_id(默认通过 DCR 自动注册)")
28
+ .option("--client-secret <secret>", "OAuth client_secret(默认 TYC_OAUTH_CLIENT_SECRET;公共 CLI 通常不需要)")
29
+ .option("--scope <scope>", "OAuth scope(默认 mcp:tools.call)")
30
+ .option("--resource <url>", "OAuth resource/audience(默认从 MCP metadata 发现)")
31
+ .option("--resource-metadata-url <url>", "手动指定 MCP protected resource metadata URL")
32
+ .option("--authorization-endpoint <url>", "手动指定 OAuth authorization endpoint")
33
+ .option("--token-endpoint <url>", "手动指定 OAuth token endpoint")
34
+ .option("--redirect-host <host>", "本地回调 host(默认 localhost)")
35
+ .option("--redirect-port <port>", "本地回调端口(默认 7078)")
36
+ .option("--callback-path <path>", "本地回调路径(默认 /oauth/callback)")
37
+ .option("--no-open", "不自动打开浏览器,只打印授权 URL")
38
+ .option("--no-verify", "保存 token 后跳过 initialize 连通性校验")
39
+ .action(async (opts) => {
40
+ const current = loadConfig() || {};
41
+ const mcpURL = opts.url || process.env.TYC_MCP_ENDPOINT || current.url || DEFAULT_MCP_URL;
42
+ const knownDefaults = knownOAuthDefaultsForMcpURL(mcpURL);
43
+ let clientId = opts.clientId || process.env.TYC_OAUTH_CLIENT_ID || "";
44
+ let clientSecret = opts.clientSecret || process.env.TYC_OAUTH_CLIENT_SECRET;
45
+ const scope = opts.scope || "mcp:tools.call";
46
+ const redirectHost = opts.redirectHost || "localhost";
47
+ const redirectPort = parsePort(opts.redirectPort || "7078");
48
+ const callbackPath = opts.callbackPath || "/oauth/callback";
49
+ const verbose = !!program.opts().verbose;
50
+ let issuer = opts.issuer || "";
51
+ let resource = opts.resource || "";
52
+ if (!opts.issuer || !opts.resource) {
53
+ if (verbose) {
54
+ console.error(`> discovering MCP OAuth metadata: ${mcpURL}`);
55
+ }
56
+ try {
57
+ const protectedResource = await discoverProtectedResourceMetadata(mcpURL, opts.resourceMetadataUrl);
58
+ if (verbose) {
59
+ console.error(`> protected resource metadata: ${protectedResource.metadataURL}`);
60
+ }
61
+ const metadataIssuer = protectedResource.metadata.authorization_servers?.[0];
62
+ if (!issuer && metadataIssuer) {
63
+ issuer = metadataIssuer;
64
+ }
65
+ resource ||= protectedResource.metadata.resource || mcpURL;
66
+ }
67
+ catch (err) {
68
+ issuer ||= knownDefaults.issuer || "";
69
+ resource ||= knownDefaults.resource || "";
70
+ if (!issuer || !resource) {
71
+ throw err;
72
+ }
73
+ if (verbose) {
74
+ const msg = err instanceof Error ? err.message : String(err);
75
+ console.error(`> metadata discovery failed, using known defaults: ${msg}`);
76
+ }
77
+ }
78
+ }
79
+ else if (verbose) {
80
+ console.error("> using explicit --issuer and --resource; skip protected resource metadata discovery");
81
+ }
82
+ if (!issuer) {
83
+ throw new Error("MCP metadata 未提供 authorization_servers,请用 --issuer 指定。");
84
+ }
85
+ // 双端点都手动指定时跳过自动发现;否则 metadata 发现失败会直接报错
86
+ // (discovery 不再猜测端点,见 discoverAuthorizationServer)。
87
+ let endpoints;
88
+ if (opts.authorizationEndpoint && opts.tokenEndpoint) {
89
+ endpoints = {
90
+ authorizationEndpoint: opts.authorizationEndpoint,
91
+ tokenEndpoint: opts.tokenEndpoint,
92
+ issuer,
93
+ };
94
+ }
95
+ else {
96
+ endpoints = await discoverAuthorizationServer(issuer);
97
+ endpoints.authorizationEndpoint =
98
+ opts.authorizationEndpoint || endpoints.authorizationEndpoint;
99
+ endpoints.tokenEndpoint = opts.tokenEndpoint || endpoints.tokenEndpoint;
100
+ }
101
+ resource ||= endpoints.resourceDocumentation || mcpURL;
102
+ if (verbose) {
103
+ console.error(`> authorization endpoint: ${endpoints.authorizationEndpoint}`);
104
+ console.error(`> token endpoint: ${endpoints.tokenEndpoint}`);
105
+ if (endpoints.registrationEndpoint) {
106
+ console.error(`> registration endpoint: ${endpoints.registrationEndpoint}`);
107
+ }
108
+ }
109
+ const state = randomState();
110
+ const pkce = createPKCE();
111
+ const callback = await startCallbackServer({
112
+ host: redirectHost,
113
+ port: redirectPort,
114
+ path: callbackPath.startsWith("/") ? callbackPath : `/${callbackPath}`,
115
+ state,
116
+ timeoutMs: 5 * 60 * 1000,
117
+ });
118
+ if (!clientId) {
119
+ if (!endpoints.registrationEndpoint) {
120
+ callback.close();
121
+ throw new Error("OAuth metadata 未提供 registration_endpoint,请用 --client-id 指定预注册客户端。");
122
+ }
123
+ if (verbose) {
124
+ console.error("> registering OAuth client via Dynamic Client Registration");
125
+ }
126
+ let registeredClient;
127
+ try {
128
+ registeredClient = await registerDynamicOAuthClient({
129
+ registrationEndpoint: endpoints.registrationEndpoint,
130
+ clientName: "tyc-cli",
131
+ redirectUris: [callback.redirectUri],
132
+ tokenEndpointAuthMethod: "none",
133
+ });
134
+ }
135
+ catch (err) {
136
+ callback.close();
137
+ throw err;
138
+ }
139
+ clientId = registeredClient.clientId;
140
+ clientSecret ||= registeredClient.clientSecret;
141
+ if (verbose) {
142
+ console.error(`> registered client_id: ${clientId}`);
143
+ }
144
+ }
145
+ const authorizationURL = buildAuthorizationURL({
146
+ authorizationEndpoint: endpoints.authorizationEndpoint,
147
+ clientId,
148
+ redirectUri: callback.redirectUri,
149
+ scope,
150
+ state,
151
+ codeChallenge: pkce.codeChallenge,
152
+ resource,
153
+ });
154
+ if (verbose) {
155
+ console.error(`> OAuth callback: ${callback.redirectUri}`);
156
+ }
157
+ if (opts.open === false) {
158
+ console.error("请在浏览器打开以下 URL 完成登录:");
159
+ console.error(authorizationURL);
160
+ }
161
+ else {
162
+ console.error("正在打开浏览器完成 OAuth 登录...");
163
+ openBrowser(authorizationURL);
164
+ }
165
+ const code = await callback.waitForCode();
166
+ if (verbose) {
167
+ console.error("> authorization code received; exchanging access token");
168
+ }
169
+ const token = await exchangeAuthorizationCode({
170
+ tokenEndpoint: endpoints.tokenEndpoint,
171
+ clientId,
172
+ clientSecret,
173
+ code,
174
+ redirectUri: callback.redirectUri,
175
+ codeVerifier: pkce.codeVerifier,
176
+ resource,
177
+ });
178
+ const cfg = loadConfig() || {};
179
+ cfg.url = mcpURL;
180
+ if (!cfg.transport)
181
+ cfg.transport = "core";
182
+ if (!cfg.coreUrl)
183
+ cfg.coreUrl = defaultCoreURL(mcpURL);
184
+ cfg.headers = { ...(cfg.headers || {}) };
185
+ cfg.headers.Authorization = `${normalizeTokenType(token.token_type)} ${token.access_token}`;
186
+ saveConfig(cfg);
187
+ clearSession();
188
+ if (verbose) {
189
+ console.error(`> OAuth login state saved to ~/.tyc/config.json (url=${cfg.url})`);
190
+ }
191
+ if (opts.verify === false) {
192
+ printLoginSuccessBanner();
193
+ return;
194
+ }
195
+ const resolved = resolveConfig();
196
+ if (resolved.transport === "mcp") {
197
+ const sess = await ensureSession(resolved, { verbose });
198
+ if (verbose) {
199
+ console.error(`> MCP session initialized (sessionId=${sess.sessionId.slice(0, 16)}...)`);
200
+ }
201
+ }
202
+ else {
203
+ await verifyCoreAuthEndpoint(resolved, verbose);
204
+ if (verbose) {
205
+ console.error("> Shared Core auth check passed");
206
+ }
207
+ }
208
+ printLoginSuccessBanner();
209
+ });
210
+ }
package/dist/config.js CHANGED
@@ -1,17 +1,20 @@
1
1
  // ~/.tyc/config.json 读写
2
2
  //
3
3
  // 配置文件格式与通用 MCP 客户端保持一致:
4
- // { "url": "https://mcp.tianyancha.com/v1",
5
- // "headers": { "Authorization": "xxxx" } }
4
+ // { "url": "https://mcp.tianyancha.com/mcp",
5
+ // "headers": { "Authorization": "xxxx" },
6
+ // "transport": "core",
7
+ // "coreUrl": "https://mcp.tianyancha.com/v1/core/tools/call" }
6
8
  //
7
- // 默认端点指向天眼查公网 MCP Server;连接本地或私有环境可通过
8
- // tyc init --url http://localhost:8080/v1 或 TYC_MCP_ENDPOINT 环境变量覆盖。
9
+ // 默认端点指向天眼查公网 MCP Server 的 canonical 入口 /mcp(/v1 是兼容别名,
10
+ // coreUrl 的 REST 端点仍只挂在 /v1/core 下);连接本地或私有环境可通过
11
+ // tyc init --url http://localhost:8080/mcp 或 TYC_MCP_ENDPOINT 环境变量覆盖。
9
12
  import { readFileSync, writeFileSync, mkdirSync, existsSync, chmodSync } from "node:fs";
10
13
  import { join } from "node:path";
11
14
  import { homedir } from "node:os";
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,30 @@ export function saveConfig(config) {
32
35
  }
33
36
  catch { /* best effort */ }
34
37
  }
38
+ function normalizeTransport(raw) {
39
+ const v = (raw || "").trim().toLowerCase();
40
+ if (v === "mcp")
41
+ return "mcp";
42
+ return "core";
43
+ }
44
+ export function defaultCoreURL(mcpURL) {
45
+ const u = new URL(mcpURL);
46
+ const path = u.pathname.replace(/\/+$/, "");
47
+ if (path === "" || path === "/v1" || path === "/mcp") {
48
+ u.pathname = "/v1/core/tools/call";
49
+ }
50
+ else if (!path.endsWith("/core/tools/call")) {
51
+ u.pathname = `${path}/core/tools/call`;
52
+ }
53
+ u.search = "";
54
+ u.hash = "";
55
+ return u.toString();
56
+ }
35
57
  export function resolveConfig() {
36
58
  const cfg = loadConfig() || {};
37
59
  const url = process.env.TYC_MCP_ENDPOINT || cfg.url || DEFAULT_MCP_URL;
60
+ const transport = normalizeTransport(process.env.TYC_TRANSPORT || cfg.transport);
61
+ const coreUrl = process.env.TYC_CORE_ENDPOINT || cfg.coreUrl || defaultCoreURL(url);
38
62
  const headers = { ...(cfg.headers || {}) };
39
63
  if (!headers.Authorization && process.env.TYC_AUTHORIZATION) {
40
64
  headers.Authorization = process.env.TYC_AUTHORIZATION;
@@ -43,5 +67,5 @@ export function resolveConfig() {
43
67
  console.error("未配置 Authorization。请先运行:tyc init --authorization YOUR_API_KEY");
44
68
  process.exit(1);
45
69
  }
46
- return { url, headers };
70
+ return { url, coreUrl, transport, headers };
47
71
  }
@@ -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 headers = {
76
+ "Content-Type": "application/json",
77
+ Accept: "application/json",
78
+ ...cfg.headers,
79
+ };
80
+ const body = {
81
+ tool_name: name,
82
+ arguments: args,
83
+ format: "json",
84
+ };
85
+ if (verbose) {
86
+ const masked = {};
87
+ for (const [k, v] of Object.entries(headers)) {
88
+ masked[k] = k.toLowerCase() === "authorization" ? maskToken(v) : v;
89
+ }
90
+ console.error(`> POST ${cfg.coreUrl}`);
91
+ console.error(`> transport: core`);
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,6 +1,7 @@
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";
@@ -67,7 +68,7 @@ OUTPUT FORMATS (mutually exclusive, priority: --md > --compact > --pretty / defa
67
68
  --pretty same as default (kept for backward compatibility / explicit intent)
68
69
  --compact compact single-line JSON (pipe / jq friendly; old default behavior)
69
70
  --md Markdown tables (human + agent on-screen)
70
- --verbose also print MCP request details to stderr (orthogonal to above)
71
+ --verbose also print core/MCP request details to stderr (orthogonal to above)
71
72
 
72
73
  OUTPUT TRUNCATION & DUMP (orthogonal — apply to any sub-command)
73
74
 
@@ -87,8 +88,11 @@ OUTPUT TRUNCATION & DUMP (orthogonal — apply to any sub-command)
87
88
 
88
89
  SETUP
89
90
 
90
- tyc init --authorization <KEY> default https://mcp.tianyancha.com/v1
91
- tyc init --url <MCP_URL> --authorization <KEY> local / self-hosted
91
+ tyc login OAuth browser login
92
+ tyc login --url <MCP_URL> OAuth for local / pre / self-hosted
93
+ tyc init --authorization <KEY> API-key compatibility path (default transport: core)
94
+ tyc init --transport mcp --authorization <KEY> legacy MCP session transport
95
+ tyc init --url <MCP_URL> --authorization <KEY> local / self-hosted service
92
96
 
93
97
  Every tool returns tyc OpenAPI native English keys verbatim. The MCP Server
94
98
  handles multi-source merge, Asia/Shanghai timestamp formatting, _summary
@@ -120,7 +124,7 @@ const program = new Command()
120
124
  .option("--pretty", "缩进 JSON 输出(默认行为,flag 保留以保持向后兼容)")
121
125
  .option("--md", "Markdown 表格化输出(适合人类阅读 / Agent 上屏)")
122
126
  .option("--compact", "紧凑单行 JSON(管道 / jq 场景;为旧默认行为)")
123
- .option("--verbose", "打印 MCP 请求详情到 stderr")
127
+ .option("--verbose", "打印 core/MCP 请求详情到 stderr")
124
128
  .option("--head [n]", "仅输出前 N 行(不传值默认 50;与 --tail 同时给则同时输出两端)")
125
129
  .option("--tail [m]", "仅输出后 M 行(不传值默认 20;与 --head 同时给则同时输出两端)")
126
130
  .option("--full", "强制输出完整内容(最高优先级,覆盖 --head/--tail/--threshold)")
@@ -128,6 +132,7 @@ const program = new Command()
128
132
  .option("--threshold <bytes>", "字节截断主开关:超过该字节数则从头按字节截断;不传则永不截断")
129
133
  .addHelpText("after", helpText);
130
134
  registerInitCommand(program);
135
+ registerLoginCommand(program);
131
136
  registerLayerCommands(program);
132
137
  registerCategoryCommands(program);
133
138
  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
+ }