tytcli 0.2.0__py3-none-any.whl

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.
tytcli/skill/SKILL.md ADDED
@@ -0,0 +1,122 @@
1
+ ---
2
+ name: tytcli
3
+ description: 用 tytcli 命令行工具查询投研通(TYT,1234567.com.cn)的数据。当用户提到投研通、TYT、tytapi、jg.1234567.com.cn,或需要确认投研通登录身份、配置 TYT_Token、查询当前账号可访问的菜单与权限,或查询公众号文章列表与文章详情时使用本技能。
4
+ ---
5
+
6
+ # tytcli
7
+
8
+ `tytcli` 是投研通 HTTP API 的命令行客户端,为自动化调用而设计:
9
+
10
+ - 结果统一输出为 **stdout 上恰好一个 JSON 文档**;命令不往 stderr 写任何东西,
11
+ 所以不存在“同一信息在两个通道里各一份”的歧义
12
+ - **退出码稳定**,失败时带机器可读的 `error.code`
13
+ - **永不交互式提问**,缺参数直接报错,适合脚本和 agent 串联
14
+
15
+ 本文件只放**每次调用都需要的东西**。细节在 `references/` 下按模块拆分,
16
+ 用到哪块读哪块,不要一次性全读进来。
17
+
18
+ ## 何时使用
19
+
20
+ 需要以某个投研通账号的身份查询数据、确认登录态、查看该账号被授权了哪些功能模块,
21
+ 或查询公众号文章时。
22
+
23
+ 所有调用都基于一个 `TYT_Token`(登录凭证),因此**第一步永远是确认 Token 已配置**:
24
+
25
+ ```bash
26
+ tytcli config show # 当前生效配置:地址、Token 状态与来源(脱敏)
27
+ tytcli config path # 配置文件在哪、是否存在
28
+ ```
29
+
30
+ ## 先记住这四条
31
+
32
+ 1. **`--pretty` 是全局参数,必须放在子命令前面。**
33
+ `tytcli --pretty whoami` ✅ / `tytcli whoami --pretty` ❌(`No such option`)
34
+ 2. **命令可能在中途自动升级并 re-exec——这是设计,不是故障。**
35
+ 隐式检查每 10 分钟最多联网一次,发现新版先升级再重跑原命令,并往 stderr
36
+ 写一行警告(stderr 唯一合法的写入者)。**没有关闭开关**:产品契约就是始终
37
+ 运行最新版。看到警告不等于命令失败;stdout 仍是一个 JSON 文档,退出码仍由
38
+ 业务结果决定。若升级真的发生,重读 SKILL.md 与相关 reference 再继续,因为
39
+ 命令行为可能已变。需要主动确认版本时用 `tytcli upgrade --check`。
40
+ 详见 `references/selfupdate.md`。
41
+ 3. **按 `error.code` 分支,绝不要解析 `message` 文案。**文案会变,code 不会。
42
+ 完整对照表见 `references/output-contract.md`。
43
+ 4. **不要打印、记录或提交完整 Token。**核对身份用 `auth --verify` 输出的 `token_hint` 掩码;
44
+ 人工在终端操作时优先用 `--token -` 走 stdin,避免进 shell 历史。
45
+
46
+ ## 调用方式
47
+
48
+ 正常情况下 `tytcli` 已在 PATH 中:
49
+
50
+ ```bash
51
+ tytcli --version
52
+ tytcli --help
53
+ ```
54
+
55
+ 若提示找不到命令,按顺序尝试:
56
+
57
+ ```bash
58
+ uv tool run tytcli --help # 通过 uv 临时运行
59
+ python -m tytcli.cli --help # 已 pip install 到当前环境
60
+ ```
61
+
62
+ 后文一律写作 `tytcli`,请按你的实际可用形式替换。
63
+
64
+ ### 文档就在 CLI 里
65
+
66
+ 本 skill 的文档随包发布,CLI 自己就能打印,**不依赖你手头有没有这份 SKILL.md**:
67
+
68
+ ```bash
69
+ tytcli help # 命令摘要 + 路由文档(等同本文件正文)
70
+ tytcli help wechat # 某命令的用法 + 对应子模块文档
71
+ tytcli help output # 输出契约 / 退出码 / 错误码
72
+ ```
73
+
74
+ 每个 `--help` 末尾都有一行 `完整文档:tytcli help <topic>` 指针。
75
+
76
+ ## 命令一览
77
+
78
+ | 命令 | 作用 |
79
+ |---|---|
80
+ | `tytcli whoami` | 查询当前 Token 的登录身份与权限菜单 |
81
+ | `tytcli auth --token TOKEN\|-` `[--verify]` | 写入 Token(`-` 从 stdin 读) |
82
+ | `tytcli config show` | 当前生效配置(Token 脱敏) |
83
+ | `tytcli config path` / `config init [--force]` | 配置文件 |
84
+ | `tytcli upgrade [--check] [--force]` | 检查/升级到新版本 |
85
+ | `tytcli wechat articles [选项]` | 公众号文章列表 |
86
+ | `tytcli wechat article --article-id HEX64` | 单篇公众号文章详情 |
87
+ | `tytcli sentiment dates` | 列出有日报的日期 |
88
+ | `tytcli sentiment image --date YYYY-MM-DD` `[--output PATH]` | 下载某天的日报图片 |
89
+ | `tytcli roadshow search [选项]` | 按时间/行业/机构/主讲人查路演 |
90
+ | `tytcli roadshow status --app-id ID` | 某一场我能否预约/取消 |
91
+ | `tytcli roadshow summary --app-id ID` `[--full]` | 公开 AI 纪要(简略/详细) |
92
+ | `tytcli roadshow book --app-id ID` `[--yes]` | 预约(**写**,不带 `--yes` 只预检) |
93
+ | `tytcli roadshow cancel --app-id ID` `[--yes]` | 取消预约(**写**,同上) |
94
+ | `tytcli roadshow bookings [选项]` | 我的预约 |
95
+ | `tytcli help [TOPIC]` | 打印命令用法 + 随包发布的文档 |
96
+
97
+ ## 参考文档(按需读取)
98
+
99
+ 路径相对于本 skill 目录。
100
+
101
+ | 文件 | 什么时候读 |
102
+ |---|---|
103
+ | `references/setup.md` | 遇到 `AUTH_EXPIRED` / `CONFIG_MISSING_TOKEN`;要写入或更换 Token;需要弄清配置文件位置与覆盖优先级 |
104
+ | `references/output-contract.md` | 要解析 stdout、按退出码或 `error.code` 分支;需要输出字段含义(`identity` vs `data`)与 PII 注意事项 |
105
+ | `references/whoami.md` | 查登录身份、权限菜单树;需要现成的 jq 组合 |
106
+ | `references/wechat.md` | 查公众号文章列表或文章详情;遇到 `--fields` / `--full` / 404 的问题 |
107
+ | `references/selfupdate.md` | 自动更新行为(没有关闭开关)、`upgrade` 的各种退出码 |
108
+ | `references/sentiment.md` | 查情绪日报日期、下载日报图片;遇到 `-101` 或超时 |
109
+ | `references/roadshow.md` | 查/预约/取消路演、看 AI 纪要;写操作的确认与幂等语义;日期筛选边界 |
110
+
111
+ ## 注意事项
112
+
113
+ - **未经用户明确授权,不要执行任何写入类操作。**`roadshow book` / `roadshow cancel`
114
+ 是**会改变服务端状态**的命令,必须先向用户确认,再加 `--yes` 执行;不带 `--yes`
115
+ 时它们只做只读预检。其余命令都是只读的(`auth` / `config init` 只写本地配置文件,
116
+ `upgrade` 会替换本地安装)。
117
+ - 向用户索要 Token,不要猜测或编造。
118
+ - CLI 不会自动刷新或续期 Token。凭证失效时如实报告,**不要反复重试**——
119
+ 重试不可能让过期凭证变有效。
120
+ - `whoami` 的响应包含真实姓名、手机号、工号、公司与部门。
121
+ **不要把完整 `data` 原样贴进报告、日志或长期保存的文件**;用 `identity` 摘要,
122
+ 需要展示给用户时先脱敏。详见 `references/output-contract.md`。
@@ -0,0 +1,93 @@
1
+ <!-- tytcli skill reference. Read this file only when the task needs it; -->
2
+ <!-- SKILL.md is the routing surface and stays small on purpose. -->
3
+
4
+ # tytcli:输出契约、退出码与错误处理
5
+
6
+ 解析 stdout、按 `error.code` 分支时读这一份。
7
+
8
+ ---
9
+
10
+ ## 输出格式
11
+
12
+ 成功:
13
+
14
+ ```json
15
+ {"ok":true,"env":"prod","base_url":"https://tytapi.1234567.com.cn","http_status":200,"identity":{"UserId":"…","UserName":"…","CompanyName":"…","MenuCount":6},"data":{"Success":true,"ErrorCode":0,"Error":"","Data":{}}}
16
+ ```
17
+
18
+ 失败:
19
+
20
+ ```json
21
+ {"ok":false,"error":{"code":"AUTH_EXPIRED","message":"登录超时,请重新登录!","details":{"env":"prod","error_code":-100,"hint":"…"}}}
22
+ ```
23
+
24
+ 两个数据字段的分工要分清:
25
+
26
+ - **`identity`**:关键身份字段的摘要,体积小。**日常判断"我是谁、登录了没"就读它。**
27
+ - **`data`**:服务端响应的原样透传(字段名为 PascalCase),未做任何改名或裁剪。`whoami` 的 `data.Data.Menus` 是完整权限菜单树,体积可达数 KB。
28
+
29
+ `identity` **刻意不包含 `Phone`**(手机号)——因为摘要是最容易被写进日志和对话记录的部分。确实需要手机号时从 `data.Data.Phone` 取。
30
+
31
+ ### ⚠️ 输出含个人信息
32
+
33
+ `whoami` 的响应包含真实姓名、手机号、工号、公司与部门。**不要把完整 `data` 原样贴进报告、日志或长期保存的文件**;用 `identity`,需要展示给用户时先脱敏。
34
+
35
+ ## 退出码与错误码
36
+
37
+ **按 `error.code` 分支处理,不要去解析 `message` 文案**(文案可能变,code 不会)。
38
+
39
+ | 退出码 | 含义 | 常见 code |
40
+ |---|---|---|
41
+ | 0 | 成功 | — |
42
+ | 1 | API / 业务错误 | `API_BUSINESS_ERROR`、`API_BAD_REQUEST`、`HTTP_ERROR`、`NON_JSON_RESPONSE`、`UNEXPECTED_SHAPE`、`CLIENT_MIDDLE_ROOT_MISSING`、`INDEX_HTTP_ERROR`、`UPDATE_NOT_PUBLISHED`、`UPDATE_FAILED`、`UPDATE_TIMEOUT`、`UPDATE_UNSUPPORTED`、`INTERNAL_ERROR` |
43
+ | 2 | 用法或配置错误 | `USAGE`、`USAGE_STDIN_REQUIRED`、`USAGE_STDIN_EMPTY`、`USAGE_PAGE_OUT_OF_RANGE`、`USAGE_PAGE_SIZE_OUT_OF_RANGE`、`USAGE_FIELDS_CONFLICT`、`USAGE_FIELDS_EMPTY`、`USAGE_ARTICLE_ID_EMPTY`、`USAGE_ARTICLE_ID_TOO_LONG`、`CONFIG_MISSING_TOKEN`、`CONFIG_EMPTY_TOKEN`、`CONFIG_NOT_FOUND`、`CONFIG_PARSE_ERROR`、`CONFIG_UNKNOWN_ENV`、`CONFIG_BASE_URL_NOT_CONFIGURABLE`、`CONFIG_EXISTS`、`CONFIG_WRITE_VERIFY_FAILED`、`AUTH_EMPTY_TOKEN`、`AUTH_MALFORMED_TOKEN` |
44
+ | 3 | 认证失败 | `AUTH_EXPIRED` |
45
+ | 4 | 网络错误或超时 | `NETWORK_TIMEOUT`、`NETWORK_ERROR` |
46
+
47
+ ## 常见错误与处理
48
+
49
+ **`AUTH_EXPIRED`(退出码 3)**
50
+ Token 无效或已过期。报错的 `error.details` 带 `env`、`base_url` 与 `hint`;注意 `TYTCLI_TOKEN` 环境变量会遮蔽配置文件里的值。向用户索要新 Token,用 `auth --verify` 写入并验证——它的输出含 `token_hint` 掩码,可核对是不是你以为的那个。
51
+ **不要循环重试**——重试不可能让过期凭证变有效。
52
+
53
+ **`CONFIG_MISSING_TOKEN`(2)**
54
+ 当前环境没有 Token。先看 `details.envs_with_token`:非空说明 Token 存在但环境不对,按 `details.hint` 处理;为空则用 `auth --token <TOKEN>` 写入。
55
+
56
+ **`CONFIG_BASE_URL_NOT_CONFIGURABLE`(2)**
57
+ 配置文件里写的 API 地址与实际不一致。删掉该 `base_url` 键即可——地址固定在 CLI 内部,不需要也不允许配置。报错信息会列出冲突的段落和正确值。
58
+ (旧版本模板生成的配置里也带 `base_url`,但只要值与内置地址相同就能正常加载,不必改动。)
59
+
60
+ **`CONFIG_PARSE_ERROR`(2)**
61
+ 配置文件不是合法 TOML,多为手工编辑出错。`tytcli config init --force` 可重新生成模板,但会丢失已存的 Token,操作前先向用户确认 Token 再重新 `auth`。
62
+
63
+ **`CONFIG_WRITE_VERIFY_FAILED`(2)**
64
+ `auth` 写入后回读校验不一致,通常是文件权限或磁盘问题。手工查看该文件,不要盲目重试。
65
+
66
+ **`AUTH_MALFORMED_TOKEN`(2)**
67
+ Token 里含空白字符,几乎都是复制粘贴带进了换行或空格。重新复制。
68
+
69
+ **`USAGE_STDIN_REQUIRED`(2)**
70
+ 用了 `--token -` 但没有管道输入。改成 `printf '%s' "$TOKEN" | tytcli auth --token -`。
71
+
72
+ **`API_BAD_REQUEST`(1)**
73
+ 服务端表示路由不存在或请求不合规。因为地址不可配置,**这一定是接口路径的问题**,不要怀疑环境或地址。
74
+
75
+ **`NON_JSON_RESPONSE`(1)**
76
+ 返回的是网关拦截页或 HTML 错误页,不是 API 响应。看 `details.body_preview` 判断是网络中间层还是服务异常。
77
+
78
+ **`NETWORK_TIMEOUT` / `NETWORK_ERROR`(4)**
79
+ 瞬时问题,重试一次是合理的;仍失败就如实上报。
80
+ 若来自 `auth --verify`,注意 `details.written` 为 `true`:**Token 已经存好了**,只是校验请求没通,不需要重写。
81
+
82
+ **`USAGE`(2)**
83
+ 命令或参数写错。`details.available_commands` 列出可用命令,`details.hint` 给出正确用法。注意 `--pretty` 要放在子命令前。
84
+
85
+ **`UPDATE_UNSUPPORTED`(1)**
86
+ 只由显式的 `tytcli upgrade` 产生:当前安装形式不允许自动升级(通常是装在系统 site-packages)。`details.hint` 里有手动升级命令。隐式检查遇到这种情况只会在 stderr 警告,不会报错。
87
+
88
+ **`UPDATE_NOT_PUBLISHED`(1)**
89
+ `tytcli upgrade` 能连上索引,但索引上没有该包的任何版本,因此**无法回答“是否需要升级”**。
90
+ 这不是成功——“无法判断”和“索引不可达”语义相同,所以两者都报错,而不是一个 exit 0、一个 exit 4。
91
+ **源码安装(`uv sync` / `pip install -e .`)本来就不在索引上,看到这个 code 属于正常情况**,
92
+ 说明你跑的是开发副本而不是已发布版本。`details` 里带有 `current_version`、`index_url`、
93
+ `install_kind` 和 `upgrade_argv`。
@@ -0,0 +1,176 @@
1
+ <!-- tytcli skill reference. Read this file only when the task needs it; -->
2
+ <!-- SKILL.md is the routing surface and stays small on purpose. -->
3
+
4
+ # tytcli:路演中心
5
+
6
+ 路演的多条件查询、预约与取消、我的预约、公开 AI 纪要。
7
+
8
+ ---
9
+
10
+ ## 命令与串联关系
11
+
12
+ ```bash
13
+ tytcli roadshow search # 查场次 → 拿 appId
14
+ tytcli roadshow status --app-id <ID> # 这一场我能不能预约/取消
15
+ tytcli roadshow summary --app-id <ID> # 公开 AI 纪要
16
+ tytcli roadshow book --app-id <ID> --yes # 预约(写)
17
+ tytcli roadshow cancel --app-id <ID> --yes # 取消(写)
18
+ tytcli roadshow bookings # 我的预约
19
+ ```
20
+
21
+ `appId` 是加密会议 ID,来自 `search` 或 `bookings` 的输出,**原样传递**(其中含
22
+ `+` `/` `=`,不要转义、不要截断、不要 URL 编码)。取消预约用的也是 `appId`,
23
+ 不是 `bookings` 里的 `bookingId`。
24
+
25
+ ## search:多条件查询
26
+
27
+ | 参数 | 说明 |
28
+ |---|---|
29
+ | `--title` | 标题模糊查询 |
30
+ | `--institution` | 机构模糊查询 |
31
+ | `--speaker` | 主讲人模糊查询;一场可能有多位主讲人,任一匹配即命中 |
32
+ | `--theme` | 行业,接受编码或行业名:`1`/`权益`、`2`/`固收+`、`3`/`纯债`、`4`/`互认`、`5`/`策略`、`6`/`热点`。不传为全部 |
33
+ | `--start` / `--end` | 会议开始日期范围,`YYYY-MM-DD` |
34
+ | `--page` / `--page-size` | 分页,默认 1 / 20 |
35
+ | `--fields` / `--full` | 字段自选 / 服务端原样全量(互斥) |
36
+
37
+ 默认每场只输出 `appId / title / roadShowTime / publishState / institutions /
38
+ speakers / theme`,其余字段在 `__tytcli_hint__` 里列出。
39
+
40
+ **默认刻意排除三类字段**,需要时用 `--fields` 或 `--full` 取:
41
+
42
+ * `summary`——列表自带的简略纪要,是长文本,完整纪要请用 `summary` 命令
43
+ * `meetingUrl` / `meetingPwd`——会议链接与**密码**,进默认输出就会被记进日志和对话
44
+ * `pictureUrl`——加密封面地址,命令行里无法渲染
45
+
46
+ ### ⚠ `--end` 不含当天
47
+
48
+ 服务端把 `--end` 当作**该日 00:00:00 截止**,所以当天白天的会议不在结果里。
49
+ 要包含 11 月 13 日全天,得写 `--end 2026-11-14`。输出里的 `filters` 会如实回显:
50
+
51
+ ```json
52
+ {"filters":{"start":"2026-11-01","start_sent_as":"2026/11/1",
53
+ "end":"2026-11-13","end_sent_as":"2026/11/13",
54
+ "end_includes_that_day":false}}
55
+ ```
56
+
57
+ `*_sent_as` 是实际发给服务端的格式。**为什么要回显**:该接口对无法解析的日期是
58
+ **静默忽略**而不是报错——筛选条件没生效时你会拿回全量结果(实测 467 条),
59
+ 看起来像"这个区间有 467 场路演"。回显让你能确认筛选真的被应用了。
60
+
61
+ `bookings` 的 `--meeting-end` 语义**相反**(含当天全天),两个命令不要互相套用。
62
+
63
+ ### 列表不含预约状态
64
+
65
+ 服务端**没有批量查询预约资格的接口**,列表也不返回 `isBooked` / `canBook`。
66
+ 要知道某一场能不能预约,只能拿 `appId` 逐个调 `status`。
67
+
68
+ ## status:预约资格
69
+
70
+ 输出 `isBooked` / `canBook` / `canCancel` / `reason`,已预约时还有 `bookTime`。
71
+
72
+ * 会议存在但当前不可操作 → **`ok:true`**,原因在 `reason` 里
73
+ * 会议不存在、已下架或 `appId` 格式不对 → 失败(exit 1)
74
+
75
+ **按 `canBook` / `canCancel` 判断,绝不要解析 `reason` 的文字。**同一种拒绝在预约
76
+ 与取消两条路径上的标点都不一样(预约用 `[标题]`,取消用 `「标题」`),文字匹配必然
77
+ 在某一条路径上失效。
78
+
79
+ ## book / cancel:写操作,默认只预检
80
+
81
+ **不带 `--yes` 时不写入**,只查资格并报告将会发生什么:
82
+
83
+ ```json
84
+ {"ok":true,"written":false,"dry_run":true,
85
+ "data":{"would_write":true,"preflight":{...},"next_step":"确认无误后加 --yes 执行预约"}}
86
+ ```
87
+
88
+ 带 `--yes` 时的四种结局:
89
+
90
+ | 情况 | 输出 | exit |
91
+ |---|---|---|
92
+ | 资格允许 | `written:true` + `data.result` + `data.confirmed`(写后回读的状态) | 0 |
93
+ | 已经是目标状态 | `already_booked:true`(或 `already_cancelled:true`),**不发写请求** | 0 |
94
+ | 资格不允许 | `error.code` = `ROADSHOW_NOT_BOOKABLE` / `ROADSHOW_NOT_CANCELLABLE`,`details.reason` 是服务端原文,**不发写请求** | 1 |
95
+ | 写入超时 | 回读状态后决定:已生效 → `recovered_after_timeout:true` + exit 0;未生效 → 超时错误,`details.reconciled_status` 带对账结果,`details.hint` 说明可安全重试 | 0 / 4 |
96
+
97
+ 超时那条是重点:**超时不等于失败**。请求可能已经落地,直接报错会让调用方重试,
98
+ 而重试就是重复预约。所以命令自己去回读真实状态再下结论。
99
+
100
+ 服务端本身也是幂等的(实测:重复预约返回成功且 `bookTime` 不变;重复取消返回成功),
101
+ 但 CLI 仍然先预检——能不发写请求就不发。
102
+
103
+ 预约的时间规则(服务端固定,无法配置):只有公开可预约的**预告**场次能预约;
104
+ 开始时间不明、直播中、已结束、回放、已下架都不行;距离开始 **15 分钟以内**也不能
105
+ 预约或取消。取消同样要求大于 15 分钟。
106
+
107
+ ## bookings:我的预约
108
+
109
+ 不含已取消的预约,默认按预约时间倒序。
110
+
111
+ | 参数 | 说明 |
112
+ |---|---|
113
+ | `--state` | `BOOKED`(已预约,含预告与直播中)/ `ENDED`(已结束);不传为全部 |
114
+ | `--meeting-start` / `--meeting-end` | 会议开始日期范围,`YYYY-MM-DD`,**含**结束日全天 |
115
+ | `--book-start` / `--book-end` | 预约时间范围,`YYYY-MM-DD HH:MM:SS`,含上限 |
116
+ | `--page` / `--page-size` / `--fields` / `--full` | 同 search |
117
+
118
+ `BOOKED` 只代表"预告或直播中",**不代表此时能取消**;取消资格要按场次查 `status`。
119
+
120
+ 已下架会议的预约可能仍留在结果里,此时 `isAvailable:false`、`unavailableReason`
121
+ 说明原因,标题与时间等字段可能整体省略,机构和主讲人是空数组。
122
+
123
+ 空结果时服务端返回的 `pageNum` 是 **0**,把它当作下一页页码回传会被拒
124
+ (`页码和每页条数必须为正整数`)。CLI 已经把它纠正为你请求的页码,并标注
125
+ `empty_page_num_corrected:true`。
126
+
127
+ ## summary:公开 AI 纪要
128
+
129
+ 无需预约即可查询。默认输出**简略版** `id / meetingId / type / summary`;
130
+ `--full` 输出**详细版**全部字段,含 `detail` 正文与十个专题段落:
131
+ `companyOverview`(公司/基金经理介绍)、`productStrategy`(产品定位与投资策略)、
132
+ `fundPerformance`(基金配置与表现)、`marketOutlook`(市场回顾与展望)、
133
+ `riskManagement`(回撤控制与风险管理)、`teamDivision`(团队构成与分工)、
134
+ `investHighlights`(投资亮点)、`mainRisks`(主要风险)、`qaPoints`(问答要点)、
135
+ `timeline`(时间线)。
136
+
137
+ 简略与详细**是同一次响应的字段裁剪**,不是两个接口,所以取详细版不会多发一次请求。
138
+
139
+ ### `available:false` 的含义
140
+
141
+ ```json
142
+ {"ok":true,"available":false,"data":null,
143
+ "unavailable_note":"没有可读的公开 AI 纪要。该接口只返回审核通过的非草稿记录,因此“尚未生成”“待审核”“草稿”“已删除”在响应中无法区分。"}
144
+ ```
145
+
146
+ 这**不是失败**(`ok:true`、exit 0),但也**不能**理解成"纪要内容为空"。该接口只返回
147
+ 审核通过的非草稿记录,所以四种情况在响应里长得一模一样,服务端没有提供进度状态。
148
+ 如实转述为"目前没有可读的公开纪要"即可,不要猜原因。
149
+
150
+ 另外:纪要里的 `meetingId` 是原始会议 ID,**不能**代替加密 `appId` 去调其他接口;
151
+ `time` 是纪要提取时间,**不是**路演开始时间(开始时间用 `roadShowTime`)。
152
+
153
+ ## 错误码
154
+
155
+ | code | exit | 含义 |
156
+ |---|---|---|
157
+ | `ROADSHOW_NOT_BOOKABLE` | 1 | 当前不可预约,`details.reason` 是服务端原文 |
158
+ | `ROADSHOW_NOT_CANCELLABLE` | 1 | 当前不可取消,同上 |
159
+ | `ROADSHOW_WRITE_REJECTED` | 1 | 写入被服务端拒绝(如"路演已经结束"),`details.reason` 是原文 |
160
+ | `USAGE_THEME_INVALID` | 2 | `--theme` 不是 1–6 或对应行业名 |
161
+ | `USAGE_STATE_INVALID` | 2 | `--state` 不是 BOOKED / ENDED |
162
+ | `USAGE_DATE_INVALID` | 2 | 日期不是 `YYYY-MM-DD` 或不是真实日期 |
163
+ | `USAGE_DATETIME_INVALID` | 2 | 时间不是 `YYYY-MM-DD HH:MM:SS` |
164
+ | `USAGE_RANGE_REVERSED` | 2 | 起始晚于结束 |
165
+ | `USAGE_APP_ID_EMPTY` | 2 | `--app-id` 去掉空白后为空 |
166
+ | `USAGE_PAGE_OUT_OF_RANGE` / `USAGE_PAGE_SIZE_OUT_OF_RANGE` | 2 | 页码或每页条数不是正整数 |
167
+ | `USAGE_FIELDS_CONFLICT` / `USAGE_FIELDS_EMPTY` | 2 | `--full` 与 `--fields` 同用 / `--fields` 为空 |
168
+
169
+ 上面这些 `USAGE_*` 是 CLI 在发请求**之前**拦下来的,所以更可分支:直接读
170
+ `error.code`,不用去匹配中文文案。
171
+
172
+ 关于 `ROADSHOW_WRITE_REJECTED`:服务端的 `ErrorCode -1` 是**重载**的,既表示"地址或
173
+ 访问方式错误",也表示"这场路演已经结束"这类业务拒绝,只有中文报文能区分。共享的信封
174
+ 映射无法知道刚调的是哪个接口,但命令层知道,所以写拒绝单独命名,并在
175
+ `details.mapped_from`(`API_BAD_REQUEST`)与 `details.server_error_code`(`-1`)里
176
+ 保留可追溯信息。读接口的 `-1` 仍然是 `API_BAD_REQUEST`。
@@ -0,0 +1,62 @@
1
+ <!-- tytcli skill reference. Read this file only when the task needs it; -->
2
+ <!-- SKILL.md is the routing surface and stays small on purpose. -->
3
+
4
+ # tytcli:自动更新
5
+
6
+ 每条命令启动时的隐式版本检查:什么时候该关掉、失败开放意味着什么。
7
+
8
+ ---
9
+
10
+ ## 自动更新:没有关闭开关
11
+
12
+ **每条命令启动时会自动做一次版本检查**:发现新版先升级、再 re-exec 重新执行原命令。
13
+
14
+ - 检查间隔**固定 10 分钟**(缓存 `$XDG_CACHE_HOME/tytcli/update-check.json`),
15
+ 不可配置;失败也会被缓存,所以离线时不会每条命令都卡在超时上
16
+ - 升级会在命令中途替换进程,并往 stderr 写一行警告——**这是 stderr 唯一合法的
17
+ 写入者,看到警告不等于命令失败**
18
+ - 首次检查多一次网络往返(实测约 1.3s,缓存命中后约 0.08s)
19
+ - 升级后命令行为可能变,同一段脚本前后两次跑得不一样
20
+
21
+ 曾经存在 `TYTCLI_NO_UPDATE_CHECK` 和 `TYTCLI_UPDATE_CHECK_INTERVAL` 两个开关,
22
+ **现已删除**:产品契约是"始终运行最新版",一个可调的间隔只会让安装悄悄过期而
23
+ 看起来健康。自动化流程要做的不是关掉它,而是**预期它**:
24
+
25
+ - 把 stderr 的那行警告当信息而非错误
26
+ - 以 stdout 的 JSON 和退出码为准
27
+ - 若升级真的发生(stderr 出现"已升级到 X,重新执行原命令"),重读 SKILL.md 与
28
+ 相关 reference 再继续
29
+
30
+ ## 主动确认版本
31
+
32
+ `tytcli upgrade --check` 绕过缓存直接联网,输出 `current_version` /
33
+ `latest_version` / `update_available` / `install_kind` / `upgrade_argv`。
34
+ 任务开始时跑一次可以提前知道是否会升级;`update_available: true` 时自行决定
35
+ 立刻 `tytcli upgrade` 还是稍后处理。
36
+
37
+ 索引不可达时 `upgrade --check` 会**报错**(exit 4,`NETWORK_ERROR`)而不是假装
38
+ "没有更新";索引上根本没有该包时是 `UPDATE_NOT_PUBLISHED`(exit 1)。两者都表示
39
+ "无法判断是否需要升级",不要当成"已是最新"。
40
+
41
+ ```bash
42
+ tytcli upgrade --check # 只看有没有新版,不动环境
43
+ tytcli upgrade # 有新版就升
44
+ ```
45
+
46
+ `upgrade --check` 的输出里,`current_version` / `latest_version` / `update_available` 告诉你版本情况,`install_kind` 和 `upgrade_argv` 告诉你它打算怎么升(`uv-tool` / `venv-pip` / `venv-uv` / `manual`)。
47
+
48
+ 其余行为,遇到时不必惊慌:
49
+
50
+ - **失败开放**:索引不通、超时、权限不足时只在 stderr 警告,命令照常执行,退出码仍由业务结果决定。**看到警告不等于命令失败。**
51
+ - **默认 10 分钟最多查一次**,缓存在 `$XDG_CACHE_HOME/tytcli/update-check.json`。失败也会被缓存,所以离线时不会每条命令都卡在超时上。
52
+ - **只接受纯数字版本**,`1.0rc1` 之类预发布版绝不会被自动安装。
53
+ - **系统/共享 site-packages 拒绝自动升级**(显式 `upgrade` 会报 `UPDATE_UNSUPPORTED` 并给出手动命令),因为那需要提权或会污染环境。
54
+
55
+ 相关环境变量:`TYTCLI_UPDATE_CHECK_TIMEOUT`(单次检查超时,默认 5s)、
56
+ `TYTCLI_UPDATE_INDEX_URL`(指定索引,优先级最高)。**没有关闭检查的开关。**
57
+
58
+ 查的是哪个索引:`TYTCLI_UPDATE_INDEX_URL` > `PIP_INDEX_URL` > `pypi.org/simple`。
59
+ 继承 `PIP_INDEX_URL` 是为了不让你在本地镜像已经很快的情况下还每 10 分钟等一次
60
+ 公网往返;`upgrade --check` 的输出里有 `index_source` 说明这次是谁决定的。若那个
61
+ 镜像上根本没有 tytcli,检查会安静地变成 no-op(不报错、也不会升级)——要指定一个
62
+ 只用于更新检查的索引,就用 `TYTCLI_UPDATE_INDEX_URL`。
@@ -0,0 +1,68 @@
1
+ <!-- tytcli skill reference. Read this file only when the task needs it; -->
2
+ <!-- SKILL.md is the routing surface and stays small on purpose. -->
3
+
4
+ # tytcli:机构交易情绪日报
5
+
6
+ `sentimentIndex` 模块的两个只读命令:查有哪些天的日报、下载某一天的日报图片。
7
+
8
+ > ⚠️ **该模块并非在所有环境都已部署。**在未部署的环境上调用会得到 HTTP 404,
9
+ > 错误的 `details.hint` 会说明“模块可能未部署”(`exit 1`)。那**不是**路由拼错,
10
+ > 重试也不会成功——报错的 `error.details` 带 `env` 与 `base_url`,据此确认当前生效的环境。
11
+
12
+ ## 查日期
13
+
14
+ ```bash
15
+ tytcli sentiment dates
16
+ ```
17
+
18
+ 输出 `data` 是日期字符串列表(`yyyy-MM-dd`,新→旧),`count` 是条数。例如:
19
+
20
+ ```json
21
+ {"ok":true,"data":["2026-11-06","2026-10-29","2026-10-28"],"count":10}
22
+ ```
23
+
24
+ ## 下载图片
25
+
26
+ ```bash
27
+ tytcli sentiment image --date 2026-11-06 # 存到当前目录
28
+ tytcli sentiment image --date 2026-11-06 --output /tmp/r.png
29
+ ```
30
+
31
+ **响应是图片本身(约 4.3 MiB 的 PNG),不是 JSON。**所以图片写入文件,stdout 只给元数据:
32
+
33
+ ```json
34
+ {"ok":true,"data":{"report_date":"2026-11-06",
35
+ "path":"/cwd/tytcli-sentiment-2026-11-06.png","bytes":4419306,"format":"png",
36
+ "sha256":"…","content_type":"image/png","overwritten":false}}
37
+ ```
38
+
39
+ - 默认文件名 `tytcli-sentiment-<日期>.<格式>`,扩展名按**实际魔数**判定
40
+ (png/jpg/gif/webp),不是盲目 `.png`
41
+ - 重复下载同一日期会覆盖,`overwritten` 变 `true`
42
+ - `--output` 的父目录不存在会自动创建
43
+ - 首次下载约需 10 秒(服务端要现渲染这张图)
44
+
45
+ ## 三个容易踩的坑
46
+
47
+ 1. **`--date` 必须是真实存在的 `yyyy-MM-dd`。**命令在客户端就校验格式和日期合法性
48
+ (`2026-02-30` 会报 `USAGE_DATE_INVALID`,exit 2),因为服务端对坏日期的回答是
49
+ 误导性的 `-101 服务器繁忙`。
50
+ 2. **`-101 服务器繁忙` 有两种含义。**① 该日期没有日报——用 `sentiment dates` 核对;
51
+ ② 有效日期但图片还没渲染完——命令已内置 2 次重试,仍失败时
52
+ `details.retries_exhausted` 会告诉你重试已用尽。
53
+ 3. **不要把 `data.path` 当 JSON 内容解析。**它是磁盘上的图片文件;要“看”图就用你的
54
+ 图像能力读该路径,不要试图把 4 MiB 二进制塞进上下文。
55
+
56
+ ## 典型管道
57
+
58
+ 先看有哪些天,再取最新一天的图:
59
+
60
+ ```bash
61
+ tytcli sentiment dates | jq -r '.data[0]' | xargs -I{} tytcli sentiment image --date {}
62
+ ```
63
+
64
+ 只要文件路径:
65
+
66
+ ```bash
67
+ tytcli sentiment image --date 2026-11-06 | jq -r '.data.path'
68
+ ```
@@ -0,0 +1,105 @@
1
+ <!-- tytcli skill reference. Read this file only when the task needs it; -->
2
+ <!-- SKILL.md is the routing surface and stays small on purpose. -->
3
+
4
+ # tytcli:配置与 Token
5
+
6
+ 认证失败的排查顺序、Token 写入、配置文件位置与覆盖优先级。
7
+
8
+ ---
9
+
10
+ ## 第一步:确认当前配置状态
11
+
12
+ 遇到任何认证类失败,按这个顺序查:
13
+
14
+ ```bash
15
+ tytcli config show # 当前生效配置:地址、Token 状态与来源(脱敏)
16
+ tytcli auth --token <TOKEN> --verify # 写入并立即联网校验,成功时返回 identity
17
+ ```
18
+
19
+ `config show` 一次回答"Token 配好了没、从哪来的、请求会打到哪":
20
+
21
+ ```json
22
+ {"ok":true,"data":{"active_env":"prod","env_source":"config","base_url":"https://tytapi.1234567.com.cn","base_url_source":"code","token_configured":true,"token_source":"config","token_hint":"dead…babe (len=32)","timeout_seconds":15.0,"config_path":"/Users/xxx/.config/tytcli/config.toml","config_path_source":"default","config_exists":true,"known_envs":["prod"]}}
23
+ ```
24
+
25
+ 关键字段:
26
+
27
+ | 字段 | 含义 |
28
+ |---|---|
29
+ | `base_url` | 实际请求的 API 地址(固定在 CLI 内部,不可覆盖) |
30
+ | `token_configured` | 是否找到了 Token |
31
+ | `token_source` | `config`(配置文件)/ `TYTCLI_TOKEN`(环境变量)/ `null` |
32
+ | `token_hint` | 掩码后的 Token(首 4 位…末 4 位),核对是不是你想要的那个 |
33
+ | `config_path` / `config_exists` | 配置文件在哪、是否存在 |
34
+
35
+ `token_hint` 永远只显示掩码,完整 Token 不会出现在任何输出里。
36
+
37
+ 补充两处:`auth` 的输出带 `token_shadowed`(布尔值,环境变量是否遮蔽了刚写入的值,
38
+ **自动化直接判它**);失败时的 `error.details` 带 `env` / `envs_with_token` / `hint`。
39
+
40
+ ## 配置 Token
41
+
42
+ 用 `auth` 子命令,**不要手工编辑配置文件**:
43
+
44
+ ```bash
45
+ tytcli auth --token <TOKEN> # 常规写法
46
+ tytcli auth --token <TOKEN> --verify # 写入后立即联网校验
47
+ printf '%s' "$TOKEN" | tytcli auth --token - # 从 stdin 读,不进 shell 历史
48
+ ```
49
+
50
+ `auth` 的行为:
51
+
52
+ 1. 配置文件不存在时,自动按模板创建,权限收紧为 `0600`(目录 `0700`)
53
+ 2. 写入后**回读文件校验**,确认落盘内容与传入值一致
54
+ 3. 输出只包含掩码,绝不回显完整 Token
55
+
56
+ `--verify` 会额外发起一次真实请求,成功时在输出里带上 `identity`(登录身份摘要),可以立刻知道这个 Token 到底能不能用。**首次配置或更换 Token 时建议加上。**
57
+
58
+ ### 两个高频坑
59
+
60
+ **坑一:`TYTCLI_TOKEN` 环境变量优先级高于配置文件。**
61
+
62
+ 如果它已经被设置(比如之前 `export` 过还没 unset),你刚写入文件的 Token 会被**静默遮蔽**。`auth` 会把这件事放进 JSON 里,而不是只在 stderr 提一句:
63
+
64
+ ```json
65
+ {"ok":true,"data":{"env":"prod","token_hint":"dead…babe (len=32)","token_shadowed":true,"shadow_warning":"TYTCLI_TOKEN 已设置,它会覆盖刚写入配置文件的 Token…"}}
66
+ ```
67
+
68
+ **自动化请直接判 `data.token_shadowed` 这个布尔值**,不要去匹配 `shadow_warning` 的文案。
69
+ 为 true 时先决定到底想用哪个 Token,再 `unset TYTCLI_TOKEN` 或改为直接用环境变量方式。
70
+
71
+ **坑二:`CONFIG_MISSING_TOKEN` 不一定是"没配过"。**
72
+
73
+ Token 是**按环境分别存放**的,而一个环境的 Token 拿到另一个环境会直接返回 `AUTH_EXPIRED`——这是服务端行为,不是配置错误。报错时看 `error.details`:`env` 是当前生效环境,`envs_with_token` 列出**已经配了 Token 的其他环境**,`hint` 会给出具体怎么修。
74
+
75
+ ```json
76
+ {"ok":false,"error":{"code":"CONFIG_MISSING_TOKEN","details":{"env":"prod","envs_with_token":["<ENV>"],"hint":"…"}}}
77
+ ```
78
+
79
+ 出现 `envs_with_token` 非空时,说明 Token 存在但当前环境不对,按 `hint` 处理即可,**不要重复写入 Token**。
80
+
81
+ ## 配置文件
82
+
83
+ 位置:`$XDG_CONFIG_HOME/tytcli/config.toml`,未设置 `XDG_CONFIG_HOME` 时为 `~/.config/tytcli/config.toml`。
84
+
85
+ ```bash
86
+ tytcli config path # 查看路径与是否存在
87
+ tytcli config init # 创建模板(0600)
88
+ tytcli config init --force # 覆盖已有文件(会丢失已存的 Token)
89
+ ```
90
+
91
+ 覆盖优先级:**环境变量 > 配置文件 > 内置默认值**
92
+
93
+ | 环境变量 | 覆盖内容 |
94
+ |---|---|
95
+ | `TYTCLI_TOKEN` | Token |
96
+ | `TYTCLI_CONFIG` | 配置文件路径(指定的文件不存在会直接报错,不会回退到默认位置) |
97
+
98
+ 几条有意为之的设计,避免踩坑:
99
+
100
+ - **不会**自动加载当前目录下的配置或 `.env`,所以切换工作目录不会误用别的凭证
101
+ - `TYTCLI_TOKEN` 被设置成**空字符串**会报错,不会静默回退到配置文件里的值
102
+ - 显式指定的 `TYTCLI_CONFIG` 文件不存在会报错,不会悄悄改用默认路径
103
+ - **API 地址固定在 CLI 内部,不可覆盖**——既不能写进配置文件,也没有对应的环境变量。因此遇到 `API_BAD_REQUEST` 时,问题一定在接口路径,而不是地址配错了
104
+
105
+ **API 地址不可配置**,环境也无需切换:正常使用永远是默认环境。