dshmarket 1.20.4 → 1.21.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/lib/dsh-cli.js CHANGED
@@ -15,6 +15,8 @@ import { createProgressTracker } from './ndjson.js';
15
15
  import { pluginArgsFor } from './pnpm-compat.js';
16
16
  import { isDshProfileName, profileDir } from './profile.js';
17
17
  import { activeRegion, DEFAULT_NPM_REGISTRY, routesFor } from './regions.js';
18
+ import { NPM_NAME_RE } from './sources.js';
19
+ import { fetchNpmLatest } from './updates.js';
18
20
  // 15 min default (slow networks + git installs), overridable for CI/tests.
19
21
  // (#6 by @qichuang321.)
20
22
  /**
@@ -279,6 +281,46 @@ export function dshArgv() {
279
281
  // Bare `dsh` is a .cmd shim on Windows that only a shell can start (#13).
280
282
  return { file: 'dsh', args: [], cwd: undefined, viaShell: winCmdShim };
281
283
  }
284
+ /** An npm name with a fully pinned version — the only target their boundary takes. */
285
+ const EXACT_NPM_TARGET_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
286
+ /**
287
+ * Rewrite an `add` argv into the shape Anywhere Labs' install boundary
288
+ * accepts, or null when it cannot be expressed there.
289
+ *
290
+ * Their validator wants exactly one target of the form `name@1.2.3` — not a
291
+ * bare name, not `@latest`, and not a `github:` source (read from
292
+ * `validateExternalMarketInstallArgs`, dsh-plugin-desktop/src/pnpm.ts). The
293
+ * market sends a bare name for a registry plugin and `dshmarket@latest` for
294
+ * itself, so both need the version resolved before that boundary will take
295
+ * them.
296
+ *
297
+ * Returning null is a normal outcome, not a failure: a github-sourced plugin
298
+ * has no `name@version` spelling at all. The caller falls back to the
299
+ * ordinary path, which on that host reports their own refusal — an accurate
300
+ * message about their contract, rather than one this package invented.
301
+ */
302
+ async function exactNpmArgs(args) {
303
+ const targets = args.slice(1).filter(argument => !argument.startsWith('-'));
304
+ const target = targets[0];
305
+ if (targets.length !== 1 || target === undefined)
306
+ return null;
307
+ if (EXACT_NPM_TARGET_RE.test(target))
308
+ return [...args];
309
+ // A bare name, or one pinned to a dist-tag. Only a registry package can be
310
+ // resolved; `github:owner/repo` and file paths stop here.
311
+ const at = target.lastIndexOf('@');
312
+ const name = at > 0 ? target.slice(0, at) : target;
313
+ if (!NPM_NAME_RE.test(name))
314
+ return null;
315
+ const version = await fetchNpmLatest(name);
316
+ if (version === null)
317
+ return null;
318
+ const rewritten = `${name}@${version}`;
319
+ if (!EXACT_NPM_TARGET_RE.test(rewritten))
320
+ return null;
321
+ logEvent('info', 'install', `desktop install boundary needs an exact version: ${target} -> ${rewritten}`);
322
+ return args.map(argument => (argument === target ? rewritten : argument));
323
+ }
282
324
  /**
283
325
  * Kill a spawned child and, on Windows, its whole process tree — `kill()`
284
326
  * there only terminates the wrapper, leaving pnpm children running.
@@ -691,7 +733,16 @@ export function createDesktopPluginRuntime(service, activeProfileDir, invokingDi
691
733
  const abort = new AbortController();
692
734
  let handle;
693
735
  try {
694
- handle = service.runPlugin(prepared.args, invokingDir, abort.signal);
736
+ // `add` goes through Anywhere Labs' install boundary when that host
737
+ // publishes one, because their Desktop rejects `add` on `runPlugin`
738
+ // outright. Feature-detected, never assumed: this method is theirs
739
+ // alone, and on every other client — including the other desktop app
740
+ // in #292 — the ordinary call below is what runs, unchanged.
741
+ const boundary = prepared.args[0] === 'add' ? service.runExternalMarketPluginInstall : undefined;
742
+ const viaBoundary = boundary === undefined ? null : await exactNpmArgs(prepared.args);
743
+ handle = boundary === undefined || viaBoundary === null
744
+ ? service.runPlugin(prepared.args, invokingDir, abort.signal)
745
+ : boundary.call(service, viaBoundary, invokingDir, abort.signal);
695
746
  }
696
747
  catch (error) {
697
748
  const message = error instanceof Error ? error.message : String(error);
package/lib/sources.js CHANGED
@@ -9,7 +9,7 @@ function validSubpath(subpath) {
9
9
  return !subpath.split('/').some(seg => seg === '' || seg === '.' || seg === '..');
10
10
  }
11
11
  /** Registry tarball names must be plain npm package names, nothing fancier. */
