dsh-plugin-shop 0.4.1 → 0.4.3

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.
@@ -38,6 +38,12 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
38
38
  })),
39
39
  'stars': z.record(z.string(), z.number()),
40
40
  })
41
+ const dsh_plugin_shop_shop_installed_result$schema = z.array(z.object({
42
+ 'name': z.string(),
43
+ 'installed': z.string(),
44
+ 'latest': z.string(),
45
+ 'outdated': z.boolean(),
46
+ }))
41
47
  const dsh_plugin_shop_shop_installStart_parameter_0$schema = z.object({
42
48
  'name': z.string(),
43
49
  'version': z.string(),
@@ -61,11 +67,13 @@ const dsh_plugin_shop_shop_installStatus_result$schema = z.object({
61
67
  'needsRestart': z.union([z.undefined(), z.literal(false), z.literal(true)]).optional(),
62
68
  'detail': z.union([z.undefined(), z.string()]).optional(),
63
69
  })
64
- const dsh_plugin_shop_shop_outdated_result$schema = z.array(z.object({
65
- 'name': z.string(),
66
- 'installed': z.string(),
67
- 'latest': z.string(),
68
- }))
70
+ const dsh_plugin_shop_shop_restart_result$schema = z.union([z.object({
71
+ 'ok': z.literal(true),
72
+ 'url': z.string(),
73
+ }), z.object({
74
+ 'ok': z.literal(false),
75
+ 'detail': z.string(),
76
+ })])
69
77
  const dsh_plugin_shop_shop_setEnabled_parameter_0$schema = z.object({
70
78
  'name': z.string(),
71
79
  'enabled': z.boolean(),
@@ -74,6 +82,16 @@ const dsh_plugin_shop_shop_setEnabled_result$schema = z.object({
74
82
  'ok': z.boolean(),
75
83
  'detail': z.union([z.undefined(), z.string()]).optional(),
76
84
  })
85
+ const dsh_plugin_shop_shop_uninstallStart_parameter_0$schema = z.object({
86
+ 'name': z.string(),
87
+ })
88
+ const dsh_plugin_shop_shop_uninstallStart_result$schema = z.union([z.object({
89
+ 'ok': z.literal(true),
90
+ 'installId': z.string(),
91
+ }), z.object({
92
+ 'ok': z.literal(false),
93
+ 'detail': z.string(),
94
+ })])
77
95
 
78
96
  export const TYPERT_REMOTE = {
79
97
  package: 'dsh-plugin-shop',
@@ -102,7 +120,22 @@ export const TYPERT_REMOTE = {
102
120
  typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
103
121
  schema: dsh_plugin_shop_shop_catalog_result$schema,
104
122
  },
105
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":180,"column":9},
123
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":218,"column":9},
124
+ },
125
+ {
126
+ id: 'dsh-plugin-shop#shop/installed',
127
+ service: 'shop',
128
+ namespace: 'shop',
129
+ method: 'installed',
130
+ invocation: { kind: 'direct' },
131
+ parameters: [
132
+ ],
133
+ result: {
134
+ mode: 'strict',
135
+ typeSymbol: 'dsh-plugin-shop#shop/installed:result',
136
+ schema: dsh_plugin_shop_shop_installed_result$schema,
137
+ },
138
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":293,"column":9},
106
139
  },
107
140
  {
108
141
  id: 'dsh-plugin-shop#shop/installStart',
@@ -128,7 +161,7 @@ export const TYPERT_REMOTE = {
128
161
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
129
162
  schema: dsh_plugin_shop_shop_installStart_result$schema,
130
163
  },
131
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":206,"column":9},
164
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":244,"column":9},
132
165
  },
133
166
  {
134
167
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -153,22 +186,22 @@ export const TYPERT_REMOTE = {
153
186
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
154
187
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
155
188
  },
156
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":244,"column":3},
189
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":282,"column":3},
157
190
  },
158
191
  {
159
- id: 'dsh-plugin-shop#shop/outdated',
192
+ id: 'dsh-plugin-shop#shop/restart',
160
193
  service: 'shop',
161
194
  namespace: 'shop',
162
- method: 'outdated',
195
+ method: 'restart',
163
196
  invocation: { kind: 'direct' },
164
197
  parameters: [
165
198
  ],
166
199
  result: {
167
200
  mode: 'strict',
168
- typeSymbol: 'dsh-plugin-shop#shop/outdated:result',
169
- schema: dsh_plugin_shop_shop_outdated_result$schema,
201
+ typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
202
+ schema: dsh_plugin_shop_shop_restart_result$schema,
170
203
  },
171
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":252,"column":9},
204
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":367,"column":9},
172
205
  },
173
206
  {
174
207
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -193,7 +226,33 @@ export const TYPERT_REMOTE = {
193
226
  typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
194
227
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
195
228
  },
196
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":149,"column":3},
229
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":187,"column":3},
230
+ },
231
+ {
232
+ id: 'dsh-plugin-shop#shop/uninstallStart',
233
+ service: 'shop',
234
+ namespace: 'shop',
235
+ method: 'uninstallStart',
236
+ implementation: 'uninstall',
237
+ invocation: { kind: 'direct' },
238
+ parameters: [
239
+ {
240
+ name: 'args',
241
+ wire: 'args',
242
+ source: 'json',
243
+ codec: {
244
+ mode: 'strict',
245
+ typeSymbol: 'dsh-plugin-shop#shop/uninstallStart:args',
246
+ schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema,
247
+ },
248
+ },
249
+ ],
250
+ result: {
251
+ mode: 'strict',
252
+ typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
253
+ schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
254
+ },
255
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":333,"column":9},
197
256
  },
198
257
  ],
199
258
  }
@@ -5,7 +5,7 @@
5
5
  * §11.3.4): no render path here may ever use dangerouslySetInnerHTML. */
6
6
  import { type ReactNode } from 'react';
7
7
  import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
8
- import type { InstallArgs, ShopCatalogResult, ShopInstallResult, ShopInstallStatusResult, ShopOutdatedEntry, ShopSetEnabledResult } from '../host/index.ts';
8
+ import type { InstallArgs, ShopCatalogResult, ShopInstalledEntry, ShopInstallResult, ShopInstallStatusResult, ShopRestartResult, ShopSetEnabledResult, ShopUninstallResult } from '../host/index.ts';
9
9
  /** The tab's Remote face: the Host result types, already unwrapped from the
10
10
  * wire envelope by `index.ts`; `catalog` throws on a wire error so the tab's
11
11
  * error state renders. */
@@ -21,7 +21,11 @@ export interface ShopTabInjected {
21
21
  name: string;
22
22
  enabled: boolean;
23
23
  }) => Promise<ShopSetEnabledResult>;
