dsh-agent-plugins-market 0.6.1 → 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.
- package/README.md +143 -169
- package/README.zh.md +136 -170
- package/client/client.js +8192 -691
- package/client/client.js.map +1 -1
- package/lib/application/catalog.js +236 -47
- package/lib/application/details.js +19 -10
- package/lib/application/panel-resources.js +95 -0
- package/lib/application/project-agent-roles.js +39 -0
- package/lib/catalog/codex-mcp.js +114 -0
- package/lib/catalog/component-files.js +151 -0
- package/lib/catalog/manifests.js +105 -44
- package/lib/catalog/native-project.js +44 -23
- package/lib/catalog/paths.js +6 -6
- package/lib/catalog/project-config.js +96 -0
- package/lib/catalog/project-hooks.js +108 -0
- package/lib/catalog/scan-pipeline.js +6 -1
- package/lib/catalog/scan-resolvers.js +70 -26
- package/lib/catalog/source-catalog.js +16 -3
- package/lib/catalog/suite-components.js +125 -0
- package/lib/catalog/surfaces.js +60 -46
- package/lib/catalog/validate.js +16 -11
- package/lib/contracts/market.js +16 -1
- package/lib/index.js +187 -35
- package/lib/model/layouts.js +49 -0
- package/lib/routes.js +106 -6
- package/lib/runtime/agent-role-router.js +226 -0
- package/lib/runtime/commands-mounts.js +27 -67
- package/lib/runtime/feedback-tool.js +181 -0
- package/lib/runtime/hooks-mounts.js +66 -13
- package/lib/runtime/host-locale.js +23 -7
- package/lib/runtime/legacy-root-migration.js +12 -0
- package/lib/runtime/lsp-direct-config.js +6 -3
- package/lib/runtime/lsp-mounts.js +33 -19
- package/lib/runtime/lsp-server-state.js +19 -0
- package/lib/runtime/lsp-status.js +3 -2
- package/lib/runtime/mcp-backend.js +6 -2
- package/lib/runtime/mcp-client/config.js +6 -0
- package/lib/runtime/mcp-client/connection.js +8 -2
- package/lib/runtime/mcp-client/tools.js +6 -3
- package/lib/runtime/mcp-config.js +16 -7
- package/lib/runtime/mcp-direct-config.js +56 -0
- package/lib/runtime/mcp-mounts.js +12 -4
- package/lib/runtime/mcp-overrides.js +11 -5
- package/lib/runtime/mcp-status.js +8 -4
- package/lib/runtime/model-catalog.js +48 -0
- package/lib/runtime/project-runtime.js +136 -0
- package/lib/runtime/reconciler.js +38 -28
- package/lib/runtime/server-config.js +75 -0
- package/lib/runtime/skills-provider.js +31 -77
- package/lib/runtime/storage-migration.js +169 -0
- package/lib/runtime/subagent-catalog.js +129 -0
- package/lib/runtime/user-commands.js +86 -0
- package/lib/runtime/user-panels.js +176 -0
- package/lib/runtime/user-store.js +142 -0
- package/lib/types/application/catalog.d.ts +39 -9
- package/lib/types/application/panel-resources.d.ts +13 -0
- package/lib/types/application/project-agent-roles.d.ts +3 -0
- package/lib/types/application/queries.d.ts +17 -1
- package/lib/types/catalog/codex-mcp.d.ts +4 -0
- package/lib/types/catalog/component-files.d.ts +17 -0
- package/lib/types/catalog/manifests.d.ts +14 -9
- package/lib/types/catalog/native-project.d.ts +2 -5
- package/lib/types/catalog/paths.d.ts +4 -4
- package/lib/types/catalog/project-config.d.ts +6 -0
- package/lib/types/catalog/project-hooks.d.ts +10 -0
- package/lib/types/catalog/scan-pipeline.d.ts +2 -0
- package/lib/types/catalog/scan-resolvers.d.ts +4 -3
- package/lib/types/catalog/source-catalog.d.ts +1 -1
- package/lib/types/catalog/suite-components.d.ts +5 -0
- package/lib/types/catalog/surfaces.d.ts +2 -2
- package/lib/types/catalog/validate.d.ts +2 -0
- package/lib/types/contracts/market.d.ts +60 -0
- package/lib/types/contracts/mcp-status.d.ts +4 -0
- package/lib/types/index.d.ts +2 -2
- package/lib/types/model/layouts.d.ts +102 -0
- package/lib/types/model/types.d.ts +51 -6
- package/lib/types/routes.d.ts +6 -1
- package/lib/types/runtime/agent-role-router.d.ts +95 -0
- package/lib/types/runtime/commands-mounts.d.ts +16 -7
- package/lib/types/runtime/feedback-tool.d.ts +49 -0
- package/lib/types/runtime/hooks-mounts.d.ts +4 -0
- package/lib/types/runtime/host-locale.d.ts +1 -1
- package/lib/types/runtime/legacy-root-migration.d.ts +2 -0
- package/lib/types/runtime/lsp-mounts.d.ts +4 -0
- package/lib/types/runtime/lsp-server-state.d.ts +3 -0
- package/lib/types/runtime/lsp-status.d.ts +1 -0
- package/lib/types/runtime/mcp-backend.d.ts +9 -1
- package/lib/types/runtime/mcp-client/config.d.ts +4 -3
- package/lib/types/runtime/mcp-client/tools.d.ts +3 -0
- package/lib/types/runtime/mcp-direct-config.d.ts +7 -0
- package/lib/types/runtime/mcp-mounts.d.ts +2 -1
- package/lib/types/runtime/mcp-overrides.d.ts +2 -0
- package/lib/types/runtime/model-catalog.d.ts +7 -0
- package/lib/types/runtime/project-runtime.d.ts +32 -0
- package/lib/types/runtime/reconciler.d.ts +2 -0
- package/lib/types/runtime/server-config.d.ts +10 -0
- package/lib/types/runtime/skills-provider.d.ts +1 -5
- package/lib/types/runtime/storage-migration.d.ts +15 -0
- package/lib/types/runtime/subagent-catalog.d.ts +52 -0
- package/lib/types/runtime/user-commands.d.ts +24 -0
- package/lib/types/runtime/user-panels.d.ts +91 -0
- package/lib/types/runtime/user-store.d.ts +51 -0
- package/package.json +9 -2
- package/schemas/README.md +69 -7
- package/schemas/agent-plugins/spec.md +48 -0
- package/schemas/claude-code/marketplace.schema.json +143 -0
- package/schemas/claude-code/plugin.schema.json +105 -0
- package/schemas/claude-code/spec.md +108 -0
- package/schemas/codex/marketplace.schema.json +78 -0
- package/schemas/codex/plugin.schema.json +77 -0
- package/schemas/codex/spec.md +91 -0
- package/schemas/cursor/marketplace.schema.json +82 -0
- package/schemas/cursor/plugin.schema.json +58 -0
- package/schemas/cursor/spec.md +92 -0
- package/schemas/github-copilot/marketplace.schema.json +107 -0
- package/schemas/github-copilot/plugin.schema.json +89 -0
- package/schemas/github-copilot/spec.md +105 -0
- package/schemas/kimi/marketplace.schema.json +38 -0
- package/schemas/kimi/plugin.schema.json +84 -0
- package/schemas/kimi/spec.md +94 -0
- package/schemas/qoder/marketplace.schema.json +115 -0
- package/schemas/qoder/plugin.schema.json +127 -0
- package/schemas/qoder/spec.md +111 -0
- package/schemas/skill-collection/spec.md +29 -0
- package/schemas/universal/marketplace.schema.json +98 -0
- package/schemas/universal/plugin.schema.json +49 -0
- package/schemas/universal/spec.md +53 -0
- package/schemas/zcode/marketplace.schema.json +161 -0
- package/schemas/zcode/plugin.schema.json +89 -0
- package/schemas/zcode/spec.md +102 -0
- package/lib/catalog/legacy-root-migration.js +0 -51
- package/lib/types/catalog/legacy-root-migration.d.ts +0 -14
package/lib/catalog/surfaces.js
CHANGED
|
@@ -9,12 +9,13 @@
|
|
|
9
9
|
* fail-closed: broken files produce a diagnostic and are skipped, never a
|
|
10
10
|
* thrown discovery.
|
|
11
11
|
*/
|
|
12
|
-
import { readFile, readdir,
|
|
13
|
-
import {
|
|
12
|
+
import { readFile, readdir, stat } from 'node:fs/promises';
|
|
13
|
+
import { dirname, join } from 'node:path';
|
|
14
14
|
import { parseSkillFrontmatter } from './skills-parse.js';
|
|
15
15
|
import { isDirectory } from './paths.js';
|
|
16
16
|
import { validateMcpJson } from './validate.js';
|
|
17
|
-
import {
|
|
17
|
+
import { readManifest } from './manifests.js';
|
|
18
|
+
import { componentDocuments, componentPath, firstComponentFile } from './component-files.js';
|
|
18
19
|
const DOT_DIRS = new Set(['.git', '.github', '.claude', '.cursor', '.kimi', '.plugin', '.sources', 'node_modules']);
|
|
19
20
|
/**
|
|
20
21
|
* Resolve a manifest-declared skills path into absolute directories (string or
|
|
@@ -22,35 +23,22 @@ const DOT_DIRS = new Set(['.git', '.github', '.claude', '.cursor', '.kimi', '.pl
|
|
|
22
23
|
* declared path may be (or pass through) a symlink whose target leaves the
|
|
23
24
|
* suite root, which lexical resolution cannot see.
|
|
24
25
|
*/
|
|
25
|
-
async function declaredSkillDirs(root, declared) {
|
|
26
|
+
async function declaredSkillDirs(root, declared, errors) {
|
|
26
27
|
const values = Array.isArray(declared) ? declared : [declared];
|
|
27
28
|
const dirs = [];
|
|
28
|
-
const realRoot = await realpath(root).catch(() => root);
|
|
29
29
|
for (const value of values) {
|
|
30
|
-
if (typeof value !== 'string' || value === '')
|
|
30
|
+
if (typeof value !== 'string' || value === '') {
|
|
31
|
+
if (declared !== undefined)
|
|
32
|
+
errors.push('skills: paths must be non-empty strings');
|
|
31
33
|
continue;
|
|
34
|
+
}
|
|
32
35
|
const cleaned = value.replace(/^\.\//, '');
|
|
33
|
-
const path =
|
|
34
|
-
|
|
35
|
-
const realPath = await realpath(path).catch(() => undefined);
|
|
36
|
-
if (realPath === undefined)
|
|
37
|
-
continue;
|
|
38
|
-
if (isWithin(realRoot, realPath))
|
|
36
|
+
const path = await componentPath(root, cleaned, errors);
|
|
37
|
+
if (path !== undefined)
|
|
39
38
|
dirs.push(path);
|
|
40
39
|
}
|
|
41
40
|
return dirs;
|
|
42
41
|
}
|
|
43
|
-
/**
|
|
44
|
-
* Whether `candidate` stays inside `root` once both are resolved. Callers pass
|
|
45
|
-
* resolved paths: a sibling directory (`<root>-evil`) and any `../` escape
|
|
46
|
-
* must be rejected — a bare string-prefix test admits both.
|
|
47
|
-
*/
|
|
48
|
-
function isWithin(root, candidate) {
|
|
49
|
-
if (candidate === root)
|
|
50
|
-
return true;
|
|
51
|
-
const separator = candidate.includes('\\') && !candidate.includes('/') ? '\\' : '/';
|
|
52
|
-
return candidate.startsWith(`${root}${separator}`);
|
|
53
|
-
}
|
|
54
42
|
/** Discover SKILL.md files under the suite's skills directory, up to 3 levels deep. */
|
|
55
43
|
export async function discoverSkills(root, errors, declared) {
|
|
56
44
|
const skills = [];
|
|
@@ -59,13 +47,13 @@ export async function discoverSkills(root, errors, declared) {
|
|
|
59
47
|
const rootParsed = await parseOneSkill(rootSkill, root, rootName, errors);
|
|
60
48
|
if (rootParsed !== undefined)
|
|
61
49
|
skills.push(rootParsed);
|
|
62
|
-
const skillsDirs = await declaredSkillDirs(root, declared);
|
|
63
|
-
if (
|
|
50
|
+
const skillsDirs = await declaredSkillDirs(root, declared, errors);
|
|
51
|
+
if (declared === undefined) {
|
|
64
52
|
const fallback = join(root, 'skills');
|
|
65
53
|
if (await isDirectory(fallback))
|
|
66
54
|
skillsDirs.push(fallback);
|
|
67
55
|
}
|
|
68
|
-
const seen = new Set();
|
|
56
|
+
const seen = new Set(skills.map(skill => skill.name));
|
|
69
57
|
const pushUnique = (skill) => {
|
|
70
58
|
if (skill === undefined || seen.has(skill.name))
|
|
71
59
|
return;
|
|
@@ -73,6 +61,13 @@ export async function discoverSkills(root, errors, declared) {
|
|
|
73
61
|
skills.push(skill);
|
|
74
62
|
};
|
|
75
63
|
for (const skillsDir of skillsDirs) {
|
|
64
|
+
if (await isFile(skillsDir)) {
|
|
65
|
+
pushUnique(await parseOneSkill(skillsDir, dirname(skillsDir), '', errors));
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
for (const name of await listMdFiles(skillsDir)) {
|
|
69
|
+
pushUnique(await parseOneSkill(join(skillsDir, name), skillsDir, name.replace(/\.md$/, ''), errors));
|
|
70
|
+
}
|
|
76
71
|
// A declared path may be one skill directory (a manifest listing
|
|
77
72
|
// individual skills, e.g. mattpocock) or a container of skills.
|
|
78
73
|
if (await isFile(join(skillsDir, 'SKILL.md'))) {
|
|
@@ -152,29 +147,48 @@ async function parseOneSkill(file, directory, fallbackName, errors) {
|
|
|
152
147
|
return { ...verdict, directory };
|
|
153
148
|
}
|
|
154
149
|
/** Read the suite's MCP config: `mcp.json` or `.mcp.json`, else the winning manifest's inline `mcpServers`. */
|
|
155
|
-
export async function discoverMcp(root, errors) {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
150
|
+
export async function discoverMcp(root, errors, manifest) {
|
|
151
|
+
const resolved = manifest ?? (await readManifest(root, errors, undefined));
|
|
152
|
+
const layout = resolved?.layout;
|
|
153
|
+
const names = layout === 'zcode'
|
|
154
|
+
? ['.mcp.json']
|
|
155
|
+
: layout === 'qoder'
|
|
156
|
+
? ['.mcp.json', 'mcp.json']
|
|
157
|
+
: layout === 'github-copilot' || layout === 'universal'
|
|
158
|
+
? ['.mcp.json', '.github/mcp.json', 'mcp.json']
|
|
159
|
+
: layout === 'codex' || layout === 'claude-code'
|
|
160
|
+
? ['.mcp.json', 'mcp.json']
|
|
161
|
+
: layout === 'cursor'
|
|
162
|
+
? ['mcp.json']
|
|
163
|
+
: ['mcp.json', '.mcp.json'];
|
|
164
|
+
const declared = resolved?.components?.mcpServers;
|
|
165
|
+
const fallback = layout === 'kimi' ? undefined : await firstComponentFile(root, names);
|
|
166
|
+
const additive = layout === 'zcode' || layout === 'claude-code';
|
|
167
|
+
const values = declared === undefined
|
|
168
|
+
? fallback === undefined
|
|
169
|
+
? []
|
|
170
|
+
: [fallback]
|
|
171
|
+
: [...(additive && fallback !== undefined ? [fallback] : []), ...(Array.isArray(declared) ? declared : [declared])];
|
|
172
|
+
if (values.length === 0)
|
|
173
|
+
return undefined;
|
|
174
|
+
const documents = await componentDocuments(root, values, errors);
|
|
175
|
+
if (documents === undefined)
|
|
176
|
+
return undefined;
|
|
177
|
+
const servers = Object.create(null);
|
|
178
|
+
let schema = '';
|
|
179
|
+
for (const document of documents) {
|
|
180
|
+
const isBareFile = document.path === join(root, 'mcp.json');
|
|
181
|
+
const strict = isBareFile && (layout === 'agent-plugin-v1' || (declared === undefined && layout !== 'cursor' && layout !== 'qoder'));
|
|
182
|
+
const result = await validateMcpJson(root, document.value, { strict });
|
|
183
|
+
errors.push(...result.errors);
|
|
184
|
+
if (result.config === undefined)
|
|
166
185
|
return undefined;
|
|
186
|
+
if (result.config !== undefined) {
|
|
187
|
+
Object.assign(servers, result.config.servers);
|
|
188
|
+
schema = result.config.schema;
|
|
167
189
|
}
|
|
168
|
-
const result = await validateMcpJson(root, raw, { strict: name === 'mcp.json' });
|
|
169
|
-
errors.push(...result.errors);
|
|
170
|
-
return result.config;
|
|
171
190
|
}
|
|
172
|
-
|
|
173
|
-
if (inline === undefined)
|
|
174
|
-
return undefined;
|
|
175
|
-
const result = await validateMcpJson(root, { mcpServers: inline }, { strict: false });
|
|
176
|
-
errors.push(...result.errors);
|
|
177
|
-
return result.config;
|
|
191
|
+
return { schema, servers };
|
|
178
192
|
}
|
|
179
193
|
/** Count surfaces for a suite; mcp counts only validated servers, lsp counts inline servers plus directory entries. */
|
|
180
194
|
export async function countSurfaces(root, skills, mcp, lsp) {
|
package/lib/catalog/validate.js
CHANGED
|
@@ -14,6 +14,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
14
14
|
// The 2020-12 dist build's d.ts resolves to a CJS namespace under NodeNext;
|
|
15
15
|
// the runtime default export is the class itself (module.exports = Ajv2020).
|
|
16
16
|
import Ajv2020Default from 'ajv/dist/2020.js';
|
|
17
|
+
import { PLUGIN_ROOT_VARIABLES, PLUGIN_DATA_VARIABLES } from '../model/layouts.js';
|
|
17
18
|
const SCHEMAS_DIR = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'schemas', '1.0.0');
|
|
18
19
|
const Ajv2020Class = Ajv2020Default;
|
|
19
20
|
export const PLUGIN_SCHEMA_ID = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json';
|
|
@@ -51,7 +52,7 @@ export function isRecognizedSchema(value) {
|
|
|
51
52
|
* @returns `undefined` when contained, or the rejection reason.
|
|
52
53
|
*/
|
|
53
54
|
export async function pathContainmentError(pluginRoot, value) {
|
|
54
|
-
const raw = value.replace(/^\$\{
|
|
55
|
+
const raw = value.replace(/^\$\{([A-Z_]+)\}(\/|$)/, (match, name) => (PLUGIN_ROOT_VARIABLES.has(name) ? './' : match));
|
|
55
56
|
if (!raw.startsWith('./'))
|
|
56
57
|
return `path "${value}" must begin with "./" (or ${'${PLUGIN_ROOT}'})`;
|
|
57
58
|
const candidate = resolve(pluginRoot, raw.slice(2));
|
|
@@ -118,7 +119,7 @@ export async function validateMcpJson(pluginRoot, raw, options) {
|
|
|
118
119
|
}
|
|
119
120
|
servers = record;
|
|
120
121
|
}
|
|
121
|
-
const valid =
|
|
122
|
+
const valid = Object.create(null);
|
|
122
123
|
for (const [name, value] of Object.entries(servers)) {
|
|
123
124
|
if (typeof value !== 'object' || value === null) {
|
|
124
125
|
errors.push(`server "${name}": not an object`);
|
|
@@ -127,7 +128,7 @@ export async function validateMcpJson(pluginRoot, raw, options) {
|
|
|
127
128
|
const server = value;
|
|
128
129
|
// Claude Code dialects: `http`/`streamable-http` are the same remote
|
|
129
130
|
// transport, and `local` or a missing `type` (with `command`) mean stdio.
|
|
130
|
-
let type = typeof server.type === 'string' ? server.type : server.command !== undefined ? 'stdio' : undefined;
|
|
131
|
+
let type = typeof server.type === 'string' ? server.type : server.command !== undefined ? 'stdio' : !strict && 'url' in server ? 'streamable-http' : undefined;
|
|
131
132
|
if (type === 'local')
|
|
132
133
|
type = 'stdio';
|
|
133
134
|
if (type === 'http')
|
|
@@ -146,17 +147,21 @@ export async function validateMcpJson(pluginRoot, raw, options) {
|
|
|
146
147
|
errors.push(`server "${name}": stdio servers require a command`);
|
|
147
148
|
continue;
|
|
148
149
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
150
|
+
const command = stdioServer.command.replace(/^\$\{([A-Z_]+)\}\//, (match, name) => (PLUGIN_ROOT_VARIABLES.has(name) ? './' : match));
|
|
151
|
+
if (options?.pathMode !== 'project' && command.includes('/')) {
|
|
152
|
+
if (!command.startsWith('./')) {
|
|
153
|
+
problems.push(`command "${command}" must be a bare executable name or a plugin-relative path beginning with "./"`);
|
|
152
154
|
}
|
|
153
155
|
else {
|
|
154
|
-
const reason = await pathContainmentError(pluginRoot,
|
|
156
|
+
const reason = await pathContainmentError(pluginRoot, command);
|
|
155
157
|
if (reason !== undefined)
|
|
156
158
|
problems.push(reason);
|
|
157
159
|
}
|
|
158
160
|
}
|
|
159
|
-
if (stdioServer.cwd !== undefined &&
|
|
161
|
+
if (stdioServer.cwd !== undefined && typeof stdioServer.cwd !== 'string') {
|
|
162
|
+
problems.push('cwd must be a string');
|
|
163
|
+
}
|
|
164
|
+
else if (options?.pathMode !== 'project' && stdioServer.cwd !== undefined && ![...PLUGIN_DATA_VARIABLES].some(name => stdioServer.cwd.startsWith(`\${${name}}`))) {
|
|
160
165
|
// `.` is the Codex dialect spelling for the plugin root.
|
|
161
166
|
const cwd = stdioServer.cwd === '.' ? './' : stdioServer.cwd;
|
|
162
167
|
const reason = await pathContainmentError(pluginRoot, cwd);
|
|
@@ -167,7 +172,7 @@ export async function validateMcpJson(pluginRoot, raw, options) {
|
|
|
167
172
|
errors.push(...problems.map(problem => `server "${name}": ${problem}`));
|
|
168
173
|
}
|
|
169
174
|
else {
|
|
170
|
-
valid[name] = { ...server, type: 'stdio' };
|
|
175
|
+
valid[name] = { ...server, command, type: 'stdio' };
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
return { config: { schema: typeof record['$schema'] === 'string' ? record['$schema'] : 'native-client', servers: valid }, errors };
|
|
@@ -177,9 +182,9 @@ export async function validateMcpJson(pluginRoot, raw, options) {
|
|
|
177
182
|
* `${NAME:-default}` fallbacks are honored for unset/empty env vars. */
|
|
178
183
|
export function expandPlaceholders(value, pluginRoot, pluginData, env = process.env) {
|
|
179
184
|
return value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)(?::-([^}]*))?\}/g, (match, name, fallback) => {
|
|
180
|
-
if (name
|
|
185
|
+
if (PLUGIN_ROOT_VARIABLES.has(name))
|
|
181
186
|
return pluginRoot;
|
|
182
|
-
if (name
|
|
187
|
+
if (PLUGIN_DATA_VARIABLES.has(name))
|
|
183
188
|
return pluginData;
|
|
184
189
|
return env[name] || fallback || '';
|
|
185
190
|
});
|
package/lib/contracts/market.js
CHANGED
|
@@ -5,10 +5,15 @@ export const MARKET_API_PREFIX = '/api/agent-plugins/';
|
|
|
5
5
|
export const MARKET_ROUTES = {
|
|
6
6
|
overview: `${MARKET_API_PREFIX}overview`,
|
|
7
7
|
mcpStatus: `${MARKET_API_PREFIX}mcp-status`,
|
|
8
|
+
addMcpServer: `${MARKET_API_PREFIX}mcp-servers/add`,
|
|
8
9
|
lspStatus: `${MARKET_API_PREFIX}lsp-status`,
|
|
9
10
|
lspServers: `${MARKET_API_PREFIX}lsp-servers`,
|
|
11
|
+
addLspServer: `${MARKET_API_PREFIX}lsp-servers/add`,
|
|
12
|
+
serverConfig: `${MARKET_API_PREFIX}server-config`,
|
|
13
|
+
saveServerConfig: `${MARKET_API_PREFIX}server-config/save`,
|
|
10
14
|
progress: `${MARKET_API_PREFIX}progress`,
|
|
11
15
|
config: `${MARKET_API_PREFIX}config`,
|
|
16
|
+
modelCatalog: `${MARKET_API_PREFIX}model-catalog`,
|
|
12
17
|
suite: `${MARKET_API_PREFIX}suite`,
|
|
13
18
|
skill: `${MARKET_API_PREFIX}skill`,
|
|
14
19
|
addSource: `${MARKET_API_PREFIX}sources/add`,
|
|
@@ -25,8 +30,18 @@ export const MARKET_ROUTES = {
|
|
|
25
30
|
mcpRetry: `${MARKET_API_PREFIX}mcp-retry`,
|
|
26
31
|
mcpReauthorize: `${MARKET_API_PREFIX}mcp-reauthorize`,
|
|
27
32
|
mcpBackend: `${MARKET_API_PREFIX}mcp-backend`,
|
|
28
|
-
setMcpBackend: `${MARKET_API_PREFIX}set-mcp-backend
|
|
33
|
+
setMcpBackend: `${MARKET_API_PREFIX}set-mcp-backend`,
|
|
34
|
+
userPanel: `${MARKET_API_PREFIX}user-panel`
|
|
29
35
|
};
|
|
36
|
+
/** Build one user-panel list route URL (`GET`, no mutation). */
|
|
37
|
+
export function userPanelRoute(kind) {
|
|
38
|
+
return `${MARKET_ROUTES.userPanel}/${kind}`;
|
|
39
|
+
}
|
|
40
|
+
/** Build one user-panel mutation route URL (all POSTs). */
|
|
41
|
+
export function userPanelMutationRoute(kind, mutation, name) {
|
|
42
|
+
const base = `${userPanelRoute(kind)}/${mutation}`;
|
|
43
|
+
return name === undefined ? base : `${base}?name=${encodeURIComponent(name)}`;
|
|
44
|
+
}
|
|
30
45
|
/** Build a suite-detail URL without duplicating route or query encoding logic. */
|
|
31
46
|
export function suiteRoute(sourceId, suiteId) {
|
|
32
47
|
return `${MARKET_ROUTES.suite}?sourceId=${encodeURIComponent(sourceId)}&suiteId=${encodeURIComponent(suiteId)}`;
|
package/lib/index.js
CHANGED
|
@@ -1,38 +1,62 @@
|
|
|
1
1
|
import { Catalog } from './application/catalog.js';
|
|
2
2
|
import { RuntimeReconciler } from './runtime/reconciler.js';
|
|
3
3
|
import { inspectToolRegistry } from './runtime/tool-registry-observer.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { migratePluginStorage } from './runtime/storage-migration.js';
|
|
5
|
+
import { mountAgentRoleTool } from './runtime/agent-role-router.js';
|
|
6
|
+
import { projectAgentRoles } from './application/project-agent-roles.js';
|
|
7
|
+
import { mountProjectCommands, mountProjectMcp, mountProjectHooks, mountSuiteInstructions } from './runtime/project-runtime.js';
|
|
8
|
+
import { createPanelResources } from './application/panel-resources.js';
|
|
9
|
+
import { resolveDataRoot, resolveUserRoot } from './catalog/paths.js';
|
|
7
10
|
import { mountSuiteRoutes } from './routes.js';
|
|
8
11
|
import { MCP_SETTINGS_NAMESPACE, MarketSettingsSchema, readMcpBackend } from './runtime/mcp-backend.js';
|
|
9
12
|
import { narrowDownloadRegion } from './runtime/regions.js';
|
|
10
13
|
import { SuiteSkillProvider } from './runtime/skills-provider.js';
|
|
11
14
|
import { loadLspServers } from './runtime/lsp-direct-config.js';
|
|
15
|
+
import { loadDisabledLspServers } from './runtime/lsp-server-state.js';
|
|
12
16
|
import { bindHostLocale, loadHostLocale } from './runtime/host-locale.js';
|
|
17
|
+
import { mountFeedbackTool } from './runtime/feedback-tool.js';
|
|
18
|
+
import { createUserPanelStores } from './runtime/user-panels.js';
|
|
19
|
+
import { UserPanelSkillProvider } from './runtime/user-panels.js';
|
|
20
|
+
import { UserCommandMountRegistry } from './runtime/user-commands.js';
|
|
13
21
|
export const name = 'dsh-agent-plugins-market';
|
|
14
22
|
export const inject = ['skills', 'commands'];
|
|
15
|
-
export function apply(ctx, config = {}) {
|
|
23
|
+
export async function apply(ctx, config = {}) {
|
|
16
24
|
const userRoot = resolveUserRoot(config.userRoot);
|
|
17
25
|
const dataRoot = resolveDataRoot(config.dataRoot, userRoot);
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
});
|
|
26
|
+
const migration = await migratePluginStorage(config);
|
|
27
|
+
if (migration.conflicts.length > 0)
|
|
28
|
+
throw new Error(`Plugin storage migration conflicts (original files retained): ${migration.conflicts.join(', ')}`);
|
|
22
29
|
let providerControl;
|
|
30
|
+
let userPanelControl;
|
|
23
31
|
// Host runtime copy resolves from the harness `locale.preference` setting;
|
|
24
32
|
// the async settings read lands before the first session starts in practice.
|
|
25
33
|
const t = bindHostLocale(undefined);
|
|
26
34
|
void loadHostLocale().then(locale => {
|
|
27
35
|
hostLocale.t = locale.t;
|
|
28
36
|
providerControl?.invalidate();
|
|
37
|
+
userPanelControl?.invalidate();
|
|
29
38
|
});
|
|
30
39
|
const hostLocale = { t };
|
|
40
|
+
/** Settings watchers to release at teardown (backend watch + feedback switch). */
|
|
41
|
+
const settingsWatchers = [];
|
|
31
42
|
const runtime = new RuntimeReconciler(ctx, dataRoot, key => hostLocale.t(key));
|
|
32
|
-
|
|
43
|
+
// User panel stores (skills / commands / agent personas) and their runtime
|
|
44
|
+
// contributions: one extra skill provider plus one command mount registry.
|
|
45
|
+
const panels = createUserPanelStores(userRoot);
|
|
46
|
+
const userCommands = new UserCommandMountRegistry(ctx, panels.commands, key => hostLocale.t(key));
|
|
47
|
+
let disposed = false;
|
|
48
|
+
let projectCommands;
|
|
49
|
+
let projectMcp;
|
|
50
|
+
let projectHooks;
|
|
51
|
+
let suitePrompts;
|
|
52
|
+
let reconcileRequested = false;
|
|
53
|
+
let reconciliation;
|
|
54
|
+
const reconcileOnce = async () => {
|
|
33
55
|
try {
|
|
34
|
-
const
|
|
35
|
-
|
|
56
|
+
const suites = await catalog.enabledUserSuites();
|
|
57
|
+
if (disposed)
|
|
58
|
+
return;
|
|
59
|
+
const diagnostics = await runtime.reconcile(suites);
|
|
36
60
|
catalog.mcpDiagnostics = diagnostics.mcp;
|
|
37
61
|
for (const diagnostic of diagnostics.mcp) {
|
|
38
62
|
ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" mcp server "${diagnostic.serverKey}": ${diagnostic.reason}`);
|
|
@@ -56,6 +80,26 @@ export function apply(ctx, config = {}) {
|
|
|
56
80
|
throw error;
|
|
57
81
|
}
|
|
58
82
|
};
|
|
83
|
+
// Settings, credential and catalog events can arrive during a slow MCP
|
|
84
|
+
// connection. Keep one current pass and one fresh follow-up, not a queue
|
|
85
|
+
// of stale snapshots that all reconnect the same servers.
|
|
86
|
+
const reconcileMounts = () => {
|
|
87
|
+
if (disposed)
|
|
88
|
+
return Promise.resolve();
|
|
89
|
+
reconcileRequested = true;
|
|
90
|
+
reconciliation ??= (async () => {
|
|
91
|
+
try {
|
|
92
|
+
do {
|
|
93
|
+
reconcileRequested = false;
|
|
94
|
+
await reconcileOnce();
|
|
95
|
+
} while (reconcileRequested && !disposed);
|
|
96
|
+
}
|
|
97
|
+
finally {
|
|
98
|
+
reconciliation = undefined;
|
|
99
|
+
}
|
|
100
|
+
})();
|
|
101
|
+
return reconciliation;
|
|
102
|
+
};
|
|
59
103
|
/**
|
|
60
104
|
* Coalesce credential updates.
|
|
61
105
|
*
|
|
@@ -78,7 +122,17 @@ export function apply(ctx, config = {}) {
|
|
|
78
122
|
}
|
|
79
123
|
};
|
|
80
124
|
const onChanged = async () => {
|
|
125
|
+
if (disposed)
|
|
126
|
+
return;
|
|
81
127
|
providerControl?.invalidate();
|
|
128
|
+
userPanelControl?.invalidate();
|
|
129
|
+
await projectCommands?.refresh();
|
|
130
|
+
await suitePrompts?.refresh();
|
|
131
|
+
await Promise.all([projectMcp?.refresh(), projectHooks?.refresh()]);
|
|
132
|
+
const userCommandDiagnostics = await userCommands.reconcile().catch(() => []);
|
|
133
|
+
for (const reason of userCommandDiagnostics) {
|
|
134
|
+
ctx.logger?.warn(`[dsh-agent-plugins-market] user commands: ${reason}`);
|
|
135
|
+
}
|
|
82
136
|
await reconcileMounts();
|
|
83
137
|
};
|
|
84
138
|
// The credential store powers the MCP re-authorize action (dropping a grant
|
|
@@ -87,6 +141,9 @@ export function apply(ctx, config = {}) {
|
|
|
87
141
|
// credentials plugin provisions, and a snapshot taken here would be
|
|
88
142
|
// permanently undefined even after the service is live.
|
|
89
143
|
const catalog = new Catalog({ userRoot, dataRoot, onChanged, ...(config.git === undefined ? {} : { git: config.git }) });
|
|
144
|
+
await catalog.load();
|
|
145
|
+
await catalog.mergeSources(config.sources ?? []);
|
|
146
|
+
const resources = createPanelResources(catalog, panels);
|
|
90
147
|
// Mirror oauth.ts's `credentialIdFor`: the record is stored under the folded
|
|
91
148
|
// serverName, so the delete must fold the same way or it misses the record.
|
|
92
149
|
const credentialIdFor = (serverName) => serverName
|
|
@@ -101,35 +158,87 @@ export function apply(ctx, config = {}) {
|
|
|
101
158
|
await store.deleteRecord(`mcp-auth/${credentialIdFor(serverName)}`);
|
|
102
159
|
}
|
|
103
160
|
});
|
|
104
|
-
runtime.setMcpOverridesProvider(() => catalog.allMcpOverrides());
|
|
161
|
+
runtime.setMcpOverridesProvider(async () => catalog.allMcpOverrides(await catalog.enabledUserSuites()));
|
|
105
162
|
// Re-authorize must rebuild the live bridge, not just drop the grant: the
|
|
106
163
|
// registry flags the mount and the following reconcile tears it down and
|
|
107
164
|
// remounts, so the server's 401 restarts the browser authorization.
|
|
108
165
|
catalog.setMcpRemountHook((suiteId, serverKey) => runtime.forceMcpRemount(suiteId, serverKey), serverName => runtime.mcpServerOwner(serverName));
|
|
109
166
|
catalog.setLspStatusSource(runtime.lsp);
|
|
110
167
|
runtime.lsp.setDirectProvider(async () => (await loadLspServers(dataRoot)).servers);
|
|
168
|
+
runtime.lsp.setDisabledProvider(() => loadDisabledLspServers(dataRoot));
|
|
111
169
|
// The MCP backend choice is a host settings namespace (the registration is
|
|
112
170
|
// also what makes the plugin-config tab serve our card). The node half owns
|
|
113
171
|
// it: the provider derives the mount backend from the scope, the writer
|
|
114
|
-
// flips the switch, and the watcher remounts servers when it flips.
|
|
172
|
+
// flips the switch, and the watcher remounts servers when it flips. The
|
|
173
|
+
// same single registration also gates the experience-feedback tool —
|
|
174
|
+
// `settings.register` throws on a duplicate namespace, so there is exactly
|
|
175
|
+
// one inject block and one `register` call.
|
|
176
|
+
//
|
|
177
|
+
// The whole callback is failure-contained on purpose: a throw here is
|
|
178
|
+
// INVISIBLE in the UI (the inject resolves asynchronously and cordis only
|
|
179
|
+
// logs), and it silently removes the namespace from settings.describe —
|
|
180
|
+
// which is exactly what hides the plugin-config card. Every failure mode
|
|
181
|
+
// lands in the logger with a loud prefix instead.
|
|
182
|
+
let feedbackDisposer;
|
|
115
183
|
ctx.inject(['settings'], settingsCtx => {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
184
|
+
try {
|
|
185
|
+
ctx.logger?.info?.('[dsh-agent-plugins-market] settings inject resolved — registering namespace');
|
|
186
|
+
const settings = settingsCtx.settings;
|
|
187
|
+
const scope = settings.register(MCP_SETTINGS_NAMESPACE, MarketSettingsSchema, {
|
|
188
|
+
base: { mcpEnhanced: true, downloadRegion: 'auto', feedbackEnabled: true, scanProjectLayouts: true }
|
|
189
|
+
});
|
|
190
|
+
ctx.logger?.info?.('[dsh-agent-plugins-market] settings namespace registered — plugin-config card will serve');
|
|
191
|
+
catalog.setMcpBackendProvider(async () => (scope.get().mcpEnhanced === false ? 'host' : 'builtin'));
|
|
192
|
+
catalog.setMcpBackendWriter(async (backend) => {
|
|
193
|
+
await scope.update({ mcpEnhanced: backend !== 'host' });
|
|
194
|
+
});
|
|
195
|
+
catalog.setDownloadRegionProvider(async () => narrowDownloadRegion(scope.get().downloadRegion));
|
|
196
|
+
const syncProjectLayouts = () => {
|
|
197
|
+
void catalog.setScanProjectLayouts(scope.get().scanProjectLayouts !== false).catch(error => {
|
|
198
|
+
ctx.logger?.error?.(`[dsh-agent-plugins-market] project layout reconciliation failed: ${String(error)}`);
|
|
199
|
+
});
|
|
200
|
+
};
|
|
201
|
+
syncProjectLayouts();
|
|
202
|
+
settingsWatchers.push(scope.watch(syncProjectLayouts));
|
|
203
|
+
// One-time migration from the earlier data-root settings.json choice.
|
|
204
|
+
void readMcpBackend(dataRoot).then(backend => {
|
|
205
|
+
if (backend === 'host')
|
|
206
|
+
void scope.update({ mcpEnhanced: false }).catch(() => { });
|
|
207
|
+
});
|
|
208
|
+
let previousBackend = scope.get().mcpEnhanced !== false;
|
|
209
|
+
settingsWatchers.push(scope.watch(() => {
|
|
210
|
+
const backend = scope.get().mcpEnhanced !== false;
|
|
211
|
+
if (backend === previousBackend)
|
|
212
|
+
return;
|
|
213
|
+
previousBackend = backend;
|
|
214
|
+
void Promise.all([reconcileMounts(), projectMcp?.refresh()]).catch(() => { });
|
|
215
|
+
}));
|
|
216
|
+
// The experience-feedback model tool: gated by the namespace's
|
|
217
|
+
// `feedbackEnabled` field (default on); the switch unregisters it. The
|
|
218
|
+
// tool mount is doubly contained — its failure must never take the
|
|
219
|
+
// settings namespace (and with it the config card) down with it.
|
|
220
|
+
const syncFeedbackTool = () => {
|
|
221
|
+
try {
|
|
222
|
+
const state = scope.get();
|
|
223
|
+
const wanted = state.feedbackEnabled !== false;
|
|
224
|
+
if (wanted && feedbackDisposer === undefined) {
|
|
225
|
+
feedbackDisposer = mountFeedbackTool(ctx, dataRoot, (key, params) => hostLocale.t(key, params)) ?? undefined;
|
|
226
|
+
}
|
|
227
|
+
else if (!wanted && feedbackDisposer !== undefined) {
|
|
228
|
+
feedbackDisposer();
|
|
229
|
+
feedbackDisposer = undefined;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
ctx.logger?.error?.(`[dsh-agent-plugins-market] feedback tool mount failed: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
syncFeedbackTool();
|
|
237
|
+
settingsWatchers.push(scope.watch(() => syncFeedbackTool()));
|
|
238
|
+
}
|
|
239
|
+
catch (error) {
|
|
240
|
+
ctx.logger?.error?.(`[dsh-agent-plugins-market] settings namespace registration failed — the plugin-config card will be hidden this boot: ${error instanceof Error ? (error.stack ?? error.message) : String(error)}`);
|
|
241
|
+
}
|
|
133
242
|
});
|
|
134
243
|
runtime.setMcpBackendProvider(() => catalog.mcpBackend());
|
|
135
244
|
const eventHost = ctx;
|
|
@@ -148,26 +257,69 @@ export function apply(ctx, config = {}) {
|
|
|
148
257
|
// own server with a clear diagnostic instead of failing mid-registration.
|
|
149
258
|
runtime.setMcpToolNamesProvider(() => inspectToolRegistry(registry).map(tool => tool.name));
|
|
150
259
|
});
|
|
151
|
-
void
|
|
152
|
-
.load()
|
|
260
|
+
void Promise.resolve()
|
|
153
261
|
.then(async () => {
|
|
154
|
-
await
|
|
262
|
+
const userCommandDiagnostics = await userCommands.reconcile().catch(() => []);
|
|
263
|
+
for (const reason of userCommandDiagnostics) {
|
|
264
|
+
ctx.logger?.warn(`[dsh-agent-plugins-market] user commands: ${reason}`);
|
|
265
|
+
}
|
|
155
266
|
await reconcileMounts();
|
|
156
267
|
})
|
|
157
268
|
.catch(() => { });
|
|
158
269
|
ctx.skills.registerProvider(control => {
|
|
159
270
|
providerControl = control;
|
|
160
|
-
return new SuiteSkillProvider(catalog
|
|
271
|
+
return new SuiteSkillProvider(catalog);
|
|
272
|
+
});
|
|
273
|
+
// User panel skills ride a second provider so a panel
|
|
274
|
+
// edit invalidates only its own catalog contribution.
|
|
275
|
+
ctx.skills.registerProvider(control => {
|
|
276
|
+
userPanelControl = control;
|
|
277
|
+
return new UserPanelSkillProvider(panels.skills, key => hostLocale.t(key));
|
|
278
|
+
});
|
|
279
|
+
ctx.inject(['tools', 'llm', 'subagents', 'agents'], hostCtx => {
|
|
280
|
+
hostCtx.effect(() => mountAgentRoleTool(hostCtx, async (parent) => [
|
|
281
|
+
...(await resources.agents.list(true)).map(entry => ({ ...entry, title: entry.name, name: entry.id ?? entry.name })),
|
|
282
|
+
...(await projectAgentRoles(catalog, parent))
|
|
283
|
+
], (key, params) => hostLocale.t(key, params)), 'dsh-agent-plugins-market: agent role routing');
|
|
284
|
+
});
|
|
285
|
+
ctx.inject(['agents'], hostCtx => {
|
|
286
|
+
hostCtx.effect(() => {
|
|
287
|
+
const mounted = mountProjectCommands(hostCtx, catalog, key => hostLocale.t(key));
|
|
288
|
+
const mcp = mountProjectMcp(hostCtx, catalog, dataRoot);
|
|
289
|
+
const hooks = mountProjectHooks(hostCtx, catalog);
|
|
290
|
+
const prompts = mountSuiteInstructions(hostCtx, catalog);
|
|
291
|
+
projectCommands = mounted;
|
|
292
|
+
projectMcp = mcp;
|
|
293
|
+
projectHooks = hooks;
|
|
294
|
+
suitePrompts = prompts;
|
|
295
|
+
return async () => {
|
|
296
|
+
if (projectCommands === mounted)
|
|
297
|
+
projectCommands = undefined;
|
|
298
|
+
if (projectMcp === mcp)
|
|
299
|
+
projectMcp = undefined;
|
|
300
|
+
if (projectHooks === hooks)
|
|
301
|
+
projectHooks = undefined;
|
|
302
|
+
if (suitePrompts === prompts)
|
|
303
|
+
suitePrompts = undefined;
|
|
304
|
+
await Promise.all([mounted.dispose(), mcp.dispose(), hooks.dispose(), prompts.dispose()]);
|
|
305
|
+
};
|
|
306
|
+
}, 'dsh-agent-plugins-market: project command lifecycle');
|
|
161
307
|
});
|
|
162
308
|
ctx.inject(['webServer', 'loader'], hostCtx => {
|
|
163
|
-
hostCtx.effect(() => mountSuiteRoutes(hostCtx, catalog), 'dsh-agent-plugins-market: http routes');
|
|
309
|
+
hostCtx.effect(() => mountSuiteRoutes(hostCtx, catalog, resources), 'dsh-agent-plugins-market: http routes');
|
|
164
310
|
});
|
|
165
311
|
ctx.effect(() => () => {
|
|
312
|
+
disposed = true;
|
|
166
313
|
disposeCredentialUpdates();
|
|
167
314
|
if (credentialFlush !== undefined)
|
|
168
315
|
clearTimeout(credentialFlush);
|
|
169
316
|
credentialFlush = undefined;
|
|
170
317
|
pendingCredentialRefs.clear();
|
|
318
|
+
for (const dispose of settingsWatchers.splice(0))
|
|
319
|
+
dispose();
|
|
320
|
+
feedbackDisposer?.();
|
|
321
|
+
feedbackDisposer = undefined;
|
|
322
|
+
userCommands.disposeAll();
|
|
171
323
|
void runtime.dispose();
|
|
172
324
|
}, 'dsh-agent-plugins-market: lifecycle');
|
|
173
325
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/** Ordered layout declarations shared by manifest and project discovery. */
|
|
2
|
+
export const PLUGIN_LAYOUTS = [
|
|
3
|
+
{ kind: 'agent-plugin-v1', manifest: 'plugin.json', marketplaces: [] },
|
|
4
|
+
{ kind: 'universal', manifest: '.plugin/plugin.json', marketplaces: ['.plugin/marketplace.json'] },
|
|
5
|
+
{ kind: 'claude-code', manifest: '.claude-plugin/plugin.json', marketplaces: ['.claude-plugin/marketplace.json'] },
|
|
6
|
+
{ kind: 'cursor', manifest: '.cursor-plugin/plugin.json', marketplaces: ['.cursor-plugin/marketplace.json'] },
|
|
7
|
+
{ kind: 'kimi', manifest: '.kimi-plugin/plugin.json', marketplaces: ['.kimi-plugin/marketplace.json'] },
|
|
8
|
+
{ kind: 'codex', manifest: '.codex-plugin/plugin.json', marketplaces: ['.agents/plugins/marketplace.json', '.agents/plugins/api_marketplace.json'] },
|
|
9
|
+
{ kind: 'zcode', manifest: '.zcode-plugin/plugin.json', marketplaces: [] },
|
|
10
|
+
{ kind: 'qoder', manifest: '.qoder-plugin/plugin.json', marketplaces: ['.qoder-plugin/marketplace.json'] },
|
|
11
|
+
{ kind: 'github-copilot', manifest: '.github/plugin/plugin.json', marketplaces: ['.github/plugin/marketplace.json'] }
|
|
12
|
+
];
|
|
13
|
+
/** Kimi Code's primary spelling precedes its compatibility spelling within that dialect. */
|
|
14
|
+
export const MANIFEST_ALIASES = { kimi: ['kimi.plugin.json'] };
|
|
15
|
+
/** Equivalent plugin-owned path variables; these never resolve as credentials. */
|
|
16
|
+
export const PLUGIN_ROOT_VARIABLES = new Set(['PLUGIN_ROOT', 'CLAUDE_PLUGIN_ROOT', 'CODEX_PLUGIN_ROOT', 'ZCODE_PLUGIN_ROOT', 'QODER_PLUGIN_ROOT']);
|
|
17
|
+
export const PLUGIN_DATA_VARIABLES = new Set(['PLUGIN_DATA', 'CLAUDE_PLUGIN_DATA', 'ZCODE_PLUGIN_DATA', 'QODER_PLUGIN_DATA']);
|
|
18
|
+
export const PROJECT_LAYOUTS = [
|
|
19
|
+
{
|
|
20
|
+
dirName: '.claude',
|
|
21
|
+
label: 'Claude Code',
|
|
22
|
+
subdirs: ['skills', 'agents', 'commands'],
|
|
23
|
+
mcpFiles: ['.mcp.json'],
|
|
24
|
+
hookFiles: ['.claude/settings.json', '.claude/settings.local.json']
|
|
25
|
+
},
|
|
26
|
+
{ dirName: '.agents', label: 'agents', subdirs: ['skills', 'agents', 'commands'] },
|
|
27
|
+
{ dirName: '.codex', label: 'Codex', subdirs: ['skills'], mcpFiles: ['.codex/config.toml'], mcpFormat: 'codex' },
|
|
28
|
+
{ dirName: '.cursor', label: 'Cursor', subdirs: ['skills', 'agents', 'commands'], mcpFiles: ['.cursor/mcp.json'] },
|
|
29
|
+
{ dirName: '.kimi', label: 'Kimi', subdirs: ['skills'] },
|
|
30
|
+
{
|
|
31
|
+
dirName: '.zcode',
|
|
32
|
+
label: 'ZCode',
|
|
33
|
+
subdirs: ['skills', 'agents', 'commands'],
|
|
34
|
+
mcpFiles: ['zcode.json', '.zcode/config.json'],
|
|
35
|
+
mcpFormat: 'zcode',
|
|
36
|
+
hookFiles: ['zcode.json', '.zcode/config.json'],
|
|
37
|
+
hookFormat: 'zcode'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
dirName: '.qoder',
|
|
41
|
+
label: 'Qoder CLI',
|
|
42
|
+
subdirs: ['skills', 'agents', 'commands'],
|
|
43
|
+
mcpFiles: ['.qoder/settings.json', '.qoder/settings.local.json'],
|
|
44
|
+
hookFiles: ['.qoder/settings.json', '.qoder/settings.local.json']
|
|
45
|
+
},
|
|
46
|
+
{ dirName: '.github', label: 'GitHub Copilot', subdirs: ['skills', 'agents'] }
|
|
47
|
+
];
|
|
48
|
+
/** Marketplace catalogs follow manifest layout order; the shared root catalog is the fallback. */
|
|
49
|
+
export const MARKETPLACE_PATHS = [...PLUGIN_LAYOUTS.flatMap(layout => layout.marketplaces), 'marketplace.json'];
|