@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,56 @@
|
|
|
1
|
+
async function count(connection, sql) {
|
|
2
|
+
const reader = await connection.runAndReadAll(sql);
|
|
3
|
+
return Number(reader.getRowsJson()[0]?.[0] ?? 0);
|
|
4
|
+
}
|
|
5
|
+
export async function validateDatabase(connection) {
|
|
6
|
+
const checks = [
|
|
7
|
+
[
|
|
8
|
+
'QUALITY_DUPLICATE_KEY',
|
|
9
|
+
'duplicate daily keys',
|
|
10
|
+
`SELECT count(*) FROM (SELECT thscode,date FROM raw_kline_daily GROUP BY thscode,date HAVING count(*)>1)`,
|
|
11
|
+
],
|
|
12
|
+
[
|
|
13
|
+
'QUALITY_INVALID_OHLC',
|
|
14
|
+
'invalid OHLC relationships',
|
|
15
|
+
`SELECT count(*) FROM raw_kline_daily WHERE high<GREATEST(open,close,low) OR low>LEAST(open,close,high)`,
|
|
16
|
+
],
|
|
17
|
+
[
|
|
18
|
+
'QUALITY_NEGATIVE_VOLUME',
|
|
19
|
+
'negative volume or amount',
|
|
20
|
+
`SELECT count(*) FROM raw_kline_daily WHERE volume<0 OR amount<0`,
|
|
21
|
+
],
|
|
22
|
+
[
|
|
23
|
+
'QUALITY_FACTOR_GAP',
|
|
24
|
+
'missing adjustment factors',
|
|
25
|
+
`SELECT count(*) FROM raw_kline_daily k LEFT JOIN calc_adjust_factor_daily f USING(thscode,date) WHERE f.date IS NULL`,
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
'QUALITY_VIEW_MISMATCH',
|
|
29
|
+
'stable view row mismatch',
|
|
30
|
+
`SELECT abs((SELECT count(*) FROM raw_kline_daily)-(SELECT count(*) FROM v_daily))`,
|
|
31
|
+
],
|
|
32
|
+
[
|
|
33
|
+
'QUALITY_SYMBOL_COVERAGE',
|
|
34
|
+
'daily rows without symbols',
|
|
35
|
+
`SELECT count(*) FROM raw_kline_daily k LEFT JOIN dim_symbol s USING(thscode) WHERE s.thscode IS NULL`,
|
|
36
|
+
],
|
|
37
|
+
[
|
|
38
|
+
'QUALITY_INCOMPLETE_BATCH',
|
|
39
|
+
'incomplete import batches',
|
|
40
|
+
`SELECT count(*) FROM _import_batches WHERE status<>'complete'`,
|
|
41
|
+
],
|
|
42
|
+
[
|
|
43
|
+
'QUALITY_INVALID_DATE',
|
|
44
|
+
'dates outside supported range',
|
|
45
|
+
`SELECT count(*) FROM raw_kline_daily WHERE date<DATE '1990-01-01' OR date>CURRENT_DATE`,
|
|
46
|
+
],
|
|
47
|
+
];
|
|
48
|
+
const issues = [];
|
|
49
|
+
for (const [code, message, sql] of checks) {
|
|
50
|
+
const issueCount = await count(connection, sql);
|
|
51
|
+
if (issueCount > 0)
|
|
52
|
+
issues.push({ code, count: issueCount, message });
|
|
53
|
+
}
|
|
54
|
+
return { ok: issues.length === 0, issues };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=quality.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const CORE_TABLES: readonly ["_meta", "_import_batches", "raw_kline_daily", "raw_adjustment_events", "dim_symbol", "calc_adjust_factor_daily", "stg_kline_daily", "stg_adjustment_events", "stg_symbol"];
|
|
2
|
+
export declare const STABLE_VIEWS: readonly ["v_symbol", "v_daily", "v_daily_qfq", "v_daily_hfq"];
|
|
3
|
+
export declare const SUPPORTED_SCHEMA_VERSION = 1;
|
|
4
|
+
//# sourceMappingURL=schema.d.ts.map
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const CORE_TABLES = [
|
|
2
|
+
'_meta',
|
|
3
|
+
'_import_batches',
|
|
4
|
+
'raw_kline_daily',
|
|
5
|
+
'raw_adjustment_events',
|
|
6
|
+
'dim_symbol',
|
|
7
|
+
'calc_adjust_factor_daily',
|
|
8
|
+
'stg_kline_daily',
|
|
9
|
+
'stg_adjustment_events',
|
|
10
|
+
'stg_symbol',
|
|
11
|
+
];
|
|
12
|
+
export const STABLE_VIEWS = ['v_symbol', 'v_daily', 'v_daily_qfq', 'v_daily_hfq'];
|
|
13
|
+
export const SUPPORTED_SCHEMA_VERSION = 1;
|
|
14
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { mkdir, open, rename, unlink } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
function errorCode(error) {
|
|
5
|
+
return typeof error === 'object' && error !== null && 'code' in error
|
|
6
|
+
? String(error.code)
|
|
7
|
+
: undefined;
|
|
8
|
+
}
|
|
9
|
+
export async function writeJsonAtomic(targetPath, value) {
|
|
10
|
+
const absoluteTarget = path.resolve(targetPath);
|
|
11
|
+
const directory = path.dirname(absoluteTarget);
|
|
12
|
+
const temporaryPath = path.join(directory, `.${path.basename(absoluteTarget)}.${process.pid}.${randomUUID()}.tmp`);
|
|
13
|
+
await mkdir(directory, { recursive: true });
|
|
14
|
+
const handle = await open(temporaryPath, 'wx', 0o600);
|
|
15
|
+
try {
|
|
16
|
+
await handle.writeFile(`${JSON.stringify(value, null, 2)}\n`, 'utf8');
|
|
17
|
+
await handle.sync();
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
await handle.close();
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await rename(temporaryPath, absoluteTarget);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const code = errorCode(error);
|
|
27
|
+
if (process.platform !== 'win32' || (code !== 'EEXIST' && code !== 'EPERM')) {
|
|
28
|
+
await unlink(temporaryPath).catch(() => undefined);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
await unlink(absoluteTarget).catch((unlinkError) => {
|
|
32
|
+
if (errorCode(unlinkError) !== 'ENOENT')
|
|
33
|
+
throw unlinkError;
|
|
34
|
+
});
|
|
35
|
+
await rename(temporaryPath, absoluteTarget);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=atomic-file.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface PlatformPaths {
|
|
2
|
+
configDir: string;
|
|
3
|
+
dataDir: string;
|
|
4
|
+
cacheDir: string;
|
|
5
|
+
stateDir: string;
|
|
6
|
+
userConfigFile: string;
|
|
7
|
+
defaultDbPath: string;
|
|
8
|
+
}
|
|
9
|
+
export interface PlatformPathInput {
|
|
10
|
+
platform?: NodeJS.Platform;
|
|
11
|
+
homeDir?: string;
|
|
12
|
+
env?: NodeJS.ProcessEnv;
|
|
13
|
+
}
|
|
14
|
+
export declare function createPlatformPaths(input?: PlatformPathInput): PlatformPaths;
|
|
15
|
+
//# sourceMappingURL=platform-paths.d.ts.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function createPlatformPaths(input = {}) {
|
|
4
|
+
const platform = input.platform ?? process.platform;
|
|
5
|
+
const homeDir = input.homeDir ?? os.homedir();
|
|
6
|
+
const env = input.env ?? process.env;
|
|
7
|
+
const pathApi = platform === 'win32' ? path.win32 : path.posix;
|
|
8
|
+
let configDir;
|
|
9
|
+
let dataDir;
|
|
10
|
+
let cacheDir;
|
|
11
|
+
let stateDir;
|
|
12
|
+
if (platform === 'win32') {
|
|
13
|
+
const roaming = env.APPDATA ?? pathApi.join(homeDir, 'AppData', 'Roaming');
|
|
14
|
+
const local = env.LOCALAPPDATA ?? pathApi.join(homeDir, 'AppData', 'Local');
|
|
15
|
+
configDir = pathApi.join(roaming, 'hithink-finance');
|
|
16
|
+
dataDir = pathApi.join(local, 'hithink-finance', 'data');
|
|
17
|
+
cacheDir = pathApi.join(local, 'hithink-finance', 'cache');
|
|
18
|
+
stateDir = pathApi.join(local, 'hithink-finance', 'state');
|
|
19
|
+
}
|
|
20
|
+
else if (platform === 'darwin') {
|
|
21
|
+
const applicationSupport = pathApi.join(homeDir, 'Library', 'Application Support');
|
|
22
|
+
configDir = pathApi.join(applicationSupport, 'hithink-finance');
|
|
23
|
+
dataDir = pathApi.join(applicationSupport, 'hithink-finance', 'data');
|
|
24
|
+
cacheDir = pathApi.join(homeDir, 'Library', 'Caches', 'hithink-finance');
|
|
25
|
+
stateDir = pathApi.join(applicationSupport, 'hithink-finance', 'state');
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
configDir = pathApi.join(env.XDG_CONFIG_HOME ?? pathApi.join(homeDir, '.config'), 'hithink-finance');
|
|
29
|
+
dataDir = pathApi.join(env.XDG_DATA_HOME ?? pathApi.join(homeDir, '.local', 'share'), 'hithink-finance');
|
|
30
|
+
cacheDir = pathApi.join(env.XDG_CACHE_HOME ?? pathApi.join(homeDir, '.cache'), 'hithink-finance');
|
|
31
|
+
stateDir = pathApi.join(env.XDG_STATE_HOME ?? pathApi.join(homeDir, '.local', 'state'), 'hithink-finance');
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
configDir,
|
|
35
|
+
dataDir,
|
|
36
|
+
cacheDir,
|
|
37
|
+
stateDir,
|
|
38
|
+
userConfigFile: pathApi.join(configDir, 'config.json'),
|
|
39
|
+
defaultDbPath: pathApi.join(dataDir, 'market.duckdb'),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=platform-paths.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { hostname } from 'node:os';
|
|
2
|
+
import { open, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { CliError } from '../../contracts/errors.js';
|
|
4
|
+
function alive(pid) {
|
|
5
|
+
try {
|
|
6
|
+
process.kill(pid, 0);
|
|
7
|
+
return true;
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function withExclusiveDataLock(lockPath, metadata, action) {
|
|
14
|
+
const payload = {
|
|
15
|
+
pid: process.pid,
|
|
16
|
+
host: hostname(),
|
|
17
|
+
command: metadata.command,
|
|
18
|
+
startedAt: new Date().toISOString(),
|
|
19
|
+
cliVersion: metadata.cliVersion,
|
|
20
|
+
};
|
|
21
|
+
let handle;
|
|
22
|
+
try {
|
|
23
|
+
handle = await open(lockPath, 'wx', 0o600);
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
let existing;
|
|
27
|
+
try {
|
|
28
|
+
existing = JSON.parse(await readFile(lockPath, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
throw new CliError({
|
|
32
|
+
code: 'DATA_LOCK_CORRUPT',
|
|
33
|
+
category: 'local-data',
|
|
34
|
+
message: 'The data lock file is corrupted.',
|
|
35
|
+
hint: `Inspect and remove the lock file if no hithink-finance data command is running: ${lockPath}`,
|
|
36
|
+
retryable: false,
|
|
37
|
+
exitCode: 5,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
if (typeof existing.pid === 'number' && !alive(existing.pid)) {
|
|
41
|
+
await rm(lockPath, { force: true });
|
|
42
|
+
return withExclusiveDataLock(lockPath, metadata, action);
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await handle.writeFile(JSON.stringify(payload));
|
|
48
|
+
await handle.close();
|
|
49
|
+
return await action();
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
await handle.close().catch(() => undefined);
|
|
53
|
+
await rm(lockPath, { force: true });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=process-lock.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export async function readStdin(input = process.stdin, options = {}) {
|
|
2
|
+
const chunks = [];
|
|
3
|
+
for await (const chunk of input) {
|
|
4
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk)));
|
|
5
|
+
}
|
|
6
|
+
const text = Buffer.concat(chunks).toString('utf8');
|
|
7
|
+
return options.stripFinalNewlines === true ? text.replace(/[\r\n]+$/u, '') : text;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=stdin.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { AuthSession } from '../../application/ports/auth-provider.js';
|
|
2
|
+
import type { ZodType } from 'zod';
|
|
3
|
+
export interface FuyaoRequest<T> {
|
|
4
|
+
path: string;
|
|
5
|
+
query?: Record<string, string | number | boolean | undefined>;
|
|
6
|
+
schema: ZodType<T>;
|
|
7
|
+
}
|
|
8
|
+
export interface FuyaoSuccess<T> {
|
|
9
|
+
data: T;
|
|
10
|
+
requestId?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface FuyaoClientOptions {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
auth: AuthSession;
|
|
15
|
+
timeoutMs?: number;
|
|
16
|
+
maxAttempts?: number;
|
|
17
|
+
fetch?: typeof globalThis.fetch;
|
|
18
|
+
sleep?: (milliseconds: number) => Promise<void>;
|
|
19
|
+
random?: () => number;
|
|
20
|
+
}
|
|
21
|
+
export declare class FuyaoClient {
|
|
22
|
+
private readonly options;
|
|
23
|
+
private readonly timeoutMs;
|
|
24
|
+
private readonly maxAttempts;
|
|
25
|
+
private readonly fetchImplementation;
|
|
26
|
+
private readonly sleep;
|
|
27
|
+
private readonly random;
|
|
28
|
+
constructor(options: FuyaoClientOptions);
|
|
29
|
+
request<T>(request: FuyaoRequest<T>): Promise<FuyaoSuccess<T>>;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { CliError } from '../../contracts/errors.js';
|
|
2
|
+
import { fuyaoEnvelopeSchema } from './envelope.js';
|
|
3
|
+
import { defaultSleep, parseRetryAfter, RETRYABLE_BUSINESS_CODES, retryDelayMs } from './retry.js';
|
|
4
|
+
function cliError(options, requestId) {
|
|
5
|
+
const complete = { ...options };
|
|
6
|
+
if (requestId !== undefined)
|
|
7
|
+
complete.requestId = requestId;
|
|
8
|
+
return new CliError(complete);
|
|
9
|
+
}
|
|
10
|
+
function businessError(code, message, requestId) {
|
|
11
|
+
if (code >= 2000 && code < 3000) {
|
|
12
|
+
return cliError({
|
|
13
|
+
code: `FUYAO_${code}`,
|
|
14
|
+
category: 'authentication',
|
|
15
|
+
message,
|
|
16
|
+
hint: 'Run `hithink-finance auth status` and verify API key permissions.',
|
|
17
|
+
retryable: false,
|
|
18
|
+
exitCode: 3,
|
|
19
|
+
}, requestId);
|
|
20
|
+
}
|
|
21
|
+
if (code >= 1000 && code < 2000) {
|
|
22
|
+
return cliError({
|
|
23
|
+
code: `FUYAO_${code}`,
|
|
24
|
+
category: 'validation',
|
|
25
|
+
message,
|
|
26
|
+
hint: 'Check the command schema and correct the supplied parameters.',
|
|
27
|
+
retryable: false,
|
|
28
|
+
exitCode: 2,
|
|
29
|
+
}, requestId);
|
|
30
|
+
}
|
|
31
|
+
return cliError({
|
|
32
|
+
code: `FUYAO_${code}`,
|
|
33
|
+
category: 'upstream',
|
|
34
|
+
message,
|
|
35
|
+
hint: 'Retry later and retain the request ID when reporting a persistent failure.',
|
|
36
|
+
retryable: RETRYABLE_BUSINESS_CODES.has(code),
|
|
37
|
+
exitCode: 4,
|
|
38
|
+
}, requestId);
|
|
39
|
+
}
|
|
40
|
+
export class FuyaoClient {
|
|
41
|
+
options;
|
|
42
|
+
timeoutMs;
|
|
43
|
+
maxAttempts;
|
|
44
|
+
fetchImplementation;
|
|
45
|
+
sleep;
|
|
46
|
+
random;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
this.timeoutMs = options.timeoutMs ?? 30_000;
|
|
50
|
+
this.maxAttempts = options.maxAttempts ?? 3;
|
|
51
|
+
this.fetchImplementation = options.fetch ?? globalThis.fetch;
|
|
52
|
+
this.sleep = options.sleep ?? defaultSleep;
|
|
53
|
+
this.random = options.random ?? Math.random;
|
|
54
|
+
}
|
|
55
|
+
async request(request) {
|
|
56
|
+
const url = new URL(request.path, this.options.baseUrl);
|
|
57
|
+
for (const [key, value] of Object.entries(request.query ?? {})) {
|
|
58
|
+
if (value !== undefined)
|
|
59
|
+
url.searchParams.set(key, String(value));
|
|
60
|
+
}
|
|
61
|
+
for (let attempt = 0; attempt < this.maxAttempts; attempt += 1) {
|
|
62
|
+
let response;
|
|
63
|
+
try {
|
|
64
|
+
response = await this.fetchImplementation(url, {
|
|
65
|
+
method: 'GET',
|
|
66
|
+
headers: { 'X-api-key': this.options.auth.apiKey, accept: 'application/json' },
|
|
67
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
if (attempt < this.maxAttempts - 1) {
|
|
72
|
+
await this.sleep(retryDelayMs(attempt, this.random));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
throw cliError({
|
|
76
|
+
code: 'UPSTREAM_NETWORK_FAILURE',
|
|
77
|
+
category: 'upstream',
|
|
78
|
+
message: 'The Fuyao service could not be reached before the timeout.',
|
|
79
|
+
hint: 'Check network connectivity and retry the command.',
|
|
80
|
+
retryable: true,
|
|
81
|
+
exitCode: 4,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
let body;
|
|
85
|
+
try {
|
|
86
|
+
body = await response.json();
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw cliError({
|
|
90
|
+
code: 'UPSTREAM_INVALID_RESPONSE',
|
|
91
|
+
category: 'upstream',
|
|
92
|
+
message: 'The Fuyao service returned invalid JSON.',
|
|
93
|
+
hint: 'Retry later and report the failure if it persists.',
|
|
94
|
+
retryable: false,
|
|
95
|
+
exitCode: 4,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
const parsedEnvelope = fuyaoEnvelopeSchema.safeParse(body);
|
|
99
|
+
if (!parsedEnvelope.success) {
|
|
100
|
+
throw cliError({
|
|
101
|
+
code: 'UPSTREAM_INVALID_RESPONSE',
|
|
102
|
+
category: 'upstream',
|
|
103
|
+
message: 'The Fuyao response envelope does not match the supported contract.',
|
|
104
|
+
hint: 'Upgrade the CLI or report a possible upstream contract change.',
|
|
105
|
+
retryable: false,
|
|
106
|
+
exitCode: 4,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
const envelope = parsedEnvelope.data;
|
|
110
|
+
if (envelope.code === 0) {
|
|
111
|
+
const parsedData = request.schema.safeParse(envelope.data);
|
|
112
|
+
if (!parsedData.success) {
|
|
113
|
+
throw cliError({
|
|
114
|
+
code: 'UPSTREAM_INVALID_DATA',
|
|
115
|
+
category: 'upstream',
|
|
116
|
+
message: 'Fuyao data does not match the command response schema.',
|
|
117
|
+
hint: 'Upgrade the CLI or report a possible upstream contract change.',
|
|
118
|
+
retryable: false,
|
|
119
|
+
exitCode: 4,
|
|
120
|
+
}, envelope.request_id);
|
|
121
|
+
}
|
|
122
|
+
const result = { data: parsedData.data };
|
|
123
|
+
if (envelope.request_id !== undefined)
|
|
124
|
+
result.requestId = envelope.request_id;
|
|
125
|
+
return result;
|
|
126
|
+
}
|
|
127
|
+
const error = businessError(envelope.code, envelope.message, envelope.request_id);
|
|
128
|
+
if (RETRYABLE_BUSINESS_CODES.has(envelope.code) && attempt < this.maxAttempts - 1) {
|
|
129
|
+
const retryAfter = parseRetryAfter(response.headers.get('retry-after'));
|
|
130
|
+
await this.sleep(retryAfter ?? retryDelayMs(attempt, this.random));
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
throw cliError({
|
|
136
|
+
code: 'UPSTREAM_NETWORK_FAILURE',
|
|
137
|
+
category: 'upstream',
|
|
138
|
+
message: 'The Fuyao request failed after all attempts.',
|
|
139
|
+
hint: 'Retry later.',
|
|
140
|
+
retryable: true,
|
|
141
|
+
exitCode: 4,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const fuyaoEnvelopeSchema: z.ZodObject<{
|
|
3
|
+
code: z.ZodNumber;
|
|
4
|
+
message: z.ZodString;
|
|
5
|
+
request_id: z.ZodOptional<z.ZodString>;
|
|
6
|
+
data: z.ZodUnknown;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export type FuyaoEnvelope = z.infer<typeof fuyaoEnvelopeSchema>;
|
|
9
|
+
//# sourceMappingURL=envelope.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface Page<T> {
|
|
2
|
+
items: T[];
|
|
3
|
+
hasMore: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface PaginationOptions {
|
|
6
|
+
maxPages?: number;
|
|
7
|
+
maxRows?: number;
|
|
8
|
+
streamed?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface PaginationResult<T> {
|
|
11
|
+
items: T[];
|
|
12
|
+
truncated: boolean;
|
|
13
|
+
pages: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function paginate<T>(fetchPage: (page: number) => Promise<Page<T>>, options: PaginationOptions): Promise<PaginationResult<T>>;
|
|
16
|
+
//# sourceMappingURL=pagination.d.ts.map
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { CliError } from '../../contracts/errors.js';
|
|
2
|
+
export async function paginate(fetchPage, options) {
|
|
3
|
+
if (options.maxPages === undefined &&
|
|
4
|
+
options.maxRows === undefined &&
|
|
5
|
+
options.streamed !== true) {
|
|
6
|
+
throw new CliError({
|
|
7
|
+
code: 'CLI_PAGINATION_BOUND_REQUIRED',
|
|
8
|
+
category: 'validation',
|
|
9
|
+
message: 'Paginated output requires a maximum page count, maximum row count, or a stream.',
|
|
10
|
+
hint: 'Set --limit/--max-pages or provide --output.',
|
|
11
|
+
retryable: false,
|
|
12
|
+
exitCode: 2,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
const items = [];
|
|
16
|
+
let pageNumber = 0;
|
|
17
|
+
let truncated = false;
|
|
18
|
+
while (true) {
|
|
19
|
+
if (options.maxPages !== undefined && pageNumber >= options.maxPages) {
|
|
20
|
+
truncated = true;
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
const page = await fetchPage(pageNumber);
|
|
24
|
+
pageNumber += 1;
|
|
25
|
+
const remaining = options.maxRows === undefined ? page.items.length : options.maxRows - items.length;
|
|
26
|
+
if (remaining <= 0) {
|
|
27
|
+
truncated = true;
|
|
28
|
+
break;
|
|
29
|
+
}
|
|
30
|
+
items.push(...page.items.slice(0, remaining));
|
|
31
|
+
if (page.items.length > remaining) {
|
|
32
|
+
truncated = true;
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
if (!page.hasMore)
|
|
36
|
+
break;
|
|
37
|
+
if (options.maxRows !== undefined && items.length >= options.maxRows) {
|
|
38
|
+
truncated = true;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { items, truncated, pages: pageNumber };
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=pagination.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const RETRYABLE_BUSINESS_CODES: Set<number>;
|
|
2
|
+
export declare function parseRetryAfter(value: string | null, now?: number): number | undefined;
|
|
3
|
+
export declare function retryDelayMs(attempt: number, random: () => number): number;
|
|
4
|
+
export declare function defaultSleep(milliseconds: number): Promise<void>;
|
|
5
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const RETRYABLE_BUSINESS_CODES = new Set([4001, 5001, 5002, 5003]);
|
|
2
|
+
export function parseRetryAfter(value, now = Date.now()) {
|
|
3
|
+
if (value === null)
|
|
4
|
+
return undefined;
|
|
5
|
+
const seconds = Number(value);
|
|
6
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
7
|
+
return Math.round(seconds * 1000);
|
|
8
|
+
const date = Date.parse(value);
|
|
9
|
+
return Number.isNaN(date) ? undefined : Math.max(0, date - now);
|
|
10
|
+
}
|
|
11
|
+
export function retryDelayMs(attempt, random) {
|
|
12
|
+
const base = Math.min(1000 * 2 ** attempt, 8000);
|
|
13
|
+
return Math.round(base + base * 0.2 * random());
|
|
14
|
+
}
|
|
15
|
+
export async function defaultSleep(milliseconds) {
|
|
16
|
+
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const TEN_YEARS_MS: number;
|
|
2
|
+
export interface TimeSlice {
|
|
3
|
+
start: number;
|
|
4
|
+
end: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function sliceTimeWindow(start: number, end: number, size?: number): TimeSlice[];
|
|
7
|
+
export declare function deduplicateAndSort<T>(values: readonly T[], key: (value: T) => string | number): T[];
|
|
8
|
+
//# sourceMappingURL=windowing.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const TEN_YEARS_MS = Math.floor(10 * 365.25 * 24 * 60 * 60 * 1000);
|
|
2
|
+
export function sliceTimeWindow(start, end, size = TEN_YEARS_MS) {
|
|
3
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || end < start || size < 1) {
|
|
4
|
+
throw new RangeError('Invalid time window.');
|
|
5
|
+
}
|
|
6
|
+
const slices = [];
|
|
7
|
+
let cursor = start;
|
|
8
|
+
while (cursor <= end) {
|
|
9
|
+
const sliceEnd = Math.min(cursor + size, end);
|
|
10
|
+
slices.push({ start: cursor, end: sliceEnd });
|
|
11
|
+
cursor = sliceEnd + 1;
|
|
12
|
+
}
|
|
13
|
+
return slices;
|
|
14
|
+
}
|
|
15
|
+
export function deduplicateAndSort(values, key) {
|
|
16
|
+
const unique = new Map();
|
|
17
|
+
for (const value of values)
|
|
18
|
+
unique.set(key(value), value);
|
|
19
|
+
return [...unique.values()].sort((left, right) => {
|
|
20
|
+
const leftKey = key(left);
|
|
21
|
+
const rightKey = key(right);
|
|
22
|
+
return leftKey < rightKey ? -1 : leftKey > rightKey ? 1 : 0;
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=windowing.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { buildSkillManifest } from './manifest.js';
|
|
2
|
+
export async function detectSkillDrift(targetRoot, canonical) {
|
|
3
|
+
const installed = await buildSkillManifest(targetRoot, canonical.cliVersion);
|
|
4
|
+
return Object.entries(canonical.files)
|
|
5
|
+
.filter(([file, hash]) => installed.files[file] !== hash)
|
|
6
|
+
.map(([file]) => file);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=drift.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function syncSkills(packageRoot: string): Promise<{
|
|
2
|
+
code: number;
|
|
3
|
+
}>;
|
|
4
|
+
export declare function removeSkills(packageRoot: string): Promise<{
|
|
5
|
+
code: number;
|
|
6
|
+
}>;
|
|
7
|
+
export declare function skillsRemoveArguments(packageRoot: string): ReturnType<typeof skillsCliArguments>;
|
|
8
|
+
export declare function skillsCliArguments(packageRoot: string): {
|
|
9
|
+
command: string;
|
|
10
|
+
args: string[];
|
|
11
|
+
env: NodeJS.ProcessEnv;
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=installer.d.ts.map
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export async function syncSkills(packageRoot) {
|
|
4
|
+
const invocation = skillsCliArguments(packageRoot);
|
|
5
|
+
return run(invocation);
|
|
6
|
+
}
|
|
7
|
+
export async function removeSkills(packageRoot) {
|
|
8
|
+
return run(skillsRemoveArguments(packageRoot));
|
|
9
|
+
}
|
|
10
|
+
function run(invocation) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
const child = spawn(invocation.command, invocation.args, {
|
|
13
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
14
|
+
env: invocation.env,
|
|
15
|
+
windowsHide: true,
|
|
16
|
+
});
|
|
17
|
+
child.once('error', reject);
|
|
18
|
+
child.once('exit', (code) => resolve({ code: code ?? 1 }));
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
const skillNames = [
|
|
22
|
+
'hithink-finance-shared',
|
|
23
|
+
'hithink-finance-symbol',
|
|
24
|
+
'hithink-finance-market',
|
|
25
|
+
'hithink-finance-special-data',
|
|
26
|
+
'hithink-finance-financials',
|
|
27
|
+
'hithink-finance-index',
|
|
28
|
+
'hithink-finance-data',
|
|
29
|
+
'hithink-finance-research',
|
|
30
|
+
];
|
|
31
|
+
export function skillsRemoveArguments(packageRoot) {
|
|
32
|
+
const cli = path.join(packageRoot, 'node_modules', 'skills', 'bin', 'cli.mjs');
|
|
33
|
+
return {
|
|
34
|
+
command: process.execPath,
|
|
35
|
+
args: [cli, 'remove', ...skillNames, '--global', '--yes'],
|
|
36
|
+
env: { ...process.env, DISABLE_TELEMETRY: '1' },
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
export function skillsCliArguments(packageRoot) {
|
|
40
|
+
const cli = path.join(packageRoot, 'node_modules', 'skills', 'bin', 'cli.mjs');
|
|
41
|
+
const source = path.join(packageRoot, 'skills');
|
|
42
|
+
return {
|
|
43
|
+
command: process.execPath,
|
|
44
|
+
args: [cli, 'add', source, '--global', '--copy', '--all', '--full-depth'],
|
|
45
|
+
env: { ...process.env, DISABLE_TELEMETRY: '1' },
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=installer.js.map
|