24
- outdated: () => Promise<ShopOutdatedEntry[]>;
24
+ installed: () => Promise<ShopInstalledEntry[]>;
25
+ uninstall: (args: {
26
+ name: string;
27
+ }) => Promise<ShopUninstallResult>;
28
+ restart: () => Promise<ShopRestartResult>;
25
29
  }
26
30
  /** Full component props assembled by the Settings slot renderer. */
27
31
  export type ShopTabProps = PropsRuntime<'settings.plugins.tab'> & PropsLocale<'settings.shop'> & InjectFace<ShopTabInjected>;
@@ -1,9 +1,9 @@
1
1
  /**
2
2
  * Shop settings surface, browser half — one tab in `settings.plugins.tab`
3
3
  * that browses the catalog, installs with acknowledgement, toggles
4
- * enablement, and lists outdated installs. Mounts the shop Remote itself
5
- * (the assembly does not know this package) and holds no privilege beyond
6
- * the five `shop/*` methods (§5.3).
4
+ * enablement, lists installed plugins, and restarts dsh after installs.
5
+ * Mounts the shop Remote itself (the assembly does not know this package)
6
+ * and holds no privilege beyond the seven `shop/*` methods (§5.3).
7
7
  */
8
8
  import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client';
9
9
  import type { ShopLocaleKey } from './locales.ts';
