feat(plugin): plugin marketplace, durable install persistence, skill manager

Add an in-app plugin marketplace fed by a remote web catalog: the host
plugin-inventory gateway gains marketplaceList/Install/Uninstall remotes,
a durable per-user install table reconciled against the actual profile, and
git/npm/tarball/bundle install paths with non-interactive git and vendored
pnpm. The Web Settings surface gains a sibling 插件市场 tab that lists the
catalog with recommended badges, repository links, and a combined sort
(recommended first, then catalog priority, then id).

Add a host + client skill manager: list local skills by direct filesystem
discovery, and install/uninstall/toggle/edit their SKILL.md from git/npm/
tarball/local sources in the writable user root.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Pine
2026-08-15 17:01:48 +08:00
parent 5f348f9b1c
commit a53769955b
57 changed files with 5168 additions and 129 deletions
+21 -1
View File
@@ -26,7 +26,7 @@ import { createRequire } from 'node:module'
import {
existsSync, lstatSync, mkdirSync, readFileSync, readlinkSync, symlinkSync, unlinkSync, writeFileSync,
} from 'node:fs'
import { basename, dirname, join } from 'node:path'
import { basename, dirname, join, resolve } from 'node:path'
import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
import { applyEntryPatches, type PatchOptions } from '@deepseek-ai/cordis-plugin-include'
import { resolveDshHome } from '@deepseek-ai/dsh-home-paths'
@@ -311,6 +311,26 @@ function normalizeShippedProfile(name: string, dir: string, manifest: ProfileMan
return normalized
}
/**
* Locate the pnpm CLI bundled into the harness. Honors a `DSH_PNPM` override,
* then looks for the vendored pnpm beside the bundled Node's harness root.
* Both the desktop's in-app plugin install and the external `dsh plugin` CLI
* use this, so an external install runs the same vendored pnpm as the app and
* needs no pnpm installed on the machine. In a development checkout (no
* vendored pnpm) it returns undefined and the caller falls back to PATH pnpm.
* @param nodeBin - the Node executable path (`process.execPath`).
* @param env - the process environment.
* @returns the pnpm.cjs path, or undefined when none is vendored.
*/
export function resolvePnpm(nodeBin: string, env: NodeJS.ProcessEnv = process.env): string | undefined {
if (env.DSH_PNPM) return env.DSH_PNPM
// nodeBin is harness/bin/node in the packaged app, so the harness root is
// one level up from bin/; pnpm is vendored under harness/pnpm/.
const harnessRoot = resolve(dirname(nodeBin), '..')
const candidate = join(harnessRoot, 'pnpm', 'node_modules', 'pnpm', 'bin', 'pnpm.cjs')
return existsSync(candidate) ? candidate : undefined
}
/**
* Resolve a package's root directory from one anchor without depending on the
* package exporting `./package.json` (`require.resolve` would need that):