dsh-agent-plugins-market 0.4.3

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 (46) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +171 -0
  3. package/README.zh.md +171 -0
  4. package/client/client.js +954 -0
  5. package/client/client.js.map +1 -0
  6. package/cordis.patch.yml +4 -0
  7. package/lib/commands-mounts.js +169 -0
  8. package/lib/context.js +80 -0
  9. package/lib/discovery/manifests.js +214 -0
  10. package/lib/discovery/surfaces.js +232 -0
  11. package/lib/discovery.js +234 -0
  12. package/lib/git.js +52 -0
  13. package/lib/hooks-mounts.js +95 -0
  14. package/lib/index.js +73 -0
  15. package/lib/manager.js +490 -0
  16. package/lib/mcp-config.js +100 -0
  17. package/lib/mcp-mounts.js +88 -0
  18. package/lib/paths.js +85 -0
  19. package/lib/routes.js +208 -0
  20. package/lib/skills-parse.js +114 -0
  21. package/lib/skills-provider.js +170 -0
  22. package/lib/state.js +64 -0
  23. package/lib/types/commands-mounts.d.ts +29 -0
  24. package/lib/types/context.d.ts +13 -0
  25. package/lib/types/discovery/manifests.d.ts +55 -0
  26. package/lib/types/discovery/surfaces.d.ts +15 -0
  27. package/lib/types/discovery.d.ts +16 -0
  28. package/lib/types/git.d.ts +8 -0
  29. package/lib/types/hooks-mounts.d.ts +19 -0
  30. package/lib/types/index.d.ts +27 -0
  31. package/lib/types/manager.d.ts +87 -0
  32. package/lib/types/mcp-config.d.ts +28 -0
  33. package/lib/types/mcp-mounts.d.ts +31 -0
  34. package/lib/types/paths.d.ts +29 -0
  35. package/lib/types/routes.d.ts +11 -0
  36. package/lib/types/skills-parse.d.ts +22 -0
  37. package/lib/types/skills-provider.d.ts +16 -0
  38. package/lib/types/state.d.ts +6 -0
  39. package/lib/types/types.d.ts +165 -0
  40. package/lib/types/validate.d.ts +34 -0
  41. package/lib/types.js +10 -0
  42. package/lib/validate.js +199 -0
  43. package/package.json +114 -0
  44. package/schemas/1.0.0/mcp.schema.json +120 -0
  45. package/schemas/1.0.0/plugin.schema.json +65 -0
  46. package/schemas/README.md +14 -0