@@ -48,6 +48,18 @@ export declare const zh: {
48
48
  installedVersion: string;
49
49
  latestVersion: string;
50
50
  update: string;
51
+ installed: string;
52
+ uninstall: string;
53
+ uninstalling: string;
54
+ uninstallFailed: string;
55
+ uninstallTransportFailed: string;
56
+ uninstalledRestartNotice: string;
57
+ restart: string;
58
+ restartTitle: string;
59
+ restartBody: string;
60
+ restartConfirm: string;
61
+ restarting: string;
62
+ restartTransportFailed: string;
51
63
  enabledSwitch: string;
52
64
  toggleFailed: string;
53
65
  hotApplyNote: string;
@@ -103,6 +115,18 @@ export declare const en: {
103
115
  installedVersion: string;
104
116
  latestVersion: string;
105
117
  update: string;
118
+ installed: string;
119
+ uninstall: string;
120
+ uninstalling: string;
121
+ uninstallFailed: string;
122
+ uninstallTransportFailed: string;
123
+ uninstalledRestartNotice: string;
124
+ restart: string;
125
+ restartTitle: string;
126
+ restartBody: string;
127
+ restartConfirm: string;
128
+ restarting: string;
129
+ restartTransportFailed: string;
106
130
  enabledSwitch: string;
107
131
  toggleFailed: string;
108
132
  hotApplyNote: string;
@@ -1,4 +1,5 @@
1
1
  /** Install driving hook for one entry: start, poll to terminal, reset. */
2
+ import { type Dispatch, type SetStateAction } from 'react';
2
3
  import { type InstallView } from './present.ts';
3
4
  import type { InstallArgs, ShopInstallResult, ShopInstallStatusResult } from '../host/index.ts';
4
5
  export interface UseInstallResult {
@@ -6,6 +7,14 @@ export interface UseInstallResult {
6
7
  start: (args: InstallArgs) => Promise<void>;
7
8
  reset: () => void;
8
9
  }
10
+ /** The poll loop shared by the install and uninstall drivers: while the view
11
+ * is `running`, poll once per second and fold each status through the
12
+ * reducer. A poll failure is transient — the host retains the record, so the
13
+ * next tick finds it. The rejection handler must be present: an unhandled
14
+ * rejection here would escape the poll loop. */
15
+ export declare function usePollStatus(view: InstallView, setView: Dispatch<SetStateAction<InstallView>>, installStatus: (args: {
16
+ installId: string;
17
+ }) => Promise<ShopInstallStatusResult>): void;
9
18
  /** Drive one install: rejections, the polling loop at INSTALL_POLL_MS, and
10
19
  * terminal states. The interval is cleared on unmount and whenever the view
11
20
  * leaves `running`, so a done/failed/rejected install never polls again. */
@@ -0,0 +1,21 @@
1
+ /** Uninstall driving hook for one entry: start, poll to terminal. Shares the
2
+ * install view machine and poll loop; the only difference is the start
3
+ * mapping — an uninstall business failure (not in the catalog / not
4
+ * installed) lands in the `failed` view with the host's published detail,
5
+ * never in the install `rejected` codes, which belong to the install gate
6
+ * (§7.2). */
7
+ import type { ShopInstallStatusResult, ShopUninstallResult } from '../host/index.ts';
8
+ import type { InstallView } from './present.ts';
9
+ export interface UseUninstallResult {
10
+ view: InstallView;
11
+ start: (args: {
12
+ name: string;
13
+ }) => Promise<void>;
14
+ }
15
+ /** Drive one uninstall: the host's business union, the shared polling loop,
16
+ * and terminal states. The `rejected` install state never occurs here. */
17
+ export declare function useUninstall(uninstall: (args: {
18
+ name: string;
19
+ }) => Promise<ShopUninstallResult>, installStatus: (args: {
20
+ installId: string;
21
+ }) => Promise<ShopInstallStatusResult>): UseUninstallResult;
@@ -1,4 +1,7 @@
1
- /** Install executor: spawn the dsh CLI, stream its output, serialize per profile. */
1
+ /** Plugin-command executor: spawn the dsh CLI, stream its output, serialize
2
+ * per profile. One implementation drives both `dsh plugin add` (install) and
3
+ * `dsh plugin remove` (uninstall); the only differences are the verb and the
4
+ * post-exit manifest confirmation. */
2
5
  export type InstallState = 'running' | 'done' | 'failed';
3
6
  export interface InstallStatus {
4
7
  state: InstallState;
@@ -13,11 +16,6 @@ interface RunningInstall {
13
16
  }
14
17
  /**
15
18
  * Run one `dsh plugin --profile <profile> add <spec>` and track it.
16
- * Never rolls back; a failure surfaces stderr verbatim plus the recovery hint
17
- * (§10). The shop never passes build-script flags: `allowBuilds` stays the
18
- * user's explicit decision in the CLI (§7.2).
19
- * The child inherits the current environment unless `env` is given — the
20
- * real-install test pins DSH_HOME to a temporary directory this way.
21
19
  * When `expectedName` is given, a zero exit is confirmed against the profile
22
20
  * manifest (§7.2 step 6) before the install reports `done`.
23
21
  */
@@ -29,4 +27,18 @@ export declare function startInstall(options: {
29
27
  expectedName?: string;
30
28
  onStatus?: (status: InstallStatus) => void;
31
29
  }): RunningInstall;
30
+ /**
31
+ * Run one `dsh plugin --profile <profile> remove <name>` and track it.
32
+ * When `expectedName` is given, a zero exit is confirmed against the profile
33
+ * manifest — the bundle must actually have LEFT `dsh.profile.bundles` — before
34
+ * the uninstall reports `done`.
35
+ */
36
+ export declare function startUninstall(options: {
37
+ profile: string;
38
+ name: string;
39
+ dshBin?: string;
40
+ env?: NodeJS.ProcessEnv;
41
+ expectedName?: string;
42
+ onStatus?: (status: InstallStatus) => void;
43
+ }): RunningInstall;
32
44
  export {};
@@ -5,6 +5,7 @@ import { loadCatalog, type LoadCatalogOptions } from './catalog.ts';
5
5
  import type { CatalogEntry, DeniedEntry } from './types.ts';
6
6
  import { type InstallArgs, type InstallRejectionCode } from './install.ts';
7
7
  import { type InstallStatus } from './executor.ts';
8
+ import { type RestartOutcome } from './restart.ts';
8
9
  export type { InstallArgs, InstallRejectionCode } from './install.ts';
9
10
  export type { CatalogEntry } from './types.ts';
10
11
  /** One Loader inventory entry, structurally — the shop never depends on
@@ -29,6 +30,15 @@ export interface ShopGatewayOptions {
29
30
  list(): InventoryEntry[];
30
31
  };
31
32
  dshBin?: string;
33
+ /** The dsh argv this process was launched with, for `shop/restart`;
34
+ * defaults to the real `process.argv` minus node and the script path. */
35
+ restartArgv?: string[];
36
+ /** Test-only injection: the exit the restart calls after the response is
37
+ * delivered. Production uses `process.exit`. */
38
+ exit?: (code?: number) => void;
39
+ /** How long the gateway waits after a successful restart response before
40
+ * exiting the old process; test-only shortening, production uses 2s. */
41
+ restartExitDelayMs?: number;
32
42
  }
33
43
  /** `shop/installStart` result (§7.3): rejections are typed wire values with an
34
44
  * author-readable `detail`, not thrown RPC errors. */
@@ -49,11 +59,28 @@ export interface ShopSetEnabledResult {
49
59
  ok: boolean;
50
60
  detail?: string;
51
61
  }
52
- /** `shop/outdated` entry (§7.3): one installed plugin behind the catalog. */
53
- export interface ShopOutdatedEntry {
62
+ /** `shop/uninstallStart` result (§7.3): a name outside the catalog or not
63
+ * installed is a typed wire value with an author-readable `detail`, not a
64
+ * thrown RPC error. */
65
+ export type ShopUninstallResult = {
66
+ ok: true;
67
+ installId: string;
68
+ } | {
69
+ ok: false;
70
+ detail: string;
71
+ };
72
+ /** `shop/restart` result (§7.3): the restarted server's URL, or a typed
73
+ * failure — on failure the old process is still serving. */
74
+ export type ShopRestartResult = RestartOutcome;
75
+ /** `shop/installed` entry (§7.3): one installed catalog plugin. `installed`
76
+ * is the profile manifest's dependency spec verbatim (a range, a tag, or
77
+ * `workspace:*`); `outdated` is the Host's verdict that the installed version
78
+ * sits behind the catalog's — the client never does version math. */
79
+ export interface ShopInstalledEntry {
54
80
  name: string;
55
81
  installed: string;
56
82
  latest: string;
83
+ outdated: boolean;
57
84
  }
58
85
  /** `shop/catalog` result (§7.3), plus the denied list for the install gate's UI. */
59
86
  export interface ShopCatalogResult {
@@ -77,11 +104,21 @@ export declare class ShopGateway extends TypertRemoteService {
77
104
  private readonly profileDir?;
78
105
  private readonly inventory?;
79
106
  private readonly dshBin;
107
+ /** The argv `shop/restart` re-spawns: the real process argv minus node and
108
+ * the CLI script path, or a test-provided substitute. */
109
+ private readonly restartArgv;
110
+ /** The exit the restart calls once the response is out; `process.exit` in
111
+ * production, a spy in tests. */
112
+ private readonly exit;
113
+ private readonly restartExitDelayMs;
80
114
  /** The install gate runs against the last loaded snapshot, never a fresh
81
115
  * fetch per request (§7.2: the Host's cached snapshot is the truth). */
82
116
  /** Finished install records retained, so a poll sees the true terminal
83
117
  * state (§8: done / needsRestart / failure detail). Oldest evicted on add. */
84
118
  private static readonly MAX_FINISHED_INSTALLS;
119
+ /** How long the gateway waits after a successful restart response before
120
+ * exiting the old process — the browser must receive the URL first. */
121
+ private static readonly RESTART_EXIT_DELAY_MS;
85
122
  /** The install gate runs against the last loaded snapshot, never a fresh
86
123
  * fetch per request (§7.2: the Host's cached snapshot is the truth). */
87
124
  private lastSnapshot;
@@ -127,7 +164,31 @@ export declare class ShopGateway extends TypertRemoteService {
127
164
  installStatus(args: {
128
165
  installId: string;
129
166
  }): ShopInstallStatusResult;
130
- /** Installed plugins whose installed version is older than the catalog's (§7.3). */
131
- outdated(): Promise<ShopOutdatedEntry[]>;
167
+ /** Installed catalog plugins (§7.3): every entry of the snapshot the profile
168
+ * manifest declares as a dependency, with the Host's `outdated` verdict
169
+ * attached. The tab's shelf cards and its installed section both derive
170
+ * from this one list. */
171
+ installed(): Promise<ShopInstalledEntry[]>;
172
+ /** Whether an installed dependency spec sits behind the catalog's version.
173
+ * A spec identical to the catalog version is current by definition; a
174
+ * pnpm-written non-semver spec like `workspace:*` is not reportable and
175
+ * reads as current rather than killing the RPC. */
176
+ private isBehind;
177
+ /** Uninstall one installed catalog plugin from the profile (§7.3 follow-up
178
+ * amendment). Removing revokes privilege rather than granting it, so there
179
+ * is no acknowledgement gate. The name must be a catalog entry the profile
180
+ * manifest declares as a dependency — the RPC cannot remove profile
181
+ * dependencies the shop does not manage (the base bundle, the shop
182
+ * itself). The same install records/polling serve the client. */
183
+ uninstall(args: {
184
+ name: string;
185
+ }): Promise<ShopUninstallResult>;
186
+ /** Restart the dsh process the shop runs in (§8 amendment, 2026-08-27):
187
+ * re-spawn this process's own command line, return the new server's URL
188
+ * once it announces itself, and only then exit. A failed restart returns a
189
+ * typed failure and the old process keeps serving — the restart is
190
+ * all-or-nothing. The response must reach the browser before the exit, so
191
+ * the exit is delayed past the RPC round-trip. */
192
+ restart(): Promise<ShopRestartResult>;
132
193
  }
133
194
  export default ShopGateway;
@@ -0,0 +1,35 @@
1
+ /** Restart executor: re-spawn the Host's own command line and hand the
2
+ * browser the new server's URL. The child is detached (its own process
3
+ * group), so it survives both the parent's exit and the launching
4
+ * terminal's. The parent exits only AFTER the child printed its
5
+ * `dsh web: <url>` line — a restart that fails to come up leaves the old
6
+ * process running untouched. */
7
+ /** `shop/restart` outcome: the new server's URL, or a typed failure with an
8
+ * author-readable detail. A failure means the OLD process is still serving —
9
+ * the restart is all-or-nothing. */
10
+ export type RestartOutcome = {
11
+ ok: true;
12
+ url: string;
13
+ } | {
14
+ ok: false;
15
+ detail: string;
16
+ };
17
+ /** Spawn the restarted server and wait for it to announce its URL.
18
+ *
19
+ * The child runs the same `dsh` with the same argv the current process was
20
+ * launched with (`process.argv.slice(2)` — node and the CLI script path
21
+ * stripped), so the profile, port and flags reproduce the user's launch
22
+ * verbatim. `--port 0` therefore yields a NEW port, and the returned URL is
23
+ * how the browser finds it.
24
+ *
25
+ * On success the caller exits the old process — but only after delivering
26
+ * the RPC response carrying `url`, which is the caller's sequencing duty,
27
+ * not this module's. On failure (child exits before announcing, spawn
28
+ * error, or the timeout) the child is killed if still running and the old
29
+ * process is untouched. */
30
+ export declare function startRestart(options: {
31
+ dshBin: string;
32
+ argv: string[];
33
+ env?: NodeJS.ProcessEnv;
34
+ timeoutMs?: number;
35
+ }): Promise<RestartOutcome>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "repository": {
6
6
  "type": "git",