dsh-agent-plugins-market 0.6.0 → 0.6.2

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.
Files changed (134) hide show
  1. package/README.md +143 -169
  2. package/README.zh.md +136 -170
  3. package/client/client.js +8202 -684
  4. package/client/client.js.map +1 -1
  5. package/lib/application/catalog.js +236 -47
  6. package/lib/application/details.js +19 -10
  7. package/lib/application/panel-resources.js +95 -0
  8. package/lib/application/project-agent-roles.js +39 -0
  9. package/lib/catalog/codex-mcp.js +114 -0
  10. package/lib/catalog/component-files.js +151 -0
  11. package/lib/catalog/manifests.js +105 -44
  12. package/lib/catalog/native-project.js +44 -23
  13. package/lib/catalog/paths.js +6 -6
  14. package/lib/catalog/project-config.js +96 -0
  15. package/lib/catalog/project-hooks.js +108 -0
  16. package/lib/catalog/scan-pipeline.js +6 -1
  17. package/lib/catalog/scan-resolvers.js +70 -26
  18. package/lib/catalog/source-catalog.js +16 -3
  19. package/lib/catalog/suite-components.js +125 -0
  20. package/lib/catalog/surfaces.js +60 -46
  21. package/lib/catalog/validate.js +16 -11
  22. package/lib/contracts/market.js +16 -1
  23. package/lib/index.js +187 -35
  24. package/lib/model/layouts.js +49 -0
  25. package/lib/routes.js +106 -6
  26. package/lib/runtime/agent-role-router.js +226 -0
  27. package/lib/runtime/commands-mounts.js +27 -67
  28. package/lib/runtime/feedback-tool.js +181 -0
  29. package/lib/runtime/hooks-mounts.js +66 -13
  30. package/lib/runtime/host-locale.js +23 -7
  31. package/lib/runtime/legacy-root-migration.js +12 -0
  32. package/lib/runtime/lsp-direct-config.js +6 -3
  33. package/lib/runtime/lsp-mounts.js +38 -24
  34. package/lib/runtime/lsp-server-state.js +19 -0
  35. package/lib/runtime/lsp-status.js +3 -2
  36. package/lib/runtime/mcp-backend.js +6 -2
  37. package/lib/runtime/mcp-client/config.js +6 -0
  38. package/lib/runtime/mcp-client/connection.js +8 -2
  39. package/lib/runtime/mcp-client/tools.js +6 -3
  40. package/lib/runtime/mcp-config.js +26 -13
  41. package/lib/runtime/mcp-direct-config.js +56 -0
  42. package/lib/runtime/mcp-mounts.js +25 -9
  43. package/lib/runtime/mcp-overrides.js +11 -5
  44. package/lib/runtime/mcp-status.js +10 -7
  45. package/lib/runtime/model-catalog.js +48 -0
  46. package/lib/runtime/project-runtime.js +136 -0
  47. package/lib/runtime/reconciler.js +38 -28
  48. package/lib/runtime/server-config.js +75 -0
  49. package/lib/runtime/skills-provider.js +31 -77
  50. package/lib/runtime/storage-migration.js +169 -0
  51. package/lib/runtime/subagent-catalog.js +129 -0
  52. package/lib/runtime/user-commands.js +86 -0
  53. package/lib/runtime/user-panels.js +176 -0
  54. package/lib/runtime/user-store.js +142 -0
  55. package/lib/types/application/catalog.d.ts +39 -9
  56. package/lib/types/application/panel-resources.d.ts +13 -0
  57. package/lib/types/application/project-agent-roles.d.ts +3 -0
  58. package/lib/types/application/queries.d.ts +17 -1
  59. package/lib/types/catalog/codex-mcp.d.ts +4 -0
  60. package/lib/types/catalog/component-files.d.ts +17 -0
  61. package/lib/types/catalog/manifests.d.ts +14 -9
  62. package/lib/types/catalog/native-project.d.ts +2 -5
  63. package/lib/types/catalog/paths.d.ts +4 -4
  64. package/lib/types/catalog/project-config.d.ts +6 -0
  65. package/lib/types/catalog/project-hooks.d.ts +10 -0
  66. package/lib/types/catalog/scan-pipeline.d.ts +2 -0
  67. package/lib/types/catalog/scan-resolvers.d.ts +4 -3
  68. package/lib/types/catalog/source-catalog.d.ts +1 -1
  69. package/lib/types/catalog/suite-components.d.ts +5 -0
  70. package/lib/types/catalog/surfaces.d.ts +2 -2
  71. package/lib/types/catalog/validate.d.ts +2 -0
  72. package/lib/types/contracts/market.d.ts +60 -0
  73. package/lib/types/contracts/mcp-status.d.ts +5 -1
  74. package/lib/types/index.d.ts +2 -2
  75. package/lib/types/model/layouts.d.ts +102 -0
  76. package/lib/types/model/types.d.ts +51 -6
  77. package/lib/types/routes.d.ts +6 -1
  78. package/lib/types/runtime/agent-role-router.d.ts +95 -0
  79. package/lib/types/runtime/commands-mounts.d.ts +16 -7
  80. package/lib/types/runtime/feedback-tool.d.ts +49 -0
  81. package/lib/types/runtime/hooks-mounts.d.ts +4 -0
  82. package/lib/types/runtime/host-locale.d.ts +1 -1
  83. package/lib/types/runtime/legacy-root-migration.d.ts +2 -0
  84. package/lib/types/runtime/lsp-mounts.d.ts +4 -0
  85. package/lib/types/runtime/lsp-server-state.d.ts +3 -0
  86. package/lib/types/runtime/lsp-status.d.ts +1 -0
  87. package/lib/types/runtime/mcp-backend.d.ts +9 -1
  88. package/lib/types/runtime/mcp-client/config.d.ts +4 -3
  89. package/lib/types/runtime/mcp-client/tools.d.ts +3 -0
  90. package/lib/types/runtime/mcp-config.d.ts +11 -5
  91. package/lib/types/runtime/mcp-direct-config.d.ts +7 -0
  92. package/lib/types/runtime/mcp-mounts.d.ts +2 -1
  93. package/lib/types/runtime/mcp-overrides.d.ts +2 -0
  94. package/lib/types/runtime/mcp-status.d.ts +1 -1
  95. package/lib/types/runtime/model-catalog.d.ts +7 -0
  96. package/lib/types/runtime/project-runtime.d.ts +32 -0
  97. package/lib/types/runtime/reconciler.d.ts +2 -0
  98. package/lib/types/runtime/server-config.d.ts +10 -0
  99. package/lib/types/runtime/skills-provider.d.ts +1 -5
  100. package/lib/types/runtime/storage-migration.d.ts +15 -0
  101. package/lib/types/runtime/subagent-catalog.d.ts +52 -0
  102. package/lib/types/runtime/user-commands.d.ts +24 -0
  103. package/lib/types/runtime/user-panels.d.ts +91 -0
  104. package/lib/types/runtime/user-store.d.ts +51 -0
  105. package/package.json +10 -3
  106. package/schemas/README.md +69 -7
  107. package/schemas/agent-plugins/spec.md +48 -0
  108. package/schemas/claude-code/marketplace.schema.json +143 -0
  109. package/schemas/claude-code/plugin.schema.json +105 -0
  110. package/schemas/claude-code/spec.md +108 -0
  111. package/schemas/codex/marketplace.schema.json +78 -0
  112. package/schemas/codex/plugin.schema.json +77 -0
  113. package/schemas/codex/spec.md +91 -0
  114. package/schemas/cursor/marketplace.schema.json +82 -0
  115. package/schemas/cursor/plugin.schema.json +58 -0
  116. package/schemas/cursor/spec.md +92 -0
  117. package/schemas/github-copilot/marketplace.schema.json +107 -0
  118. package/schemas/github-copilot/plugin.schema.json +89 -0
  119. package/schemas/github-copilot/spec.md +105 -0
  120. package/schemas/kimi/marketplace.schema.json +38 -0
  121. package/schemas/kimi/plugin.schema.json +84 -0
  122. package/schemas/kimi/spec.md +94 -0
  123. package/schemas/qoder/marketplace.schema.json +115 -0
  124. package/schemas/qoder/plugin.schema.json +127 -0
  125. package/schemas/qoder/spec.md +111 -0
  126. package/schemas/skill-collection/spec.md +29 -0
  127. package/schemas/universal/marketplace.schema.json +98 -0
  128. package/schemas/universal/plugin.schema.json +49 -0
  129. package/schemas/universal/spec.md +53 -0
  130. package/schemas/zcode/marketplace.schema.json +161 -0
  131. package/schemas/zcode/plugin.schema.json +89 -0
  132. package/schemas/zcode/spec.md +102 -0
  133. package/lib/catalog/legacy-root-migration.js +0 -51
  134. package/lib/types/catalog/legacy-root-migration.d.ts +0 -14
