@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
package/CHANGELOG.md
ADDED
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 HiThink-Tech
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# hithink-finance CLI
|
|
2
|
+
|
|
3
|
+
`hithink-finance` 是同花顺金融数据服务的 Node.js 命令行客户端,面向人类终端、AI Agent、CI 和自动化脚本。它统一提供远端数据、本地 DuckDB、认证、结构化输出、能力发现、诊断和生命周期管理,运行时不依赖 monorepo 的 Python 子项目。
|
|
4
|
+
|
|
5
|
+
CLI 文档只维护命令和运行语义;上游 REST 参数与响应字段统一见 [`docs/api/`](../docs/api/README.md)。
|
|
6
|
+
|
|
7
|
+
## 环境与安装
|
|
8
|
+
|
|
9
|
+
需要 Node.js `>=22.12.0`。
|
|
10
|
+
|
|
11
|
+
### 从当前仓库运行
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
cd hithink-finance-cli
|
|
15
|
+
npm ci --ignore-scripts
|
|
16
|
+
npm run build
|
|
17
|
+
node dist/cli/main.js --help
|
|
18
|
+
node dist/cli/main.js capabilities --format json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### npm 发布后安装
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install -g @hithink-tech/hithink-finance-cli
|
|
25
|
+
hithink-finance version --format json
|
|
26
|
+
hithink-finance doctor --format json
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
如果 `npm install` 返回 `E404`,说明包尚未发布或当前 registry 无法访问。报告实际状态,不要在终端指引中把源码安装伪装成正式发布安装。
|
|
30
|
+
|
|
31
|
+
## API Key 与认证
|
|
32
|
+
|
|
33
|
+
远端命令使用统一 API Key,获取地址:<https://fuyao.aicubes.cn/admin>。
|
|
34
|
+
|
|
35
|
+
交互式终端:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
hithink-finance auth login
|
|
39
|
+
hithink-finance auth status --format json
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Agent/CI 使用 stdin 或进程环境变量:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
printf '%s' "$HITHINK_FINANCE_API_KEY" | \
|
|
46
|
+
hithink-finance auth login --api-key-stdin --format json
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
不要把 Key 写入配置文件、命令参数记录、日志、Markdown、对话或 Git。`config show` 只展示非敏感配置。
|
|
50
|
+
|
|
51
|
+
## 能力发现
|
|
52
|
+
|
|
53
|
+
CLI 的当前命令事实由运行时输出提供:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
hithink-finance capabilities --format json
|
|
57
|
+
hithink-finance schema market.snapshot --format json
|
|
58
|
+
hithink-finance market snapshot --help
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Agent 不应只凭 README 猜参数。先读取 `capabilities`,再对目标 capability 读取 `schema`。
|
|
62
|
+
|
|
63
|
+
## 命令导航
|
|
64
|
+
|
|
65
|
+
| 命令组 | 用途 |
|
|
66
|
+
| ------------------------------- | ---------------------------------------------------- |
|
|
67
|
+
| `version`, `doctor` | 版本和环境诊断 |
|
|
68
|
+
| `auth`, `config` | API Key 与非敏感配置 |
|
|
69
|
+
| `symbol` | 标的检索与代码表 |
|
|
70
|
+
| `market` | 个股行情、交易日历、本地面板和复权因子 |
|
|
71
|
+
| `financials` | 财务报表与财务指标 |
|
|
72
|
+
| `index` | 指数/板块目录、成分和行情 |
|
|
73
|
+
| `special` | 涨停、连板、异动、热榜和龙虎榜 |
|
|
74
|
+
| `data` | 本地数据库初始化、同步、状态、校验、迁移、修复和清理 |
|
|
75
|
+
| `db` | DuckDB 描述、只读 SQL 和导出 |
|
|
76
|
+
| `capabilities`, `schema` | 机器可读命令契约 |
|
|
77
|
+
| `skills`, `update`, `uninstall` | Agent Skills 与 CLI 生命周期 |
|
|
78
|
+
|
|
79
|
+
常见调用:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
hithink-finance symbol search --q 600519 --limit 5 --format json
|
|
83
|
+
hithink-finance market snapshot --thscodes 600519.SH --format json
|
|
84
|
+
hithink-finance financials income --thscode 600519.SH --limit 4 --format json
|
|
85
|
+
hithink-finance index constituents --thscode 000300.SH --format json
|
|
86
|
+
hithink-finance special limit-up-pool --size 50 --format json
|
|
87
|
+
hithink-finance data status --format json
|
|
88
|
+
hithink-finance db query --sql "SELECT * FROM v_daily_qfq LIMIT 10" --format json
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## 输出与错误语义
|
|
92
|
+
|
|
93
|
+
- 机器读取显式使用 `--format json`;人类交互可选择 `table`。
|
|
94
|
+
- 成功以进程退出码 0 和 JSON 信封 `ok=true` 为准。
|
|
95
|
+
- 失败以非 0 退出码和 `ok=false` 为准,读取 `error.code`、`error.category`、`error.hint` 与请求标识。
|
|
96
|
+
- 远端上游使用 `{code, message, request_id, data}`,但 CLI 会规范化为自己的信封;不要混用两套成功判断。
|
|
97
|
+
- `--debug` 只向 stderr 输出诊断,不污染机器可读 stdout。
|
|
98
|
+
|
|
99
|
+
## 大结果与数据源
|
|
100
|
+
|
|
101
|
+
`--source auto|local|remote` 控制支持该选项的行情路由。只有具体命令声明的 `--output <path>` 才能直接落盘,它不是全局选项。
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
hithink-finance market panel \
|
|
105
|
+
--start 2026-01-01 --end 2026-01-31 \
|
|
106
|
+
--output panel.parquet --file-format parquet --format json
|
|
107
|
+
|
|
108
|
+
hithink-finance db export \
|
|
109
|
+
--sql "SELECT * FROM v_daily_qfq" \
|
|
110
|
+
--output daily.parquet --file-format parquet --format json
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
全市场、分页全集、多标的或长时间窗口必须落盘。终端和 Agent 对话只报告路径、行数、窗口和摘要。
|
|
114
|
+
|
|
115
|
+
## 本地 DuckDB
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
hithink-finance data init --format json
|
|
119
|
+
hithink-finance data sync --format json
|
|
120
|
+
hithink-finance data status --format json
|
|
121
|
+
hithink-finance data validate --format json
|
|
122
|
+
hithink-finance db describe --format json
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
初始化和同步需要远端 API Key;已有本地库的只读查询通常不需要。迁移、修复、清理和卸载等有副作用操作先查看计划或帮助,并在命令要求时获得用户明确确认。
|
|
126
|
+
|
|
127
|
+
## 配置优先级
|
|
128
|
+
|
|
129
|
+
CLI 按以下顺序解析非敏感配置:
|
|
130
|
+
|
|
131
|
+
1. CLI 参数
|
|
132
|
+
2. 环境变量
|
|
133
|
+
3. 项目 `hithink-finance.config.json`
|
|
134
|
+
4. 用户配置
|
|
135
|
+
5. 默认值
|
|
136
|
+
|
|
137
|
+
使用 `hithink-finance config show --format json` 查看最终生效的非敏感配置。多套环境使用 `--profile <name>`。
|
|
138
|
+
|
|
139
|
+
## Agent Skill
|
|
140
|
+
|
|
141
|
+
跨 REST API、MCP、CLI 和 Python SDK 的统一入口是仓库根 [`skills/hithink-finance`](../skills/hithink-finance/SKILL.md):
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
npx skills add HiThink-Tech/Financial-API --skill hithink-finance -g --yes
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
CLI 包还可以通过 `hithink-finance skills status|sync|remove` 管理命令专用的领域 Skills。它们补充 CLI 参数路由,不替代统一 `hithink-finance` Skill,也不是上游 API 文档的事实源。
|
|
148
|
+
|
|
149
|
+
## 开发与验证
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm ci --ignore-scripts
|
|
153
|
+
npm run generate:contracts
|
|
154
|
+
npm run verify
|
|
155
|
+
node scripts/release-smoke.mjs
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- 改动命令、选项、输出或错误语义后,先运行 `npm run generate:contracts` 更新 CLI 自带能力与 Skill 契约。
|
|
159
|
+
- `npm run verify` 依次检查格式、lint、类型、测试和构建。
|
|
160
|
+
- 发布流程与许可守门见 [`docs/maintainers/npm-publishing.md`](docs/maintainers/npm-publishing.md)。
|
|
161
|
+
- 版本变化见 [`CHANGELOG.md`](CHANGELOG.md),安全问题按 [`SECURITY.md`](SECURITY.md) 私下报告。
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { type PlatformPaths } from '../infrastructure/filesystem/platform-paths.js';
|
|
3
|
+
declare const configFileSchema: z.ZodObject<{
|
|
4
|
+
dbPath: z.ZodOptional<z.ZodString>;
|
|
5
|
+
profile: z.ZodOptional<z.ZodString>;
|
|
6
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
7
|
+
auto: "auto";
|
|
8
|
+
json: "json";
|
|
9
|
+
ndjson: "ndjson";
|
|
10
|
+
csv: "csv";
|
|
11
|
+
table: "table";
|
|
12
|
+
}>>;
|
|
13
|
+
language: z.ZodOptional<z.ZodEnum<{
|
|
14
|
+
"zh-CN": "zh-CN";
|
|
15
|
+
en: "en";
|
|
16
|
+
}>>;
|
|
17
|
+
updateCheck: z.ZodOptional<z.ZodBoolean>;
|
|
18
|
+
}, z.core.$strict>;
|
|
19
|
+
export type ConfigFile = z.infer<typeof configFileSchema>;
|
|
20
|
+
export interface ConfigOverrides {
|
|
21
|
+
dbPath?: string;
|
|
22
|
+
profile?: string;
|
|
23
|
+
format?: ConfigFile['format'];
|
|
24
|
+
language?: ConfigFile['language'];
|
|
25
|
+
configPath?: string;
|
|
26
|
+
}
|
|
27
|
+
export interface ResolvedConfig {
|
|
28
|
+
dbPath: string;
|
|
29
|
+
profile: string;
|
|
30
|
+
format: NonNullable<ConfigFile['format']>;
|
|
31
|
+
language?: ConfigFile['language'];
|
|
32
|
+
updateCheck: boolean;
|
|
33
|
+
configPath?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface LoadConfigInput {
|
|
36
|
+
cli?: ConfigOverrides;
|
|
37
|
+
env?: NodeJS.ProcessEnv;
|
|
38
|
+
cwd?: string;
|
|
39
|
+
paths?: PlatformPaths;
|
|
40
|
+
}
|
|
41
|
+
export declare function parseConfigFile(value: unknown, source: string): ConfigFile;
|
|
42
|
+
export declare function loadConfig(input?: LoadConfigInput): Promise<ResolvedConfig>;
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { CliError } from '../contracts/errors.js';
|
|
5
|
+
import { createPlatformPaths, } from '../infrastructure/filesystem/platform-paths.js';
|
|
6
|
+
const configFileSchema = z
|
|
7
|
+
.object({
|
|
8
|
+
dbPath: z.string().min(1).optional(),
|
|
9
|
+
profile: z.string().min(1).optional(),
|
|
10
|
+
format: z.enum(['auto', 'json', 'ndjson', 'csv', 'table']).optional(),
|
|
11
|
+
language: z.enum(['zh-CN', 'en']).optional(),
|
|
12
|
+
updateCheck: z.boolean().optional(),
|
|
13
|
+
})
|
|
14
|
+
.strict();
|
|
15
|
+
const SECRET_KEYS = new Set(['apikey', 'token', 'authorization', 'cookie', 'secret']);
|
|
16
|
+
function containsSecret(value) {
|
|
17
|
+
if (Array.isArray(value))
|
|
18
|
+
return value.some(containsSecret);
|
|
19
|
+
if (value === null || typeof value !== 'object')
|
|
20
|
+
return false;
|
|
21
|
+
return Object.entries(value).some(([key, entry]) => {
|
|
22
|
+
const normalized = key.toLowerCase().replaceAll(/[^a-z]/g, '');
|
|
23
|
+
return SECRET_KEYS.has(normalized) || containsSecret(entry);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
function configError(code, message, hint) {
|
|
27
|
+
return new CliError({
|
|
28
|
+
code,
|
|
29
|
+
category: 'validation',
|
|
30
|
+
message: `${code}: ${message}`,
|
|
31
|
+
hint,
|
|
32
|
+
retryable: false,
|
|
33
|
+
exitCode: 2,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
export function parseConfigFile(value, source) {
|
|
37
|
+
if (containsSecret(value)) {
|
|
38
|
+
throw configError('CONFIG_SECRET_FORBIDDEN', `Secrets are not allowed in ${source}.`, 'Use HITHINK_FINANCE_API_KEY, --api-key-stdin, or the system credential store.');
|
|
39
|
+
}
|
|
40
|
+
const parsed = configFileSchema.safeParse(value);
|
|
41
|
+
if (!parsed.success) {
|
|
42
|
+
throw configError('CONFIG_INVALID', `Configuration in ${source} does not match the supported schema.`, parsed.error.issues.map((issue) => issue.message).join('; '));
|
|
43
|
+
}
|
|
44
|
+
return parsed.data;
|
|
45
|
+
}
|
|
46
|
+
async function readConfig(configPath) {
|
|
47
|
+
let content;
|
|
48
|
+
try {
|
|
49
|
+
content = await readFile(configPath, 'utf8');
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT') {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
return parseConfigFile(JSON.parse(content), configPath);
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (error instanceof CliError)
|
|
62
|
+
throw error;
|
|
63
|
+
throw configError('CONFIG_INVALID', `Configuration in ${configPath} is not valid JSON.`, 'Fix the JSON syntax or remove the file.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function resolveFrom(baseDirectory, value) {
|
|
67
|
+
return path.isAbsolute(value) ? path.normalize(value) : path.resolve(baseDirectory, value);
|
|
68
|
+
}
|
|
69
|
+
export async function loadConfig(input = {}) {
|
|
70
|
+
const cwd = input.cwd ?? process.cwd();
|
|
71
|
+
const env = input.env ?? process.env;
|
|
72
|
+
const paths = input.paths ?? createPlatformPaths({ env });
|
|
73
|
+
const cli = input.cli ?? {};
|
|
74
|
+
const explicitConfigPath = cli.configPath ?? env.HITHINK_FINANCE_CONFIG;
|
|
75
|
+
const projectConfigPath = resolveFrom(cwd, explicitConfigPath ?? 'hithink-finance.config.json');
|
|
76
|
+
const [userConfig, projectConfig] = await Promise.all([
|
|
77
|
+
readConfig(paths.userConfigFile),
|
|
78
|
+
readConfig(projectConfigPath),
|
|
79
|
+
]);
|
|
80
|
+
let dbPath = paths.defaultDbPath;
|
|
81
|
+
if (userConfig?.dbPath !== undefined) {
|
|
82
|
+
dbPath = resolveFrom(path.dirname(paths.userConfigFile), userConfig.dbPath);
|
|
83
|
+
}
|
|
84
|
+
if (projectConfig?.dbPath !== undefined) {
|
|
85
|
+
dbPath = resolveFrom(path.dirname(projectConfigPath), projectConfig.dbPath);
|
|
86
|
+
}
|
|
87
|
+
if (env.HITHINK_FINANCE_DB_PATH !== undefined) {
|
|
88
|
+
dbPath = resolveFrom(cwd, env.HITHINK_FINANCE_DB_PATH);
|
|
89
|
+
}
|
|
90
|
+
if (cli.dbPath !== undefined) {
|
|
91
|
+
dbPath = resolveFrom(cwd, cli.dbPath);
|
|
92
|
+
}
|
|
93
|
+
const language = cli.language ?? projectConfig?.language ?? userConfig?.language;
|
|
94
|
+
const result = {
|
|
95
|
+
dbPath,
|
|
96
|
+
profile: cli.profile ??
|
|
97
|
+
env.HITHINK_FINANCE_PROFILE ??
|
|
98
|
+
projectConfig?.profile ??
|
|
99
|
+
userConfig?.profile ??
|
|
100
|
+
'default',
|
|
101
|
+
format: cli.format ?? projectConfig?.format ?? userConfig?.format ?? 'auto',
|
|
102
|
+
updateCheck: env.HITHINK_FINANCE_NO_UPDATE_CHECK === undefined &&
|
|
103
|
+
(projectConfig?.updateCheck ?? userConfig?.updateCheck ?? true),
|
|
104
|
+
};
|
|
105
|
+
if (language !== undefined)
|
|
106
|
+
result.language = language;
|
|
107
|
+
if (explicitConfigPath !== undefined)
|
|
108
|
+
result.configPath = projectConfigPath;
|
|
109
|
+
return result;
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface AuthSession {
|
|
2
|
+
method: 'api-key';
|
|
3
|
+
profile: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
source: 'explicit' | 'environment' | 'keyring';
|
|
6
|
+
}
|
|
7
|
+
export interface LoginInput {
|
|
8
|
+
profile: string;
|
|
9
|
+
apiKey: string;
|
|
10
|
+
}
|
|
11
|
+
export interface AuthStatus {
|
|
12
|
+
method: 'api-key';
|
|
13
|
+
profile: string;
|
|
14
|
+
configured: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface AuthProvider {
|
|
17
|
+
readonly method: string;
|
|
18
|
+
resolve(profile: string, explicit?: string): Promise<AuthSession>;
|
|
19
|
+
login(input: LoginInput): Promise<AuthStatus>;
|
|
20
|
+
logout(profile: string): Promise<void>;
|
|
21
|
+
status(profile: string): Promise<AuthStatus>;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=auth-provider.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type DataSource = 'auto' | 'local' | 'remote';
|
|
2
|
+
export type RequestKind = 'snapshot' | 'financials' | 'index' | 'special' | 'calendar' | 'panel' | 'factors' | 'db' | 'maintenance' | 'history';
|
|
3
|
+
export interface SourceRequest {
|
|
4
|
+
kind: RequestKind;
|
|
5
|
+
requested: DataSource;
|
|
6
|
+
symbolCount?: number;
|
|
7
|
+
}
|
|
8
|
+
export interface LocalState {
|
|
9
|
+
exists: boolean;
|
|
10
|
+
coversWindow: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function chooseSource(request: SourceRequest, local: LocalState): Exclude<DataSource, 'auto'>;
|
|
13
|
+
//# sourceMappingURL=source-policy.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { CliError } from '../contracts/errors.js';
|
|
2
|
+
function sourceError(code, message) {
|
|
3
|
+
throw new CliError({
|
|
4
|
+
code,
|
|
5
|
+
category: 'local-data',
|
|
6
|
+
message: `${code}: ${message}`,
|
|
7
|
+
hint: 'Initialize/sync local data or choose a supported --source.',
|
|
8
|
+
retryable: false,
|
|
9
|
+
exitCode: 5,
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export function chooseSource(request, local) {
|
|
13
|
+
const remoteOnly = ['snapshot', 'financials', 'index', 'special', 'calendar'].includes(request.kind);
|
|
14
|
+
const localOnly = ['panel', 'factors', 'db', 'maintenance'].includes(request.kind);
|
|
15
|
+
if (request.requested === 'local' && remoteOnly)
|
|
16
|
+
return sourceError('SOURCE_NOT_SUPPORTED', `${request.kind} is remote-only.`);
|
|
17
|
+
if (request.requested === 'remote' && localOnly)
|
|
18
|
+
return sourceError('SOURCE_NOT_SUPPORTED', `${request.kind} is local-only.`);
|
|
19
|
+
if (request.requested !== 'auto')
|
|
20
|
+
return request.requested;
|
|
21
|
+
if (remoteOnly)
|
|
22
|
+
return 'remote';
|
|
23
|
+
if (localOnly)
|
|
24
|
+
return 'local';
|
|
25
|
+
if (local.exists && local.coversWindow)
|
|
26
|
+
return 'local';
|
|
27
|
+
if ((request.symbolCount ?? 1) === 1)
|
|
28
|
+
return 'remote';
|
|
29
|
+
return sourceError('DATA_INITIALIZATION_REQUIRED', 'Bulk history requires covered local data.');
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=source-policy.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { CliError } from '../../contracts/errors.js';
|
|
4
|
+
export async function cleanManagedCache(cachePath, managedRoot) {
|
|
5
|
+
const target = path.resolve(cachePath);
|
|
6
|
+
const root = path.resolve(managedRoot);
|
|
7
|
+
if (target !== root && !target.startsWith(`${root}${path.sep}`))
|
|
8
|
+
throw new CliError({
|
|
9
|
+
code: 'PATH_OUTSIDE_MANAGED_ROOT',
|
|
10
|
+
category: 'validation',
|
|
11
|
+
message: 'The cleanup target is outside the managed cache root.',
|
|
12
|
+
hint: 'Only pass paths inside the CLI-managed cache directory.',
|
|
13
|
+
retryable: false,
|
|
14
|
+
exitCode: 2,
|
|
15
|
+
});
|
|
16
|
+
await rm(target, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=data-clean.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DuckDBConnection } from '@duckdb/node-api';
|
|
2
|
+
import { type ParquetBundle } from '../../infrastructure/duckdb/importer.js';
|
|
3
|
+
export declare function initializeData(connection: DuckDBConnection, bundle: ParquetBundle): Promise<number>;
|
|
4
|
+
//# sourceMappingURL=data-init.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { importParquetBundle } from '../../infrastructure/duckdb/importer.js';
|
|
2
|
+
import { applyMigrations } from '../../infrastructure/duckdb/migrations.js';
|
|
3
|
+
import { rebuildAdjustmentFactors } from '../../infrastructure/duckdb/factors.js';
|
|
4
|
+
export async function initializeData(connection, bundle) {
|
|
5
|
+
await applyMigrations(connection);
|
|
6
|
+
await importParquetBundle(connection, bundle);
|
|
7
|
+
return rebuildAdjustmentFactors(connection);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=data-init.js.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { rm } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { CliError } from '../../contracts/errors.js';
|
|
4
|
+
export async function removeDatabase(databasePath, allowedExactPath, confirmed) {
|
|
5
|
+
const target = path.resolve(databasePath);
|
|
6
|
+
const allowed = path.resolve(allowedExactPath);
|
|
7
|
+
if (target !== allowed)
|
|
8
|
+
throw new CliError({
|
|
9
|
+
code: 'PATH_NOT_CONFIRMED',
|
|
10
|
+
category: 'validation',
|
|
11
|
+
message: 'The database path does not match the confirmed removal target.',
|
|
12
|
+
hint: 'Run `data remove --plan` and confirm the exact path before deleting.',
|
|
13
|
+
retryable: false,
|
|
14
|
+
exitCode: 2,
|
|
15
|
+
});
|
|
16
|
+
if (!confirmed)
|
|
17
|
+
throw new CliError({
|
|
18
|
+
code: 'CONFIRMATION_REQUIRED',
|
|
19
|
+
category: 'validation',
|
|
20
|
+
message: 'Database removal requires explicit confirmation.',
|
|
21
|
+
hint: 'Re-run with --yes after reviewing the removal plan.',
|
|
22
|
+
retryable: false,
|
|
23
|
+
exitCode: 2,
|
|
24
|
+
});
|
|
25
|
+
await rm(target, { force: true });
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=data-remove.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SyncDecision = 'SKIP' | 'INCREMENTAL' | 'FULL';
|
|
2
|
+
export interface LocalSyncState {
|
|
3
|
+
maxDate: string | null;
|
|
4
|
+
releaseId: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface RemoteReleaseState {
|
|
7
|
+
latestDate: string;
|
|
8
|
+
releaseId: string;
|
|
9
|
+
lagTradingDays: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function chooseSyncDecision(local: LocalSyncState, remote: RemoteReleaseState, maxIncrementalLag?: number): SyncDecision;
|
|
12
|
+
import type { DuckDBConnection } from '@duckdb/node-api';
|
|
13
|
+
export interface FuyaoSyncOptions {
|
|
14
|
+
baseUrl: string;
|
|
15
|
+
apiKey: string;
|
|
16
|
+
cacheDir: string;
|
|
17
|
+
now?: Date;
|
|
18
|
+
}
|
|
19
|
+
export declare function syncDataFromFuyao(connection: DuckDBConnection, options: FuyaoSyncOptions): Promise<{
|
|
20
|
+
decision: SyncDecision;
|
|
21
|
+
releaseId: string;
|
|
22
|
+
factorRows: number;
|
|
23
|
+
quality: unknown;
|
|
24
|
+
}>;
|
|
25
|
+
//# sourceMappingURL=data-sync.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export function chooseSyncDecision(local, remote, maxIncrementalLag = 5) {
|
|
2
|
+
if (local.maxDate === null)
|
|
3
|
+
return 'FULL';
|
|
4
|
+
if (local.maxDate >= remote.latestDate && local.releaseId === remote.releaseId)
|
|
5
|
+
return 'SKIP';
|
|
6
|
+
return remote.lagTradingDays <= maxIncrementalLag ? 'INCREMENTAL' : 'FULL';
|
|
7
|
+
}
|
|
8
|
+
import { fetchFuyaoDump } from '../../infrastructure/duckdb/dump-client.js';
|
|
9
|
+
import { importParquetBundle } from '../../infrastructure/duckdb/importer.js';
|
|
10
|
+
import { applyMigrations } from '../../infrastructure/duckdb/migrations.js';
|
|
11
|
+
import { rebuildAdjustmentFactors } from '../../infrastructure/duckdb/factors.js';
|
|
12
|
+
import { validateDatabase } from '../../infrastructure/duckdb/quality.js';
|
|
13
|
+
async function scalar(connection, sql) {
|
|
14
|
+
const reader = await connection.runAndReadAll(sql);
|
|
15
|
+
const value = reader.getRowsJson()[0]?.[0];
|
|
16
|
+
return value === null || value === undefined ? null : String(value);
|
|
17
|
+
}
|
|
18
|
+
export async function syncDataFromFuyao(connection, options) {
|
|
19
|
+
await applyMigrations(connection);
|
|
20
|
+
const maxDate = await scalar(connection, 'SELECT max(date)::VARCHAR FROM raw_kline_daily');
|
|
21
|
+
const previousRelease = await scalar(connection, "SELECT value FROM _meta WHERE key='last_kline_release_id'");
|
|
22
|
+
const now = options.now ?? new Date();
|
|
23
|
+
const lagDays = maxDate === null
|
|
24
|
+
? Number.POSITIVE_INFINITY
|
|
25
|
+
: Math.floor((now.getTime() - Date.parse(`${maxDate}T00:00:00Z`)) / 86_400_000);
|
|
26
|
+
const kind = maxDate === null || lagDays > 14 ? 'daily-k' : 'daily-k-10d';
|
|
27
|
+
const kline = await fetchFuyaoDump({ ...options, kind });
|
|
28
|
+
if (previousRelease === kline.releaseId) {
|
|
29
|
+
return {
|
|
30
|
+
decision: 'SKIP',
|
|
31
|
+
releaseId: kline.releaseId,
|
|
32
|
+
factorRows: 0,
|
|
33
|
+
quality: await validateDatabase(connection),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const events = await fetchFuyaoDump({ ...options, kind: 'adjustment-factors' });
|
|
37
|
+
const decision = kind === 'daily-k' ? 'FULL' : 'INCREMENTAL';
|
|
38
|
+
const batchId = `${decision.toLowerCase()}-${Date.now()}`;
|
|
39
|
+
await importParquetBundle(connection, {
|
|
40
|
+
klinePath: kline.path,
|
|
41
|
+
eventsPath: events.path,
|
|
42
|
+
batchId,
|
|
43
|
+
source: `fuyao:${kline.releaseId}`,
|
|
44
|
+
mode: decision,
|
|
45
|
+
});
|
|
46
|
+
const factorRows = await rebuildAdjustmentFactors(connection);
|
|
47
|
+
await connection.run("INSERT OR REPLACE INTO _meta VALUES ('last_kline_release_id',$kline),('last_adjustment_release_id',$events),('last_sync_mode',$mode)", { kline: kline.releaseId, events: events.releaseId, mode: decision });
|
|
48
|
+
return {
|
|
49
|
+
decision,
|
|
50
|
+
releaseId: kline.releaseId,
|
|
51
|
+
factorRows,
|
|
52
|
+
quality: await validateDatabase(connection),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=data-sync.js.map
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DuckDBConnection } from '@duckdb/node-api';
|
|
2
|
+
export declare function queryReadOnly(connection: DuckDBConnection, sql: string): Promise<Record<string, unknown>[]>;
|
|
3
|
+
export declare function exportQuery(connection: DuckDBConnection, sql: string, outputPath: string, format: 'ndjson' | 'csv' | 'parquet'): Promise<number>;
|
|
4
|
+
//# sourceMappingURL=local-query.d.ts.map
|