@hithink-tech/hithink-finance-cli 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/CHANGELOG.md +5 -0
- package/LICENSE +21 -0
- package/README.md +161 -0
- package/SECURITY.md +3 -0
- package/dist/application/config.d.ts +44 -0
- package/dist/application/config.js +111 -0
- package/dist/application/ports/auth-provider.d.ts +23 -0
- package/dist/application/ports/auth-provider.js +2 -0
- package/dist/application/source-policy.d.ts +13 -0
- package/dist/application/source-policy.js +31 -0
- package/dist/application/use-cases/data-clean.d.ts +2 -0
- package/dist/application/use-cases/data-clean.js +18 -0
- package/dist/application/use-cases/data-init.d.ts +4 -0
- package/dist/application/use-cases/data-init.js +9 -0
- package/dist/application/use-cases/data-remove.d.ts +2 -0
- package/dist/application/use-cases/data-remove.js +27 -0
- package/dist/application/use-cases/data-repair.d.ts +5 -0
- package/dist/application/use-cases/data-repair.js +5 -0
- package/dist/application/use-cases/data-sync.d.ts +25 -0
- package/dist/application/use-cases/data-sync.js +55 -0
- package/dist/application/use-cases/local-query.d.ts +4 -0
- package/dist/application/use-cases/local-query.js +88 -0
- package/dist/application/use-cases/market-history.d.ts +8 -0
- package/dist/application/use-cases/market-history.js +16 -0
- package/dist/application/use-cases/market-panel.d.ts +3 -0
- package/dist/application/use-cases/market-panel.js +5 -0
- package/dist/application/use-cases/remote-query.d.ts +4 -0
- package/dist/application/use-cases/remote-query.js +66 -0
- package/dist/cli/context.d.ts +17 -0
- package/dist/cli/context.js +52 -0
- package/dist/cli/i18n.d.ts +23 -0
- package/dist/cli/i18n.js +150 -0
- package/dist/cli/main.d.ts +3 -0
- package/dist/cli/main.js +80 -0
- package/dist/cli/program.d.ts +20 -0
- package/dist/cli/program.js +111 -0
- package/dist/commands/auth/index.d.ts +10 -0
- package/dist/commands/auth/index.js +131 -0
- package/dist/commands/config/index.d.ts +13 -0
- package/dist/commands/config/index.js +26 -0
- package/dist/commands/data/index.d.ts +10 -0
- package/dist/commands/data/index.js +185 -0
- package/dist/commands/db/index.d.ts +4 -0
- package/dist/commands/db/index.js +72 -0
- package/dist/commands/financials/index.d.ts +5 -0
- package/dist/commands/financials/index.js +6 -0
- package/dist/commands/index/index.d.ts +5 -0
- package/dist/commands/index/index.js +6 -0
- package/dist/commands/market/index.d.ts +5 -0
- package/dist/commands/market/index.js +6 -0
- package/dist/commands/market/local.d.ts +4 -0
- package/dist/commands/market/local.js +97 -0
- package/dist/commands/remote.d.ts +11 -0
- package/dist/commands/remote.js +195 -0
- package/dist/commands/skills/index.d.ts +4 -0
- package/dist/commands/skills/index.js +43 -0
- package/dist/commands/special/index.d.ts +5 -0
- package/dist/commands/special/index.js +6 -0
- package/dist/commands/symbol/index.d.ts +5 -0
- package/dist/commands/symbol/index.js +6 -0
- package/dist/commands/system/capabilities.d.ts +4 -0
- package/dist/commands/system/capabilities.js +24 -0
- package/dist/commands/system/doctor.d.ts +4 -0
- package/dist/commands/system/doctor.js +15 -0
- package/dist/commands/system/schema.d.ts +4 -0
- package/dist/commands/system/schema.js +47 -0
- package/dist/commands/system/uninstall.d.ts +11 -0
- package/dist/commands/system/uninstall.js +68 -0
- package/dist/commands/system/update.d.ts +11 -0
- package/dist/commands/system/update.js +72 -0
- package/dist/contracts/envelope.d.ts +37 -0
- package/dist/contracts/envelope.js +36 -0
- package/dist/contracts/errors.d.ts +23 -0
- package/dist/contracts/errors.js +33 -0
- package/dist/contracts/local-capabilities.d.ts +13 -0
- package/dist/contracts/local-capabilities.js +37 -0
- package/dist/contracts/registry.d.ts +9 -0
- package/dist/contracts/registry.js +4 -0
- package/dist/contracts/remote-capabilities.d.ts +24 -0
- package/dist/contracts/remote-capabilities.js +667 -0
- package/dist/domains/financials/types.d.ts +6 -0
- package/dist/domains/financials/types.js +2 -0
- package/dist/domains/index/types.d.ts +6 -0
- package/dist/domains/index/types.js +2 -0
- package/dist/domains/market/types.d.ts +6 -0
- package/dist/domains/market/types.js +2 -0
- package/dist/domains/special/types.d.ts +5 -0
- package/dist/domains/special/types.js +2 -0
- package/dist/domains/symbol/types.d.ts +6 -0
- package/dist/domains/symbol/types.js +2 -0
- package/dist/infrastructure/credentials/api-key-provider.d.ts +14 -0
- package/dist/infrastructure/credentials/api-key-provider.js +102 -0
- package/dist/infrastructure/credentials/keyring.d.ts +15 -0
- package/dist/infrastructure/credentials/keyring.js +21 -0
- package/dist/infrastructure/credentials/redact.d.ts +3 -0
- package/dist/infrastructure/credentials/redact.js +26 -0
- package/dist/infrastructure/duckdb/connection.d.ts +8 -0
- package/dist/infrastructure/duckdb/connection.js +18 -0
- package/dist/infrastructure/duckdb/dump-client.d.ts +20 -0
- package/dist/infrastructure/duckdb/dump-client.js +66 -0
- package/dist/infrastructure/duckdb/factors.d.ts +3 -0
- package/dist/infrastructure/duckdb/factors.js +83 -0
- package/dist/infrastructure/duckdb/importer.d.ts +11 -0
- package/dist/infrastructure/duckdb/importer.js +105 -0
- package/dist/infrastructure/duckdb/migrations.d.ts +17 -0
- package/dist/infrastructure/duckdb/migrations.js +92 -0
- package/dist/infrastructure/duckdb/quality.d.ts +12 -0
- package/dist/infrastructure/duckdb/quality.js +56 -0
- package/dist/infrastructure/duckdb/schema.d.ts +4 -0
- package/dist/infrastructure/duckdb/schema.js +14 -0
- package/dist/infrastructure/filesystem/atomic-file.d.ts +2 -0
- package/dist/infrastructure/filesystem/atomic-file.js +38 -0
- package/dist/infrastructure/filesystem/platform-paths.d.ts +15 -0
- package/dist/infrastructure/filesystem/platform-paths.js +42 -0
- package/dist/infrastructure/filesystem/process-lock.d.ts +6 -0
- package/dist/infrastructure/filesystem/process-lock.js +56 -0
- package/dist/infrastructure/filesystem/stdin.d.ts +5 -0
- package/dist/infrastructure/filesystem/stdin.js +9 -0
- package/dist/infrastructure/fuyao/client.d.ts +31 -0
- package/dist/infrastructure/fuyao/client.js +145 -0
- package/dist/infrastructure/fuyao/envelope.d.ts +9 -0
- package/dist/infrastructure/fuyao/envelope.js +8 -0
- package/dist/infrastructure/fuyao/pagination.d.ts +16 -0
- package/dist/infrastructure/fuyao/pagination.js +44 -0
- package/dist/infrastructure/fuyao/retry.d.ts +5 -0
- package/dist/infrastructure/fuyao/retry.js +18 -0
- package/dist/infrastructure/fuyao/windowing.d.ts +8 -0
- package/dist/infrastructure/fuyao/windowing.js +25 -0
- package/dist/infrastructure/skills/drift.d.ts +3 -0
- package/dist/infrastructure/skills/drift.js +8 -0
- package/dist/infrastructure/skills/installer.d.ts +13 -0
- package/dist/infrastructure/skills/installer.js +48 -0
- package/dist/infrastructure/skills/manifest.d.ts +11 -0
- package/dist/infrastructure/skills/manifest.js +62 -0
- package/dist/infrastructure/updater/cache.d.ts +15 -0
- package/dist/infrastructure/updater/cache.js +76 -0
- package/dist/infrastructure/updater/check.d.ts +13 -0
- package/dist/infrastructure/updater/check.js +51 -0
- package/dist/infrastructure/updater/install.d.ts +4 -0
- package/dist/infrastructure/updater/install.js +39 -0
- package/dist/output/json.d.ts +3 -0
- package/dist/output/json.js +16 -0
- package/dist/output/renderer.d.ts +4 -0
- package/dist/output/renderer.js +47 -0
- package/dist/output/table.d.ts +2 -0
- package/dist/output/table.js +31 -0
- package/migrations/001-initial.sql +95 -0
- package/migrations/manifest.json +12 -0
- package/package.json +66 -0
- package/schemas/capabilities.json +290 -0
- package/schemas/command-envelope.schema.json +12 -0
- package/scripts/postinstall.mjs +27 -0
- package/scripts/update-check.mjs +23 -0
- package/skills/hithink-finance-data/SKILL.md +74 -0
- package/skills/hithink-finance-data/references/data-clean.md +33 -0
- package/skills/hithink-finance-data/references/data-init.md +35 -0
- package/skills/hithink-finance-data/references/data-migrate.md +33 -0
- package/skills/hithink-finance-data/references/data-remove.md +33 -0
- package/skills/hithink-finance-data/references/data-repair.md +33 -0
- package/skills/hithink-finance-data/references/data-status.md +33 -0
- package/skills/hithink-finance-data/references/data-sync.md +34 -0
- package/skills/hithink-finance-data/references/data-validate.md +33 -0
- package/skills/hithink-finance-data/references/db-describe.md +33 -0
- package/skills/hithink-finance-data/references/db-export.md +33 -0
- package/skills/hithink-finance-data/references/db-query.md +33 -0
- package/skills/hithink-finance-financials/SKILL.md +59 -0
- package/skills/hithink-finance-financials/references/financials-balance-sheet.md +40 -0
- package/skills/hithink-finance-financials/references/financials-cash-flow.md +40 -0
- package/skills/hithink-finance-financials/references/financials-income.md +40 -0
- package/skills/hithink-finance-financials/references/financials-indicators.md +37 -0
- package/skills/hithink-finance-index/SKILL.md +59 -0
- package/skills/hithink-finance-index/references/index-catalog.md +36 -0
- package/skills/hithink-finance-index/references/index-constituents.md +36 -0
- package/skills/hithink-finance-index/references/index-history.md +38 -0
- package/skills/hithink-finance-index/references/index-snapshot.md +37 -0
- package/skills/hithink-finance-market/SKILL.md +63 -0
- package/skills/hithink-finance-market/references/market-adjustment-factors.md +33 -0
- package/skills/hithink-finance-market/references/market-calendar.md +35 -0
- package/skills/hithink-finance-market/references/market-corporate-actions.md +38 -0
- package/skills/hithink-finance-market/references/market-history.md +39 -0
- package/skills/hithink-finance-market/references/market-panel.md +33 -0
- package/skills/hithink-finance-market/references/market-snapshot.md +39 -0
- package/skills/hithink-finance-research/SKILL.md +58 -0
- package/skills/hithink-finance-research/references/research-workflow.md +34 -0
- package/skills/hithink-finance-shared/SKILL.md +55 -0
- package/skills/hithink-finance-shared/references/auth-and-config.md +31 -0
- package/skills/hithink-finance-shared/references/global-rules.md +21 -0
- package/skills/hithink-finance-shared/references/lifecycle.md +23 -0
- package/skills/hithink-finance-shared/references/skills-management.md +22 -0
- package/skills/hithink-finance-special-data/SKILL.md +68 -0
- package/skills/hithink-finance-special-data/references/special-anomaly-list.md +36 -0
- package/skills/hithink-finance-special-data/references/special-anomaly-stock.md +37 -0
- package/skills/hithink-finance-special-data/references/special-dragon-tiger.md +37 -0
- package/skills/hithink-finance-special-data/references/special-hot-stock-history.md +36 -0
- package/skills/hithink-finance-special-data/references/special-hot-stock-trend.md +38 -0
- package/skills/hithink-finance-special-data/references/special-hot-stock.md +36 -0
- package/skills/hithink-finance-special-data/references/special-limit-up-ladder.md +35 -0
- package/skills/hithink-finance-special-data/references/special-limit-up-pool.md +40 -0
- package/skills/hithink-finance-special-data/references/special-skyrocket.md +36 -0
- package/skills/hithink-finance-symbol/SKILL.md +56 -0
- package/skills/hithink-finance-symbol/references/symbol-list.md +39 -0
- package/skills/hithink-finance-symbol/references/symbol-search.md +39 -0
- package/skills/manifest.json +55 -0
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { CliError } from '../../contracts/errors.js';
|
|
2
|
+
import { mkdir, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function readOnlyViolation() {
|
|
5
|
+
return new CliError({
|
|
6
|
+
code: 'DB_READ_ONLY_VIOLATION',
|
|
7
|
+
category: 'validation',
|
|
8
|
+
message: 'db query accepts exactly one read-only SELECT statement.',
|
|
9
|
+
hint: 'Use SELECT or WITH ... SELECT. Database writes are available only through managed data commands.',
|
|
10
|
+
retryable: false,
|
|
11
|
+
exitCode: 2,
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export async function queryReadOnly(connection, sql) {
|
|
15
|
+
await connection.run('SET enable_external_access=false; SET autoinstall_known_extensions=false; SET autoload_known_extensions=false');
|
|
16
|
+
let extracted;
|
|
17
|
+
try {
|
|
18
|
+
extracted = await connection.extractStatements(sql);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
throw readOnlyViolation();
|
|
22
|
+
}
|
|
23
|
+
if (extracted.count !== 1)
|
|
24
|
+
throw readOnlyViolation();
|
|
25
|
+
let prepared;
|
|
26
|
+
try {
|
|
27
|
+
prepared = await extracted.prepare(0);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
throw readOnlyViolation();
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
if (prepared.statementType !== 1)
|
|
34
|
+
throw readOnlyViolation();
|
|
35
|
+
const reader = await prepared.runAndReadAll();
|
|
36
|
+
return reader.getRowObjectsJson();
|
|
37
|
+
}
|
|
38
|
+
finally {
|
|
39
|
+
prepared.destroySync();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function assertReadOnly(connection, sql) {
|
|
43
|
+
if (/\b(?:attach|copy|install|load|read_csv|read_json|read_parquet|httpfs|sqlite_scan)\b/iu.test(sql))
|
|
44
|
+
throw readOnlyViolation();
|
|
45
|
+
let extracted;
|
|
46
|
+
try {
|
|
47
|
+
extracted = await connection.extractStatements(sql);
|
|
48
|
+
if (extracted.count !== 1)
|
|
49
|
+
throw readOnlyViolation();
|
|
50
|
+
const prepared = await extracted.prepare(0);
|
|
51
|
+
try {
|
|
52
|
+
if (prepared.statementType !== 1)
|
|
53
|
+
throw readOnlyViolation();
|
|
54
|
+
}
|
|
55
|
+
finally {
|
|
56
|
+
prepared.destroySync();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error instanceof CliError)
|
|
61
|
+
throw error;
|
|
62
|
+
throw readOnlyViolation();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export async function exportQuery(connection, sql, outputPath, format) {
|
|
66
|
+
await assertReadOnly(connection, sql);
|
|
67
|
+
const absolute = path.resolve(outputPath);
|
|
68
|
+
const temporary = `${absolute}.${process.pid}.tmp`;
|
|
69
|
+
await mkdir(path.dirname(absolute), { recursive: true });
|
|
70
|
+
const escaped = temporary.replaceAll("'", "''");
|
|
71
|
+
const copyOptions = format === 'parquet'
|
|
72
|
+
? 'FORMAT PARQUET'
|
|
73
|
+
: format === 'csv'
|
|
74
|
+
? 'FORMAT CSV, HEADER true'
|
|
75
|
+
: 'FORMAT JSON, ARRAY false';
|
|
76
|
+
try {
|
|
77
|
+
await connection.run(`COPY (${sql}) TO '${escaped}' (${copyOptions})`);
|
|
78
|
+
const countReader = await connection.runAndReadAll(`SELECT count(*) FROM (${sql}) q`);
|
|
79
|
+
const count = Number(countReader.getRowsJson()[0]?.[0] ?? 0);
|
|
80
|
+
await rename(temporary, absolute);
|
|
81
|
+
return count;
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
await rm(temporary, { force: true });
|
|
85
|
+
throw error;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=local-query.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { DuckDBConnection } from '@duckdb/node-api';
|
|
2
|
+
export declare function getHistory(connection: DuckDBConnection, input: {
|
|
3
|
+
thscodes: readonly string[];
|
|
4
|
+
start?: string;
|
|
5
|
+
end?: string;
|
|
6
|
+
adjust?: 'none' | 'forward' | 'backward';
|
|
7
|
+
}): Promise<Record<string, unknown>[]>;
|
|
8
|
+
//# sourceMappingURL=market-history.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { queryReadOnly } from './local-query.js';
|
|
2
|
+
export async function getHistory(connection, input) {
|
|
3
|
+
const view = input.adjust === 'backward'
|
|
4
|
+
? 'v_daily_hfq'
|
|
5
|
+
: input.adjust === 'none'
|
|
6
|
+
? 'v_daily'
|
|
7
|
+
: 'v_daily_qfq';
|
|
8
|
+
const codes = input.thscodes.map((code) => `'${code.replaceAll("'", "''")}'`).join(',');
|
|
9
|
+
const filters = [`thscode IN (${codes || "''"})`];
|
|
10
|
+
if (input.start !== undefined)
|
|
11
|
+
filters.push(`date >= DATE '${input.start}'`);
|
|
12
|
+
if (input.end !== undefined)
|
|
13
|
+
filters.push(`date <= DATE '${input.end}'`);
|
|
14
|
+
return queryReadOnly(connection, `SELECT * FROM ${view} WHERE ${filters.join(' AND ')} ORDER BY thscode,date`);
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=market-history.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { queryReadOnly } from './local-query.js';
|
|
2
|
+
export async function getPanel(connection, start, end) {
|
|
3
|
+
return queryReadOnly(connection, `SELECT * FROM v_daily_qfq WHERE date BETWEEN DATE '${start}' AND DATE '${end}' ORDER BY date,thscode`);
|
|
4
|
+
}
|
|
5
|
+
//# sourceMappingURL=market-panel.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { RemoteCapabilityDescriptor } from '../../contracts/remote-capabilities.js';
|
|
2
|
+
import type { FuyaoClient, FuyaoSuccess } from '../../infrastructure/fuyao/client.js';
|
|
3
|
+
export declare function executeRemoteQuery(capability: RemoteCapabilityDescriptor, input: Record<string, unknown>, client: FuyaoClient): Promise<FuyaoSuccess<unknown>>;
|
|
4
|
+
//# sourceMappingURL=remote-query.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { deduplicateAndSort, sliceTimeWindow } from '../../infrastructure/fuyao/windowing.js';
|
|
2
|
+
function optionKey(flags) {
|
|
3
|
+
const longFlag = flags.split(/[ ,|]+/u).find((part) => part.startsWith('--')) ?? flags;
|
|
4
|
+
return longFlag
|
|
5
|
+
.replace(/^--/u, '')
|
|
6
|
+
.replace(/^no-/u, '')
|
|
7
|
+
.replace(/-([a-z])/gu, (_match, letter) => letter.toUpperCase());
|
|
8
|
+
}
|
|
9
|
+
function queryFor(capability, input) {
|
|
10
|
+
const query = {};
|
|
11
|
+
for (const option of capability.options) {
|
|
12
|
+
const key = optionKey(option.flags);
|
|
13
|
+
const value = input[key];
|
|
14
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
15
|
+
query[option.queryName ?? key] = value;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
if (capability.id === 'market.history' || capability.id === 'index.history') {
|
|
19
|
+
query.interval = '1d';
|
|
20
|
+
}
|
|
21
|
+
return query;
|
|
22
|
+
}
|
|
23
|
+
function mergeWindowData(values) {
|
|
24
|
+
const objects = values.filter((value) => value !== null && typeof value === 'object');
|
|
25
|
+
const items = objects.flatMap((value) => (Array.isArray(value.item) ? value.item : []));
|
|
26
|
+
const mergedItems = deduplicateAndSort(items, (item) => {
|
|
27
|
+
if (item !== null && typeof item === 'object' && 'date_ms' in item) {
|
|
28
|
+
const date = item.date_ms;
|
|
29
|
+
if (typeof date === 'number' || typeof date === 'string')
|
|
30
|
+
return date;
|
|
31
|
+
}
|
|
32
|
+
return JSON.stringify(item);
|
|
33
|
+
});
|
|
34
|
+
return { ...(objects[objects.length - 1] ?? {}), item: mergedItems };
|
|
35
|
+
}
|
|
36
|
+
export async function executeRemoteQuery(capability, input, client) {
|
|
37
|
+
const query = queryFor(capability, input);
|
|
38
|
+
const start = input.startMs;
|
|
39
|
+
const end = input.endMs;
|
|
40
|
+
if (capability.window === 'ten-years' && typeof start === 'number' && typeof end === 'number') {
|
|
41
|
+
const slices = sliceTimeWindow(start, end);
|
|
42
|
+
if (slices.length > 1) {
|
|
43
|
+
const responses = [];
|
|
44
|
+
for (const slice of slices) {
|
|
45
|
+
responses.push(await client.request({
|
|
46
|
+
path: capability.endpoint,
|
|
47
|
+
query: { ...query, start: slice.start, end: slice.end },
|
|
48
|
+
schema: capability.outputSchema,
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
const result = {
|
|
52
|
+
data: mergeWindowData(responses.map((response) => response.data)),
|
|
53
|
+
};
|
|
54
|
+
const requestId = responses.at(-1)?.requestId;
|
|
55
|
+
if (requestId !== undefined)
|
|
56
|
+
result.requestId = requestId;
|
|
57
|
+
return result;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return client.request({
|
|
61
|
+
path: capability.endpoint,
|
|
62
|
+
query,
|
|
63
|
+
schema: capability.outputSchema,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=remote-query.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type Language } from './i18n.js';
|
|
2
|
+
export type OutputFormat = 'auto' | 'json' | 'ndjson' | 'csv' | 'table';
|
|
3
|
+
export interface CliContext {
|
|
4
|
+
format: OutputFormat;
|
|
5
|
+
language: Language;
|
|
6
|
+
color: boolean;
|
|
7
|
+
requestId: string;
|
|
8
|
+
stdout: NodeJS.WriteStream;
|
|
9
|
+
stderr: NodeJS.WriteStream;
|
|
10
|
+
}
|
|
11
|
+
export declare function optionValue(argv: readonly string[], name: string): string | undefined;
|
|
12
|
+
export declare function createCliContext(argv: readonly string[], defaults?: {
|
|
13
|
+
format?: OutputFormat;
|
|
14
|
+
language?: Language;
|
|
15
|
+
}): CliContext;
|
|
16
|
+
export declare function inferCommand(argv: readonly string[]): string;
|
|
17
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { resolveLanguage } from './i18n.js';
|
|
3
|
+
export function optionValue(argv, name) {
|
|
4
|
+
const inlinePrefix = `${name}=`;
|
|
5
|
+
const inline = argv.find((argument) => argument.startsWith(inlinePrefix));
|
|
6
|
+
if (inline !== undefined) {
|
|
7
|
+
return inline.slice(inlinePrefix.length);
|
|
8
|
+
}
|
|
9
|
+
const index = argv.indexOf(name);
|
|
10
|
+
return index >= 0 ? argv[index + 1] : undefined;
|
|
11
|
+
}
|
|
12
|
+
export function createCliContext(argv, defaults = {}) {
|
|
13
|
+
const rawFormat = optionValue(argv, '--format');
|
|
14
|
+
const format = rawFormat === 'json' || rawFormat === 'ndjson' || rawFormat === 'csv' || rawFormat === 'table'
|
|
15
|
+
? rawFormat
|
|
16
|
+
: (defaults.format ?? 'auto');
|
|
17
|
+
const language = resolveLanguage(optionValue(argv, '--lang') ?? defaults.language, process.env.LC_ALL ?? process.env.LC_MESSAGES ?? process.env.LANG);
|
|
18
|
+
return {
|
|
19
|
+
format,
|
|
20
|
+
language,
|
|
21
|
+
color: process.stderr.isTTY === true && process.env.NO_COLOR === undefined,
|
|
22
|
+
requestId: optionValue(argv, '--request-id') ?? randomUUID(),
|
|
23
|
+
stdout: process.stdout,
|
|
24
|
+
stderr: process.stderr,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function inferCommand(argv) {
|
|
28
|
+
const optionsWithValues = new Set([
|
|
29
|
+
'--format',
|
|
30
|
+
'--lang',
|
|
31
|
+
'--profile',
|
|
32
|
+
'--api-key',
|
|
33
|
+
'--db',
|
|
34
|
+
'--config',
|
|
35
|
+
'--source',
|
|
36
|
+
'--request-id',
|
|
37
|
+
]);
|
|
38
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
39
|
+
const argument = argv[index];
|
|
40
|
+
if (argument === undefined)
|
|
41
|
+
continue;
|
|
42
|
+
if (optionsWithValues.has(argument)) {
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (!argument.startsWith('-')) {
|
|
47
|
+
return argument;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return 'root';
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type Language = 'zh-CN' | 'en';
|
|
2
|
+
declare const messages: {
|
|
3
|
+
readonly en: {
|
|
4
|
+
readonly rootDescription: "Enterprise financial data CLI for humans and AI agents";
|
|
5
|
+
readonly versionDescription: "Print the installed CLI version";
|
|
6
|
+
readonly unknownCommand: "Unknown command.";
|
|
7
|
+
readonly unknownCommandHint: "Run `hithink-finance --help` to list available commands.";
|
|
8
|
+
readonly authLoginAlreadyConfigured: "This profile is already logged in. To switch API keys, run `hithink-finance auth logout` first, then run `hithink-finance auth login` again.";
|
|
9
|
+
};
|
|
10
|
+
readonly 'zh-CN': {
|
|
11
|
+
readonly rootDescription: "面向人类与 AI Agent 的企业级金融数据命令行工具";
|
|
12
|
+
readonly versionDescription: "显示已安装的 CLI 版本";
|
|
13
|
+
readonly unknownCommand: "未知命令。";
|
|
14
|
+
readonly unknownCommandHint: "运行 `hithink-finance --help` 查看可用命令。";
|
|
15
|
+
readonly authLoginAlreadyConfigured: "当前 profile 已登录。如需切换 API Key,请先运行 `hithink-finance auth logout`,再重新运行 `hithink-finance auth login`。";
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
export type MessageKey = keyof (typeof messages)['en'];
|
|
19
|
+
export declare function translate(language: Language, key: MessageKey): string;
|
|
20
|
+
export declare function localizeText(language: Language, english: string): string;
|
|
21
|
+
export declare function resolveLanguage(explicit: string | undefined, locale: string | undefined): Language;
|
|
22
|
+
export {};
|
|
23
|
+
//# sourceMappingURL=i18n.d.ts.map
|
package/dist/cli/i18n.js
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
const messages = {
|
|
2
|
+
en: {
|
|
3
|
+
rootDescription: 'Enterprise financial data CLI for humans and AI agents',
|
|
4
|
+
versionDescription: 'Print the installed CLI version',
|
|
5
|
+
unknownCommand: 'Unknown command.',
|
|
6
|
+
unknownCommandHint: 'Run `hithink-finance --help` to list available commands.',
|
|
7
|
+
authLoginAlreadyConfigured: 'This profile is already logged in. To switch API keys, run `hithink-finance auth logout` first, then run `hithink-finance auth login` again.',
|
|
8
|
+
},
|
|
9
|
+
'zh-CN': {
|
|
10
|
+
rootDescription: '面向人类与 AI Agent 的企业级金融数据命令行工具',
|
|
11
|
+
versionDescription: '显示已安装的 CLI 版本',
|
|
12
|
+
unknownCommand: '未知命令。',
|
|
13
|
+
unknownCommandHint: '运行 `hithink-finance --help` 查看可用命令。',
|
|
14
|
+
authLoginAlreadyConfigured: '当前 profile 已登录。如需切换 API Key,请先运行 `hithink-finance auth logout`,再重新运行 `hithink-finance auth login`。',
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
const zhTextByEnglish = {
|
|
18
|
+
'output format': '输出格式',
|
|
19
|
+
'human interface language': '人类界面语言',
|
|
20
|
+
'configuration and credential profile': '配置与凭据 profile',
|
|
21
|
+
'explicit JSON configuration file': '显式指定 JSON 配置文件',
|
|
22
|
+
'API key for this process': '仅当前进程使用的 API Key',
|
|
23
|
+
'read an API key from stdin': '从标准输入读取 API Key',
|
|
24
|
+
'disable interactive input': '禁用交互式输入',
|
|
25
|
+
'confirm non-interactive operations': '确认非交互式操作',
|
|
26
|
+
'data source: auto, local, or remote': '数据来源:auto、local 或 remote',
|
|
27
|
+
'local DuckDB path': '本地 DuckDB 路径',
|
|
28
|
+
'caller-supplied correlation ID': '调用方提供的关联 ID',
|
|
29
|
+
'enable diagnostic details on stderr': '在 stderr 输出诊断细节',
|
|
30
|
+
'disable terminal colors': '禁用终端颜色',
|
|
31
|
+
'Manage API key authentication': '管理 API Key 认证',
|
|
32
|
+
'Store an API key in the system credential store': '保存 API Key 到系统凭据库',
|
|
33
|
+
'Show whether an API key is configured': '查看 API Key 是否已配置',
|
|
34
|
+
'Delete API key credentials': '删除 API Key 凭据',
|
|
35
|
+
'delete every hithink-finance profile': '删除所有 hithink-finance profile 凭据',
|
|
36
|
+
'Inspect resolved non-secret configuration': '查看已解析的非敏感配置',
|
|
37
|
+
'Show resolved non-secret configuration': '显示已解析的非敏感配置',
|
|
38
|
+
'List machine-readable command capabilities': '列出机器可读命令能力清单',
|
|
39
|
+
'Show a command contract': '查看命令契约',
|
|
40
|
+
'Manage Agent Skills': '管理 Agent Skills',
|
|
41
|
+
'Check or repair the installed CLI version': '检查或修复已安装的 CLI 版本',
|
|
42
|
+
'Check for a newer version without installing': '仅检查新版本,不安装',
|
|
43
|
+
'Repair the current or target installation': '修复当前或目标安装',
|
|
44
|
+
'Install a specific SemVer version': '安装指定 SemVer 版本',
|
|
45
|
+
'Plan or run CLI uninstall cleanup': '预览或执行 CLI 卸载清理',
|
|
46
|
+
'Show the uninstall plan without deleting anything': '只显示卸载计划,不删除任何内容',
|
|
47
|
+
'Delete local data during uninstall': '卸载时删除本地数据',
|
|
48
|
+
'Delete local configuration during uninstall': '卸载时删除本地配置',
|
|
49
|
+
'Delete all CLI-managed API key credentials': '删除所有 CLI 管理的 API Key 凭据',
|
|
50
|
+
'Run local environment diagnostics': '运行本地环境诊断',
|
|
51
|
+
'Manage local DuckDB data': '管理本地 DuckDB 数据(初始化/同步需要先登录)',
|
|
52
|
+
'Initialize local data from a verified dump': '初始化本地数据(远端初始化需要 API Key)',
|
|
53
|
+
'Synchronize local data': '同步本地数据(需要 API Key)',
|
|
54
|
+
'Show local database status': '显示本地数据库状态',
|
|
55
|
+
'Validate local data quality': '校验本地数据质量',
|
|
56
|
+
'Rebuild derived factors': '重建本地派生因子',
|
|
57
|
+
'Plan or apply schema migrations': '预览或执行 schema 迁移',
|
|
58
|
+
'Clean managed download caches': '清理托管下载缓存',
|
|
59
|
+
'Remove the explicitly confirmed database': '删除已显式确认的数据库',
|
|
60
|
+
'Query the local DuckDB': '查询本地 DuckDB',
|
|
61
|
+
'Describe local database objects': '查看本地数据库对象',
|
|
62
|
+
'Run a read-only SQL query': '执行只读 SQL 查询',
|
|
63
|
+
'Export a read-only SQL query': '导出只读 SQL 查询结果',
|
|
64
|
+
'ndjson, csv, or parquet': 'ndjson、csv 或 parquet',
|
|
65
|
+
'Export a local full-market panel': '导出本地全市场面板',
|
|
66
|
+
'Query local daily adjustment factors': '查询本地日频复权因子',
|
|
67
|
+
'symbol remote data commands': '标的目录与代码搜索命令',
|
|
68
|
+
'market remote data commands': '普通行情与交易日历命令',
|
|
69
|
+
'special remote data commands': '特色榜单与事件数据命令',
|
|
70
|
+
'financials remote data commands': '财务报表与指标命令',
|
|
71
|
+
'index remote data commands': '指数目录、成分和行情命令',
|
|
72
|
+
'write the full JSON response envelope to a file': '将完整 JSON 响应信封写入文件',
|
|
73
|
+
'read thscodes from a comma/newline-delimited file': '从逗号或换行分隔文件读取 thscode',
|
|
74
|
+
'read thscodes from stdin': '从标准输入读取 thscode',
|
|
75
|
+
'Resolve a name or code to thscode': '将名称、ticker 或代码消歧为 thscode',
|
|
76
|
+
'List symbols with bounded pagination': '分页列出标的目录',
|
|
77
|
+
'Query A-share price snapshots': '查询 A 股行情快照',
|
|
78
|
+
'Query daily A-share history': '查询 A 股日线历史行情',
|
|
79
|
+
'Query adjustment events': '查询公司行动/复权事件',
|
|
80
|
+
'Query income financial statements': '查询利润表',
|
|
81
|
+
'Query balance-sheet financial statements': '查询资产负债表',
|
|
82
|
+
'Query cash-flow financial statements': '查询现金流量表',
|
|
83
|
+
'Query financial indicators for a report': '查询指定报告期财务指标',
|
|
84
|
+
'Query the one-year A-share trading calendar': '查询一年内 A 股交易日历',
|
|
85
|
+
'List THS indices by category': '按类别列出同花顺指数',
|
|
86
|
+
'Query index constituents': '查询指数成分股',
|
|
87
|
+
'Query index price snapshots': '查询指数行情快照',
|
|
88
|
+
'Query daily index history': '查询指数日线历史行情',
|
|
89
|
+
'Query the limit-up stock pool': '查询涨停股票池',
|
|
90
|
+
'Query the 30-day limit-up ladder': '查询 30 日连板天梯',
|
|
91
|
+
'Query today-only anomaly analysis rows': '查询今日个股异动列表',
|
|
92
|
+
'Query today-only anomalies for up to 50 raw code tokens': '查询最多 50 只股票的今日异动原因',
|
|
93
|
+
'Query the skyrocket ranking': '查询飙升榜',
|
|
94
|
+
'Query the current hot-stock ranking': '查询当前热股榜',
|
|
95
|
+
'Query a historical hot-stock ranking': '查询历史热股榜',
|
|
96
|
+
'Query one stock hot-rank trend': '查询单股热度排名趋势',
|
|
97
|
+
'Query dragon-tiger board records': '查询龙虎榜记录',
|
|
98
|
+
'name, ticker, or thscode': '名称、ticker 或 thscode',
|
|
99
|
+
'exchange filter': '交易所过滤条件',
|
|
100
|
+
'asset type': '资产类型',
|
|
101
|
+
'maximum matches (1-50)': '最大匹配数量(1-50)',
|
|
102
|
+
'comma-separated exchanges': '逗号分隔的交易所',
|
|
103
|
+
'page size (1-10000)': '分页大小(1-10000)',
|
|
104
|
+
'row offset': '行偏移量',
|
|
105
|
+
'comma-separated A-share thscodes': '逗号分隔的 A 股 thscode',
|
|
106
|
+
'page size': '分页大小',
|
|
107
|
+
'single A-share thscode': '单只 A 股 thscode',
|
|
108
|
+
'start timestamp': '开始时间戳',
|
|
109
|
+
'end timestamp': '结束时间戳',
|
|
110
|
+
'adjustment mode': '复权模式',
|
|
111
|
+
'first ex-date YYYY-MM-DD': '最早除权除息日期 YYYY-MM-DD',
|
|
112
|
+
'last ex-date YYYY-MM-DD': '最晚除权除息日期 YYYY-MM-DD',
|
|
113
|
+
'financial period': '财报周期',
|
|
114
|
+
'recent report count (1-20)': '最近报告数量(1-20)',
|
|
115
|
+
'range start in milliseconds': '区间开始毫秒时间戳',
|
|
116
|
+
'range end in milliseconds': '区间结束毫秒时间戳',
|
|
117
|
+
'report quarter YYYY-[1-4]': '报告季度 YYYY-[1-4]',
|
|
118
|
+
'index category': '指数类别',
|
|
119
|
+
'single index thscode': '单个指数 thscode',
|
|
120
|
+
'comma-separated index thscodes': '逗号分隔的指数 thscode',
|
|
121
|
+
'trade date at Asia/Shanghai midnight': 'Asia/Shanghai 零点对应的交易日',
|
|
122
|
+
'page number': '页码',
|
|
123
|
+
'page size (1-200)': '分页大小(1-200)',
|
|
124
|
+
'sort field': '排序字段',
|
|
125
|
+
'sort direction': '排序方向',
|
|
126
|
+
'comma-separated anomaly tags': '逗号分隔的异动标签',
|
|
127
|
+
'1-50 comma-separated A-share thscodes': '1-50 个逗号分隔的 A 股 thscode',
|
|
128
|
+
'ranking period': '榜单周期',
|
|
129
|
+
'trade date YYYY-MM-DD': '交易日 YYYY-MM-DD',
|
|
130
|
+
'start date YYYY-MM-DD': '开始日期 YYYY-MM-DD',
|
|
131
|
+
'end date YYYY-MM-DD': '结束日期 YYYY-MM-DD',
|
|
132
|
+
'board category': '榜单类别',
|
|
133
|
+
'optional trade date YYYY-MM-DD': '可选交易日 YYYY-MM-DD',
|
|
134
|
+
};
|
|
135
|
+
export function translate(language, key) {
|
|
136
|
+
return messages[language][key];
|
|
137
|
+
}
|
|
138
|
+
export function localizeText(language, english) {
|
|
139
|
+
return language === 'zh-CN' ? (zhTextByEnglish[english] ?? english) : english;
|
|
140
|
+
}
|
|
141
|
+
export function resolveLanguage(explicit, locale) {
|
|
142
|
+
if (explicit === 'zh-CN' || explicit === 'en') {
|
|
143
|
+
return explicit;
|
|
144
|
+
}
|
|
145
|
+
if (locale?.toLowerCase().startsWith('en') === true) {
|
|
146
|
+
return 'en';
|
|
147
|
+
}
|
|
148
|
+
return 'zh-CN';
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/cli/main.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { CommanderError } from 'commander';
|
|
4
|
+
import { createCliContext, inferCommand, optionValue } from './context.js';
|
|
5
|
+
import { createProgram, commanderError } from './program.js';
|
|
6
|
+
import { errorEnvelope } from '../contracts/envelope.js';
|
|
7
|
+
import { internalError } from '../contracts/errors.js';
|
|
8
|
+
import { renderResult } from '../output/renderer.js';
|
|
9
|
+
import { ApiKeyAuthProvider } from '../infrastructure/credentials/api-key-provider.js';
|
|
10
|
+
import { KeyringCredentialStore } from '../infrastructure/credentials/keyring.js';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { createPlatformPaths } from '../infrastructure/filesystem/platform-paths.js';
|
|
13
|
+
import { loadConfig } from '../application/config.js';
|
|
14
|
+
import { maybeEmitCachedUpdateNotice } from '../infrastructure/updater/check.js';
|
|
15
|
+
const require = createRequire(import.meta.url);
|
|
16
|
+
const packageMetadata = require('../../package.json');
|
|
17
|
+
const argv = process.argv.slice(2);
|
|
18
|
+
if (argv.length === 0) {
|
|
19
|
+
argv.push('--help');
|
|
20
|
+
process.argv.push('--help');
|
|
21
|
+
}
|
|
22
|
+
let context = createCliContext(argv);
|
|
23
|
+
if (argv.length === 1 && (argv[0] === '--version' || argv[0] === '-V')) {
|
|
24
|
+
process.stdout.write(`${packageMetadata.version}\n`);
|
|
25
|
+
}
|
|
26
|
+
else
|
|
27
|
+
try {
|
|
28
|
+
const platformPaths = createPlatformPaths();
|
|
29
|
+
const packageRoot = path.resolve(import.meta.dirname, '../..');
|
|
30
|
+
const rawDbPath = optionValue(argv, '--db');
|
|
31
|
+
const rawProfile = optionValue(argv, '--profile');
|
|
32
|
+
const rawFormat = optionValue(argv, '--format');
|
|
33
|
+
const rawLanguage = optionValue(argv, '--lang');
|
|
34
|
+
const rawConfigPath = optionValue(argv, '--config');
|
|
35
|
+
const resolvedConfig = await loadConfig({
|
|
36
|
+
paths: platformPaths,
|
|
37
|
+
cli: {
|
|
38
|
+
...(rawDbPath === undefined ? {} : { dbPath: rawDbPath }),
|
|
39
|
+
...(rawProfile === undefined ? {} : { profile: rawProfile }),
|
|
40
|
+
...(rawFormat === undefined ? {} : { format: rawFormat }),
|
|
41
|
+
...(rawLanguage === 'zh-CN' || rawLanguage === 'en' ? { language: rawLanguage } : {}),
|
|
42
|
+
...(rawConfigPath === undefined ? {} : { configPath: rawConfigPath }),
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
context = createCliContext(argv, {
|
|
46
|
+
format: resolvedConfig.format,
|
|
47
|
+
...(resolvedConfig.language === undefined ? {} : { language: resolvedConfig.language }),
|
|
48
|
+
});
|
|
49
|
+
const program = createProgram(packageMetadata, context, {
|
|
50
|
+
authProvider: new ApiKeyAuthProvider(new KeyringCredentialStore(), process.env),
|
|
51
|
+
fuyaoBaseUrl: process.env.HITHINK_FINANCE_FUYAO_BASE_URL ?? 'https://fuyao.aicubes.cn',
|
|
52
|
+
packageRoot,
|
|
53
|
+
platformPaths: { ...platformPaths, defaultDbPath: resolvedConfig.dbPath },
|
|
54
|
+
resolvedConfig,
|
|
55
|
+
});
|
|
56
|
+
await program.parseAsync(process.argv);
|
|
57
|
+
if (inferCommand(argv) !== 'update') {
|
|
58
|
+
await maybeEmitCachedUpdateNotice({
|
|
59
|
+
packageRoot,
|
|
60
|
+
packageName: packageMetadata.name,
|
|
61
|
+
currentVersion: packageMetadata.version,
|
|
62
|
+
cacheFile: path.join(platformPaths.stateDir, 'update-cache.json'),
|
|
63
|
+
stderr: context.stderr,
|
|
64
|
+
disabled: !resolvedConfig.updateCheck,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error instanceof CommanderError && error.exitCode === 0) {
|
|
70
|
+
process.exitCode = 0;
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
const cliError = error instanceof CommanderError
|
|
74
|
+
? commanderError(error, context.language)
|
|
75
|
+
: internalError(error);
|
|
76
|
+
await renderResult(errorEnvelope(inferCommand(argv), cliError, packageMetadata.version), context);
|
|
77
|
+
process.exitCode = cliError.exitCode;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Command, CommanderError } from 'commander';
|
|
2
|
+
import type { CliContext } from './context.js';
|
|
3
|
+
import { CliError } from '../contracts/errors.js';
|
|
4
|
+
import type { ApiKeyAuthProvider } from '../infrastructure/credentials/api-key-provider.js';
|
|
5
|
+
import type { PlatformPaths } from '../infrastructure/filesystem/platform-paths.js';
|
|
6
|
+
import type { ResolvedConfig } from '../application/config.js';
|
|
7
|
+
export interface PackageMetadata {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
}
|
|
11
|
+
export interface ProgramDependencies {
|
|
12
|
+
authProvider: ApiKeyAuthProvider;
|
|
13
|
+
fuyaoBaseUrl: string;
|
|
14
|
+
packageRoot: string;
|
|
15
|
+
platformPaths: PlatformPaths;
|
|
16
|
+
resolvedConfig: ResolvedConfig;
|
|
17
|
+
}
|
|
18
|
+
export declare function createProgram(metadata: PackageMetadata, context: CliContext, dependencies: ProgramDependencies): Command;
|
|
19
|
+
export declare function commanderError(error: CommanderError, language: CliContext['language']): CliError;
|
|
20
|
+
//# sourceMappingURL=program.d.ts.map
|