# 获取并配置 PAT（Personal Access Token）

本插件用 **PAT** 接入 Qoder。这份文档只讲三件事：PAT 是什么、从哪拿、怎么填进插件。

## 一、PAT 是什么

PAT 是 Qoder 官方发给开发者的**个人访问令牌**，形如 `pt-` 开头的一串字符。你可以把它理解成「给外部程序用的一把钥匙」：谁持有它，谁就能以你的身份调用 Qoder 的接口。

它的两个特点决定了本插件的设计：

- **你自己签发、自己吊销**：不需要把账号密码交给任何第三方，随时能在设置页删掉。
- **等价于你的账号权限**：泄露它等于泄露账号，因此不要提交进代码仓库、也不要贴到聊天窗口或 issue 里。

插件拿到 PAT 之后做的事（这也是它**为什么只需要 PAT**的原因）：

```text
PAT  ──(一次性)──>  Qoder 网关 /jobToken 交换  ──>  jobToken
                                                      │
                                                      ↓
                                          之后每次请求带签名会话头
                                          （PAT 此后不再出现）
```

PAT 只在**进程内**参与这次交换：DSH 的模型客户端（pi-ai）连的是本机的回环端口，真正的 PAT 一直在插件进程里，不进网络请求、不写进会话记录。

## 二、从哪里拿 PAT

两个区域各发一份，**互不通用**：

