@lyhue1991/tvsearch 0.1.0
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/.claude/commands/opsx/apply.md +152 -0
- package/.claude/commands/opsx/archive.md +157 -0
- package/.claude/commands/opsx/explore.md +173 -0
- package/.claude/commands/opsx/propose.md +106 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
- package/.claude/skills/openspec-archive-change/SKILL.md +114 -0
- package/.claude/skills/openspec-explore/SKILL.md +288 -0
- package/.claude/skills/openspec-propose/SKILL.md +110 -0
- package/.opencode/command/opsx-apply.md +149 -0
- package/.opencode/command/opsx-archive.md +154 -0
- package/.opencode/command/opsx-explore.md +170 -0
- package/.opencode/command/opsx-propose.md +103 -0
- package/.opencode/skills/openspec-apply-change/SKILL.md +156 -0
- package/.opencode/skills/openspec-archive-change/SKILL.md +114 -0
- package/.opencode/skills/openspec-explore/SKILL.md +288 -0
- package/.opencode/skills/openspec-propose/SKILL.md +110 -0
- package/AGENTS.md +154 -0
- package/README.md +192 -0
- package/dist/api/tavily.d.ts +25 -0
- package/dist/api/tavily.d.ts.map +1 -0
- package/dist/api/tavily.js +94 -0
- package/dist/api/tavily.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.d.ts +13 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +64 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/search.d.ts +13 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +66 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +89 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +10 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config.d.ts +55 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +122 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/errors.d.ts +54 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +109 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/output.d.ts +26 -0
- package/dist/utils/output.d.ts.map +1 -0
- package/dist/utils/output.js +80 -0
- package/dist/utils/output.js.map +1 -0
- package/jest.config.js +18 -0
- package/openspec/changes/archive/2026-03-21-enhance-help-documentation/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-21-enhance-help-documentation/design.md +58 -0
- package/openspec/changes/archive/2026-03-21-enhance-help-documentation/proposal.md +31 -0
- package/openspec/changes/archive/2026-03-21-enhance-help-documentation/specs/cli-interface/spec.md +41 -0
- package/openspec/changes/archive/2026-03-21-enhance-help-documentation/tasks.md +27 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/design.md +123 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/proposal.md +29 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/specs/cli-interface/spec.md +57 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/specs/config-management/spec.md +45 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/specs/web-search/spec.md +49 -0
- package/openspec/changes/archive/2026-03-21-init-tavily-search/tasks.md +59 -0
- package/openspec/config.yaml +20 -0
- package/openspec/specs/cli-interface/spec.md +79 -0
- package/openspec/specs/config-management/spec.md +45 -0
- package/openspec/specs/web-search/spec.md +49 -0
- package/package.json +50 -0
- package/skill/tvsearch/SKILL.md +212 -0
- package/src/api/tavily.ts +132 -0
- package/src/cli.ts +57 -0
- package/src/commands/config.ts +88 -0
- package/src/commands/search.ts +98 -0
- package/src/index.ts +4 -0
- package/src/types/index.ts +98 -0
- package/src/utils/config.ts +140 -0
- package/src/utils/errors.ts +127 -0
- package/src/utils/output.ts +96 -0
- package/test/config.test.ts +88 -0
- package/test/output.test.ts +76 -0
- package/test/tavily.test.ts +85 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import ora from 'ora';
|
|
2
|
+
import { searchWeb, validateApiKey } from '../api/tavily.js';
|
|
3
|
+
import { getApiKey, getDefaultFormat, getDefaultCount, getDefaultFreshness } from '../utils/config.js';
|
|
4
|
+
import { formatOutput } from '../utils/output.js';
|
|
5
|
+
import { AuthError, handleError, validateCount, validateFormat, validateFreshness, validateDepth, validateTopic, } from '../utils/errors.js';
|
|
6
|
+
import { ExitCode } from '../types/index.js';
|
|
7
|
+
/**
|
|
8
|
+
* 执行搜索命令
|
|
9
|
+
*/
|
|
10
|
+
export async function executeSearch(query, options) {
|
|
11
|
+
try {
|
|
12
|
+
// 验证参数
|
|
13
|
+
if (options.count) {
|
|
14
|
+
validateCount(options.count);
|
|
15
|
+
}
|
|
16
|
+
if (options.format) {
|
|
17
|
+
validateFormat(options.format);
|
|
18
|
+
}
|
|
19
|
+
if (options.freshness) {
|
|
20
|
+
validateFreshness(options.freshness);
|
|
21
|
+
}
|
|
22
|
+
if (options.depth) {
|
|
23
|
+
validateDepth(options.depth);
|
|
24
|
+
}
|
|
25
|
+
if (options.topic) {
|
|
26
|
+
validateTopic(options.topic);
|
|
27
|
+
}
|
|
28
|
+
// 获取 API Key
|
|
29
|
+
const apiKey = getApiKey(options.apiKey);
|
|
30
|
+
if (!apiKey) {
|
|
31
|
+
throw new AuthError('API Key 无效或未配置。请使用 tvsearch config --api-key 设置');
|
|
32
|
+
}
|
|
33
|
+
// 验证 API Key 格式
|
|
34
|
+
if (!validateApiKey(apiKey)) {
|
|
35
|
+
console.warn('警告: API Key 格式可能不正确,应以 tvly- 开头');
|
|
36
|
+
}
|
|
37
|
+
// 构建搜索选项
|
|
38
|
+
const searchOptions = {
|
|
39
|
+
query,
|
|
40
|
+
count: options.count ?? getDefaultCount(),
|
|
41
|
+
format: options.format ?? getDefaultFormat(),
|
|
42
|
+
freshness: options.freshness ?? getDefaultFreshness(),
|
|
43
|
+
depth: options.depth,
|
|
44
|
+
topic: options.topic,
|
|
45
|
+
};
|
|
46
|
+
// 执行搜索
|
|
47
|
+
const spinner = ora('搜索中...').start();
|
|
48
|
+
let results;
|
|
49
|
+
try {
|
|
50
|
+
results = await searchWeb(searchOptions, apiKey);
|
|
51
|
+
spinner.succeed(`找到 ${results.length} 条结果`);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
spinner.fail('搜索失败');
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
// 输出结果
|
|
58
|
+
const output = formatOutput(results, searchOptions.format ?? 'json');
|
|
59
|
+
console.log(output);
|
|
60
|
+
return ExitCode.SUCCESS;
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
return handleError(error);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACvG,OAAO,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAC;AAC/D,OAAO,EAEL,SAAS,EAET,WAAW,EACX,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,aAAa,GACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAW7C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAa,EACb,OAA6B;IAE7B,IAAI,CAAC;QACH,OAAO;QACP,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,cAAc,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC;QAED,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;YACtB,iBAAiB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;YAClB,aAAa,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAC/B,CAAC;QAED,aAAa;QACb,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QACzC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,SAAS,CAAC,iDAAiD,CAAC,CAAC;QACzE,CAAC;QAED,gBAAgB;QAChB,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,iCAAiC,CAAC,CAAC;QAClD,CAAC;QAED,SAAS;QACT,MAAM,aAAa,GAAkB;YACnC,KAAK;YACL,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,eAAe,EAAE;YACzC,MAAM,EAAG,OAAO,CAAC,MAAc,IAAI,gBAAgB,EAAE;YACrD,SAAS,EAAG,OAAO,CAAC,SAAiB,IAAI,mBAAmB,EAAE;YAC9D,KAAK,EAAE,OAAO,CAAC,KAAY;YAC3B,KAAK,EAAE,OAAO,CAAC,KAAY;SAC5B,CAAC;QAEF,OAAO;QACP,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,KAAK,EAAE,CAAC;QACtC,IAAI,OAAuB,CAAC;QAE5B,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,SAAS,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YACjD,OAAO,CAAC,OAAO,CAAC,MAAM,OAAO,CAAC,MAAM,MAAM,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrB,MAAM,KAAK,CAAC;QACd,CAAC;QAED,OAAO;QACP,MAAM,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,aAAa,CAAC,MAAM,IAAI,MAAM,CAAC,CAAC;QACrE,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEpB,OAAO,QAAQ,CAAC,OAAO,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;AACH,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 输出格式类型
|
|
3
|
+
*/
|
|
4
|
+
export type OutputFormat = 'json' | 'markdown' | 'table';
|
|
5
|
+
/**
|
|
6
|
+
* 时间范围类型
|
|
7
|
+
*/
|
|
8
|
+
export type Freshness = 'pd' | 'pw' | 'pm' | 'py' | string;
|
|
9
|
+
/**
|
|
10
|
+
* 搜索深度类型
|
|
11
|
+
*/
|
|
12
|
+
export type SearchDepth = 'basic' | 'advanced';
|
|
13
|
+
/**
|
|
14
|
+
* 搜索主题类型
|
|
15
|
+
*/
|
|
16
|
+
export type Topic = 'general' | 'news';
|
|
17
|
+
/**
|
|
18
|
+
* 搜索结果项
|
|
19
|
+
*/
|
|
20
|
+
export interface SearchResult {
|
|
21
|
+
title: string;
|
|
22
|
+
url: string;
|
|
23
|
+
content: string;
|
|
24
|
+
score: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 搜索选项
|
|
28
|
+
*/
|
|
29
|
+
export interface SearchOptions {
|
|
30
|
+
query: string;
|
|
31
|
+
count?: number;
|
|
32
|
+
freshness?: Freshness;
|
|
33
|
+
format?: OutputFormat;
|
|
34
|
+
depth?: SearchDepth;
|
|
35
|
+
topic?: Topic;
|
|
36
|
+
apiKey?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 存储的配置
|
|
40
|
+
*/
|
|
41
|
+
export interface StoredConfig {
|
|
42
|
+
apiKey?: string;
|
|
43
|
+
defaultFormat?: OutputFormat;
|
|
44
|
+
defaultCount?: number;
|
|
45
|
+
defaultFreshness?: Freshness;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Tavily API 请求参数
|
|
49
|
+
*/
|
|
50
|
+
export interface TavilySearchRequest {
|
|
51
|
+
api_key: string;
|
|
52
|
+
query: string;
|
|
53
|
+
search_depth?: 'basic' | 'advanced';
|
|
54
|
+
topic?: 'general' | 'news';
|
|
55
|
+
max_results?: number;
|
|
56
|
+
time_range?: 'day' | 'week' | 'month' | 'year';
|
|
57
|
+
include_answer?: boolean;
|
|
58
|
+
include_raw_content?: boolean;
|
|
59
|
+
include_images?: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Tavily API 响应结果项
|
|
63
|
+
*/
|
|
64
|
+
export interface TavilyResultItem {
|
|
65
|
+
title: string;
|
|
66
|
+
url: string;
|
|
67
|
+
content: string;
|
|
68
|
+
score: number;
|
|
69
|
+
raw_content?: string;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Tavily API 响应
|
|
73
|
+
*/
|
|
74
|
+
export interface TavilySearchResponse {
|
|
75
|
+
answer?: string;
|
|
76
|
+
results: TavilyResultItem[];
|
|
77
|
+
images?: string[];
|
|
78
|
+
response_time?: number;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* CLI 退出码
|
|
82
|
+
*/
|
|
83
|
+
export declare const ExitCode: {
|
|
84
|
+
readonly SUCCESS: 0;
|
|
85
|
+
readonly INVALID_ARGS: 1;
|
|
86
|
+
readonly AUTH_ERROR: 2;
|
|
87
|
+
readonly API_ERROR: 3;
|
|
88
|
+
};
|
|
89
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,MAAM,CAAC;AAE3D;;GAEG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,CAAC;AAE/C;;GAEG;AACH,MAAM,MAAM,KAAK,GAAG,SAAS,GAAG,MAAM,CAAC;AAEvC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,KAAK,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,YAAY,CAAC;IAC7B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,SAAS,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,OAAO,GAAG,UAAU,CAAC;IACpC,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAC/C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,gBAAgB,EAAE,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,eAAO,MAAM,QAAQ;;;;;CAKX,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"AAyFA;;GAEG;AACH,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,OAAO,EAAE,CAAC;IACV,YAAY,EAAE,CAAC;IACf,UAAU,EAAE,CAAC;IACb,SAAS,EAAE,CAAC;CACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { StoredConfig, OutputFormat, Freshness } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 获取 API Key(优先级:环境变量 > 配置文件)
|
|
4
|
+
*/
|
|
5
|
+
export declare function getApiKey(cliApiKey?: string): string | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* 设置 API Key
|
|
8
|
+
*/
|
|
9
|
+
export declare function setApiKey(apiKey: string): void;
|
|
10
|
+
/**
|
|
11
|
+
* 脱敏显示 API Key
|
|
12
|
+
* 显示格式: tvly-****xxxx(前缀 + **** + 后4位)
|
|
13
|
+
*/
|
|
14
|
+
export declare function maskApiKey(apiKey: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* 获取默认输出格式
|
|
17
|
+
*/
|
|
18
|
+
export declare function getDefaultFormat(): OutputFormat;
|
|
19
|
+
/**
|
|
20
|
+
* 设置默认输出格式
|
|
21
|
+
*/
|
|
22
|
+
export declare function setDefaultFormat(format: OutputFormat): void;
|
|
23
|
+
/**
|
|
24
|
+
* 获取默认结果数量
|
|
25
|
+
*/
|
|
26
|
+
export declare function getDefaultCount(): number;
|
|
27
|
+
/**
|
|
28
|
+
* 设置默认结果数量
|
|
29
|
+
*/
|
|
30
|
+
export declare function setDefaultCount(count: number): void;
|
|
31
|
+
/**
|
|
32
|
+
* 获取默认时间范围
|
|
33
|
+
*/
|
|
34
|
+
export declare function getDefaultFreshness(): Freshness | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* 设置默认时间范围
|
|
37
|
+
*/
|
|
38
|
+
export declare function setDefaultFreshness(freshness: Freshness): void;
|
|
39
|
+
/**
|
|
40
|
+
* 获取完整配置
|
|
41
|
+
*/
|
|
42
|
+
export declare function getConfig(): StoredConfig;
|
|
43
|
+
/**
|
|
44
|
+
* 显示当前配置(API Key 脱敏)
|
|
45
|
+
*/
|
|
46
|
+
export declare function displayConfig(): string;
|
|
47
|
+
/**
|
|
48
|
+
* 重置配置
|
|
49
|
+
*/
|
|
50
|
+
export declare function resetConfig(): void;
|
|
51
|
+
/**
|
|
52
|
+
* 获取配置文件路径
|
|
53
|
+
*/
|
|
54
|
+
export declare function getConfigPath(): string;
|
|
55
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAW1E;;GAEG;AACH,wBAAgB,SAAS,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAchE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAE9C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,YAAY,CAE/C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAE3D;AAED;;GAEG;AACH,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,SAAS,GAAG,SAAS,CAE3D;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,SAAS,EAAE,SAAS,GAAG,IAAI,CAE9D;AAED;;GAEG;AACH,wBAAgB,SAAS,IAAI,YAAY,CAOxC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAStC;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
/**
|
|
5
|
+
* 配置存储实例
|
|
6
|
+
* 配置文件路径: ~/.config/tvsearch/tvsearch.json
|
|
7
|
+
*/
|
|
8
|
+
const config = new Conf({
|
|
9
|
+
cwd: path.join(os.homedir(), '.config', 'tvsearch'),
|
|
10
|
+
configName: 'tvsearch',
|
|
11
|
+
});
|
|
12
|
+
/**
|
|
13
|
+
* 获取 API Key(优先级:环境变量 > 配置文件)
|
|
14
|
+
*/
|
|
15
|
+
export function getApiKey(cliApiKey) {
|
|
16
|
+
// 命令行参数优先级最高
|
|
17
|
+
if (cliApiKey) {
|
|
18
|
+
return cliApiKey;
|
|
19
|
+
}
|
|
20
|
+
// 环境变量次之
|
|
21
|
+
const envKey = process.env.TAVILY_API_KEY;
|
|
22
|
+
if (envKey) {
|
|
23
|
+
return envKey;
|
|
24
|
+
}
|
|
25
|
+
// 最后读取配置文件
|
|
26
|
+
return config.get('apiKey');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 设置 API Key
|
|
30
|
+
*/
|
|
31
|
+
export function setApiKey(apiKey) {
|
|
32
|
+
config.set('apiKey', apiKey);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 脱敏显示 API Key
|
|
36
|
+
* 显示格式: tvly-****xxxx(前缀 + **** + 后4位)
|
|
37
|
+
*/
|
|
38
|
+
export function maskApiKey(apiKey) {
|
|
39
|
+
if (!apiKey) {
|
|
40
|
+
return '(未配置)';
|
|
41
|
+
}
|
|
42
|
+
if (apiKey.startsWith('tvly-')) {
|
|
43
|
+
const suffix = apiKey.slice(-4);
|
|
44
|
+
return `tvly-****${suffix}`;
|
|
45
|
+
}
|
|
46
|
+
// 非 tvly- 前缀,显示前4位 + ****
|
|
47
|
+
const prefix = apiKey.slice(0, 4);
|
|
48
|
+
return `${prefix}****`;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 获取默认输出格式
|
|
52
|
+
*/
|
|
53
|
+
export function getDefaultFormat() {
|
|
54
|
+
return config.get('defaultFormat') ?? 'json';
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 设置默认输出格式
|
|
58
|
+
*/
|
|
59
|
+
export function setDefaultFormat(format) {
|
|
60
|
+
config.set('defaultFormat', format);
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 获取默认结果数量
|
|
64
|
+
*/
|
|
65
|
+
export function getDefaultCount() {
|
|
66
|
+
return config.get('defaultCount') ?? 10;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 设置默认结果数量
|
|
70
|
+
*/
|
|
71
|
+
export function setDefaultCount(count) {
|
|
72
|
+
config.set('defaultCount', count);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 获取默认时间范围
|
|
76
|
+
*/
|
|
77
|
+
export function getDefaultFreshness() {
|
|
78
|
+
return config.get('defaultFreshness');
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 设置默认时间范围
|
|
82
|
+
*/
|
|
83
|
+
export function setDefaultFreshness(freshness) {
|
|
84
|
+
config.set('defaultFreshness', freshness);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 获取完整配置
|
|
88
|
+
*/
|
|
89
|
+
export function getConfig() {
|
|
90
|
+
return {
|
|
91
|
+
apiKey: config.get('apiKey'),
|
|
92
|
+
defaultFormat: config.get('defaultFormat'),
|
|
93
|
+
defaultCount: config.get('defaultCount'),
|
|
94
|
+
defaultFreshness: config.get('defaultFreshness'),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 显示当前配置(API Key 脱敏)
|
|
99
|
+
*/
|
|
100
|
+
export function displayConfig() {
|
|
101
|
+
const cfg = getConfig();
|
|
102
|
+
const lines = [
|
|
103
|
+
`API Key: ${maskApiKey(cfg.apiKey ?? '')}`,
|
|
104
|
+
`默认格式: ${cfg.defaultFormat ?? 'json'}`,
|
|
105
|
+
`默认数量: ${cfg.defaultCount ?? 10}`,
|
|
106
|
+
`默认时间范围: ${cfg.defaultFreshness ?? '(未设置)'}`,
|
|
107
|
+
];
|
|
108
|
+
return lines.join('\n');
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* 重置配置
|
|
112
|
+
*/
|
|
113
|
+
export function resetConfig() {
|
|
114
|
+
config.clear();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 获取配置文件路径
|
|
118
|
+
*/
|
|
119
|
+
export function getConfigPath() {
|
|
120
|
+
return config.path;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/utils/config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 错误类
|
|
3
|
+
*/
|
|
4
|
+
export declare class CliError extends Error {
|
|
5
|
+
readonly code: number;
|
|
6
|
+
constructor(message: string, code?: number);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 参数验证错误
|
|
10
|
+
*/
|
|
11
|
+
export declare class ValidationError extends CliError {
|
|
12
|
+
constructor(message: string);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 认证错误
|
|
16
|
+
*/
|
|
17
|
+
export declare class AuthError extends CliError {
|
|
18
|
+
constructor(message?: string);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* API 错误
|
|
22
|
+
*/
|
|
23
|
+
export declare class ApiError extends CliError {
|
|
24
|
+
constructor(message: string);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 格式化错误消息
|
|
28
|
+
*/
|
|
29
|
+
export declare function formatError(error: unknown): string;
|
|
30
|
+
/**
|
|
31
|
+
* 输出错误到 stderr 并返回退出码
|
|
32
|
+
*/
|
|
33
|
+
export declare function handleError(error: unknown): number;
|
|
34
|
+
/**
|
|
35
|
+
* 验证 count 参数
|
|
36
|
+
*/
|
|
37
|
+
export declare function validateCount(count: number): void;
|
|
38
|
+
/**
|
|
39
|
+
* 验证 format 参数
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateFormat(format: string): void;
|
|
42
|
+
/**
|
|
43
|
+
* 验证 freshness 参数
|
|
44
|
+
*/
|
|
45
|
+
export declare function validateFreshness(freshness: string): void;
|
|
46
|
+
/**
|
|
47
|
+
* 验证 depth 参数
|
|
48
|
+
*/
|
|
49
|
+
export declare function validateDepth(depth: string): void;
|
|
50
|
+
/**
|
|
51
|
+
* 验证 topic 参数
|
|
52
|
+
*/
|
|
53
|
+
export declare function validateTopic(topic: string): void;
|
|
54
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;gBAEV,OAAO,EAAE,MAAM,EAAE,IAAI,GAAE,MAA2B;CAK/D;AAED;;GAEG;AACH,qBAAa,eAAgB,SAAQ,QAAQ;gBAC/B,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,qBAAa,SAAU,SAAQ,QAAQ;gBACzB,OAAO,GAAE,MAAyB;CAI/C;AAED;;GAEG;AACH,qBAAa,QAAS,SAAQ,QAAQ;gBACxB,OAAO,EAAE,MAAM;CAI5B;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAUlD;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CASlD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIjD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAKnD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAUzD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKjD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAKjD"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ExitCode } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* CLI 错误类
|
|
4
|
+
*/
|
|
5
|
+
export class CliError extends Error {
|
|
6
|
+
code;
|
|
7
|
+
constructor(message, code = ExitCode.API_ERROR) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'CliError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 参数验证错误
|
|
15
|
+
*/
|
|
16
|
+
export class ValidationError extends CliError {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message, ExitCode.INVALID_ARGS);
|
|
19
|
+
this.name = 'ValidationError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 认证错误
|
|
24
|
+
*/
|
|
25
|
+
export class AuthError extends CliError {
|
|
26
|
+
constructor(message = 'API Key 无效或未配置') {
|
|
27
|
+
super(message, ExitCode.AUTH_ERROR);
|
|
28
|
+
this.name = 'AuthError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* API 错误
|
|
33
|
+
*/
|
|
34
|
+
export class ApiError extends CliError {
|
|
35
|
+
constructor(message) {
|
|
36
|
+
super(message, ExitCode.API_ERROR);
|
|
37
|
+
this.name = 'ApiError';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* 格式化错误消息
|
|
42
|
+
*/
|
|
43
|
+
export function formatError(error) {
|
|
44
|
+
if (error instanceof CliError) {
|
|
45
|
+
return error.message;
|
|
46
|
+
}
|
|
47
|
+
if (error instanceof Error) {
|
|
48
|
+
return error.message;
|
|
49
|
+
}
|
|
50
|
+
return '未知错误';
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 输出错误到 stderr 并返回退出码
|
|
54
|
+
*/
|
|
55
|
+
export function handleError(error) {
|
|
56
|
+
const message = formatError(error);
|
|
57
|
+
console.error(`错误: ${message}`);
|
|
58
|
+
if (error instanceof CliError) {
|
|
59
|
+
return error.code;
|
|
60
|
+
}
|
|
61
|
+
return ExitCode.API_ERROR;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 验证 count 参数
|
|
65
|
+
*/
|
|
66
|
+
export function validateCount(count) {
|
|
67
|
+
if (count < 1 || count > 50) {
|
|
68
|
+
throw new ValidationError('count 参数必须在 1-50 之间');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 验证 format 参数
|
|
73
|
+
*/
|
|
74
|
+
export function validateFormat(format) {
|
|
75
|
+
const validFormats = ['json', 'markdown', 'table'];
|
|
76
|
+
if (!validFormats.includes(format)) {
|
|
77
|
+
throw new ValidationError(`不支持的输出格式: ${format}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* 验证 freshness 参数
|
|
82
|
+
*/
|
|
83
|
+
export function validateFreshness(freshness) {
|
|
84
|
+
const validFreshness = ['pd', 'pw', 'pm', 'py'];
|
|
85
|
+
// 支持自定义日期范围 YYYY-MM-DDtoYYYY-MM-DD
|
|
86
|
+
const dateRangePattern = /^\d{4}-\d{2}-\d{2}to\d{4}-\d{2}-\d{2}$/;
|
|
87
|
+
if (!validFreshness.includes(freshness) && !dateRangePattern.test(freshness)) {
|
|
88
|
+
throw new ValidationError(`不支持的 freshness 参数: ${freshness}。支持: pd, pw, pm, py 或 YYYY-MM-DDtoYYYY-MM-DD`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 验证 depth 参数
|
|
93
|
+
*/
|
|
94
|
+
export function validateDepth(depth) {
|
|
95
|
+
const validDepths = ['basic', 'advanced'];
|
|
96
|
+
if (!validDepths.includes(depth)) {
|
|
97
|
+
throw new ValidationError(`不支持的搜索深度: ${depth}。支持: basic, advanced`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 验证 topic 参数
|
|
102
|
+
*/
|
|
103
|
+
export function validateTopic(topic) {
|
|
104
|
+
const validTopics = ['general', 'news'];
|
|
105
|
+
if (!validTopics.includes(topic)) {
|
|
106
|
+
throw new ValidationError(`不支持的搜索主题: ${topic}。支持: general, news`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SearchResult, OutputFormat } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* 输出 JSON 格式
|
|
4
|
+
*/
|
|
5
|
+
export declare function outputJson(results: SearchResult[]): string;
|
|
6
|
+
/**
|
|
7
|
+
* 输出 Markdown 格式
|
|
8
|
+
*/
|
|
9
|
+
export declare function outputMarkdown(results: SearchResult[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* 输出 Table 格式
|
|
12
|
+
*/
|
|
13
|
+
export declare function outputTable(results: SearchResult[]): string;
|
|
14
|
+
/**
|
|
15
|
+
* 根据格式输出结果
|
|
16
|
+
*/
|
|
17
|
+
export declare function formatOutput(results: SearchResult[], format: OutputFormat): string;
|
|
18
|
+
/**
|
|
19
|
+
* 输出错误消息到 stderr
|
|
20
|
+
*/
|
|
21
|
+
export declare function outputError(message: string): void;
|
|
22
|
+
/**
|
|
23
|
+
* 输出成功消息
|
|
24
|
+
*/
|
|
25
|
+
export declare function outputSuccess(message: string): void;
|
|
26
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/utils/output.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE/D;;GAEG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAoB9D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAwB3D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,YAAY,EAAE,EACvB,MAAM,EAAE,YAAY,GACnB,MAAM,CAWR;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEjD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAEnD"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import Table from 'cli-table3';
|
|
2
|
+
/**
|
|
3
|
+
* 输出 JSON 格式
|
|
4
|
+
*/
|
|
5
|
+
export function outputJson(results) {
|
|
6
|
+
return JSON.stringify(results, null, 2);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* 输出 Markdown 格式
|
|
10
|
+
*/
|
|
11
|
+
export function outputMarkdown(results) {
|
|
12
|
+
if (results.length === 0) {
|
|
13
|
+
return '无搜索结果';
|
|
14
|
+
}
|
|
15
|
+
const lines = [];
|
|
16
|
+
results.forEach((result, index) => {
|
|
17
|
+
lines.push(`## ${index + 1}. ${result.title}`);
|
|
18
|
+
lines.push(`[${result.url}](${result.url})`);
|
|
19
|
+
lines.push('');
|
|
20
|
+
lines.push(result.content);
|
|
21
|
+
lines.push('');
|
|
22
|
+
lines.push(`**相关度**: ${result.score.toFixed(2)}`);
|
|
23
|
+
lines.push('');
|
|
24
|
+
lines.push('---');
|
|
25
|
+
lines.push('');
|
|
26
|
+
});
|
|
27
|
+
return lines.join('\n');
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 输出 Table 格式
|
|
31
|
+
*/
|
|
32
|
+
export function outputTable(results) {
|
|
33
|
+
if (results.length === 0) {
|
|
34
|
+
return '无搜索结果';
|
|
35
|
+
}
|
|
36
|
+
const table = new Table({
|
|
37
|
+
head: ['#', '标题', '来源', '相关度'],
|
|
38
|
+
colWidths: [4, 40, 50, 10],
|
|
39
|
+
wordWrap: true,
|
|
40
|
+
wrapOnWordBoundary: false,
|
|
41
|
+
});
|
|
42
|
+
results.forEach((result, index) => {
|
|
43
|
+
const title = result.title.length > 38
|
|
44
|
+
? result.title.slice(0, 35) + '...'
|
|
45
|
+
: result.title;
|
|
46
|
+
const url = result.url.length > 48
|
|
47
|
+
? result.url.slice(0, 45) + '...'
|
|
48
|
+
: result.url;
|
|
49
|
+
table.push([index + 1, title, url, result.score.toFixed(2)]);
|
|
50
|
+
});
|
|
51
|
+
return table.toString();
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* 根据格式输出结果
|
|
55
|
+
*/
|
|
56
|
+
export function formatOutput(results, format) {
|
|
57
|
+
switch (format) {
|
|
58
|
+
case 'json':
|
|
59
|
+
return outputJson(results);
|
|
60
|
+
case 'markdown':
|
|
61
|
+
return outputMarkdown(results);
|
|
62
|
+
case 'table':
|
|
63
|
+
return outputTable(results);
|
|
64
|
+
default:
|
|
65
|
+
return outputJson(results);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* 输出错误消息到 stderr
|
|
70
|
+
*/
|
|
71
|
+
export function outputError(message) {
|
|
72
|
+
console.error(`错误: ${message}`);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 输出成功消息
|
|
76
|
+
*/
|
|
77
|
+
export function outputSuccess(message) {
|
|
78
|
+
console.log(`✓ ${message}`);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=output.js.map
|