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
package/lib/git.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Git operations for repository sources: clone, pull, head, remove.
3
+ *
4
+ * Every operation runs `git` through `execFile` without a shell, so a source
5
+ * URL or branch can never interpolate into a command string. Failures carry
6
+ * the stderr tail as the message; the manager surfaces them as source errors.
7
+ */
8
+ import { execFile } from 'node:child_process';
9
+ import { rm } from 'node:fs/promises';
10
+ import { promisify } from 'node:util';
11
+ const run = promisify(execFile);
12
+ const DEFAULT_TIMEOUT_MS = 120_000;
13
+ /** Clone a source (depth 1, optional branch) into a not-yet-existing directory. */
14
+ export async function gitClone(url, branch, dest, timeoutMs = DEFAULT_TIMEOUT_MS) {
15
+ const args = ['clone', '--depth', '1', ...(branch === undefined ? [] : ['--branch', branch]), '--', url, dest];
16
+ try {
17
+ await run('git', args, { timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 });
18
+ }
19
+ catch (error) {
20
+ throw gitError('clone', error);
21
+ }
22
+ }
23
+ /** Fast-forward a checked-out source to its configured branch. */
24
+ export async function gitPull(dir, timeoutMs = DEFAULT_TIMEOUT_MS) {
25
+ try {
26
+ await run('git', ['-C', dir, 'pull', '--ff-only'], { timeout: timeoutMs, maxBuffer: 10 * 1024 * 1024 });
27
+ }
28
+ catch (error) {
29
+ throw gitError('pull', error);
30
+ }
31
+ }
32
+ /** Read the checked-out HEAD commit. */
33
+ export async function gitHead(dir, timeoutMs = 30_000) {
34
+ try {
35
+ const { stdout } = await run('git', ['-C', dir, 'rev-parse', 'HEAD'], { timeout: timeoutMs });
36
+ return stdout.trim();
37
+ }
38
+ catch (error) {
39
+ throw gitError('rev-parse', error);
40
+ }
41
+ }
42
+ /** Remove a source checkout tree entirely. */
43
+ export async function gitRemove(dir) {
44
+ await rm(dir, { recursive: true, force: true });
45
+ }
46
+ function gitError(operation, error) {
47
+ if (error instanceof Error && 'stderr' in error && typeof error.stderr === 'string') {
48
+ const stderr = error.stderr.trim();
49
+ return new Error(`git ${operation} failed: ${stderr.split('\n').at(-1) ?? stderr}`);
50
+ }
51
+ return new Error(`git ${operation} failed: ${error instanceof Error ? error.message : String(error)}`);
52
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Claude Code hook compatibility: each enabled suite's `hooks/hooks.json`
3
+ * mounts one live `dsh-hooks-claude-code` bridge on the harness's canonical
4
+ * interception points (SessionStart, UserPromptSubmit, PreToolUse,
5
+ * PostToolUse, Stop, SubagentStart, SubagentStop).
6
+ *
7
+ * The bridge is a cordis function plugin (`inject: ['shell']`) whose config
8
+ * is a single `configPath` plus `pluginRoot` for `${CLAUDE_PLUGIN_ROOT}`
9
+ * substitution — exactly the shape a suite hook file needs. Mounts reconcile
10
+ * on every enable/disable/install/uninstall; a missing bridge package, a
11
+ * broken hook file, or a mount failure is contained per suite.
12
+ */
13
+ import { stat } from 'node:fs/promises';
14
+ import { join } from 'node:path';
15
+ export class HooksMountRegistry {
16
+ ctx;
17
+ live = new Map();
18
+ constructor(ctx) {
19
+ this.ctx = ctx;
20
+ }
21
+ /** Mount/unmount one bridge per suite to match the enabled suites exactly. */
22
+ async reconcile(enabledSuites) {
23
+ const diagnostics = [];
24
+ const wanted = new Set(enabledSuites.map(suite => suite.id));
25
+ for (const [suiteId, handle] of [...this.live]) {
26
+ if (!wanted.has(suiteId)) {
27
+ await this.unmount(suiteId, handle);
28
+ }
29
+ }
30
+ for (const suite of enabledSuites) {
31
+ if (this.live.has(suite.id))
32
+ continue;
33
+ const reason = await this.mount(suite);
34
+ if (reason !== undefined)
35
+ diagnostics.push({ suiteId: suite.id, reason });
36
+ }
37
+ return diagnostics;
38
+ }
39
+ /** Dispose every live bridge; used at plugin teardown. */
40
+ async disposeAll() {
41
+ for (const [suiteId, handle] of [...this.live]) {
42
+ await this.unmount(suiteId, handle);
43
+ }
44
+ }
45
+ async mount(suite) {
46
+ const configPath = await hookConfigPath(suite.root);
47
+ if (configPath === undefined)
48
+ return undefined;
49
+ let bridge;
50
+ try {
51
+ bridge = await import('@deepseek-ai/dsh-hooks-claude-code');
52
+ }
53
+ catch {
54
+ return 'the @deepseek-ai/dsh-hooks-claude-code package is not installed in this profile';
55
+ }
56
+ const mountCtx = this.ctx;
57
+ if (typeof mountCtx.plugin !== 'function')
58
+ return 'the host context does not support dynamic plugin mounting';
59
+ try {
60
+ const handle = mountCtx.plugin(bridge, {
61
+ configPath,
62
+ pluginRoot: suite.root,
63
+ });
64
+ await handle.await();
65
+ this.live.set(suite.id, handle);
66
+ return undefined;
67
+ }
68
+ catch (error) {
69
+ return `mount failed: ${error instanceof Error ? error.message : String(error)}`;
70
+ }
71
+ }
72
+ async unmount(suiteId, handle) {
73
+ this.live.delete(suiteId);
74
+ try {
75
+ await handle.dispose();
76
+ }
77
+ catch (error) {
78
+ this.ctx.logger?.warn(`[dsh-agent-plugins-market] hooks unmount ${suiteId} failed: ${error instanceof Error ? error.message : String(error)}`);
79
+ }
80
+ }
81
+ }
82
+ /** The first existing CC hook config of a suite root. */
83
+ export async function hookConfigPath(root) {
84
+ for (const relative of [join('hooks', 'hooks.json'), 'hooks.json']) {
85
+ const path = join(root, relative);
86
+ try {
87
+ if ((await stat(path)).isFile())
88
+ return path;
89
+ }
90
+ catch {
91
+ // try the next candidate
92
+ }
93
+ }
94
+ return undefined;
95
+ }
package/lib/index.js ADDED
@@ -0,0 +1,73 @@
1
+ import { CommandMountRegistry } from './commands-mounts.js';
2
+ import { HooksMountRegistry } from './hooks-mounts.js';
3
+ import { mountSuiteContext } from './context.js';
4
+ import { SuiteManager } from './manager.js';
5
+ import { McpMountRegistry } from './mcp-mounts.js';
6
+ import { resolveDataRoot, resolveUserRoot } from './paths.js';
7
+ import { mountSuiteRoutes } from './routes.js';
8
+ import { SuiteSkillProvider } from './skills-provider.js';
9
+ export const name = 'dsh-agent-plugins-market';
10
+ export const inject = ['skills', 'commands'];
11
+ export function apply(ctx, config = {}) {
12
+ const userRoot = resolveUserRoot(config.userRoot);
13
+ const dataRoot = resolveDataRoot(config.dataRoot);
14
+ let providerControl;
15
+ const mounts = new McpMountRegistry(ctx, dataRoot);
16
+ const commandMounts = new CommandMountRegistry(ctx);
17
+ const hookMounts = new HooksMountRegistry(ctx);
18
+ const reconcileMounts = () => {
19
+ void (async () => {
20
+ try {
21
+ const diagnostics = await mounts.reconcile(await manager.enabledUserSuites());
22
+ for (const diagnostic of diagnostics) {
23
+ ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" mcp server "${diagnostic.serverKey}": ${diagnostic.reason}`);
24
+ }
25
+ }
26
+ catch (error) {
27
+ ctx.logger?.warn(`[dsh-agent-plugins-market] mcp reconcile failed: ${error instanceof Error ? error.message : String(error)}`);
28
+ }
29
+ try {
30
+ const diagnostics = await commandMounts.reconcile(await manager.enabledUserSuites());
31
+ for (const diagnostic of diagnostics) {
32
+ if (diagnostic.reason !== '')
33
+ ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" command "${diagnostic.command}": ${diagnostic.reason}`);
34
+ }
35
+ }
36
+ catch (error) {
37
+ ctx.logger?.warn(`[dsh-agent-plugins-market] command reconcile failed: ${error instanceof Error ? error.message : String(error)}`);
38
+ }
39
+ try {
40
+ const diagnostics = await hookMounts.reconcile(await manager.enabledUserSuites());
41
+ for (const diagnostic of diagnostics) {
42
+ ctx.logger?.warn(`[dsh-agent-plugins-market] suite "${diagnostic.suiteId}" hooks: ${diagnostic.reason}`);
43
+ }
44
+ }
45
+ catch (error) {
46
+ ctx.logger?.warn(`[dsh-agent-plugins-market] hooks reconcile failed: ${error instanceof Error ? error.message : String(error)}`);
47
+ }
48
+ })();
49
+ };
50
+ const onChanged = () => {
51
+ providerControl?.invalidate();
52
+ reconcileMounts();
53
+ };
54
+ const manager = new SuiteManager({ userRoot, dataRoot, onChanged });
55
+ void manager.load().then(async () => {
56
+ await manager.mergeSources(config.sources ?? []);
57
+ reconcileMounts();
58
+ });
59
+ ctx.skills.registerProvider((control) => {
60
+ providerControl = control;
61
+ return new SuiteSkillProvider(manager);
62
+ });
63
+ const disposeContext = mountSuiteContext(ctx, manager);
64
+ ctx.inject(['webServer', 'loader'], (hostCtx) => {
65
+ hostCtx.effect(() => mountSuiteRoutes(hostCtx, manager), 'dsh-agent-plugins-market: http routes');
66
+ });
67
+ ctx.effect(() => () => {
68
+ void mounts.disposeAll();
69
+ void hookMounts.disposeAll();
70
+ commandMounts.disposeAll();
71
+ disposeContext();
72
+ }, 'dsh-agent-plugins-market: lifecycle');
73
+ }