12
- const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
12
+ export const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
13
13
  /**
14
14
  * Parse a registry source url: a github repo, optionally with a
15
15
  * `/tree/<branch>/<subpath>` suffix (how the curated list links monorepo
@@ -154,17 +154,55 @@ export interface PluginCommandRuntime {
154
154
  }>;
155
155
  cancelActive(): boolean;
156
156
  }
157
- /** Structural subset of DSH Desktop's public `desktopPnpm` contract. */
157
+ /** One running package operation, however it was started. */
158
+ export interface DesktopPnpmHandleLike {
159
+ readonly stdout: NodeJS.ReadableStream;
160
+ readonly stderr: NodeJS.ReadableStream;
161
+ readonly done: Promise<{
162
+ readonly exitCode: number | null;
163
+ readonly signal: NodeJS.Signals | null;
164
+ }>;
165
+ cancel(): void;
166
+ }
167
+ /**
168
+ * Structural subset of DSH Desktop's public `desktopPnpm` contract.
169
+ *
170
+ * Anywhere Labs' DSH Desktop is ONE third-party client among several, and
171
+ * this interface exists only for it. Nothing here is part of the official
172
+ * DSH protocol — `desktopPnpm`, `installPlugin` and the install boundary
173
+ * below appear nowhere in `@deepseek-ai/*`. Every other client the market
174
+ * runs under, including other desktop apps, installs through the ordinary
175
+ * `dsh plugin --profile <p> add` CLI, and so does the market itself when
176
+ * none of these services are present.
177
+ *
178
+ * That is why every member past `runPlugin` is optional and reached by
179
+ * feature detection. A host that does not publish one simply never enters
180
+ * the branch, and the ordinary path it already used stays untouched — the
181
+ * cost of accommodating one vendor must not be paid by the others, or by
182
+ * the far larger number of people on plain `dsh web`.
183
+ */
158
184
  export interface DesktopPnpmLike {
159
- runPlugin(args: readonly string[], invokingDir: string, signal?: AbortSignal): {
160
- readonly stdout: NodeJS.ReadableStream;
161
- readonly stderr: NodeJS.ReadableStream;
162
- readonly done: Promise<{
163
- readonly exitCode: number | null;
164
- readonly signal: NodeJS.Signals | null;
165
- }>;
166
- cancel(): void;
167
- };
185
+ runPlugin(args: readonly string[], invokingDir: string, signal?: AbortSignal): DesktopPnpmHandleLike;
186
+ /**
187
+ * Desktop 2.x refuses `add` through `runPlugin` — "plugin add must use the
188
+ * recoverable install boundary" (#215, #219, #272) — and offers this
189
+ * instead, which their launcher enables only for the selected market
190
+ * provider. Same arguments, same handle, no recovery receipt and no
191
+ * write-ahead log for the caller to reconcile.
192
+ *
193
+ * Optional because it is theirs: absent on every other host, including
194
+ * the other third-party desktop client in #292, which installs perfectly
195
+ * well through the ordinary CLI.
196
+ *
197
+ * Read from their published source rather than assumed: it accepts ONLY
198
+ * `add` with exactly one target of the form `name@exact.version`
199
+ * (`validateExternalMarketInstallArgs` in dsh-plugin-desktop/src/pnpm.ts).
200
+ * A `github:owner/repo` target is rejected before any process starts, so
201
+ * the 1085 catalog entries with no npm package — 57% of it — cannot be
202
+ * installed on that host by any spelling this market could send. That is
203
+ * a gap in their contract, not something to work around here.
204
+ */
205
+ runExternalMarketPluginInstall?(args: readonly string[], invokingDir: string, signal?: AbortSignal): DesktopPnpmHandleLike;
168
206
  }
169
207
  /** Desktop runtime also owns cleanup of any operation started by this fiber. */