@@ -0,0 +1,55 @@
1
+ import type { SuiteManifest } from '../types.js';
2
+ export type ManifestKind = 'agent-plugin-v1' | 'universal' | 'claude-code' | 'cursor' | 'kimi' | 'codex';
3
+ /** One manifest candidate: its file path and the dialect it selects. */
4
+ export interface ManifestCandidate {
5
+ kind: ManifestKind;
6
+ path: string;
7
+ }
8
+ /** The highest-precedence manifest file a directory carries, if any. */
9
+ export declare function detectManifest(dir: string): Promise<ManifestCandidate | undefined>;
10
+ /** Whether a directory carries any known suite manifest. */
11
+ export declare function hasSuiteManifest(dir: string): Promise<boolean>;
12
+ /** Fallback suite identity for manifest-less skill collections. */
13
+ export declare function syntheticManifestName(root: string): string;
14
+ /**
15
+ * Parse one manifest document into a normalized SuiteManifest. The v1 dialect
16
+ * is schema-validated (fail-closed); the others are structurally read with
17
+ * light tolerance, and `hint` (a marketplace plugin entry) fills in gaps.
18
+ */
19
+ export declare function readManifest(root: string, errors: string[], hint: {
20
+ name?: string;
21
+ version?: string;
22
+ description?: string;
23
+ } | undefined): Promise<SuiteManifest | undefined>;
24
+ export interface MarketplaceEntry {
25
+ name?: string;
26
+ version?: string;
27
+ description?: string;
28
+ /** Claude Code: a relative path string or `{ source: 'url', url }`.
29
+ * Codex: `{ source: 'local', path }` or `{ source: 'remote', url }`. */
30
+ source: string | {
31
+ source?: string;
32
+ url?: string;
33
+ path?: string;
34
+ };
35
+ }
36
+ export interface Marketplace {
37
+ name?: string;
38
+ entries: MarketplaceEntry[];
39
+ }
40
+ /** Read a marketplace manifest from a checkout root, or undefined when absent. */
41
+ export declare function readMarketplace(checkoutDir: string): Promise<Marketplace | undefined>;
42
+ /** Resolve one marketplace entry to a local checkout-relative directory, or
43
+ * `undefined` for remote-URL entries that are not present in the clone. */
44
+ export declare function marketplaceEntryDir(checkoutDir: string, entry: MarketplaceEntry): string | undefined;
45
+ /**
46
+ * Resolve a repo-level name for source-id derivation, in precedence order:
47
+ * marketplace plugin entry name > marketplace name > root manifest name >
48
+ * the checkout basename. The suite repo's own JSON is authoritative; the
49
+ * basename is only the fallback.
50
+ */
51
+ export declare function repoName(checkoutDir: string): Promise<string>;
52
+ /** The winning manifest's declared `skills` path (string or array), or undefined. */
53
+ export declare function declaredSkillsPath(root: string): Promise<unknown>;
54
+ /** The winning manifest's inline `mcpServers`, or undefined. */
55
+ export declare function declaredMcpServers(root: string): Promise<Record<string, unknown> | undefined>;
@@ -0,0 +1,15 @@
1
+ import type { McpSuiteConfig, SuiteSkill, SuiteSurfaceCounts } from '../types.js';
2
+ /** Discover SKILL.md files under the suite's skills directory, up to 3 levels deep. */
3
+ export declare function discoverSkills(root: string, errors: string[], declared?: unknown): Promise<SuiteSkill[]>;
4
+ /** Read the suite's MCP config: `mcp.json` or `.mcp.json`, else the winning manifest's inline `mcpServers`. */
5
+ export declare function discoverMcp(root: string, errors: string[]): Promise<McpSuiteConfig | undefined>;
6
+ /** Count surfaces for a suite; mcp counts only validated servers. */
7
+ export declare function countSurfaces(root: string, skills: SuiteSkill[], mcp: McpSuiteConfig | undefined): Promise<SuiteSurfaceCounts>;
8
+ export interface LspEntry {
9
+ name: string;
10
+ path: string;
11
+ }
12
+ /** LSP definitions: `.claude-plugin/lsp/*.json` plus reverse-domain `lsp/` dirs. */
13
+ export declare function discoverLspEntries(root: string): Promise<LspEntry[]>;
14
+ /** File names under a suite's commands/ or agents/ directory. */
15
+ export declare function listMdFiles(dir: string): Promise<string[]>;
@@ -0,0 +1,16 @@
1
+ import type { SourceRef, Suite, SuiteDimension } from './types.js';
2
+ import { repoName } from './discovery/manifests.js';
3
+ import { discoverLspEntries, listMdFiles, type LspEntry } from './discovery/surfaces.js';
4
+ export { repoName, listMdFiles, discoverLspEntries };
5
+ export type { LspEntry };
6
+ /** Discover every suite under one cloned source checkout. */
7
+ export declare function discoverSuitesInSource(checkoutDir: string, sourceId: string, dimension: SuiteDimension): Promise<Suite[]>;
8
+ /** Whether a suite root path lies outside the checkout (defense for malformed marketplace sources). */
9
+ export declare function isOutside(root: string, candidate: string): boolean;
10
+ /**
11
+ * Discover every suite of one dimension's configured sources, plus manual
12
+ * checkouts present under the dimension's `.sources/` that no source entry
13
+ * names. Local sources read their directory directly; git sources read
14
+ * their clone; a missing checkout contributes nothing.
15
+ */
16
+ export declare function discoverSourceList(sources: SourceRef[], dimension: SuiteDimension, dimensionRoot: string): Promise<Suite[]>;
@@ -0,0 +1,8 @@
1
+ /** Clone a source (depth 1, optional branch) into a not-yet-existing directory. */
2
+ export declare function gitClone(url: string, branch: string | undefined, dest: string, timeoutMs?: number): Promise<void>;
3
+ /** Fast-forward a checked-out source to its configured branch. */
4
+ export declare function gitPull(dir: string, timeoutMs?: number): Promise<void>;
5
+ /** Read the checked-out HEAD commit. */
6
+ export declare function gitHead(dir: string, timeoutMs?: number): Promise<string>;
7
+ /** Remove a source checkout tree entirely. */
8
+ export declare function gitRemove(dir: string): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ import type { Suite } from './types.js';
3
+ export interface HooksMountDiagnostic {
4
+ suiteId: string;
5
+ reason: string;
6
+ }
7
+ export declare class HooksMountRegistry {
8
+ private readonly ctx;
9
+ private readonly live;
10
+ constructor(ctx: Context);
11
+ /** Mount/unmount one bridge per suite to match the enabled suites exactly. */
12
+ reconcile(enabledSuites: Suite[]): Promise<HooksMountDiagnostic[]>;
13
+ /** Dispose every live bridge; used at plugin teardown. */
14
+ disposeAll(): Promise<void>;
15
+ private mount;
16
+ private unmount;
17
+ }
18
+ /** The first existing CC hook config of a suite root. */
19
+ export declare function hookConfigPath(root: string): Promise<string | undefined>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * dsh-agent-plugins-market host entry: the Agent Plugins Market manager.
3
+ *
4
+ * Function plugin (named exports, no default export). It registers one skill
5
+ * provider feeding enabled suites into `ctx.skills`, reconciles enabled
6
+ * suites' `mcp.json` servers into live `dsh-mcp-client` mounts, injects the
7
+ * enabled-suite catalog at session start, registers the `agent_plugins`
8
+ * tool, and mounts the market page's HTTP routes on the web server.
9
+ *
10
+ * Requires `ctx.skills` (the dsh skill registry). The MCP client package is
11
+ * optional at runtime: without it, suites still load their skills and the
12
+ * manager reports a per-server mount failure.
13
+ */
14
+ import type { Context } from '@deepseek-ai/cordis';
15
+ import type { SourceRef } from './types.js';
16
+ export declare const name = "dsh-agent-plugins-market";
17
+ export declare const inject: string[];
18
+ /** Host configuration. */
19
+ export interface Config {
20
+ /** User-dimension suite root; defaults to `~/.dsh/agent-plugins` (`$DSH_HOME/agent-plugins`). */
21
+ userRoot?: string;
22
+ /** Per-suite data root backing `${PLUGIN_DATA}`; defaults to `~/.dsh/agent-plugins-data`. */
23
+ dataRoot?: string;
24
+ /** Initial repository sources, merged into the persisted state on first load. */
25
+ sources?: SourceRef[];
26
+ }
27
+ export declare function apply(ctx: Context, config?: Config): void;
@@ -0,0 +1,87 @@
1
+ import type { OverviewPayload, SourceRef, Suite, SuiteDimension } from './types.js';
2
+ export interface ManagerOptions {
3
+ userRoot: string;
4
+ dataRoot: string;
5
+ onChanged: () => void;
6
+ }
7
+ export declare class SuiteManager {
8
+ private readonly options;
9
+ private state;
10
+ private mutationQueue;
11
+ private readonly statePath;
12
+ constructor(options: ManagerOptions);
13
+ /** Load persisted state once at plugin activation. */
14
+ load(): Promise<void>;
15
+ get sources(): SourceRef[];
16
+ /** The user-dimension suite root this manager operates. */
17
+ get userRoot(): string;
18
+ /**
19
+ * One suite's full detail for the market detail modal: manifest fields,
20
+ * skill metadata, validated mcp.json servers, and per-surface file lists.
21
+ */
22
+ suiteDetail(sourceId: string, suiteId: string): Promise<Record<string, unknown>>;
23
+ private readPreview;
24
+ /** One skill's full SKILL.md text for the market detail modal. */
25
+ skillContent(sourceId: string, suiteId: string, skillName: string): Promise<{
26
+ name: string;
27
+ description: string;
28
+ content: string;
29
+ path: string;
30
+ }>;
31
+ /** The full market overview: fresh discovery merged with install entries. */
32
+ overview(): Promise<OverviewPayload>;
33
+ /** Enabled user-dimension suites (the MCP mount input). */
34
+ enabledUserSuites(): Promise<Suite[]>;
35
+ /** Enabled user- and project-dimension suites for a workspace cwd. */
36
+ enabledSuitesForCwd(cwd: string): Promise<{
37
+ user: Suite[];
38
+ project: Suite[];
39
+ }>;
40
+ /** All suites (any enabled state) of one dimension. */
41
+ suitesForDimension(dimension: SuiteDimension, dimensionRoot: string): Promise<Suite[]>;
42
+ /**
43
+ * Add a source and clone it immediately. The id is derived from the suite
44
+ * repository's own JSON (marketplace plugin entry name first, then the
45
+ * root manifest name, then the repo basename), sanitized; collisions get a
46
+ * numeric suffix.
47
+ */
48
+ addSource(input: {
49
+ url: string;
50
+ branch?: string;
51
+ local?: boolean;
52
+ }): Promise<SourceRef>;
53
+ private uniqueSourceId;
54
+ /**
55
+ * Update one source's url / branch / local flag. A git source whose URL
56
+ * changes drops its stale checkout (the next refresh clones the new URL);
57
+ * a local source's directory is never touched.
58
+ */
59
+ updateSource(sourceId: string, patch: {
60
+ url?: string;
61
+ branch?: string;
62
+ local?: boolean;
63
+ }): Promise<void>;
64
+ /** Remove a source: delete its checkout, forget its suites and install entries. */
65
+ removeSource(sourceId: string): Promise<void>;
66
+ /** Refresh one source checkout (pull), or every source when `sourceId` is omitted. */
67
+ refreshSource(sourceId?: string): Promise<void>;
68
+ /** Install a suite: the source must be cloned; the suite dir becomes an enabled install entry. */
69
+ install(sourceId: string, suiteId: string): Promise<void>;
70
+ /** Uninstall a suite: drop the install entry (the source clone stays for browsing). */
71
+ uninstall(sourceId: string, suiteId: string): Promise<void>;
72
+ /** Enable or disable an installed suite. */
73
+ setEnabled(sourceId: string, suiteId: string, enabled: boolean): Promise<void>;
74
+ /** Append config-seeded sources missing from state and persist; clones lazily on first refresh/install. */
75
+ mergeSources(sources: SourceRef[]): Promise<void>;
76
+ private setInstalled;
77
+ private ensureClone;
78
+ /** The filesystem location one source is read from. */
79
+ private sourceCheckoutPath;
80
+ private discoverDimension;
81
+ private commandPreviews;
82
+ private agentPreviews;
83
+ /** Parse CC hooks.json into flat {event, matcher, command} preview entries. */
84
+ private hooksPreviews;
85
+ private lspPreviews;
86
+ private enqueue;
87
+ }
@@ -0,0 +1,28 @@
1
+ import type { StdioConfig, StreamableHttpConfig } from '@deepseek-ai/dsh-mcp-client';
2
+ import type { Suite } from './types.js';
3
+ export interface McpMountRequest {
4
+ suiteId: string;
5
+ serverKey: string;
6
+ config: StdioConfig | StreamableHttpConfig;
7
+ }
8
+ export interface McpMountFailure {
9
+ serverKey: string;
10
+ reason: string;
11
+ }
12
+ /**
13
+ * Build one mount request per supported mcp.json server.
14
+ * @returns mount requests plus per-server failures (unsupported transport,
15
+ * invalid server key, or derived serverName collision candidates are
16
+ * checked by the mount registry, not here).
17
+ */
18
+ export declare function toMcpMounts(suite: Suite, pluginDataRoot: string): {
19
+ mounts: McpMountRequest[];
20
+ failures: McpMountFailure[];
21
+ };
22
+ /**
23
+ * Derive a stable, unique-ish `dsh-mcp-client` serverName from the suite and
24
+ * server ids: `${suiteId}__${serverKey}` sanitized, truncated to 32 chars
25
+ * with a deterministic 12-hex suffix when the join exceeds the budget (the
26
+ * same deterministic-hash policy the MCP client uses for long tool names).
27
+ */
28
+ export declare function deriveServerName(suiteId: string, serverKey: string): string;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Runtime MCP mounts: one live `dsh-mcp-client` child plugin per enabled
3
+ * suite's mcp.json server, mounted through `ctx.plugin`.
4
+ *
5
+ * Mounts reconcile against the enabled-suite set: reconcile() unmounts rows
6
+ * whose suite was disabled or removed and mounts rows that appeared. A
7
+ * missing `@deepseek-ai/dsh-mcp-client` install, a duplicate derived
8
+ * serverName, or a load failure is contained per server — a broken third-party
9
+ * suite must not take the host down — and reported through the manager's
10
+ * diagnostic list.
11
+ */
12
+ import type { Context } from '@deepseek-ai/cordis';
13
+ import type { Suite } from './types.js';
14
+ export interface McpMountDiagnostic {
15
+ suiteId: string;
16
+ serverKey: string;
17
+ reason: string;
18
+ }
19
+ export declare class McpMountRegistry {
20
+ private readonly ctx;
21
+ private readonly pluginDataRoot;
22
+ private readonly live;
23
+ private readonly names;
24
+ constructor(ctx: Context, pluginDataRoot: string);
25
+ /** Mount/unmount MCP servers to match the enabled suites exactly. */
26
+ reconcile(enabledSuites: Suite[]): Promise<McpMountDiagnostic[]>;
27
+ /** Dispose every live mount; used at plugin teardown. */
28
+ disposeAll(): Promise<void>;
29
+ private mount;
30
+ private unmount;
31
+ }
@@ -0,0 +1,29 @@
1
+ /** Source checkouts live under `<dimensionRoot>/.sources/<sourceId>/`. */
2
+ export declare const SOURCES_DIR_NAME = ".sources";
3
+ /** Per-suite mutable data directory (the `${PLUGIN_DATA}` placeholder). */
4
+ export declare const DATA_DIR_NAME = "data";
5
+ export declare const STATE_FILE_NAME = "state.json";
6
+ /** Expand a leading `~/` (or `~\` on Windows) to the home directory; other values pass through. */
7
+ export declare function expandHome(path: string): string;
8
+ /** Resolve the harness home (`$DSH_HOME` or `~/.dsh`). */
9
+ export declare function resolveDshHome(): string;
10
+ /** Resolve the user-dimension suite root. */
11
+ export declare function resolveUserRoot(configUserRoot?: string): string;
12
+ /** Resolve the suite data root hosting `${PLUGIN_DATA}` directories. */
13
+ export declare function resolveDataRoot(configDataRoot?: string): string;
14
+ /** Resolve a project root from a workspace cwd: nearest ancestor with `.git`. */
15
+ export declare function findProjectRoot(cwd: string): Promise<string>;
16
+ /** Resolve the project-dimension suite root for a workspace cwd. */
17
+ export declare function resolveProjectRoot(cwd: string): Promise<string>;
18
+ /** Source checkout directory for one source id. */
19
+ export declare function sourcesDir(dimensionRoot: string): string;
20
+ /** Source checkout directory for one source id. */
21
+ export declare function sourceCheckoutDir(dimensionRoot: string, sourceId: string): string;
22
+ /** Per-suite data directory for `${PLUGIN_DATA}`. */
23
+ export declare function suiteDataDir(dataRoot: string, suiteId: string): string;
24
+ /** Async existence probe that follows symlinks for a final component. */
25
+ export declare function isDirectory(path: string): Promise<boolean>;
26
+ /** Sanitize a plugin or server id into `[a-z0-9-]` (lowercased). */
27
+ export declare function sanitizeId(raw: string): string;
28
+ /** Derive a source id from a repository URL or local path: last path segment, `.git` stripped. */
29
+ export declare function deriveSourceId(url: string): string;
@@ -0,0 +1,11 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { SuiteManager } from './manager.js';
3
+ export interface WebServerService {
4
+ register(route: {
5
+ kind: 'exact' | 'prefix';
6
+ path: string;
7
+ handler: (request: IncomingMessage, response: ServerResponse) => void | Promise<void>;
8
+ }): () => void;
9
+ }
10
+ /** Mount every route; returns the disposer releasing them all. */
11
+ export declare function mountSuiteRoutes(hostCtx: unknown, manager: SuiteManager): () => void;
@@ -0,0 +1,22 @@
1
+ import type { SkillInvocationPolicy } from '@deepseek-ai/dsh-skill';
2
+ export interface ParsedSkillFrontmatter {
3
+ name: string;
4
+ description: string;
5
+ whenToUse?: string;
6
+ invocation: SkillInvocationPolicy;
7
+ }
8
+ /** Kebab-case skill names only, matching the shipped provider's rule. */
9
+ export declare function isSkillName(name: string): boolean;
10
+ /**
11
+ * Normalize a display-style skill name into kebab-case (e.g. "Presentations"
12
+ * → "presentations"), or `undefined` when nothing usable remains.
13
+ */
14
+ export declare function normalizeSkillName(name: string): string | undefined;
15
+ /**
16
+ * Parse skill frontmatter with the shipped provider's fail-closed semantics.
17
+ * @returns the parsed frontmatter, or a rejection string explaining why the
18
+ * skill must be dropped from discovery.
19
+ */
20
+ export declare function parseSkillFrontmatter(text: string, expectedName: string | undefined): ParsedSkillFrontmatter | string;
21
+ /** Strip the frontmatter block, returning the instruction body. */
22
+ export declare function stripFrontmatter(text: string): string;
@@ -0,0 +1,16 @@
1
+ import type { SkillCandidate, SkillDefinition, SkillLookupOptions, SkillProvider } from '@deepseek-ai/dsh-skill';
2
+ import type { SuiteManager } from './manager.js';
3
+ export declare const SUITE_PROJECT_SOURCE = "agent-plugin-project";
4
+ export declare const SUITE_USER_SOURCE = "agent-plugin-user";
5
+ export declare class SuiteSkillProvider implements SkillProvider {
6
+ private readonly manager;
7
+ readonly name = "agent-plugin";
8
+ constructor(manager: SuiteManager);
9
+ list(options: SkillLookupOptions): Promise<SkillCandidate[]>;
10
+ /** Parse `agents/*.md` of one suite into agent-definition candidates. */
11
+ private agentsOf;
12
+ get(candidate: SkillCandidate, options: SkillLookupOptions): Promise<SkillDefinition | undefined>;
13
+ private candidateFor;
14
+ private locate;
15
+ private locateProject;
16
+ }
@@ -0,0 +1,6 @@
1
+ import type { SuiteState } from './types.js';
2
+ export declare const EMPTY_STATE: SuiteState;
3
+ /** Parse persisted state; unreadable or wrong-version files yield a contained empty state. */
4
+ export declare function loadState(statePath: string): Promise<SuiteState>;
5
+ /** Persist state atomically through a sibling-temp rename. */
6
+ export declare function saveState(statePath: string, state: SuiteState): Promise<void>;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Shared shapes for the dsh-agent-plugins-market Agent Plugins manager.
3
+ *
4
+ * A suite is the portable Agent Plugins package defined by the
5
+ * agent-plugins.org v1.0.0 specification, plus the two dialect layouts this
6
+ * manager normalizes as inputs (Claude Code `.claude-plugin/plugin.json` and
7
+ * Codex `.codex-plugin/plugin.json`). Discovery maps every layout onto this
8
+ * internal shape; runtime injection consumes only the internal shape.
9
+ */
10
+ /** One configured repository source. */
11
+ export interface SourceRef {
12
+ /** Stable local id; `[a-z0-9][a-z0-9-]*`, unique across sources. */
13
+ id: string;
14
+ /** Git URL to clone, or a local directory path when `local` is set. */
15
+ url: string;
16
+ /** Optional branch to pin (git sources only). */
17
+ branch?: string;
18
+ /** Read the source directory directly instead of cloning it. */
19
+ local?: boolean;
20
+ }
21
+ /** The manifest layout a suite root was discovered under. */
22
+ export type SuiteLayoutKind = 'agent-plugin-v1' | 'universal' | 'claude-code' | 'cursor' | 'kimi' | 'codex' | 'skill-collection' | 'remote';
23
+ /** Normalized suite manifest fields. */
24
+ export interface SuiteManifest {
25
+ layout: SuiteLayoutKind;
26
+ /** Absolute manifest file path. */
27
+ path: string;
28
+ /** Suite id derived from the manifest name or its root directory, sanitized to `[a-z0-9-]`. */
29
+ id: string;
30
+ name: string;
31
+ version?: string;
32
+ description?: string;
33
+ author?: string;
34
+ keywords?: string[];
35
+ /** For agent-plugin-v1: the recognized `$schema` identifier. */
36
+ schemaVersion?: string;
37
+ }
38
+ /** One skill shipped inside a suite (`<suiteRoot>/skills/<name>/SKILL.md`). */
39
+ export interface SuiteSkill {
40
+ /** Skill directory name; validated kebab-case. */
41
+ name: string;
42
+ /** Absolute skill directory. */
43
+ directory: string;
44
+ /** Absolute SKILL.md path. */
45
+ file: string;
46
+ /** Frontmatter description, required for model catalogs. */
47
+ description: string;
48
+ /** Optional extra routing guidance from frontmatter. */
49
+ whenToUse?: string;
50
+ /** Invocation policy parsed from frontmatter with fail-closed semantics. */
51
+ invocation: {
52
+ modelInvocable: boolean;
53
+ userInvocable: boolean;
54
+ };
55
+ }
56
+ /** Counted surface tags rendered on market cards. */
57
+ export interface SuiteSurfaceCounts {
58
+ skills: number;
59
+ mcp: number;
60
+ hooks: number;
61
+ commands: number;
62
+ agents: number;
63
+ lsp: number;
64
+ }
65
+ /** agent-plugins.org v1 `mcp.json` server variants. */
66
+ export interface McpServerStdio {
67
+ type: 'stdio';
68
+ command: string;
69
+ args?: string[];
70
+ env?: Record<string, string>;
71
+ cwd?: string;
72
+ }
73
+ export interface McpServerStreamableHttp {
74
+ type: 'streamable-http';
75
+ url: string;
76
+ headers?: Record<string, string>;
77
+ }
78
+ export interface McpServerSse {
79
+ type: 'sse';
80
+ url: string;
81
+ headers?: Record<string, string>;
82
+ }
83
+ export type McpServer = McpServerStdio | McpServerStreamableHttp | McpServerSse;
84
+ /** Parsed and validated `mcp.json` content. */
85
+ export interface McpSuiteConfig {
86
+ schema: string;
87
+ servers: Record<string, McpServer>;
88
+ }
89
+ /** Install dimension of a suite. */
90
+ export type SuiteDimension = 'user' | 'project';
91
+ /** One discovered suite with runtime-relevant fields resolved. */
92
+ export interface Suite {
93
+ sourceId: string;
94
+ id: string;
95
+ root: string;
96
+ manifest: SuiteManifest;
97
+ skills: SuiteSkill[];
98
+ /** Validated mcp.json content; absent when the file is missing or invalid. */
99
+ mcp?: McpSuiteConfig;
100
+ surfaces: SuiteSurfaceCounts;
101
+ dimension: SuiteDimension;
102
+ enabled: boolean;
103
+ lockCommit?: string;
104
+ installedAt?: string;
105
+ /** Remote marketplace reference (not cloned): the source URL plus the
106
+ * marketplace entry metadata; no local content is available. */
107
+ remote?: {
108
+ url: string;
109
+ };
110
+ /** Discovery/validation failures for this suite; skills of a suite with a
111
+ * broken manifest are still exposed when they parse, per spec §7.1. */
112
+ errors: string[];
113
+ }
114
+ /** Persisted install entry, keyed `${sourceId}/${suiteId}`. */
115
+ export interface InstalledEntry {
116
+ enabled: boolean;
117
+ lockCommit?: string;
118
+ installedAt: string;
119
+ }
120
+ /** The persisted suite state file (`<dimensionRoot>/state.json`). */
121
+ export interface SuiteState {
122
+ version: 1;
123
+ sources: SourceRef[];
124
+ installed: Record<string, InstalledEntry>;
125
+ }
126
+ /** Card-shaped view for the market UI. */
127
+ export interface SuiteOverviewCard {
128
+ sourceId: string;
129
+ suiteId: string;
130
+ name: string;
131
+ version?: string;
132
+ description?: string;
133
+ keywords: string[];
134
+ surfaces: SuiteSurfaceCounts;
135
+ enabled: boolean;
136
+ installed: boolean;
137
+ dimension: SuiteDimension;
138
+ layout: SuiteLayoutKind;
139
+ errors: string[];
140
+ }
141
+ export interface SourceOverview {
142
+ id: string;
143
+ url: string;
144
+ branch?: string;
145
+ /** Local-directory source (read directly, never cloned or deleted). */
146
+ local?: boolean;
147
+ /** Whether the checkout (or local directory) is present. */
148
+ cloned: boolean;
149
+ lockCommit?: string;
150
+ error?: string;
151
+ suiteIds: string[];
152
+ }
153
+ export interface OverviewPayload {
154
+ sources: SourceOverview[];
155
+ suites: SuiteOverviewCard[];
156
+ totals: {
157
+ all: number;
158
+ installed: number;
159
+ enabled: number;
160
+ };
161
+ roots: {
162
+ user: string;
163
+ data: string;
164
+ };
165
+ }
@@ -0,0 +1,34 @@
1
+ import type { McpSuiteConfig } from './types.js';
2
+ export declare const PLUGIN_SCHEMA_ID = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
3
+ export declare const MCP_SCHEMA_ID = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
4
+ /** Validate one JSON document against a vendored schema; errors joined for surfacing. */
5
+ export declare function validateAgainstSchema(schemaId: string, document: unknown): Promise<string[]>;
6
+ /** Whether a `$schema` value selects the local v1.0.0 ruleset. */
7
+ export declare function isRecognizedSchema(value: unknown): boolean;
8
+ /**
9
+ * Spec §4 containment: a plugin-relative path must begin with `./` and, after
10
+ * resolution against the plugin root, stay inside the filesystem-resolved
11
+ * plugin root. Symlinks resolving outside the root are rejected.
12
+ * @returns `undefined` when contained, or the rejection reason.
13
+ */
14
+ export declare function pathContainmentError(pluginRoot: string, value: string): Promise<string | undefined>;
15
+ /** Validate a plugin manifest per the vendored schema plus `$schema` recognition. */
16
+ export declare function validatePluginManifest(raw: unknown): Promise<string[]>;
17
+ export interface McpValidateOptions {
18
+ /** Strict portable mode (`mcp.json`): `$schema` required and schema-validated.
19
+ * Lenient mode (`.mcp.json`, native client file): no `$schema` requirement,
20
+ * unknown transports skipped per server, known transports still validated. */
21
+ strict?: boolean;
22
+ }
23
+ export declare function validateMcpJson(pluginRoot: string, raw: unknown, options?: McpValidateOptions): Promise<{
24
+ config?: McpSuiteConfig;
25
+ errors: string[];
26
+ }>;
27
+ /** Expand `${PLUGIN_ROOT}`, `${PLUGIN_DATA}`, and `${NAME}` (process env) in one string.
28
+ * Claude Code aliases `${CLAUDE_PLUGIN_ROOT}` / `${CLAUDE_PLUGIN_DATA}` and
29
+ * `${NAME:-default}` fallbacks are honored for unset/empty env vars. */
30
+ export declare function expandPlaceholders(value: string, pluginRoot: string, pluginData: string, env?: NodeJS.ProcessEnv): string;
31
+ /** Resolve an expanded plugin-relative cwd to an absolute path. */
32
+ export declare function resolveCwd(value: string, pluginRoot: string, pluginData: string): string;
33
+ /** Whether one absolute path equals or lies under another absolute path. */
34
+ export declare function isWithin(root: string, candidate: string): boolean;
package/lib/types.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Shared shapes for the dsh-agent-plugins-market Agent Plugins manager.
3
+ *
4
+ * A suite is the portable Agent Plugins package defined by the
5
+ * agent-plugins.org v1.0.0 specification, plus the two dialect layouts this
6
+ * manager normalizes as inputs (Claude Code `.claude-plugin/plugin.json` and
7
+ * Codex `.codex-plugin/plugin.json`). Discovery maps every layout onto this
8
+ * internal shape; runtime injection consumes only the internal shape.
9
+ */
10
+ export {};