opencode-dynamic-skills 1.0.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +180 -0
  3. package/README.zh-CN.md +180 -0
  4. package/dist/api.d.ts +96 -0
  5. package/dist/commands/SlashCommand.d.ts +26 -0
  6. package/dist/commands/SlashCommand.test.d.ts +1 -0
  7. package/dist/config.d.ts +47 -0
  8. package/dist/config.test.d.ts +1 -0
  9. package/dist/index.d.ts +29 -0
  10. package/dist/index.js +23805 -0
  11. package/dist/lib/Identifiers.d.ts +17 -0
  12. package/dist/lib/Indentifiers.test.d.ts +1 -0
  13. package/dist/lib/OpenCodeChat.d.ts +5 -0
  14. package/dist/lib/OpenCodeChat.test.d.ts +1 -0
  15. package/dist/lib/ReadyStateMachine.d.ts +36 -0
  16. package/dist/lib/SkillFs.d.ts +30 -0
  17. package/dist/lib/createPromptRenderer.d.ts +52 -0
  18. package/dist/lib/createPromptRenderer.test.d.ts +9 -0
  19. package/dist/lib/getModelFormat.d.ts +35 -0
  20. package/dist/lib/renderers/JsonPromptRenderer.d.ts +8 -0
  21. package/dist/lib/renderers/JsonPromptRenderer.test.d.ts +10 -0
  22. package/dist/lib/renderers/MdPromptRenderer.d.ts +18 -0
  23. package/dist/lib/renderers/MdPromptRenderer.test.d.ts +11 -0
  24. package/dist/lib/renderers/XmlPromptRenderer.d.ts +9 -0
  25. package/dist/lib/renderers/XmlPromptRenderer.test.d.ts +11 -0
  26. package/dist/lib/renderers/resourceMapToArray.d.ts +2 -0
  27. package/dist/lib/xml.d.ts +1 -0
  28. package/dist/mocks.d.ts +32 -0
  29. package/dist/mocks.skillfs.d.ts +1 -0
  30. package/dist/services/MessageModelIdAccountant.d.ts +22 -0
  31. package/dist/services/SkillRegistry.d.ts +9 -0
  32. package/dist/services/SkillRegistry.test.d.ts +1 -0
  33. package/dist/services/SkillResourceResolver.d.ts +20 -0
  34. package/dist/services/SkillResourceResolver.test.d.ts +1 -0
  35. package/dist/services/SkillSearcher.d.ts +77 -0
  36. package/dist/services/SkillSearcher.functions.test.d.ts +1 -0
  37. package/dist/services/SkillSearcher.test.d.ts +1 -0
  38. package/dist/services/logger.d.ts +2 -0
  39. package/dist/tools/SkillFinder.d.ts +46 -0
  40. package/dist/tools/SkillRecommender.d.ts +25 -0
  41. package/dist/tools/SkillRecommender.test.d.ts +1 -0
  42. package/dist/tools/SkillResourceReader.d.ts +43 -0
  43. package/dist/tools/SkillUser.d.ts +5 -0
  44. package/dist/types.d.ts +211 -0
  45. package/package.json +56 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Normalize a query string to match against toolName paths
