@noob-stupid/dsh-plugin-console 0.3.15 → 0.3.18

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
@@ -166,7 +166,7 @@ Requires: DSH ≥ 0.1.0-rc.6 (web profile, with `dsh-client-modules` / `dsh-host
166
166
  - **Real-time progress**: a `DSH-Upgrade` console window pops up showing live pnpm download
167
167
  progress; the in-panel progress card shows the waiting time;
168
168
  - **Upgrade protection**: failed installs **auto-rollback** (robocopy, backup verified before
169
- upgrade), version check catches fake success, 10-min hard timeout, stall detection
169
+ upgrade), version check catches fake success, 15-min hard timeout, stall detection
170
170
  (no debug-log updates → auto switch registry), global trap fallback, and 15-min stale-state
171
171
  cleanup — the framework is never left broken;
172
172
  - **pnpm channel**: npm-cli.js freezes at startup in the schtasks task environment (0-byte
package/README.zh.md CHANGED
@@ -152,7 +152,7 @@ git clone https://github.com/Noob-stupid/dsh-plugin-hub /tmp/dsh-plugin-console
152
152
  - **实时进度**:升级时弹出 `DSH-Upgrade` 窗口,实时显示 pnpm 下载进度;面板内进度卡片
153
153
  同步显示等待时长;
154
154
  - **升级保护**:失败**自动回滚**(robocopy,升级前校验回滚点有效)、版本校验防假成功、
155
- 10 分钟硬超时、卡死检测(debug 日志无更新自动切换 registry)、全局异常兜底、
155
+ 15 分钟硬超时、卡死检测(debug 日志无更新自动切换 registry)、全局异常兜底、
156
156
  15 分钟残留状态清理——框架绝不处于损坏状态;
157
157
  - **pnpm 通道**:npm-cli.js 在 schtasks 任务环境启动即卡死(debug 日志 0 字节、网络请求
158
158
  都发不出);升级改用 `corepack pnpm`(实测 0.4s 秒启动、11.5s 装完 rc.8)+ **国内源
package/lib/index.js CHANGED
@@ -236,7 +236,6 @@ function postJsonUrl(url, formBody) {
236
236
  'user-agent': GITHUB_UA,
237
237
  accept: 'application/json',
238
238
  },
239
- rejectUnauthorized: false,
240
239
  timeout: 20000,
241
240
  }, (res) => {
242
241
  const chunks = []
@@ -377,6 +376,8 @@ function cleanupStalePackageDir(profileDir, packageName) {
377
376
 
378
377
  /** 软件源配置文件(registry 列表,安装链按主→备依次尝试)。 */
379
378
  const SOURCES_FILE = join(dshHome(), 'plugin-console-sources.json')
379
+ /** 敏感凭据单独落盘(避免与可分享配置混存):自定义搜索源 headers + Gitee secret/token。 */
380
+ const SOURCES_SECRETS_FILE = join(dshHome(), 'plugin-console-sources.secrets.json')
380
381
  const DEFAULT_SOURCES = {
381
382
  registries: [
382
383
  { id: 'npmmirror', name: 'npmmirror(国内镜像)', url: 'https://registry.npmmirror.com', primary: true },
@@ -425,6 +426,7 @@ function readSources() {
425
426
  const defaults = JSON.parse(JSON.stringify(DEFAULT_SOURCES))
426
427
  try {
427
428
  const data = JSON.parse(readFileSync(SOURCES_FILE, 'utf8'))
429
+ const secrets = readSourceSecrets()
428
430
  const registries = (Array.isArray(data.registries) ? data.registries : [])
429
431
  .filter((r) => r && typeof r.url === 'string' && isAllowedSourceUrl(r.url))
430
432
  .map((r) => ({
@@ -439,13 +441,18 @@ function readSources() {
439
441
  return { id: String(s.id), name: String(s.name ?? s.id), type: 'builtin' }
440
442
  }
441
443
  if (s && typeof s.url === 'string' && s.url.includes('{q}')) {
442
- const headers = {}
443
- if (Array.isArray(s.headers)) {
444
+ let headers = {}
445
+ const secretHeaders = secrets.headers?.[s.id]
446
+ if (secretHeaders && typeof secretHeaders === 'object') {
447
+ headers = { ...secretHeaders }
448
+ } else if (Array.isArray(s.headers)) {
444
449
  for (const h of s.headers) {
445
450
  if (h && typeof h.name === 'string' && h.name !== '' && typeof h.value === 'string') {
446
451
  headers[h.name] = h.value
447
452
  }
448
453
  }
454
+ } else if (s.headers && typeof s.headers === 'object') {
455
+ headers = { ...s.headers }
449
456
  }
450
457
  return {
451
458
  id: String(s.id ?? `search-${Math.random().toString(36).slice(2, 8)}`).slice(0, 40),
@@ -458,16 +465,58 @@ function readSources() {
458
465
  return null
459
466
  })
460
467
  .filter((s) => s !== null)
468
+ const giteeBase = readGiteeConfig(data)
469
+ const gitee = {
470
+ ...giteeBase,
471
+ clientSecret: secrets.gitee?.clientSecret ?? giteeBase.clientSecret,
472
+ token: secrets.gitee?.token ?? giteeBase.token,
473
+ }
461
474
  if (registries.length > 0) {
462
475
  if (!registries.some((r) => r.primary)) registries[0].primary = true
463
- return { registries, searchSources, gitee: readGiteeConfig(data) }
476
+ return { registries, searchSources, gitee }
464
477
  }
465
478
  } catch {}
466
479
  return defaults
467
480
  }
468
481
 
482
+ function readSourceSecrets() {
483
+ try {
484
+ const data = JSON.parse(readFileSync(SOURCES_SECRETS_FILE, 'utf8'))
485
+ return data && typeof data === 'object' ? data : {}
486
+ } catch {
487
+ return {}
488
+ }
489
+ }
490
+
491
+ async function writeSourceSecrets(secrets) {
492
+ const gitee = secrets.gitee ?? {}
493
+ const headers = secrets.headers ?? {}
494
+ const hasGitee = typeof gitee.clientSecret === 'string' && gitee.clientSecret !== '' || typeof gitee.token === 'string' && gitee.token !== ''
495
+ const hasHeaders = Object.keys(headers).some((id) => { const h = headers[id]; return h && typeof h === 'object' && Object.keys(h).length > 0 })
496
+ if (!hasGitee && !hasHeaders) {
497
+ try { rmSync(SOURCES_SECRETS_FILE, { force: true }) } catch {}
498
+ return
499
+ }
500
+ await writeFile(SOURCES_SECRETS_FILE, JSON.stringify({ gitee, headers }, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 })
501
+ }
469
502
  async function writeSources(sources) {
470
- await writeFile(SOURCES_FILE, JSON.stringify(sources, null, 2) + '\n', 'utf8')
503
+ const secrets = { gitee: {}, headers: {} }
504
+ const cleanSearch = (sources.searchSources ?? []).map((s) => {
505
+ if (!s) return s
506
+ if (s.headers && typeof s.headers === 'object' && Object.keys(s.headers).length > 0) {
507
+ secrets.headers[s.id] = { ...s.headers }
508
+ const { headers, ...rest } = s
509
+ return rest
510
+ }
511
+ return s
512
+ })
513
+ const gitee = readGiteeConfig(sources)
514
+ if (typeof gitee.clientSecret === 'string' && gitee.clientSecret !== '') secrets.gitee.clientSecret = gitee.clientSecret
515
+ if (typeof gitee.token === 'string' && gitee.token !== '') secrets.gitee.token = gitee.token
516
+ const cleanGitee = { ...(sources.gitee ?? {}), clientSecret: undefined, token: undefined }
517
+ const main = { ...sources, searchSources: cleanSearch, gitee: cleanGitee }
518
+ await writeFile(SOURCES_FILE, JSON.stringify(main, null, 2) + '\n', 'utf8')
519
+ await writeSourceSecrets(secrets)
471
520
  }
472
521
 
473
522
  /**
@@ -520,6 +569,30 @@ function queuedWrite(fn) {
520
569
  return run
521
570
  }
522
571
 
572
+ /** 解析 DSH 框架版本号为可比较对象;正式版(无预发布段)视为 rc.∞。 */
573
+ function parseFrameworkVersion(value) {
574
+ const m = String(value ?? '').match(/^(\d+)\.(\d+)\.(\d+)(?:-(?:[a-z]+\.)?(\d+))?$/iu)
575
+ if (!m) return -1
576
+ const [, maj, min, pat, rc] = m
577
+ return {
578
+ maj: Number.parseInt(maj, 10),
579
+ min: Number.parseInt(min, 10),
580
+ pat: Number.parseInt(pat, 10),
581
+ rc: rc === undefined ? Number.POSITIVE_INFINITY : Number.parseInt(rc, 10),
582
+ }
583
+ }
584
+
585
+ /** 判断 candidate 是否比 current 更新(候选与当前必须是合法版本号,否则视为不可比)。 */
586
+ function isFrameworkVersionNewer(candidate, current) {
587
+ const a = parseFrameworkVersion(candidate)
588
+ const b = parseFrameworkVersion(current)
589
+ if (a === -1 || b === -1) return false
590
+ if (a.maj !== b.maj) return a.maj > b.maj
591
+ if (a.min !== b.min) return a.min > b.min
592
+ if (a.pat !== b.pat) return a.pat > b.pat
593
+ return a.rc > b.rc
594
+ }
595
+
523
596
  /** 默认 profile 用户补丁层路径(无 include 条目可推导时的兜底)。 */
524
597
  function defaultPatchPath() {
525
598
  const home = dshHome()
@@ -876,6 +949,25 @@ function isLoopback(address) {
876
949
  return address === '127.0.0.1' || address === '::1' || address === '::ffff:127.0.0.1'
877
950
  }
878
951
 
952
+ /** 写请求跨站防护:浏览器同源策略不阻止跨站发送,必须校验 Origin / Sec-Fetch-Site。
953
+ * 非浏览器客户端(无 Origin/Sec-Fetch-Site 头)仍允许,避免 curl/脚本/测试被误伤。 */
954
+ function isAllowedWriteOrigin(req, port) {
955
+ const origin = typeof req.headers?.origin === 'string' ? req.headers.origin : null
956
+ const site = typeof req.headers?.['sec-fetch-site'] === 'string' ? req.headers['sec-fetch-site'] : null
957
+ if (origin !== null && origin !== '' && !ALLOWED_LOCAL_ORIGINS(port).has(origin)) return false
958
+ if (site !== null && site !== 'same-origin' && site !== 'none') return false
959
+ return true
960
+ }
961
+
962
+ function ALLOWED_LOCAL_ORIGINS(port) {
963
+ return new Set([
964
+ `http://127.0.0.1:${port}`,
965
+ `http://localhost:${port}`,
966
+ `http://[::1]:${port}`,
967
+ ])
968
+ }
969
+
970
+
879
971
  function sendJson(res, status, body) {
880
972
  const payload = JSON.stringify(body)
881
973
  res.writeHead(status, {
@@ -915,7 +1007,6 @@ function githubRequestOnce(url, { signal, accept, token, timeout = 20000 } = {})
915
1007
  accept: accept ?? 'application/vnd.github+json',
916
1008
  ...(token ? { authorization: `Bearer ${token}` } : {}),
917
1009
  },
918
- rejectUnauthorized: false,
919
1010
  }, resolve)
