dsh-plugin-capabilities 0.3.7 → 0.3.8

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/src/routes.ts CHANGED
@@ -12,7 +12,7 @@ import { dshLaunch, restartOwnedByShell, scheduleRestart, trustedRestartRequest
12
12
  import { deleteSkill, setSkillPolicy, updateSkillFile, userSkillsDir, validateSkillInput, writeSkill, type SkillInput } from './skills.ts'
13
13
  import { findRootByUrl, loadState, pluginStateDir, removeSkillRoot, type SkillRootEntry } from './state.ts'
14
14
  import { removeTree } from './rmtree.ts'
15
- import { listMcp, removeMcp, setMcpDisabled, upsertMcp, validateMcpInput, type McpInput } from './mcp.ts'
15
+ import { listMcpScoped, mcpScopeDir, removeMcp, setMcpDisabled, upsertMcp, validateMcpInput, type McpInput, type McpScope } from './mcp.ts'
16
16
  import type { CapabilitiesHost, HostSkill } from './types.ts'
17
17
 
18
18
  /**
@@ -57,10 +57,17 @@ function toRootView(entry: SkillRootEntry): SkillRootEntry & { live: boolean } {
57
57
 
58
58
  export interface CapabilitiesRoutesConfig {
59
59
  profileDirPath: string
60
+ /** The dsh home directory: holds the machine-wide `cordis.patch.yml`. */
61
+ dshHomePath: string
60
62
  /** Remount the host-plane skill provider after root-set changes. */
61
63
  remountProvider: () => Promise<void>
62
64
  }
63
65
 
66
+ /** The request's target patch layer; unknown values fall back to the profile. */
67
+ function readScope(value: unknown): McpScope {
68
+ return value === 'global' ? 'global' : 'profile'
69
+ }
70
+
64
71
  /** Register the manager's routes; returns the disposer removing them all. */
65
72
  export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: CapabilitiesRoutesConfig): () => void {
66
73
  const disposers = [
@@ -408,7 +415,7 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
408
415
  sendJson(response, 502, { error: 'market index unavailable (offline?)' })
409
416
  return
410
417
  }
411
- const existing = new Set(listMcp(config.profileDirPath).map(row => row.serverName))
418
+ const existing = new Set(listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map(row => row.serverName))
412
419
  sendJson(response, 200, {
413
420
  source: index.source,
414
421
  servers: (index.servers ?? []).map(server => ({ ...server, installed: existing.has(server.id) })),
@@ -469,7 +476,7 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
469
476
  sendJson(response, 404, { error: 'server not found in the market index' })
470
477
  return
471
478
  }
472
- if (listMcp(config.profileDirPath).some(row => row.serverName === server.id)) {
479
+ if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some(row => row.serverName === server.id)) {
473
480
  sendJson(response, 409, { error: 'already installed' })
474
481
  return
475
482
  }
@@ -503,7 +510,12 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
503
510
  response.end()
504
511
  return
505
512
  }
506
- sendJson(response, 200, { servers: listMcp(config.profileDirPath), restartNeeded: true })
513
+ const listing = listMcpScoped(config.profileDirPath, config.dshHomePath)
514
+ sendJson(response, 200, {
515
+ servers: listing.servers,
516
+ ...(listing.globalError !== undefined ? { globalError: listing.globalError } : {}),
517
+ restartNeeded: true,
518
+ })
507
519
  },
508
520
  }),
509
521
 
@@ -521,14 +533,15 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
521
533
  return
522
534
  }
523
535
  try {
524
- const input = (await readJsonBody(request)) as McpInput
536
+ const input = (await readJsonBody(request)) as McpInput & { scope?: unknown }
525
537
  const invalid = validateMcpInput(input)
526
538
  if (invalid !== null) {
527
539
  sendJson(response, 400, { error: invalid })
528
540
  return
529
541
  }
530
- const id = upsertMcp(config.profileDirPath, input)
531
- sendJson(response, 200, { ok: true, id, restartNeeded: true })
542
+ const scope = readScope(input.scope)
543
+ const id = upsertMcp(mcpScopeDir(scope, config.profileDirPath, config.dshHomePath), input)
544
+ sendJson(response, 200, { ok: true, id, scope, restartNeeded: true })
532
545
  } catch (error) {
533
546
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
534
547
  }
@@ -549,12 +562,13 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
549
562
  return
550
563
  }
551
564
  try {
552
- const body = (await readJsonBody(request)) as { id?: unknown; disabled?: unknown }
565
+ const body = (await readJsonBody(request)) as { id?: unknown; disabled?: unknown; scope?: unknown }
553
566
  if (typeof body.id !== 'string' || typeof body.disabled !== 'boolean') {
554
567
  sendJson(response, 400, { error: 'id and disabled are required' })
555
568
  return
556
569
  }
557
- const ok = setMcpDisabled(config.profileDirPath, body.id, body.disabled)
570
+ const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath)
571
+ const ok = setMcpDisabled(dir, body.id, body.disabled)
558
572
  sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: 'server row not found' })
559
573
  } catch (error) {
560
574
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
@@ -576,12 +590,13 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
576
590
  return
577
591
  }
578
592
  try {
579
- const body = (await readJsonBody(request)) as { id?: unknown }
593
+ const body = (await readJsonBody(request)) as { id?: unknown; scope?: unknown }
580
594
  if (typeof body.id !== 'string') {
581
595
  sendJson(response, 400, { error: 'id is required' })
582
596
  return
583
597
  }
584
- const ok = removeMcp(config.profileDirPath, body.id)
598
+ const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath)
599
+ const ok = removeMcp(dir, body.id)
585
600
  sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: 'server row not found' })
586
601
  } catch (error) {
587
602
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
@@ -597,13 +612,14 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
597
612
  response.end()
598
613
  return
599
614
  }
600
- try {
601
- sendJson(response, 200, {
602
- servers: scanAllMcp(),
603
- // Profile serverNames, so the browser can grey out existing ones.
604
- existing: listMcp(config.profileDirPath).map(row => row.serverName),
605
- })
606
- } catch (error) {
615
+ try {
616
+ sendJson(response, 200, {
617
+ servers: scanAllMcp(),
618
+ // ServerNames present in either patch layer, so the browser can
619
+ // grey out existing ones.
620
+ existing: listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map(row => row.serverName),
621
+ })
622
+ } catch (error) {
607
623
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) })
608
624
  }
609
625
  },
@@ -633,9 +649,8 @@ export function mountCapabilitiesRoutes(host: CapabilitiesHost, config: Capabili
633
649
  const results: Array<{ name: string; ok: boolean; error?: string }> = []
634
650
  for (const server of scanAllMcp()) {
635
651
  if (!wanted.has(`${server.agent}/${server.name}`)) continue
636
- const existing = listMcp(config.profileDirPath).some(row => row.serverName === server.name)
637
- if (existing) {
638
- results.push({ name: server.name, ok: false, error: 'already in profile' })
652
+ if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some(row => row.serverName === server.name)) {
653
+ results.push({ name: server.name, ok: false, error: 'already configured' })
639
654
  continue
640
655
  }
641
656
  const input: McpInput = {