170
208
  export interface DesktopPluginRuntime extends PluginCommandRuntime {
@@ -2,6 +2,8 @@
2
2
  * Registry-source knowledge: how a curated registry entry's URL maps to an
3
3
  * installable pnpm target. Pure string logic, no I/O.
4
4
  */
5
+ /** Registry tarball names must be plain npm package names, nothing fancier. */
6
+ export declare const NPM_NAME_RE: RegExp;
5
7
  /**
6
8
  * Parse a registry source url: a github repo, optionally with a
7
9
  * `/tree/<branch>/<subpath>` suffix (how the curated list links monorepo
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dshmarket",
3
3
  "description": "Visual plugin market inside DeepSeek Harness — browse, search, and one-click install community plugins. · DSH 可视化插件市场:逛一逛,点一下,装好。",
4
- "version": "1.20.4",
4
+ "version": "1.21.0",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/types/index.d.ts",
package/src/dsh-cli.ts CHANGED
@@ -17,6 +17,8 @@ import { createProgressTracker, type ProgressPhase } from './ndjson.ts'
17
17
  import { pluginArgsFor } from './pnpm-compat.ts'
18
18
  import { isDshProfileName, profileDir } from './profile.ts'
19
19
  import { activeRegion, DEFAULT_NPM_REGISTRY, routesFor, type Region } from './regions.ts'
20
+ import { NPM_NAME_RE } from './sources.ts'
21
+ import { fetchNpmLatest } from './updates.ts'
20
22
 
21
23
  // 15 min default (slow networks + git installs), overridable for CI/tests.
22
24
  // (#6 by @qichuang321.)
@@ -331,21 +333,102 @@ export interface PluginCommandRuntime {
331
333
  cancelActive(): boolean
332
334
  }
333
335
 
334
- /** Structural subset of DSH Desktop's public `desktopPnpm` contract. */
336
+ /** One running package operation, however it was started. */
337
+ export interface DesktopPnpmHandleLike {
338
+ readonly stdout: NodeJS.ReadableStream
339
+ readonly stderr: NodeJS.ReadableStream
340
+ readonly done: Promise<{
341
+ readonly exitCode: number | null
342
+ readonly signal: NodeJS.Signals | null
343
+ }>
344
+ cancel(): void
345
+ }
346
+
347
+ /**
348
+ * Structural subset of DSH Desktop's public `desktopPnpm` contract.
349
+ *
350
+ * Anywhere Labs' DSH Desktop is ONE third-party client among several, and
351
+ * this interface exists only for it. Nothing here is part of the official
352
+ * DSH protocol — `desktopPnpm`, `installPlugin` and the install boundary
353
+ * below appear nowhere in `@deepseek-ai/*`. Every other client the market
354
+ * runs under, including other desktop apps, installs through the ordinary
355
+ * `dsh plugin --profile <p> add` CLI, and so does the market itself when
356
+ * none of these services are present.
357
+ *
358
+ * That is why every member past `runPlugin` is optional and reached by
359
+ * feature detection. A host that does not publish one simply never enters
360
+ * the branch, and the ordinary path it already used stays untouched — the
361
+ * cost of accommodating one vendor must not be paid by the others, or by
362
+ * the far larger number of people on plain `dsh web`.
363
+ */
335
364
  export interface DesktopPnpmLike {
336
365
  runPlugin(
337
366
  args: readonly string[],
338
367
  invokingDir: string,
339
368
  signal?: AbortSignal,
340
- ): {
341
- readonly stdout: NodeJS.ReadableStream
342
- readonly stderr: NodeJS.ReadableStream
343
- readonly done: Promise<{
344
- readonly exitCode: number | null
345
- readonly signal: NodeJS.Signals | null
346
- }>
347
- cancel(): void
348
- }
369
+ ): DesktopPnpmHandleLike
370
+
371
+ /**
372
+ * Desktop 2.x refuses `add` through `runPlugin` — "plugin add must use the
373
+ * recoverable install boundary" (#215, #219, #272) — and offers this
374
+ * instead, which their launcher enables only for the selected market
375
+ * provider. Same arguments, same handle, no recovery receipt and no
376
+ * write-ahead log for the caller to reconcile.
377
+ *
378
+ * Optional because it is theirs: absent on every other host, including
379
+ * the other third-party desktop client in #292, which installs perfectly
380
+ * well through the ordinary CLI.
381
+ *
382
+ * Read from their published source rather than assumed: it accepts ONLY
383
+ * `add` with exactly one target of the form `name@exact.version`
384
+ * (`validateExternalMarketInstallArgs` in dsh-plugin-desktop/src/pnpm.ts).
385
+ * A `github:owner/repo` target is rejected before any process starts, so
386
+ * the 1085 catalog entries with no npm package — 57% of it — cannot be
387
+ * installed on that host by any spelling this market could send. That is
388
+ * a gap in their contract, not something to work around here.
389
+ */
390
+ runExternalMarketPluginInstall?(
391
+ args: readonly string[],
392
+ invokingDir: string,
393
+ signal?: AbortSignal,
394
+ ): DesktopPnpmHandleLike
395
+ }
396
+
397
+ /** An npm name with a fully pinned version — the only target their boundary takes. */
398
+ const EXACT_NPM_TARGET_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*@\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/
399
+
400
+ /**
401
+ * Rewrite an `add` argv into the shape Anywhere Labs' install boundary
402
+ * accepts, or null when it cannot be expressed there.
403
+ *
404
+ * Their validator wants exactly one target of the form `name@1.2.3` — not a
405
+ * bare name, not `@latest`, and not a `github:` source (read from
406
+ * `validateExternalMarketInstallArgs`, dsh-plugin-desktop/src/pnpm.ts). The
407
+ * market sends a bare name for a registry plugin and `dshmarket@latest` for
408
+ * itself, so both need the version resolved before that boundary will take
409
+ * them.
410
+ *
411
+ * Returning null is a normal outcome, not a failure: a github-sourced plugin
412
+ * has no `name@version` spelling at all. The caller falls back to the
413
+ * ordinary path, which on that host reports their own refusal — an accurate
414
+ * message about their contract, rather than one this package invented.
415
+ */
416
+ async function exactNpmArgs(args: readonly string[]): Promise<string[] | null> {
417
+ const targets = args.slice(1).filter(argument => !argument.startsWith('-'))
418
+ const target = targets[0]
419
+ if (targets.length !== 1 || target === undefined) return null
420
+ if (EXACT_NPM_TARGET_RE.test(target)) return [...args]
421
+ // A bare name, or one pinned to a dist-tag. Only a registry package can be
422
+ // resolved; `github:owner/repo` and file paths stop here.
423
+ const at = target.lastIndexOf('@')
424
+ const name = at > 0 ? target.slice(0, at) : target
425
+ if (!NPM_NAME_RE.test(name)) return null
426
+ const version = await fetchNpmLatest(name)
427
+ if (version === null) return null
428
+ const rewritten = `${name}@${version}`
429
+ if (!EXACT_NPM_TARGET_RE.test(rewritten)) return null
430
+ logEvent('info', 'install', `desktop install boundary needs an exact version: ${target} -> ${rewritten}`)
431
+ return args.map(argument => (argument === target ? rewritten : argument))
349
432
  }
