@sayknow-cli/coding-agent 0.3.5 → 0.3.7
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/README.md +76 -0
- package/dist/types/capability/mcp.d.ts +2 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/config/keybindings.d.ts +12 -6
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +284 -4
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/defaults/skc-defaults.d.ts +7 -0
- package/dist/types/discovery/cursor.d.ts +4 -1
- package/dist/types/discovery/index.d.ts +0 -1
- package/dist/types/discovery/windsurf.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/i18n/messages/en.d.ts +1 -0
- package/dist/types/modes/components/custom-editor.d.ts +6 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-selector.d.ts +5 -0
- package/dist/types/modes/components/model-selector.d.ts +2 -0
- package/dist/types/modes/components/settings-selector.d.ts +5 -1
- package/dist/types/modes/components/skill-message.d.ts +1 -0
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +15 -3
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/welcome.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/input-controller.d.ts +6 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +10 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +3 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +41 -7
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +5 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/html-format.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +5 -1
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon-control.d.ts +8 -0
- package/dist/types/notifications/telegram-daemon.d.ts +4 -10
- package/dist/types/notifications/telegram-reference.d.ts +4 -0
- package/dist/types/notifications/threaded-render.d.ts +1 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +9 -0
- package/dist/types/runtime-mcp/config.d.ts +2 -0
- package/dist/types/runtime-mcp/loader.d.ts +2 -0
- package/dist/types/runtime-mcp/manager.d.ts +2 -0
- package/dist/types/runtime-mcp/types.d.ts +6 -0
- package/dist/types/sdk.d.ts +1 -1
- package/dist/types/skc-runtime/launch-tmux.d.ts +2 -2
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +3 -2
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +1 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +5 -0
- package/dist/types/skc-runtime/state-writer.d.ts +1 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +7 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +0 -4
- package/dist/types/slash-commands/types.d.ts +2 -0
- package/dist/types/system-prompt.d.ts +7 -0
- package/dist/types/tools/skill.d.ts +1 -1
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/web/search/index.d.ts +4 -2
- package/dist/types/web/search/provider.d.ts +14 -0
- package/dist/types/web/search/providers/utils.d.ts +42 -13
- package/dist/types/web/search/render.d.ts +1 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +19 -0
- package/package.json +8 -8
- package/scripts/build-binary.ts +3 -0
- package/scripts/verify-insane-vendor.ts +0 -5
- package/src/capability/mcp.ts +2 -0
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +4 -2
- package/src/cli/telegram-cli.ts +156 -0
- package/src/cli/update-cli.ts +179 -13
- package/src/cli/web-search-cli.ts +5 -7
- package/src/cli.ts +20 -1
- package/src/commands/launch.ts +47 -2
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/commands/ralplan.ts +1 -0
- package/src/commands/telegram.ts +47 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/mcp-schema.json +4 -0
- package/src/config/model-registry.ts +55 -1
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +256 -4
- package/src/config/settings.ts +11 -0
- package/src/config/telegram-autostart.ts +107 -0
- package/src/config/telegram-env-bridge.ts +92 -0
- package/src/coordinator-mcp/server.ts +276 -34
- package/src/defaults/skc/skills/ralplan/SKILL.md +29 -20
- package/src/defaults/skc/skills/ultragoal/SKILL.md +37 -112
- package/src/defaults/skc-defaults.ts +16 -0
- package/src/discovery/builtin.ts +12 -0
- package/src/discovery/cursor.ts +5 -84
- package/src/discovery/gemini.ts +3 -89
- package/src/discovery/index.ts +4 -1
- package/src/discovery/mcp-json.ts +11 -0
- package/src/discovery/opencode.ts +5 -122
- package/src/discovery/windsurf.ts +4 -82
- package/src/edit/read-file.ts +9 -1
- package/src/extensibility/extensions/runner.ts +2 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/skills.ts +30 -2
- package/src/hooks/skill-state.ts +33 -73
- package/src/i18n/messages/de.ts +1 -0
- package/src/i18n/messages/en.ts +1 -0
- package/src/i18n/messages/es.ts +1 -0
- package/src/i18n/messages/fr.ts +1 -0
- package/src/i18n/messages/ja.settings.ts +28 -0
- package/src/i18n/messages/ja.ts +1 -0
- package/src/i18n/messages/ko.settings.ts +28 -0
- package/src/i18n/messages/ko.ts +1 -0
- package/src/i18n/messages/zh.settings.ts +28 -0
- package/src/i18n/messages/zh.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +12 -7
- package/src/main.ts +5 -2
- package/src/migrate/executor.ts +23 -6
- package/src/modes/acp/acp-agent.ts +7 -6
- package/src/modes/components/agent-dashboard.ts +3 -35
- package/src/modes/components/custom-editor.ts +26 -0
- package/src/modes/components/custom-model-preset-wizard.ts +30 -189
- package/src/modes/components/extensions/extension-dashboard.ts +2 -48
- package/src/modes/components/hook-selector.ts +21 -3
- package/src/modes/components/model-selector.ts +246 -11
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/session-selector.ts +19 -9
- package/src/modes/components/settings-selector.ts +551 -11
- package/src/modes/components/skill-message.ts +18 -10
- package/src/modes/components/status-line/presets.ts +11 -0
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +281 -117
- package/src/modes/components/tree-selector.ts +28 -8
- package/src/modes/components/two-column-body.ts +36 -0
- package/src/modes/components/welcome.ts +165 -30
- package/src/modes/controllers/command-controller.ts +26 -2
- package/src/modes/controllers/event-controller.ts +102 -4
- package/src/modes/controllers/extension-ui-controller.ts +21 -2
- package/src/modes/controllers/input-controller.ts +214 -47
- package/src/modes/controllers/selector-controller.ts +35 -33
- package/src/modes/interactive-mode.ts +36 -30
- package/src/modes/prompt-action-autocomplete.ts +48 -8
- package/src/modes/rpc/rpc-types.ts +1 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +72 -15
- package/src/modes/shared/agent-wire/workflow-gate-schema.ts +20 -0
- package/src/modes/slash-command-visibility.ts +9 -0
- package/src/modes/theme/theme.ts +5 -1
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +8 -1
- package/src/modes/utils/hotkeys-markdown.ts +5 -2
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/modes/utils/ui-helpers.ts +76 -3
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/html-format.ts +75 -1
- package/src/notifications/index.ts +32 -2
- package/src/notifications/lifecycle-control-runtime.ts +12 -3
- package/src/notifications/recent-activity.ts +32 -8
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon-control.ts +9 -0
- package/src/notifications/telegram-daemon.ts +88 -46
- package/src/notifications/telegram-reference.ts +36 -14
- package/src/notifications/threaded-render.ts +14 -6
- package/src/prompts/agents/architect.md +7 -4
- package/src/prompts/agents/critic.md +15 -6
- package/src/prompts/agents/planner.md +8 -3
- package/src/prompts/system/system-prompt.md +20 -2
- package/src/prompts/tools/bash.md +1 -1
- package/src/runtime-mcp/config-writer.ts +28 -0
- package/src/runtime-mcp/config.ts +7 -0
- package/src/runtime-mcp/loader.ts +3 -0
- package/src/runtime-mcp/manager.ts +3 -0
- package/src/runtime-mcp/types.ts +6 -0
- package/src/sdk.ts +10 -11
- package/src/session/agent-session.ts +103 -13
- package/src/session/session-manager.ts +58 -18
- package/src/skc-runtime/launch-tmux.ts +34 -8
- package/src/skc-runtime/launch-worktree.ts +16 -2
- package/src/skc-runtime/ralplan-runtime.ts +22 -6
- package/src/skc-runtime/restricted-role-agent-bash.ts +1 -0
- package/src/skc-runtime/session-state-sidecar.ts +167 -24
- package/src/skc-runtime/state-runtime.ts +8 -5
- package/src/skc-runtime/state-writer.ts +5 -5
- package/src/skc-runtime/team-runtime.ts +21 -6
- package/src/skc-runtime/tmux-common.ts +16 -0
- package/src/skc-runtime/tmux-sessions.ts +13 -9
- package/src/skc-runtime/ultragoal-guard.ts +150 -24
- package/src/skc-runtime/ultragoal-runtime.ts +16 -124
- package/src/skc-runtime/workflow-manifest.generated.json +0 -8
- package/src/skc-runtime/workflow-manifest.ts +0 -1
- package/src/slash-commands/builtin-registry.ts +136 -23
- package/src/slash-commands/types.ts +2 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/executor.ts +5 -0
- package/src/tools/ast-grep.ts +10 -7
- package/src/tools/bash.ts +39 -14
- package/src/tools/index.ts +8 -1
- package/src/tools/skill.ts +1 -1
- package/src/utils/cmux-workspace.ts +233 -0
- package/src/utils/title-generator.ts +2 -0
- package/src/web/kagi.ts +1 -1
- package/src/web/parallel.ts +2 -2
- package/src/web/search/index.ts +137 -52
- package/src/web/search/provider.ts +80 -0
- package/src/web/search/providers/anthropic.ts +1 -1
- package/src/web/search/providers/brave.ts +1 -1
- package/src/web/search/providers/codex.ts +1 -1
- package/src/web/search/providers/duckduckgo.ts +1 -1
- package/src/web/search/providers/exa.ts +1 -1
- package/src/web/search/providers/gemini.ts +7 -3
- package/src/web/search/providers/jina.ts +1 -1
- package/src/web/search/providers/kimi.ts +6 -1
- package/src/web/search/providers/openai-compatible.ts +1 -1
- package/src/web/search/providers/parallel.ts +1 -1
- package/src/web/search/providers/perplexity.ts +2 -2
- package/src/web/search/providers/searxng.ts +1 -1
- package/src/web/search/providers/synthetic.ts +1 -1
- package/src/web/search/providers/tavily.ts +1 -1
- package/src/web/search/providers/utils.ts +67 -17
- package/src/web/search/providers/xai.ts +1 -1
- package/src/web/search/providers/zai.ts +1 -1
- package/src/web/search/render.ts +3 -0
- package/src/workflow/workflow-intent-diff.ts +199 -0
- package/vendor/insane-search/MANIFEST.json +1 -3
- package/vendor/insane-search/engine/tests/test_u1.py +0 -28
- package/vendor/insane-search/engine/transport.py +2 -3
- package/vendor/insane-search/engine/validators.py +0 -14
- package/src/defaults/skc/rules/ponytail.md +0 -68
package/src/discovery/gemini.ts
CHANGED
|
@@ -9,11 +9,13 @@
|
|
|
9
9
|
* - Project: .gemini/ (cwd only)
|
|
10
10
|
*
|
|
11
11
|
* Capabilities:
|
|
12
|
-
* - mcps: From settings.json with mcpServers key
|
|
13
12
|
* - context-files: GEMINI.md files
|
|
14
13
|
* - system-prompt: system.md files for custom system prompt
|
|
15
14
|
* - extensions: From extensions/STAR/gemini-extension.json manifests (STAR = wildcard)
|
|
16
15
|
* - settings: From settings.json
|
|
16
|
+
*
|
|
17
|
+
* MCP servers are intentionally NOT inherited live from Gemini config: SKC owns
|
|
18
|
+
* MCP runtime execution. Copy definitions into SKC's own mcp.json instead.
|
|
17
19
|
*/
|
|
18
20
|
import * as path from "node:path";
|
|
19
21
|
import { tryParseJson } from "@sayknow-cli/utils";
|
|
@@ -22,7 +24,6 @@ import { type ContextFile, contextFileCapability } from "../capability/context-f
|
|
|
22
24
|
import { type Extension, type ExtensionManifest, extensionCapability } from "../capability/extension";
|
|
23
25
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
24
26
|
import { readDirEntries, readFile } from "../capability/fs";
|
|
25
|
-
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
26
27
|
import { type Settings, settingsCapability } from "../capability/settings";
|
|
27
28
|
import { type SystemPrompt, systemPromptCapability } from "../capability/system-prompt";
|
|
28
29
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
@@ -31,7 +32,6 @@ import {
|
|
|
31
32
|
calculateDepth,
|
|
32
33
|
createSourceMeta,
|
|
33
34
|
discoverExtensionModulePaths,
|
|
34
|
-
expandEnvVarsDeep,
|
|
35
35
|
getProjectPath,
|
|
36
36
|
getUserPath,
|
|
37
37
|
} from "./helpers";
|
|
@@ -40,84 +40,6 @@ const PROVIDER_ID = "gemini";
|
|
|
40
40
|
const DISPLAY_NAME = "Gemini CLI";
|
|
41
41
|
const PRIORITY = 60;
|
|
42
42
|
|
|
43
|
-
// =============================================================================
|
|
44
|
-
// MCP Servers
|
|
45
|
-
// =============================================================================
|
|
46
|
-
|
|
47
|
-
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
48
|
-
const items: MCPServer[] = [];
|
|
49
|
-
const warnings: string[] = [];
|
|
50
|
-
|
|
51
|
-
// User-level: ~/.gemini/settings.json → mcpServers
|
|
52
|
-
const userPath = getUserPath(ctx, "gemini", "settings.json");
|
|
53
|
-
if (userPath) {
|
|
54
|
-
const result = await loadMCPFromSettings(ctx, userPath, "user");
|
|
55
|
-
items.push(...result.items);
|
|
56
|
-
if (result.warnings) warnings.push(...result.warnings);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// Project-level: .gemini/settings.json → mcpServers
|
|
60
|
-
const projectPath = getProjectPath(ctx, "gemini", "settings.json");
|
|
61
|
-
if (projectPath) {
|
|
62
|
-
const result = await loadMCPFromSettings(ctx, projectPath, "project");
|
|
63
|
-
items.push(...result.items);
|
|
64
|
-
if (result.warnings) warnings.push(...result.warnings);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return { items, warnings };
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function loadMCPFromSettings(
|
|
71
|
-
_ctx: LoadContext,
|
|
72
|
-
path: string,
|
|
73
|
-
level: "user" | "project",
|
|
74
|
-
): Promise<LoadResult<MCPServer>> {
|
|
75
|
-
const items: MCPServer[] = [];
|
|
76
|
-
const warnings: string[] = [];
|
|
77
|
-
|
|
78
|
-
const content = await readFile(path);
|
|
79
|
-
if (!content) {
|
|
80
|
-
return { items, warnings };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const parsed = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
|
|
84
|
-
if (!parsed) {
|
|
85
|
-
warnings.push(`Invalid JSON in ${path}`);
|
|
86
|
-
return { items, warnings };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
if (!parsed.mcpServers || typeof parsed.mcpServers !== "object") {
|
|
90
|
-
return { items, warnings };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const servers = expandEnvVarsDeep(parsed.mcpServers);
|
|
94
|
-
|
|
95
|
-
for (const [name, config] of Object.entries(servers)) {
|
|
96
|
-
if (!config || typeof config !== "object") {
|
|
97
|
-
warnings.push(`Invalid config for server "${name}" in ${path}`);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const raw = config as Record<string, unknown>;
|
|
102
|
-
|
|
103
|
-
items.push({
|
|
104
|
-
name,
|
|
105
|
-
command: typeof raw.command === "string" ? raw.command : undefined,
|
|
106
|
-
args: Array.isArray(raw.args) ? (raw.args as string[]) : undefined,
|
|
107
|
-
env: raw.env && typeof raw.env === "object" ? (raw.env as Record<string, string>) : undefined,
|
|
108
|
-
url: typeof raw.url === "string" ? raw.url : undefined,
|
|
109
|
-
headers: raw.headers && typeof raw.headers === "object" ? (raw.headers as Record<string, string>) : undefined,
|
|
110
|
-
transport: ["stdio", "sse", "http"].includes(raw.type as string)
|
|
111
|
-
? (raw.type as "stdio" | "sse" | "http")
|
|
112
|
-
: undefined,
|
|
113
|
-
timeout: typeof raw.timeout === "number" ? raw.timeout : undefined,
|
|
114
|
-
_source: createSourceMeta(PROVIDER_ID, path, level),
|
|
115
|
-
} as MCPServer);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
return { items, warnings };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
43
|
// =============================================================================
|
|
122
44
|
// Context Files
|
|
123
45
|
// =============================================================================
|
|
@@ -296,14 +218,6 @@ async function loadSettings(ctx: LoadContext): Promise<LoadResult<Settings>> {
|
|
|
296
218
|
// Provider Registration
|
|
297
219
|
// =============================================================================
|
|
298
220
|
|
|
299
|
-
registerProvider(mcpCapability.id, {
|
|
300
|
-
id: PROVIDER_ID,
|
|
301
|
-
displayName: DISPLAY_NAME,
|
|
302
|
-
description: "Load MCP servers from ~/.gemini/settings.json and .gemini/settings.json",
|
|
303
|
-
priority: PRIORITY,
|
|
304
|
-
load: loadMCPServers,
|
|
305
|
-
});
|
|
306
|
-
|
|
307
221
|
registerProvider(contextFileCapability.id, {
|
|
308
222
|
id: PROVIDER_ID,
|
|
309
223
|
displayName: DISPLAY_NAME,
|
package/src/discovery/index.ts
CHANGED
|
@@ -30,7 +30,10 @@ import "./opencode";
|
|
|
30
30
|
import "./github";
|
|
31
31
|
import "./mcp-json";
|
|
32
32
|
import "./ssh";
|
|
33
|
-
|
|
33
|
+
// NOTE: There is deliberately no VS Code provider and no MCP registration in
|
|
34
|
+
// the cursor/gemini/opencode/windsurf providers: SKC does not inherit MCP
|
|
35
|
+
// servers live from other hosts. MCP comes from SKC's own config (builtin,
|
|
36
|
+
// mcp-json), validated plugin bundles, or an explicit `skc mcp import <host>`.
|
|
34
37
|
import "./windsurf";
|
|
35
38
|
|
|
36
39
|
// Re-export the main API from capability registry
|
|
@@ -25,6 +25,7 @@ interface MCPConfigFile {
|
|
|
25
25
|
string,
|
|
26
26
|
{
|
|
27
27
|
enabled?: boolean;
|
|
28
|
+
autoload?: boolean;
|
|
28
29
|
timeout?: number;
|
|
29
30
|
command?: string;
|
|
30
31
|
args?: string[];
|
|
@@ -82,9 +83,19 @@ function transformMCPConfig(config: MCPConfigFile, source: SourceMeta): MCPServe
|
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
let autoload: boolean | undefined;
|
|
87
|
+
if (serverConfig.autoload !== undefined) {
|
|
88
|
+
if (typeof serverConfig.autoload === "boolean") {
|
|
89
|
+
autoload = serverConfig.autoload;
|
|
90
|
+
} else {
|
|
91
|
+
logger.warn("MCP server has invalid 'autoload' value, ignoring", { name, value: serverConfig.autoload });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
85
95
|
const server: MCPServer = {
|
|
86
96
|
name,
|
|
87
97
|
enabled,
|
|
98
|
+
autoload,
|
|
88
99
|
timeout,
|
|
89
100
|
command: serverConfig.command,
|
|
90
101
|
args: serverConfig.args,
|
|
@@ -7,21 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Capabilities:
|
|
9
9
|
* - context-files: AGENTS.md (user-level only at ~/.config/opencode/AGENTS.md)
|
|
10
|
-
* - mcps: From opencode.json "mcp" key
|
|
11
10
|
* - settings: From opencode.json
|
|
12
11
|
* - skills: From skills/ subdirectories
|
|
13
12
|
* - slash-commands: From commands/ subdirectories
|
|
14
13
|
* - extension-modules: From plugins/ subdirectories
|
|
15
14
|
*
|
|
15
|
+
* MCP servers are intentionally NOT inherited live from OpenCode config: SKC
|
|
16
|
+
* owns MCP runtime execution. Use `skc mcp import opencode` to copy definitions
|
|
17
|
+
* into SKC's own mcp.json instead.
|
|
18
|
+
*
|
|
16
19
|
* Priority: 55 (tool-specific provider)
|
|
17
20
|
*/
|
|
18
21
|
import * as path from "node:path";
|
|
19
|
-
import {
|
|
22
|
+
import { parseFrontmatter, tryParseJson } from "@sayknow-cli/utils";
|
|
20
23
|
import { registerProvider } from "../capability";
|
|
21
24
|
import { type ContextFile, contextFileCapability } from "../capability/context-file";
|
|
22
25
|
import { type ExtensionModule, extensionModuleCapability } from "../capability/extension-module";
|
|
23
26
|
import { readFile } from "../capability/fs";
|
|
24
|
-
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
25
27
|
import { type Settings, settingsCapability } from "../capability/settings";
|
|
26
28
|
import { type Skill, skillCapability } from "../capability/skill";
|
|
27
29
|
import { type SlashCommand, slashCommandCapability } from "../capability/slash-command";
|
|
@@ -32,7 +34,6 @@ import {
|
|
|
32
34
|
buildExtensionModuleItems,
|
|
33
35
|
createSourceMeta,
|
|
34
36
|
discoverExtensionModulePaths,
|
|
35
|
-
expandEnvVarsDeep,
|
|
36
37
|
getProjectPath,
|
|
37
38
|
getUserPath,
|
|
38
39
|
loadFilesFromDir,
|
|
@@ -43,22 +44,6 @@ const PROVIDER_ID = "opencode";
|
|
|
43
44
|
const DISPLAY_NAME = "OpenCode";
|
|
44
45
|
const PRIORITY = 55;
|
|
45
46
|
|
|
46
|
-
// =============================================================================
|
|
47
|
-
// JSON Config Loading
|
|
48
|
-
// =============================================================================
|
|
49
|
-
|
|
50
|
-
async function loadJsonConfig(configPath: string): Promise<Record<string, unknown> | null> {
|
|
51
|
-
const content = await readFile(configPath);
|
|
52
|
-
if (!content) return null;
|
|
53
|
-
|
|
54
|
-
const parsed = tryParseJson<Record<string, unknown>>(content);
|
|
55
|
-
if (!parsed) {
|
|
56
|
-
logger.warn("Failed to parse OpenCode JSON config", { path: configPath });
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
return parsed;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
47
|
// =============================================================================
|
|
63
48
|
// Context Files (AGENTS.md)
|
|
64
49
|
// =============================================================================
|
|
@@ -84,100 +69,6 @@ async function loadContextFiles(ctx: LoadContext): Promise<LoadResult<ContextFil
|
|
|
84
69
|
return { items, warnings };
|
|
85
70
|
}
|
|
86
71
|
|
|
87
|
-
// =============================================================================
|
|
88
|
-
// MCP Servers (opencode.json → mcp)
|
|
89
|
-
// =============================================================================
|
|
90
|
-
|
|
91
|
-
/** OpenCode MCP server config (from opencode.json "mcp" key) */
|
|
92
|
-
interface OpenCodeMCPConfig {
|
|
93
|
-
type?: "local" | "remote";
|
|
94
|
-
command?: string;
|
|
95
|
-
args?: string[];
|
|
96
|
-
env?: Record<string, string>;
|
|
97
|
-
url?: string;
|
|
98
|
-
headers?: Record<string, string>;
|
|
99
|
-
enabled?: boolean;
|
|
100
|
-
timeout?: number;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
104
|
-
const items: MCPServer[] = [];
|
|
105
|
-
const warnings: string[] = [];
|
|
106
|
-
|
|
107
|
-
// User-level: ~/.config/opencode/opencode.json
|
|
108
|
-
const userConfigPath = getUserPath(ctx, "opencode", "opencode.json");
|
|
109
|
-
if (userConfigPath) {
|
|
110
|
-
const config = await loadJsonConfig(userConfigPath);
|
|
111
|
-
if (config) {
|
|
112
|
-
const result = extractMCPServers(config, userConfigPath, "user");
|
|
113
|
-
items.push(...result.items);
|
|
114
|
-
if (result.warnings) warnings.push(...result.warnings);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Project-level: opencode.json in project root
|
|
119
|
-
const projectConfigPath = path.join(ctx.cwd, "opencode.json");
|
|
120
|
-
const projectConfig = await loadJsonConfig(projectConfigPath);
|
|
121
|
-
if (projectConfig) {
|
|
122
|
-
const result = extractMCPServers(projectConfig, projectConfigPath, "project");
|
|
123
|
-
items.push(...result.items);
|
|
124
|
-
if (result.warnings) warnings.push(...result.warnings);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return { items, warnings };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function extractMCPServers(
|
|
131
|
-
config: Record<string, unknown>,
|
|
132
|
-
configPath: string,
|
|
133
|
-
level: "user" | "project",
|
|
134
|
-
): LoadResult<MCPServer> {
|
|
135
|
-
const items: MCPServer[] = [];
|
|
136
|
-
const warnings: string[] = [];
|
|
137
|
-
|
|
138
|
-
if (!config.mcp || typeof config.mcp !== "object") {
|
|
139
|
-
return { items, warnings };
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const servers = expandEnvVarsDeep(config.mcp as Record<string, unknown>);
|
|
143
|
-
|
|
144
|
-
for (const [name, raw] of Object.entries(servers)) {
|
|
145
|
-
if (!raw || typeof raw !== "object") {
|
|
146
|
-
warnings.push(`Invalid MCP config for "${name}" in ${configPath}`);
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const serverConfig = raw as OpenCodeMCPConfig;
|
|
151
|
-
|
|
152
|
-
// Determine transport from OpenCode's "type" field
|
|
153
|
-
let transport: "stdio" | "sse" | "http" | undefined;
|
|
154
|
-
if (serverConfig.type === "local") {
|
|
155
|
-
transport = "stdio";
|
|
156
|
-
} else if (serverConfig.type === "remote") {
|
|
157
|
-
transport = "http";
|
|
158
|
-
} else if (serverConfig.url) {
|
|
159
|
-
transport = "http";
|
|
160
|
-
} else if (serverConfig.command) {
|
|
161
|
-
transport = "stdio";
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
items.push({
|
|
165
|
-
name,
|
|
166
|
-
command: serverConfig.command,
|
|
167
|
-
args: Array.isArray(serverConfig.args) ? (serverConfig.args as string[]) : undefined,
|
|
168
|
-
env: serverConfig.env && typeof serverConfig.env === "object" ? serverConfig.env : undefined,
|
|
169
|
-
url: typeof serverConfig.url === "string" ? serverConfig.url : undefined,
|
|
170
|
-
headers: serverConfig.headers && typeof serverConfig.headers === "object" ? serverConfig.headers : undefined,
|
|
171
|
-
enabled: serverConfig.enabled,
|
|
172
|
-
timeout: typeof serverConfig.timeout === "number" ? serverConfig.timeout : undefined,
|
|
173
|
-
transport,
|
|
174
|
-
_source: createSourceMeta(PROVIDER_ID, configPath, level),
|
|
175
|
-
});
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
return { items, warnings };
|
|
179
|
-
}
|
|
180
|
-
|
|
181
72
|
// =============================================================================
|
|
182
73
|
// Skills (skills/)
|
|
183
74
|
// =============================================================================
|
|
@@ -357,14 +248,6 @@ registerProvider(contextFileCapability.id, {
|
|
|
357
248
|
load: loadContextFiles,
|
|
358
249
|
});
|
|
359
250
|
|
|
360
|
-
registerProvider(mcpCapability.id, {
|
|
361
|
-
id: PROVIDER_ID,
|
|
362
|
-
displayName: DISPLAY_NAME,
|
|
363
|
-
description: "Load MCP servers from opencode.json mcp key",
|
|
364
|
-
priority: PRIORITY,
|
|
365
|
-
load: loadMCPServers,
|
|
366
|
-
});
|
|
367
|
-
|
|
368
251
|
registerProvider(skillCapability.id, {
|
|
369
252
|
id: PROVIDER_ID,
|
|
370
253
|
displayName: DISPLAY_NAME,
|
|
@@ -6,93 +6,23 @@
|
|
|
6
6
|
* - Project: .windsurf
|
|
7
7
|
*
|
|
8
8
|
* Supports:
|
|
9
|
-
* - MCP servers from mcp_config.json
|
|
10
9
|
* - Rules from .windsurf/rules/*.md and ~/.codeium/windsurf/memories/global_rules.md
|
|
11
10
|
* - Legacy .windsurfrules file
|
|
11
|
+
*
|
|
12
|
+
* MCP servers are intentionally NOT inherited live from Windsurf config: SKC
|
|
13
|
+
* owns MCP runtime execution. Copy definitions into SKC's own mcp.json instead.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
|
-
import { tryParseJson } from "@sayknow-cli/utils";
|
|
15
16
|
import { registerProvider } from "../capability";
|
|
16
17
|
import { readFile } from "../capability/fs";
|
|
17
|
-
import { type MCPServer, mcpCapability } from "../capability/mcp";
|
|
18
18
|
import { type Rule, ruleCapability } from "../capability/rule";
|
|
19
19
|
import type { LoadContext, LoadResult } from "../capability/types";
|
|
20
|
-
import {
|
|
21
|
-
buildRuleFromMarkdown,
|
|
22
|
-
createSourceMeta,
|
|
23
|
-
expandEnvVarsDeep,
|
|
24
|
-
getProjectPath,
|
|
25
|
-
getUserPath,
|
|
26
|
-
loadFilesFromDir,
|
|
27
|
-
} from "./helpers";
|
|
20
|
+
import { buildRuleFromMarkdown, createSourceMeta, getProjectPath, getUserPath, loadFilesFromDir } from "./helpers";
|
|
28
21
|
|
|
29
22
|
const PROVIDER_ID = "windsurf";
|
|
30
23
|
const DISPLAY_NAME = "Windsurf";
|
|
31
24
|
const PRIORITY = 50;
|
|
32
25
|
|
|
33
|
-
// =============================================================================
|
|
34
|
-
// MCP Servers
|
|
35
|
-
// =============================================================================
|
|
36
|
-
|
|
37
|
-
function parseServerConfig(
|
|
38
|
-
name: string,
|
|
39
|
-
serverConfig: unknown,
|
|
40
|
-
path: string,
|
|
41
|
-
scope: "user" | "project",
|
|
42
|
-
): { server?: MCPServer; warning?: string } {
|
|
43
|
-
if (typeof serverConfig !== "object" || serverConfig === null) {
|
|
44
|
-
return { warning: `Invalid server config for "${name}" in ${path}` };
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const server = expandEnvVarsDeep(serverConfig as Record<string, unknown>);
|
|
48
|
-
return {
|
|
49
|
-
server: {
|
|
50
|
-
name,
|
|
51
|
-
command: server.command as string | undefined,
|
|
52
|
-
args: server.args as string[] | undefined,
|
|
53
|
-
env: server.env as Record<string, string> | undefined,
|
|
54
|
-
url: server.url as string | undefined,
|
|
55
|
-
headers: server.headers as Record<string, string> | undefined,
|
|
56
|
-
transport: server.type as "stdio" | "sse" | "http" | undefined,
|
|
57
|
-
timeout: typeof server.timeout === "number" ? server.timeout : undefined,
|
|
58
|
-
_source: createSourceMeta(PROVIDER_ID, path, scope),
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async function loadMCPServers(ctx: LoadContext): Promise<LoadResult<MCPServer>> {
|
|
64
|
-
const items: MCPServer[] = [];
|
|
65
|
-
const warnings: string[] = [];
|
|
66
|
-
|
|
67
|
-
const userPath = getUserPath(ctx, "windsurf", "mcp_config.json");
|
|
68
|
-
const [userContent, projectPath] = await Promise.all([
|
|
69
|
-
userPath ? readFile(userPath) : Promise.resolve(null),
|
|
70
|
-
getProjectPath(ctx, "windsurf", "mcp_config.json"),
|
|
71
|
-
]);
|
|
72
|
-
|
|
73
|
-
const projectContent = projectPath ? await readFile(projectPath) : null;
|
|
74
|
-
|
|
75
|
-
const configs: Array<{ content: string | null; path: string | null; scope: "user" | "project" }> = [
|
|
76
|
-
{ content: userContent, path: userPath, scope: "user" },
|
|
77
|
-
{ content: projectContent, path: projectPath, scope: "project" },
|
|
78
|
-
];
|
|
79
|
-
|
|
80
|
-
for (const { content, path, scope } of configs) {
|
|
81
|
-
if (!content || !path) continue;
|
|
82
|
-
|
|
83
|
-
const config = tryParseJson<{ mcpServers?: Record<string, unknown> }>(content);
|
|
84
|
-
if (!config?.mcpServers) continue;
|
|
85
|
-
|
|
86
|
-
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
87
|
-
const result = parseServerConfig(name, serverConfig, path, scope);
|
|
88
|
-
if (result.warning) warnings.push(result.warning);
|
|
89
|
-
if (result.server) items.push(result.server);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { items, warnings };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
26
|
// =============================================================================
|
|
97
27
|
// Rules
|
|
98
28
|
// =============================================================================
|
|
@@ -130,14 +60,6 @@ async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
|
|
|
130
60
|
// Provider Registration
|
|
131
61
|
// =============================================================================
|
|
132
62
|
|
|
133
|
-
registerProvider<MCPServer>(mcpCapability.id, {
|
|
134
|
-
id: PROVIDER_ID,
|
|
135
|
-
displayName: DISPLAY_NAME,
|
|
136
|
-
description: "Load MCP servers from Windsurf config (mcp_config.json)",
|
|
137
|
-
priority: PRIORITY,
|
|
138
|
-
load: loadMCPServers,
|
|
139
|
-
});
|
|
140
|
-
|
|
141
63
|
registerProvider<Rule>(ruleCapability.id, {
|
|
142
64
|
id: PROVIDER_ID,
|
|
143
65
|
displayName: DISPLAY_NAME,
|
package/src/edit/read-file.ts
CHANGED
|
@@ -14,6 +14,13 @@ import { isNotebookPath, readEditableNotebookText, serializeEditedNotebookText }
|
|
|
14
14
|
*/
|
|
15
15
|
export const MAX_EDIT_FILE_BYTES = 8 * 1024 * 1024;
|
|
16
16
|
|
|
17
|
+
const UTF8_BOM = [0xef, 0xbb, 0xbf] as const;
|
|
18
|
+
|
|
19
|
+
async function fileHasUtf8Bom(file: Bun.BunFile): Promise<boolean> {
|
|
20
|
+
const bytes = await file.slice(0, UTF8_BOM.length).bytes();
|
|
21
|
+
return UTF8_BOM.every((byte, index) => bytes[index] === byte);
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
export async function readEditFileText(absolutePath: string, path: string): Promise<string> {
|
|
18
25
|
try {
|
|
19
26
|
const file = Bun.file(absolutePath);
|
|
@@ -28,7 +35,8 @@ export async function readEditFileText(absolutePath: string, path: string): Prom
|
|
|
28
35
|
// Guard BEFORE the notebook fast-path: a >8 MiB .ipynb would otherwise load + JSON-parse
|
|
29
36
|
// + convert the whole file via readEditableNotebookText, bypassing the F19 freeze guard.
|
|
30
37
|
if (isNotebookPath(absolutePath)) return await readEditableNotebookText(absolutePath, path);
|
|
31
|
-
|
|
38
|
+
const text = await file.text();
|
|
39
|
+
return (await fileHasUtf8Bom(file)) && !text.startsWith("\uFEFF") ? `\uFEFF${text}` : text;
|
|
32
40
|
} catch (error) {
|
|
33
41
|
if (isEnoent(error)) {
|
|
34
42
|
throw new Error(`File not found: ${path}`);
|
|
@@ -136,6 +136,17 @@ export interface ExtensionUIDialogOptions {
|
|
|
136
136
|
optionLabel: string;
|
|
137
137
|
onSubmit: (text: string) => void;
|
|
138
138
|
};
|
|
139
|
+
/**
|
|
140
|
+
* Inline free-text input for a non-answer clarification action. It is
|
|
141
|
+
* separate from `customInput` so callers can distinguish "ask about these
|
|
142
|
+
* choices" from "provide my own answer". Empty/whitespace-only submissions
|
|
143
|
+
* may be rejected by the selector when `allowEmpty` is false.
|
|
144
|
+
*/
|
|
145
|
+
clarificationInput?: {
|
|
146
|
+
optionLabel: string;
|
|
147
|
+
onSubmit: (text: string) => void;
|
|
148
|
+
allowEmpty?: boolean;
|
|
149
|
+
};
|
|
139
150
|
}
|
|
140
151
|
|
|
141
152
|
/** Raw terminal input listener for extensions. */
|
|
@@ -326,12 +326,28 @@ interface SkillTokenMatch {
|
|
|
326
326
|
skill: Skill;
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
function buildInlineSkillInvocationArgs(text: string, matches: readonly SkillTokenMatch[]): string {
|
|
330
|
+
const pieces: string[] = [];
|
|
331
|
+
let cursor = 0;
|
|
332
|
+
for (const match of matches) {
|
|
333
|
+
pieces.push(text.slice(cursor, match.index));
|
|
334
|
+
cursor = match.end;
|
|
335
|
+
}
|
|
336
|
+
pieces.push(text.slice(cursor));
|
|
337
|
+
return pieces
|
|
338
|
+
.join("")
|
|
339
|
+
.replace(/[ \t]{2,}/g, " ")
|
|
340
|
+
.replace(/[ \t]+\n/g, "\n")
|
|
341
|
+
.replace(/\n[ \t]+/g, "\n")
|
|
342
|
+
.trim();
|
|
343
|
+
}
|
|
344
|
+
|
|
329
345
|
export function parseSkillInvocations(
|
|
330
346
|
text: string,
|
|
331
347
|
skillsByCommandName: ReadonlyMap<string, Skill>,
|
|
332
348
|
): ParsedSkillInvocation[] {
|
|
333
349
|
const trimmedText = text.trim();
|
|
334
|
-
if (!trimmedText
|
|
350
|
+
if (!trimmedText) return [];
|
|
335
351
|
const canonicalSkillCommandPattern = /(^|\s)\/(skill:[^\s]+)/g;
|
|
336
352
|
const matches: SkillTokenMatch[] = [];
|
|
337
353
|
for (const match of trimmedText.matchAll(canonicalSkillCommandPattern)) {
|
|
@@ -348,7 +364,19 @@ export function parseSkillInvocations(
|
|
|
348
364
|
skill,
|
|
349
365
|
});
|
|
350
366
|
}
|
|
351
|
-
if (matches.length === 0
|
|
367
|
+
if (matches.length === 0) return [];
|
|
368
|
+
if (matches[0]?.index !== 0) {
|
|
369
|
+
// Preserve leading slash-command semantics: `/skill:unknown /skill:alpha`
|
|
370
|
+
// should remain plain text rather than silently skipping the unknown
|
|
371
|
+
// leading command and invoking the later known one.
|
|
372
|
+
if (trimmedText.startsWith("/")) return [];
|
|
373
|
+
const args = buildInlineSkillInvocationArgs(trimmedText, matches);
|
|
374
|
+
return matches.map(match => ({
|
|
375
|
+
commandName: match.commandName,
|
|
376
|
+
args,
|
|
377
|
+
skill: match.skill,
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
352
380
|
return matches.map((match, index) => {
|
|
353
381
|
const next = matches[index + 1];
|
|
354
382
|
return {
|