3
+ * Converts slashes and hyphens to underscores for prefix matching
4
+ */
5
+ export declare function normalizePathQuery(query: string): string;
6
+ /**
7
+ * Strip the "skills_" prefix from toolName for user-facing matching
8
+ */
9
+ export declare function stripSkillsPrefix(toolName: string): string;
10
+ /**
11
+ * Generate tool name from skill path
12
+ * Examples:
13
+ * skills/brand-guidelines/SKILL.md → skills_brand_guidelines
14
+ * skills/document-skills/docx/SKILL.md → skills_document_skills_docx
15
+ * skills/image-processing/SKILL.md → skills_image_processing
16
+ */
17
+ export declare function toolName(skillPath: string): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ import type { PluginInput } from '@opencode-ai/plugin';
2
+ export declare function createInstructionInjector(ctx: PluginInput): (text: string, props: {
3
+ sessionId: string;
4
+ agent: string;
5
+ }) => Promise<void>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ReadyStateMachine - Async Initialization State Coordinator
3
+ *
4
+ * WHY: Tools need to wait for the skill registry to finish async discovery and
5
+ * parsing before they can execute. This state machine coordinates that initialization
6
+ * without requiring callers to manage Promise chains manually.
7
+ *
8
+ * DESIGN: Implements a simple state machine with watchers pattern for notification.
9
+ * State transitions: idle → loading → (ready|error)
10
+ *
11
+ * CONTRACT:
12
+ * - setStatus(): update internal state and notify all watchers
13
+ * - watchReady(callback): subscribe to state changes, returns unsubscribe function
14
+ * - whenReady(): blocking async method that resolves when state is ready, rejects if error
15
+ *
16
+ * WHY NOT A SIMPLE PROMISE:
17
+ * - Promise resolves once, but multiple tools may call whenReady() at different times
18
+ * - This pattern allows multiple concurrent waiters without race conditions
19
+ * - Supports reversible state (could transition back to loading if needed)
20
+ *
21
+ * EXAMPLE:
22
+ * const ready = createReadyStateMachine();
23
+ * ready.setStatus('loading');
24
+ * // ... do async work ...
25
+ * ready.setStatus('ready');
26
+ *
27
+ * // Tool execution:
28
+ * await ready.whenReady(); // blocks until ready
29
+ * // ... now safe to execute ...
30
+ */
31
+ export type ReadyStateMachine = ReturnType<typeof createReadyStateMachine>;
32
+ export declare function createReadyStateMachine(): {
33
+ setStatus: (newStatus: "idle" | "loading" | "ready" | "error") => void;
34
+ watchReady: (callback: (status: "idle" | "loading" | "ready" | "error") => void) => () => void;
35
+ whenReady: () => Promise<void>;
36
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * SkillFs - Abstract filesystem access for skills
3
+ *
4
+ * This module encapsulates all filesystem operations related to skill discovery and loading.
5
+ * It provides a mockable interface that works across different Node.js implementations,
6
+ * enabling unit tests to stub filesystem operations without complex mocking libraries.
7
+ *
8
+ * Each function is designed as a pure export to be easily replaced in test environments
9
+ * (e.g., via mocking FS access in test suites).
10
+ */
11
+ export declare const readSkillFile: (path: string) => Promise<string>;
12
+ /**
13
+ * List all files in a skill subdirectory (e.g., scripts/, resources/)
14
+ * Returns a flat array of absolute file paths
15
+ *
16
+ * @param skillPath - Base path to the skill directory
17
+ * @param subdirectory - Subdirectory to scan (e.g., 'scripts', 'resources')
18
+ * @returns Array of absolute file paths
19
+ */
20
+ export declare const listSkillFiles: (skillPath: string, subdirectory: string) => string[];
21
+ export declare const findSkillPaths: (basePath: string) => Promise<string[]>;
22
+ export declare const doesPathExist: (path: string) => boolean;
23
+ /**
24
+ * Detect MIME type from file extension
25
+ * Used for skill resources to identify content type
26
+ *
27
+ * @param filePath - Path to the file
28
+ * @returns MIME type string
29
+ */
30
+ export declare const detectMimeType: (filePath: string) => string;
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Prompt Renderer Factory
3
+ *
4
+ * WHY: Factory pattern centralizes renderer instantiation and makes it easy to:
5
+ * - Add new renderer types in the future
6
+ * - Test with different renderers
7
+ * - Handle invalid formats gracefully
8
+ */
9
+ /**
10
+ * Create a prompt renderer for the specified format
11
+ *
12
+ * @param format The desired format: 'json' | 'xml' | 'md'
13
+ * @returns A PromptRenderer instance for the specified format
14
+ * @throws Error if format is not recognized
15
+ */
16
+ export declare function createPromptRenderer(): {
17
+ getFormatter: (format: "json" | "xml" | "md") => (args: {
18
+ data: import("../types").Skill;
19
+ type: "Skill";
20
+ } | {
21
+ data: {
22
+ skill_name: string;
23
+ resource_path: string;
24
+ resource_mimetype: string;
25
+ content: string;
26
+ };
27
+ type: "SkillResource";
28
+ } | {
29
+ data: {
30
+ mode?: "search" | "recommend";
31
+ query: string | string[];
32
+ skills: Array<{
33
+ name: string;
34
+ description: string;
35
+ }>;
36
+ summary: {
37
+ total: number;
38
+ matches: number;
39
+ feedback: string;
40
+ };
41
+ recommendations?: Array<{
42
+ name: string;
43
+ description: string;
44
+ score: number;
45
+ reason: string;
46
+ }>;
47
+ guidance?: string;
48
+ debug?: import("../types").SkillRegistryDebugInfo;
49
+ };
50
+ type: "SkillSearchResults";
51
+ }) => string;
52
+ };
@@ -0,0 +1,9 @@
1
+ /**
2
+ * createPromptRenderer Factory Tests
3
+ *
4
+ * Test coverage:
5
+ * - Correct renderer instantiation for each format
6
+ * - Invalid format error handling
7
+ * - Format identifier verification
8
+ */
9
+ export {};
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Model Format Resolver - Select renderer based on active LLM model
3
+ *
4
+ * WHY: Different LLM models have different preferences and strengths:
5
+ * - Claude models: optimized for XML, prefer structured XML injection
6
+ * - GPT models: strong JSON parsing, prefer JSON-formatted data
7
+ * - Other models: may benefit from markdown readability
8
+ *
9
+ * This function detects the active model and selects the configured
10
+ * format preference for that model, falling back to progressively more
11
+ * generic model patterns.
12
+ *
13
+ * HOW IT WORKS:
14
+ * 1. Query the current session via client.session.message()
15
+ * 2. Extract the modelID from the response (e.g., "anthropic-claude-3-5-sonnet")
16
+ * 3. Try matching in order:
17
+ * - Full model ID (e.g., "anthropic-claude-3-5-sonnet")
18
+ * - Generic model pattern (e.g., "claude-3-5-sonnet")
19
+ * 4. If no match, fall back to promptRenderer default
20
+ */
21
+ import type { PluginConfig } from '../types';
22
+ /**
23
+ * Resolve the appropriate prompt format for the current model
24
+ *
25
+ * @param args An object containing:
26
+ * - modelId?: The identifier of the active model (e.g., "claude-3-5-sonnet")
27
+ * - providerId?: The identifier of the model provider (e.g., "anthropic")
28
+ * - config: The plugin configuration (has promptRenderer and modelRenderers)
29
+ * @returns The format to use: 'json' | 'xml' | 'md'
30
+ */
31
+ export declare function getModelFormat(args: {
32
+ modelId?: string;
33
+ providerId?: string;
34
+ config: PluginConfig;
35
+ }): 'json' | 'xml' | 'md';
@@ -0,0 +1,8 @@
1
+ /**
2
+ * JsonPromptRenderer - Format objects as JSON
3
+ *
4
+ * WHY: Some LLM models (especially GPT family) have strong JSON parsing
5
+ * and prefer structured JSON data over XML for reliability and clarity.
6
+ */
7
+ import type { PromptRenderer } from '../../types';
8
+ export declare const createJsonPromptRenderer: () => PromptRenderer;
@@ -0,0 +1,10 @@
1
+ /**
2
+ * JsonPromptRenderer Tests
3
+ *
4
+ * Test coverage for real use cases:
5
+ * - Rendering Skill objects for prompt injection
6
+ * - Rendering SkillResource objects (file content)
7
+ * - Rendering SkillSearchResults objects (search results)
8
+ * - Proper JSON formatting with indentation
9
+ */
10
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * MdPromptRenderer - Format objects as human-readable Markdown
3
+ *
4
+ * WHY: Markdown provides human-readable formatting that works well for:
5
+ * - Models that benefit from visual structure and readability
6
+ * - Debugging and development (easier to read in logs)
7
+ * - Accessibility and presentation
8
+ *
9
+ * STRUCTURE:
10
+ * - Top-level keys → H3 headings (### key)
11
+ * - Nested objects → H4 headings (#### key) with increased indentation
12
+ * - Leaf nodes → nested bullet list items with emphasis: - **key**: *value*
13
+ * - Arrays → nested bullets under parent key
14
+ * - Special characters → HTML-escaped (&lt;, &gt;, &amp;)
15
+ * - Skill content → appended after --- separator with ### Content heading
16
+ */
17
+ import type { PromptRenderer } from '../../types';
18
+ export declare const createMdPromptRenderer: () => PromptRenderer;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * MdPromptRenderer Tests
3
+ *
4
+ * Test coverage for real use cases:
5
+ * - Rendering Skill objects with metadata, references, scripts, assets
6
+ * - Rendering SkillResource objects (file content)
7
+ * - Rendering SkillSearchResults objects
8
+ * - HTML character escaping for security
9
+ * - Special values handling (null, undefined)
10
+ */
11
+ export {};
@@ -0,0 +1,9 @@
1
+ /**
2
+ * XmlPromptRenderer - Format objects as XML (current default)
3
+ *
4
+ * WHY: Claude models are trained extensively on XML and prefer structured
5
+ * XML injection for skill metadata and search results. This maintains the
6
+ * current behavior as the default and recommended format for Claude models.
7
+ */
8
+ import type { PromptRenderer } from '../../types';
9
+ export declare const createXmlPromptRenderer: () => PromptRenderer;
@@ -0,0 +1,11 @@
1
+ /**
2
+ * XmlPromptRenderer Tests
3
+ *
4
+ * Test coverage for real use cases:
5
+ * - Rendering Skill objects as XML with resource maps converted to arrays
6
+ * - Rendering SkillResource objects (file content)
7
+ * - Rendering SkillSearchResults objects
8
+ * - XML character escaping for security
9
+ * - Resource map serialization
10
+ */
11
+ export {};
@@ -0,0 +1,2 @@
1
+ import { MapValue, SkillResourceMap } from '../../types';
2
+ export declare const resourceMapToArray: (resources: SkillResourceMap) => MapValue<SkillResourceMap>[];
@@ -0,0 +1 @@
1
+ export declare function jsonToXml(json: object, rootElement?: string): string;
@@ -0,0 +1,32 @@
1
+ import type { Skill, SkillRegistry, SkillRegistryController, SkillSearchResult } from './types';
2
+ /**
3
+ * Creates a mock Skill object with sensible defaults
4
+ * @param overrides Partial skill properties to override defaults
5
+ * @returns A complete Skill object
6
+ */
7
+ export declare function createMockSkill(overrides?: Partial<Skill>): Skill;
8
+ /**
9
+ * Creates a mock SkillRegistryController
10
+ * @param skills Array of skills to include in the registry
11
+ * @returns A mock registry controller
12
+ */
13
+ export declare function createMockRegistryController(skills?: Skill[]): SkillRegistryController;
14
+ /**
15
+ * Creates a mock SkillProvider with a registry controller
16
+ * @param skills Array of skills to include in the provider
17
+ * @returns A mock skill provider
18
+ */
19
+ export declare function createMockRegistry(skills?: Skill[]): Promise<SkillRegistry>;
20
+ /**
21
+ * Creates a mock SkillSearchResult
22
+ * @param overrides Partial search result properties to override defaults
23
+ * @returns A mock search result
24
+ */
25
+ export declare function mockSearchResult(overrides?: Partial<SkillSearchResult>): SkillSearchResult;
26
+ /**
27
+ * Creates a fully configured mock search result with skill matches and metadata
28
+ * @param skills Array of skill matches to include in the result
29
+ * @param overrides Additional overrides for search result properties
30
+ * @returns A complete search result with proper structure
31
+ */
32
+ export declare function mockFullSearchResult(skills?: Skill[], overrides?: Partial<SkillSearchResult>): SkillSearchResult;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,22 @@
1
+ declare function createMessageModelIdAccountant(): {
2
+ reset: () => void;
3
+ track: (info: {
4
+ sessionID: string;
5
+ messageID: string;
6
+ modelID: string;
7
+ providerID: string;
8
+ }) => void;
9
+ untrackMessage: (args: {
10
+ messageID: string;
11
+ sessionID: string;
12
+ }) => void;
13
+ untrackSession: (sessionID: string) => void;
14
+ getModelInfo: (args: {
15
+ messageID: string;
16
+ sessionID: string;
17
+ }) => {
18
+ modelID: string;
19
+ providerID: string;
20
+ } | undefined;
21
+ };
22
+ export { createMessageModelIdAccountant };
@@ -0,0 +1,9 @@
1
+ import { PluginConfig, PluginLogger, SkillRegistry, SkillRegistryController, SkillResourceMap } from '../types';
2
+ export declare const stripTrailingPathSeparators: (path: string) => string;
3
+ export declare const suggestSkillsDirectoryPath: (path: string) => string | null;
4
+ export declare function createSkillRegistryController(): SkillRegistryController;
5
+ /**
6
+ * SkillRegistry manages skill discovery and parsing
7
+ */
8
+ export declare function createSkillRegistry(config: PluginConfig, logger: PluginLogger): Promise<SkillRegistry>;
9
+ export declare function createSkillResourceMap(skillPath: string, filePaths: string[]): SkillResourceMap;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import type { SkillRegistry } from '../types';
2
+ export declare function normalizeSkillResourcePath(inputPath: string): string;
3
+ /**
4
+ * Skill resources are mapped on startup as a dictionary of relative paths to resource metadata.
5
+ *
6
+ * This resolver uses that mapping to solve several things:
7
+ *
8
+ * - locate and read the actual resource files.
9
+ * - ensure the requested path isn't outside the skill directory (security).
10
+ * - return the content and mime type of the resource.
11
+ */
12
+ export declare function createSkillResourceResolver(provider: SkillRegistry): (args: {
13
+ skill_name: string;
14
+ type?: string;
15
+ relative_path: string;
16
+ }) => Promise<{
17
+ absolute_path: string;
18
+ content: string;
19
+ mimeType: string;
20
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,77 @@
1
+ /**
2
+ * SkillSearcher - Natural Language Query Parser and Skill Ranking Engine
3
+ *
4
+ * WHY: Users expect Gmail-style search syntax (quoted phrases, negation, multiple terms).
5
+ * This module abstracts the search-string library and implements skill-specific ranking.
6
+ *
7
+ * SEARCH STRATEGY:
8
+ * 1. Parse user query into include/exclude terms (using search-string for Gmail syntax)
9
+ * 2. Filter: ALL include terms must appear in skill (name, description, or toolName)
10
+ * 3. Exclude: Remove matches that contain any exclude term
11
+ * 4. Rank: Score by match location (name matches weighted 3×, description 1×)
12
+ * 5. Sort: By total score (desc) → name matches (desc) → alphabetically
13
+ *
14
+ * QUERY INTERPRETATION:
15
+ * - "git commit" → include: [git, commit]
16
+ * - "git -rebase" → include: [git], exclude: [rebase]
17
+ * - '"git rebase"' → include: [git rebase] (phrase, no split)
18
+ * - "*" or empty → list all skills
19
+ *
20
+ * RANKING ALGORITHM:
21
+ * - Name match: +3 per term found in skill name
22
+ * - Description match: +1 per term found in description
23
+ * - Exact name match: +10 bonus
24
+ * - Final sort: highest score first, tie-break by name matches, then alphabetical
25
+ *
26
+ * WHY SEARCH-STRING: Handles complex syntax edge cases (quotes, escaped chars)
27
+ * without rolling our own parser. Proven in production systems (Gmail, etc).
28
+ *
29
+ * WHY NO PARTIAL MATCHING: "python" won't match "python-docstring" because we check
30
+ * term-by-term inclusion, not word-level matching. This matches user expectations
31
+ * (they searched for "python", not "python-" or "pythonish").
32
+ */
33
+ import type { Skill, ParsedSkillQuery, SkillRank, SkillRegistryController, SkillSearcher } from '../types';
34
+ /**
35
+ * Parse a user query into structured search terms
36
+ */
37
+ export declare function parseQuery(queryString: string | string[]): ParsedSkillQuery;
38
+ /**
39
+ * Calculate ranking score for a skill against query terms
40
+ *
41
+ * SCORING FORMULA:
42
+ * - nameMatches: number of include terms found in skill name (0 or more)
43
+ * - descMatches: number of include terms found in description (0 or more)
44
+ * - totalScore = (nameMatches × 3) + (descMatches × 1) + exactBonus
45
+ * - exactBonus: +10 if query is single term and equals skill name exactly
46
+ *
47
+ * WHY THIS WEIGHTING:
48
+ * - Skill name is a strong signal (3× weight) because it's the identifier
49
+ * - Description matches are weaker (1× weight) because skill names vary
50
+ * - Exact match bonus breaks ties and promotes direct matches to top
51
+ *
52
+ * EXAMPLE:
53
+ * - Query: "git" | Skill name: "writing-git-commits" | Score: 3 (name match)
54
+ * - Query: "git" | Skill name: "git" | Score: 13 (name match + exact bonus)
55
+ * - Query: "revert changes" | Skill name: "git-revert" | Description: "Revert..."
56
+ * | Score: 4 (1 name match + 1 desc match + 2 bonus doesn't apply)
57
+ */
58
+ export declare function rankSkill(skill: Skill, includeTerms: string[]): SkillRank;
59
+ /**
60
+ * Filter out skills matching exclusion terms
61
+ */
62
+ export declare function shouldIncludeSkill(skill: Skill, excludeTerms: string[]): boolean;
63
+ /**
64
+ * Generate user-friendly feedback about query interpretation
65
+ */
66
+ export declare function generateFeedback(query: ParsedSkillQuery, matchCount: number): string;
67
+ /**
68
+ * SkillSearcher - Natural Language Query Parser for Skills
69
+ *
70
+ * Provides clean abstraction over search-string library for skill discovery.
71
+ * Handles query parsing, ranking, and result formatting with support for:
72
+ * - Natural syntax (Gmail-style)
73
+ * - Negation (-term)
74
+ * - Quoted phrases
75
+ * - Multiple search terms (AND logic)
76
+ */
77
+ export declare function createSkillSearcher(registry: SkillRegistryController): SkillSearcher;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ import { PluginConfig, PluginLogger } from '../types';
2
+ export declare function createLogger(config: PluginConfig): PluginLogger;
@@ -0,0 +1,46 @@
1
+ /**
2
+ * SkillFinder Tool - Natural Language Skill Discovery
3
+ *
4
+ * WHY: This tool wraps SkillSearcher with ready-state synchronization.
5
+ * Users call skill_find("git commits") to search for relevant skills.
6
+ * The tool must block until the registry is fully initialized before searching.
7
+ *
8
+ * CRITICAL PATTERN: `await provider.controller.ready.whenReady()`
9
+ * This line ensures that skill discovery has completed before we attempt to
10
+ * search. Without this, early calls would search an empty registry.
11
+ *
12
+ * RETURN VALUE: Object with:
13
+ * - query: original query for user reference
14
+ * - skills: matched skills with toolName and description
15
+ * - summary: metadata (total skills, matches found, feedback message)
16
+ * - debug: registry debug info (only if enabled in config)
17
+ *
18
+ * TOOL REGISTRATION: This factory is called in api.ts like:
19
+ * findSkills: createSkillFinder(registry)
20
+ * Which returns an async function that OpenCode registers as a tool.
21
+ *
22
+ * FEEDBACK: The feedback message explains the query interpretation to the user:
23
+ * "Searching for: **git commit** | Found 3 matches"
24
+ *
25
+ * @param provider SkillRegistry instance (must be initialized first)
26
+ * @returns Async function callable by OpenCode as skill_find tool
27
+ */
28
+ import type { SkillRegistry } from '../types';
29
+ /**
30
+ * Creates a tool function that searches for skills
31
+ */
32
+ export declare function createSkillFinder(provider: SkillRegistry): (args: {
33
+ query: string | string[];
34
+ }) => Promise<{
35
+ query: string | string[];
36
+ skills: {
37
+ name: string;
38
+ description: string;
39
+ }[];
40
+ summary: {
41
+ total: number;
42
+ matches: number;
43
+ feedback: string;
44
+ };
45
+ debug: import("../types").SkillRegistryDebugInfo | undefined;
46
+ }>;
@@ -0,0 +1,25 @@
1
+ import type { SkillRegistry } from '../types';
2
+ export declare function createSkillRecommender(provider: SkillRegistry): (args: {
3
+ task: string;
4
+ limit?: number;
5
+ }) => Promise<{
6
+ mode: "recommend";
7
+ query: string;
8
+ skills: {
9
+ name: string;
10
+ description: string;
11
+ }[];
12
+ recommendations: {
13
+ name: string;
14
+ description: string;
15
+ score: number;
16
+ reason: string;
17
+ }[];
18
+ guidance: string;
19
+ summary: {
20
+ total: number;
21
+ matches: number;
22
+ feedback: string;
23
+ };
24
+ debug: import("../types").SkillRegistryDebugInfo | undefined;
25
+ }>;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,43 @@
1
+ /**
2
+ * SkillResourceReader Tool - Safe Skill Resource Access
3
+ *
4
+ * WHY: Skills contain resources (scripts, assets, references) that need to be
5
+ * retrieved safely. This tool:
6
+ * 1. Validates the resource type (scripts|assets|references)
7
+ * 2. Resolves the path safely (prevents path traversal attacks)
8
+ * 3. Reads the file content and returns it for injection
9
+ *
10
+ * CRITICAL SECURITY: Resources are pre-indexed at parse time. This tool can only
11
+ * retrieve resources that were explicitly registered in the skill's resource maps.
12
+ * It cannot request arbitrary paths like "../../etc/passwd".
13
+ *
14
+ * PATH PARSING: Input path format is "type/relative/path"
15
+ * - type: one of 'scripts', 'assets', 'references' (validated)
16
+ * - relative/path: path within that type's directory
17
+ *
18
+ * EXAMPLE:
19
+ * - Args: { skill_name: 'git-commit', relative_path: 'scripts/commit.sh' }
20
+ * - Type: 'scripts', path: 'commit.sh'
21
+ * - Resolves: skill.scripts.get('commit.sh') → reads file content
22
+ *
23
+ * RETURN VALUE: Object with:
24
+ * - injection: contains skill_name, resource_path, MIME type, and content
25
+ * The caller (index.ts) injects this content into the message body
26
+ *
27
+ * READY STATE: Must wait for registry initialization before accessing skills
28
+ *
29
+ * @param provider SkillRegistry instance (must be initialized first)
30
+ * @returns Async function callable by OpenCode as skill_resource tool
31
+ */
32
+ import { SkillRegistry } from '../types';
33
+ export declare function createSkillResourceReader(provider: SkillRegistry): (args: {
34
+ skill_name: string;
35
+ relative_path: string;
36
+ }) => Promise<{
37
+ injection: {
38
+ skill_name: string;
39
+ resource_path: string;
40
+ resource_mimetype: string;
41
+ content: string;
42
+ };
43
+ }>;
@@ -0,0 +1,5 @@
1
+ import type { Skill, SkillRegistry } from '../types';
2
+ export declare function createSkillLoader(provider: SkillRegistry): (skillNames: string[]) => Promise<{
3
+ loaded: Skill[];
4
+ notFound: string[];
5
+ }>;