dshmarket 1.31.2 → 1.33.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/README.md CHANGED
@@ -37,6 +37,7 @@ its own dsh: it may be older than the one `npm` would give you (#139).
37
37
  - **One-click install** — confirm the source, watch live progress; most plugins go live after a page refresh, no restart
38
38
  - **Backup & restore** — export your profile's plugin list and configuration as readable JSON, import it on another machine, store it on WebDAV with daily auto-backup, or sync through a private GitHub Gist; restores **merge** (plugins installed after the backup are kept), validate before writing, and roll back on failure
39
39
  - **Updates** — per-plugin update checks (npm version or pinned commit vs HEAD), one-click update, or update everything at once; the market updates itself the same way
40
+ - **Public update API** — plugin-owned settings pages can use the versioned, capability-gated [update API v1](UPDATE-API-V1.md) (beta) instead of copying package-manager logic or depending on private Market UI responses
40
41
  - **Uninstall** — two-step confirm; plugins installed this session are removed live
41
42
  - **Hot disable / enable** — toggles write `- id: …` + `disabled: true|false` into the profile's `cordis.patch.yml` (the official patch layer, mechanism ported from [dsh-plugin-hub](https://github.com/Noob-stupid/dsh-plugin-hub)): DSH's HMR re-composes within ~1s, no restart, and the loader re-applies the choice on every boot; hand-edited patch rows show as badges, host-infrastructure plugins are protected from toggling, and a malformed patch file is never made worse
42
43
  - **Restart when needed** — changes that cannot hot-load show a one-click restart beside the pending-change banner; the action is restricted to same-origin loopback requests
package/README.zh.md CHANGED
@@ -35,6 +35,7 @@ dsh plugin --profile web add dshmarket
35
35
  - **一键安装**——确认来源,实时进度;多数插件刷新页面即可用,无需重启
36
36
  - **备份与恢复**——把 profile 的插件清单与配置导出为可读 JSON,换机导入,存到 WebDAV 并每日自动备份,或通过私有 GitHub Gist 跨机器同步;恢复采用**合并**方式(备份之后新装的插件会保留),写入前校验、失败自动回滚
37
37
  - **更新**——逐插件检测(npm 版本或锁定 commit 对比 HEAD),一键更新或全部更新;市场自己也走同一通道升级
38
+ - **公共更新接口**——插件自己的设置页可调用带版本号、能力探测和回滚状态的[更新 API v1](UPDATE-API-V1.md)(beta),无需复制包管理逻辑,也不依赖市场 UI 的私有响应字段
38
39
  - **卸载**——两步确认防误触;本次会话装的插件即点即卸
39
40
  - **热禁用 / 启用**——开关会往 profile 的 `cordis.patch.yml`(官方补丁层,机制移植自 [dsh-plugin-hub](https://github.com/Noob-stupid/dsh-plugin-hub))写入 `- id: …` + `disabled: true|false`:DSH 的 HMR 约 1 秒内重新组合,无需重启,loader 每次启动都会重新应用这个选择;手工改过的补丁行会显示成徽标,宿主基础设施插件禁止开关,补丁文件格式不对时绝不会被写得更糟
40
41
  - **按需重启**——无法热加载的变更会在待重启提示旁显示一键重启;操作仅接受本机同源请求
@@ -0,0 +1,115 @@
1
+ # Public plugin update API v1
2
+
3
+ > **Status: beta.** The shape described here may still change between
4
+ > releases. `GET /dsh-market/api/v1/capabilities` reports
5
+ > `"stability": "beta"` while that is true, and `"stable"` once it stops
6
+ > moving — read that field rather than assuming from the `v1` in the path.
7
+ >
8
+ > Nothing here is going away; what is not yet promised is that field names
9
+ > and response shapes will survive untouched. If you ship against it now,
10
+ > say so in an issue: a shape somebody depends on is a much stronger reason
11
+ > not to move it, and it is how this reaches `stable`.
12
+
13
+
14
+ `dshmarket` exposes a small, versioned, same-origin JSON API for plugin-owned
15
+ update surfaces. It lets a plugin show its own update button without spawning a
16
+ package manager, copying the Market installation algorithm, or binding to the
17
+ Market UI's private response fields.
18
+
19
+ All responses carry:
20
+
21
+ ```json
22
+ { "schema": "dsh-market/update-api/v1" }
23
+ ```
24
+
25
+ Clients must discover the API before enabling mutation controls:
26
+
27
+ ```http
28
+ GET /dsh-market/api/v1/capabilities
29
+ ```
30
+
31
+ The response names the Market version, profile, runtime (`web` or `desktop`),
32
+ supported features, restart owner and endpoint paths. A client must hide its
33
+ restart button when `restart.supported` is false. Desktop and supervised hosts
34
+ normally delegate restart to their owning shell or operator.
35
+
36
+ ## Check one installed package
37
+
38
+ ```http
39
+ GET /dsh-market/api/v1/updates?name=dsh-mcp-connector&force=1
40
+ ```
41
+
42
+ The response includes the installed version, target version, source kind and
43
+ whether the target is a forward update. Omitting `force=1` allows the Market's
44
+ short update-check cache.
45
+
46
+ ## Start and observe an update
47
+
48
+ Mutation requests require the same-origin protection used by the Market UI.
49
+
50
+ ```http
51
+ POST /dsh-market/api/v1/updates
52
+ Content-Type: application/json
53
+
54
+ { "packageName": "dsh-mcp-connector" }
55
+ ```
56
+
57
+ An accepted request returns HTTP `202` immediately with an `operationId`.
58
+ Passing `"force": true` opts this one operation out of the registry release-age
59
+ wait; clients should offer it only after the normal operation reports
60
+ `RELEASE_TOO_FRESH` or `VERSION_UNCHANGED`.
61
+
62
+ Poll the operation by id:
63
+
64
+ ```http
65
+ GET /dsh-market/api/v1/operations?operationId=<id>
66
+ ```
67
+
68
+ States are `queued`, `running`, `succeeded`, `failed`, `cancelled` and
69
+ `rolled-back`. Running operations include structured package progress when
70
+ pnpm provides it. Terminal operations include:
71
+
72
+ - the before and actually installed versions;
73
+ - `refreshRequired` and `restartRequired` outcomes;
74
+ - a stable failure code, bounded user-facing message and retryability;
75
+ - whether a compatibility rollback is currently available.
76
+
77
+ Up to 50 operation records live in the current Host process. A boot id is
78
+ embedded in every operation id, so a client never mistakes a stale browser
79
+ record for a task belonging to the replacement process.
80
+
81
+ ## Roll back
82
+
83
+ ```http
84
+ POST /dsh-market/api/v1/rollback
85
+ Content-Type: application/json
86
+
87
+ { "operationId": "<id>" }
88
+ ```
89
+
90
+ Rollback is intentionally capability- and operation-scoped. It is available
91
+ only when the Market's compatibility verification retained a recovery point;
92
+ a later mutation may supersede it. The normalized result is written back to
93
+ the same operation record.
94
+
95
+ ## Restart
96
+
97
+ ```http
98
+ POST /dsh-market/api/v1/restart
99
+ Content-Type: application/json
100
+
101
+ {}
102
+ ```
103
+
104
+ This preserves the Market's stricter restart guard: direct loopback,
105
+ same-origin, no forwarding headers, no package mutation in progress, and a Host
106
+ whose lifecycle is not owned by Desktop or a supervisor. Clients must feature
107
+ detect it; they must not invent an alternative process-control path.
108
+
109
+ ## Compatibility policy
110
+
111
+ - New optional response fields may be added within v1.
112
+ - Existing v1 fields and meanings are not repurposed.
113
+ - A breaking change uses a new path and schema version.
114
+ - When discovery is unavailable, plugin UIs should fall back to opening the
115
+ Market rather than calling legacy `/dsh-market/*` mutation routes directly.
package/client/client.js CHANGED
@@ -4428,12 +4428,16 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
4428
4428
  * clock instead of letting it fire again moments later: without that, a
4429
4429
  * deliberate "go back one" reads as broken when it auto-advances right past
4430
4430
  * where the user just navigated to.
4431
+ *
4432
+ * `intervalMs <= 0` disables the timer entirely (no auto-advance at all);
4433
+ * manual jumps still work. The lightbox uses this: a full-bleed image needs
4434
+ * to stay put until the viewer moves on, so it must never page itself.
4431
4435
  */
4432
4436
  function useAutoCarousel(count, initial, intervalMs = 3500) {
4433
4437
  const [index, setIndexState] = (0, react.useState)(initial);
4434
4438
  const [resetTick, setResetTick] = (0, react.useState)(0);
4435
4439
  (0, react.useEffect)(() => {
4436
- if (count <= 1) return;
4440
+ if (count <= 1 || intervalMs <= 0) return;
4437
4441
  const timer = setInterval(() => {
4438
4442
  setIndexState((i) => (i + 1) % count);
4439
4443
  }, intervalMs);
@@ -4779,7 +4783,7 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
4779
4783
  * vs "full size" asset to fetch.
4780
4784
  */
4781
4785
  function ScreenshotLightbox({ shots, startIndex, onClose, t }) {
4782
- const [index, setIndex] = useAutoCarousel(shots.length, startIndex, 4e3);
4786
+ const [index, setIndex] = useAutoCarousel(shots.length, startIndex, 0);
4783
4787
  (0, react.useEffect)(() => {
4784
4788
  const onKey = (e) => {
4785
4789
  if (e.key === "Escape") {
@@ -5610,6 +5614,24 @@ window.__ModuleLoader__.load({ id: "dshmarket", factory: (require) => {
5610
5614
  else el.scrollTop = 0;
5611
5615
  }
5612
5616
  };
5617
+ (0, react.useLayoutEffect)(() => {
5618
+ const el = bodyRef.current;
5619
+ if (el !== null) el.scrollTop = 0;
5620
+ setShowTop(false);
5621
+ }, [
5622
+ tab,
5623
+ q,
5624
+ cat,
5625
+ sortField,
5626
+ sortDir,
5627
+ timeRange,
5628
+ qThemes,
5629
+ themeSortField,
5630
+ themeSortDir,
5631
+ themeTimeRange,
5632
+ qInstalled,
5633
+ installedView
5634
+ ]);
5613
5635
  const plugins = (0, react.useMemo)(() => data === null ? [] : visiblePlugins(data.plugins, {
5614
5636
  category: cat,
5615
5637
  query: q,