Skip to main content

futucli/cli/commands/
key.rs

1//! Key-management clap argument structs split from commands.rs.
2
3use std::path::PathBuf;
4
5use clap::Args;
6
7#[derive(Args)]
8pub struct GenKeyArgs {
9    /// 人读 ID(审计日志用,必须唯一)
10    #[arg(long)]
11    pub(crate) id: String,
12
13    /// 授予的 scope,逗号分隔(v1.4.34+ 补齐 admin;详见 --help 的
14    /// scopes 长说明)
15    #[arg(
16        long,
17        long_help = "\
18授予的 scope,逗号分隔。
19
20可选(v1.8.0+ 含 qot:write):
21
22  qot:read         行情读(订阅 / 报价 / K 线 / 摆盘)
23  qot:write        行情用户态写(自选股 / 到价提醒 / 期权异动提醒 / 股票备注)
24  acc:read         交易账户只读(资金 / 持仓 / 订单历史)
25  trade:simulate   模拟盘下单 / 改单 / 撤单
26  trade:real       实盘下单 / 改单 / 撤单(高风险)
27  trade:unlock     解锁交易(/api/unlock-trade / futu_unlock_trade)
28  admin            v1.4.32+ daemon 生命周期:/api/admin/status /
29                   /api/admin/reload / /api/admin/shutdown
30
31多个 scope 用逗号分隔,例如:
32  --scopes qot:read,acc:read
33  --scopes qot:read,trade:simulate
34  --scopes admin                   # daemon 运维专用 key"
35    )]
36    pub(crate) scopes: String,
37
38    /// keys.json 路径。默认(按 OS):macOS ~/Library/Application Support/futu/keys.json / Linux ~/.config/futu/keys.json / Windows %APPDATA%/futu/keys.json
39    #[arg(long)]
40    pub(crate) keys_file: Option<PathBuf>,
41
42    /// 过期时间: Nd / Nh / Nm 或 RFC3339(如 2026-12-31T23:59:59Z)
43    #[arg(long)]
44    pub(crate) expires: Option<String>,
45
46    /// 备注
47    #[arg(long)]
48    pub(crate) note: Option<String>,
49
50    /// 允许的市场,逗号分隔(如 HK,US)
51    #[arg(long)]
52    pub(crate) allowed_markets: Option<String>,
53
54    /// 允许的品种白名单,逗号分隔(MARKET.CODE 格式,如 HK.00700,HK.09988)
55    #[arg(long)]
56    pub(crate) allowed_symbols: Option<String>,
57
58    /// 单笔订单金额上限
59    #[arg(long)]
60    pub(crate) max_order_value: Option<f64>,
61
62    /// 单日累计金额上限
63    #[arg(long)]
64    pub(crate) max_daily_value: Option<f64>,
65
66    /// 允许交易的时间窗口(服务器本地时区)HH:MM-HH:MM,跨午夜如 22:00-04:00
67    #[arg(long)]
68    pub(crate) hours_window: Option<String>,
69
70    /// 每 60s 最多下单次数(滑动窗口速率限制)
71    ///
72    /// 单笔金额 / 日累计之外再加一层防 spray-and-pray:
73    /// 假设攻击者拿到一把 `max_order_value = 50` 的 key,没速率限制的话
74    /// 一分钟能刷 N 千单;加了这个就被钳制在 `max_orders_per_minute` 内。
75    #[arg(long)]
76    pub(crate) max_orders_per_minute: Option<u32>,
77
78    /// 允许的交易方向白名单,逗号分隔
79    ///
80    /// 典型用法:`--allowed-trd-sides SELL` → 只允许平仓 bot 卖;
81    /// 值:`BUY` / `SELL` / `SELL_SHORT` / `BUY_BACK`(大小写敏感)。
82    /// 改单 / 撤单路径不带 side,不受此限制(避免误伤运维操作)。
83    #[arg(long)]
84    pub(crate) allowed_trd_sides: Option<String>,
85
86    /// v1.4.35: 允许的 acc_id 白名单,逗号分隔(如 `10001,10002`)
87    ///
88    /// **operational safety**:该 key 只能对这些 acc_id 发 trade / unlock / query
89    /// 操作;超出列表的 acc_id 直接 403。None / 空 → 不限(向后兼容)。
90    ///
91    /// 典型用法(多 agent 隔离):
92    ///   `--allowed-acc-ids 10001,10002` → bot-A 只能动 10001/10002
93    ///   `--allowed-acc-ids 10003`       → bot-B 只能动 10003
94    ///
95    /// 注意:这是 **operational** 隔离(防 agent bug / LLM 幻觉 / key 泄露
96    /// 爆炸半径),对**纯现金策略**用户实质等同财务隔离。真·财务隔离(融资 /
97    /// 期权组合 / 跨品种保证金)需要多 union card,见 CLAUDE.md 隔离层级。
98    #[arg(long)]
99    pub(crate) allowed_acc_ids: Option<String>,
100
101    /// v1.4.103 (B10): per-key card_num 白名单, 逗号分隔.
102    ///
103    /// 接受 **4 位 suffix** (App 显示的 "保证金综合账户(`<card-suffix>`)"
104    /// 末 4 位) 或 **16 位完整 card_num** (e.g. `<full-card-num>`).
105    /// 示例为 synthetic placeholder, 不是真实账户信息. daemon 启动后通过
106    /// GetAccList **resolve** → **合并**进 `allowed_acc_ids` (内部仍用 u64).
107    ///
108    /// **设计动机**: App 用户看到的是 card_num, 不是内部 `acc_id`. 直接
109    /// 写 4 位 / 16 位避免先调 /api/accounts 拿映射.
110    ///
111    /// 典型用法 (与 --allowed-acc-ids 等价但用 App 可见的号码):
112    ///   --allowed-card-nums <card-suffix>      → 末 4 位匹配的账户
113    ///   --allowed-card-nums <full-card-num>    → 完整 card_num
114    ///   --allowed-card-nums <suffix-A>,<suffix-B>  → 多个 (e.g. HK + US 各一)
115    ///
116    /// 多 suffix 撞 (短 4 位 match 多账户) → daemon 启动 log warn + skip
117    /// (loud, 不静默接受). 找不到 → log warn + skip (cache load 后可补).
118    #[arg(long)]
119    pub(crate) allowed_card_nums: Option<String>,
120
121    /// 将 key 绑定到本机(软绑定:读 machine-id 计算指纹)
122    ///
123    /// 设置后 key 只能在本机使用;复制到别的机器会被拒。
124    /// 可以和 `--bind-machines` 同时用来同时允许多台机器。
125    #[arg(long)]
126    pub(crate) bind_this_machine: bool,
127
128    /// 追加绑定其他机器(需要对方先跑 `futucli machine-id --for-key <id>` 拿指纹)
129    ///
130    /// 逗号分隔的 64 位 hex 指纹列表,如:
131    ///   --bind-machines aabb...,ccdd...
132    #[arg(long)]
133    pub(crate) bind_machines: Option<String>,
134}
135
136#[derive(Args)]
137pub struct BindKeyArgs {
138    /// 要编辑的 key id
139    #[arg(long)]
140    pub(crate) id: String,
141
142    /// keys.json 路径。默认(按 OS):macOS ~/Library/Application Support/futu/keys.json / Linux ~/.config/futu/keys.json / Windows %APPDATA%/futu/keys.json
143    #[arg(long)]
144    pub(crate) keys_file: Option<PathBuf>,
145
146    /// 追加本机指纹
147    #[arg(long)]
148    pub(crate) this_machine: bool,
149
150    /// 追加指定指纹,逗号分隔的 64 位 hex
151    #[arg(long)]
152    pub(crate) machines: Option<String>,
153
154    /// 替换模式:用新指纹覆盖整个白名单(默认是追加)
155    #[arg(long, conflicts_with_all = &["clear", "freeze"])]
156    pub(crate) replace: bool,
157
158    /// 清除绑定:把 allowed_machines 置为 None(等同于未启用绑定)
159    #[arg(long, conflicts_with_all = &["freeze", "replace", "this_machine", "machines"])]
160    pub(crate) clear: bool,
161
162    /// 冻结:把 allowed_machines 置为 [](任何机器都不过)
163    #[arg(long, conflicts_with_all = &["clear", "replace", "this_machine", "machines"])]
164    pub(crate) freeze: bool,
165}
166
167#[derive(Args)]
168pub struct MachineIdArgs {
169    /// 要生成指纹的 key id(和签发方的 `gen-key --id` 保持一致)
170    #[arg(long)]
171    pub(crate) for_key: Option<String>,
172}
173
174#[derive(Args)]
175pub struct ListKeysArgs {
176    /// keys.json 路径。默认(按 OS):macOS ~/Library/Application Support/futu/keys.json / Linux ~/.config/futu/keys.json / Windows %APPDATA%/futu/keys.json
177    #[arg(long)]
178    pub(crate) keys_file: Option<PathBuf>,
179
180    /// 输出 JSON 数组而不是短表格
181    ///
182    /// JSON 字段含完整 KeyRecord (hash / created_at / allowed_machines /
183    /// allowed_acc_ids / allowed_card_nums / allowed_symbols / hours_window
184    /// 等) 适合脚本 / agent 读取. 空 keys.json 输出 `[]`.
185    #[arg(long)]
186    pub(crate) json: bool,
187}
188
189#[derive(Args)]
190pub struct RevokeKeyArgs {
191    /// 要吊销的 key id
192    #[arg(long)]
193    pub(crate) id: String,
194
195    /// keys.json 路径。默认(按 OS):macOS ~/Library/Application Support/futu/keys.json / Linux ~/.config/futu/keys.json / Windows %APPDATA%/futu/keys.json
196    #[arg(long)]
197    pub(crate) keys_file: Option<PathBuf>,
198
199    /// 确认吊销(未加 --yes 时仅 dry-run)
200    #[arg(long)]
201    pub(crate) yes: bool,
202}
203
204#[derive(Args)]
205pub struct UnlockTradeArgs {
206    /// 锁回交易(清除 gateway 侧 cipher 缓存)
207    #[arg(long)]
208    pub(crate) lock: bool,
209
210    /// 从 stdin 读一行作为密码(脚本管道友好,覆盖环境变量 / tty prompt)
211    #[arg(long)]
212    pub(crate) from_stdin: bool,
213
214    /// 交易密码所属登录账号,用于读取账号级 OS keychain 条目。
215    ///
216    /// 对应 `futucli set-trade-pwd --account <login-account>` 写入的
217    /// `trade-password.<login-account>`。未传时会依次看
218    /// `FUTU_TRADE_PWD_ACCOUNT` / `FUTU_ACCOUNT`;仍找不到时再读 legacy
219    /// 全局 keychain 条目或交互式 prompt。
220    #[arg(long, value_name = "LOGIN_ACCOUNT")]
221    pub(crate) trade_pwd_account: Option<String>,
222
223    /// OTP / 令牌动态密码明文(v1.4.31+,2FA 用户必填)
224    ///
225    /// 若账号开启了"令牌动态密码"二次验证:首次 unlock 会收到 daemon 提示
226    /// `需要令牌动态密码`,再次调用带此参数即可。无 2FA 账号留空。
227    #[arg(long)]
228    pub(crate) otp: Option<String>,
229
230    /// 只解锁该券商下的账户。不传则解锁所有 broker(默认,向后兼容)。
231    /// 对齐 C++ OpenD 的 per-broker unlock 语义。
232    ///
233    /// 接受 3 种形式(v1.4.34 验证数字和别名确实能跑;clap 默认报错
234    /// 消息只列官方名,用 `--help` 看详细用法):官方名、1-7 数字、短别名。
235    #[arg(
236        long,
237        value_enum,
238        value_name = "FIRM",
239        long_help = "\
240只解锁该券商下的账户。不传则解锁所有 broker(默认,向后兼容)。
241
242接受 3 种形式(等价):
243  1. 官方名:FutuHK FutuUS FutuSG FutuAU FutuCA FutuMY FutuJP
244  2. 数字:   1       2      3      4      5      6      7
245            (HK)   (US)    (SG)   (AU)   (CA)   (MY)   (JP)
246  3. 短别名:hk / us / sg / au / ca / my / jp
247            (moomoo 账号 = us;其他别名 futu-hk / futu-us / mm 等)
248
249示例:
250  --security-firm 1
251  --security-firm hk
252  --security-firm FutuHK
253  --security-firm moomoo    # 等价 --security-firm us"
254    )]
255    pub(crate) security_firm: Option<crate::cmd::unlock::SecurityFirmArg>,
256
257    /// v1.4.34+ 只解锁指定 acc_id 列表(逗号分隔)。和 `--security-firm`
258    /// 同时传时取**交集**。解决同 broker 内影子账户拖垮主账户的场景——
259    /// 显式传主账户 acc_id,影子账户不进请求。
260    #[arg(long, value_delimiter = ',', value_name = "ACC_ID[,ACC_ID]...")]
261    pub(crate) acc_ids: Vec<u64>,
262}
263
264#[derive(Args)]
265pub struct SetTradePwdArgs {
266    /// 登录账号(牛牛号 / 手机号 / 邮箱)—— 和 futu-opend --login-account 完全一致
267    #[arg(long)]
268    pub(crate) account: String,
269
270    /// 从 stdin 读一行密码(脚本/CI 友好,不做二次确认)
271    #[arg(long)]
272    pub(crate) from_stdin: bool,
273}
274
275#[derive(Args)]
276pub struct ClearTradePwdArgs {
277    #[arg(long)]
278    pub(crate) account: String,
279}
280
281#[derive(Args)]
282pub struct SetLoginPwdArgs {
283    /// 账号(牛牛号 / 手机号 / 邮箱)—— 和 futu-opend --login-account 完全一致
284    #[arg(long)]
285    pub(crate) account: String,
286
287    /// 从 stdin 读一行密码(脚本/CI 友好,不做二次确认)
288    #[arg(long)]
289    pub(crate) from_stdin: bool,
290}
291
292#[derive(Args)]
293pub struct ClearLoginPwdArgs {
294    #[arg(long)]
295    pub(crate) account: String,
296}