docs: 上游同步指南与二开计划/凭据文档;scripts: sync-upstream 一键合并

This commit is contained in:
Liuxinyu176 2026-09-11 23:37:29 +08:00
parent fd3d74cfef
commit 2da780bf09
6 changed files with 511 additions and 0 deletions

1
.gitignore vendored
View File

@ -7,6 +7,7 @@
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
/.idea/
.DS_Store
/build
app/build/

113
docs/FEATURE-PLAN.md Normal file
View File

@ -0,0 +1,113 @@
# RainyToken 二开:功能设计(FEATURE-PLAN)
> 状态:已实现 + 多轮独立审计定稿(v1.0 · 2026-09-11)
> 依据:用户需求 + 实测调研(Sub2API / Trae / WorkBuddy 三家接口均经真实登录态实测,字段与口径已定稿;见 §5)。
---
## 0. 需求清单(用户原话提炼)
1. **新增供应商**:Trae、WorkBuddy(腾讯)、sub2api(自托管)
2. **DeepSeek 支持充值**:App 内 WebView 打开官方充值页
3. **首页仅显示已配置的服务**(有凭据就显示)
4. **新增「API 管理」页**:按供应商集中展示/管理已保存的 API Key(含状态、新增/编辑/删除/测试)
5. **用量单独一页**:独立的用量统计页(不塞进 API 管理页)
6. 约束:**任何人**都能用 sub2api(配置各自的实例地址+凭据),不只是本机;全程遵守 taste.md(独立审计→同代理复审→通过才收尾;实测优先;凭据仅实测不落码;不擅自提交推送)。
---
## 1. 现有扩展点(改前理解结论)
| 层 | 文件 | 新增服务需改动 |
|---|---|---|
| 服务枚举 | `app/src/main/java/com/rainy/token/domain/service/ServiceType.kt` | 加枚举项(storageKey 稳定) |
| 服务配置 | `app/src/main/java/com/rainy/token/domain/service/ServiceConfigProvider.kt` | 加 config;`FetchMethod` 视情况扩枚举(如自托管 REST) |
| 凭据模型 | `app/src/main/java/com/rainy/token/domain/model/Credential.kt` | 密封类加子类 |
| 凭据指纹 | `data/repository/CredentialRepository.kt`(`cacheIdentityFingerprint` / `credentialFingerprint` 两处 `when`) | 密封类新增子类后**编译器强制**补分支(无 else 的 when) |
| 刷新用例 | `domain/usecase/RefreshBalanceUseCase.kt`(硬编码 `when(service)` + `Provider<XxxRepository>`) | 加分支 + 注入新仓库 |
| DI | `di/NetworkModule.kt`(@Provides) | 加 `@Provides` 显式构造(规避 KSP 2.x 跨文件误报) |
| 凭据表单 | `ui/settings/CredentialEditScreen.kt` + `CredentialEditViewModel.kt` | 为新凭据类型加表单分支 |
| 详情卡片 | `ui/servicedetail/ServiceDetailScreen.kt`(`when(service)` 分卡) | 加分支或抽象 |
| 图标 | `ui/components/ServiceIcon.kt` | 加分支 + drawable |
| 字符串 | `res/values/strings.xml`(en)+ `values-b+zh+Hans` + `values-b+zh+Hant` | 新增 key 三处同步 |
| 导航 | `ui/RainyTokenNavHost.kt`(Compact 单栈 + Expanded 双窗格两套) | 新页面两处都要接 |
---
## 2. 功能分期
### 阶段 P1(不依赖调研,立即做)
- **首页仅显示已配置**:`DashboardViewModel.loadFromCache()` / `refresh()` 由 `ServiceType.entries.map` 改为**过滤 `state != NOT_CONFIGURED`**;首页空态给引导文案(空列表 → 提示"请先在设置/API 管理里配置服务"+ 配置入口)。
- **DeepSeek 充值**:
- `ServiceConfig` 增加 `externalUrl`(DEEPSEEK = `https://platform.deepseek.com/top_up`)。
- 新增通用 `WebViewPageScreen`(URL 容器,复用 `WebViewLoginScreen` 的 AndroidView WebView 模式,BackHandler + 进度)。
- `ServiceDetailScreen` DS 详情加「充值」按钮 → 路由到 WebView 页;API 管理页同款按钮。
- **API 管理页**:新增 `ui/account/ApiManagementScreen` + ViewModel:列出全部服务(含未配置),各卡片显示图标/名称/状态 chip/Key 掩码/最后验证/操作(编辑→credentialEdit、删除、测试、充值 for DS)。入口:Dashboard 顶栏图标 + 设置页列表项。
- **用量页(独立)**:新增 `ui/usage/UsageStatScreen`:按配置的服务聚合缓存余额与用量指标(amount/unit/monthlySpent/totalQuota/nextResetAt/extras 摘要),可一键全部刷新;并保留既有用量详情/图表/热力入口作为二级页。入口:Dashboard 顶栏图标。
### 阶段 P2(依赖三方调研回报,统一落地)
- **Trae**(调研已回,2026-09-11):
- 取数凭据 = **`Cloud-IDE-JWT`**(RS256 JWT,~14 天有效期),请求头 `Authorization: Cloud-IDE-JWT <jwt>`;CN 需 `X-User-Region: CN` + `X-Device-Id` + 插件 UA;缺 device 绑定会触发 1001/9074 风控。
- 用户获取方式:浏览器登录 Trae 后从 DevTools Network / 授权回调链接复制 JWT(走 traework2api 同款 `/authorization` 回调粘贴流程)。
- 核心端点:
- CN 积分池:`POST https://api.trae.cn/trae/api/v2/pay/ide_user_ent_usage`(`user_entitlement_pack_list[].entitlement_base_info.quota.credits_limit − usage.credits_amount`)
- CN 套餐状态:`POST https://api.trae.cn/trae/api/v1/pay/ide_user_pay_status`
- 兑换 token:`POST https://api.trae.com.cn/cloudide/api/v3/trae/oauth/ExchangeToken`
- Intl:`https://grow-normal.trae.ai/trae/api/v1/pay/ide_user_ent_usage` 等(v1)
- 参考实现:Sliverkiss/traework2api · mmqz/cpa-multi-plugins · diegosouzapw/OmniRoute(trae.ts)
- 凭据模型:`Credential.TraeCredential(jwt, region)`(简化为单字段 JWT + 地区优化显示)。
- **WorkBuddy(腾讯)**(调研已回,2026-09-11):
- 产品=腾讯 AI 办公 Agent(`workbuddy.qq.com` → 301 → `www.codebuddy.cn/work/`),与 CodeBuddy 共用积分(Credits)体系;无公开 API。
- 取数 = **OAuth Bearer token 流程**(非 cookie):设备授权 `POST https://copilot.tencent.com/v2/plugin/auth/state?platform=CLI` → 微信/QQ 扫码 → `GET .../v2/plugin/auth/token?state=` 拿 `accessToken/refreshToken` → 保活刷新 `POST .../v2/plugin/auth/token/refresh`(头 `X-Refresh-Token`)。
- 余额接口:`POST https://www.codebuddy.cn/v2/billing/meter/get-user-resource`,`Authorization: Bearer <accessToken>`,体含 `"ProductCode":"p_tcaca"`,响应 `Accounts[].CycleCapacityRemain/Used/Size` 累加得可用/已用/总额。
- 凭据模型:复用 **Codex 的 OAuth 模式**(`accessToken/refreshToken`),新增 `Credential.WorkBuddyCredential`(含 refresh)。细节待真实账号实测回填。
- 参考实现:Sliverkiss/workbuddy2api · mmqz/cpa-multi-plugins(billing.go)。
- **Sub2API(自托管,任人可用)**(调研已回,2026-09-11,源码实读:`research-sub2api`,HEAD cdb5cfaf):
- 各上游订阅 = 后台「账号(Accounts)」,管理 API 全部在 `{实例}/api/v1/admin/*`,与对外 OpenAI 网关分离。
- 认证:`POST {base}/api/v1/auth/login`(`{"email","password"}`;可被 TOTP-2FA / 验证码 / backend-mode 拦;返 `access_token/refresh_token`);或 `x-api-key: <admin-api-key>`(免登录)。新版有**合规 423 闸门**:未 accept 时 `/admin/*` 全 423,需先 `POST /api/v1/admin/compliance/accept`。
- 查询序列(最小):`GET /admin/accounts`(列表/详情)→ `POST /admin/accounts/usage/batch`(`source=passive`,不打上游、带缓存,适合 App 轮询)→ 按平台打 `openai|grok|cn-providers/.../quota|balance`(实时余额)。
- 字段以 optional 防御式解析(项目近每日发版、响应字段多变)。
- 凭据模型:`Credential.Sub2ApiCredential(baseUrl, email, password, apiKey?)`;`ServiceConfig` 需支持自定义 baseUrl(`FetchMethod.SELF_HOSTED_REST`)。
- 新服务的**表单/测试/详情卡/图标/字符串**全链路补齐;三者的取数接口都用**真实凭据实测**后再定稿字段映射。
---
## 3. 关键设计决策
1. **“有凭据就显示”**:以 `CredentialStatus.State != NOT_CONFIGURED` 为准(任一凭据存在即算配置),不要求最近验证成功(用户已选)。
2. **充值交互**:App 内 WebView(用户已选);WebView 内完成登录+充值,返回键退出页面。
3. **用量页判据**:展示已配置服务的缓存用量(无网也可见);未配置服务不展示。
4. **API 管理与用量分离**:API 页只管 Key/凭据;用量单独一页(用户已选)。
5. **凭据安全**:任何新凭据只进 SecureStorage,指纹化展示;新子类强制补 `CredentialRepository` 指纹分支。
6. **实测优先**:新供应商接口以调研/实测回报为准回填,不臆造字段。
---
## 4. 测试与审计计划
- P1 每完成一块:`.\gradlew.bat :app:assembleDebug`(CI=true)编译验证。
- 代码修改后用**独立 subagent** 审计(无阻断 + 无 UX 问题),修复后**同 subagent 复审**,通过才收尾(taste.md 强制)。
- 不开 Commit/Push(user 未授权)。
- 新供应商接口回填后,用真实凭据实测(仅验证,不落码)。
## 5. 实施状态(2026 goal round 4)
- ✅ P1a 首页仅显示已配置(含空态引导)——独立审计 + 同代理复审通过
- ✅ P1b DeepSeek 充值(App 内 WebView)——审计修复 + 复审通过
- ✅ P1c API 管理页(集中管理/测试/删除/DS 充值入口)——[A] 修复确认 + 复审通过
- ✅ P1d 用量统计页(独立页面,含失败计数/防闪/extras 过滤三修)——随 P2 审计确认生效
- ✅ P2 数据/领域层:TraeRepository / WorkBuddyRepository / Sub2ApiRepository + 凭据子类
+ 指纹双 when + ServiceType/Config(FetchMethod.SELF_HOSTED_REST)/RefreshBalanceUseCase/NetworkModule
—— 独立审计**无阻断**;F1–F4 已修(缓存身份稳定化 / 负数钳制+数值放宽 / 423 透传 / Sub2Api 掩码改实例基址),F1–F4 复审通过
- ✅ P2 专用凭据表单(Trae JWT+区域 / WorkBuddy tokens / Sub2API 实例+认证二选一)三语
- ✅ Sub2API 实测(xxcsn.site,goal round 4):确认【普通用户用 sk- API Key + GET /v1/usage】即可查余额/用量(无需管理员)。
仓库从 admin 方向重写为**用户级**(Bearer sk- → /v1/usage,404 兜底 /api/v1/usage;remaining/balance/unit/isValid/usage.total.cost/model_stats/daily_usage 实测校准;login email/password 备选);
**独立审计通过(无阻断)+ 同代理复审通过**;3 条建议已落实(desc 用户级文案/acceptCompliance KDoc/清导入)
- ✅ 全量最终审计(55682c88):**无[阻断]、可收尾**;建议1(delete 清理表单字段)已修并复审通过;建议2(test 双击竞态)评估不改;提示项(gradle 本地开关/AGP 补丁、充值 WebView 无会话持久化)已记录。
- ✅ Trae / WorkBuddy 实测校准(goal round 5,经用户授权用 logged-in 浏览器调官方接口,全程不落凭据):
- Trae:POST api.trae.cn/trae/api/v2/pay/ide_user_ent_usage 仅 Authorization:Cloud-IDE-JWT(+Content-Type) 即 200;
权威口径 usage_summary.total_amount-consumed_amount;pack 用 display_desc/credits_limit(-1=∞)/usage.credits_amount;
→ TraeRepository 重写(去 X-Device-Id/Region 头、usage_summary 优先、包级兜底)
- WorkBuddy:POST www.workbuddy.cn/billing/meter/get-user-resource-summary → data.Packages[](Cycle{Total,Remain,Used,Frozen}Capacity+CapacityUnit);
→ WorkBuddyRepository 重写(主端点 workbuddy.cn + codebuddy.cn 兜底、refresh 轮换保留)
- ✅ 两仓库实测校准独立审计(924f04e0):**通过(无阻断)**;5 项建议落实(unit 定案 Credits 且生效化/空体 EMPTY_BODY/无数据 ParseError/清未用 import/语义保留)+ 同代理复审通过;微观察②(单包明细单位)修复后再次确认通过 —— **Trae 与 WorkBuddy 实测校准正式定稿**
- ✅ **收尾(goal round 5)**:三家新供应商(Sub2API/Trae/WorkBuddy)全部实测校准+审计+复审定稿;
原始二开目标 ①-④ 全部达成且审计闭环;`assembleDebug`/`assembleRelease` 均绿;
**未提交未推送**(用户授权前保持工作区本地状态)

