gangtise-openapi-cli 0.51.0 → 0.52.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/README.md +77 -49
- package/dist/src/cli.js +29 -8
- package/dist/src/commands/ai.js +12 -12
- package/dist/src/commands/alternative.js +2 -2
- package/dist/src/commands/bond.js +4 -4
- package/dist/src/commands/fund.js +1 -1
- package/dist/src/commands/fundamental.js +11 -11
- package/dist/src/commands/indicator.js +5 -5
- package/dist/src/commands/insight.js +38 -17
- package/dist/src/commands/quote.js +6 -6
- package/dist/src/commands/raw.js +3 -3
- package/dist/src/commands/reference.js +12 -3
- package/dist/src/commands/shared.js +20 -8
- package/dist/src/commands/tool.js +9 -4
- package/dist/src/commands/update.js +11 -0
- package/dist/src/commands/vault.js +15 -15
- package/dist/src/core/client.js +18 -2
- package/dist/src/core/download.js +12 -3
- package/dist/src/core/driveUpload.js +6 -2
- package/dist/src/core/dryRun.js +68 -0
- package/dist/src/core/endpoints.js +61 -6
- package/dist/src/core/etfComponents.js +45 -8
- package/dist/src/core/exitStatus.js +4 -0
- package/dist/src/core/fileParse.js +10 -3
- package/dist/src/core/printer.js +39 -7
- package/dist/src/core/statePaths.js +41 -0
- package/dist/src/core/transport.js +7 -0
- package/gangtise-openapi/SKILL.md +34 -154
- package/gangtise-openapi/references/billing.md +142 -0
- package/gangtise-openapi/references/commands/fund.md +3 -3
- package/gangtise-openapi/references/commands/fundamental.md +1 -0
- package/gangtise-openapi/references/commands/indicator.md +51 -8
- package/gangtise-openapi/references/commands/insight.md +31 -6
- package/gangtise-openapi/references/commands/reference-and-lookup.md +22 -4
- package/gangtise-openapi/references/commands/tool.md +2 -2
- package/gangtise-openapi/references/commands/vault.md +1 -1
- package/gangtise-openapi/references/errors.md +2 -2
- package/gangtise-openapi/references/examples.md +3 -3
- package/gangtise-openapi/references/lookup-ids.md +13 -0
- package/gangtise-openapi/references/response-schema.md +4 -1
- package/package.json +2 -2
|
@@ -4,16 +4,16 @@ import { COSTLY_FETCH_CREDITS } from "../core/endpoints.js";
|
|
|
4
4
|
import { normalizeRows } from "../core/normalize.js";
|
|
5
5
|
import { parseOutputFormat } from "../core/output.js";
|
|
6
6
|
import { printData } from "../core/printer.js";
|
|
7
|
-
import { emit, withClient, flagIfLimitTruncated, field,
|
|
7
|
+
import { emit, withClient, flagIfLimitTruncated, field, date, list, format, output, query, OUTPUT_HELP, SECURITY_CODE_FLAGS, SECURITY_CODE_HELP, oneSecurityCode, securityCode } from "./shared.js";
|
|
8
8
|
export const fundamental = new Command("fundamental").description("Fundamental APIs");
|
|
9
|
-
const addFinancialReport = (name, endpointKey, periodHelp = "Period") => query(fundamental, name, {
|
|
9
|
+
const addFinancialReport = (name, endpointKey, periodHelp = "Period: q1/interim/q3/annual/latest") => query(fundamental, name, {
|
|
10
10
|
endpoint: endpointKey,
|
|
11
11
|
fields: [
|
|
12
|
-
|
|
12
|
+
securityCode(),
|
|
13
13
|
date("--start-date <date>", "Start date (yyyy-MM-dd)", "startDate"),
|
|
14
14
|
date("--end-date <date>", "End date (yyyy-MM-dd)", "endDate"),
|
|
15
|
-
list("--fiscal-year <year>", "Fiscal year", "fiscalYear"),
|
|
16
|
-
list("--period <period>", periodHelp
|
|
15
|
+
list("--fiscal-year <year>", "Fiscal year; pass --period with it, or the latest report comes back whatever the year", "fiscalYear"),
|
|
16
|
+
list("--period <period>", `${periodHelp} (server default: latest)`, "period"),
|
|
17
17
|
list("--report-type <type>", "Report type", "reportType"),
|
|
18
18
|
list("--field <field>", "Field", "fieldList"),
|
|
19
19
|
format(), output(),
|
|
@@ -33,7 +33,7 @@ addFinancialReport("cash-flow-us", "fundamental.cash-flow-us", "Period: q1/h1/q3
|
|
|
33
33
|
query(fundamental, "main-business", {
|
|
34
34
|
endpoint: "fundamental.main-business",
|
|
35
35
|
fields: [
|
|
36
|
-
|
|
36
|
+
securityCode(),
|
|
37
37
|
date("--start-date <date>", "Start date (yyyy-MM-dd)", "startDate"),
|
|
38
38
|
date("--end-date <date>", "End date (yyyy-MM-dd)", "endDate"),
|
|
39
39
|
field(new Option("--breakdown <type>", "Breakdown: product/industry/region").choices(["product", "industry", "region"]).default("product"), (v) => ({ breakdown: v })),
|
|
@@ -62,7 +62,7 @@ function firstRowTradeDate(data) {
|
|
|
62
62
|
return typeof value === "string" ? value : undefined;
|
|
63
63
|
}
|
|
64
64
|
fundamental.command("valuation-analysis")
|
|
65
|
-
.requiredOption(
|
|
65
|
+
.requiredOption(SECURITY_CODE_FLAGS, SECURITY_CODE_HELP, oneSecurityCode)
|
|
66
66
|
.addOption(new Option("--indicator <name>", "Indicator").choices(["peTtm", "pbMrq", "peg", "psTtm", "pcfTtm", "em"]).makeOptionMandatory())
|
|
67
67
|
.option("--start-date <date>", "Start date (yyyy-MM-dd)", dateArg("--start-date"))
|
|
68
68
|
.option("--end-date <date>", "End date (yyyy-MM-dd)", dateArg("--end-date"))
|
|
@@ -70,7 +70,7 @@ fundamental.command("valuation-analysis")
|
|
|
70
70
|
.option("--field <field>", "Field", collectList, [])
|
|
71
71
|
.option("--skip-null", "Drop rows where value or percentileRank is null")
|
|
72
72
|
.option("--format <format>", "Output format", "table")
|
|
73
|
-
.option("--output <path>")
|
|
73
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
74
74
|
.action((options) => withClient(async (client) => {
|
|
75
75
|
const format = parseOutputFormat(options.format);
|
|
76
76
|
const requested = maybeArray(options.field);
|
|
@@ -115,7 +115,7 @@ fundamental.command("valuation-analysis")
|
|
|
115
115
|
query(fundamental, "top-holders", {
|
|
116
116
|
endpoint: "fundamental.top-holders",
|
|
117
117
|
fields: [
|
|
118
|
-
|
|
118
|
+
securityCode(),
|
|
119
119
|
field(new Option("--holder-type <type>", "Holder type: top10/top10Float").choices(["top10", "top10Float"]).makeOptionMandatory(), (v) => ({ holderType: v })),
|
|
120
120
|
date("--start-date <date>", "Start date (yyyy-MM-dd)", "startDate"),
|
|
121
121
|
date("--end-date <date>", "End date (yyyy-MM-dd)", "endDate"),
|
|
@@ -125,13 +125,13 @@ query(fundamental, "top-holders", {
|
|
|
125
125
|
],
|
|
126
126
|
});
|
|
127
127
|
fundamental.command("earning-forecast")
|
|
128
|
-
.requiredOption(
|
|
128
|
+
.requiredOption(SECURITY_CODE_FLAGS, SECURITY_CODE_HELP, oneSecurityCode)
|
|
129
129
|
.option("--start-date <date>", "Start date (default: 1 year before end-date)", dateArg("--start-date"))
|
|
130
130
|
.option("--end-date <date>", "End date (default: today)", dateArg("--end-date"))
|
|
131
131
|
.option("--consensus <name>", "Consensus indicator: netIncome/netIncomeYoy/eps/pe/bps/pb/peg/roe/ps", collectList, [])
|
|
132
132
|
.option("--yes", `Fetch a range estimated above ${COSTLY_FETCH_CREDITS} credits (billed per row; see the refusal for the estimate)`)
|
|
133
133
|
.option("--format <format>", "Output format", "table")
|
|
134
|
-
.option("--output <path>")
|
|
134
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
135
135
|
.action((options) => emit(options, (client) => {
|
|
136
136
|
const endDate = options.endDate ?? beijingDateString(new Date());
|
|
137
137
|
const startDate = options.startDate ?? new Date(new Date(`${endDate}T00:00:00Z`).getTime() - 365 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
|
|
@@ -5,7 +5,7 @@ import { checkScreenerBindings, droppedFromMatrix, flattenCrossSection, flattenT
|
|
|
5
5
|
import { ValidationError } from "../core/errors.js";
|
|
6
6
|
import { parseOutputFormat } from "../core/output.js";
|
|
7
7
|
import { printData } from "../core/printer.js";
|
|
8
|
-
import { withClient } from "./shared.js";
|
|
8
|
+
import { withClient, OUTPUT_HELP } from "./shared.js";
|
|
9
9
|
function flagDropped(rows, data, requestedSecurities, requestedIndicators, timeSeries = false) {
|
|
10
10
|
if (isEmptyMatrix(data)) {
|
|
11
11
|
if (timeSeries) {
|
|
@@ -44,7 +44,7 @@ indicator.command("search")
|
|
|
44
44
|
.requiredOption("--keyword <text>", "Search keyword, e.g. '收盘价' '成交量' '营业收入' (not free-form questions)")
|
|
45
45
|
.option("--limit <number>", "Max results (default: 50, max: 100)", "50")
|
|
46
46
|
.option("--format <format>", "Output format", "table")
|
|
47
|
-
.option("--output <path>")
|
|
47
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
48
48
|
.action((options) => withClient(async (client) => {
|
|
49
49
|
const format = parseOutputFormat(options.format);
|
|
50
50
|
const raw = await client.call("indicator.search", {
|
|
@@ -62,7 +62,7 @@ indicator.command("cross-section")
|
|
|
62
62
|
.option("--indicator-param <spec>", "Per-indicator param 'code:key=value', e.g. qte_close:adjustType=2 for 前复权 (repeat); read exact keys from 'indicator search'. Bare 'code:' (nothing after the colon) declares the indicator takes NO date — required by any indicator whose parameterList has no date key at all, which otherwise rejects the tradeDate --date injects: the pty_* / scr_* static-attribute families (pty_op_scope, scr_exchg_mkt, scr_isin …), plus div_cash_paid_ratio / div_cash_yr (add 'code:fiscalYear=YYYY' too) and pty_shr_reg. It composes with real params, so 'code:' + 'code:scale=8' keeps the scale", collectNames, [])
|
|
63
63
|
.addOption(new Option("--key-by <mode>", "Column key: name=display name (default) | code=indicatorCode, unique & order-stable for batch code→value mapping").choices(["name", "code"]).default("name"))
|
|
64
64
|
.option("--format <format>", "Output format", "table")
|
|
65
|
-
.option("--output <path>")
|
|
65
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
66
66
|
.action((options) => withClient(async (client) => {
|
|
67
67
|
const format = parseOutputFormat(options.format);
|
|
68
68
|
requireIndicatorScope(options.indicator, options.security);
|
|
@@ -83,7 +83,7 @@ indicator.command("time-series")
|
|
|
83
83
|
.option("--indicator-param <spec>", "Per-indicator param 'code:key=value', e.g. qte_close:adjustType=2 for 前复权 (repeat); read exact keys from 'indicator search'", collectNames, [])
|
|
84
84
|
.addOption(new Option("--key-by <mode>", "Column key: name=display name (default) | code=indicatorCode/securityCode, unique & order-stable for batch mapping").choices(["name", "code"]).default("name"))
|
|
85
85
|
.option("--format <format>", "Output format", "table")
|
|
86
|
-
.option("--output <path>")
|
|
86
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
87
87
|
.action((options) => withClient(async (client) => {
|
|
88
88
|
const format = parseOutputFormat(options.format);
|
|
89
89
|
requireIndicatorScope(options.indicator, options.security);
|
|
@@ -102,7 +102,7 @@ indicator.command("screener")
|
|
|
102
102
|
.option("--indicator-param <spec>", "Per-variable param 'F1:key=value', e.g. F1:scale=8 (repeat); read exact keys from 'indicator search'. Bare 'F1:' (nothing after the colon) declares that the indicator takes NO date — required by any indicator whose parameterList has no date key at all, which otherwise rejects the tradeDate --date injects: the pty_* / scr_* static-attribute families (pty_op_scope, scr_exchg_sctr, scr_isin …), plus div_cash_paid_ratio / div_cash_yr (add 'F1:fiscalYear=YYYY' too) and pty_shr_reg. It composes with real params, so 'F1:' + 'F1:scale=8' keeps the scale", collectNames, [])
|
|
103
103
|
.addOption(new Option("--key-by <mode>", "Column key: name=display name (default) | code=indicatorCode").choices(["name", "code"]).default("name"))
|
|
104
104
|
.option("--format <format>", "Output format", "table")
|
|
105
|
-
.option("--output <path>")
|
|
105
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
106
106
|
.action((options) => withClient(async (client) => {
|
|
107
107
|
const format = parseOutputFormat(options.format);
|
|
108
108
|
requireIndicatorScope(options.indicator, options.security);
|
|
@@ -3,7 +3,7 @@ import { collectList, dateArg, datetimeArg, maybeArray, parseChoiceList, parseEn
|
|
|
3
3
|
import { ValidationError } from "../core/errors.js";
|
|
4
4
|
import { fetchOpinionDetails } from "../core/opinionDetail.js";
|
|
5
5
|
import { rowCount } from "../core/rowSink.js";
|
|
6
|
-
import { emit, addDownloadCommand, confirmCostly, multiChoice, field, value, required, list, numberList, choiceList, count, rankType, searchType, top, flag, format, output, from, size, startTime, endTime, timeFilters, query, RESEARCH_AREA_CITIC_OR_DIRECTION } from "./shared.js";
|
|
6
|
+
import { emit, addDownloadCommand, confirmCostly, multiChoice, field, value, required, list, numberList, choiceList, count, rankType, searchType, top, flag, format, output, from, size, startTime, endTime, timeFilters, query, RESEARCH_AREA_CITIC_OR_DIRECTION, OUTPUT_HELP, securityCode } from "./shared.js";
|
|
7
7
|
export const insight = new Command("insight").description("Insight APIs");
|
|
8
8
|
const SW_INDUSTRY_ONLY = "Industry ID -- swIndustry codes only (104xx0000); citicIndustry codes are rejected with 100005 even where constant-category declares them";
|
|
9
9
|
const opinion = new Command("opinion");
|
|
@@ -21,6 +21,7 @@ const announcementHk = new Command("announcement-hk");
|
|
|
21
21
|
const announcementUs = new Command("announcement-us");
|
|
22
22
|
const foreignOpinion = new Command("foreign-opinion");
|
|
23
23
|
const independentOpinion = new Command("independent-opinion");
|
|
24
|
+
const overseasOpinion = new Command("overseas-opinion");
|
|
24
25
|
const officialAccount = new Command("official-account");
|
|
25
26
|
const qa = new Command("qa");
|
|
26
27
|
const reportImage = new Command("report-image");
|
|
@@ -46,7 +47,7 @@ query(opinion, "list", {
|
|
|
46
47
|
list("--broker-tag <tag>", "Broker tag: topBroker/newFortune", "brokerTagList"),
|
|
47
48
|
list("--source <source>", "Source", "sourceList"),
|
|
48
49
|
flag("--with-content", "Return the full body inline (v1 list, 30 credits/row) instead of the 200-char brief (1 credit/row). v1 shape: title and body sit under contentList.title / contentList.content, and there is no brief. Takes no --category / --broker-tag, and --llm-tag only strongRcmd/earningsReview/topBroker/newFortune"),
|
|
49
|
-
format(), output(
|
|
50
|
+
format(), output(),
|
|
50
51
|
...timeFilters(),
|
|
51
52
|
],
|
|
52
53
|
});
|
|
@@ -62,7 +63,7 @@ query(summary, "list", {
|
|
|
62
63
|
list("--category <name>", "Category", "categoryList"),
|
|
63
64
|
list("--market <name>", "Market", "marketList"),
|
|
64
65
|
list("--participant-role <name>", "Participant role", "participantRoleList"),
|
|
65
|
-
format(), output(
|
|
66
|
+
format(), output(),
|
|
66
67
|
...timeFilters(),
|
|
67
68
|
],
|
|
68
69
|
});
|
|
@@ -79,7 +80,7 @@ query(pamirsSummary, "list", {
|
|
|
79
80
|
list("--security <code>", "Security code, e.g. 000001.SZ", "securityList"),
|
|
80
81
|
choiceList("--category <name>", `Category: ${PAMIRS_CATEGORIES.join(" / ")}`, "categoryList", PAMIRS_CATEGORIES),
|
|
81
82
|
choiceList("--market <name>", `Market: ${PAMIRS_MARKETS.join(" / ")}`, "marketList", PAMIRS_MARKETS),
|
|
82
|
-
format(), output(
|
|
83
|
+
format(), output(),
|
|
83
84
|
...timeFilters(),
|
|
84
85
|
],
|
|
85
86
|
});
|
|
@@ -102,7 +103,7 @@ const addScheduleList = (command, endpointKey, fields) => query(command, "list",
|
|
|
102
103
|
fields.brokerType && list("--broker-type <name>", "Lead broker type: cnBroker/otherBroker", "brokerTypeList"),
|
|
103
104
|
fields.permission && numberList("--permission <number>", "Permission: 1=public 2=private", "permission"),
|
|
104
105
|
fields.location && list("--location <id>", "Location ID (domesticCity constant, via 'reference constant-list')", "locationList"),
|
|
105
|
-
format(), output(
|
|
106
|
+
format(), output(),
|
|
106
107
|
...timeFilters(),
|
|
107
108
|
publishTime("--publish-start-time <datetime>", "Collected from (publishTime, when the schedule first came in — not when it is held); combines with --start-time/--end-time", "publishStartTime"),
|
|
108
109
|
publishTime("--publish-end-time <datetime>", "Collected until (publishTime); a bare date covers that whole day", "publishEndTime"),
|
|
@@ -146,7 +147,7 @@ performanceCalendar.command("list").description("Earnings calendar (业绩预告
|
|
|
146
147
|
.option("--security <code>", "Security code (e.g. 000001.SZ)", collectList, [])
|
|
147
148
|
.addOption(multiChoice("--market <name>", `Market: ${PERFORMANCE_MARKETS.join("/")}`, PERFORMANCE_MARKETS))
|
|
148
149
|
.addOption(multiChoice("--category <name>", `Event type: ${PERFORMANCE_CATEGORIES.join("/")}`, PERFORMANCE_CATEGORIES))
|
|
149
|
-
.option("--format <format>", "Output format", "table").option("--output <path>",
|
|
150
|
+
.option("--format <format>", "Output format", "table").option("--output <path>", OUTPUT_HELP)
|
|
150
151
|
.addOption(confirmCostly().option)
|
|
151
152
|
.action((options) => {
|
|
152
153
|
const marketList = parseChoiceList(options.market, "--market", PERFORMANCE_MARKETS);
|
|
@@ -186,7 +187,7 @@ query(research, "list", {
|
|
|
186
187
|
count("--min-pages <number>", "Min report pages", "minReportPages"),
|
|
187
188
|
count("--max-pages <number>", "Max report pages", "maxReportPages"),
|
|
188
189
|
list("--source <type>", "Source type", "sourceList"),
|
|
189
|
-
format(), output(
|
|
190
|
+
format(), output(),
|
|
190
191
|
...timeFilters(),
|
|
191
192
|
],
|
|
192
193
|
});
|
|
@@ -206,7 +207,7 @@ query(foreignReport, "list", {
|
|
|
206
207
|
list("--rating-change <name>", "Rating change", "ratingChangeList"),
|
|
207
208
|
count("--min-pages <number>", "Min report pages", "minReportPages"),
|
|
208
209
|
count("--max-pages <number>", "Max report pages", "maxReportPages"),
|
|
209
|
-
format(), output(
|
|
210
|
+
format(), output(),
|
|
210
211
|
...timeFilters(),
|
|
211
212
|
],
|
|
212
213
|
});
|
|
@@ -218,7 +219,7 @@ query(announcement, "list", {
|
|
|
218
219
|
searchType(), rankType(),
|
|
219
220
|
list("--security <code>", "Security code", "securityList"),
|
|
220
221
|
list("--category <id>", "Category ID", "categoryList"),
|
|
221
|
-
format(), output(
|
|
222
|
+
format(), output(),
|
|
222
223
|
...timeFilters(parseTimestamp13, parseEndTimestamp13),
|
|
223
224
|
],
|
|
224
225
|
});
|
|
@@ -230,7 +231,7 @@ query(announcementHk, "list", {
|
|
|
230
231
|
searchType(), rankType(),
|
|
231
232
|
list("--security <code>", "Security code (e.g. 01913.HK)", "securityList"),
|
|
232
233
|
list("--category <id>", "Category ID", "categoryList"),
|
|
233
|
-
format(), output(
|
|
234
|
+
format(), output(),
|
|
234
235
|
...timeFilters(),
|
|
235
236
|
],
|
|
236
237
|
});
|
|
@@ -242,7 +243,7 @@ query(announcementUs, "list", {
|
|
|
242
243
|
searchType(), rankType(),
|
|
243
244
|
list("--security <code>", "Security code (e.g. TSLA.O)", "securityList"),
|
|
244
245
|
list("--category <id>", "Category ID (constant-list usShareAnnouncementCategory)", "categoryList"),
|
|
245
|
-
format(), output(
|
|
246
|
+
format(), output(),
|
|
246
247
|
...timeFilters(),
|
|
247
248
|
],
|
|
248
249
|
});
|
|
@@ -258,17 +259,17 @@ query(foreignOpinion, "list", {
|
|
|
258
259
|
list("--rating <name>", "Rating", "ratingList"),
|
|
259
260
|
list("--rating-change <name>", "Rating change", "ratingChangeList"),
|
|
260
261
|
flag("--with-content", "Return the full body inline (v1 list, 30 credits/row) as content / contentTranslate, instead of the 200-char brief (1 credit/row)"),
|
|
261
|
-
format(), output(
|
|
262
|
+
format(), output(),
|
|
262
263
|
...timeFilters(),
|
|
263
264
|
],
|
|
264
265
|
});
|
|
265
266
|
opinion.command("detail").description("Full bodies of domestic chief opinions by ID (30 credits per returned opinion; batched 20 per call)")
|
|
266
267
|
.requiredOption("--chief-opinion-id <id>", "chiefOpinionId from 'insight opinion list' (repeat or comma-separate)", collectList)
|
|
267
|
-
.option("--format <format>", "Output format", "json").option("--output <path>")
|
|
268
|
+
.option("--format <format>", "Output format", "json").option("--output <path>", OUTPUT_HELP)
|
|
268
269
|
.action((options) => emit(options, (client) => fetchOpinionDetails(client, "insight.opinion.detail", "chiefOpinionIdList", "chiefOpinionId", options.chiefOpinionId)));
|
|
269
270
|
foreignOpinion.command("detail").description("Full bodies (content + contentTranslate) of foreign opinions by ID (30 credits per returned opinion; batched 20 per call)")
|
|
270
271
|
.requiredOption("--foreign-opinion-id <id>", "foreignOpinionId from 'insight foreign-opinion list' (repeat or comma-separate)", collectList)
|
|
271
|
-
.option("--format <format>", "Output format", "json").option("--output <path>")
|
|
272
|
+
.option("--format <format>", "Output format", "json").option("--output <path>", OUTPUT_HELP)
|
|
272
273
|
.action((options) => emit(options, (client) => fetchOpinionDetails(client, "insight.foreign-opinion.detail", "foreignOpinionIdList", "foreignOpinionId", options.foreignOpinionId)));
|
|
273
274
|
query(independentOpinion, "list", {
|
|
274
275
|
endpoint: "insight.independent-opinion.list",
|
|
@@ -278,11 +279,30 @@ query(independentOpinion, "list", {
|
|
|
278
279
|
list("--industry <id>", SW_INDUSTRY_ONLY, "industryList"),
|
|
279
280
|
list("--rating <name>", "Rating", "ratingList"),
|
|
280
281
|
list("--rating-change <name>", "Rating change", "ratingChangeList"),
|
|
281
|
-
format(), output(
|
|
282
|
+
format(), output(),
|
|
282
283
|
...timeFilters(),
|
|
283
284
|
],
|
|
284
285
|
});
|
|
285
286
|
addDownloadCommand(independentOpinion, { endpointKey: "insight.independent-opinion.download", idOption: "--independent-opinion-id", idField: "independentOpinionId", fallbackPrefix: "independent-opinion", fileType: { description: "File type: 1=original HTML 2=CN-translated HTML", choices: ["1", "2"], required: true } });
|
|
287
|
+
query(overseasOpinion, "list", {
|
|
288
|
+
description: "List overseas opinions: brief + briefTranslate, body via 'overseas-opinion detail' (0.1 credits/row)",
|
|
289
|
+
endpoint: "insight.overseas-opinion.list",
|
|
290
|
+
fields: [
|
|
291
|
+
rankType(),
|
|
292
|
+
list("--account <id>", "Account ID from 'reference overseas-account-search' (repeat or comma-separate)", "accountIdList"),
|
|
293
|
+
list("--security <code>", "Security code with market suffix, e.g. NVDA.O. A missing suffix is rejected (120001); a real suffix of the wrong market (NVDA.N) matches nothing", "securityList"),
|
|
294
|
+
list("--category <name>", "Category: opinion/news/dataAnalysis/promotion (repeat). A row in several categories is returned under each; rows without a category only when this is omitted", "categoryList"),
|
|
295
|
+
list("--industry <id>", "GICS sector ID: 107700001 energy ... 107700011 real estate (list: 'reference constant-list --category gicsIndustry'). Other industry code sets are rejected (100005)", "industryList"),
|
|
296
|
+
list("--region <code>", "Region code: cn/cnHk/cnTw/us/jp/kr/uk/gl; other regionCategory values are rejected (100005)", "regionList"),
|
|
297
|
+
count("--original-flag <number>", "1=original posts only 0=reposts only; omit for both", "originalFlag"),
|
|
298
|
+
format(), output(),
|
|
299
|
+
...timeFilters(),
|
|
300
|
+
],
|
|
301
|
+
});
|
|
302
|
+
overseasOpinion.command("detail").description("Full bodies (content + contentTranslate) of overseas opinions by ID (10 credits per returned opinion; batched 20 per call)")
|
|
303
|
+
.requiredOption("--overseas-opinion-id <id>", "overseasOpinionId from 'insight overseas-opinion list' (repeat or comma-separate)", collectList)
|
|
304
|
+
.option("--format <format>", "Output format", "json").option("--output <path>", OUTPUT_HELP)
|
|
305
|
+
.action((options) => emit(options, (client) => fetchOpinionDetails(client, "insight.overseas-opinion.detail", "overseasOpinionIdList", "overseasOpinionId", options.overseasOpinionId)));
|
|
286
306
|
query(officialAccount, "list", {
|
|
287
307
|
endpoint: "insight.official-account.list",
|
|
288
308
|
cache: { endpointKey: "insight.official-account.list", idField: "articleId" },
|
|
@@ -292,7 +312,7 @@ query(officialAccount, "list", {
|
|
|
292
312
|
list("--security <code>", "Security code (e.g. 000001.SZ)", "securityList"),
|
|
293
313
|
list("--category <type>", "Article type: news/law/report/view/data/event/meeting/notice/recruit/investEdu/brand/notes/other", "categoryList"),
|
|
294
314
|
list("--industry <id>", "Industry ID (constant-list citicIndustry/swIndustry)", "industryList"),
|
|
295
|
-
format(), output(
|
|
315
|
+
format(), output(),
|
|
296
316
|
...timeFilters(),
|
|
297
317
|
],
|
|
298
318
|
});
|
|
@@ -300,7 +320,7 @@ addDownloadCommand(officialAccount, { endpointKey: "insight.official-account.dow
|
|
|
300
320
|
query(qa, "list", {
|
|
301
321
|
endpoint: "insight.qa.list",
|
|
302
322
|
fields: [
|
|
303
|
-
|
|
323
|
+
securityCode("Security code, e.g. 601012.SH"),
|
|
304
324
|
from(), size("Total rows to return; omit to fetch all (max page 500)"),
|
|
305
325
|
startTime("Start time (yyyy-MM-dd or yyyy-MM-dd HH:mm:ss)"), endTime("End time (yyyy-MM-dd or yyyy-MM-dd HH:mm:ss)"),
|
|
306
326
|
list("--source <type>", "Source: conference/interactive/survey (repeat)", "source"),
|
|
@@ -335,6 +355,7 @@ insight.addCommand(announcementHk);
|
|
|
335
355
|
insight.addCommand(announcementUs);
|
|
336
356
|
insight.addCommand(foreignOpinion);
|
|
337
357
|
insight.addCommand(independentOpinion);
|
|
358
|
+
insight.addCommand(overseasOpinion);
|
|
338
359
|
insight.addCommand(officialAccount);
|
|
339
360
|
insight.addCommand(qa);
|
|
340
361
|
insight.addCommand(reportImage);
|
|
@@ -8,7 +8,7 @@ import { flagMissingFields } from "../core/normalize.js";
|
|
|
8
8
|
import { parseOutputFormat } from "../core/output.js";
|
|
9
9
|
import { printData } from "../core/printer.js";
|
|
10
10
|
import { rowCount } from "../core/rowSink.js";
|
|
11
|
-
import { emit, withClient, DEFAULT_QUOTE_LIMIT, MAX_QUOTE_LIMIT, flagIfLimitTruncated, noteLateStart } from "./shared.js";
|
|
11
|
+
import { emit, withClient, DEFAULT_QUOTE_LIMIT, MAX_QUOTE_LIMIT, flagIfLimitTruncated, noteLateStart, OUTPUT_HELP } from "./shared.js";
|
|
12
12
|
export const quote = new Command("quote").description("Quote APIs");
|
|
13
13
|
const KLINE_MARKETS = { aShares: 1, hkStocks: 2, usStocks: 1 };
|
|
14
14
|
const LEGACY_ALL_MARKET = (shardDays) => ({ all: shardDays });
|
|
@@ -48,7 +48,7 @@ const addKlineCommand = (name, endpointKey, securityHelp, markets, noKeywordReas
|
|
|
48
48
|
.option("--limit <number>", `Max rows per request (default: ${DEFAULT_QUOTE_LIMIT}, max: ${MAX_QUOTE_LIMIT}). With several securities it also sizes the groups they are fetched in, so a small value fetches one security per request`)
|
|
49
49
|
.option("--field <field>", "Field", collectList, [])
|
|
50
50
|
.option("--format <format>", "Output format", "table")
|
|
51
|
-
.option("--output <path>")
|
|
51
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
52
52
|
.action((options) => {
|
|
53
53
|
checkMarketKeywords(options.security, Object.keys(markets), `quote ${name}`, noKeywordReason);
|
|
54
54
|
options.security = canonicalizeMarketKeywords(options.security, Object.keys(markets));
|
|
@@ -100,7 +100,7 @@ quote.command("minute-kline")
|
|
|
100
100
|
.option("--limit <number>", `Max rows per request (default: ${DEFAULT_QUOTE_LIMIT}, max: ${MAX_QUOTE_LIMIT})`)
|
|
101
101
|
.option("--field <field>", "Field", collectList, [])
|
|
102
102
|
.option("--format <format>", "Output format", "table")
|
|
103
|
-
.option("--output <path>")
|
|
103
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
104
104
|
.action((options) => withClient(options, async (client) => {
|
|
105
105
|
const format = parseOutputFormat(options.format);
|
|
106
106
|
client.rowSink?.watchEarliest("tradeTime");
|
|
@@ -127,7 +127,7 @@ quote.command("realtime")
|
|
|
127
127
|
.option("--security <code>", "Security code — stock .SH/.SZ/.BJ/.HK/.O/.N/.A, ETF .SH/.SZ (e.g. 512800.SH), exchange index .SH/.SZ/.BJ, concept index .GT, industry index .CI/.SWI, global index (e.g. SPX.SPI / N225.NKI / HSI.HI); or one market keyword: aShares / hkStocks / usStocks (must be passed alone; keywords cover stocks only — ETFs and indices have no whole-market keyword)", collectList, [])
|
|
128
128
|
.option("--field <field>", "Field", collectList, [])
|
|
129
129
|
.option("--format <format>", "Output format", "table")
|
|
130
|
-
.option("--output <path>")
|
|
130
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
131
131
|
.action((options) => {
|
|
132
132
|
checkMarketKeywords(options.security, REALTIME_MARKETS, "quote realtime");
|
|
133
133
|
return emit(options, async (client) => {
|
|
@@ -147,7 +147,7 @@ quote.command("fund-flow")
|
|
|
147
147
|
.option("--limit <number>", `Max rows per request (default: ${DEFAULT_QUOTE_LIMIT}, max: ${MAX_QUOTE_LIMIT}; single-security cap — aShares auto-shards by day)`)
|
|
148
148
|
.option("--field <field>", "Field, e.g. mainNetInflow/largeInflow/xlargeOutflow (repeat); omit for all", collectList, [])
|
|
149
149
|
.option("--format <format>", "Output format", "table")
|
|
150
|
-
.option("--output <path>")
|
|
150
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
151
151
|
.action((options) => {
|
|
152
152
|
checkMarketKeywords(options.security, FUND_FLOW_MARKETS, "quote fund-flow");
|
|
153
153
|
options.security = canonicalizeMarketKeywords(options.security, FUND_FLOW_MARKETS);
|
|
@@ -187,7 +187,7 @@ quote.command("sector-fund-flow")
|
|
|
187
187
|
.option("--limit <number>", `Max rows returned (default: ${DEFAULT_QUOTE_LIMIT}, max: ${MAX_QUOTE_LIMIT}). It cuts the answer only: a query too heavy to compute fails with 100006 whatever the limit — shorten the range, or pass fewer sectors or fields`)
|
|
188
188
|
.option("--field <field>", "Field, e.g. sectorMainNetInflow / sectorLargeInflow (repeat); omit for all. sectorId and tradeDate always come first", collectList, [])
|
|
189
189
|
.option("--format <format>", "Output format", "table")
|
|
190
|
-
.option("--output <path>")
|
|
190
|
+
.option("--output <path>", OUTPUT_HELP)
|
|
191
191
|
.action((options) => emit(options, async (client) => {
|
|
192
192
|
const limit = parseOptionalNumberOption(options.limit, "--limit", { integer: true, min: 1, max: MAX_QUOTE_LIMIT }) ?? DEFAULT_QUOTE_LIMIT;
|
|
193
193
|
const fieldList = maybeArray(options.field);
|
package/dist/src/commands/raw.js
CHANGED
|
@@ -5,8 +5,8 @@ import { ConfigError, ValidationError } from "../core/errors.js";
|
|
|
5
5
|
import { flagFailedItems } from "../core/normalize.js";
|
|
6
6
|
import { parseOutputFormat } from "../core/output.js";
|
|
7
7
|
import { printData } from "../core/printer.js";
|
|
8
|
-
import { createClient, runDownload, assertConfirmed } from "./shared.js";
|
|
9
|
-
export const raw = new Command("raw").description("Raw API calls").addCommand(new Command("call").argument("<endpointKey>").option("--body <json>").option("--query <key=value>", "Query string pair", collectKeyValue, {}).option("--yes", "Confirm an irreversible endpoint (required for the ones marked destructive), or fetch every row of a per-row billed list past the credit guard").option("--format <format>", "Output format", "json").option("--output <path>").action(async (endpointKey, options) => {
|
|
8
|
+
import { createClient, runDownload, assertConfirmed, OUTPUT_HELP } from "./shared.js";
|
|
9
|
+
export const raw = new Command("raw").description("Raw API calls").addCommand(new Command("call").argument("<endpointKey>").option("--body <json>", "Request body as a JSON string").option("--query <key=value>", "Query string pair", collectKeyValue, {}).option("--yes", "Confirm an irreversible endpoint (required for the ones marked destructive), or fetch every row of a per-row billed list past the credit guard").option("--format <format>", "Output format", "json").option("--output <path>", OUTPUT_HELP).action(async (endpointKey, options) => {
|
|
10
10
|
const endpoint = ENDPOINTS[endpointKey];
|
|
11
11
|
if (!endpoint) {
|
|
12
12
|
throw new ConfigError(`Unknown endpoint key: ${endpointKey}`);
|
|
@@ -47,4 +47,4 @@ export const raw = new Command("raw").description("Raw API calls").addCommand(ne
|
|
|
47
47
|
finally {
|
|
48
48
|
await client.rowSink?.abort();
|
|
49
49
|
}
|
|
50
|
-
})).addCommand(new Command("list").description("List all registered endpoint keys (for use with 'raw call')").option("--format <format>", "Output format", "table").option("--output <path>").action((options) => printData(listEndpoints(), parseOutputFormat(options.format), options.output)));
|
|
50
|
+
})).addCommand(new Command("list").description("List all registered endpoint keys (for use with 'raw call')").option("--format <format>", "Output format", "table").option("--output <path>", OUTPUT_HELP).action((options) => printData(listEndpoints(), parseOutputFormat(options.format), options.output)));
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
import { value, required, requiredList, date, choiceList, top, format, output, query } from "./shared.js";
|
|
2
|
+
import { value, required, requiredList, date, choiceList, top, format, output, from, size, query } from "./shared.js";
|
|
3
3
|
export const reference = new Command("reference").description("Reference data APIs");
|
|
4
4
|
query(reference, "securities-search", {
|
|
5
5
|
endpoint: "reference.securities-search",
|
|
6
6
|
fields: [
|
|
7
7
|
required("--keyword <text>", "Search keyword (name/code/pinyin/English)", "keyword"),
|
|
8
|
-
choiceList("--category <type>", "Category: stock/dr/index/fund/bond.
|
|
8
|
+
choiceList("--category <type>", "Category: stock/dr/index/fund/bond. Without it, a convertible bond can rank above its stock on a short name", "category", ["stock", "dr", "index", "fund", "bond"]),
|
|
9
9
|
top(10),
|
|
10
10
|
format(), output(),
|
|
11
11
|
],
|
|
@@ -73,9 +73,18 @@ query(reference, "institution-search", {
|
|
|
73
73
|
query(reference, "official-account-search", {
|
|
74
74
|
endpoint: "reference.official-account-search",
|
|
75
75
|
fields: [
|
|
76
|
-
required("--keyword <text>", "
|
|
76
|
+
required("--keyword <text>", "Account name fragment, e.g. 中信证券 (finds 中信证券研究). Matches the account name: an institution's full name misses accounts named by short form (东吴证券 finds none, 东吴 finds 东吴策略陈刚团队)", "keyword"),
|
|
77
77
|
choiceList("--category <name>", "Category: listedCompany/broker/government/media (repeat); omit for all incl. uncategorized", "category", ["listedCompany", "broker", "government", "media"]),
|
|
78
78
|
top(10),
|
|
79
79
|
format(), output(),
|
|
80
80
|
],
|
|
81
81
|
});
|
|
82
|
+
query(reference, "overseas-account-search", {
|
|
83
|
+
description: "Search overseas opinion account IDs by name fragment, for 'insight overseas-opinion list --account' (free)",
|
|
84
|
+
endpoint: "reference.overseas-account-search",
|
|
85
|
+
fields: [
|
|
86
|
+
required("--keyword <text>", "Account name fragment: substring match anywhere in the name, case-insensitive (e.g. elo matches Elon Musk and Financelot)", "accountName"),
|
|
87
|
+
from(), size(),
|
|
88
|
+
format(), output(),
|
|
89
|
+
],
|
|
90
|
+
});
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { Option } from "commander";
|
|
3
|
+
import { InvalidArgumentError, Option } from "commander";
|
|
4
4
|
import { QUOTE_MAX_LIMIT } from "../core/quoteSharding.js";
|
|
5
5
|
import { collectList, dateArg, datetimeArg, maybeArray, numberListArg, parseChoiceList, parseFrom, parseNumberOption, parseOptionalNumberOption, parseSize } from "../core/args.js";
|
|
6
6
|
import { loadConfig } from "../core/config.js";
|
|
7
|
-
import { placeStreamedDownload, releaseClaim, resolveTitle, saveDownloadResult, uniquePath } from "../core/download.js";
|
|
7
|
+
import { assertOutputFilePath, placeStreamedDownload, releaseClaim, resolveTitle, saveDownloadResult, uniquePath } from "../core/download.js";
|
|
8
|
+
import { isDryRun } from "../core/dryRun.js";
|
|
8
9
|
import { ENDPOINTS } from "../core/endpoints.js";
|
|
9
10
|
import { ValidationError } from "../core/errors.js";
|
|
10
11
|
import { parseOutputFormat, stagingPath } from "../core/output.js";
|
|
11
|
-
import { printData } from "../core/printer.js";
|
|
12
|
+
import { columnsSelected, printData } from "../core/printer.js";
|
|
12
13
|
import { ExportSink, getRowSink } from "../core/rowSink.js";
|
|
13
14
|
export async function createClient(stream) {
|
|
14
15
|
const { GangtiseClient } = await import("../core/client.js");
|
|
15
|
-
const sink = stream?.output && (stream.format === "jsonl" || stream.format === "csv") ? new ExportSink(stream.output, stream.format, stream.cache) : undefined;
|
|
16
|
+
const sink = stream?.output && (stream.format === "jsonl" || stream.format === "csv") && !columnsSelected() ? new ExportSink(stream.output, stream.format, stream.cache) : undefined;
|
|
16
17
|
const client = new GangtiseClient(loadConfig(), sink);
|
|
17
18
|
client.allowCostlyFetch = Boolean(stream?.yes);
|
|
18
19
|
return client;
|
|
@@ -89,6 +90,7 @@ export function noteLateStart(data, requestedStart, field, label, cause = "The s
|
|
|
89
90
|
}
|
|
90
91
|
export async function runDownload(client, endpointKey, query, options) {
|
|
91
92
|
if (options.output) {
|
|
93
|
+
await assertOutputFilePath(options.output);
|
|
92
94
|
const result = await client.call(endpointKey, options.body, query, { streamTo: options.output });
|
|
93
95
|
await saveDownloadResult(result, options.fallbackName, options.output);
|
|
94
96
|
return;
|
|
@@ -120,7 +122,7 @@ export async function runDownload(client, endpointKey, query, options) {
|
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
export function addDownloadCommand(parent, spec) {
|
|
123
|
-
const cmd = parent.command(spec.name ?? "download").requiredOption(`${spec.idOption} <id
|
|
125
|
+
const cmd = parent.command(spec.name ?? "download").requiredOption(`${spec.idOption} <id>`, `The ${spec.idField} of the record, from the matching list command`);
|
|
124
126
|
if (spec.fileType) {
|
|
125
127
|
const option = new Option("--file-type <number>", spec.fileType.description).choices(spec.fileType.choices);
|
|
126
128
|
cmd.addOption(spec.fileType.required ? option.makeOptionMandatory() : option.default(spec.fileType.default));
|
|
@@ -132,7 +134,7 @@ export function addDownloadCommand(parent, spec) {
|
|
|
132
134
|
if (spec.titleListEndpoint) {
|
|
133
135
|
cmd.option("--resolve-title", "On a title-cache miss, query the list endpoint for a friendly filename (4 extra requests; most of these list endpoints bill per row). Without it the server filename or <prefix>-<id> is used. Ignored when --output is given");
|
|
134
136
|
}
|
|
135
|
-
cmd.option("--output <path>").action((options) => withClient(async (client) => {
|
|
137
|
+
cmd.option("--output <path>", DOWNLOAD_OUTPUT_HELP).action((options) => withClient(async (client) => {
|
|
136
138
|
const id = options[spec.idField];
|
|
137
139
|
const qp = { [spec.idField]: id };
|
|
138
140
|
if (spec.fileType && options.fileType)
|
|
@@ -151,7 +153,7 @@ export function addDownloadCommand(parent, spec) {
|
|
|
151
153
|
}
|
|
152
154
|
export function assertConfirmed(endpointKey, confirmed, target) {
|
|
153
155
|
const destructive = ENDPOINTS[endpointKey]?.destructive;
|
|
154
|
-
if (!destructive || confirmed)
|
|
156
|
+
if (!destructive || confirmed || isDryRun())
|
|
155
157
|
return;
|
|
156
158
|
throw new ValidationError(`${destructive.warning} 确认要对 ${target} 执行就加上 --yes。`);
|
|
157
159
|
}
|
|
@@ -201,8 +203,18 @@ export const rankType = () => numberEnum("--rank-type <number>", "Rank type: 1=c
|
|
|
201
203
|
export const searchType = () => numberEnum("--search-type <number>", "Search type: 1=title 2=fulltext", "searchType", ["1", "2"], "1");
|
|
202
204
|
export const top = (max) => field(new Option("--top <number>", `Max results (default: 10, max: ${max})`).default("10"), (v) => ({ top: parseNumberOption(v, "--top", { integer: true, min: 1, max }) }));
|
|
203
205
|
export const flag = (flags, description) => field(new Option(flags, description));
|
|
206
|
+
export const OUTPUT_HELP = "Write the result to this file instead of stdout (a file path, not a directory)";
|
|
207
|
+
export const DOWNLOAD_OUTPUT_HELP = "Save the file to this path (a file path, not a directory); omit for an automatic name in the current directory";
|
|
208
|
+
export const SECURITY_CODE_FLAGS = "--security, --security-code <code>";
|
|
209
|
+
export const SECURITY_CODE_HELP = "Security code, e.g. 600519.SH";
|
|
210
|
+
export const oneSecurityCode = (value) => {
|
|
211
|
+
if (value.includes(","))
|
|
212
|
+
throw new InvalidArgumentError("takes one security code; run the command once per code");
|
|
213
|
+
return value;
|
|
214
|
+
};
|
|
204
215
|
export const format = (fallback = "table") => field(new Option("--format <format>", "Output format").default(fallback));
|
|
205
|
-
export const output = (description) => field(new Option("--output <path>", description));
|
|
216
|
+
export const output = (description = OUTPUT_HELP) => field(new Option("--output <path>", description));
|
|
217
|
+
export const securityCode = (description = SECURITY_CODE_HELP) => field(new Option(SECURITY_CODE_FLAGS, description).makeOptionMandatory().argParser(oneSecurityCode), (v) => ({ securityCode: v }));
|
|
206
218
|
export const from = () => field(new Option("--from <number>", "Starting offset").default("0"), (v) => ({ from: parseFrom(v) }));
|
|
207
219
|
export const size = (description = "Total rows to return; omit to fetch all") => field(new Option("--size <number>", description), (v) => ({ size: parseSize(v) }));
|
|
208
220
|
const asTyped = (v) => v;
|
|
@@ -2,15 +2,18 @@ import { Command, Option } from "commander";
|
|
|
2
2
|
import { POLL_MAX_ATTEMPTS } from "../core/asyncContent.js";
|
|
3
3
|
import { collectList, maybeArray, parseNumberOption, parseOptionalNumberOption } from "../core/args.js";
|
|
4
4
|
import { ValidationError } from "../core/errors.js";
|
|
5
|
+
import { assertOutputFilePath } from "../core/download.js";
|
|
5
6
|
import { fetchFileParseResult, pollFileParseResult, submitFileParse } from "../core/fileParse.js";
|
|
6
7
|
import { markFailed } from "../core/exitStatus.js";
|
|
7
|
-
import { emit, withClient } from "./shared.js";
|
|
8
|
+
import { emit, withClient, OUTPUT_HELP } from "./shared.js";
|
|
8
9
|
export const tool = new Command("tool").description("Research tool APIs: PDF parsing");
|
|
9
10
|
tool.command("file-parse").description("Parse a PDF into Markdown + images (async; 0.8 credits/page, billed at submit)")
|
|
10
11
|
.requiredOption("--file <path>", "PDF to upload (max 100MB / 500 pages)")
|
|
11
12
|
.option("--wait", "Wait for the parse to finish and save the result ZIP (blocking, up to ~5 min)")
|
|
12
|
-
.option("--output <path>", "Where to save the result ZIP (used with --wait)")
|
|
13
|
+
.option("--output <path>", "Where to save the result ZIP (a file path, not a directory; used with --wait)")
|
|
13
14
|
.action((options) => withClient(async (client) => {
|
|
15
|
+
if (options.wait && options.output)
|
|
16
|
+
await assertOutputFilePath(options.output);
|
|
14
17
|
const taskId = await submitFileParse(client, options.file);
|
|
15
18
|
if (!options.wait) {
|
|
16
19
|
process.stderr.write(`File parse task submitted. taskId: ${taskId}\n`);
|
|
@@ -25,8 +28,10 @@ tool.command("file-parse").description("Parse a PDF into Markdown + images (asyn
|
|
|
25
28
|
}));
|
|
26
29
|
tool.command("file-parse-check").description("Download a finished file-parse result ZIP by taskId (free)")
|
|
27
30
|
.requiredOption("--task-id <id>", "taskId from 'tool file-parse'")
|
|
28
|
-
.option("--output <path>", "Where to save the result ZIP")
|
|
31
|
+
.option("--output <path>", "Where to save the result ZIP (a file path, not a directory)")
|
|
29
32
|
.action((options) => withClient(async (client) => {
|
|
33
|
+
if (options.output)
|
|
34
|
+
await assertOutputFilePath(options.output);
|
|
30
35
|
if (await fetchFileParseResult(client, options.taskId, options.output) === "pending") {
|
|
31
36
|
process.stdout.write(`${JSON.stringify({ taskId: options.taskId, status: "pending", hint: "Parse not finished yet, retry in ~1 minute" })}\n`);
|
|
32
37
|
}
|
|
@@ -39,7 +44,7 @@ tool.command("web-search").description("Search the public web for research: dedu
|
|
|
39
44
|
.option("--site <domain>", "Restrict to a registered domain or subdomain, e.g. csrc.gov.cn (repeatable, max 10, OR-ed)", collectList, [])
|
|
40
45
|
.option("--include-content", "Return page body as Markdown; caps --size at 5")
|
|
41
46
|
.option("--max-content-chars <number>", "Max chars per body, 1000-20000 (only with --include-content)")
|
|
42
|
-
.option("--format <format>", "Output format", "table").option("--output <path>")
|
|
47
|
+
.option("--format <format>", "Output format", "table").option("--output <path>", OUTPUT_HELP)
|
|
43
48
|
.action((options) => emit(options, (client) => {
|
|
44
49
|
const size = parseNumberOption(options.size ?? (options.includeContent ? "5" : "10"), "--size", { integer: true, min: 1, max: 20 });
|
|
45
50
|
if (options.includeContent && size > 5)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import { Command } from "commander";
|
|
3
3
|
import { isVersionNewer } from "../core/args.js";
|
|
4
|
+
import { isDryRun } from "../core/dryRun.js";
|
|
4
5
|
import { installVersion, refreshSkillCopies } from "../core/selfUpdate.js";
|
|
5
6
|
import { latestVersion, PACKAGE_NAME, REGISTRY } from "../core/updateCheck.js";
|
|
6
7
|
import { CLI_VERSION, PACKAGE_ROOT } from "../version.js";
|
|
@@ -9,6 +10,16 @@ export const update = new Command("update")
|
|
|
9
10
|
.option("--check", "Only report whether a newer version is published; install nothing")
|
|
10
11
|
.option("--no-skill", "Leave the Agent Skill copies as they are")
|
|
11
12
|
.action(async (options) => {
|
|
13
|
+
if (isDryRun()) {
|
|
14
|
+
const steps = [`look up the latest published version on ${REGISTRY}`];
|
|
15
|
+
if (!options.check) {
|
|
16
|
+
steps.push(`if newer than ${CLI_VERSION}: npm install -g ${PACKAGE_NAME}@<latest> --registry=${REGISTRY}`);
|
|
17
|
+
if (options.skill)
|
|
18
|
+
steps.push("refresh older copies of the Agent Skill");
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write(`${JSON.stringify({ dryRun: true, command: "update", steps, note: "Nothing was looked up or installed." }, null, 2)}\n`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
12
23
|
const latest = await latestVersion(undefined, 15_000, Date.now(), true);
|
|
13
24
|
if (!latest)
|
|
14
25
|
throw new Error(`Could not reach ${REGISTRY} to look up the latest version. Check the network and retry, or install directly: npm install -g ${PACKAGE_NAME}@latest --registry=${REGISTRY}`);
|