| 区域 | 服务站点 | 发证入口 |
| --- | --- | --- |
| 国内版（通义灵码） | `qoder.com.cn` | 登录 [Qoder CN 控制台](https://www.alibabacloud.com/help/zh/lingma/cloud-agents-cn/api-authentication) → **设置 → API 令牌** → 创建令牌 |
| 国际版 | `qoder.sh` | 登录 Qoder 控制台 → **Settings → Personal Access Tokens** → 创建（见 [Qoder 官方认证文档](https://docs.qoder.com/cloud-agents/api/conventions/authentication)） |

创建时：

1. 给令牌起一个**名字**（只是给你自己看的备注，比如「DSH 笔记本」）。
2. 如果页面要求选**权限范围 / 有效期**，按需选择——本插件只用模型推理能力，选默认即可；有有效期的话注意到期后要重新签发。
3. 点创建。**令牌通常只完整显示一次**，当场复制保存（形如 `pt-xxxxxxxx`）。

> 注意：这里说的是 Qoder **控制台网页版**里的入口，不是桌面客户端的设置页。

## 三、怎么填进插件

插件侧有两条路，二选一（同时配也没问题，会自动去重合并）。

### 方式 A：在设置卡片里粘贴（推荐，可多账号）

```text
DSH → 插件 → dsh-connect-qoder → 设置卡片
     → 选对应的 tab（国内版 / 国际版）
     → 点「添加 PAT」
     → 在输入框贴入 pt-…（可顺便填一个显示名，会自动带出真实账号名）
     → 提交
```

提交时插件会**立刻拿它去 Qoder 网关做一次真实验证**：能换到 jobToken 才算通过，失败会直接拒绝并报错，不会把一个无效令牌存进去。所以「提交后没反应」以外的情况，都说明令牌本身有问题。

卡片里可以放**多个**账号（每区域一个保险库），换号、删号都在卡片内完成。

### 方式 B：环境变量

适合不想让令牌进 DSH 设置文件、或者希望由密钥管理器注入的场景：

```bash
# 国际版
export QODER_PERSONAL_ACCESS_TOKEN=pt-xxxxxxxx

# 国内版
export QODERCN_PERSONAL_ACCESS_TOKEN=pt-xxxxxxxx
```

放进 shell 配置（`~/.zshrc` 等）或系统的密钥管理工具（1Password / Bitwarden 等的 shell 插件），DSH 启动时会自动读取。

### 优先级

```text
设置卡片的 PAT  >  环境变量 PAT  >  保险库（vault）账号
```

三者按账号去重合并，不是互相覆盖。谁在生效可以用命令行看：

```bash
dsh-connect-qoder status      # 各区域当前凭据来源与上游可达性
dsh-connect-qoder doctor      # PAT 是否就位、指纹派生、账号数（不联网）
```

## 四、常见问题

**提交后报「验证失败」**
令牌与区域不匹配（国内版的令牌不能用在国际版 tab），或复制时带上了空格/换行，或已过期。

**「凭据被拒」但令牌明明是新生成的（实测记录）**
2026-10 实测到一个值得记下来的现象：同一个 CN 令牌，

| 请求 | 结果 |
| --- | --- |
| `api.qoder.com.cn/api/v1/cloud/agents` | **200** —— 令牌有效、账号正常 |
| `openapi.qoder.com.cn/sash/api/v2/me/usage` | 401 `TOKEN_EXPIRE / token is not active` |
| `openapi.qoder.sh/sash/api/v2/me/usage` | 401 `TOKEN_EXPIRE` |
| `gateway.qoder.com.cn/algo/api/v2/model/list` | 403 `Signature invalid` |

两个结论：

1. **一张令牌有效与否，不能只看单一端点。** `sash` 系列接口回 `TOKEN_EXPIRE`
   时，令牌本身可能完全正常——它只是不接受 PAT 直连（那条路径预期的是
   浏览器会话 cookie：`qoder.com.cn/sash/...` 对缺 cookie 的请求回
   `UNAUTHORIZED / missing cookie header`，与令牌无关）。
2. **`gateway.*/algo/*` 必须走 COSY 签名**，裸 `Bearer` 一律 `Signature invalid`。
   本插件正是走「PAT → jobToken → 签名会话」这条路，所以**聊天与模型列表不受
   上面那条 401 影响**；受影响的只有用量/积分查询。

因此：卡片上积分显示「—」而对话能正常用，属于「积分端点读不到」，不等于
「令牌坏了」。判断令牌真实状态应看对话能否发送，或按上表用 Cloud Agents 端点核对。
（本插件现在的积分查询已改用 jobToken 的 `jt-` 令牌，实测 200；PAT 直连只作兜底。）

**模型列表是空的，但令牌看着没问题**
未配置 PAT 的区域**不会列出任何模型**——一个请求都发不出去，列出来只会点到就失败。确认「从 Qoder 刷新」执行过。

**不想用 PAT 了**
把令牌在 Qoder 控制台里删掉即可；本插件侧用 `dsh-connect-qoder logout [--region cn|global]` 清除本地 vault 账号。

**能不能像 WorkBuddy 那样直接读本机登录？**
不能。WorkBuddy 桌面端把登录态存在固定路径的明文/可解密文件里，插件可以直接扫描复用；Qoder 桌面端把凭据加密后存进系统钥匙串相关的封装文件，且 CLI 侧的 `.auth/user` 只有登录过 Qoder CLI 才会生成（大多数用户不会）。所以 Qoder 这边只有官方 PAT 这一条稳妥路径——所有第三方 Qoder 接入工具都是这么做的。

## 五、Qoder 每日签到（实测协议，2026-10）

**参考实现**：https://github.com/L0NE-6/Qoder-Daily （青龙脚本，CN + Global）
**官方规则**：https://help.aliyun.com/zh/lingma/daily-check-in-100-credits-reward-program-terms

### 协议（CN 实测通过）

拉活动列表（**v1**，且叫 campaigns，不是 checkin）：

    GET {openapi}/sash/api/v1/me/campaigns?forceRefresh=true
    headers:
      authorization:    Bearer {jobToken.securityOauthToken}   ← jt- 令牌，与用量同一套
      cosy-clienttype:  10
      accept:           application/json
      accept-language:  zh-CN
      user-agent:       Qoder

响应：

    {"campaigns":[
      {"campaignId":"01a12102-…","actionType":"CLAIM_BENEFIT","claimStatus":"CLAIMED",
       "benefit":{"kind":"CREDITS","amount":100,
                  "modelScope":{"modelSeries":{"key":"ALL_MODELS"}},
                  "validity":{"mode":"RELATIVE_DAYS","days":30}}},
      {"campaignId":"01a05bbf-…","actionType":"VIEW_DETAILS","claimStatus":"CLAIMED"}
    ]}

领取：

    POST {openapi}/sash/api/v1/me/campaigns/{campaignId}/claim
    （同 headers + content-type: application/json + origin）

已领取的判定：HTTP 409，或 body.result === "ALREADY_CLAIMED"，或 body.replayed === true。

### 关键事实

- 活动名里**没有**「签到」二字，所以照着 checkin / signin / reward 猜路径一律 404
  （我实测猜了 13 个全 404）。真正的词是 **campaigns**（活动/权益）。
- 状态机：`claimStatus` ∈ {CLAIMABLE, CLAIMED}；只有 `actionType === "CLAIM_BENEFIT"`
  且 `CLAIMABLE` 的才可领。
- 官方刷新时间：**每天 10:00 (UTC+8)** 开窗（阿里云文档写 0 点，桌面端截图显示 10:00
  ——以桌面端为准）。
- **国际版**还需要设备风控头 `Cosy-MachineToken` / `Cosy-MachineCode` /
  `Cosy-MachineType`；**CN 不需要**（实测多发这些头在 CN 侧是噪音）。
  三件套由 Qoder 客户端自带的 UMID 组件在本机生成，插件按区域读取：

  | 区域 | 优先组件路径 |
  |---|---|
  | 国内版 | `/Applications/Qoder CN.app/Contents/Resources/umid/runtime-info` |
  | 国际版 | `/Applications/Qoder.app/Contents/Resources/umid/runtime-info` |

  两侧都装了时各自优先自己那个 —— 设备身份与区域绑定，用错一侧会被风控拒绝。
  组件不存在时签到**如实报错**（「未找到 Qoder 设备风控组件」），而不是把
  「取不到设备身份」伪装成「今天没有活动」。也可用 `QODER_UMID_PATH` 显式指定。
  读取结果按 50 分钟在进程内缓存（参考实现同量级），避免每次签到都 fork 进程。
- 企业版账号不支持该活动（仅个人用户）。