350
433
 
351
434
  /** Desktop runtime also owns cleanup of any operation started by this fiber. */
@@ -799,9 +882,18 @@ export function createDesktopPluginRuntime(
799
882
  }
800
883
 
801
884
  const abort = new AbortController()
802
- let handle: ReturnType<DesktopPnpmLike['runPlugin']>
885
+ let handle: DesktopPnpmHandleLike
803
886
  try {
804
- handle = service.runPlugin(prepared.args, invokingDir, abort.signal)
887
+ // `add` goes through Anywhere Labs' install boundary when that host
888
+ // publishes one, because their Desktop rejects `add` on `runPlugin`
889
+ // outright. Feature-detected, never assumed: this method is theirs
890
+ // alone, and on every other client — including the other desktop app
891
+ // in #292 — the ordinary call below is what runs, unchanged.
892
+ const boundary = prepared.args[0] === 'add' ? service.runExternalMarketPluginInstall : undefined
893
+ const viaBoundary = boundary === undefined ? null : await exactNpmArgs(prepared.args)
894
+ handle = boundary === undefined || viaBoundary === null
895
+ ? service.runPlugin(prepared.args, invokingDir, abort.signal)
896
+ : boundary.call(service, viaBoundary, invokingDir, abort.signal)
805
897
  } catch (error) {
806
898
  const message = error instanceof Error ? error.message : String(error)
807
899
  const busy = /another desktop pnpm operation is already running/i.test(message)
package/src/sources.ts CHANGED
@@ -11,7 +11,7 @@ function validSubpath(subpath: string): boolean {
11
11
  }
12
12
 
13
13
  /** Registry tarball names must be plain npm package names, nothing fancier. */
14
- const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
14
+ export const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/
15
15
 
16
16
  /**
17
17
  * Parse a registry source url: a github repo, optionally with a