dsh-app-updater 1.3.2 → 1.3.4

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/README.en.md CHANGED
@@ -1,240 +1,244 @@
1
- # dsh-app-updater — App Updater for DeepSeek Harness
2
-
3
- **English** | [简体中文](README.md)
4
- [![npm](https://img.shields.io/npm/v/dsh-app-updater?style=flat-square&label=npm&color=0891b2&logo=npm)](https://www.npmjs.com/package/dsh-app-updater)
5
- ![license](https://img.shields.io/badge/license-MIT-2f6f4f?style=flat-square)
6
- ![platform](https://img.shields.io/badge/platform-Windows-7da1de?style=flat-square)
7
- ![dsh-plugin](https://img.shields.io/badge/DSH-plugin-263146?style=flat-square)
8
-
9
-
10
- Detect every installed application that has a newer version, then update them
11
- with one click — downloading the **full installer from each vendor's own website** and
12
- installing it silently. **The detected applications are never launched.**
13
- Windows (winget) and macOS (Homebrew), architecture-aware: ARM64 and x64/Intel installers are
14
- chosen to match the machine.
15
-
16
- Where to find it: DeepSeek Harness → Settings → **应用更新 (App Updater)**
17
-
18
- ## Install
19
-
20
- ```bash
21
- # npm package (recommended; also one-click installable from the plugin center)
22
- dsh plugin --profile web add dsh-app-updater
23
- ```
24
-
25
- Without npm, install from source or from the prebuilt tarball:
26
-
27
- ```bash
28
- # source repository
29
- dsh plugin --profile web add github:19952730389/dsh-app-updater
30
-
31
- # prebuilt tgz (see Releases)
32
- dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
33
- ```
34
-
35
- Restart DSH afterwards; the plugin lives at **Settings → 应用更新**. The Electron-managed
36
- `desktop` profile does not accept `dsh plugin` writes, so link the package into that
37
- profile's `node_modules` and add it to `dsh.profile.bundles` manually.
38
-
39
- ---
40
-
41
- ## What it actually does
42
-
43
- | Step | How |
44
- |---|---|
45
- | 1. List installed apps | `winget list` (apps known to the winget catalog plus Store/MSIX packages) **and** the three Windows registry `Uninstall` keys (apps the catalog does not know). Read-only: no application is ever started. |
46
- | 2. Decide whether an update exists | `winget upgrade --include-unknown` gives "installed version → available version" from the official winget catalog, merged into the inventory by package id. |
47
- | 3. One-click update | `winget download` fetches the **vendor's full installer** from the URL recorded in the winget manifest (SHA-256 verified), the downloaded manifest supplies the publisher's own `InstallerSwitches.Silent`, the installer runs silently, and the resulting version is re-read with `winget list --id`. |
48
- | 4. Fallback | If the vendor re-signed the installer (hash no longer matches the catalog) or the manifest forbids direct download, it falls back to `winget upgrade --id … --exact --silent` — still the full installer from the vendor, with winget doing the fetching and installing. |
49
-
50
- Those download URLs really are the vendors' own: `github.com/git-for-windows/…`,
51
- `dl.google.com/dl/chrome/install/…`, `download.cpuid.com/cpu-z/…`, `dl.hdslb.com/…`.
52
- Both the installer and its `.yaml` manifest are kept in the download folder, so the
53
- `InstallerUrl`, `InstallerSha256` and silent switches can be audited afterwards.
54
-
55
- ---
56
-
57
- ## Usage
58
-
59
- 1. Open **Settings → 应用更新**.
60
- 2. Click **刷新检测** (nothing runs in the background; scanning only happens when you ask).
61
- 3. The table sorts updateable apps first. Use the search box and the
62
- **全部 / 仅可更新 / 未收录** segments to narrow it down.
63
- 4. Tick the apps to update (or press **全部更新**), or update a single app from its row.
64
- 5. While updating you get a progress bar, the current app name and a per-item log;
65
- **取消** stops the items that have not started yet.
66
- 6. **打开下载目录** reveals the downloaded vendor installers in Explorer.
67
-
68
- ### Status values
69
-
70
- | Status | Meaning |
71
- |---|---|
72
- | **有新版本** | The catalog reports a higher version; one-click update is available. |
73
- | **已是最新** | No newer version in the catalog. |
74
- | **未收录** | An update exists in the catalog but this plugin cannot drive it (see reasons below). |
75
-
76
- | Reason | Meaning |
77
- |---|---|
78
- | 软件目录未收录 | The app is not in the winget catalog (registry-only software), so its official update cannot be discovered or downloaded. |
79
- | 由应用商店管理 | MSIX / Store app — update it through Microsoft Store. |
80
- | 版本未知 | The installed version cannot be read (winget reports `Unknown`); updating is still allowed. |
81
- | 系统组件 | System component / runtime; not driven by this plugin by default. |
82
-
83
- ---
84
-
85
- ## Settings
86
-
87
- State file: `%USERPROFILE%\.dsh\dsh-app-updater\state.json`
88
-
89
- | Key | Default | Meaning |
90
- |---|---|---|
91
- | `includeStoreApps` | `true` | List store apps too (MSIX on Windows, App Store on macOS; display only). |
92
- | `includeRegistryOnly` | `true` | Windows: fill in apps missing from the winget catalog using the registry (display only). |
93
- | `includeUnmanagedApps` | `true` | macOS: also list apps that Homebrew does not manage (drag-to-Applications installs; display only). |
94
- | `downloadDir` | empty | Custom download folder; empty = `%USERPROFILE%\Downloads\DSH App Updater`. Must be absolute. |
95
- | `keepInstallers` | `true` | Keep the installer after a successful update. |
96
- | `hashFallbackUpgrade` | `true` | Fall back to `winget upgrade` when the download fails. |
97
- | `dryRun` | `false` | **Rehearsal mode**: download the vendor's full installer but never install it. |
98
- | `matchSystemArch` | `true` | Match the installer to the detected system and CPU architecture (Windows passes `--architecture`; macOS is resolved by Homebrew). |
99
- | `autoElevate` | `true` | **Automatic elevation**: install commands that need administrator rights run in an elevated copy (UAC prompts once per install, when UAC prompts at all). |
100
-
101
- Everything above is editable from the plugin's own settings panel inside the settings page.
102
- The panel also accepts API calls:
103
-
104
- ```bash
105
- curl -X POST -H "Content-Type: application/json" \
106
- -d '{"confirm":true,"settings":{"dryRun":true}}' \
107
- http://127.0.0.1:<port>/dsh-app-updater/settings
108
- ```
109
-
110
- ---
111
-
112
- ## Safety boundaries
113
-
114
- - **Every route is deny-by-default**: each request first goes through `ctx.connection.admit(request)`
115
- — DSH's Host/Origin fence plus browser-session / API authentication, the same gate the host uses for
116
- its own `/api` routes. Rejected requests get `401` / `403` / `409` with `X-DSH-Admission: denied`.
117
- - **Legacy loopback mode is off by default** (since 1.3.1): when the host has no `connection`
118
- service, requests are no longer trusted just because they come from loopback — they get `403`.
119
- If you really need it on an old host, opt in explicitly, either via plugin config:
120
-
121
- ```yaml
122
- - id: dsh-app-updater
123
- config:
124
- allowLegacyLoopback: true
125
- ```
126
-
127
- or by exporting `DSH_APP_UPDATER_ALLOW_LEGACY_LOOPBACK=1` before starting DSH. Once enabled, allowed
128
- requests carry `X-DSH-Admission: loopback-legacy-optin` and `X-DSH-Admission-Warning`, the
129
- `admission` field in `state.json` / `health.json` reports `mode: "loopback-legacy-optin"`, the
130
- settings page shows a red banner, and a `warn` line is written to the log.
131
- - **Every write still requires explicit confirmation**: `scan` / `update` / `cancel` /
132
- `settings` / `open-downloads` require `confirm:true`.
133
- - **No guessed silent switches**: the publisher's declared `Silent` switches are preferred;
134
- generic defaults are used only when a manifest is unavailable.
135
- - **Elevation only for installs, and on demand**: install commands that need administrator rights
136
- (MSI packages, installers writing into `Program Files`, the `winget upgrade` fallback) run in an
137
- elevated copy through `Start-Process -Verb RunAs`. With UAC set to "always notify" you get one
138
- prompt per install; with `ConsentPromptBehaviorAdmin = 0` it is completely silent. Reads
139
- (`winget list` / `upgrade --include-unknown` / `--version`) never elevate.
140
- - **A zero exit code is not success**: after installing, the plugin compares the installed version
141
- against the target version and reports a failure when nothing actually changed — machine-scope
142
- installers silently exit 0 without installing anything when they lack administrator rights
143
- (Logitech G HUB used to be reported as "updated" that way).
144
- - **System components** (`.NET`, VC++ runtimes, …) are listed but not updated by default.
145
- - **Only its own directory is written**: state and logs live in `~/.dsh/dsh-app-updater/`.
146
-
147
- ## Requirements
148
-
149
- - **Windows**: `winget.exe` (Microsoft App Installer). Without winget the plugin can still
150
- list registry apps, but cannot tell whether they are up to date.
151
- - **macOS**: no extra dependency is needed to **list** apps (the plugin reads `.app` bundles from
152
- `/Applications` and friends); judging and applying updates needs `brew` ([Homebrew](https://brew.sh)).
153
- Without Homebrew the apps are still listed, with a note that installing Homebrew enables one-click
154
- cask upgrades.
155
- - No third-party npm dependencies: the host half uses Node built-ins only, the browser half
156
- only the host-provided `react`.
157
-
158
- ## Supported systems and architectures
159
-
160
- The plugin detects its own environment at startup and picks the installer that matches it:
161
-
162
- | System | Architecture | Engine | Notes |
163
- |---|---|---|---|
164
- | Windows 10 / 11 | x64 / ARM64 / x86 | **winget** | the detected architecture is passed as `winget --architecture`; if the manifest has no installer for it, the plugin falls back to automatic matching and says so in the log |
165
- | macOS 11+ | Apple Silicon / Intel | **Homebrew casks** | apps are enumerated from `/Applications` and friends, then matched against the cask catalog; Homebrew resolves the right cask for the machine. Without Homebrew the apps are still listed, they just cannot be upgraded in one click |
166
- | anything else | — | none | the scan/update routes answer 400 with the reason |
167
-
168
- The result is shown in the status line and in the settings panel's “运行环境 / Environment” row, e.g.
169
- `Windows 11(build 26300) · x64(AMD64) · winget 1.29.380` or
170
- `macOS 15(Sequoia) · Apple Silicon(ARM64) · Homebrew 4.5.0`.
171
-
172
- The “按本机架构安装包 / match system architecture” switch (on by default) can be turned off to hand
173
- the choice back to winget; Homebrew always resolves for the local architecture.
174
-
175
- > The macOS app enumeration (`/Applications` + `Info.plist`) and cask matching are covered by unit
176
- > tests and were validated against the real `formulae.brew.sh` catalog (7,800+ casks), but the
177
- > macOS **install/update execution** has not been verified on a real Mac — this plugin is developed
178
- > on Windows. Please open an issue if something misbehaves there.
179
- >
180
- > Only the standard application directories are enumerated. Apps installed elsewhere (a custom
181
- > folder, the Desktop, an external volume) are outside the scan scope by design.
182
-
183
- ## HTTP API (host half)
184
-
185
- | Method | Path | Purpose |
186
- |---|---|---|
187
- | GET | `/dsh-app-updater/state.json` | Status, inventory, log, settings |
188
- | GET | `/dsh-app-updater/apps.json` | Inventory and statistics only |
189
- | GET | `/dsh-app-updater/health.json` | Self-check: version, environment, engine, capabilities, route count, data directory |
190
- | POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` start scanning |
191
- | POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` start the update queue |
192
- | POST | `/dsh-app-updater/cancel` | `{confirm:true}` cancel items not yet started |
193
- | POST | `/dsh-app-updater/settings` | `{confirm:true, settings:{...}}` |
194
- | POST | `/dsh-app-updater/open-downloads` | `{confirm:true}` open the download folder |
195
-
196
- ## Install (from a checkout)
197
-
198
- ```bash
199
- # from a local checkout
200
- dsh plugin --profile <profile> add file:/path/to/dsh-app-updater
201
-
202
- # or as a local link
203
- dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
204
- ```
205
-
206
- Then add `dsh-app-updater` to `dsh.profile.bundles` in the profile's `package.json`
207
- (the bundled `cordis.patch.yml` mounts the host half) and reload DSH.
208
-
209
- ## Uninstall
210
-
211
- 1. Remove `dsh-app-updater` from `dsh.profile.bundles` and from `dependencies` in the profile's `package.json`.
212
- 2. Delete `<profile>/node_modules/dsh-app-updater`.
213
- 3. Restart DSH; the data directory `%USERPROFILE%\.dsh\dsh-app-updater` can be removed as well.
214
-
215
- ## Tests
216
-
217
- ```bash
218
- node test/unit.mjs # 152 checks: table parsing, version compare, manifest & switches, OS/arch detection, Homebrew parsing, macOS app enumeration & cask matching
219
- node test/render.mjs # 44 checks: settings panel (per platform), rehearsal banner, column drag, request bodies
220
- node test/deny.mjs # 12 checks: deny-by-default per route when the host has no connection service
221
- node test/harness.mjs --scan # boots the host half against a fake cordis ctx + real HTTP
222
- ```
223
-
224
- ## Layout
225
-
226
- | File | Purpose |
227
- |---|---|
228
- | `lib/index.js` | Host half: HTTP routes, write gate, scan/update scheduling |
229
- | `lib/engine.js` | Inventory merge, vendor installer download, silent install, winget fallback, version re-check |
230
- | `lib/winget.js` | winget discovery/invocation, fixed-width table parsing (CJK-aware) |
231
- | `lib/registry.js` | Registry `Uninstall` enumeration (PowerShell, UTF-8) |
232
- | `lib/version.js` | Windows-style version comparison |
233
- | `lib/state.js` | Settings and state persistence (atomic writes) |
234
- | `lib/proc.js` | Child process runner (timeouts, cancellation, UTF-8) |
235
- | `lib/client.js` | Browser half: the settings page UI |
236
- | `CONTRACT.md` | Host ↔ client interface contract |
237
-
238
- ## License
239
-
1
+ # dsh-app-updater — App Updater for DeepSeek Harness
2
+
3
+ **English** | [简体中文](README.md)
4
+ [![npm](https://img.shields.io/npm/v/dsh-app-updater?style=flat-square&label=npm&color=0891b2&logo=npm)](https://www.npmjs.com/package/dsh-app-updater)
5
+ ![license](https://img.shields.io/badge/license-MIT-2f6f4f?style=flat-square)
6
+ ![platform](https://img.shields.io/badge/platform-Windows-7da1de?style=flat-square)
7
+ ![dsh-plugin](https://img.shields.io/badge/DSH-plugin-263146?style=flat-square)
8
+
9
+
10
+ Detect every installed application that has a newer version, then update them
11
+ with one click — downloading the **full installer from each vendor's own website** and
12
+ installing it silently. **The detected applications are never launched.**
13
+ Windows (winget) and macOS (Homebrew), architecture-aware: ARM64 and x64/Intel installers are
14
+ chosen to match the machine.
15
+
16
+ Where to find it: DeepSeek Harness → Settings → **应用更新 (App Updater)**
17
+
18
+ ## Install
19
+
20
+ ```bash
21
+ # npm package (recommended; also one-click installable from the plugin center)
22
+ dsh plugin --profile web add dsh-app-updater
23
+ ```
24
+
25
+ Without npm, install from source or from the prebuilt tarball:
26
+
27
+ ```bash
28
+ # source repository
29
+ dsh plugin --profile web add github:19952730389/dsh-app-updater
30
+
31
+ # prebuilt tgz (see Releases)
32
+ dsh plugin --profile web add https://github.com/19952730389/dsh-app-updater/releases/latest/download/dsh-app-updater.tgz
33
+ ```
34
+
35
+ Restart DSH afterwards; the plugin lives at **Settings → 应用更新**. The Electron-managed
36
+ `desktop` profile does not accept `dsh plugin` writes, so link the package into that
37
+ profile's `node_modules` and add it to `dsh.profile.bundles` manually.
38
+
39
+ ---
40
+
41
+ ## What it actually does
42
+
43
+ | Step | How |
44
+ |---|---|
45
+ | 1. List installed apps | `winget list` (apps known to the winget catalog plus Store/MSIX packages) **and** the three Windows registry `Uninstall` keys (apps the catalog does not know). Read-only: no application is ever started. |
46
+ | 2. Decide whether an update exists | `winget upgrade --include-unknown` gives "installed version → available version" from the official winget catalog, merged into the inventory by package id. |
47
+ | 3. One-click update | `winget download` fetches the **vendor's full installer** from the URL recorded in the winget manifest (SHA-256 verified), the downloaded manifest supplies the publisher's own `InstallerSwitches.Silent`, the installer runs silently, and the resulting version is re-read with `winget list --id`. |
48
+ | 4. Fallback | If the vendor re-signed the installer (hash no longer matches the catalog) or the manifest forbids direct download, it falls back to `winget upgrade --id … --exact --silent` — still the full installer from the vendor, with winget doing the fetching and installing. |
49
+
50
+ Those download URLs really are the vendors' own: `github.com/git-for-windows/…`,
51
+ `dl.google.com/dl/chrome/install/…`, `download.cpuid.com/cpu-z/…`, `dl.hdslb.com/…`.
52
+ Both the installer and its `.yaml` manifest are kept in the download folder, so the
53
+ `InstallerUrl`, `InstallerSha256` and silent switches can be audited afterwards.
54
+
55
+ ---
56
+
57
+ ## Usage
58
+
59
+ 1. Open **Settings → 应用更新**.
60
+ 2. Click **刷新检测** (nothing runs in the background; scanning only happens when you ask).
61
+ 3. The table sorts updateable apps first. Use the search box and the
62
+ **全部 / 仅可更新 / 未收录** segments to narrow it down.
63
+ 4. Tick the apps to update (or press **全部更新**), or update a single app from its row.
64
+ 5. While updating you get a progress bar, the current app name and a per-item log;
65
+ **取消** stops the items that have not started yet.
66
+ 6. **打开下载目录** reveals the downloaded vendor installers in Explorer.
67
+
68
+ ### Status values
69
+
70
+ | Status | Meaning |
71
+ |---|---|
72
+ | **有新版本** | The catalog reports a higher version; one-click update is available. |
73
+ | **已是最新** | No newer version in the catalog. |
74
+ | **未收录** | An update exists in the catalog but this plugin cannot drive it (see reasons below). |
75
+
76
+ | Reason | Meaning |
77
+ |---|---|
78
+ | 软件目录未收录 | The app is not in the winget catalog (registry-only software), so its official update cannot be discovered or downloaded. |
79
+ | 由应用商店管理 | MSIX / Store app — update it through Microsoft Store. |
80
+ | 版本未知 | The installed version cannot be read (winget reports `Unknown`); updating is still allowed. |
81
+ | 系统组件 | System component / runtime; not driven by this plugin by default. |
82
+
83
+ ---
84
+
85
+ ## Settings
86
+
87
+ State file: `%USERPROFILE%\.dsh\dsh-app-updater\state.json`
88
+
89
+ | Key | Default | Meaning |
90
+ |---|---|---|
91
+ | `includeStoreApps` | `true` | List store apps too (MSIX on Windows, App Store on macOS; display only). |
92
+ | `includeRegistryOnly` | `true` | Windows: fill in apps missing from the winget catalog using the registry (display only). |
93
+ | `includeUnmanagedApps` | `true` | macOS: also list apps that Homebrew does not manage (drag-to-Applications installs; display only). |
94
+ | `downloadDir` | empty | Custom download folder; empty = `%USERPROFILE%\Downloads\DSH App Updater`. Must be absolute. |
95
+ | `keepInstallers` | `true` | Keep the installer after a successful update. |
96
+ | `hashFallbackUpgrade` | `true` | Fall back to `winget upgrade` when the download fails. |
97
+ | `dryRun` | `false` | **Rehearsal mode**: download the vendor's full installer but never install it. |
98
+ | `matchSystemArch` | `true` | Match the installer to the detected system and CPU architecture (Windows passes `--architecture`; macOS is resolved by Homebrew). |
99
+ | `autoElevate` | `true` | **Automatic elevation**: install commands that need administrator rights run in an elevated copy (UAC prompts once per install, when UAC prompts at all). |
100
+
101
+ Everything above is editable from the plugin's own settings panel inside the settings page.
102
+ The panel also accepts API calls:
103
+
104
+ ```bash
105
+ curl -X POST -H "Content-Type: application/json" \
106
+ -d '{"confirm":true,"settings":{"dryRun":true}}' \
107
+ http://127.0.0.1:<port>/dsh-app-updater/settings
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Safety boundaries
113
+
114
+ - **Every route is deny-by-default**: each request first goes through `ctx.connection.admit(request)`
115
+ — DSH's Host/Origin fence plus browser-session / API authentication, the same gate the host uses for
116
+ its own `/api` routes. Rejected requests get `401` / `403` / `409` with `X-DSH-Admission: denied`.
117
+ - **Legacy loopback mode is off by default** (since 1.3.1): when the host has no `connection`
118
+ service, requests are no longer trusted just because they come from loopback — they get `403`.
119
+ If you really need it on an old host, opt in explicitly, either via plugin config:
120
+
121
+ ```yaml
122
+ - id: dsh-app-updater
123
+ config:
124
+ allowLegacyLoopback: true
125
+ ```
126
+
127
+ or by exporting `DSH_APP_UPDATER_ALLOW_LEGACY_LOOPBACK=1` before starting DSH. Once enabled, allowed
128
+ requests carry `X-DSH-Admission: loopback-legacy-optin` and `X-DSH-Admission-Warning`, the
129
+ `admission` field in `state.json` / `health.json` reports `mode: "loopback-legacy-optin"`, the
130
+ settings page shows a red banner, and a `warn` line is written to the log.
131
+ - **Every write still requires explicit confirmation**: `scan` / `update` / `cancel` /
132
+ `settings` / `open-downloads` require `confirm:true`.
133
+ - **No guessed silent switches**: the publisher's declared `Silent` switches are preferred;
134
+ generic defaults are used only when a manifest is unavailable.
135
+ - **Elevation only for installs, and on demand**: install commands that need administrator rights
136
+ (MSI packages, installers writing into `Program Files`, the `winget upgrade` fallback) run in an
137
+ elevated copy through `Start-Process -Verb RunAs`. With UAC set to "always notify" you get one
138
+ prompt per install; with `ConsentPromptBehaviorAdmin = 0` it is completely silent. Reads
139
+ (`winget list` / `upgrade --include-unknown` / `--version`) never elevate.
140
+ - **A zero exit code is not success**: after installing, the plugin compares the installed version
141
+ against the target version and reports a failure when nothing actually changed — machine-scope
142
+ installers silently exit 0 without installing anything when they lack administrator rights
143
+ (Logitech G HUB used to be reported as "updated" that way).
144
+ - **System components** (`.NET`, VC++ runtimes, …) are listed but not updated by default.
145
+ - **Only its own directory is written**: state and logs live in `~/.dsh/dsh-app-updater/`.
146
+
147
+ ## Requirements
148
+
149
+ - **Windows**: `winget.exe` (Microsoft App Installer). Without winget the plugin can still
150
+ list registry apps, but cannot tell whether they are up to date.
151
+ - **macOS**: no extra dependency is needed to **list** apps (the plugin reads `.app` bundles from
152
+ `/Applications` and friends); judging and applying updates needs `brew` ([Homebrew](https://brew.sh)).
153
+ Without Homebrew the apps are still listed, with a note that installing Homebrew enables one-click
154
+ cask upgrades.
155
+ - No third-party npm dependencies: the host half uses Node built-ins only, the browser half
156
+ only the host-provided `react`.
157
+
158
+ ## Supported systems and architectures
159
+
160
+ The plugin detects its own environment at startup and picks the installer that matches it:
161
+
162
+ | System | Architecture | Engine | Notes |
163
+ |---|---|---|---|
164
+ | Windows 10 / 11 | x64 / ARM64 / x86 | **winget** | the detected architecture is passed as `winget --architecture`; if the manifest has no installer for it, the plugin falls back to automatic matching and says so in the log |
165
+ | macOS 11+ | Apple Silicon / Intel | **Homebrew casks** | apps are enumerated from `/Applications` and friends, then matched against the cask catalog; Homebrew resolves the right cask for the machine. Without Homebrew the apps are still listed, they just cannot be upgraded in one click |
166
+ | anything else | — | none | the scan/update routes answer 400 with the reason |
167
+
168
+ The result is shown in the status line and in the settings panel's “运行环境 / Environment” row, e.g.
169
+ `Windows 11(build 26300) · x64(AMD64) · winget 1.29.380` or
170
+ `macOS 15(Sequoia) · Apple Silicon(ARM64) · Homebrew 4.5.0`.
171
+
172
+ The “按本机架构安装包 / match system architecture” switch (on by default) can be turned off to hand
173
+ the choice back to winget; Homebrew always resolves for the local architecture.
174
+
175
+ > The macOS app enumeration (`/Applications` + `Info.plist`) and cask matching are covered by unit
176
+ > tests and were validated against the real `formulae.brew.sh` catalog (7,800+ casks), but the
177
+ > macOS **install/update execution** has not been verified on a real Mac — this plugin is developed
178
+ > on Windows. Please open an issue if something misbehaves there.
179
+ >
180
+ > Only the standard application directories are enumerated. Apps installed elsewhere (a custom
181
+ > folder, the Desktop, an external volume) are outside the scan scope by design.
182
+
183
+ ## HTTP API (host half)
184
+
185
+ | Method | Path | Purpose |
186
+ |---|---|---|
187
+ | GET | `/dsh-app-updater/state.json` | Status, inventory, log, settings |
188
+ | GET | `/dsh-app-updater/apps.json` | Inventory and statistics only |
189
+ | GET | `/dsh-app-updater/health.json` | Self-check: version, environment, engine, capabilities, route count, data directory |
190
+ | POST | `/dsh-app-updater/scan` | `{confirm:true, force:true}` start scanning |
191
+ | POST | `/dsh-app-updater/update` | `{confirm:true, ids:[...]}` start the update queue |
192
+ | POST | `/dsh-app-updater/cancel` | `{confirm:true}` cancel items not yet started |
193
+ | POST | `/dsh-app-updater/settings` | `{confirm:true, settings:{...}}` |
194
+ | POST | `/dsh-app-updater/open-downloads` | `{confirm:true}` open the download folder |
195
+
196
+ ## Install (from a checkout)
197
+
198
+ ```bash
199
+ # from a local checkout
200
+ dsh plugin --profile <profile> add file:/path/to/dsh-app-updater
201
+
202
+ # or as a local link
203
+ dsh plugin --profile <profile> add link:/path/to/dsh-app-updater
204
+ ```
205
+
206
+ Then add `dsh-app-updater` to `dsh.profile.bundles` in the profile's `package.json`
207
+ (the bundled `cordis.patch.yml` mounts the host half) and reload DSH.
208
+
209
+ ## Uninstall
210
+
211
+ 1. Remove `dsh-app-updater` from `dsh.profile.bundles` and from `dependencies` in the profile's `package.json`.
212
+ 2. Delete `<profile>/node_modules/dsh-app-updater`.
213
+ 3. Restart DSH; the data directory `%USERPROFILE%\.dsh\dsh-app-updater` can be removed as well.
214
+
215
+ ## Tests
216
+
217
+ ```bash
218
+ node test/unit.mjs # 153 checks: table parsing, version compare, manifest & switches, OS/arch detection, Homebrew parsing, macOS app enumeration & cask matching
219
+ node test/render.mjs # 51 checks: settings panel (per platform), rehearsal banner, scan-warning banner, column drag, request bodies
220
+ node test/deny.mjs # 12 checks: deny-by-default per route when the host has no connection service
221
+ node test/harness.mjs --scan # boots the host half against a fake cordis ctx + real HTTP
222
+ ```
223
+
224
+ > The tests do not depend on whether Homebrew is installed locally: the `findBrew` assertion checks
225
+ > existence across the three default prefixes (Apple Silicon / Intel / Linuxbrew), so it stays green
226
+ > on a real Mac (1.3.2 and earlier hard-coded `=== null` and was always red there; fixed in 1.3.3).
227
+
228
+ ## Layout
229
+
230
+ | File | Purpose |
231
+ |---|---|
232
+ | `lib/index.js` | Host half: HTTP routes, write gate, scan/update scheduling |
233
+ | `lib/engine.js` | Inventory merge, vendor installer download, silent install, winget fallback, version re-check |
234
+ | `lib/winget.js` | winget discovery/invocation, fixed-width table parsing (CJK-aware) |
235
+ | `lib/registry.js` | Registry `Uninstall` enumeration (PowerShell, UTF-8) |
236
+ | `lib/version.js` | Windows-style version comparison |
237
+ | `lib/state.js` | Settings and state persistence (atomic writes) |
238
+ | `lib/proc.js` | Child process runner (timeouts, cancellation, UTF-8) |
239
+ | `lib/client.js` | Browser half: the settings page UI |
240
+ | `CONTRACT.md` | Host ↔ client interface contract |
241
+
242
+ ## License
243
+
240
244
  MIT