@oh-my-pi/pi-coding-agent 0.1.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.
- package/CHANGELOG.md +1629 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/config-usage.md +113 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +670 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +89 -0
- package/src/bun-imports.d.ts +16 -0
- package/src/capability/context-file.ts +40 -0
- package/src/capability/extension.ts +48 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +616 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +52 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule.ts +56 -0
- package/src/capability/settings.ts +35 -0
- package/src/capability/skill.ts +49 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/system-prompt.ts +35 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +166 -0
- package/src/cli/args.ts +259 -0
- package/src/cli/file-processor.ts +121 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +661 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli/update-cli.ts +274 -0
- package/src/cli.ts +10 -0
- package/src/config.ts +391 -0
- package/src/core/agent-session.ts +2178 -0
- package/src/core/auth-storage.ts +258 -0
- package/src/core/bash-executor.ts +197 -0
- package/src/core/compaction/branch-summarization.ts +315 -0
- package/src/core/compaction/compaction.ts +664 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +153 -0
- package/src/core/custom-commands/bundled/review/index.ts +156 -0
- package/src/core/custom-commands/index.ts +15 -0
- package/src/core/custom-commands/loader.ts +226 -0
- package/src/core/custom-commands/types.ts +112 -0
- package/src/core/custom-tools/index.ts +22 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +185 -0
- package/src/core/custom-tools/wrapper.ts +29 -0
- package/src/core/exec.ts +139 -0
- package/src/core/export-html/index.ts +159 -0
- package/src/core/export-html/template.css +774 -0
- package/src/core/export-html/template.generated.ts +2 -0
- package/src/core/export-html/template.html +45 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/template.macro.ts +24 -0
- package/src/core/file-mentions.ts +54 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +288 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +98 -0
- package/src/core/hooks/types.ts +770 -0
- package/src/core/index.ts +53 -0
- package/src/core/logger.ts +112 -0
- package/src/core/mcp/client.ts +185 -0
- package/src/core/mcp/config.ts +248 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +99 -0
- package/src/core/mcp/manager.ts +235 -0
- package/src/core/mcp/tool-bridge.ts +156 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +228 -0
- package/src/core/messages.ts +211 -0
- package/src/core/model-registry.ts +334 -0
- package/src/core/model-resolver.ts +494 -0
- package/src/core/plugins/doctor.ts +67 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +339 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +37 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +900 -0
- package/src/core/session-manager.ts +1837 -0
- package/src/core/settings-manager.ts +860 -0
- package/src/core/skills.ts +352 -0
- package/src/core/slash-commands.ts +132 -0
- package/src/core/system-prompt.ts +442 -0
- package/src/core/timings.ts +25 -0
- package/src/core/title-generator.ts +110 -0
- package/src/core/tools/ask.ts +193 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +91 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +487 -0
- package/src/core/tools/edit.ts +140 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +63 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +200 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +338 -0
- package/src/core/tools/exa/types.ts +167 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +244 -0
- package/src/core/tools/grep.ts +584 -0
- package/src/core/tools/index.ts +283 -0
- package/src/core/tools/ls.ts +142 -0
- package/src/core/tools/lsp/client.ts +767 -0
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +845 -0
- package/src/core/tools/lsp/edits.ts +110 -0
- package/src/core/tools/lsp/index.ts +1364 -0
- package/src/core/tools/lsp/render.ts +560 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +495 -0
- package/src/core/tools/lsp/utils.ts +526 -0
- package/src/core/tools/notebook.ts +182 -0
- package/src/core/tools/output.ts +198 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +507 -0
- package/src/core/tools/renderers.ts +820 -0
- package/src/core/tools/review.ts +275 -0
- package/src/core/tools/rulebook.ts +124 -0
- package/src/core/tools/task/agents.ts +158 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/commands.ts +157 -0
- package/src/core/tools/task/discovery.ts +217 -0
- package/src/core/tools/task/executor.ts +531 -0
- package/src/core/tools/task/index.ts +548 -0
- package/src/core/tools/task/model-resolver.ts +176 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +502 -0
- package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
- package/src/core/tools/task/types.ts +142 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2511 -0
- package/src/core/tools/web-search/auth.ts +199 -0
- package/src/core/tools/web-search/index.ts +583 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +196 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +372 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +63 -0
- package/src/core/ttsr.ts +211 -0
- package/src/core/utils.ts +187 -0
- package/src/discovery/agents-md.ts +75 -0
- package/src/discovery/builtin.ts +647 -0
- package/src/discovery/claude.ts +623 -0
- package/src/discovery/cline.ts +104 -0
- package/src/discovery/codex.ts +571 -0
- package/src/discovery/cursor.ts +266 -0
- package/src/discovery/gemini.ts +368 -0
- package/src/discovery/github.ts +120 -0
- package/src/discovery/helpers.test.ts +127 -0
- package/src/discovery/helpers.ts +249 -0
- package/src/discovery/index.ts +84 -0
- package/src/discovery/mcp-json.ts +127 -0
- package/src/discovery/vscode.ts +99 -0
- package/src/discovery/windsurf.ts +219 -0
- package/src/index.ts +192 -0
- package/src/main.ts +507 -0
- package/src/migrations.ts +156 -0
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +48 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +199 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +296 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +479 -0
- package/src/modes/interactive/components/extensions/index.ts +9 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +313 -0
- package/src/modes/interactive/components/extensions/state-manager.ts +558 -0
- package/src/modes/interactive/components/extensions/types.ts +191 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +560 -0
- package/src/modes/interactive/components/oauth-selector.ts +136 -0
- package/src/modes/interactive/components/plugin-settings.ts +481 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +220 -0
- package/src/modes/interactive/components/settings-defs.ts +597 -0
- package/src/modes/interactive/components/settings-selector.ts +545 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +384 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +946 -0
- package/src/modes/interactive/components/tree-selector.ts +877 -0
- package/src/modes/interactive/components/ttsr-notification.ts +82 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +228 -0
- package/src/modes/interactive/interactive-mode.ts +2669 -0
- package/src/modes/interactive/theme/dark.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +99 -0
- package/src/modes/interactive/theme/theme-schema.json +424 -0
- package/src/modes/interactive/theme/theme.ts +2211 -0
- package/src/modes/print-mode.ts +163 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +494 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/prompts/architect-plan.md +10 -0
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/browser.md +71 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/explore.md +82 -0
- package/src/prompts/implement-with-critic.md +11 -0
- package/src/prompts/implement.md +11 -0
- package/src/prompts/init.md +30 -0
- package/src/prompts/plan.md +54 -0
- package/src/prompts/reviewer.md +81 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/prompts/task.md +56 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell-snapshot.ts +218 -0
- package/src/utils/shell.ts +364 -0
- package/src/utils/tools-manager.ts +265 -0
|
@@ -0,0 +1,616 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capability Registry
|
|
3
|
+
*
|
|
4
|
+
* Central registry for capabilities and providers. Provides the main API for:
|
|
5
|
+
* - Defining capabilities (what we're looking for)
|
|
6
|
+
* - Registering providers (where to find it)
|
|
7
|
+
* - Loading items for a capability across all providers
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { type Dirent, readdirSync, readFileSync, statSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
13
|
+
import type {
|
|
14
|
+
Capability,
|
|
15
|
+
CapabilityInfo,
|
|
16
|
+
CapabilityResult,
|
|
17
|
+
LoadContext,
|
|
18
|
+
LoadOptions,
|
|
19
|
+
Provider,
|
|
20
|
+
ProviderInfo,
|
|
21
|
+
SourceMeta,
|
|
22
|
+
} from "./types";
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Registry State
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/** Registry of all capabilities */
|
|
29
|
+
const capabilities = new Map<string, Capability<unknown>>();
|
|
30
|
+
|
|
31
|
+
/** Reverse index: provider ID -> capability IDs it's registered for */
|
|
32
|
+
const providerCapabilities = new Map<string, Set<string>>();
|
|
33
|
+
|
|
34
|
+
/** Provider display metadata (shared across capabilities) */
|
|
35
|
+
const providerMeta = new Map<string, { displayName: string; description: string }>();
|
|
36
|
+
|
|
37
|
+
/** Disabled providers (by ID) */
|
|
38
|
+
const disabledProviders = new Set<string>();
|
|
39
|
+
|
|
40
|
+
/** Settings manager for persistence (if set) */
|
|
41
|
+
let settingsManager: { getDisabledProviders(): string[]; setDisabledProviders(ids: string[]): void } | null = null;
|
|
42
|
+
|
|
43
|
+
// =============================================================================
|
|
44
|
+
// Filesystem Cache
|
|
45
|
+
// =============================================================================
|
|
46
|
+
|
|
47
|
+
type StatResult = "file" | "dir" | null;
|
|
48
|
+
|
|
49
|
+
const statCache = new Map<string, StatResult>();
|
|
50
|
+
const contentCache = new Map<string, string | null>();
|
|
51
|
+
const dirCache = new Map<string, Dirent[]>();
|
|
52
|
+
|
|
53
|
+
function clearCache(): void {
|
|
54
|
+
statCache.clear();
|
|
55
|
+
contentCache.clear();
|
|
56
|
+
dirCache.clear();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function createFsHelpers(cwd: string): LoadContext["fs"] {
|
|
60
|
+
return {
|
|
61
|
+
exists(path: string): boolean {
|
|
62
|
+
const abs = resolve(cwd, path);
|
|
63
|
+
if (!statCache.has(abs)) {
|
|
64
|
+
try {
|
|
65
|
+
const stat = statSync(abs);
|
|
66
|
+
statCache.set(abs, stat.isDirectory() ? "dir" : stat.isFile() ? "file" : null);
|
|
67
|
+
} catch {
|
|
68
|
+
statCache.set(abs, null);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return statCache.get(abs) !== null;
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
isDir(path: string): boolean {
|
|
75
|
+
this.exists(path);
|
|
76
|
+
return statCache.get(resolve(cwd, path)) === "dir";
|
|
77
|
+
},
|
|
78
|
+
|
|
79
|
+
isFile(path: string): boolean {
|
|
80
|
+
this.exists(path);
|
|
81
|
+
return statCache.get(resolve(cwd, path)) === "file";
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
readFile(path: string): string | null {
|
|
85
|
+
const abs = resolve(cwd, path);
|
|
86
|
+
if (!contentCache.has(abs)) {
|
|
87
|
+
try {
|
|
88
|
+
contentCache.set(abs, readFileSync(abs, "utf-8"));
|
|
89
|
+
} catch {
|
|
90
|
+
contentCache.set(abs, null);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return contentCache.get(abs) ?? null;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
readDir(path: string): string[] {
|
|
97
|
+
const abs = resolve(cwd, path);
|
|
98
|
+
if (!this.isDir(path)) return [];
|
|
99
|
+
if (!dirCache.has(abs)) {
|
|
100
|
+
try {
|
|
101
|
+
dirCache.set(abs, readdirSync(abs, { withFileTypes: true }));
|
|
102
|
+
} catch {
|
|
103
|
+
dirCache.set(abs, []);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return (dirCache.get(abs) ?? []).map((e) => e.name);
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
walkUp(name: string, opts: { file?: boolean; dir?: boolean } = {}): string | null {
|
|
110
|
+
const { file = true, dir = true } = opts;
|
|
111
|
+
let current = cwd;
|
|
112
|
+
while (true) {
|
|
113
|
+
const candidate = join(current, name);
|
|
114
|
+
if (file && this.isFile(candidate)) return candidate;
|
|
115
|
+
if (dir && this.isDir(candidate)) return candidate;
|
|
116
|
+
const parent = dirname(current);
|
|
117
|
+
if (parent === current) return null;
|
|
118
|
+
current = parent;
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// Registration API
|
|
126
|
+
// =============================================================================
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Define a new capability.
|
|
130
|
+
*/
|
|
131
|
+
export function defineCapability<T>(def: Omit<Capability<T>, "providers">): Capability<T> {
|
|
132
|
+
if (capabilities.has(def.id)) {
|
|
133
|
+
throw new Error(`Capability "${def.id}" is already defined`);
|
|
134
|
+
}
|
|
135
|
+
const capability: Capability<T> = { ...def, providers: [] };
|
|
136
|
+
capabilities.set(def.id, capability as Capability<unknown>);
|
|
137
|
+
return capability;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Register a provider for a capability.
|
|
142
|
+
*/
|
|
143
|
+
export function registerProvider<T>(capabilityId: string, provider: Provider<T>): void {
|
|
144
|
+
const capability = capabilities.get(capabilityId);
|
|
145
|
+
if (!capability) {
|
|
146
|
+
throw new Error(`Unknown capability: "${capabilityId}". Define it first with defineCapability().`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Store provider metadata (for cross-capability display)
|
|
150
|
+
if (!providerMeta.has(provider.id)) {
|
|
151
|
+
providerMeta.set(provider.id, {
|
|
152
|
+
displayName: provider.displayName,
|
|
153
|
+
description: provider.description,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Track which capabilities this provider is registered for
|
|
158
|
+
if (!providerCapabilities.has(provider.id)) {
|
|
159
|
+
providerCapabilities.set(provider.id, new Set());
|
|
160
|
+
}
|
|
161
|
+
providerCapabilities.get(provider.id)!.add(capabilityId);
|
|
162
|
+
|
|
163
|
+
// Insert in priority order (highest first)
|
|
164
|
+
const providers = capability.providers as Provider<T>[];
|
|
165
|
+
const idx = providers.findIndex((p) => p.priority < provider.priority);
|
|
166
|
+
if (idx === -1) {
|
|
167
|
+
providers.push(provider);
|
|
168
|
+
} else {
|
|
169
|
+
providers.splice(idx, 0, provider);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// =============================================================================
|
|
174
|
+
// Loading API
|
|
175
|
+
// =============================================================================
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Core loading logic shared by both load() and loadSync().
|
|
179
|
+
*/
|
|
180
|
+
function loadImpl<T>(
|
|
181
|
+
capability: Capability<T>,
|
|
182
|
+
providers: Provider<T>[],
|
|
183
|
+
ctx: LoadContext,
|
|
184
|
+
options: LoadOptions,
|
|
185
|
+
): CapabilityResult<T> {
|
|
186
|
+
const allItems: Array<T & { _source: SourceMeta; _shadowed?: boolean }> = [];
|
|
187
|
+
const allWarnings: string[] = [];
|
|
188
|
+
const contributingProviders: string[] = [];
|
|
189
|
+
|
|
190
|
+
for (const provider of providers) {
|
|
191
|
+
try {
|
|
192
|
+
const result = provider.load(ctx);
|
|
193
|
+
|
|
194
|
+
if (result instanceof Promise) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Provider "${provider.id}" returned a Promise. Use load() instead of loadSync() for async providers.`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (result.warnings) {
|
|
201
|
+
allWarnings.push(...result.warnings.map((w) => `[${provider.displayName}] ${w}`));
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (result.items.length > 0) {
|
|
205
|
+
contributingProviders.push(provider.id);
|
|
206
|
+
|
|
207
|
+
for (const item of result.items) {
|
|
208
|
+
const itemWithSource = item as T & { _source: SourceMeta };
|
|
209
|
+
if (itemWithSource._source) {
|
|
210
|
+
itemWithSource._source.providerName = provider.displayName;
|
|
211
|
+
allItems.push(itemWithSource as T & { _source: SourceMeta; _shadowed?: boolean });
|
|
212
|
+
} else {
|
|
213
|
+
allWarnings.push(`[${provider.displayName}] Item missing _source metadata, skipping`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} catch (err) {
|
|
218
|
+
if (err instanceof Error && err.message.includes("returned a Promise")) {
|
|
219
|
+
throw err;
|
|
220
|
+
}
|
|
221
|
+
allWarnings.push(`[${provider.displayName}] Failed to load: ${err}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Deduplicate by key (first wins = highest priority)
|
|
226
|
+
const seen = new Map<string, number>();
|
|
227
|
+
const deduped: Array<T & { _source: SourceMeta }> = [];
|
|
228
|
+
|
|
229
|
+
for (let i = 0; i < allItems.length; i++) {
|
|
230
|
+
const item = allItems[i];
|
|
231
|
+
const key = capability.key(item);
|
|
232
|
+
|
|
233
|
+
if (key === undefined) {
|
|
234
|
+
deduped.push(item);
|
|
235
|
+
} else if (!seen.has(key)) {
|
|
236
|
+
seen.set(key, i);
|
|
237
|
+
deduped.push(item);
|
|
238
|
+
} else {
|
|
239
|
+
item._shadowed = true;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Validate items (only non-shadowed items)
|
|
244
|
+
if (capability.validate && !options.includeInvalid) {
|
|
245
|
+
for (let i = deduped.length - 1; i >= 0; i--) {
|
|
246
|
+
const error = capability.validate(deduped[i]);
|
|
247
|
+
if (error) {
|
|
248
|
+
const source = deduped[i]._source;
|
|
249
|
+
allWarnings.push(
|
|
250
|
+
`[${source?.providerName ?? "unknown"}] Invalid item at ${source?.path ?? "unknown"}: ${error}`,
|
|
251
|
+
);
|
|
252
|
+
deduped.splice(i, 1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
items: deduped,
|
|
259
|
+
all: allItems,
|
|
260
|
+
warnings: allWarnings,
|
|
261
|
+
providers: contributingProviders,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Async loading logic shared by load().
|
|
267
|
+
*/
|
|
268
|
+
async function loadImplAsync<T>(
|
|
269
|
+
capability: Capability<T>,
|
|
270
|
+
providers: Provider<T>[],
|
|
271
|
+
ctx: LoadContext,
|
|
272
|
+
options: LoadOptions,
|
|
273
|
+
): Promise<CapabilityResult<T>> {
|
|
274
|
+
const allItems: Array<T & { _source: SourceMeta; _shadowed?: boolean }> = [];
|
|
275
|
+
const allWarnings: string[] = [];
|
|
276
|
+
const contributingProviders: string[] = [];
|
|
277
|
+
|
|
278
|
+
for (const provider of providers) {
|
|
279
|
+
try {
|
|
280
|
+
const result = await provider.load(ctx);
|
|
281
|
+
|
|
282
|
+
if (result.warnings) {
|
|
283
|
+
allWarnings.push(...result.warnings.map((w) => `[${provider.displayName}] ${w}`));
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (result.items.length > 0) {
|
|
287
|
+
contributingProviders.push(provider.id);
|
|
288
|
+
|
|
289
|
+
for (const item of result.items) {
|
|
290
|
+
const itemWithSource = item as T & { _source: SourceMeta };
|
|
291
|
+
if (itemWithSource._source) {
|
|
292
|
+
itemWithSource._source.providerName = provider.displayName;
|
|
293
|
+
allItems.push(itemWithSource as T & { _source: SourceMeta; _shadowed?: boolean });
|
|
294
|
+
} else {
|
|
295
|
+
allWarnings.push(`[${provider.displayName}] Item missing _source metadata, skipping`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
} catch (err) {
|
|
300
|
+
allWarnings.push(`[${provider.displayName}] Failed to load: ${err}`);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Deduplicate by key (first wins = highest priority)
|
|
305
|
+
const seen = new Map<string, number>();
|
|
306
|
+
const deduped: Array<T & { _source: SourceMeta }> = [];
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < allItems.length; i++) {
|
|
309
|
+
const item = allItems[i];
|
|
310
|
+
const key = capability.key(item);
|
|
311
|
+
|
|
312
|
+
if (key === undefined) {
|
|
313
|
+
deduped.push(item);
|
|
314
|
+
} else if (!seen.has(key)) {
|
|
315
|
+
seen.set(key, i);
|
|
316
|
+
deduped.push(item);
|
|
317
|
+
} else {
|
|
318
|
+
item._shadowed = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Validate items (only non-shadowed items)
|
|
323
|
+
if (capability.validate && !options.includeInvalid) {
|
|
324
|
+
for (let i = deduped.length - 1; i >= 0; i--) {
|
|
325
|
+
const error = capability.validate(deduped[i]);
|
|
326
|
+
if (error) {
|
|
327
|
+
const source = deduped[i]._source;
|
|
328
|
+
allWarnings.push(
|
|
329
|
+
`[${source?.providerName ?? "unknown"}] Invalid item at ${source?.path ?? "unknown"}: ${error}`,
|
|
330
|
+
);
|
|
331
|
+
deduped.splice(i, 1);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
items: deduped,
|
|
338
|
+
all: allItems,
|
|
339
|
+
warnings: allWarnings,
|
|
340
|
+
providers: contributingProviders,
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Filter providers based on options and disabled state.
|
|
346
|
+
*/
|
|
347
|
+
function filterProviders<T>(capability: Capability<T>, options: LoadOptions): Provider<T>[] {
|
|
348
|
+
let providers = (capability.providers as Provider<T>[]).filter((p) => !disabledProviders.has(p.id));
|
|
349
|
+
|
|
350
|
+
if (options.providers) {
|
|
351
|
+
const allowed = new Set(options.providers);
|
|
352
|
+
providers = providers.filter((p) => allowed.has(p.id));
|
|
353
|
+
}
|
|
354
|
+
if (options.excludeProviders) {
|
|
355
|
+
const excluded = new Set(options.excludeProviders);
|
|
356
|
+
providers = providers.filter((p) => !excluded.has(p.id));
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return providers;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Load a capability by ID.
|
|
364
|
+
*/
|
|
365
|
+
export async function load<T>(capabilityId: string, options: LoadOptions = {}): Promise<CapabilityResult<T>> {
|
|
366
|
+
const capability = capabilities.get(capabilityId) as Capability<T> | undefined;
|
|
367
|
+
if (!capability) {
|
|
368
|
+
throw new Error(`Unknown capability: "${capabilityId}"`);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
const cwd = options.cwd ?? process.cwd();
|
|
372
|
+
const home = homedir();
|
|
373
|
+
const ctx: LoadContext = { cwd, home, fs: createFsHelpers(cwd) };
|
|
374
|
+
const providers = filterProviders(capability, options);
|
|
375
|
+
|
|
376
|
+
return loadImplAsync(capability, providers, ctx, options);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Synchronous load (for capabilities where all providers are sync).
|
|
381
|
+
* Throws if any provider returns a Promise.
|
|
382
|
+
*/
|
|
383
|
+
export function loadSync<T>(capabilityId: string, options: LoadOptions = {}): CapabilityResult<T> {
|
|
384
|
+
const capability = capabilities.get(capabilityId) as Capability<T> | undefined;
|
|
385
|
+
if (!capability) {
|
|
386
|
+
throw new Error(`Unknown capability: "${capabilityId}"`);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const cwd = options.cwd ?? process.cwd();
|
|
390
|
+
const home = homedir();
|
|
391
|
+
const ctx: LoadContext = { cwd, home, fs: createFsHelpers(cwd) };
|
|
392
|
+
const providers = filterProviders(capability, options);
|
|
393
|
+
|
|
394
|
+
return loadImpl(capability, providers, ctx, options);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// =============================================================================
|
|
398
|
+
// Provider Enable/Disable API
|
|
399
|
+
// =============================================================================
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Initialize capability system with settings manager for persistence.
|
|
403
|
+
* Call this once on startup to enable persistent provider state.
|
|
404
|
+
*/
|
|
405
|
+
export function initializeWithSettings(manager: {
|
|
406
|
+
getDisabledProviders(): string[];
|
|
407
|
+
setDisabledProviders(ids: string[]): void;
|
|
408
|
+
}): void {
|
|
409
|
+
settingsManager = manager;
|
|
410
|
+
// Load disabled providers from settings
|
|
411
|
+
const disabled = manager.getDisabledProviders();
|
|
412
|
+
disabledProviders.clear();
|
|
413
|
+
for (const id of disabled) {
|
|
414
|
+
disabledProviders.add(id);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Persist current disabled providers to settings.
|
|
420
|
+
*/
|
|
421
|
+
function persistDisabledProviders(): void {
|
|
422
|
+
if (settingsManager) {
|
|
423
|
+
settingsManager.setDisabledProviders(Array.from(disabledProviders));
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Disable a provider globally (across all capabilities).
|
|
429
|
+
*/
|
|
430
|
+
export function disableProvider(providerId: string): void {
|
|
431
|
+
disabledProviders.add(providerId);
|
|
432
|
+
persistDisabledProviders();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Enable a previously disabled provider.
|
|
437
|
+
*/
|
|
438
|
+
export function enableProvider(providerId: string): void {
|
|
439
|
+
disabledProviders.delete(providerId);
|
|
440
|
+
persistDisabledProviders();
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Check if a provider is enabled.
|
|
445
|
+
*/
|
|
446
|
+
export function isProviderEnabled(providerId: string): boolean {
|
|
447
|
+
return !disabledProviders.has(providerId);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Get list of all disabled provider IDs.
|
|
452
|
+
*/
|
|
453
|
+
export function getDisabledProviders(): string[] {
|
|
454
|
+
return Array.from(disabledProviders);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Set disabled providers from a list (replaces current set).
|
|
459
|
+
*/
|
|
460
|
+
export function setDisabledProviders(providerIds: string[]): void {
|
|
461
|
+
disabledProviders.clear();
|
|
462
|
+
for (const id of providerIds) {
|
|
463
|
+
disabledProviders.add(id);
|
|
464
|
+
}
|
|
465
|
+
persistDisabledProviders();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// =============================================================================
|
|
469
|
+
// Introspection API
|
|
470
|
+
// =============================================================================
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Get a capability definition (for introspection).
|
|
474
|
+
*/
|
|
475
|
+
export function getCapability<T>(id: string): Capability<T> | undefined {
|
|
476
|
+
return capabilities.get(id) as Capability<T> | undefined;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* List all registered capability IDs.
|
|
481
|
+
*/
|
|
482
|
+
export function listCapabilities(): string[] {
|
|
483
|
+
return Array.from(capabilities.keys());
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Get capability info for UI display.
|
|
488
|
+
*/
|
|
489
|
+
export function getCapabilityInfo(capabilityId: string): CapabilityInfo | undefined {
|
|
490
|
+
const capability = capabilities.get(capabilityId);
|
|
491
|
+
if (!capability) return undefined;
|
|
492
|
+
|
|
493
|
+
return {
|
|
494
|
+
id: capability.id,
|
|
495
|
+
displayName: capability.displayName,
|
|
496
|
+
description: capability.description,
|
|
497
|
+
providers: capability.providers.map((p) => ({
|
|
498
|
+
id: p.id,
|
|
499
|
+
displayName: p.displayName,
|
|
500
|
+
description: p.description,
|
|
501
|
+
priority: p.priority,
|
|
502
|
+
enabled: !disabledProviders.has(p.id),
|
|
503
|
+
})),
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get all capabilities info for UI display.
|
|
509
|
+
*/
|
|
510
|
+
export function getAllCapabilitiesInfo(): CapabilityInfo[] {
|
|
511
|
+
return listCapabilities().map((id) => getCapabilityInfo(id)!);
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Get provider info for UI display.
|
|
516
|
+
*/
|
|
517
|
+
export function getProviderInfo(providerId: string): ProviderInfo | undefined {
|
|
518
|
+
const meta = providerMeta.get(providerId);
|
|
519
|
+
const caps = providerCapabilities.get(providerId);
|
|
520
|
+
if (!meta || !caps) return undefined;
|
|
521
|
+
|
|
522
|
+
// Find priority from first capability's provider list
|
|
523
|
+
let priority = 0;
|
|
524
|
+
for (const capId of caps) {
|
|
525
|
+
const cap = capabilities.get(capId);
|
|
526
|
+
const provider = cap?.providers.find((p) => p.id === providerId);
|
|
527
|
+
if (provider) {
|
|
528
|
+
priority = provider.priority;
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
id: providerId,
|
|
535
|
+
displayName: meta.displayName,
|
|
536
|
+
description: meta.description,
|
|
537
|
+
priority,
|
|
538
|
+
capabilities: Array.from(caps),
|
|
539
|
+
enabled: !disabledProviders.has(providerId),
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Get all providers info for UI display (deduplicated across capabilities).
|
|
545
|
+
*/
|
|
546
|
+
export function getAllProvidersInfo(): ProviderInfo[] {
|
|
547
|
+
const providers: ProviderInfo[] = [];
|
|
548
|
+
|
|
549
|
+
for (const providerId of providerMeta.keys()) {
|
|
550
|
+
const info = getProviderInfo(providerId);
|
|
551
|
+
if (info) {
|
|
552
|
+
providers.push(info);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Sort by priority (highest first)
|
|
557
|
+
providers.sort((a, b) => b.priority - a.priority);
|
|
558
|
+
|
|
559
|
+
return providers;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// =============================================================================
|
|
563
|
+
// Cache Management
|
|
564
|
+
// =============================================================================
|
|
565
|
+
|
|
566
|
+
/**
|
|
567
|
+
* Reset all caches. Call after chdir or filesystem changes.
|
|
568
|
+
*/
|
|
569
|
+
export function reset(): void {
|
|
570
|
+
clearCache();
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/**
|
|
574
|
+
* Invalidate cache for a specific path.
|
|
575
|
+
* @param path - Absolute or relative path to invalidate
|
|
576
|
+
* @param cwd - Working directory for resolving relative paths (defaults to process.cwd())
|
|
577
|
+
*/
|
|
578
|
+
export function invalidate(path: string, cwd?: string): void {
|
|
579
|
+
const abs = resolve(cwd ?? process.cwd(), path);
|
|
580
|
+
statCache.delete(abs);
|
|
581
|
+
contentCache.delete(abs);
|
|
582
|
+
dirCache.delete(abs);
|
|
583
|
+
// Also invalidate parent for directory listings
|
|
584
|
+
const parent = dirname(abs);
|
|
585
|
+
if (parent !== abs) {
|
|
586
|
+
statCache.delete(parent);
|
|
587
|
+
dirCache.delete(parent);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Get cache stats for diagnostics.
|
|
593
|
+
*/
|
|
594
|
+
export function cacheStats(): { stat: number; content: number; dir: number } {
|
|
595
|
+
return {
|
|
596
|
+
stat: statCache.size,
|
|
597
|
+
content: contentCache.size,
|
|
598
|
+
dir: dirCache.size,
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// =============================================================================
|
|
603
|
+
// Re-exports
|
|
604
|
+
// =============================================================================
|
|
605
|
+
|
|
606
|
+
export type {
|
|
607
|
+
Capability,
|
|
608
|
+
CapabilityInfo,
|
|
609
|
+
CapabilityResult,
|
|
610
|
+
LoadContext,
|
|
611
|
+
LoadOptions,
|
|
612
|
+
LoadResult,
|
|
613
|
+
Provider,
|
|
614
|
+
ProviderInfo,
|
|
615
|
+
SourceMeta,
|
|
616
|
+
} from "./types";
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Instructions Capability
|
|
3
|
+
*
|
|
4
|
+
* GitHub Copilot-style instructions with optional file pattern matching.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineCapability } from "./index";
|
|
8
|
+
import type { SourceMeta } from "./types";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* An instruction with optional file pattern matching.
|
|
12
|
+
*/
|
|
13
|
+
export interface Instruction {
|
|
14
|
+
/** Instruction name (derived from filename) */
|
|
15
|
+
name: string;
|
|
16
|
+
/** Absolute path to instruction file */
|
|
17
|
+
path: string;
|
|
18
|
+
/** Instruction content (markdown) */
|
|
19
|
+
content: string;
|
|
20
|
+
/** Glob pattern for files this applies to */
|
|
21
|
+
applyTo?: string;
|
|
22
|
+
/** Source metadata */
|
|
23
|
+
_source: SourceMeta;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const instructionCapability = defineCapability<Instruction>({
|
|
27
|
+
id: "instructions",
|
|
28
|
+
displayName: "Instructions",
|
|
29
|
+
description: "File-specific instructions with glob pattern matching (GitHub Copilot format)",
|
|
30
|
+
key: (inst) => inst.name,
|
|
31
|
+
validate: (inst) => {
|
|
32
|
+
if (!inst.name) return "Missing name";
|
|
33
|
+
if (!inst.path) return "Missing path";
|
|
34
|
+
if (inst.content === undefined) return "Missing content";
|
|
35
|
+
return undefined;
|
|
36
|
+
},
|
|
37
|
+
});
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) Servers Capability
|
|
3
|
+
*
|
|
4
|
+
* Canonical shape for MCP server configurations, regardless of source format.
|
|
5
|
+
* All providers translate their native format to this shape.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { defineCapability } from "./index";
|
|
9
|
+
import type { SourceMeta } from "./types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Canonical MCP server configuration.
|
|
13
|
+
*/
|
|
14
|
+
export interface MCPServer {
|
|
15
|
+
/** Server name (unique key) */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Command to run (for stdio transport) */
|
|
18
|
+
command?: string;
|
|
19
|
+
/** Command arguments */
|
|
20
|
+
args?: string[];
|
|
21
|
+
/** Environment variables */
|
|
22
|
+
env?: Record<string, string>;
|
|
23
|
+
/** URL (for HTTP/SSE transport) */
|
|
24
|
+
url?: string;
|
|
25
|
+
/** HTTP headers (for HTTP transport) */
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
/** Transport type */
|
|
28
|
+
transport?: "stdio" | "sse" | "http";
|
|
29
|
+
/** Source metadata (added by loader) */
|
|
30
|
+
_source: SourceMeta;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const mcpCapability = defineCapability<MCPServer>({
|
|
34
|
+
id: "mcps",
|
|
35
|
+
displayName: "MCP Servers",
|
|
36
|
+
description: "Model Context Protocol server configurations for external tool integrations",
|
|
37
|
+
key: (server) => server.name,
|
|
38
|
+
validate: (server) => {
|
|
39
|
+
if (!server.name) return "Missing server name";
|
|
40
|
+
if (!server.command && !server.url) return "Must have command or url";
|
|
41
|
+
|
|
42
|
+
// Validate transport-endpoint pairing
|
|
43
|
+
if (server.transport === "stdio" && !server.command) {
|
|
44
|
+
return "stdio transport requires command field";
|
|
45
|
+
}
|
|
46
|
+
if ((server.transport === "http" || server.transport === "sse") && !server.url) {
|
|
47
|
+
return "http/sse transport requires url field";
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return undefined;
|
|
51
|
+
},
|
|
52
|
+
});
|