dsh-install-radar 0.0.0-stage → 1.0.0
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.
- package/LICENSE +21 -0
- package/README.en.md +66 -0
- package/README.md +211 -2
- package/bin/radar.mjs +124 -0
- package/cordis.patch.yml +13 -0
- package/package.json +73 -4
- package/src/client.js +1396 -0
- package/src/engine/audit.js +110 -0
- package/src/engine/autoclean.js +105 -0
- package/src/engine/classify.js +126 -0
- package/src/engine/clean.js +115 -0
- package/src/engine/commands.js +218 -0
- package/src/engine/history.js +149 -0
- package/src/engine/procs.js +269 -0
- package/src/engine/radar.js +357 -0
- package/src/engine/recent.js +168 -0
- package/src/engine/registry.js +92 -0
- package/src/engine/repro.js +255 -0
- package/src/engine/resolve.js +296 -0
- package/src/engine/risk.js +164 -0
- package/src/engine/roots.js +105 -0
- package/src/engine/scan.js +168 -0
- package/src/engine/sessions.js +277 -0
- package/src/engine/signature.js +103 -0
- package/src/engine/sizecache.js +77 -0
- package/src/engine/trend.js +108 -0
- package/src/engine/uninstall.js +30 -0
- package/src/index.js +670 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-install-radar contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# dsh-install-radar
|
|
2
|
+
|
|
3
|
+
Per-session install radar for DeepSeek Harness (dsh): track **what every conversation installed on this machine** — Python packages, global npm packages, git clones, winget/choco programs — with **size, real path, created time, purpose, uninstall consequence and the reason it was installed**.
|
|
4
|
+
|
|
5
|
+
Three complementary evidence sources (miss any one and you get "C: lost 20 GB and the plugin sees nothing"):
|
|
6
|
+
|
|
7
|
+
| Source | Answers | Limits |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| **Session logs** (`$DSH_HOME/sessions/**/session.v*.jsonl.zstd`, `tool/call`) | which session, which turn, which command installed X; where it landed, how big, why | only sees command evidence; `$var` targets and remote scripts stay invisible |
|
|
10
|
+
| **Snapshot diff** (baseline vs now) | new entries in the watched roots, directories that grew | needs a baseline; roots outside the watch list are blind |
|
|
11
|
+
| **Timestamp sweep** (`Device` tab) | "which folder ate the disk in the last N days" including temp dirs and other tools | time window only, no judgement of intent |
|
|
12
|
+
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Left sidebar panel** (`sidebar.panellist`, above Workspaces) with two top-level tabs: **From sessions** (blue) and **Not from sessions** (orange), each with sub-views (per session / by kind / by month, and a 1/3/7-day sweep).
|
|
16
|
+
- **Every row** carries: size, path (copy / open in Explorer), created time, purpose, **🎯 why it was installed** (agent's own note → its reasoning sentence → your prompt), and **🟢🟡🔴 uninstall consequence**.
|
|
17
|
+
- **Process → disk causal table**: samples `Win32_Process` every 90 s, classifies Chrome/CDP launches, temp browser profiles, pip/npm installs, model downloads, builds, ffmpeg; groups by launcher and joins with on-disk size.
|
|
18
|
+
- **Backtrace** for directories left behind: correlates leftover `cdp-*` dirs with the session commands active at their creation time (probability + evidence), then finds the driver script and tells you whether it cleans up, with a ready-to-paste cleanup hook.
|
|
19
|
+
- **Cleanup** with two-step confirm, a temp-directory whitelist, a 50 MB floor, `danger` classes excluded, multi-select with 🟢/🟢🟡 select-all, in-place row removal (no refetch, no scroll jump) and an auto-clean switch (off by default, safe items older than N days only).
|
|
20
|
+
- **6 agent tools**: `install_radar_report / sessions / recent / baseline / diff / audit / history / procs / repro / autoclean`.
|
|
21
|
+
- **CLI** (`node bin/radar.mjs …`) works without dsh.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
dsh plugin --profile desktop add dsh-install-radar
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Restart dsh (or hot-enable the plugin). An icon appears in the left sidebar above Workspaces.
|
|
30
|
+
|
|
31
|
+
## Agent tools
|
|
32
|
+
|
|
33
|
+
| Tool | Use |
|
|
34
|
+
| --- | --- |
|
|
35
|
+
| `install_radar_report` | full per-session report + writes `$DSH_HOME/install-radar/report.md` |
|
|
36
|
+
| `install_radar_sessions` | session command evidence only, filterable |
|
|
37
|
+
| `install_radar_recent` | "who ate the disk in the last N days", aggregated by owner folder |
|
|
38
|
+
| `install_radar_baseline` / `install_radar_diff` | take a baseline / diff since baseline |
|
|
39
|
+
| `install_radar_audit` | index `sessions/` and record vanished session dirs |
|
|
40
|
+
| `install_radar_history` | backtrace leftover temp dirs against session commands (probability) |
|
|
41
|
+
| `install_radar_procs` | process command-line causal table (CDP / pip / npm / models / builds) |
|
|
42
|
+
| `install_radar_repro` | find the driver script behind leftover dirs + cleanup hook |
|
|
43
|
+
| `install_radar_autoclean` | toggle / preview scheduled safe cleanup |
|
|
44
|
+
|
|
45
|
+
## CLI
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
node bin/radar.mjs roots # which roots are watched
|
|
49
|
+
node bin/radar.mjs baseline # take a baseline
|
|
50
|
+
node bin/radar.mjs scan # current snapshot, top 15
|
|
51
|
+
node bin/radar.mjs sessions # what history sessions installed
|
|
52
|
+
node bin/radar.mjs recent --days 3 # disk growth by owner
|
|
53
|
+
node bin/radar.mjs diff # new / grown since baseline
|
|
54
|
+
node bin/radar.mjs report --out report.md
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Safety
|
|
58
|
+
|
|
59
|
+
- Cleanup only ever touches the temp-directory whitelist (`%SystemRoot%\Temp`, `%LOCALAPPDATA%\Temp`, `C:\Temp`, `TMP`); the roots themselves are refused.
|
|
60
|
+
- `danger` classes (`.pnpm-store`, `Windows\Installer`, `site-packages`, `node_modules`, browser `User Data`) never get a one-click delete.
|
|
61
|
+
- Nothing is deleted without a second confirmation; every removal is appended to `$DSH_HOME/install-radar/clean-log.jsonl`.
|
|
62
|
+
- Read-only with respect to session logs: the plugin never writes under `sessions/`.
|
|
63
|
+
|
|
64
|
+
## License
|
|
65
|
+
|
|
66
|
+
MIT
|
package/README.md
CHANGED
|
@@ -1,3 +1,212 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-install-radar · 会话安装雷达
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
追踪「每个 DSH 会话到底往这台电脑里装了什么」,并给出**体积 / 安装位置 / 创建时间 / 用途 / 命令证据**。
|
|
4
|
+
|
|
5
|
+
三路证据互补——漏掉任何一路都会出现「明明少了 20G,插件却说没事」:
|
|
6
|
+
|
|
7
|
+
| 来源 | 能回答什么 | 边界 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| **会话日志归因**(`$DSH_HOME/sessions/**/session.v*.jsonl.zstd` 里的 `tool/call`) | 哪个会话、哪一轮、哪条命令装了 X,落在哪、多大、干什么用 | 只能看到命令痕迹;`$变量` 目标、远程脚本内部行为看不到 |
|
|
10
|
+
| **目录快照对比**(基线 vs 现在) | 已知监视根里凭空多出来的条目、明显变大的目录 | 需要先立基线;监视根以外的目录看不见 |
|
|
11
|
+
| **时间戳扫描**(`今天` 栏) | 「今天/近 N 天,哪个目录在吃盘」——包括临时目录、其它工具偷偷写的 | 只看时间窗口,不判断「该不该存在」 |
|
|
12
|
+
|
|
13
|
+
## v0.12 变更
|
|
14
|
+
|
|
15
|
+
- **条目分卡**:展开一个会话后,每条安装记录都是一张独立小卡(交替底色 + 左侧 3px 色条 + 8px 间距),相邻两行不再糊成一片。
|
|
16
|
+
- **「为什么装」**:每条记录多一行 `🎯 为什么装:…`。来源优先级:
|
|
17
|
+
1. agent 自己给这条命令写的说明(`tool/call` 的 `description`)
|
|
18
|
+
2. 它当轮推理/说明里最贴近的那一句(命中包名或「安装/依赖/SDK/clone」等关键词)
|
|
19
|
+
3. 最后退回用户那句原话
|
|
20
|
+
本机实测 8 条全部命中 agent 说明,例如「安装并验证腾讯云 ai3d SDK」「克隆 Hunyuan3D-2 仓库」「安装 Inno Setup 打包工具」。
|
|
21
|
+
- 工具输出(`install_radar_report` / `install_radar_sessions`)也在每条下面带 `为什么装: …`。
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
- **清理后不再重新拉取**:删完只把「真的删掉的行」从本地列表里摘掉——列表顺序、滚动位置、其它行的勾选状态全部原地保留。合计体积/文件数、`可清理 N / 总数`、两个全选按钮的数字都跟着本地扣减,不再触发一次全量重扫。
|
|
25
|
+
- 通配行(`C:\Windows\Temp\cdp-*`)只要有一个子目录删成功就整行摘掉;删失败(被占用等)的行会留在原地,并保留它的勾选状态。
|
|
26
|
+
- 单条清理与批量清理走同一套逻辑(`filterCleaned()`,纯函数,测试里直接断言)。
|
|
27
|
+
- 需要真正的全量刷新时仍可按「重扫」。
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
- **选择栏吸附**:`可清理 N / 总数 · 全选 · 清空 · 删除选中 N · X GB` 这一条改成 `position: sticky`,吸附在列表滚动区顶部;滚到最下面勾完,直接点上面的按钮,不用再滚回顶部。按钮组靠右、计数靠左(`flex:1` 撑开),确认态在同一位置整条变红,不再单独出现在列表里。
|
|
31
|
+
- **两档全选**:`🟢 只选安全 9` 只勾 `risk === 'safe'` 的(cdp-*、HeadlessChrome*、pip-unpack-*、安装包…);`🟢🟡 全选(含需注意) 12` 把 caution 也一起勾上(用户 Temp 下的普通目录之类)。两个按钮都带数量,一眼知道各自会勾多少。
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
- **定时自动清理(默认关)**:面板 `非会话新增` 顶部一行「定时自动清理 ⚪关 / 🟢开 + 超过 7 天 / 14 / 30 天」。开启后宿主每 90 秒采样时顺带执行:只清 **🟢 safe 且创建时间早于 N 天** 的临时垃圾(`cdp-*`、`HeadlessChrome*`、`pip-unpack-*`、安装包),仍然走同一套白名单与 `risk` 判定,每条都写 `clean-log.jsonl`(note=`auto(>Nd, safe)`)。设置存在 `$DSH_HOME/install-radar/config.json` 的 `autoClean` 里。
|
|
35
|
+
- 只扫临时目录白名单的**一级子项**,不递归乱翻;被占用的删不掉就跳过,下一轮再试。
|
|
36
|
+
- `install_radar_autoclean` 工具可以开关、改天数、`dryRun` 预览。
|
|
37
|
+
- 自测:`node test/autoclean-check.mjs` —— 沙箱里造 60 MB / 1 KB / `.pnpm-store` 三个目录,验证「够大够老才删、小的不删、danger 不删」。
|
|
38
|
+
- **增长趋势**:`非会话新增` 顶部 14 天按天柱状图(按文件 `max(mtime, birthtime)` 分桶,鼠标悬停看当天体积与文件数),一眼看出哪几天堆得最快。走 `/api/install-radar/trend`,进程内缓存 5 分钟。
|
|
39
|
+
- 本机实测:14 天合计 7.0 GB,峰值 2026-11-08 = 4.9 GB(就是 cdp-* 堆得最凶那天)。
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
- **批量删除(多选)**:`非会话新增` 每行前面多了勾选框(只有「可一键清理」的行能勾:≥50 MB 且不是 danger),顶部一条操作栏——`可清理 N / 总数`、`全选可清理`、`清空`、`删除选中 N 项 · X GB`。点删除先出确认条(🔴 确认删除以下 N 项,共 X GB?),第二下才真删;服务端一次最多处理 50 条,删完写 `clean-log.jsonl`。
|
|
43
|
+
- **三色警告**:所有后果标注改成 emoji 前置——🟢 不影响 / 🟡 需注意 / 🔴 不要删;不能一键删的行(风险高或 <50 MB)不显示勾选框,改用 🔴 标记。会话条目的「卸载后果」也带同样的三色。
|
|
44
|
+
- **复现线索的回退**:临时目录被清掉以后,`install_radar_repro` 不再空转——没有遗留目录时自动退回到「浏览器命令最多的那一轮会话」,并在界面标明是按哪种方式挑的(`pickedBy: history | commands`)。
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
- **复现线索**:`install_radar_repro` 从「历史侧写排名第一的那一轮」里把驱动脚本揪出来——收集会话里出现过的脚本名 + 会话命令里 `cd/Set-Location/New-Item` 提到的目录作为候选基,解析成绝对路径(解析不到的浅层按文件名找),读内容找 CDP 特征,再判断它**有没有清理临时 profile**;没有的给对应语言的清理钩子(Node / Python / PowerShell)。没有脚本文件、直接在命令里拉起浏览器的,单独给「内联驱动」结论。
|
|
48
|
+
- 本机实测:714 条命令 → 6 个候选,第一候选 `E:\3D3\douyin-bridge.mjs`;漏得最狠的是 `E:\大肥鱼\tools\eval.mjs`(硬编码 `C:\Windows\Temp\cdp-eval-${PORT}`,无清理)与 `seq.mjs` / `poses.mjs`(各 142 / 17 次)。
|
|
49
|
+
- **两条页签上色**:`会话安装` 蓝、`非会话新增` 橙,未选中也有同色描边与圆点,选中态填色 + 光晕,不用猜现在在哪一栏。
|
|
50
|
+
- **会话条目也有卸载后果**:每个会话装的条目下面多一行「卸载后果」——`danger`(git 仓库删了整个目录没、node_modules 影响项目跑不起来)、`caution`(pip 包/全局命令,重装可恢复)、`safe`(安装包/下载文件)。
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
- **历史侧写(补过去)**:`install_radar_history` 把磁盘上遗留的 `cdp-*` 目录按**创建时间**,与会话日志里当时执行的 shell 命令对表(±20 分钟窗口)。命中浏览器特征(chrome / cdp / playwright / puppeteer / 截图 / 浏览器…)给「高概率」,只有普通命令给「中等」,窗口内没命令则判「非会话」。**只给概率与证据,不写死结论。**
|
|
54
|
+
- 本机实测:303 个遗留目录 / 17.7 GB → 298 次对上一个会话,157 次高置信,证据是该会话在反复定位 `chrome.exe`。
|
|
55
|
+
- **清理门槛 50 MB**:小于 50 MB 的行不出现「清理」按钮,避免把正在用的临时文件删掉;`danger` 级(`.pnpm-store`、`Windows\Installer`、`site-packages`、浏览器 `User Data`…)一律不给一键删。
|
|
56
|
+
- **后果标注**:`非会话新增` 每行下面一行彩色结论——`不影响 / 需注意 / 不要删` + 具体后果(例:`无影响:浏览器自动化每次都会新建 profile,下次运行会重建`;`会破坏已装依赖:pnpm 的硬链接源,删了现有 node_modules 变断链`)。确认框里同样带这条。
|
|
57
|
+
- **采样器扩容 + 因果表**:不只认 CDP,还认临时 `--user-data-dir`、`pip install`、`npm/pnpm install`、模型下载、构建、ffmpeg。Chromium 的 GPU/渲染/crashpad 子进程被排除(否则全是噪音),`node/python` 脚本按进程名收紧。面板输出「发起者 → 类别 → 次数 → 关联路径」,并把路径与磁盘占用对账。
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
- **自动基线**:没有基线、或监视根变了(新增/删除目录)时,宿主自己重立,不用再点按钮。报告里带 `baselineAuto`(`first-run` / `roots-changed` / `null`)。
|
|
61
|
+
- **两类分开**:顶层只有两个页签——`会话安装`(蓝)与 `非会话新增`(橙)。
|
|
62
|
+
- 会话安装下面三个子页签:按会话 / 按类型 / 按时间
|
|
63
|
+
- 非会话新增里三块:时间戳扫描(1/3/7 天)、谁在拉起 CDP、基线以来新条目
|
|
64
|
+
- **颜色分层**:会话卡片交替底色 + 左侧蓝色条,展开后的条目再缩进一层、淡蓝底;非会话块一律橙色左边条,一眼分清「这是会话装的」还是「机器上长出来的」。
|
|
65
|
+
- **进程命令行审计**:`install_radar_procs` 抓 `Win32_Process` 完整命令行,命中 `--remote-debugging-port` / `--user-data-dir=*cdp-*` 的进程会被还原出父进程链;宿主每 90 秒自动采一次,落 `cdp-procs.jsonl`,面板按发起者聚合次数。
|
|
66
|
+
- **一键清理(二段式)**:`非会话新增` 每行有「清理」,第一下只弹确认行(显示路径与体积),第二下才真删;服务端还有一层白名单(只允许系统临时目录 / 用户临时目录 / `C:\Temp` / `TMP`),白名单外的路径直接拒绝,删完写 `clean-log.jsonl`。
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
- **新增「今天」栏**:按文件系统时间戳扫系统临时目录、用户临时目录、Local、Roaming、下载、桌面、C 盘根;同名数字后缀的兄弟目录自动合并(`C:\Windows\Temp\cdp-seq-9667…` → `cdp-seq*`),直接给出「17 GB 全是 cdp-*」这种结论。可切 1 / 3 / 7 天。
|
|
70
|
+
- **修掉漏检**:旧版只盯 `Program Files`、`%LOCALAPPDATA%\Programs` 等少数根,`C:\Windows\Temp`、`%LOCALAPPDATA%`、`C:\Windows\Installer`、`%ProgramData%`、用户主目录全都不在名单里——所以 17.7 GB 的临时目录堆了三天,报告一声不吭。现在这些根都进了默认监视列表。
|
|
71
|
+
- **面板默认全部折叠**,想看哪个点哪个。
|
|
72
|
+
- **一键打开文件夹**:每个条目除了「复制路径」还有「打开文件夹」(文件用 `explorer /select,` 选中,目录直接打开)。
|
|
73
|
+
- **会话消失审计**:`install_radar_audit` 给 `sessions/` 建索引并比对,消失的会话记进 `session-audit.jsonl`(谁、何时、消失前多大)。
|
|
74
|
+
- **用途兜底改成读签名**:知识库没收录时,去读目录里的 `package.json` / `*.dist-info/METADATA` / `README` / `.git/config`,把「这是什么」写在报告里;不是瞎猜,也不发网络请求。
|
|
75
|
+
|
|
76
|
+
## 安装
|
|
77
|
+
|
|
78
|
+
标准装法(需要一次重启):
|
|
79
|
+
|
|
80
|
+
```powershell
|
|
81
|
+
dsh plugin --profile desktop add link:"E:\DeepSeek Harness工作区\dsh-install-radar"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
本机实际装法是手工落地的:
|
|
85
|
+
|
|
86
|
+
1. `profiles/desktop/node_modules/dsh-install-radar` → 指向本目录的 **junction**
|
|
87
|
+
2. `profiles/desktop/package.json`:`dependencies["dsh-install-radar"] = "link:…"`,并把 `dsh-install-radar` 追加进 `dsh.profile.bundles`
|
|
88
|
+
|
|
89
|
+
回滚:删掉 junction、用 `package.json.bak-radar-*` 覆盖回去、重启。
|
|
90
|
+
|
|
91
|
+
### 顺手修掉的坑:`pnpm add` 不再 404
|
|
92
|
+
|
|
93
|
+
原来 profile 里 `dsh-purge` 指向的 GitHub 归档(`codeload.github.com/YuJunZhiXue/dsh-purge/tar.gz/refs/heads/master`)已经 404,任何 `pnpm add` 都会整树重解析失败,还可能把 `link:` 依赖冲掉。
|
|
94
|
+
|
|
95
|
+
修法(不动插件行为,只换来源):把已装的 **dsh-purge 1.1.92** 原样收进 `profiles/desktop/vendor/dsh-purge`,依赖改成 `"dsh-purge": "file:vendor/dsh-purge"`,然后在隔离目录用捆绑 pnpm 跑 `install --lockfile-only` 验证(253 个包,`exit 0`),最后同步真实 profile 的锁文件。
|
|
96
|
+
|
|
97
|
+
npm 上的 `dsh-purge` 只有占位版 `0.0.0-stage`,不能顶替;市场里的同名分叉会改变行为,所以选择 vendoring。上游恢复后改回 `github:<owner>/dsh-purge` 再 `pnpm install` 即可。
|
|
98
|
+
|
|
99
|
+
彻底对齐(可选,建议关掉 DSH 再跑):
|
|
100
|
+
|
|
101
|
+
```powershell
|
|
102
|
+
node "E:\新建文件夹 (2)\resources\runtime\primary-runtime\dependencies\pnpm\bin\pnpm.cjs" install `
|
|
103
|
+
--dir "E:\DeepSeek Harness\shuju\profiles\desktop"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## 侧栏面板
|
|
107
|
+
|
|
108
|
+
| 项 | 值 |
|
|
109
|
+
| --- | --- |
|
|
110
|
+
| 插槽 | `sidebar.panellist`(root 作用域 list,一行一个图标)+ `main`(root 作用域 keyed,key = `install-radar`) |
|
|
111
|
+
| 位置 | `order: -20`,排在官方面板行之前,也就是**工作区之上** |
|
|
112
|
+
| 图标 | 16/18px 盒子轮廓 + 中心点,跟主题同色(`currentColor`) |
|
|
113
|
+
| 数据 | 只走宿主路由 `/api/install-radar/{report,refresh,sessions,baseline,diff,recent,open}`(`ctx.connection.rpc.call`) |
|
|
114
|
+
|
|
115
|
+
四个视图:
|
|
116
|
+
|
|
117
|
+
- `按会话` —— 谁装的,默认全部折叠,点开才展开
|
|
118
|
+
- `按类型` —— Python 包 / Node 包 / 代码仓库 / winget 程序…按体积降序,带头部占比条
|
|
119
|
+
- `按时间` —— 按 `YYYY-MM` 分组
|
|
120
|
+
- `今天` —— 时间戳扫描结果,1 / 3 / 7 天可切;每行一个归属目录,附示例路径
|
|
121
|
+
|
|
122
|
+
每个条目行尾:`复制路径` / `打开文件夹` / `卸载命令`(只复制,不执行)。
|
|
123
|
+
|
|
124
|
+
## Agent 工具
|
|
125
|
+
|
|
126
|
+
- `install_radar_report` —— 完整报告,并写 `$DSH_HOME/install-radar/report.md`
|
|
127
|
+
- `install_radar_sessions` —— 只看会话命令证据,可按关键词过滤
|
|
128
|
+
- `install_radar_recent` —— 「今天/近 N 天谁在吃盘」,按归属目录聚合
|
|
129
|
+
- `install_radar_baseline` —— 立基线(装东西之前跑一次)
|
|
130
|
+
- `install_radar_diff` —— 基线以来新出现 / 明显变大的目录
|
|
131
|
+
- `install_radar_audit` —— 会话目录索引与消失审计
|
|
132
|
+
|
|
133
|
+
## 不进 DSH 也能跑(CLI)
|
|
134
|
+
|
|
135
|
+
```powershell
|
|
136
|
+
node bin/radar.mjs roots # 看会扫哪些根
|
|
137
|
+
node bin/radar.mjs baseline # 立基线
|
|
138
|
+
node bin/radar.mjs scan # 扫现状(最大的 15 项)
|
|
139
|
+
node bin/radar.mjs sessions # 历史会话装了啥
|
|
140
|
+
node bin/radar.mjs recent --days 3 # 近 3 天谁在吃盘
|
|
141
|
+
node bin/radar.mjs diff # 基线以来新增
|
|
142
|
+
node bin/radar.mjs report --out report.md # 完整 Markdown 报告
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
通用参数:`--home <DSH_HOME>`、`--extra <目录>`(可重复)、`--limit <会话数>`、`--days <天数>`、`--json`。
|
|
146
|
+
|
|
147
|
+
## 自测
|
|
148
|
+
|
|
149
|
+
```powershell
|
|
150
|
+
node test/host-smoke.mjs # 6 个工具 + 7 条路由 + report 路由真跑一遍(会扫盘)
|
|
151
|
+
node test/client-smoke.mjs # 不开浏览器:加载 client bundle、断言两个插槽注册、构造元素树(含默认折叠)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 认哪些安装方式
|
|
155
|
+
|
|
156
|
+
`pip / python -m pip / uv add`、`npm / pnpm / yarn / bun`、`git clone`、`winget`、`choco / scoop`、`cargo install`、`go install`、`docker pull`、`huggingface-cli download`、`python -m venv`、`conda create`、`Invoke-WebRequest/curl -OutFile`、`irm | iex` 远程脚本、静默安装器(`setup.exe /S`)。
|
|
157
|
+
|
|
158
|
+
## 落到路径的规则(逐级退化)
|
|
159
|
+
|
|
160
|
+
1. 生态约定路径:`site-packages/<包>`、`node_modules/<包>`、`WinGet\Packages/<id>*`、`chocolatey\lib/<包>`
|
|
161
|
+
2. 发行名 ≠ import 名时按前缀退化(`tencentcloud-sdk-python-ai3d` → `tencentcloud`)
|
|
162
|
+
3. 注册表 `Uninstall` 清单(DisplayName / InstallLocation / EstimatedSize)——GUI 程序与安装器靠这个
|
|
163
|
+
4. 命令里的 `cd` / `New-Item` 提示目录(`cd E:\3DAI; git clone …` → 落点按 E:\3DAI 算)
|
|
164
|
+
5. 快照里叶名严格同名
|
|
165
|
+
6. 都失败 → 明确标 `[未找到产物]`,不猜
|
|
166
|
+
|
|
167
|
+
## 体积怎么算
|
|
168
|
+
|
|
169
|
+
递归求和,跳过符号链接与 junction(Windows 上是 reparse point,会成环),单条目最多数 40 万个文件,超了标 `truncated`。
|
|
170
|
+
|
|
171
|
+
量过的大目录进 `$DSH_HOME/install-radar/size-cache.json`(30 分钟有效,目录 mtime 变了重算);快照进程内缓存 10 分钟,面板数据再叠 45 秒缓存。
|
|
172
|
+
|
|
173
|
+
## 目录
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
src/engine/roots.js 监视根定义(含「今天」专用根)
|
|
177
|
+
src/engine/scan.js 递归体积、并发池
|
|
178
|
+
src/engine/recent.js 时间戳扫描 + 同名前缀合并
|
|
179
|
+
src/engine/sessions.js zstd 逐帧解压 → 抽 tool/call
|
|
180
|
+
src/engine/commands.js 命令 → 安装事件(含 cd/New-Item 落点提示)
|
|
181
|
+
src/engine/resolve.js 安装事件 → 磁盘路径 + 体积
|
|
182
|
+
src/engine/registry.js Windows 已安装程序清单
|
|
183
|
+
src/engine/audit.js 会话目录索引 / 消失审计
|
|
184
|
+
src/engine/signature.js 目录签名推断(package.json / METADATA / README)
|
|
185
|
+
src/engine/sizecache.js 体积缓存
|
|
186
|
+
src/engine/classify.js 用途知识库 + 体积格式化
|
|
187
|
+
src/engine/uninstall.js 卸载命令生成
|
|
188
|
+
src/engine/radar.js 编排、基线、diff、recent、Markdown
|
|
189
|
+
src/index.js Cordis 宿主:6 个工具 + 7 条面板路由
|
|
190
|
+
src/client.js 浏览器半:侧栏图标 + 主面板(四栏 + 三个按钮)
|
|
191
|
+
bin/radar.mjs CLI
|
|
192
|
+
tools/fix-profile-deps.cjs 把 dsh-purge 依赖改到 vendor 目录
|
|
193
|
+
test/ 两端冒烟测试
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## 发布
|
|
197
|
+
|
|
198
|
+
`publish/` 里是发布用的脚本与市场条目模板,见 [publish/README.md](publish/README.md):
|
|
199
|
+
|
|
200
|
+
```powershell
|
|
201
|
+
pwsh -File publish\npm-publish.ps1 # 发 npm(需先 npm login)
|
|
202
|
+
node publish\github-repo.mjs --user <你> # 建 GitHub 仓库(需 GITHUB_TOKEN)
|
|
203
|
+
node publish\market-pr.mjs --user <你> # 提市场 PR(只加一个 yml)
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## 已知边界
|
|
207
|
+
|
|
208
|
+
- 会话日志是 zstd 多帧格式,必须逐帧解压;正在写入的尾帧可能解不开(跳过,不影响其余)。
|
|
209
|
+
- `git clone` 到 `$t` 这类临时变量、以及远程脚本内部再装的包,只能看到命令、看不到产物。
|
|
210
|
+
- conda 环境整体算一个体积单元,不拆到包级。
|
|
211
|
+
- 「今天」扫描默认只看交互用户能读到的目录,`C:\Windows\WinSxS`、回收站等被显式跳过。
|
|
212
|
+
- 基线扩容后第一次 `diff` 会把新纳入的根当成「新出现」,重新点一次「立基线」即可清干净。
|
package/bin/radar.mjs
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* install-radar CLI —— 不依赖 DSH 也能跑:
|
|
4
|
+
* node bin/radar.mjs baseline 取基线(之后新装的都能被 diff 出来)
|
|
5
|
+
* node bin/radar.mjs scan 扫一遍现状(体积/路径/创建时间)
|
|
6
|
+
* node bin/radar.mjs sessions 只看会话命令证据(找出历史上装了什么)
|
|
7
|
+
* node bin/radar.mjs report [--out] 生成完整报告
|
|
8
|
+
* node bin/radar.mjs roots 看会扫哪些根目录
|
|
9
|
+
* 通用参数:--home <DSH_HOME> --extra <路径> --limit <会话数> --json
|
|
10
|
+
*/
|
|
11
|
+
import { mkdir, writeFile } from 'node:fs/promises'
|
|
12
|
+
import { dirname, resolve } from 'node:path'
|
|
13
|
+
import { buildRecent, buildReport, currentSnapshot, dshHome, readBaseline, renderMarkdown, takeBaseline, stateDir } from '../src/engine/radar.js'
|
|
14
|
+
import { resolveRoots } from '../src/engine/roots.js'
|
|
15
|
+
import { formatBytes } from '../src/engine/classify.js'
|
|
16
|
+
|
|
17
|
+
const argv = process.argv.slice(2)
|
|
18
|
+
const command = argv[0] || 'report'
|
|
19
|
+
const flag = (name, fallback) => {
|
|
20
|
+
const index = argv.indexOf(`--${name}`)
|
|
21
|
+
return index >= 0 && argv[index + 1] ? argv[index + 1] : fallback
|
|
22
|
+
}
|
|
23
|
+
const has = (name) => argv.includes(`--${name}`)
|
|
24
|
+
const home = flag('home', dshHome())
|
|
25
|
+
const extraRoots = []
|
|
26
|
+
for (let i = 0; i < argv.length; i++) if (argv[i] === '--extra' && argv[i + 1]) extraRoots.push(argv[i + 1])
|
|
27
|
+
const config = extraRoots.length > 0 ? { extraRoots } : {}
|
|
28
|
+
const limit = Number(flag('limit', '0')) || 0
|
|
29
|
+
const json = has('json')
|
|
30
|
+
|
|
31
|
+
function out(value) {
|
|
32
|
+
process.stdout.write(`${typeof value === 'string' ? value : JSON.stringify(value, null, 2)}\n`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (command) {
|
|
36
|
+
case 'roots': {
|
|
37
|
+
const roots = resolveRoots(config)
|
|
38
|
+
out({ home, stateDir: stateDir(home), roots })
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
case 'baseline': {
|
|
42
|
+
const t0 = Date.now()
|
|
43
|
+
const baseline = await takeBaseline({ home, config })
|
|
44
|
+
const bytes = baseline.entries.reduce((sum, entry) => sum + entry.bytes, 0)
|
|
45
|
+
out(
|
|
46
|
+
json
|
|
47
|
+
? baseline
|
|
48
|
+
: `基线已建立:${baseline.entries.length} 个条目,合计 ${formatBytes(bytes)},用时 ${((Date.now() - t0) / 1000).toFixed(1)}s\n写入 ${stateDir(home)}\\baseline.json`,
|
|
49
|
+
)
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
case 'scan': {
|
|
53
|
+
const snapshot = await currentSnapshot({ home, config, force: true })
|
|
54
|
+
const bytes = snapshot.entries.reduce((sum, entry) => sum + entry.bytes, 0)
|
|
55
|
+
if (json) out(snapshot)
|
|
56
|
+
else {
|
|
57
|
+
out(`${snapshot.entries.length} 个条目,合计 ${formatBytes(bytes)}`)
|
|
58
|
+
for (const root of snapshot.roots) out(` ${root.label}: ${root.entries} 条 / ${formatBytes(root.bytes)} / ${root.ms}ms`)
|
|
59
|
+
const top = snapshot.entries.slice(0, 15)
|
|
60
|
+
out('\n最大的 15 项:')
|
|
61
|
+
for (const entry of top) out(` ${formatBytes(entry.bytes).padStart(9)} ${entry.name} (${entry.path})`)
|
|
62
|
+
}
|
|
63
|
+
break
|
|
64
|
+
}
|
|
65
|
+
case 'sessions': {
|
|
66
|
+
const report = await buildReport({ home, config, maxSessions: limit })
|
|
67
|
+
if (json) out(report.perSession)
|
|
68
|
+
else {
|
|
69
|
+
out(`含安装命令的会话:${report.perSession.length} / 扫描 ${report.scannedSessions}`)
|
|
70
|
+
for (const session of report.perSession) {
|
|
71
|
+
out(`\n== ${session.title || session.id} (${session.items.length} 项, ${formatBytes(session.bytes)})`)
|
|
72
|
+
for (const item of session.items) out(` ${item.name.padEnd(28)} ${item.size.padStart(9)} ${item.path || '未找到产物'} — ${item.purpose}`)
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
case 'diff': {
|
|
78
|
+
const baseline = await readBaseline(home)
|
|
79
|
+
const snapshot = await currentSnapshot({ home, config, force: true })
|
|
80
|
+
const { diffSnapshot } = await import('../src/engine/radar.js')
|
|
81
|
+
const delta = diffSnapshot(baseline, snapshot)
|
|
82
|
+
if (json) out(delta)
|
|
83
|
+
else {
|
|
84
|
+
out(`基线:${baseline ? new Date(baseline.takenAt).toLocaleString('sv-SE') : '无'}`)
|
|
85
|
+
out(`新出现:${delta.newEntries.length} 项`)
|
|
86
|
+
for (const entry of delta.newEntries.slice(0, 40)) out(` + ${formatBytes(entry.bytes).padStart(9)} ${entry.name} ${entry.path}`)
|
|
87
|
+
out(`明显变大:${delta.grownEntries.length} 项`)
|
|
88
|
+
for (const entry of delta.grownEntries.slice(0, 40)) out(` ↑ +${formatBytes(entry.grew).padStart(8)} ${entry.name} ${entry.path}`)
|
|
89
|
+
}
|
|
90
|
+
break
|
|
91
|
+
}
|
|
92
|
+
case 'recent': {
|
|
93
|
+
const days = Number(flag('days', '0')) || 0
|
|
94
|
+
const sinceFlag = Number(flag('since', '0')) || 0
|
|
95
|
+
const since = sinceFlag > 0 ? sinceFlag : days > 0 ? Date.now() - days * 86400_000 : undefined
|
|
96
|
+
const recent = await buildRecent({ home, config, since })
|
|
97
|
+
if (json) out(recent)
|
|
98
|
+
else {
|
|
99
|
+
out(`统计起点:${recent.sinceLabel} 合计 ${recent.totalSize} / ${recent.totalFiles} 个文件`)
|
|
100
|
+
out(`扫描根:${recent.roots.map((root) => `${root.label}(${root.ms}ms)`).join(' · ')}`)
|
|
101
|
+
for (const item of recent.items.slice(0, 25)) {
|
|
102
|
+
out('')
|
|
103
|
+
out(`${item.size.padStart(10)} ${item.path}`)
|
|
104
|
+
out(` ${item.purpose}`)
|
|
105
|
+
if (item.samples && item.samples[0]) out(` 示例 ${item.samples[0]}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
break
|
|
109
|
+
}
|
|
110
|
+
case 'report':
|
|
111
|
+
default: {
|
|
112
|
+
const report = await buildReport({ home, config, maxSessions: limit })
|
|
113
|
+
const markdown = renderMarkdown(report)
|
|
114
|
+
const target = flag('out', '')
|
|
115
|
+
if (target) {
|
|
116
|
+
const file = resolve(target)
|
|
117
|
+
await mkdir(dirname(file), { recursive: true })
|
|
118
|
+
await writeFile(file, markdown, 'utf8')
|
|
119
|
+
out(`已写入 ${file}`)
|
|
120
|
+
out(`会话 ${report.totals.sessions} · 条目 ${report.totals.items} · 合计 ${report.totals.size}`)
|
|
121
|
+
} else if (json) out(report)
|
|
122
|
+
else out(markdown)
|
|
123
|
+
}
|
|
124
|
+
}
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# dsh-install-radar bundle patch: 注册插件行。
|
|
2
|
+
# 宿主半部分注册四个工具(report / sessions / baseline / diff),无客户端界面,
|
|
3
|
+
# 不改动任何官方 UI 与配置。
|
|
4
|
+
- insert:
|
|
5
|
+
- id: install-radar
|
|
6
|
+
name: 'dsh-install-radar'
|
|
7
|
+
config:
|
|
8
|
+
# 额外要盯的目录(例如 E:\AI、D:\ComfyUI)
|
|
9
|
+
extraRoots: []
|
|
10
|
+
# 是否使用内置默认根(Program Files / winget / npm 全局 / pip 用户包 / conda …)
|
|
11
|
+
includeDefaults: true
|
|
12
|
+
# 会话扫描上限,0 = 全部
|
|
13
|
+
maxSessions: 0
|
package/package.json
CHANGED
|
@@ -1,6 +1,75 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-install-radar",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Per-session install radar for DeepSeek Harness: what each conversation installed (Python / npm / git / winget), with size, path, created time, purpose, why it was installed and what removing it costs — plus a sidebar panel and process→disk causal audit.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./src/index.js",
|
|
9
|
+
"./client": "./src/client.js",
|
|
10
|
+
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"bin": {
|
|
14
|
+
"install-radar": "bin/radar.mjs"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"src",
|
|
18
|
+
"bin",
|
|
19
|
+
"cordis.patch.yml",
|
|
20
|
+
"README.md",
|
|
21
|
+
"README.en.md",
|
|
22
|
+
"LICENSE"
|
|
23
|
+
],
|
|
24
|
+
"keywords": [
|
|
25
|
+
"dsh",
|
|
26
|
+
"dsh-plugin",
|
|
27
|
+
"deepseek-harness",
|
|
28
|
+
"install-audit",
|
|
29
|
+
"inventory",
|
|
30
|
+
"disk-usage",
|
|
31
|
+
"session-tools",
|
|
32
|
+
"sidebar-panel"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"author": "YG",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"registry": "https://registry.npmjs.org",
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
43
|
+
"dsh": {
|
|
44
|
+
"engines": {
|
|
45
|
+
"dsh": ">=0.2.0-rc.1"
|
|
46
|
+
},
|
|
47
|
+
"bundle": {
|
|
48
|
+
"patch": "./cordis.patch.yml"
|
|
49
|
+
},
|
|
50
|
+
"client": {
|
|
51
|
+
"platform": "web",
|
|
52
|
+
"inject": [
|
|
53
|
+
"@deepseek-ai/dsh-client-connection",
|
|
54
|
+
"@deepseek-ai/dsh-client-ui-slots"
|
|
55
|
+
]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"peerDependencies": {
|
|
59
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
60
|
+
"@deepseek-ai/dsh-tools": ">=0.2.0-rc.1"
|
|
61
|
+
},
|
|
62
|
+
"peerDependenciesMeta": {
|
|
63
|
+
"@deepseek-ai/dsh-tools": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"repository": {
|
|
68
|
+
"type": "git",
|
|
69
|
+
"url": "git+https://github.com/cccc11f3/dsh-install-radar.git"
|
|
70
|
+
},
|
|
71
|
+
"homepage": "https://github.com/cccc11f3/dsh-install-radar",
|
|
72
|
+
"bugs": {
|
|
73
|
+
"url": "https://github.com/cccc11f3/dsh-install-radar/issues"
|
|
74
|
+
}
|
|
75
|
+
}
|