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
+14 -8
View File
@@ -19,6 +19,7 @@ import {
PROFILE_TEMPLATES,
readProfileManifest,
reconcileProfileBundles,
resolvePnpm,
resolveProfileDir,
} from '@deepseek-ai/dsh-app-boot'
import { INSTALL_ANCHOR } from './profile-boot.ts'
@@ -59,17 +60,22 @@ export function runPlugin(profile: string, args: readonly string[]): number {
process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`)
}
const before = readProfileManifest(NAME, dir)
// Windows resolves pnpm through its .cmd shim, which spawn() refuses
// without a shell since the CVE-2024-27980 hardening.
const result = spawnSync('pnpm', args.map(argument => anchorPathSpec(argument, process.cwd())), {
cwd: dir,
stdio: 'inherit',
shell: process.platform === 'win32',
})
// Prefer the pnpm vendored into the packaged harness (deterministic, matching
// the desktop's in-app install and needing no pnpm on the machine); fall back
// to pnpm on PATH in a development checkout. Windows resolves pnpm through
// its .cmd shim, which spawn() refuses without a shell since the
// CVE-2024-27980 hardening; the vendored path runs node against pnpm.cjs.
const anchored = args.map(argument => anchorPathSpec(argument, process.cwd()))
const vendored = resolvePnpm(process.execPath)
const result = vendored === undefined
? spawnSync('pnpm', anchored, { cwd: dir, stdio: 'inherit', shell: process.platform === 'win32' })
: spawnSync(process.execPath, [vendored, ...anchored], { cwd: dir, stdio: 'inherit', shell: process.platform === 'win32' })
if (result.error !== undefined) {
const code = (result.error as NodeJS.ErrnoException).code
if (code === 'ENOENT') {
process.stderr.write(`${NAME}: pnpm not found on PATH — install pnpm to manage profile plugins\n`)
process.stderr.write(
`${NAME}: pnpm not found on PATH and no bundled pnpm is available — install pnpm to manage profile plugins\n`,
)
return 127
}
throw result.error