192
docs/REDEV-GUIDE.md Normal file
View File

@ -0,0 +1,192 @@
# 🌧️ 雨晴Token 二开开发手册
> 本文档面向在本地 fork/二开 `RainyToken`(CATMIAOZHI/Rainytoken)的开发者。
> 记录本机已就绪的构建环境、项目结构地图、五个常见二开方向的落地清单、测试与发布流程、以及本仓库强制协作约定。
> 仓库路径:`D:\work-space\deepseek_harness\雨晴token`
---
## 1. 环境现状(本机已就绪)
| 组件 | 位置 / 版本 | 状态 |
|---|---|---|
| JDK | `C:\Users\lxy\.jdks\openjdk-22.0.1`(另有 `corretto-17.0.11` 备用) | ✅ |
| Android SDK | `D:\Android-SDK`(cmdline-tools + platform-tools + platform android-35 + build-tools 35.0.0) | ✅ |
| Gradle | wrapper **9.1.0**(`gradle/wrapper/gradle-wrapper.properties`,首次自动下载) | ✅ |
| AGP / Kotlin | 9.0.0 / 2.3.10(`gradle/libs.versions.toml`) | 项目自带 |
| SDK 路径配置 | `D:\work-space\deepseek_harness\雨晴token\local.properties` → `sdk.dir=D:/Android-SDK` | ✅ |
| Android Studio | `D:\Android-Studio`(Narwhal 2025.1.1,`bin/studio64.exe`;桌面 + 开始菜单均建快捷方式「Android Studio」);用户环境变量 `ANDROID_HOME`/`ANDROID_SDK_ROOT` 已指向 `D:\Android-SDK` | ✅ |
| Git | 全局代理 `127.0.0.1:12000` 失效;**直连可用**。拉取/推送请用 `git -c http.proxy= -c https.proxy= ...` | ⚠️ |
> ⚠️ 若换了机器/别人接手:删掉 `local.properties`,自己写 `sdk.dir=你的SDK路径`;SDK 组件与 CI 保持一致(`platforms;android-35` + `build-tools;35.0.0`)。
---
## 2. 构建与验证
```bash
# 单元测试(CI 第一步)
./gradlew testDebugUnitTest
# Debug APK(日常二开验证用这个)
./gradlew assembleDebug
# 产物: app/build/outputs/apk/debug/app-debug.apk
# Lint
./gradlew lintDebug
# Release APK(本机必须有 release.jks + 环境变量,见 §7;否则报错属正常防御)
# 想临时本机验证 Release 编译: 设 CI=true 会自动 fallback 到 debug keystore
# set CI=true && ./gradlew assembleRelease
```
**常见坑**
- `java.net.ConnectException` 拉依赖:确认没走失效代理(本机直连可用,无需代理)。
- `SDK location not found`:检查 `local.properties` 的 `sdk.dir`。
- `Release KEYSTORE_PASSWORD ... not set`:这是有意的防御(见 `app/build.gradle.kts` signingConfigs),别绕过。
- `Your project path contains non-ASCII characters`(Windows / AGP 9):目录名含中文(如本仓库 `雨晴token`)AGP 默认硬失败。已在本项目 `gradle.properties` 加 `android.overridePathCheck=true` 绕过(**该行属本地改动**,对 repo 而言是未提交 diff,勿顺手提交;要提交需经 owners 同意)。新机器照此处理。
---
## 3. 项目结构地图
```
app/src/main/java/com/rainy/token/
├── data/
│ ├── cache/ BalanceCache(DataStore 余额缓存)
│ ├── local/ UsageCache + Room(UsageRecordEntity/UsageDao/UsageDatabase/ChartBucket)、SecureStorage(Keystore AES-256-GCM 密文存储)、ChartSettingsStore
│ ├── remote/ DeepSeekApi(Retrofit 接口)
│ ├── debug/ DebugLog(App 内调试日志)
│ └── repository/ 各服务 R/E(OpenCodeGo / CommandCodeGo(+Usage) / Codex / Ollama / DeepSeek)+ CredentialRepository / RepositoryError / RefreshWriteSession / RetryHelper
├── domain/
│ ├── model/ ServiceBalance(统一余额模型)、Credential(密封类三种)、TriggerSummary
│ ├── service/ ServiceType 枚举、ServiceConfigProvider(FetchMethod + 每个服务的 URL/单位)
│ └── usecase/ RefreshBalanceUseCase(余额,五个服务 when 分发)、SyncUsageUseCase、SyncCommandCodeUsageUseCase
├── ui/
│ ├── RainyTokenNavHost.kt Navigation 路由
│ ├── dashboard/ 仪表盘 / 图表 / 详情(ViewModel 按 ServiceType.entries 自动生成卡片)
│ ├── heatmap/ Token 活动热力图
│ ├── servicedetail/ 服务详情(57KB,最大改动区之一)
│ ├── settings/ Settings / CredentialEdit(凭据表单由 FetchMethod 分发)
│ ├── webview/ WebView 登录 / Codex OAuth(PKCE)
│ ├── widget/ OpenCodeGo 桌面小组件
│ ├── components/ ServiceIcon / StatusChip / FormatUtils / UiText
│ └── theme/ Color.kt / Theme.kt / Type.kt(雨晴粉品牌)
└── di/ NetworkModule(Retrofit/OkHttp/Repository @Provides)、StorageModule(DataStore)
```
关键扩展点速查:
- 新增服务入口:**一个枚举 + 一个配置文件**(`ServiceType.kt` + `ServiceConfigProvider.kt`),仪表盘卡片由 `DashboardViewModel` 遍历 `ServiceType.entries` 自动生成。
- 统一数据模型:`ServiceBalance`,所有 Repository 最终都返回它。
- 统一错误:`RepositoryError` 密封类(InvalidCredential / RateLimited / Network / ServerError / ParseError…)。
- 凭据:`Credential` 密封类(ApiKey / Session / Codex)+ `SecureStorage` 加密落盘。
---
## 4. 二开方向指南
### A. 新增/替换 AI 服务商(如 Claude、Gemini、GLM、Kimi…)
> 如果只是"换一个 REST API 余额查询",改动清单如下(按序):
1. `domain/service/ServiceType.kt` —— 追加枚举项 `Xxx(displayName, storageKey)`。**storageKey 要稳定**(枚举名不可用于索引,改名会丢凭据)。
2. `domain/service/ServiceConfigProvider.kt` —— 追加 `ServiceConfig`(选 `FetchMethod.REST_API` 或 `WEBVIEW_SCRAPER`;`displayUnit` 如 `¥`/`$`/`Credits`)。
3. `data/remote/` —— REST 服务新建 Retrofit API 接口(参照 `DeepSeekApi.kt`);页面抓取服务参照 `OllamaRepository` 的 OkHttp + HTML 解析。
4. `data/repository/` —— 新建 `XxxRepository`:实现 `fetchBalance(): ServiceBalance`(可复用 `CredentialRepository` + `BalanceCache`),HTTP 错误映射到 `RepositoryError`。
5. `di/NetworkModule.kt` —— 用 `@Provides @Singleton` 显式提供(**不要用 `@Inject constructor`**,代码注释明确说明 KSP 2.x 误报隐患)。
6. `domain/usecase/RefreshBalanceUseCase.kt` —— `when (service)` 加分支(注意这个类写死了五个服务,新增必改)。
7. UI:
- `ui/components/ServiceIcon.kt` —— 加图标分支。
- `ui/dashboard/DashboardViewModel.kt` —— 卡片默认自动出现(它遍历 `ServiceType.entries` 生成卡片);**注意**:其中的 OPENCODE_GO 特判(:139)只用于刷新桌面小组件,不是"直达详情"。
- 若新服务要"直达详情/直达用法明细"或定制卡片主体,改动点在 `ui/dashboard/DashboardScreen.kt` 的 `when (card.service)` 跳转分支,与 `ui/dashboard/ServiceBalanceCards.kt` 的 `BalanceMainArea` 卡片渲染分支(参照现有 OPENCODE_GO 卡片直达;`ServiceBalanceCards` 有 `else` 通用兜底,新服务不配自定义主体也会正常渲染)。
- `ui/settings/CredentialEditViewModel.kt` —— REST_API 服务自动走 API Key 表单;Cookie 类自动走 Session 表单;特殊形态(如 Codex OAuth PKCE)需自建流程。
8. 本地化:`res/values*/strings.xml` —— 默认 `values/` 为英文(app_name=RainyToken),中文在 `values-b+zh+Hans` / `values-b+zh+Hant`(app_name=雨晴Token),**没有 `values-en` 目录**。
9. 测试:参照 `app/src/test/.../OllamaRepositoryTest.kt` 写单测(含 HTML/JSON 解析样例)。
> 若目标是"替换"某个服务(如把 OpenCodeGo 换成别家),先删除对应枚举/配置/Repository/UseCase 分支,再按上面加新的。
### B. UI / 品牌换皮(不改功能)
| 想改的东西 | 改哪里 |
|---|---|
| 主题主色(雨晴粉) | `ui/theme/Color.kt`(`StrawberryPink` 系)/ `Theme.kt` |
| 排版 | `ui/theme/Type.kt` |
| App 名称 | `res/values*/strings.xml` 的 `app_name`(含小组件 `widget_name`) |
| 桌面图标 | `res/mipmap-anydpi-v26/ic_launcher*.xml` + `drawable/ic_launcher_*` |
| 启动背景/词语 | `drawable/ic_rainy_wordmark.xml`、`RainyBackground.kt` |
| 仪表盘卡片 | `ui/dashboard/DashboardScreen.kt`(38KB 主战场) |
| 服务详情页 | `ui/servicedetail/ServiceDetailScreen.kt`(57KB,最大) |
> 换皮不影响业务逻辑,纯 Compose/资源改动,风险低。改完跑 `assembleDebug` + 截图自查。
### C. 改包名 + 独立发布自己的版本
1. `app/build.gradle.kts`:`namespace`、`applicationId` 改成 `com.你的域名.xxx`;`versionName/versionCode` 另起(如 1.0.0/1)。**严禁只改 applicationId 不改 namespace**(会连带所有 import 断裂)。
2. 全量重命名目录:`app/src/main/java/com/rainy/token/...` → 新包路径(Android Studio 右键 Refactor → Rename Package 最稳)。
3. `ui/theme/Type.kt`/`Theme.kt` 主题名、`strings.xml` app_name、图标 —— 换皮步骤同 §B。
4. `src/test/java/...` 包路径同步重命名;`AndroidManifest.xml` 内类引用是相对名(`.MainActivity`),重命名目录即自动跟随。
5. MIT License 保留:**必须保留原作者版权声明**(`LICENSE` 文件 + README 出处),不得删除/篡改署名。
6. 独立仓库:本地新建 remote(未授权不 push);CI/Release 用的 4 个 secrets(`KEYSTORE_BASE64` / `KEYSTORE_PASSWORD` / `KEYSTORE_ALIAS` / `KEY_PASSWORD`,见 `.github/workflows/release.yml`)在你自己仓库的 Settings → Secrets and variables 配置。
### D. 架构升级 / 大版本重构
当前版本基线:AGP 9.0.0 / Kotlin 2.3.10 / Gradle 9.1.0 / compileSdk 35 / minSdk 31 / targetSdk 35。可考虑方向:
- **依赖升级**:`gradle/libs.versions.toml` 里 compose-bom 2024.10.01、Kotlin 2.3.10 等,升级前看 [AGP release notes](https://developer.android.com/build/releases/gradle-plugin) 与 [compose-bom mapping](https://developer.android.com/develop/ui/compose/bom/bom-mapping)。
- **模块化**:把 `data` / `domain` / `ui` 拆成 Gradle module,便于多人并行 + 复用。
- **跨平台 Desktop(可选)**:项目现在是纯 Android(Compose UI + Android-only 依赖 retrofit/okhttp 大多已跨平台化,但 DataStore/Keystore/Widget/WebView 都有 Android 专属部分),迁移 KMP 工作量大,建议先评估改桌面版的真实收益(用户可在 Windows 部署一个桌面临时版,直接复用 `domain` + `data` 代码,换 Compose Desktop UI + 本地文件存凭据)。
- **测试基建**:现有仅单测(JUnit),可补 Robolectric UI 测试或增加 Repository 集成测试。
> 重构类改动务必走 §6 的审计流程,分批小步提交。
---
## 5. 测试
```bash
./gradlew testDebugUnitTest
# 报告: app/build/reports/tests/testDebugUnitTest/index.html
```
现有测试资产(`app/src/test/`):
- Repository 解析测试:Codex / CommandCodeGo(Plan映射) / CommandCodeUsage / Ollama / OpenCodeGo / Credential / RefreshWriteSession
- UI 逻辑测试:FormatUtils / FormatCodexPrimaryLabel / Heatmap(AllViews, Insights, Stats) / ServiceDetailViewModel
---
## 6. 发布流程(原仓库约定)
CI:`main` 推代码 → test → debug APK / release(debug-signed) APK。
Release:打 **轻量 tag** `v*` → `release.yml` 用 Secrets 解码 `release.jks` 签名出正式包 → 建 GitHub Release 附件。
本地"发布"流程(若沿用):
1. `app/build.gradle.kts` bump `versionCode`/`versionName`
2. 独立 commit:`release: bump to vX.Y.Z`
3. 打 tag:`git tag vX.Y.Z`(轻量)
4. push main + tag(**需用户授权**)
**签名密钥**:正式构建必须在项目根放 `release.jks`,并注入 `KEYSTORE_PASSWORD / KEYSTORE_ALIAS / KEY_PASSWORD` 环境变量;无密钥时本机 Release 会主动报错(防御设计,勿绕过)。
---
## 7. 协作约定(taste.md — 强制)
本项目由水晴喵/雨晴喵维护,本仓库及 fork 二开均须遵守:
1. **修改后独立审计(强制)**:完成任何代码修改后,派发独立 subagent 审计:
- 无阻断问题(编译失败、逻辑错误、数据流断裂、回归风险)
- 无影响用户体验的问题(UI 展示错误、交互异常、数值显示错误、异常路径处理)
2. **修复后复审**:若审计发现并修复问题,必须**复用同一个 subagent**(续接其 task_id)复审,确认解决且无新问题。
3. **审计通过才收尾**:推送 / 发版 / 宣告完成前必须已有审计通过结论。
4. **禁推送**:未经用户明确允许,不得 commit/push。
5. **实测优先**:下结论前用真实凭据/日志/接口实测,不臆断(字段单位、数值含义、异常原因)。
6. **凭据安全**:用户 cookie / API Key / token 仅用于实测,不写入代码、不提交、不泄露。
7. **子代理路径**:派发 subagent 时,prompt 里所有工作区文件路径一律用**完整绝对路径**(如 `D:\work-space\deepseek_harness\雨晴token\app\src\...`),子代理没有工作区附着上下文。
8. **改前理解**:修改任何文件前先理解相关代码及上下文,遵循项目既有惯例,不搞发明创造。
---
## 8. 备忘
- Git 拉取/推送绕过失效代理:`git -c http.proxy= -c https.proxy= push/pull/fetch ...`
- 当前 HEAD:`eecf549`(`main`,v1.6.3 基线)
- 本项目是雨晴系列第 4 个成员(RainyLLM / RainyScanner / Rainy2FA / RainyToken),MIT License

84
docs/UPSTREAM-SYNC.md Normal file
View File

@ -0,0 +1,84 @@
# 上游同步指南(Rainytoken 二开合并流程)
> 目的:上游(CATMIAOZHI/Rainytoken)持续更新时,把二开工作区(本地分支)与上游
> 新基线对齐,冲突可控、可重复、可一键执行。
> 本仓库两条线:**main = 上游基线跟踪**(保持可随时 fetch/rebase);**dev = 二开改动**。
---
## 1. 仓库当前状态
| 分支 | 内容 | 说明 |
|---|---|---|
| `main` | 上游基线(当前 eecf549,v1.6.3 后继续可追) | 不加入二开改动,保持与 origin/main 一致 |
| `dev`(建议名) | 全部二开改动(3 家供应商 + API 管理页 + 用量页 + 充值 + Sub2API 明细升级 + 本指南) | 二开唯一工作分支 |
> 因为当初二开没有分叉提交,**全部二开改动都在工作区**;首次授权后统一 commit 到 `dev`。
> 上游的 5 个提交(v1.6.4)尚未合入二开,按下面的流程合并后二开同时拥有两边功能。
---
## 2. 每轮上游更新的快捷流程(一键脚本)
在项目根目录执行(Git Bash):
```bash
./scripts/sync-upstream.sh
```
脚本自动完成:fetch 上游 → 切到 `dev` → rebase 到 `origin/main` → `assembleDebug` 编译验证。
手动等价流程:
```bash
# 1. 拉上游(直连,绕过失效代理)
git -c http.proxy= -c https.proxy= fetch origin main
# 2. 在二开分支上 rebase 到新基线
git checkout dev
git rebase origin/main
# 3. 若冲突:手动解决相同文件后
git add <冲突文件>
git rebase --continue
# 4. 编译回归
CI=true ./gradlew.bat :app:assembleDebug
```
---
## 3. 已知冲突面(预计最多遇到的)
二开与上游都在改的文件,但**改动区域通常不同**,多为小冲突:
| 文件 | 上游改 | 二开改 | 冲突可能性 |
|---|---|---|---|
| `app/src/main/java/com/rainy/token/domain/service/ServiceType.kt` | CCGO 显示名 | 追加 Trae/WorkBuddy/Sub2API 枚举 | 低(相邻区域,git 自动合) |
| `app/src/main/res/values*/strings.xml` ×3 | CCGO 更名 + 时间选择器 key | sub2_* 系列 + 服务三语 | 低 |
| `app/src/main/java/com/rainy/token/ui/components/ServiceIcon.kt` | CCGO 更名 | 新服务图标分支 | 低 |
| `app/src/main/java/com/rainy/token/di/NetworkModule.kt` | CCGO 更名 | 3 个新 Repository @Provides | 低 |
| `app/src/main/java/com/rainy/token/ui/servicedetail/ServiceDetailScreen.kt` | CCGO 文案 | Sub2API 明细卡分支 + 充值 | 低 |
| `app/src/main/java/com/rainy/token/ui/dashboard/Usage*Screen.kt` | 时间选择器大改 | (二开基本没动) | 无 |
> 尾部逗号/顺序类冲突:每次合并时**统一以「二开语义」为准**重新排布,不影响功能。
> 版本号(`app/build.gradle.kts`):上游 taste.md 已声明**版本号控制权归上游作者**——
> 二开合并上游后**不要自作主张 bump 版本**;由用户在发版时决策。
---
## 4. 变更落地检查清单(每次合并后)
1. `assembleDebug` 绿(脚本已做);
2. 服务枚举/凭据指纹 `when` 无编译告警(新增枚举必有分支);
3. 三语 strings 无缺 key(lint 可查);
4. 若有 UI 改动 → taste.md 要求的独立 subagent 审计;
5. 需要推手机看效果:`adb -s <serial> install -r app/build/outputs/apk/debug/app-debug.apk`。
---
## 5. 建议节奏
- 上游「发版」(release commit)或你看到感兴趣的新功能时再合并,不必每次小提交都跟;
- 合完上游后建议打一个二开本地 tag(`rain-<日期>`)便于回溯;
- 禁止直接 push 到上游;二开仓库如需推送,仅推 `dev` 且需用户授权。

View File

@ -0,0 +1,77 @@
# 实测凭据提取指南(仅用于功能验证,绝不写入代码/提交)
> 目标:为 RainyToken 三家新供应商做「实测优先」验证。
> 凭据只在本机实测时临时使用,用完即弃;不会进入 Git 历史或仓库文件。
## ✅ 实测进度(2026-09,goal round 5)
- **Sub2API**:已用真实 sk- Key 实测通(GET /v1/usage),字段校准完成。
- **Trae**:已实测通(authorized 登录态调 api.trae.cn `ide_user_ent_usage`,HTTP 200),
权威口径 `usage_summary.total_amount - consumed_amount`,无需 X-Device-Id/X-User-Region 头。
- **WorkBuddy**:已实测通(authorized 登录态调 workbuddy.cn `billing/meter/get-user-resource-summary`,
HTTP 200),`data.Packages[].Cycle{Total,Remain,Used,Frozen}Capacity + CapacityUnit`。
- 以下步骤保留作「他人/日后自助提取」参考。
---
## 1. Trae —— 提取 Cloud-IDE-JWT(点击级步骤,5 分钟)
RainyToken 用 `Authorization: Cloud-IDE-JWT <jwt>` 认证(官方 IDE 同款)。
**推荐在浏览器 Web IDE 上抓**(Chrome / Edge 都行):
### 步骤
1. 打开 Chrome 或 Edge,访问 **https://www.trae.cn** 并登录你的 Trae 账号;
2. 按 **F12** 打开开发者工具 → 切到 **Network(网络)** 标签;
- 建议勾选日志左上角的 **Preserve log(保留日志)**,避免跳转丢记录;
3. 保持 DevTools 开着,访问 **https://ide.trae.cn**(或点右上角「进入 IDE / Web IDE」)。页面加载时会自动发一堆请求;
4. 在 Network 顶部的 **Filter(过滤)** 输入框里输入 `ent_usage`;
5. 找到名为 **`ide_user_ent_usage`** 的请求(方法 POST,域名 `api.trae.cn` 或 `grow-normal.trae.ai`):
- 点击它 → 右侧切到 **Headers** → 往下到 **Request Headers**;
- 找到 **`Authorization`** 一行,值是 `Cloud-IDE-JWT eyJ…`;
- 复制 `Cloud-IDE-JWT ` 后面的整段 **`eyJ…`**(JWT 三段、以点分隔,很长);
6. 告诉我:
- 这个 JWT;
- 你的区域:域名/请求是 `api.trae.cn` → **CN**;是 `grow-normal.trae.ai` → **INTL**(对应请求头 `X-User-Region`)。
### 校验(防止复制错)
- 复制的内容应**以 `eyJ` 开头**,形如 `eyJhbGciOiJIUzI1NiJ9.eyJ…。….`(三个点分片);
- 如果抓到的是 `Cloud-IDE-V2` / `Cookie` / `Bearer` 开头的值,则**不是**这个 JWT,换一个请求再看;
- 若 `ent_usage` 过滤为空:到 **账户/配额页**(trae.cn 头像 → 用量/套餐)点一下刷新,同时清空 Filter 输入 `pay` 或 `ide_user` 再找。
### 备选(浏览器方案无效时)
- 桌面 Trae IDE 里查看「额度/套餐」面板并打开系统代理抓包(Charles/Fiddler 需装根证书,较麻烦);
- 或告诉我,我改成其它抓取路径(如 CLI 登录态文件)。
> JWT 约 14 天有效;只用于实测,用完即弃,绝不写入仓库/提交。
---
## 2. WorkBuddy / CodeBuddy —— 需要 accessToken + refreshToken(OAuth Bearer)
腾讯 WorkBuddy 与 CodeBuddy 共用积分体系,实测走官方 CLI 插件协议:
1. 安装 CodeBuddy CLI(`npm i -g @codebuddy/cli` 或官方安装包)并登录;
2. 或直接抓浏览器:登录 https://www.codebuddy.cn / copilot.tencent.com 后在开发者工具
Network 里找 `get-user-resource` 请求的 **Authorization: Bearer <accessToken>**;
3. **refreshToken** 一般可从浏览器的本地存储 / CLI 的 `~/.codebuddy/auth` 之类配置里读到
(名为 `refresh_token` / `X-Refresh-Token` 对应字段)。
> 若你不想装 CLI,告诉我你平时怎么用 WorkBuddy/CodeBuddy,我改写成对应抓法。
---
## 3. Sub2API —— ✅ 已实测确认(xxcsn.site,2026-09):普通用户用自己的 sk- API Key 即可,无需管理员
- 接口:`GET {实例基址}/v1/usage`,请求头 `Authorization: Bearer <你的 sk-… API Key>`
- 返回:`remaining` / `balance`(剩余余额)、`unit`(如 USD)、`usage.total.cost`(累计花费)、
`model_stats[]`(分模型)、`daily_usage[]`(按日)。
- RainyToken 填:实例地址 + 你账号的 `sk-…` Key(推荐);无 Key 可用面板账号邮箱+密码登录(/api/v1/auth/login → Bearer token)。
- 兜底:`/v1/usage` 404 时自动尝试 `/api/v1/usage`。
---
## 留下什么
- 你在下一条消息里按上面任一项贴出凭据(可先只给一个供应商);我会:
1. 直接用 curl 验证官方接口的**真实响应结构**;
2. 对照校准我方仓库的字段解析(`TraeRepository` / `WorkBuddyRepository` / `Sub2ApiRepository`);
3. 在你提供后**当场删除**,不落入任何文件。

44
scripts/sync-upstream.sh Normal file
View File

@ -0,0 +1,44 @@
#!/usr/bin/env bash
# ═══════════════════════════════════════════════════════════════
# 一键同步上游 Rainytoken 更新到二开分支
# 用法: ./scripts/sync-upstream.sh [分支名] (默认 dev)
# 流程: fetch origin/main → checkout dev → rebase → assembleDebug
# ═══════════════════════════════════════════════════════════════
set -uo pipefail
cd "$(dirname "$0")/.."
BRANCH="${1:-dev}"
REMOTE="origin"
echo "== [1/5] fetch 上游 main =="
git -c http.proxy= -c https.proxy= fetch "$REMOTE" main || { echo "!! fetch 失败(检查网络/代理)"; exit 1; }
echo "== [2/5] 确保分支 $BRANCH 存在并切过去 =="
if ! git show-ref --verify --quiet "refs/heads/$BRANCH"; then
echo "!! 本地分支 $BRANCH 不存在。若二开工作区尚未提交,请先手动 commit 到 $BRANCH。"
exit 1
fi
git checkout "$BRANCH" || exit 1
echo "== [3/5] rebase 到 origin/main =="
if ! git rebase origin/main; then
echo ""
echo "!! 存在冲突。解决方式:"
echo " 1) 打开冲突文件手动解决(参考 docs/UPSTREAM-SYNC.md §3 已知冲突面)"
echo " 2) git add <冲突文件>"
echo " 3) git rebase --continue"
exit 1
fi
echo "== [4/5] 编译验证 =="
if command -v cmd >/dev/null 2>&1; then
CI=true ./gradlew.bat :app:assembleDebug || { echo "!! 编译失败"; exit 1; }
else
CI=true ./gradlew :app:assembleDebug || { echo "!! 编译失败"; exit 1; }
fi
echo "== [5/5] 完成。最近 5 条提交: =="
git log --oneline -5
echo ""
echo "✅ 同步完成。如需推送手机验证:"
echo " adb install -r app/build/outputs/apk/debug/app-debug.apk"