@dsh-cc/plugin-loader 0.5.0

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 (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.i18n.yaml +6 -0
  3. package/README.md +68 -0
  4. package/README.zh.md +68 -0
  5. package/lib/agents.d.ts +110 -0
  6. package/lib/agents.d.ts.map +1 -0
  7. package/lib/agents.js +140 -0
  8. package/lib/agents.js.map +1 -0
  9. package/lib/commands.d.ts +119 -0
  10. package/lib/commands.d.ts.map +1 -0
  11. package/lib/commands.js +159 -0
  12. package/lib/commands.js.map +1 -0
  13. package/lib/discovery.d.ts +49 -0
  14. package/lib/discovery.d.ts.map +1 -0
  15. package/lib/discovery.js +223 -0
  16. package/lib/discovery.js.map +1 -0
  17. package/lib/hooks.d.ts +44 -0
  18. package/lib/hooks.d.ts.map +1 -0
  19. package/lib/hooks.js +89 -0
  20. package/lib/hooks.js.map +1 -0
  21. package/lib/index.d.ts +81 -0
  22. package/lib/index.d.ts.map +1 -0
  23. package/lib/index.js +92 -0
  24. package/lib/index.js.map +1 -0
  25. package/lib/manifest.d.ts +29 -0
  26. package/lib/manifest.d.ts.map +1 -0
  27. package/lib/manifest.js +120 -0
  28. package/lib/manifest.js.map +1 -0
  29. package/lib/mcp.d.ts +41 -0
  30. package/lib/mcp.d.ts.map +1 -0
  31. package/lib/mcp.js +64 -0
  32. package/lib/mcp.js.map +1 -0
  33. package/lib/resolve-manifest.d.ts +27 -0
  34. package/lib/resolve-manifest.d.ts.map +1 -0
  35. package/lib/resolve-manifest.js +85 -0
  36. package/lib/resolve-manifest.js.map +1 -0
  37. package/lib/seams.d.ts +59 -0
  38. package/lib/seams.d.ts.map +1 -0
  39. package/lib/seams.js +56 -0
  40. package/lib/seams.js.map +1 -0
  41. package/lib/settings.d.ts +41 -0
  42. package/lib/settings.d.ts.map +1 -0
  43. package/lib/settings.js +44 -0
  44. package/lib/settings.js.map +1 -0
  45. package/lib/skill-semantics.d.ts +92 -0
  46. package/lib/skill-semantics.d.ts.map +1 -0
  47. package/lib/skill-semantics.js +98 -0
  48. package/lib/skill-semantics.js.map +1 -0
  49. package/lib/skills.d.ts +49 -0
  50. package/lib/skills.d.ts.map +1 -0
  51. package/lib/skills.js +168 -0
  52. package/lib/skills.js.map +1 -0
  53. package/lib/types.d.ts +96 -0
  54. package/lib/types.d.ts.map +1 -0
  55. package/lib/types.js +12 -0
  56. package/lib/types.js.map +1 -0
  57. package/package.json +64 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Mount a Claude Code plugin's slash commands.
3
+ *
4
+ * Translates manifest `commands` entries (inline content or a source file) into
5
+ * typed command definitions registered on the commands seam. Each handler
6
+ * substitutes `$ARGUMENTS` and dispatches the rendered body as a user prompt
7
+ * on the invoking agent; registration is effect-scoped.
8
+ *
9
+ * @module
10
+ */
11
+ import type { CcPluginManifest } from './types.ts';
12
+ import { ComponentTally } from './seams.ts';
13
+ /** The commands seam: accepts a typed slash-command definition. */
14
+ export interface CommandsSeam {
15
+ /**
16
+ * Register one command definition.
17
+ * @param definition - the command to register.
18
+ * @returns the exact disposer that removes the command.
19
+ */
20
+ register(definition: CommandDefinition): () => void;
21
+ }
22
+ /** The minimal typed command definition this loader emits. */
23
+ export interface CommandDefinition {
24
+ /** The bare command name (harness registries reject colon names). */
25
+ readonly name: string;
26
+ /** Human-readable command description. */
27
+ readonly description: string;
28
+ /** Argument placeholder hint, surfaced to the user. */
29
+ readonly input?: {
30
+ hint: string;
31
+ };
32
+ /** Dispatches the rendered command body as a user prompt on the invoking agent. */
33
+ readonly handler: (invocation: CommandInvocationLike) => CommandResult | Promise<CommandResult>;
34
+ }
35
+ /** Result shape the loader's command handlers return. */
36
+ /**
37
+ * Minimal structural view of the upstream command invocation. Duck-typed on
38
+ * purpose: the loader never imports the commands package directly.
39
+ */
40
+ export interface CommandInvocationLike {
41
+ /** The agent the command was executed against; the prompt injection target. */
42
+ readonly agent: {
43
+ followup(message: unknown): unknown;
44
+ };
45
+ /** Raw argument text following the command name (empty when none given). */
46
+ readonly rawInput: string;
47
+ }
48
+ export type CommandResult = {
49
+ readonly kind: 'success';
50
+ readonly text?: string;
51
+ } | {
52
+ readonly kind: 'error';
53
+ readonly text: string;
54
+ };
55
+ /** A plugin command's public summary, using the `plugin:command` display name. */
56
+ export interface CcPluginCommandInfo {
57
+ /** Display name `plugin:command`, even when both parts are identical. */
58
+ readonly name: string;
59
+ /** The plugin's manifest name. */
60
+ readonly plugin: string;
61
+ /** Human-readable command description. */
62
+ readonly description: string;
63
+ /** Argument placeholder hint, when one is known. */
64
+ readonly argumentHint?: string;
65
+ }
66
+ /** One mounted plugin command: its public summary plus a direct dispatcher. */
67
+ export interface MountedPluginCommand {
68
+ /** Public summary (colon display name; served by the host's own channel). */
69
+ readonly info: CcPluginCommandInfo;
70
+ /**
71
+ * Dispatch the rendered command body as a user prompt on the given agent —
72
+ * the same path the harness-registered bare alias uses.
73
+ */
74
+ run(invocation: CommandInvocationLike): CommandResult | Promise<CommandResult>;
75
+ }
76
+ /** Commands live under this directory in a plugin root, when present. */
77
+ export declare const STANDARD_COMMANDS_DIR = "commands";
78
+ /** Options for mounting one plugin's commands. */
79
+ export interface MountCommandsOptions {
80
+ /** The plugin root directory; inline `source` paths resolve against it. */
81
+ readonly pluginRoot: string;
82
+ /** The parsed manifest, whose `commands` field drives registration. */
83
+ readonly manifest: CcPluginManifest;
84
+ /** The commands seam (probed; `undefined` to skip commands). */
85
+ readonly commands: CommandsSeam | undefined;
86
+ }
87
+ /**
88
+ * Register a plugin's manifest commands.
89
+ *
90
+ * Only bare command names reach the host commands seam: harness command
91
+ * registries validate names against `/^[a-z][a-z0-9_-]*$/` and reject the
92
+ * `plugin:command` colon form. The colon form stays available through the
93
+ * returned `mounted` list, for hosts that expose plugin commands on their own
94
+ * channel (e.g. cc-shell's `CcPluginsService.runPluginCommand`).
95
+ *
96
+ * A rejected registration (bare name already taken) skips that one command,
97
+ * never the whole plugin mount.
98
+ *
99
+ * @param options - plugin root, manifest, and the commands seam.
100
+ * @returns mounted disposers, per-component counts, and the mounted commands.
101
+ */
102
+ export declare function mountCommands(options: MountCommandsOptions): {
103
+ disposers: (() => void)[];
104
+ mounted: MountedPluginCommand[];
105
+ tally: ComponentTally;
106
+ };
107
+ /**
108
+ * Substitute `$ARGUMENTS` with the invocation's raw input and steer the
109
+ * rendered command body into the conversation as a user message — the same
110
+ * cross-plane seam the upstream `/plan` command uses. Returning success
111
+ * without `text` keeps the command plane from echoing the body back as a
112
+ * status row: the injected message itself is the visible artifact.
113
+ *
114
+ * `Agent.followup` is async upstream, so a thenable return is awaited and a
115
+ * rejection is folded into `{ kind: 'error' }` instead of surfacing as an
116
+ * unhandled rejection (a synchronous throw is caught the same way).
117
+ */
118
+ export declare function dispatchCommandPrompt(invocation: CommandInvocationLike, body: string): CommandResult | Promise<CommandResult>;
119
+ //# sourceMappingURL=commands.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAMH,OAAO,KAAK,EAAE,gBAAgB,EAAa,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,iBAAiB,GAAG,MAAM,IAAI,CAAA;CACpD;AAED,8DAA8D;AAC9D,MAAM,WAAW,iBAAiB;IAChC,qEAAqE;IACrE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACjC,mFAAmF;IACnF,QAAQ,CAAC,OAAO,EAAE,CAAC,UAAU,EAAE,qBAAqB,KAAK,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;CAChG;AAED,yDAAyD;AACzD;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAA;KAAE,CAAA;IACvD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,aAAa,GACrB;IAAE,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GACpD;IAAE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAErD,kFAAkF;AAClF,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kCAAkC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED,+EAA+E;AAC/E,MAAM,WAAW,oBAAoB;IACnC,6EAA6E;IAC7E,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAA;IAClC;;;OAGG;IACH,GAAG,CAAC,UAAU,EAAE,qBAAqB,GAAG,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;CAC/E;AAED,yEAAyE;AACzE,eAAO,MAAM,qBAAqB,aAAa,CAAA;AAE/C,kDAAkD;AAClD,MAAM,WAAW,oBAAoB;IACnC,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,uEAAuE;IACvE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE,YAAY,GAAG,SAAS,CAAA;CAC5C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG;IAC5D,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAA;IACzB,OAAO,EAAE,oBAAoB,EAAE,CAAA;IAC/B,KAAK,EAAE,cAAc,CAAA;CACtB,CA4CA;AAwDD;;;;;;;;;;GAUG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,qBAAqB,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAkB7H"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Mount a Claude Code plugin's slash commands.
3
+ *
4
+ * Translates manifest `commands` entries (inline content or a source file) into
5
+ * typed command definitions registered on the commands seam. Each handler
6
+ * substitutes `$ARGUMENTS` and dispatches the rendered body as a user prompt
7
+ * on the invoking agent; registration is effect-scoped.
8
+ *
9
+ * @module
10
+ */
11
+ import { readdirSync, readFileSync } from 'node:fs';
12
+ import { createUserMessage } from '@deepseek-ai/dsh-llm';
13
+ import { parseCcFrontmatter, parseCcFrontmatterDocument } from '@dsh-cc/skill-loader';
14
+ import { basename, join, resolve } from 'node:path';
15
+ import { ComponentTally } from "./seams.js";
16
+ /** Commands live under this directory in a plugin root, when present. */
17
+ export const STANDARD_COMMANDS_DIR = 'commands';
18
+ /**
19
+ * Register a plugin's manifest commands.
20
+ *
21
+ * Only bare command names reach the host commands seam: harness command
22
+ * registries validate names against `/^[a-z][a-z0-9_-]*$/` and reject the
23
+ * `plugin:command` colon form. The colon form stays available through the
24
+ * returned `mounted` list, for hosts that expose plugin commands on their own
25
+ * channel (e.g. cc-shell's `CcPluginsService.runPluginCommand`).
26
+ *
27
+ * A rejected registration (bare name already taken) skips that one command,
28
+ * never the whole plugin mount.
29
+ *
30
+ * @param options - plugin root, manifest, and the commands seam.
31
+ * @returns mounted disposers, per-component counts, and the mounted commands.
32
+ */
33
+ export function mountCommands(options) {
34
+ const tally = new ComponentTally('commands');
35
+ const disposers = [];
36
+ const mounted = [];
37
+ if (options.commands === undefined) {
38
+ tally.addSkipped('commands seam "commands" is not mounted');
39
+ return { disposers, mounted, tally };
40
+ }
41
+ const entries = options.manifest.commandsDeclared
42
+ ? [...options.manifest.commands]
43
+ : defaultCommandEntries(options.pluginRoot, tally);
44
+ if (entries.length === 0) {
45
+ tally.addSkipped('plugin ships no commands');
46
+ return { disposers, mounted, tally };
47
+ }
48
+ for (const entry of entries) {
49
+ const rendered = renderCommand(options.pluginRoot, entry);
50
+ if (rendered.error !== undefined) {
51
+ tally.addFailed(`command "${entry.name}": ${rendered.error}`);
52
+ continue;
53
+ }
54
+ const hint = entry.argumentHint ?? rendered.argumentHint;
55
+ const body = rendered.body ?? '';
56
+ const info = {
57
+ name: `${options.manifest.name}:${entry.name}`,
58
+ plugin: options.manifest.name,
59
+ description: entry.description ?? rendered.description ?? '',
60
+ ...hint === undefined ? {} : { argumentHint: hint },
61
+ };
62
+ const run = (invocation) => dispatchCommandPrompt(invocation, body);
63
+ try {
64
+ disposers.push(options.commands.register({
65
+ name: entry.name,
66
+ description: info.description,
67
+ ...hint === undefined ? {} : { input: { hint } },
68
+ handler: run,
69
+ }));
70
+ mounted.push({ info, run });
71
+ tally.addLoaded();
72
+ }
73
+ catch (error) {
74
+ tally.addSkipped(`bare name "${entry.name}" not registered: ${String(error)}`);
75
+ }
76
+ }
77
+ return { disposers, mounted, tally };
78
+ }
79
+ /**
80
+ * Scan `commands/*.md` when the manifest omitted `commands`. Nested
81
+ * subdirectories are skipped with a reason (no silent drop, no colon names).
82
+ */
83
+ function defaultCommandEntries(pluginRoot, tally) {
84
+ const dir = join(pluginRoot, STANDARD_COMMANDS_DIR);
85
+ let entries;
86
+ try {
87
+ entries = readdirSync(dir, { withFileTypes: true });
88
+ }
89
+ catch {
90
+ return [];
91
+ }
92
+ const found = [];
93
+ for (const entry of entries) {
94
+ if (entry.isDirectory()) {
95
+ tally.addSkipped(`skipped nested commands directory "${entry.name}"`);
96
+ continue;
97
+ }
98
+ if (!entry.isFile() && !entry.isSymbolicLink())
99
+ continue;
100
+ if (!entry.name.endsWith('.md'))
101
+ continue;
102
+ found.push({ name: basename(entry.name, '.md'), source: join(STANDARD_COMMANDS_DIR, entry.name) });
103
+ }
104
+ return found;
105
+ }
106
+ /** Resolve a command entry to consumable content, or a failure reason. */
107
+ function renderCommand(pluginRoot, entry) {
108
+ if (entry.content !== undefined) {
109
+ return { body: entry.content };
110
+ }
111
+ if (entry.source !== undefined) {
112
+ const path = resolve(pluginRoot, entry.source);
113
+ try {
114
+ const raw = readFileSync(path, 'utf8');
115
+ const document = parseCcFrontmatterDocument(raw);
116
+ const parsed = parseCcFrontmatter(raw);
117
+ if (document === undefined || parsed === undefined)
118
+ return { body: raw };
119
+ return {
120
+ body: document.body,
121
+ ...parsed.description === undefined ? {} : { description: parsed.description },
122
+ ...parsed.argumentHint === undefined ? {} : { argumentHint: parsed.argumentHint },
123
+ };
124
+ }
125
+ catch (error) {
126
+ return { error: `could not read command file "${entry.source}": ${String(error)}` };
127
+ }
128
+ }
129
+ return { error: 'has neither "content" nor a readable "source"' };
130
+ }
131
+ /**
132
+ * Substitute `$ARGUMENTS` with the invocation's raw input and steer the
133
+ * rendered command body into the conversation as a user message — the same
134
+ * cross-plane seam the upstream `/plan` command uses. Returning success
135
+ * without `text` keeps the command plane from echoing the body back as a
136
+ * status row: the injected message itself is the visible artifact.
137
+ *
138
+ * `Agent.followup` is async upstream, so a thenable return is awaited and a
139
+ * rejection is folded into `{ kind: 'error' }` instead of surfacing as an
140
+ * unhandled rejection (a synchronous throw is caught the same way).
141
+ */
142
+ export function dispatchCommandPrompt(invocation, body) {
143
+ const text = body.split('$ARGUMENTS').join(invocation.rawInput);
144
+ let scheduled;
145
+ try {
146
+ scheduled = invocation.agent.followup(createUserMessage({
147
+ content: [{ type: 'text', text }],
148
+ source: { kind: 'user' },
149
+ }));
150
+ }
151
+ catch (error) {
152
+ return { kind: 'error', text: `could not dispatch command prompt: ${String(error)}` };
153
+ }
154
+ if (scheduled === null || scheduled === undefined || typeof scheduled.then !== 'function') {
155
+ return { kind: 'success' };
156
+ }
157
+ return Promise.resolve(scheduled).then(() => ({ kind: 'success' }), (error) => ({ kind: 'error', text: `could not dispatch command prompt: ${String(error)}` }));
158
+ }
159
+ //# sourceMappingURL=commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"commands.js","sourceRoot":"","sources":["../src/commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Discover Claude Code plugin roots the way Claude Code actually installs them.
3
+ *
4
+ * Default (no `pluginDirs`): the intersection of `enabledPlugins` across the
5
+ * user/project/local settings cascade and `installed_plugins.json`. Explicit
6
+ * `pluginDirs` keeps the historical flatten (a root, or its one-level
7
+ * children, that hold `plugin.json` or `.claude-plugin/plugin.json`). Empty
8
+ * or null `pluginDirs` disables discovery. Best-effort: unreadable files and
9
+ * missing install paths skip rather than throw.
10
+ *
11
+ * @module
12
+ */
13
+ /** One discovered plugin root plus the name used to match marketplace overlays. */
14
+ export interface DiscoveredCcPlugin {
15
+ /** Absolute plugin root (the directory that holds the manifest / components). */
16
+ readonly root: string;
17
+ /** Plugin name without `@marketplace`, used as a marketplace-entry hint. */
18
+ readonly nameHint: string;
19
+ }
20
+ /** Options for one discovery pass. */
21
+ export interface DiscoverCcPluginRootsOptions {
22
+ /**
23
+ * Explicit discovery roots. `undefined` (absent) uses installed ∩ enabled;
24
+ * `[]` or `null` disables discovery; a non-empty list flattens those dirs.
25
+ */
26
+ readonly pluginDirs?: readonly string[] | null;
27
+ /** Claude config home; defaults to `$CLAUDE_CONFIG_DIR` or `~/.claude`. */
28
+ readonly claudeHome?: string;
29
+ /** Workspace used for project/local `enabledPlugins`; defaults to `process.cwd()`. */
30
+ readonly cwd?: string;
31
+ /** Optional logger for project-scope cwd and skipped bare enablement keys. */
32
+ readonly log?: {
33
+ info(message: string): void;
34
+ warn(message: string): void;
35
+ };
36
+ }
37
+ /** Nested Claude Code manifest path, preferred over a top-level `plugin.json`. */
38
+ export declare const NESTED_MANIFEST: string;
39
+ /** Legacy / fixture manifest path at the plugin root. */
40
+ export declare const TOP_LEVEL_MANIFEST = "plugin.json";
41
+ /**
42
+ * Discover plugin roots for the glue to mount.
43
+ * @param options - explicit dirs or the Claude-home / cwd pair for the default path.
44
+ * @returns unique `{ root, nameHint }` entries, in discovery order.
45
+ */
46
+ export declare function discoverCcPluginRoots(options?: DiscoverCcPluginRootsOptions): DiscoveredCcPlugin[];
47
+ /** `$CLAUDE_CONFIG_DIR` if set, otherwise `~/.claude`. */
48
+ export declare function resolveClaudeHome(explicit?: string): string;
49
+ //# sourceMappingURL=discovery.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,mFAAmF;AACnF,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;CAC1B;AAED,sCAAsC;AACtC,MAAM,WAAW,4BAA4B;IAC3C;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;IAC9C,2EAA2E;IAC3E,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,sFAAsF;IACtF,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAA;IACrB,8EAA8E;IAC9E,QAAQ,CAAC,GAAG,CAAC,EAAE;QAAE,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAA;CAC5E;AAED,kFAAkF;AAClF,eAAO,MAAM,eAAe,QAAwC,CAAA;AAEpE,yDAAyD;AACzD,eAAO,MAAM,kBAAkB,gBAAgB,CAAA;AAE/C;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,4BAAiC,GAAG,kBAAkB,EAAE,CAQtG;AAOD,0DAA0D;AAC1D,wBAAgB,iBAAiB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAK3D"}
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Discover Claude Code plugin roots the way Claude Code actually installs them.
3
+ *
4
+ * Default (no `pluginDirs`): the intersection of `enabledPlugins` across the
5
+ * user/project/local settings cascade and `installed_plugins.json`. Explicit
6
+ * `pluginDirs` keeps the historical flatten (a root, or its one-level
7
+ * children, that hold `plugin.json` or `.claude-plugin/plugin.json`). Empty
8
+ * or null `pluginDirs` disables discovery. Best-effort: unreadable files and
9
+ * missing install paths skip rather than throw.
10
+ *
11
+ * @module
12
+ */
13
+ import { readdirSync, readFileSync, statSync } from 'node:fs';
14
+ import { homedir } from 'node:os';
15
+ import { basename, join, resolve } from 'node:path';
16
+ import { resolveLocalSettingsDir } from '@dsh-cc/settings-cascade/local-root';
17
+ /** Nested Claude Code manifest path, preferred over a top-level `plugin.json`. */
18
+ export const NESTED_MANIFEST = join('.claude-plugin', 'plugin.json');
19
+ /** Legacy / fixture manifest path at the plugin root. */
20
+ export const TOP_LEVEL_MANIFEST = 'plugin.json';
21
+ /**
22
+ * Discover plugin roots for the glue to mount.
23
+ * @param options - explicit dirs or the Claude-home / cwd pair for the default path.
24
+ * @returns unique `{ root, nameHint }` entries, in discovery order.
25
+ */
26
+ export function discoverCcPluginRoots(options = {}) {
27
+ if (options.pluginDirs === null || isEmptyList(options.pluginDirs))
28
+ return [];
29
+ if (options.pluginDirs !== undefined)
30
+ return flattenPluginDirs(options.pluginDirs);
31
+ return discoverInstalledEnabled(resolveClaudeHome(options.claudeHome), options.cwd ?? process.cwd(), options.log);
32
+ }
33
+ /** True when an explicit `pluginDirs` list is present and empty. */
34
+ function isEmptyList(value) {
35
+ return value !== undefined && value.length === 0;
36
+ }
37
+ /** `$CLAUDE_CONFIG_DIR` if set, otherwise `~/.claude`. */
38
+ export function resolveClaudeHome(explicit) {
39
+ if (explicit !== undefined)
40
+ return explicit;
41
+ const fromEnv = process.env.CLAUDE_CONFIG_DIR;
42
+ if (fromEnv !== undefined && fromEnv.length > 0)
43
+ return fromEnv;
44
+ return join(homedir(), '.claude');
45
+ }
46
+ /** Flatten configured dirs: the dir itself, or one-level children that look like plugin roots. */
47
+ function flattenPluginDirs(dirs) {
48
+ const found = [];
49
+ const seen = new Set();
50
+ for (const dir of dirs) {
51
+ const hint = nameHintFromRoot(dir);
52
+ if (hint !== undefined) {
53
+ pushUnique(found, seen, dir, hint);
54
+ continue;
55
+ }
56
+ try {
57
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
58
+ if (!entry.isDirectory())
59
+ continue;
60
+ const child = join(dir, entry.name);
61
+ const childHint = nameHintFromRoot(child);
62
+ if (childHint !== undefined)
63
+ pushUnique(found, seen, child, childHint);
64
+ }
65
+ }
66
+ catch {
67
+ // Best-effort: an unreadable discovery root contributes nothing.
68
+ }
69
+ }
70
+ return found;
71
+ }
72
+ /**
73
+ * Manifest `name` when a nested or top-level plugin.json is present.
74
+ * Missing files are not a plugin root; unreadable/invalid JSON still is (basename fallback).
75
+ */
76
+ function nameHintFromRoot(dir) {
77
+ for (const file of [NESTED_MANIFEST, TOP_LEVEL_MANIFEST]) {
78
+ const text = readText(join(dir, file));
79
+ if (text === undefined)
80
+ continue;
81
+ const parsed = parseJson(text);
82
+ if (isRecord(parsed) && typeof parsed['name'] === 'string' && parsed['name'].length > 0)
83
+ return parsed['name'];
84
+ return basename(resolve(dir));
85
+ }
86
+ return undefined;
87
+ }
88
+ /** Default path: enabledPlugins cascade ∩ installed_plugins.json. */
89
+ function discoverInstalledEnabled(claudeHome, cwd, log) {
90
+ const enabled = enabledKeys(claudeHome, cwd, log);
91
+ const installed = readInstalled(join(claudeHome, 'plugins', 'installed_plugins.json'));
92
+ const found = [];
93
+ const seen = new Set();
94
+ for (const key of enabled) {
95
+ const picked = installed[key];
96
+ if (picked === undefined || !isDirectory(picked))
97
+ continue;
98
+ pushUnique(found, seen, picked, key.slice(0, key.indexOf('@')));
99
+ }
100
+ return found;
101
+ }
102
+ /** Keys whose cascaded `enabledPlugins` value is JSON `true`, in first-seen order. */
103
+ function enabledKeys(claudeHome, cwd, log) {
104
+ const cascade = [
105
+ { path: join(claudeHome, 'settings.json'), scope: 'user' },
106
+ { path: join(cwd, '.claude', 'settings.json'), scope: 'project' },
107
+ {
108
+ // Claude Code-parity: the *local* settings file is read from the git
109
+ // main checkout root (linked worktree) or git toplevel (subdirectory
110
+ // start); the project settings above stay at the launch cwd.
111
+ path: join(resolveLocalSettingsDir(cwd), '.claude', 'settings.local.json'),
112
+ scope: 'project',
113
+ },
114
+ ];
115
+ const state = new Map();
116
+ const order = [];
117
+ let projectScoped = false;
118
+ for (const file of cascade) {
119
+ const parsed = readJson(file.path);
120
+ if (!isRecord(parsed))
121
+ continue;
122
+ const block = parsed['enabledPlugins'];
123
+ if (!isRecord(block))
124
+ continue;
125
+ if (file.scope === 'project' && Object.keys(block).length > 0)
126
+ projectScoped = true;
127
+ for (const [key, value] of Object.entries(block)) {
128
+ if (typeof value !== 'boolean')
129
+ continue;
130
+ if (!state.has(key))
131
+ order.push(key);
132
+ state.set(key, value);
133
+ }
134
+ }
135
+ if (projectScoped)
136
+ log?.info(`cc-plugin-loader: project-scope enabledPlugins read from cwd ${cwd}`);
137
+ const enabled = [];
138
+ for (const key of order) {
139
+ if (state.get(key) !== true)
140
+ continue;
141
+ if (!key.includes('@')) {
142
+ log?.warn(`cc-plugin-loader: skipping bare enabledPlugins key "${key}" (expected name@marketplace)`);
143
+ continue;
144
+ }
145
+ enabled.push(key);
146
+ }
147
+ return enabled;
148
+ }
149
+ /** Winning `installPath` per `name@marketplace` (latest lastUpdated, then later array element). */
150
+ function readInstalled(path) {
151
+ const parsed = readJson(path);
152
+ if (!isRecord(parsed))
153
+ return {};
154
+ const plugins = parsed['plugins'];
155
+ if (!isRecord(plugins))
156
+ return {};
157
+ const out = {};
158
+ for (const [key, raw] of Object.entries(plugins)) {
159
+ if (!Array.isArray(raw))
160
+ continue;
161
+ const picked = pickInstallPath(raw);
162
+ if (picked !== undefined)
163
+ out[key] = picked;
164
+ }
165
+ return out;
166
+ }
167
+ function pickInstallPath(raw) {
168
+ let best;
169
+ for (const [index, item] of raw.entries()) {
170
+ if (!isRecord(item) || typeof item['installPath'] !== 'string')
171
+ continue;
172
+ const candidate = { path: item['installPath'], time: parseTime(item['lastUpdated']), index };
173
+ if (best === undefined || candidate.time > best.time || (candidate.time === best.time && candidate.index > best.index)) {
174
+ best = candidate;
175
+ }
176
+ }
177
+ return best?.path;
178
+ }
179
+ function parseTime(value) {
180
+ if (typeof value !== 'string')
181
+ return Number.NEGATIVE_INFINITY;
182
+ const time = Date.parse(value);
183
+ return Number.isNaN(time) ? Number.NEGATIVE_INFINITY : time;
184
+ }
185
+ function pushUnique(found, seen, root, nameHint) {
186
+ const resolved = resolve(root);
187
+ if (seen.has(resolved))
188
+ return;
189
+ seen.add(resolved);
190
+ found.push({ root: resolved, nameHint });
191
+ }
192
+ function isDirectory(path) {
193
+ try {
194
+ return statSync(path).isDirectory();
195
+ }
196
+ catch {
197
+ return false;
198
+ }
199
+ }
200
+ function readText(path) {
201
+ try {
202
+ return readFileSync(path, 'utf8');
203
+ }
204
+ catch {
205
+ return undefined;
206
+ }
207
+ }
208
+ function parseJson(text) {
209
+ try {
210
+ return JSON.parse(text);
211
+ }
212
+ catch {
213
+ return undefined;
214
+ }
215
+ }
216
+ function readJson(path) {
217
+ const text = readText(path);
218
+ return text === undefined ? undefined : parseJson(text);
219
+ }
220
+ function isRecord(value) {
221
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
222
+ }
223
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"discovery.js","sourceRoot":"","sources":["../src/discovery.ts"],"names":[],"mappings":"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"}
package/lib/hooks.d.ts ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Mount a Claude Code plugin's hooks.
3
+ *
4
+ * Reads the plugin's `hooks/hooks.json` (or an inline manifest `hooks`
5
+ * declaration), validates the structure, and injects the hooks into the
6
+ * hooks-claude-code bridge through the optional `hooks` guest seam. When the
7
+ * seam is absent the component is reported skipped, never failed, so a
8
+ * deployment without the bridge keeps loading the rest of the plugin.
9
+ *
10
+ * @module
11
+ */
12
+ import type { CcPluginManifest } from './types.ts';
13
+ import { ComponentTally } from './seams.ts';
14
+ /** The hooks seam: accepts a plugin's translated per-event hooks. */
15
+ export interface HooksSeam {
16
+ /**
17
+ * Merge a plugin's hooks into the bridge.
18
+ * @param pluginName - the plugin that owns the hooks.
19
+ * @param config - the per-event hook map (`ClaudeCodeHookConfig` shape).
20
+ * @returns the exact disposer that removes the injected hooks.
21
+ */
22
+ mergePluginHooks(pluginName: string, config: unknown): () => void;
23
+ }
24
+ /** Hooks live under this file in a plugin root, when present. */
25
+ export declare const STANDARD_HOOKS_FILE = "hooks/hooks.json";
26
+ /** Options for mounting one plugin's hooks. */
27
+ export interface MountHooksOptions {
28
+ /** The plugin root directory; the standard hooks file resolves against it. */
29
+ readonly pluginRoot: string;
30
+ /** The parsed manifest; `hooks` supplies an inline or file reference. */
31
+ readonly manifest: CcPluginManifest;
32
+ /** The hooks seam (probed; `undefined` to skip hooks). */
33
+ readonly hooks: HooksSeam | undefined;
34
+ }
35
+ /**
36
+ * Read and inject a plugin's hooks through the optional seam.
37
+ * @param options - plugin root, manifest, and the hooks seam.
38
+ * @returns mounted disposers and per-component counts.
39
+ */
40
+ export declare function mountHooks(options: MountHooksOptions): {
41
+ disposers: (() => void)[];
42
+ tally: ComponentTally;
43
+ };
44
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE3C,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACxB;;;;;OAKG;IACH,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,IAAI,CAAA;CAClE;AAED,iEAAiE;AACjE,eAAO,MAAM,mBAAmB,qBAAqB,CAAA;AAErD,+CAA+C;AAC/C,MAAM,WAAW,iBAAiB;IAChC,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;IACnC,0DAA0D;IAC1D,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAAA;CACtC;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG;IAAE,SAAS,EAAE,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;IAAC,KAAK,EAAE,cAAc,CAAA;CAAE,CAmB3G"}