@@ -0,0 +1,176 @@
1
+ /**
2
+ * User panel storage: the persistence layer behind the skills / commands /
3
+ * agent-personas panels. Each surface owns one directory of Markdown files
4
+ * under `<userRoot>/user/<kind>/`, so user-authored entries survive restarts,
5
+ * are trivially hand-editable, and stay outside suite checkouts.
6
+ *
7
+ * Skills entries follow the SKILL.md frontmatter grammar (`name`,
8
+ * `description`, optional `whenToUse`, invocation controls) plus a
9
+ * `disabled: true` panel control; commands follow Claude Code command
10
+ * frontmatter (`description`, optional `argument-hint`, `disabled`); agent
11
+ * personas follow Claude Code agents frontmatter (`name`, `description`,
12
+ * optional `whenToUse`, `tools`, `model`, `disabled`).
13
+ * @module runtime/user-panels
14
+ */
15
+ import { join } from 'node:path';
16
+ import { deleteEntryFile, entryExists, listEntryFiles, readEntryFile, USER_ENTRY_NAME, userEntryDir, writeEntryFile } from './user-store.js';
17
+ /** The skill-source label user-panel entries carry into the skill registry. */
18
+ export const USER_PANEL_SKILL_SOURCE = 'user-panel';
19
+ /** Ranks sit behind every shipped root so real files always win a name clash. */
20
+ const USER_PANEL_RANK = 600;
21
+ /** Throwing CRUD over one panel directory, shared by the three panels. */
22
+ export class UserPanelStore {
23
+ dataRoot;
24
+ kind;
25
+ extraNameCheck;
26
+ constructor(dataRoot, kind,
27
+ /** Extra name grammar for this panel (runs after USER_ENTRY_NAME). */
28
+ extraNameCheck = () => true) {
29
+ this.dataRoot = dataRoot;
30
+ this.kind = kind;
31
+ this.extraNameCheck = extraNameCheck;
32
+ }
33
+ /** The panel's directory under the data root. */
34
+ dirPath() {
35
+ return userEntryDir(this.dataRoot, this.kind);
36
+ }
37
+ /** The plugin data root the panel lives under. */
38
+ root() {
39
+ return this.dataRoot;
40
+ }
41
+ get dir() {
42
+ return this.dirPath();
43
+ }
44
+ /** Every entry, sorted by name; strict snapshots propagate temporary I/O failures. */
45
+ async list(strict = false) {
46
+ const files = await listEntryFiles(this.dir, strict);
47
+ return files.map(file => this.serialize(file));
48
+ }
49
+ /** One entry's full record, including the raw file text. */
50
+ async get(name) {
51
+ if (!USER_ENTRY_NAME.test(name))
52
+ return undefined;
53
+ const file = await readEntryFile(join(this.dir, `${name}.md`), name);
54
+ if (file === undefined)
55
+ return undefined;
56
+ return this.serialize(file);
57
+ }
58
+ /**
59
+ * Project one parsed file onto the wire shape. The description prefers the
60
+ * strict frontmatter parse (multi-line YAML like `description: |` survives
61
+ * intact there) and falls back to the shallow line record.
62
+ */
63
+ serialize(file) {
64
+ const strictDescription = typeof file.description === 'string' && file.description !== '' ? file.description : undefined;
65
+ const shallowDescription = typeof file.meta['description'] === 'string' && file.meta['description'] !== '' ? file.meta['description'] : '';
66
+ return {
67
+ name: file.fallbackName,
68
+ description: strictDescription ?? shallowDescription,
69
+ disabled: file.meta['disabled'] === true,
70
+ metadata: file.meta,
71
+ path: file.file,
72
+ content: file.body,
73
+ rawText: file.text,
74
+ origin: 'user'
75
+ };
76
+ }
77
+ /** Create an entry; refuses an occupied name. */
78
+ async create(name, text) {
79
+ if (!USER_ENTRY_NAME.test(name) || !this.extraNameCheck(name))
80
+ throw new Error(`invalid name "${name}" — use lowercase letters, digits, and dashes, starting with a letter or digit`);
81
+ if (await entryExists(this.dir, name))
82
+ throw new Error(`an entry named "${name}" already exists`);
83
+ await writeEntryFile(this.dir, name, text);
84
+ const created = await this.get(name);
85
+ if (created === undefined)
86
+ throw new Error(`entry "${name}" vanished after write`);
87
+ return created;
88
+ }
89
+ /** Replace one entry's file content wholesale. */
90
+ async update(name, text) {
91
+ if (!USER_ENTRY_NAME.test(name) || !this.extraNameCheck(name))
92
+ throw new Error(`invalid entry name "${name}"`);
93
+ if (!(await entryExists(this.dir, name)))
94
+ throw new Error(`no entry named "${name}"`);
95
+ await writeEntryFile(this.dir, name, text);
96
+ }
97
+ /** Delete one entry; a missing file is a no-op (idempotent delete). */
98
+ async remove(name) {
99
+ if (!USER_ENTRY_NAME.test(name))
100
+ throw new Error(`invalid entry name "${name}"`);
101
+ await deleteEntryFile(this.dir, name);
102
+ }
103
+ }
104
+ /** Locate the three stores; constructed once per plugin activation. */
105
+ export function createUserPanelStores(dataRoot) {
106
+ return {
107
+ skills: new UserPanelStore(dataRoot, 'skills', isUserSkillEntryName),
108
+ commands: new UserPanelStore(dataRoot, 'commands'),
109
+ agents: new UserPanelStore(dataRoot, 'agents', isUserSkillEntryName)
110
+ };
111
+ }
112
+ /**
113
+ * The skills/agents panels accept only harness skill-grammar names
114
+ * (`[a-z0-9-]`, no underscores): a candidate name reaching the skill
115
+ * registry's `get()` with an invalid name is dropped there, and several
116
+ * consumers validate strictly, so `_`-named entries would be dead weight.
117
+ * Commands keep the looser command grammar (`_` allowed).
118
+ */
119
+ export function isUserSkillEntryName(name) {
120
+ return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
121
+ }
122
+ /**
123
+ * Skill provider over user skills only. Agent definitions belong to the
124
+ * subagent catalog and never become skill candidates or slash entries.
125
+ */
126
+ export class UserPanelSkillProvider {
127
+ skills;
128
+ t;
129
+ name = 'user-panel';
130
+ constructor(skills, t) {
131
+ this.skills = skills;
132
+ this.t = t;
133
+ }
134
+ async list(_options) {
135
+ const candidates = [];
136
+ const skills = await this.skills.list();
137
+ for (const entry of skills) {
138
+ if (entry.disabled || !isUserSkillEntryName(entry.name))
139
+ continue;
140
+ candidates.push({
141
+ name: entry.name,
142
+ description: this.t('userSkillDescription', { description: entry.description === '' ? entry.name : entry.description }),
143
+ ...(typeof entry.metadata['whenToUse'] === 'string' && entry.metadata['whenToUse'] !== '' ? { whenToUse: entry.metadata['whenToUse'] } : {}),
144
+ invocation: {
145
+ modelInvocable: entry.metadata['disable-model-invocation'] !== true,
146
+ userInvocable: entry.metadata['user-invocable'] !== false
147
+ },
148
+ source: USER_PANEL_SKILL_SOURCE,
149
+ provider: this.name,
150
+ rank: USER_PANEL_RANK,
151
+ locator: { name: entry.name },
152
+ path: entry.path,
153
+ resourceBase: { kind: 'directory', path: this.skills.dirPath() }
154
+ });
155
+ }
156
+ return candidates;
157
+ }
158
+ async get(candidate, _options) {
159
+ const locator = candidate.locator;
160
+ const store = this.skills;
161
+ const entry = await store.get(locator.name);
162
+ if (entry === undefined || entry.disabled)
163
+ return undefined;
164
+ return {
165
+ name: candidate.name,
166
+ description: candidate.description,
167
+ ...(typeof entry.metadata['whenToUse'] === 'string' && entry.metadata['whenToUse'] !== '' ? { whenToUse: entry.metadata['whenToUse'] } : {}),
168
+ invocation: candidate.invocation,
169
+ source: candidate.source,
170
+ provider: this.name,
171
+ resourceBase: { kind: 'directory', path: store.dirPath() },
172
+ path: entry.path,
173
+ content: entry.content
174
+ };
175
+ }
176
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Shared persistence primitives for the market's user-authored panels:
3
+ * skills, commands, and agent personas. Each panel owns one directory of
4
+ * Markdown files under the plugin data root, parsed with the same frontmatter
5
+ * rules the catalog uses, so user entries look exactly like suite entries to
6
+ * the runtime mounts.
7
+ * @module runtime/user-store
8
+ */
9
+ import { mkdir, readdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { parseDocument, stringify } from 'yaml';
12
+ import { parseSkillFrontmatter, stripFrontmatter } from '../catalog/skills-parse.js';
13
+ /** Where user panel entries persist: `<userRoot>/user/<kind>/`. */
14
+ export function userEntryDir(dataRoot, kind) {
15
+ return join(dataRoot, 'user', kind);
16
+ }
17
+ /** `[a-z][a-z0-9_-]*` — the grammar every panel entry name must satisfy. */
18
+ export const USER_ENTRY_NAME = /^[a-z][a-z0-9_-]*$/;
19
+ /** The enablement frontmatter key shared by skills and commands. */
20
+ export const DISABLED_KEY = 'disabled';
21
+ /** Parse full YAML metadata without flattening arrays, mappings, or multiline strings. */
22
+ export function parseFrontmatterRecord(text) {
23
+ const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
24
+ if (match === null) {
25
+ if (/^---(?:\r?\n|$)/.test(text))
26
+ throw new Error('Invalid frontmatter: missing closing delimiter');
27
+ return {};
28
+ }
29
+ const doc = parseDocument(match[1]);
30
+ if (doc.errors.length > 0)
31
+ throw new Error(`Invalid frontmatter: ${doc.errors[0].message}`);
32
+ const value = doc.toJS({ maxAliasCount: 100 });
33
+ if (value === null)
34
+ return {};
35
+ if (typeof value !== 'object' || Array.isArray(value))
36
+ throw new Error('Frontmatter must be a mapping');
37
+ return value;
38
+ }
39
+ /** Read one entry; strict runtime snapshots throw on I/O errors other than a vanished file. */
40
+ export async function readEntryFile(file, fallbackName, strict = false) {
41
+ if (fallbackName !== '' && !USER_ENTRY_NAME.test(fallbackName))
42
+ return undefined;
43
+ let text;
44
+ try {
45
+ text = await readFile(file, 'utf8');
46
+ }
47
+ catch (error) {
48
+ if (strict && error.code !== 'ENOENT')
49
+ throw error;
50
+ return undefined;
51
+ }
52
+ let meta;
53
+ try {
54
+ meta = parseFrontmatterRecord(text);
55
+ }
56
+ catch (error) {
57
+ meta = { disabled: true, validationError: String(error) };
58
+ }
59
+ const parsed = parseSkillFrontmatter(text, fallbackName);
60
+ const name = typeof parsed === 'string' ? fallbackName : typeof parsed.name === 'string' && parsed.name !== '' ? parsed.name : fallbackName;
61
+ return {
62
+ fallbackName,
63
+ name,
64
+ file,
65
+ text,
66
+ body: stripFrontmatter(text),
67
+ meta,
68
+ ...(typeof parsed === 'string' ? {} : parsed)
69
+ };
70
+ }
71
+ /** List `.md` entries; strict runtime snapshots propagate I/O errors instead of publishing partial inventories. */
72
+ export async function listEntryFiles(dir, strict = false) {
73
+ let entries;
74
+ try {
75
+ entries = await readdir(dir);
76
+ }
77
+ catch (error) {
78
+ if (strict && error.code !== 'ENOENT')
79
+ throw error;
80
+ return [];
81
+ }
82
+ const found = [];
83
+ for (const entry of entries.sort()) {
84
+ if (!entry.endsWith('.md'))
85
+ continue;
86
+ const file = join(dir, entry);
87
+ try {
88
+ const info = await stat(file);
89
+ if (!info.isFile())
90
+ continue;
91
+ }
92
+ catch (error) {
93
+ if (strict && error.code !== 'ENOENT')
94
+ throw error;
95
+ continue;
96
+ }
97
+ const parsed = await readEntryFile(file, entry.slice(0, -3), strict);
98
+ if (parsed !== undefined)
99
+ found.push(parsed);
100
+ }
101
+ return found;
102
+ }
103
+ /** Write one panel entry; the name becomes `<name>.md`. */
104
+ export async function writeEntryFile(dir, name, text) {
105
+ assertEntryName(name);
106
+ parseFrontmatterRecord(text);
107
+ await mkdir(dir, { recursive: true });
108
+ await writeFile(join(dir, `${name}.md`), text, 'utf8');
109
+ }
110
+ /**
111
+ * Enforce the entry-name grammar at every filesystem choke point. The name
112
+ * is interpolated into `join(dir, name + '.md')`, so an unchecked name with
113
+ * separators or `..` could address any `.md` file (or directory) on disk —
114
+ * read, write, and delete all go through this guard.
115
+ */
116
+ function assertEntryName(name) {
117
+ if (!USER_ENTRY_NAME.test(name))
118
+ throw new Error(`invalid entry name "${name}" — use lowercase letters, digits, dashes, or underscores, starting with a letter`);
119
+ }
120
+ /** Delete one panel entry file; missing files resolve silently. */
121
+ export async function deleteEntryFile(dir, name) {
122
+ assertEntryName(name);
123
+ await rm(join(dir, `${name}.md`), { force: true });
124
+ }
125
+ /** Whether an entry file with that name already exists. */
126
+ export async function entryExists(dir, name) {
127
+ if (!USER_ENTRY_NAME.test(name))
128
+ return false;
129
+ try {
130
+ await stat(join(dir, `${name}.md`));
131
+ return true;
132
+ }
133
+ catch {
134
+ return false;
135
+ }
136
+ }
137
+ /** Serialize a frontmatter block from a shallow record (deterministic key order). */
138
+ export function serializeFrontmatter(meta) {
139
+ if (Object.keys(meta).length === 0)
140
+ return '';
141
+ return `---\n${stringify(meta, { sortMapEntries: true })}---\n`;
142
+ }
@@ -7,7 +7,7 @@ import { type HostMcpClientProbe, type McpBackend } from '../runtime/mcp-backend
7
7
  import { type DownloadRegionSetting, type EffectiveRegion } from '../runtime/regions.js';
8
8
  import type { McpStatusPayload } from '../contracts/mcp-status.js';
9
9
  import type { LspStatusPayload } from '../contracts/lsp-status.js';
10
- import type { OverviewPayload, SkillContent, SuiteDetail } from '../contracts/market.js';
10
+ import type { OverviewPayload, ServerConfigPayload, SkillContent, SuiteDetail } from '../contracts/market.js';
11
11
  import { type SourceKind, type SourceRef, type Suite, type SuiteDimension, type SuiteSurfaceKey } from '../model/types.js';
12
12
  /**
13
13
  * Git network tuning, plumbed from the host config to every remote-touching
@@ -70,12 +70,13 @@ export declare class Catalog {
70
70
  * discovery (cheap mapping) instead of re-walking every checkout — a full
71
71
  * rescan of a 2,500-suite catalog costs ~1s and UI mutations happen in
72
72
  * bursts. Content-changing mutations (add / update / remove / adopt /
73
- * refresh / acquire) set {@link scanCacheDirty}, which bypasses the cache
74
- * until the next scan; the TTL bounds staleness for in-place working-tree
73
+ * refresh / acquire) clear every cached discovery generation; the TTL
74
+ * bounds staleness for in-place working-tree
75
75
  * edits of local sources.
76
76
  */
77
77
  private readonly scanCache;
78
- private scanCacheDirty;
78
+ private scanGeneration;
79
+ private readonly scanPromises;
79
80
  private static readonly SCAN_CACHE_TTL_MS;
80
81
  private static readonly SCAN_CACHE_MAX_ENTRIES;
81
82
  /**
@@ -107,6 +108,9 @@ export declare class Catalog {
107
108
  private mcpBackendWriter;
108
109
  /** Download-region source (the host settings namespace scope). */
109
110
  private downloadRegionProvider;
111
+ private scanProjectLayouts;
112
+ /** Apply the host's project-layout switch and invalidate every project snapshot. */
113
+ setScanProjectLayouts(enabled: boolean): Promise<void>;
110
114
  constructor(options: CatalogOptions);
111
115
  /** Install the host tool snapshot provider used by the MCP status surface. */
112
116
  setMcpToolSnapshotProvider(provider: () => readonly McpToolSnapshot[]): void;
@@ -137,12 +141,22 @@ export declare class Catalog {
137
141
  subscribe(listener: () => void): () => void;
138
142
  /** Build the flat MCP service inventory for the status surface. */
139
143
  mcpStatus(): Promise<McpStatusPayload>;
144
+ /** Persist a user-owned MCP service and reconcile its bridge mount. */
145
+ addMcpServer(name: string, server: unknown): Promise<void>;
146
+ /** Read effective service configuration without exposing credential literals. */
147
+ serverConfig(kind: 'mcp' | 'lsp', id: string): Promise<ServerConfigPayload>;
148
+ private resolveServerConfig;
149
+ /** Validate a complete replacement before writing; plugin checkouts remain untouched. */
150
+ saveServerConfig(kind: 'mcp' | 'lsp', id: string, config: unknown): Promise<void>;
140
151
  /** The LSP status surface: declared servers merged with mount diagnostics. */
141
152
  lspStatus(): Promise<LspStatusPayload>;
142
153
  /** The user's direct LSP server table (normalized specs). */
143
154
  lspServers(): Promise<Record<string, import('../model/types.js').LspServerSpec>>;
155
+ /** Create one direct LSP declaration without replacing other user servers. */
156
+ addLspServer(name: string, config: unknown): Promise<void>;
144
157
  /** Validate and persist the user's direct LSP server table. */
145
158
  setLspServers(raw: unknown): Promise<Record<string, import('../model/types.js').LspServerSpec>>;
159
+ setLspServerEnabled(id: string, enabled: boolean): Promise<void>;
146
160
  /** One suite's persisted MCP overrides, addressed by qualified suite id. */
147
161
  mcpOverrides(sourceId: string, suiteId: string): Promise<McpSuiteOverrides>;
148
162
  /**
@@ -183,8 +197,8 @@ export declare class Catalog {
183
197
  setMcpBackendWriter(writer: (backend: McpBackend) => Promise<void>): void;
184
198
  /** Install the download-region source (the host settings namespace scope). */
185
199
  setDownloadRegionProvider(provider: () => Promise<DownloadRegionSetting>): void;
186
- /** All persisted MCP overrides keyed by qualified suite id (mount-time provider). */
187
- allMcpOverrides(): Promise<Map<string, McpSuiteOverrides>>;
200
+ /** MCP overrides for a supplied runtime selection, or the full market catalog when omitted. */
201
+ allMcpOverrides(suites?: readonly Suite[]): Promise<Map<string, McpSuiteOverrides>>;
188
202
  /** Load persisted user state once at plugin activation. */
189
203
  load(): Promise<void>;
190
204
  get sources(): SourceRef[];
@@ -201,7 +215,7 @@ export declare class Catalog {
201
215
  skillContent(sourceId: string, suiteId: string, skillName: string): Promise<SkillContent>;
202
216
  /** The full market overview from one user snapshot. */
203
217
  overview(): Promise<OverviewPayload>;
204
- /** Enabled user-dimension suites from one user snapshot. */
218
+ /** Runtime discovery scans only sources containing an enabled install. No acquisition or network access. */
205
219
  enabledUserSuites(): Promise<Suite[]>;
206
220
  /** Enabled user- and project-dimension suites for a workspace cwd. */
207
221
  enabledSuitesForCwd(cwd: string): Promise<{
@@ -271,8 +285,16 @@ export declare class Catalog {
271
285
  kind?: SourceKind;
272
286
  sha256?: string;
273
287
  }): Promise<void>;
274
- /** Remove a source, its install entries, and its managed checkout. */
275
- removeSource(sourceId: string): Promise<void>;
288
+ /**
289
+ * Remove a source and its install entries. `deleteCheckout` (the UI's
290
+ * "also delete the managed market directory" option) physically removes the
291
+ * `.sources/<id>` checkout — including an adopted one, since a directory
292
+ * under `.sources` is manager-owned storage. A `local` source whose URL
293
+ * points outside `.sources` is registration-only: its directory is never
294
+ * touched. Deleting the checkout is what keeps a removed source from
295
+ * reappearing as an unmanaged entry.
296
+ */
297
+ removeSource(sourceId: string, deleteCheckout?: boolean): Promise<void>;
276
298
  /** Refresh one source checkout, or every source when sourceId is omitted. */
277
299
  refreshSource(sourceId?: string): Promise<void>;
278
300
  /** Install a suite from a source and enable it. The client confirms the
@@ -287,6 +309,13 @@ export declare class Catalog {
287
309
  setSurface(sourceId: string, suiteId: string, surface: SuiteSurfaceKey, enabled: boolean): Promise<void>;
288
310
  /** Append config-seeded sources missing from user state and persist them. */
289
311
  mergeSources(sources: SourceRef[]): Promise<void>;
312
+ /**
313
+ * Panel changed hook: the HTTP layer notifies after a user-panel mutation
314
+ * (skills / commands / agent personas). Catalog state is untouched — this
315
+ * only runs the change pipeline so the runtime remounts commands and the
316
+ * skill providers re-read their catalogs.
317
+ */
318
+ notifyPanelsChanged(): Promise<void>;
290
319
  private setInstalled;
291
320
  /**
292
321
  * Acquire one source's content by kind: clone (git), download+extract
@@ -299,6 +328,7 @@ export declare class Catalog {
299
328
  private sourceCheckoutPath;
300
329
  private buildSnapshot;
301
330
  private invalidateSnapshot;
331
+ private invalidateScans;
302
332
  /**
303
333
  * Invalidate snapshots and run the change pipeline. `keepScanCache` marks
304
334
  * state-only mutations (install, enable, surface toggles, MCP overrides)
@@ -0,0 +1,13 @@
1
+ import type { UserPanelEntryWire, UserPanelKind } from '../contracts/market.js';
2
+ import type { UserPanelStore } from '../runtime/user-panels.js';
3
+ import type { Catalog } from './catalog.js';
4
+ /** The routes consume this structural surface, also implemented by user-only stores in tests. */
5
+ export interface PanelResourceStore {
6
+ /** Strict runtime reads propagate I/O failures so catalogs cannot publish partial replacements. */
7
+ list(strict?: boolean): Promise<UserPanelEntryWire[]>;
8
+ get(id: string): Promise<UserPanelEntryWire | undefined>;
9
+ create(name: string, text: string): Promise<UserPanelEntryWire>;
10
+ update(id: string, text: string): Promise<void>;
11
+ remove(id: string): Promise<void>;
12
+ }
13
+ export declare function createPanelResources(catalog: Catalog, users: Record<UserPanelKind, UserPanelStore>): Record<UserPanelKind, PanelResourceStore>;
@@ -0,0 +1,3 @@
1
+ import { type AgentRoleEntry } from '../runtime/agent-role-router.js';
2
+ import type { Catalog } from './catalog.js';
3
+ export declare function projectAgentRoles(catalog: Catalog, parent: unknown): Promise<AgentRoleEntry[]>;
@@ -5,8 +5,10 @@ import type { OverviewPayload, SkillContent, SourceProgress, SuiteDetail } from
5
5
  import type { SourceRef, SuiteSurfaceKey } from '../model/types.js';
6
6
  import type { McpServerOverride, McpSuiteOverrides } from '../runtime/mcp-overrides.js';
7
7
  import type { HostMcpClientProbe, McpBackend } from '../runtime/mcp-backend.js';
8
+ import type { ServerConfigPayload } from '../contracts/market.js';
8
9
  /** Read-only market operations required by HTTP routes. */
9
10
  export interface MarketQueries {
11
+ serverConfig(kind: 'mcp' | 'lsp', id: string): Promise<ServerConfigPayload>;
10
12
  readonly sources: SourceRef[];
11
13
  overview(): Promise<OverviewPayload>;
12
14
  mcpStatus(): Promise<McpStatusPayload>;
@@ -19,6 +21,9 @@ export interface MarketQueries {
19
21
  }
20
22
  /** Mutating market operations required by HTTP routes. */
21
23
  export interface MarketMutations {
24
+ saveServerConfig(kind: 'mcp' | 'lsp', id: string, config: unknown): Promise<void>;
25
+ addMcpServer(name: string, server: unknown): Promise<void>;
26
+ addLspServer(name: string, config: unknown): Promise<void>;
22
27
  addSource(input: {
23
28
  url: string;
24
29
  branch?: string;
@@ -33,7 +38,11 @@ export interface MarketMutations {
33
38
  kind?: 'git' | 'local' | 'archive';
34
39
  sha256?: string;
35
40
  }): Promise<void>;
36
- removeSource(sourceId: string): Promise<void>;
41
+ /**
42
+ * Remove a source registration; `deleteCheckout` also physically deletes
43
+ * its managed `.sources/<id>` checkout. External local paths are never deleted.
44
+ */
45
+ removeSource(sourceId: string, deleteCheckout?: boolean): Promise<void>;
37
46
  /** Register an unmanaged `.sources/` checkout in place (manual-clone repair). */
38
47
  adoptSource(id: string): Promise<SourceRef>;
39
48
  refreshSource(sourceId?: string): Promise<void>;
@@ -44,6 +53,7 @@ export interface MarketMutations {
44
53
  setMcpOverride(sourceId: string, suiteId: string, serverKey: string, override: McpServerOverride | null): Promise<void>;
45
54
  /** Validate and persist the user's direct LSP server table. */
46
55
  setLspServers(raw: unknown): Promise<Record<string, import('../model/types.js').LspServerSpec>>;
56
+ setLspServerEnabled(id: string, enabled: boolean): Promise<void>;
47
57
  /** Re-run the MCP reconcile pass: retries failed mounts and clears residual tools. */
48
58
  retryMounts(): Promise<void>;
49
59
  reauthorizeMcpServer(serverName: string): Promise<void>;
@@ -59,6 +69,12 @@ export interface MarketMutations {
59
69
  }>;
60
70
  /** Switch the MCP mount backend and remount every suite server through it. */
61
71
  setMcpBackend(backend: McpBackend): Promise<void>;
72
+ /**
73
+ * Panel change hook: the HTTP layer notifies after a user-panel mutation
74
+ * (skills / commands / agent personas) so the runtime remounts commands
75
+ * and the skill providers re-read their catalogs.
76
+ */
77
+ notifyPanelsChanged(): Promise<void>;
62
78
  }
63
79
  /** Complete application surface required by the HTTP routes. */
64
80
  export type MarketService = MarketQueries & MarketMutations;
@@ -0,0 +1,4 @@
1
+ /** Codex TOML MCP tables normalize into the existing transport and credential contracts. */
2
+ import type { McpServer } from '../model/types.js';
3
+ /** Unknown or malformed server options reject that server; other servers stay independently usable. */
4
+ export declare function normalizeCodexMcp(raw: Record<string, unknown>, errors: string[]): Record<string, McpServer>;
@@ -0,0 +1,17 @@
1
+ import type { SuiteMarkdownResource } from '../model/types.js';
2
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
3
+ /** Reject both lexical and symlink escapes. Optional defaults may be absent without a diagnostic. */
4
+ export declare function componentPath(root: string, value: string, errors: string[], required?: boolean): Promise<string | undefined>;
5
+ /** Commands and agents may declare one file, several files, or recursive directories. */
6
+ export declare function discoverMarkdownResources(root: string, component: 'commands' | 'agents', declaration: unknown, manifestPath: string, errors: string[], extensions?: readonly string[]): Promise<SuiteMarkdownResource[]>;
7
+ export interface ComponentDocument {
8
+ path?: string;
9
+ value: Record<string, unknown>;
10
+ }
11
+ /** A malformed declared file invalidates that configuration surface instead of falling back. */
12
+ export declare function componentDocuments(root: string, declaration: unknown, errors: string[], allowDirectory?: boolean): Promise<ComponentDocument[] | undefined>;
13
+ export declare function firstComponentFile(root: string, candidates: readonly string[]): Promise<string | undefined>;
14
+ /** Legacy/native suites without catalog resource lists keep their conventional directory behavior. */
15
+ export declare function defaultMarkdownResources(root: string, component: 'agents' | 'commands'): Promise<SuiteMarkdownResource[]>;
16
+ export declare function resourceText(resource: SuiteMarkdownResource): Promise<string>;
17
+ export declare function resourceCommandName(name: string): string;
@@ -1,5 +1,6 @@
1
- import type { SuiteManifest } from '../model/types.js';
2
- export type ManifestKind = 'agent-plugin-v1' | 'universal' | 'claude-code' | 'cursor' | 'kimi' | 'codex';
1
+ import type { SuiteManifest, SuiteComponents } from '../model/types.js';
2
+ import { type ManifestKind } from '../model/layouts.js';
3
+ export type { ManifestKind } from '../model/layouts.js';
3
4
  /** One manifest candidate: its file path and the dialect it selects. */
4
5
  export interface ManifestCandidate {
5
6
  kind: ManifestKind;
@@ -11,17 +12,20 @@ export declare function detectManifest(dir: string): Promise<ManifestCandidate |
11
12
  export declare function hasSuiteManifest(dir: string): Promise<boolean>;
12
13
  /** Fallback suite identity for manifest-less skill collections. */
13
14
  export declare function syntheticManifestName(root: string): string;
15
+ export declare function componentDeclarations(record: object): SuiteComponents;
14
16
  /**
15
17
  * Parse one manifest document into a normalized SuiteManifest. The v1 dialect
16
18
  * is schema-validated (fail-closed); the others are structurally read with
17
19
  * light tolerance, and `hint` (a marketplace plugin entry) fills in gaps.
18
20
  */
19
- export declare function readManifest(root: string, errors: string[], hint: {
21
+ export declare function readManifest(root: string, errors: string[], hint: ({
20
22
  name?: string;
21
23
  version?: string;
22
24
  description?: string;
23
- } | undefined): Promise<SuiteManifest | undefined>;
24
- export interface MarketplaceEntry {
25
+ } & SuiteComponents) | undefined): Promise<SuiteManifest | undefined>;
26
+ export interface MarketplaceEntry extends SuiteComponents {
27
+ layout?: ManifestKind;
28
+ strict?: boolean;
25
29
  name?: string;
26
30
  version?: string;
27
31
  description?: string;
@@ -36,6 +40,7 @@ export interface MarketplaceEntry {
36
40
  };
37
41
  }
38
42
  export interface Marketplace {
43
+ pluginRoot?: string;
39
44
  name?: string;
40
45
  entries: MarketplaceEntry[];
41
46
  }
@@ -45,11 +50,11 @@ export interface ReadMarketplaceResult extends Marketplace {
45
50
  }
46
51
  /**
47
52
  * Read every marketplace manifest the checkout carries, in dialect
48
- * precedence order (Claude Code before Codex). Both dialects can coexist
49
- * (e.g. a repo shipping one marketplace per agent); callers decide which
50
- * dialect's entries win, so no dialect is silently shadowed.
53
+ * precedence order shared with suite manifests, then the shared root catalog.
54
+ * The scan strategy selects the first catalog that produces suites; an empty
55
+ * or invalid catalog allows the next candidate to be tried.
51
56
  */
52
- export declare function readMarketplaces(checkoutDir: string): Promise<ReadMarketplaceResult[]>;
57
+ export declare function readMarketplaces(checkoutDir: string, errors?: string[]): Promise<ReadMarketplaceResult[]>;
53
58
  /** Read the highest-precedence marketplace manifest, or undefined when absent. */
54
59
  export declare function readMarketplace(checkoutDir: string): Promise<Marketplace | undefined>;
55
60
  /** Resolve one marketplace entry to a local checkout-relative directory, or
@@ -1,14 +1,11 @@
1
1
  import type { Suite, SuiteDimension } from '../model/types.js';
2
+ import { type ProjectLayout } from '../model/layouts.js';
2
3
  /**
3
4
  * Project-local directory conventions, in precedence order. Each entry maps
4
5
  * one agent convention onto the content subdirectories it owns.
5
6
  */
6
7
  export declare const NATIVE_PROJECT_DIRS: readonly NativeProjectDir[];
7
- export interface NativeProjectDir {
8
- dirName: string;
9
- label: string;
10
- subdirs: readonly string[];
11
- }
8
+ export type NativeProjectDir = ProjectLayout;
12
9
  /**
13
10
  * Discover native project-layout suites under a project root. Each
14
11
  * convention directory that carries content becomes one synthetic suite;
@@ -7,14 +7,14 @@ export declare const STATE_FILE_NAME = "state.json";
7
7
  export declare function expandHome(path: string): string;
8
8
  /** Resolve the harness home (`$DSH_HOME` or `~/.dsh`). */
9
9
  export declare function resolveDshHome(): string;
10
- /** Resolve the user-dimension suite root. */
11
- export declare function resolveUserRoot(configUserRoot?: string): string;
10
+ /** Resolve the canonical user-dimension root. Legacy overrides are migration inputs only. */
11
+ export declare function resolveUserRoot(_configUserRoot?: string): string;
12
12
  /**
13
13
  * Resolve the suite data root hosting `${PLUGIN_DATA}` directories and the
14
14
  * MCP overrides. Defaults under the user root so the whole plugin persists
15
- * into one directory; an explicit `dataRoot` config still wins.
15
+ * into one directory. Legacy overrides are migration inputs only.
16
16
  */
17
- export declare function resolveDataRoot(configDataRoot?: string, configUserRoot?: string): string;
17
+ export declare function resolveDataRoot(_configDataRoot?: string, _configUserRoot?: string): string;
18
18
  /** Resolve a project root from a workspace cwd: nearest ancestor with `.git`. */
19
19
  export declare function findProjectRoot(cwd: string): Promise<string>;
20
20
  /** Resolve the project-dimension suite root for a workspace cwd. */
@@ -0,0 +1,6 @@
1
+ import type { McpSuiteConfig } from '../model/types.js';
2
+ import type { ProjectMcpFormat } from '../model/layouts.js';
3
+ /** Later files override earlier keys; a malformed layer invalidates the whole layout's table. */
4
+ export declare function discoverProjectMcp(projectRoot: string, files: readonly string[], errors: string[], format?: ProjectMcpFormat): Promise<McpSuiteConfig | undefined>;
5
+ /** Missing is undefined; malformed/unreadable is null and carries a diagnostic. */
6
+ export declare function readProjectDocument(projectRoot: string, file: string, errors: string[], format?: 'JSON' | 'TOML'): Promise<Record<string, unknown> | undefined | null>;
@@ -0,0 +1,10 @@
1
+ /** Normalize native Claude-shaped command hooks for the existing host execution bridge. */
2
+ import type { ProjectHooks } from '../model/types.js';
3
+ import type { ProjectHookFormat } from '../model/layouts.js';
4
+ /** Merge settings layers additively, deduplicating identical event/matcher/command triples. */
5
+ export declare function discoverProjectHooks(projectRoot: string, files: readonly string[], errors: string[], format?: ProjectHookFormat): Promise<ProjectHooks | undefined>;
6
+ /** Pure validation shared by native settings and plugin file/inline declarations. */
7
+ export declare function normalizeHookDocuments(projectRoot: string, documents: Array<{
8
+ file: string;
9
+ settings: Record<string, unknown>;
10
+ }>, errors: string[], format?: ProjectHookFormat): ProjectHooks | undefined;