# DEVELOPING — 本地开发、构建与接入指南

[English](DEVELOPING.md)

本文档面向想在本机跑这个插件、对着自己的 DSH 应用调试、或手动把它接进 DSH profile 的开源贡献者。

## 1. 克隆

```bash
git clone https://github.com/Elpsycoogroo/dsh-work-report.git
cd dsh-work-report
```

插件是独立仓库，自带 `.git`。它**不属于** DSH 的 `packages/` workspace；运行时位于 `plugins/`，由 profile 通过 `file:` 依赖引用。

## 2. 安装依赖

用 npm（或 pnpm）安装：

```bash
npm install
```

`prepare` 会在安装时执行 `tsdown`。如果构建因 DSH 运行时包（`@deepseek-ai/dsh-*`）不存在而失败，这是正常的——这些包只在 DSH monorepo 或 profile 内才有。两个选择：

1. **在 DSH 环境中构建**（见下方 [§4](#4-接进本地-dsh-profile)）。
2. **跳过构建失败**，在 DSH profile 目录内通过 `npm install` 安装 peer 包。

无论哪种方式，构建成功就会产出 `lib/`。

> **pnpm / `dsh plugin add` 安装**：pnpm 默认拦截 install 脚本。先跑一次安装命令，pnpm 会打印它要加到 profile 下 `pnpm-workspace.yaml` 里 `allowBuilds` 的 key，加进去再装一次。

## 3. 构建

```bash
npm run build        # = tsdown
```

产物：
- `lib/index.js` — host 端（server 插件，ESM）
- `lib/client.js` — browser 端（CJS + `__ModuleLoader__` banner；echarts 已内联）

## 4. 接进本地 DSH profile

两种方式。

### 4a. `file:` 依赖（推荐开发模式）

在 DSH 仓库里改 profile 清单，例如 `dsh/.dsh-home/profiles/web/package.json`：

```json
{
  "dependencies": {
    "dsh-work-report": "file:../../../plugins/dsh-work-report"
  },
  "dsh": { "profile": { "bundles": ["dsh-work-report"] } }
}
```

然后在 `.dsh-home/profiles/web` 里重装 profile（pnpm/npm）。插件作为本地包被加载；用 `npm run build` 重新构建并重启 DSH 应用即可。

> ⚠️ **`package.json` 的 `exports` 必须包含 `"./package.json"`（否则插件列表可见但 UI 永远不加载）**
> DSH 的 client-modules 用 `require.resolve('<包名>/package.json')` 读取插件清单，进而定位 `./client` 入口。若 `exports` 没导 `"./package.json"`，这个调用会抛 `ERR_PACKAGE_PATH_NOT_EXPORTED`，client-modules 会把该包**永久缓存为"非客户端插件"**——插件出现在插件列表、host 壳正常加载，但 client.js 从不注入，UI 永不出现。还要保持三处 name 对齐（package.json / 插件自带 patch / profile 引用）。改完清单后**必须重启 dsh**（负判定是进程内缓存）。

### 4b. 直接复制 `lib/`（快速冒烟测试）

先构建，再把产物复制进已安装的插件目录：

```bash
cd dsh/plugins/dsh-work-report
../../node_modules/.bin/tsdown
Copy-Item -Recurse -Force lib\* "dsh/.dsh-home/profiles/web/node_modules/dsh-work-report/lib/"
```

重启 DSH Web 应用。更流畅的开发循环用 `npm run dev`（或 `node dev.mjs`）——它监听 `src/`、自动构建、并在设了 `DSH_PROFILE_DIR` 环境变量时自动把**整个包**同步进 DSH profile（配置见 [dev.mjs](dev.mjs)）。

## 5. 确认加载成功

打开 DSH 应用的浏览器控制台，应看到：

```
[dsh-work-report] v0.1.0 client loaded
```

点击 🧠 悬浮球——神经账本覆盖层打开。如果没出现，检查：

- Server 日志有 `[dsh-work-report] host plugin loaded: GET /api/work-report`。
- `curl http://127.0.0.1:3080/api/work-report?days=7` 返回 JSON（而不是 `not found`）。

## 6. 用控制台桥接定位问题

| 日志 | 含义 |
|-----|------|
| `[dsh-work-report] v0.1.0 client loaded` | Client bundle 已注入。 |
| `[dsh-work-report] host plugin loaded: GET /api/work-report` | Host 路由已注册。 |
| `work-report error: ...` | 战报构建失败，错误详情跟在后面。 |

调试要点：

- **API 返回 `not found`** — 路由被同前缀 `/api` 的处理抢了；确认注册用的是 `kind: 'exact'`（`ctx.webServer.register`）。
- **没有数据（0 会话）** — 检查 `sessions.list()`/`sessionPersistence` 是否可用，`$DSH_HOME/storages/session_projcache.json` 是否存在。
- **子代理 token 为 0** — 那些是空白会话（创建但未运行），按设计会被过滤。若真实会话仍为 0，说明事件 `usage` 聚合没找到块——检查 `assistant/message` 事件结构。
- **预测全为 0** — 每日数据稀疏会触发回退基线；历史足够时线性回归生效。

## 7. 项目结构

```
├── src/
│   ├── index.ts             # Host 入口（再导出 apply/name/inject）
│   ├── server/
│   │   ├── index.ts         # 路由注册 + mock 模式
│   │   └── report-data.ts   # 收集、聚合、洞察、预测
│   ├── client/
│   │   ├── index.ts         # client 入口：可拖拽悬浮球 + 覆盖层 + hover 标签
│   │   ├── i18n.tsx         # 中英字典 + 语言 Provider
│   │   ├── ReportView.tsx   # 主仪表盘壳
│   │   ├── StatCards.tsx / Insights.tsx / TokenCharts.tsx / ForecastCard.tsx
│   │   ├── WorkspaceChart.tsx / EfficiencyCharts.tsx / ToolRanking.tsx
│   │   ├── SessionTimeline.tsx / ContextExporter.tsx / markdown.ts / report-api.ts
│   └── types/
│       └── dsh-env.d.ts     # 环境类型
```

不修改任何 DSH 源文件。集成只发生在 DOM 层。

## 8. 常见问题

| 问题 | 修复 |
|------|------|
| pnpm 拒绝跑 install 脚本 | 把打印的 key 加进 profile `pnpm-workspace.yaml` 的 `allowBuilds`。 |
| 插件加载了但悬浮球不出现 | 确认 client bundle 被服务：`curl /plugins/dsh-work-report/client.js`；改 manifest 后重启 dsh。 |
| echarts 构建报错 | echarts 放在 `devDependencies` 才被打包；别移到 `dependencies`（会被 external 化并因模块表缺失而崩）。 |