920
1011
  req.on('error', reject)
921
1012
  req.setTimeout(timeout, () => req.destroy(new Error('GitHub 请求超时')))
@@ -1100,7 +1191,7 @@ async function githubText(url, signal, token = null) {
1100
1191
  /** 用 curl 子进程请求 JSON(绕过本机中间设备对 node:https TLS 指纹的拦截;curl 走系统网络栈)。 */
1101
1192
  async function curlJson(url, timeout = 15000, headers = {}) {
1102
1193
  const bin = process.platform === 'win32' ? 'curl.exe' : 'curl'
1103
- const args = ['-s', '-m', String(Math.max(5, Math.ceil(timeout / 1000))), '--insecure', '-H', 'accept: application/json', '-w', '\n__HTTP__%{http_code}']
1194
+ const args = ['-s', '-m', String(Math.max(5, Math.ceil(timeout / 1000))), '-H', 'accept: application/json', '-w', '\n__HTTP__%{http_code}']
1104
1195
  for (const [key, value] of Object.entries(headers)) args.push('-H', `${key}: ${value}`)
1105
1196
  args.push(url)
1106
1197
  const { stdout } = await execFileAsync(bin, args, { timeout: timeout + 3000, windowsHide: true, maxBuffer: 8 * 1024 * 1024 })
@@ -1116,7 +1207,7 @@ async function curlText(url, timeout = 10000) {
1116
1207
  const bin = process.platform === 'win32' ? 'curl.exe' : 'curl'
1117
1208
  const { stdout } = await execFileAsync(
1118
1209
  bin,
1119
- ['-s', '-m', String(Math.max(3, Math.ceil(timeout / 1000))), '--insecure', '-H', 'accept: text/plain', '-w', '\n__HTTP__%{http_code}', url],
1210
+ ['-s', '-m', String(Math.max(3, Math.ceil(timeout / 1000))), '-H', 'accept: text/plain', '-w', '\n__HTTP__%{http_code}', url],
1120
1211
  { timeout: timeout + 3000, windowsHide: true, maxBuffer: 8 * 1024 * 1024 },
1121
1212
  )
1122
1213
  const m = String(stdout).match(/\n__HTTP__(\d+)\s*$/u)
@@ -2535,6 +2626,18 @@ export function apply(ctx) {
2535
2626
  sendError(res, 403, '仅允许本机访问')
2536
2627
  return
2537
2628
  }
2629
+ const port = webPort(ctx)
2630
+ const host = typeof req.headers?.host === 'string' ? req.headers.host : ''
2631
+ if (![`127.0.0.1:${port}`, `localhost:${port}`, `[::1]:${port}`].includes(host)) {
2632
+ sendError(res, 403, 'Host 校验失败(仅允许本机访问)')
2633
+ return
2634
+ }
2635
+ // 安全护栏(issue #9):写路由必须是本机同源请求,防止恶意网页跨站驱动敏感操作。
2636
+ const method = req.method ?? 'GET'
2637
+ if (method !== 'GET' && method !== 'HEAD' && !isAllowedWriteOrigin(req, port)) {
2638
+ sendError(res, 403, '跨站请求被拒绝(Origin/Sec-Fetch-Site 校验)')
2639
+ return
2640
+ }
2538
2641
  try {
2539
2642
  await handle(ctx, req, res)
2540
2643
  } catch (error) {
@@ -3331,10 +3434,11 @@ async function handle(ctx, req, res) {
3331
3434
  // 网络黑洞/超时:区分「检测失败」与「无更新」,避免误导用户以为已是最新
3332
3435
  registryError = error instanceof Error ? error.message : String(error)
3333
3436
  }
3334
- // 升级目标:稳定版 latest 优先;latest 与当前相同而 next(预发布渠道)更新时,目标取 next
3335
- const target = latest !== null && current !== null && latest !== current
3437
+ // 升级目标:稳定版 latest 优先;latest 不高于当前而 next(预发布渠道)确实更新时,目标取 next。
3438
+ // 必须用版本号比较而不是字符串不等(避免 current=0.1.1 稳定版时被 next=0.1.1-rc.3 反向降级)。
3439
+ const target = latest !== null && current !== null && isFrameworkVersionNewer(latest, current)
3336
3440
  ? latest
3337
- : (next !== null && current !== null && next !== current ? next : null)
3441
+ : (next !== null && current !== null && isFrameworkVersionNewer(next, current) ? next : null)
3338
3442
  const profileDir = dirname(findPatchPath(ctx))
3339
3443
  // 1) 升级前打包备份现有配置(patch / profile package.json / 插件清单)
3340
3444
  let backupDir = null
@@ -3420,18 +3524,18 @@ async function handle(ctx, req, res) {
3420
3524
  `Set-Location -Path ${ps(dirname(dshDir))}`,
3421
3525
  // npm 安装函数:Start-Process 跑 npm-cli.js(唯一正确入口——lib/cli.js 是函数模块,
3422
3526
  // 直跑只加载不执行,假成功 exit 0)+ --force(显式指定版本但已在 package.json 的
3423
- // semver 范围内时 npm 会跳过安装,假成功)+ 10 分钟总时长硬超时。注意:**不能加
3527
+ // semver 范围内时 npm 会跳过安装,假成功)+ 15 分钟总时长硬超时。注意:**不能加
3424
3528
  // -RedirectStandardOutput/-RedirectStandardError**——schtasks 任务环境下带重定向的
3425
3529
  // Start-Process 子进程会启动即异常退出(ExitCode 读不到、输出 0 字节,实测);
3426
3530
  // 无重定向时 ExitCode 正常(实测 exit=0)。npm 自身 debug 日志(cache\_logs)留档。
3427
- // 超时策略:总时长 10 分钟硬上限 + npm debug 日志 5 分钟无更新判定卡死(网络黑洞/挂起时
3428
- // 快速失败换 registry,不用干等 10 分钟);等待期间每 15 秒更新进度状态(客户端可见)。
3531
+ // 超时策略:总时长 15 分钟硬上限 + npm debug 日志 5 分钟无更新判定卡死(网络黑洞/挂起时
3532
+ // 快速失败换 registry,不用干等 15 分钟);等待期间每 15 秒更新进度状态(客户端可见)。
3429
3533
  // 成功后校验 package.json 版本 === target(防 npm 假成功)。
3430
3534
  // 另注意:npm install 在 schtasks 环境启动慢(约 1-2 分钟 0 日志,初始化/编译缓存),
3431
3535
  // 属正常,5 分钟卡死阈值不会误杀。
3432
3536
  `function Install-Framework($reg) {`,
3433
3537
  ` $startAt = Get-Date`,
3434
- ` $deadline = $startAt.AddMinutes(10)`,
3538
+ ` $deadline = $startAt.AddMinutes(15)`,
3435
3539
  ` $stallDeadline = $startAt.AddMinutes(5)`,
3436
3540
  ` $lastLogM = (Get-Date)`,
3437
3541
  ` $logSeen = $false`,
@@ -3468,7 +3572,7 @@ async function handle(ctx, req, res) {
3468
3572
  ` }`,
3469
3573
  ` }`,
3470
3574
  ` if (-not $done) {`,
3471
- ` Log 'pnpm 超过 10 分钟未完成,判定超时'`,
3575
+ ` Log 'pnpm 超过 15 分钟未完成,判定超时'`,
3472
3576
  ` try { taskkill /F /FI "WINDOWTITLE eq DSH-Upgrade" 2>$null | Out-Null } catch {}`,
3473
3577
  ` return 1`,
3474
3578
  ` }`,
@@ -3583,8 +3687,12 @@ async function handle(ctx, req, res) {
3583
3687
  ` New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null`,
3584
3688
  ` $tgz = Join-Path $tmpDir 'pkg.tgz'`,
3585
3689
  ` $encName = $name.Replace('/','%2f')`,
3586
- ` $url = 'https://registry.npmjs.org/' + $encName + '/-/' + $short + '-' + $refVersion + '.tgz'`,
3690
+ ` $url = 'https://registry.npmmirror.com/' + $encName + '/-/' + $short + '-' + $refVersion + '.tgz'`,
3587
3691
  ` curl.exe -s -L -m 60 -o $tgz $url`,
3692
+ ` if (-not (Test-Path $tgz) -or (Get-Item $tgz).Length -lt 1000) {`,
3693
+ ` $url = 'https://registry.npmjs.org/' + $encName + '/-/' + $short + '-' + $refVersion + '.tgz'`,
3694
+ ` curl.exe -s -L -m 60 -o $tgz $url`,
3695
+ ` }`,
3588
3696
  ` if (-not (Test-Path $tgz) -or (Get-Item $tgz).Length -lt 1000) { Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue; continue }`,
3589
3697
  ` tar -xzf $tgz -C $tmpDir`,
3590
3698
  ` $ver = (Get-Content (Join-Path $tmpDir 'package\\package.json') -Raw | ConvertFrom-Json).version`,
@@ -3612,6 +3720,10 @@ async function handle(ctx, req, res) {
3612
3720
  ` $indexJs = Join-Path $selfDir 'lib\\index.js'`,
3613
3721
  ` $clientJs = Join-Path $selfDir 'lib\\client.js'`,
3614
3722
  ` if (-not (Test-Path $indexJs)) { Log ('自报名校验跳过:index.js 未找到'); return }`,
3723
+ ` # 计算部署目录的完整包名(scoped 包为 @scope/name,非 scoped 包为 name)`,
3724
+ ` $leaf = Split-Path $selfDir -Leaf`,
3725
+ ` $parentLeaf = Split-Path (Split-Path $selfDir -Parent) -Leaf`,
3726
+ ` if ($parentLeaf.StartsWith('@')) { $deployName = $parentLeaf + '/' + $leaf } else { $deployName = $leaf }`,
3615
3727
  ` $idx = Get-Content $indexJs -Raw`,
3616
3728
  ` $nameOk = $idx.Contains("export const name = '" + $expectedName + "'")`,
3617
3729
  ` $clientOk = $true`,
@@ -3619,7 +3731,8 @@ async function handle(ctx, req, res) {
3619
3731
  ` $cli = Get-Content $clientJs -Raw`,
3620
3732
  ` $clientOk = $cli.Contains('id: "' + $expectedName + '"')`,
3621
3733
  ` }`,
3622
- ` if ($nameOk -and $clientOk) { Log ('自报名一致性 OK:' + $expectedName) } else { Log ('⚠️ 自报名一致性异常:部署目录=' + (Split-Path $selfDir -Leaf) + ',期望 ' + $expectedName + '(index=' + $nameOk + ' client=' + $clientOk + ')——请检查部署目录与包名是否匹配') }`,
3734
+ ` $dirOk = ($deployName -eq $expectedName)`,
3735
+ ` if ($nameOk -and $clientOk -and $dirOk) { Log ('自报名一致性 OK:' + $expectedName + ' @ ' + $deployName) } else { Log ('⚠️ 自报名一致性异常:部署目录=' + $deployName + ',期望 ' + $expectedName + '(index=' + $nameOk + ' client=' + $clientOk + ' dir=' + $dirOk + ')——请检查部署目录与包名是否匹配') }`,
3623
3736
  `}`,
3624
3737
  `Verify-SelfNameConsistency '${selfName ?? '@noob-stupid/dsh-plugin-console'}'`,
3625
3738
  pkgArgs.length > 0 ? `SetState 'pkg' '更新官方配套包…'\n Log '更新官方配套包…'\n Set-Location -Path ${ps(profileDir2)}\n if (Test-Path ${ps(corepackJs)}) {\n $pp = Start-Process -FilePath ${ps(nodePath)} -ArgumentList @(${ps(corepackJs)}, 'pnpm', 'update', ${pkgArgs.join(', ')}, '--no-optional', '--registry', 'https://registry.npmmirror.com') -PassThru -WindowStyle Hidden\n if (-not $pp.WaitForExit(300000)) { Stop-Process -Id $pp.Id -Force -ErrorAction SilentlyContinue; Log '官方配套包更新超时(5 分钟),已跳过' } else { try { Log ('官方配套包更新结束(exit=' + $pp.ExitCode + ')') } catch { Log '官方配套包更新结束(退出码读取失败)' } }\n }` : '',
package/package.json CHANGED
@@ -1,39 +1,39 @@
1
- {
2
- "name": "@noob-stupid/dsh-plugin-console",
3
- "version": "0.3.15",
4
- "description": "DSH plugin management panel \u0026 marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
5
- "repository": {
6
- "type": "git",
7
- "url": "https://github.com/Noob-stupid/dsh-plugin-hub"
8
- },
9
- "type": "module",
10
- "main": "./lib/index.js",
11
- "exports": {
12
- ".": "./lib/index.js",
13
- "./client": "./lib/client.js",
14
- "./package.json": "./package.json"
15
- },
16
- "keywords": [
17
- "dsh-plugin",
18
- "deepseek-harness",
19
- "plugin-manager"
20
- ],
21
- "dsh": {
22
- "bundle": {
23
- "patch": "./cordis.patch.yml"
24
- },
25
- "client": {
26
- "platform": "web",
27
- "inject": [
28
- "@deepseek-ai/dsh-client-runtime",
29
- "@deepseek-ai/dsh-client-locale",
30
- "@deepseek-ai/dsh-client-ui-settings"
31
- ]
32
- }
33
- },
34
- "files": [
35
- "lib",
36
- "cordis.patch.yml"
37
- ],
38
- "license": "MIT"
39
- }
1
+ {
2
+ "name": "@noob-stupid/dsh-plugin-console",
3
+ "version": "0.3.18",
4
+ "description": "DSH plugin management panel & marketplace: one-click enable/disable, multi-source market (GitHub/Gitee/custom), static-index market (500+ plugins / 300 skills), skills, suites, and one-click framework upgrade.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Noob-stupid/dsh-plugin-hub"
8
+ },
9
+ "type": "module",
10
+ "main": "./lib/index.js",
11
+ "exports": {
12
+ ".": "./lib/index.js",
13
+ "./client": "./lib/client.js",
14
+ "./package.json": "./package.json"
15
+ },
16
+ "keywords": [
17
+ "dsh-plugin",
18
+ "deepseek-harness",
19
+ "plugin-manager"
20
+ ],
21
+ "dsh": {
22
+ "bundle": {
23
+ "patch": "./cordis.patch.yml"
24
+ },
25
+ "client": {
26
+ "platform": "web",
27
+ "inject": [
28
+ "@deepseek-ai/dsh-client-runtime",
29
+ "@deepseek-ai/dsh-client-locale",
30
+ "@deepseek-ai/dsh-client-ui-settings"
31
+ ]
32
+ }
33
+ },
34
+ "files": [
35
+ "lib",
36
+ "cordis.patch.yml"
37
+ ],
38
+ "license": "MIT"
39
+ }