@sayknow-cli/coding-agent 0.3.1 → 0.3.4
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/bin/skc.js +4 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/plugin-cli.d.ts +2 -0
- package/dist/types/cli.d.ts +6 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/plugin.d.ts +6 -0
- package/dist/types/commands/session.d.ts +6 -0
- package/dist/types/config/keybindings.d.ts +2 -2
- package/dist/types/config/model-profile-activation.d.ts +8 -1
- package/dist/types/config/model-profiles.d.ts +2 -2
- package/dist/types/config/model-registry.d.ts +3 -3
- package/dist/types/config/models-config-schema.d.ts +0 -5
- package/dist/types/config/settings-schema.d.ts +79 -68
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/skc-plugins/index.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/injection.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/skc-plugins/types.d.ts +158 -2
- package/dist/types/extensibility/skc-plugins/validation.d.ts +8 -1
- package/dist/types/i18n/messages/en.d.ts +0 -1
- package/dist/types/main.d.ts +2 -0
- package/dist/types/modes/components/custom-editor.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +8 -0
- package/dist/types/modes/components/settings-defs.d.ts +0 -1
- package/dist/types/modes/components/status-line/git-utils.d.ts +6 -0
- package/dist/types/modes/theme/defaults/index.d.ts +99 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/notifications/html-format.d.ts +11 -0
- package/dist/types/notifications/index.d.ts +149 -1
- package/dist/types/notifications/lifecycle-commands.d.ts +60 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +98 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +2 -0
- package/dist/types/notifications/recent-activity.d.ts +35 -0
- package/dist/types/notifications/telegram-daemon.d.ts +114 -16
- package/dist/types/notifications/telegram-reference.d.ts +3 -1
- package/dist/types/notifications/topic-registry.d.ts +12 -9
- package/dist/types/runtime-mcp/types.d.ts +7 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +14 -4
- package/dist/types/session/blob-store.d.ts +25 -0
- package/dist/types/session/session-manager.d.ts +57 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +1 -0
- package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/skc-runtime/state-renderer.d.ts +5 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +2 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +30 -2
- package/dist/types/skc-runtime/tmux-sessions.d.ts +18 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +37 -1
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +80 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +6 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +9 -1
- package/dist/types/tools/browser/tab-supervisor.d.ts +31 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/computer-gc.d.ts +23 -0
- package/dist/types/tools/cron.d.ts +36 -61
- package/dist/types/tools/index.d.ts +3 -2
- package/dist/types/tools/resource-gc.d.ts +54 -0
- package/dist/types/utils/changelog.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +6 -3
- package/dist/types/web/scrapers/types.d.ts +5 -0
- package/dist/types/web/scrapers/utils.d.ts +7 -1
- package/dist/types/web/search/index.d.ts +1 -0
- package/dist/types/web/search/providers/utils.d.ts +11 -4
- package/package.json +11 -9
- package/scripts/g004-tmux-smoke.ts +100 -0
- package/scripts/g005-daemon-smoke.ts +180 -0
- package/scripts/g011-daemon-path-smoke.ts +153 -0
- package/src/cli/args.ts +0 -1
- package/src/cli/fast-help.ts +29 -2
- package/src/cli/mcp-cli.ts +272 -0
- package/src/cli/plugin-cli.ts +66 -3
- package/src/cli/web-search-cli.ts +5 -0
- package/src/cli.ts +30 -12
- package/src/commands/mcp.ts +117 -0
- package/src/commands/plugin.ts +4 -0
- package/src/commands/session.ts +18 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-profile-activation.ts +62 -8
- package/src/config/model-profiles.ts +3 -4
- package/src/config/model-registry.ts +3 -6
- package/src/config/models-config-schema.ts +1 -1
- package/src/config/settings-schema.ts +88 -103
- package/src/deep-interview/plaintext-gate-guard.ts +94 -0
- package/src/defaults/skc/extensions/grok-cli-vendor/biome.json +1 -1
- package/src/defaults/skc/skills/deep-interview/SKILL.md +7 -6
- package/src/defaults/skc/skills/team/SKILL.md +5 -3
- package/src/defaults/skc/skills/ultragoal/SKILL.md +41 -13
- package/src/export/html/index.ts +2 -2
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +0 -12
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/skc-plugins/compiler.ts +351 -0
- package/src/extensibility/skc-plugins/constrained-hooks.ts +170 -0
- package/src/extensibility/skc-plugins/index.ts +9 -0
- package/src/extensibility/skc-plugins/injection.ts +109 -0
- package/src/extensibility/skc-plugins/installer.ts +434 -0
- package/src/extensibility/skc-plugins/loader.ts +3 -1
- package/src/extensibility/skc-plugins/mcp-policy.ts +239 -0
- package/src/extensibility/skc-plugins/observability.ts +84 -0
- package/src/extensibility/skc-plugins/paths.ts +1 -1
- package/src/extensibility/skc-plugins/prompt-appendix.ts +109 -0
- package/src/extensibility/skc-plugins/registry.ts +180 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +234 -0
- package/src/extensibility/skc-plugins/schema.ts +250 -20
- package/src/extensibility/skc-plugins/session-validation.ts +147 -0
- package/src/extensibility/skc-plugins/types.ts +199 -3
- package/src/extensibility/skc-plugins/validation.ts +80 -0
- package/src/extensibility/skills.ts +15 -0
- package/src/goals/tools/goal-tool.ts +14 -1
- package/src/hooks/skill-state.ts +57 -0
- package/src/i18n/messages/de.ts +0 -1
- package/src/i18n/messages/en.ts +0 -1
- package/src/i18n/messages/es.ts +0 -1
- package/src/i18n/messages/fr.ts +0 -1
- package/src/i18n/messages/ja.ts +0 -1
- package/src/i18n/messages/ko.ts +0 -1
- package/src/i18n/messages/zh.ts +0 -1
- package/src/internal-urls/docs-index.generated.ts +14 -11
- package/src/main.ts +14 -3
- package/src/modes/bridge/bridge-mode.ts +11 -0
- package/src/modes/components/assistant-message.ts +49 -1
- package/src/modes/components/custom-editor.ts +2 -0
- package/src/modes/components/footer.ts +2 -3
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/hook-selector.ts +67 -43
- package/src/modes/components/model-selector.ts +65 -12
- package/src/modes/components/settings-defs.ts +1 -2
- package/src/modes/components/settings-selector.ts +2 -7
- package/src/modes/components/status-line/git-utils.ts +25 -0
- package/src/modes/components/status-line.ts +10 -11
- package/src/modes/components/welcome.ts +2 -3
- package/src/modes/controllers/extension-ui-controller.ts +0 -27
- package/src/modes/controllers/selector-controller.ts +59 -11
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/shared/agent-wire/scopes.ts +1 -1
- package/src/modes/theme/defaults/gruvbox-dark.json +99 -0
- package/src/modes/theme/defaults/index.ts +2 -0
- package/src/modes/theme/theme.ts +0 -4
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/notifications/html-format.ts +38 -0
- package/src/notifications/index.ts +242 -12
- package/src/notifications/lifecycle-commands.ts +238 -0
- package/src/notifications/lifecycle-control-runtime.ts +405 -0
- package/src/notifications/lifecycle-orchestrator.ts +358 -0
- package/src/notifications/operator-runtime.ts +171 -0
- package/src/notifications/rate-limit-pool.ts +19 -0
- package/src/notifications/recent-activity.ts +132 -0
- package/src/notifications/telegram-daemon.ts +778 -257
- package/src/notifications/telegram-reference.ts +25 -7
- package/src/notifications/topic-registry.ts +23 -9
- package/src/prompts/agents/executor.md +2 -2
- package/src/prompts/system/system-prompt.md +2 -2
- package/src/prompts/tools/cron.md +5 -3
- package/src/prompts/tools/read.md +1 -1
- package/src/runtime-mcp/transports/stdio.ts +38 -4
- package/src/runtime-mcp/types.ts +7 -0
- package/src/sdk.ts +162 -10
- package/src/session/agent-session.ts +210 -74
- package/src/session/blob-store.ts +196 -8
- package/src/session/session-manager.ts +762 -12
- package/src/skc-runtime/launch-tmux.ts +68 -19
- package/src/skc-runtime/psmux-detect.ts +239 -0
- package/src/skc-runtime/state-renderer.ts +13 -0
- package/src/skc-runtime/team-runtime.ts +56 -23
- package/src/skc-runtime/tmux-common.ts +88 -4
- package/src/skc-runtime/tmux-sessions.ts +111 -9
- package/src/skc-runtime/ultragoal-guard.ts +192 -8
- package/src/skc-runtime/ultragoal-runtime.ts +296 -16
- package/src/slash-commands/builtin-registry.ts +23 -3
- package/src/slash-commands/helpers/fast-status-report.ts +13 -3
- package/src/slash-commands/helpers/parse.ts +2 -1
- package/src/system-prompt.ts +9 -0
- package/src/task/executor.ts +31 -7
- package/src/task/index.ts +2 -0
- package/src/tools/ask.ts +5 -1
- package/src/tools/bash.ts +9 -0
- package/src/tools/browser/tab-supervisor.ts +86 -2
- package/src/tools/composer-bash-policy.ts +96 -0
- package/src/tools/computer-gc.ts +66 -0
- package/src/tools/computer.ts +2 -0
- package/src/tools/cron.ts +75 -112
- package/src/tools/fetch.ts +18 -2
- package/src/tools/index.ts +5 -9
- package/src/tools/read.ts +25 -55
- package/src/tools/renderers.ts +0 -2
- package/src/tools/resource-gc.ts +291 -0
- package/src/tools/ultragoal-ask-guard.ts +7 -1
- package/src/utils/changelog.ts +8 -0
- package/src/web/insane/url-guard.ts +18 -14
- package/src/web/scrapers/types.ts +143 -45
- package/src/web/scrapers/utils.ts +70 -19
- package/src/web/search/index.ts +1 -0
- package/src/web/search/providers/utils.ts +22 -5
- package/vendor/insane-search/MANIFEST.json +3 -1
- package/vendor/insane-search/engine/__init__.py +14 -0
- package/vendor/insane-search/engine/content_safety.py +151 -0
- package/vendor/insane-search/engine/fetch_chain.py +32 -0
- package/vendor/insane-search/engine/tests/test_u8.py +216 -0
- package/dist/types/tools/inspect-image-renderer.d.ts +0 -26
- package/dist/types/tools/inspect-image.d.ts +0 -31
- package/src/prompts/tools/inspect-image-system.md +0 -20
- package/src/prompts/tools/inspect-image.md +0 -32
- package/src/tools/inspect-image-renderer.ts +0 -103
- package/src/tools/inspect-image.ts +0 -172
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct MCP server registration CLI helpers.
|
|
3
|
+
*
|
|
4
|
+
* This surface only writes explicit user-provided server definitions to SKC's
|
|
5
|
+
* own MCP config. It never imports or inherits live configs from other agents.
|
|
6
|
+
*/
|
|
7
|
+
import { getMCPConfigPath, getProjectDir } from "@sayknow-cli/utils";
|
|
8
|
+
import { getMCPServer, readMCPConfigFile, removeMCPServer, upsertMCPServer } from "../runtime-mcp/config-writer";
|
|
9
|
+
import type { MCPConfigFile, MCPServerConfig } from "../runtime-mcp/types";
|
|
10
|
+
|
|
11
|
+
export type MCPAction = "add" | "list" | "remove";
|
|
12
|
+
|
|
13
|
+
export interface MCPCommandArgs {
|
|
14
|
+
action: MCPAction;
|
|
15
|
+
name?: string;
|
|
16
|
+
commandArgs?: string[];
|
|
17
|
+
flags: {
|
|
18
|
+
project?: boolean;
|
|
19
|
+
force?: boolean;
|
|
20
|
+
json?: boolean;
|
|
21
|
+
type?: "stdio" | "http" | "sse";
|
|
22
|
+
command?: string;
|
|
23
|
+
url?: string;
|
|
24
|
+
arg?: string[];
|
|
25
|
+
env?: string[];
|
|
26
|
+
header?: string[];
|
|
27
|
+
cwd?: string;
|
|
28
|
+
timeout?: number;
|
|
29
|
+
};
|
|
30
|
+
cwd?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class MCPArgsError extends Error {}
|
|
34
|
+
|
|
35
|
+
interface ScopedPath {
|
|
36
|
+
scope: "user" | "project";
|
|
37
|
+
path: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface RedactedServerEntry {
|
|
41
|
+
name: string;
|
|
42
|
+
config: MCPServerConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const REDACTED = "<redacted>";
|
|
46
|
+
const SENSITIVE_KEY_PATTERN =
|
|
47
|
+
/(?:token|secret|key|credential|password|passwd|pwd|authorization|auth|bearer|cookie|session)/i;
|
|
48
|
+
|
|
49
|
+
function resolvePath(args: MCPCommandArgs): ScopedPath {
|
|
50
|
+
const scope = args.flags.project ? "project" : "user";
|
|
51
|
+
return { scope, path: getMCPConfigPath(scope, args.cwd ?? getProjectDir()) };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function parsePairs(values: string[] | undefined, label: string): Record<string, string> | undefined {
|
|
55
|
+
if (!values || values.length === 0) return undefined;
|
|
56
|
+
const parsed: Record<string, string> = {};
|
|
57
|
+
for (const value of values) {
|
|
58
|
+
const index = value.indexOf("=");
|
|
59
|
+
if (index <= 0) {
|
|
60
|
+
throw new MCPArgsError(`Invalid ${label}. Use KEY=VALUE.`);
|
|
61
|
+
}
|
|
62
|
+
const key = value.slice(0, index).trim();
|
|
63
|
+
if (!key) {
|
|
64
|
+
throw new MCPArgsError(`Invalid ${label}. Key cannot be empty.`);
|
|
65
|
+
}
|
|
66
|
+
parsed[key] = value.slice(index + 1);
|
|
67
|
+
}
|
|
68
|
+
return parsed;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function buildServerConfig(args: MCPCommandArgs): MCPServerConfig {
|
|
72
|
+
const type = args.flags.type ?? (args.flags.url ? "http" : "stdio");
|
|
73
|
+
const timeout = args.flags.timeout;
|
|
74
|
+
const shared = timeout === undefined ? {} : { timeout };
|
|
75
|
+
|
|
76
|
+
if (type === "stdio") {
|
|
77
|
+
const command = args.flags.command ?? args.commandArgs?.[0];
|
|
78
|
+
if (!command) {
|
|
79
|
+
throw new MCPArgsError("`skc mcp add` requires --command <cmd> or a positional command for stdio servers.");
|
|
80
|
+
}
|
|
81
|
+
const config: MCPServerConfig = {
|
|
82
|
+
...shared,
|
|
83
|
+
type: "stdio",
|
|
84
|
+
command,
|
|
85
|
+
};
|
|
86
|
+
const positionalArgs = args.flags.command ? [] : (args.commandArgs ?? []).slice(1);
|
|
87
|
+
const serverArgs = [...positionalArgs, ...(args.flags.arg ?? [])];
|
|
88
|
+
if (serverArgs.length > 0) config.args = serverArgs;
|
|
89
|
+
const env = parsePairs(args.flags.env, "env");
|
|
90
|
+
if (env) config.env = env;
|
|
91
|
+
if (args.flags.cwd) config.cwd = args.flags.cwd;
|
|
92
|
+
return config;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const url = args.flags.url ?? args.commandArgs?.[0];
|
|
96
|
+
if (!url) {
|
|
97
|
+
throw new MCPArgsError(`\`skc mcp add --type ${type}\` requires --url <url> or a positional URL.`);
|
|
98
|
+
}
|
|
99
|
+
const headers = parsePairs(args.flags.header, "header");
|
|
100
|
+
if (type === "http") {
|
|
101
|
+
const config: MCPServerConfig = {
|
|
102
|
+
...shared,
|
|
103
|
+
type,
|
|
104
|
+
url,
|
|
105
|
+
};
|
|
106
|
+
if (headers) config.headers = headers;
|
|
107
|
+
return config;
|
|
108
|
+
}
|
|
109
|
+
const config: MCPServerConfig = {
|
|
110
|
+
...shared,
|
|
111
|
+
type,
|
|
112
|
+
url,
|
|
113
|
+
};
|
|
114
|
+
if (headers) config.headers = headers;
|
|
115
|
+
return config;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function redactRecord(
|
|
119
|
+
record: Record<string, string> | undefined,
|
|
120
|
+
redactAllValues: boolean,
|
|
121
|
+
): Record<string, string> | undefined {
|
|
122
|
+
if (!record) return undefined;
|
|
123
|
+
return Object.fromEntries(
|
|
124
|
+
Object.entries(record).map(([key, value]) => [
|
|
125
|
+
key,
|
|
126
|
+
redactAllValues || SENSITIVE_KEY_PATTERN.test(key) ? REDACTED : value,
|
|
127
|
+
]),
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function redactMCPServerConfig(config: MCPServerConfig): MCPServerConfig {
|
|
132
|
+
const redacted = { ...config } as MCPServerConfig;
|
|
133
|
+
if ("env" in redacted) {
|
|
134
|
+
const env = redactRecord(redacted.env, true);
|
|
135
|
+
if (env) redacted.env = env;
|
|
136
|
+
}
|
|
137
|
+
if ("headers" in redacted) {
|
|
138
|
+
const headers = redactRecord(redacted.headers, true);
|
|
139
|
+
if (headers) redacted.headers = headers;
|
|
140
|
+
}
|
|
141
|
+
if (redacted.auth) {
|
|
142
|
+
redacted.auth = {
|
|
143
|
+
type: redacted.auth.type,
|
|
144
|
+
credentialId: redacted.auth.credentialId ? REDACTED : undefined,
|
|
145
|
+
tokenUrl: redacted.auth.tokenUrl,
|
|
146
|
+
clientId: redacted.auth.clientId ? REDACTED : undefined,
|
|
147
|
+
clientSecret: redacted.auth.clientSecret ? REDACTED : undefined,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (redacted.oauth) {
|
|
151
|
+
redacted.oauth = {
|
|
152
|
+
clientId: redacted.oauth.clientId ? REDACTED : undefined,
|
|
153
|
+
clientSecret: redacted.oauth.clientSecret ? REDACTED : undefined,
|
|
154
|
+
redirectUri: redacted.oauth.redirectUri,
|
|
155
|
+
callbackPort: redacted.oauth.callbackPort,
|
|
156
|
+
callbackPath: redacted.oauth.callbackPath,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
return redacted;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function collectEntries(config: MCPConfigFile): RedactedServerEntry[] {
|
|
163
|
+
return Object.entries(config.mcpServers ?? {})
|
|
164
|
+
.sort(([a], [b]) => a.localeCompare(b))
|
|
165
|
+
.map(([name, serverConfig]) => ({ name, config: redactMCPServerConfig(serverConfig) }));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function writeJson(value: unknown): void {
|
|
169
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function renderServerLine(entry: RedactedServerEntry): string {
|
|
173
|
+
const config = entry.config;
|
|
174
|
+
if (config.type === "http" || config.type === "sse") {
|
|
175
|
+
return `${entry.name}\t${config.type}\t${config.url}`;
|
|
176
|
+
}
|
|
177
|
+
const args = config.args && config.args.length > 0 ? ` ${config.args.join(" ")}` : "";
|
|
178
|
+
return `${entry.name}\tstdio\t${config.command}${args}`;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function renderDetails(entry: RedactedServerEntry): string {
|
|
182
|
+
return `${renderServerLine(entry)}\n${JSON.stringify(entry.config, null, 2)}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function runAdd(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
186
|
+
if (!args.name) throw new MCPArgsError("`skc mcp add` requires a server name.");
|
|
187
|
+
const config = buildServerConfig(args);
|
|
188
|
+
const result = await upsertMCPServer(scoped.path, args.name, config, { force: args.flags.force });
|
|
189
|
+
const redacted = redactMCPServerConfig(config);
|
|
190
|
+
if (args.flags.json) {
|
|
191
|
+
writeJson({
|
|
192
|
+
action: "add",
|
|
193
|
+
status: result.status,
|
|
194
|
+
name: args.name,
|
|
195
|
+
scope: scoped.scope,
|
|
196
|
+
path: scoped.path,
|
|
197
|
+
config: redacted,
|
|
198
|
+
});
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
if (result.status === "skipped") {
|
|
202
|
+
process.stdout.write(
|
|
203
|
+
`MCP server "${args.name}" already exists in ${scoped.scope} config. Pass --force to overwrite.\n`,
|
|
204
|
+
);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
process.stdout.write(`MCP server "${args.name}" ${result.status} in ${scoped.scope} config: ${scoped.path}\n`);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function runList(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
211
|
+
const config = await readMCPConfigFile(scoped.path);
|
|
212
|
+
const entries = collectEntries(config);
|
|
213
|
+
if (args.flags.json) {
|
|
214
|
+
writeJson({ action: "list", scope: scoped.scope, path: scoped.path, servers: entries });
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
if (entries.length === 0) {
|
|
218
|
+
process.stdout.write(`No MCP servers registered in ${scoped.scope} config: ${scoped.path}\n`);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
process.stdout.write(`MCP servers in ${scoped.scope} config: ${scoped.path}\n`);
|
|
222
|
+
for (const entry of entries) {
|
|
223
|
+
process.stdout.write(`${renderDetails(entry)}\n`);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async function runRemove(args: MCPCommandArgs, scoped: ScopedPath): Promise<void> {
|
|
228
|
+
if (!args.name) throw new MCPArgsError("`skc mcp remove` requires a server name.");
|
|
229
|
+
const existing = await getMCPServer(scoped.path, args.name);
|
|
230
|
+
if (!existing) {
|
|
231
|
+
throw new MCPArgsError(`MCP server "${args.name}" not found in ${scoped.scope} config.`);
|
|
232
|
+
}
|
|
233
|
+
await removeMCPServer(scoped.path, args.name);
|
|
234
|
+
const entry = { name: args.name, config: redactMCPServerConfig(existing) };
|
|
235
|
+
if (args.flags.json) {
|
|
236
|
+
writeJson({
|
|
237
|
+
action: "remove",
|
|
238
|
+
status: "removed",
|
|
239
|
+
name: args.name,
|
|
240
|
+
scope: scoped.scope,
|
|
241
|
+
path: scoped.path,
|
|
242
|
+
removed: entry,
|
|
243
|
+
});
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(`Removed MCP server "${args.name}" from ${scoped.scope} config: ${scoped.path}\n`);
|
|
247
|
+
process.stdout.write(`${renderDetails(entry)}\n`);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function runMCPCommand(args: MCPCommandArgs): Promise<void> {
|
|
251
|
+
const scoped = resolvePath(args);
|
|
252
|
+
try {
|
|
253
|
+
switch (args.action) {
|
|
254
|
+
case "add":
|
|
255
|
+
await runAdd(args, scoped);
|
|
256
|
+
return;
|
|
257
|
+
case "list":
|
|
258
|
+
await runList(args, scoped);
|
|
259
|
+
return;
|
|
260
|
+
case "remove":
|
|
261
|
+
await runRemove(args, scoped);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
if (error instanceof MCPArgsError) {
|
|
266
|
+
process.stderr.write(`${error.message}\n`);
|
|
267
|
+
process.exitCode = 2;
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
throw error;
|
|
271
|
+
}
|
|
272
|
+
}
|
package/src/cli/plugin-cli.ts
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
MarketplaceManager,
|
|
18
18
|
} from "../extensibility/plugins/marketplace/index.js";
|
|
19
19
|
import { type ScanReport, scanPluginDir } from "../extensibility/plugins/security-scanner";
|
|
20
|
+
import { installSkcPluginBundle, isSkcPluginBundleSource, readRegistry } from "../extensibility/skc-plugins";
|
|
20
21
|
import { theme } from "../modes/theme/theme";
|
|
21
22
|
|
|
22
23
|
// =============================================================================
|
|
@@ -50,6 +51,8 @@ export interface PluginCommandArgs {
|
|
|
50
51
|
disable?: string;
|
|
51
52
|
set?: string;
|
|
52
53
|
scope?: "user" | "project";
|
|
54
|
+
user?: boolean;
|
|
55
|
+
project?: boolean;
|
|
53
56
|
};
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -111,6 +114,10 @@ export function parsePluginArgs(args: string[]): PluginCommandArgs | undefined {
|
|
|
111
114
|
result.flags.dryRun = true;
|
|
112
115
|
} else if (arg === "-l" || arg === "--local") {
|
|
113
116
|
result.flags.local = true;
|
|
117
|
+
} else if (arg === "--user") {
|
|
118
|
+
result.flags.user = true;
|
|
119
|
+
} else if (arg === "--project") {
|
|
120
|
+
result.flags.project = true;
|
|
114
121
|
} else if (arg === "--enable" && i + 1 < args.length) {
|
|
115
122
|
result.flags.enable = args[++i];
|
|
116
123
|
} else if (arg === "--disable" && i + 1 < args.length) {
|
|
@@ -366,7 +373,14 @@ function printSecurityAdvisory(report: ScanReport): void {
|
|
|
366
373
|
async function handleInstall(
|
|
367
374
|
manager: PluginManager,
|
|
368
375
|
packages: string[],
|
|
369
|
-
flags: {
|
|
376
|
+
flags: {
|
|
377
|
+
json?: boolean;
|
|
378
|
+
force?: boolean;
|
|
379
|
+
dryRun?: boolean;
|
|
380
|
+
scope?: "user" | "project";
|
|
381
|
+
user?: boolean;
|
|
382
|
+
project?: boolean;
|
|
383
|
+
},
|
|
370
384
|
): Promise<void> {
|
|
371
385
|
if (packages.length === 0) {
|
|
372
386
|
console.error(chalk.red(`Usage: ${APP_NAME} plugin install <package[@version]>[features] ...`));
|
|
@@ -381,6 +395,32 @@ async function handleInstall(
|
|
|
381
395
|
const knownMarketplaces = new Set((await mktMgr.listMarketplaces()).map(m => m.name));
|
|
382
396
|
|
|
383
397
|
for (const spec of packages) {
|
|
398
|
+
// SKC plugin bundle classifier: a source containing sayknow-plugin.json (or a
|
|
399
|
+
// git/tarball source) routes to the bundle installer BEFORE marketplace/npm.
|
|
400
|
+
if (await isSkcPluginBundleSource(spec)) {
|
|
401
|
+
if (flags.user === flags.project) {
|
|
402
|
+
console.error(
|
|
403
|
+
chalk.red(`SKC plugin bundle install requires exactly one of --user or --project for "${spec}".`),
|
|
404
|
+
);
|
|
405
|
+
process.exit(1);
|
|
406
|
+
}
|
|
407
|
+
const scope: "user" | "project" = flags.user ? "user" : "project";
|
|
408
|
+
try {
|
|
409
|
+
const res = await installSkcPluginBundle(spec, { scope, cwd: process.cwd(), force: flags.force });
|
|
410
|
+
if (flags.json) {
|
|
411
|
+
console.log(JSON.stringify({ name: res.entry.name, status: res.status, scope }, null, 2));
|
|
412
|
+
} else {
|
|
413
|
+
console.log(
|
|
414
|
+
chalk.green(`${theme.status.success} ${res.status} SKC plugin ${res.entry.name} (${scope})`),
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
} catch (err) {
|
|
418
|
+
console.error(chalk.red(`${theme.status.error} Failed to install SKC plugin ${spec}: ${err}`));
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
|
|
384
424
|
const target = classifyInstallTarget(spec, knownMarketplaces);
|
|
385
425
|
|
|
386
426
|
if (target.type === "marketplace") {
|
|
@@ -519,13 +559,16 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
|
|
|
519
559
|
const npmPlugins = await manager.list();
|
|
520
560
|
const mktMgr = await makeMarketplaceManager();
|
|
521
561
|
const mktPlugins = await mktMgr.listInstalledPlugins();
|
|
562
|
+
const cwd = getProjectDir();
|
|
563
|
+
const [skcUser, skcProject] = await Promise.all([readRegistry("user", cwd), readRegistry("project", cwd)]);
|
|
564
|
+
const skcBundles = [...skcUser.plugins, ...skcProject.plugins];
|
|
522
565
|
|
|
523
566
|
if (flags.json) {
|
|
524
|
-
console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins }, null, 2));
|
|
567
|
+
console.log(JSON.stringify({ npm: npmPlugins, marketplace: mktPlugins, skc: skcBundles }, null, 2));
|
|
525
568
|
return;
|
|
526
569
|
}
|
|
527
570
|
|
|
528
|
-
if (npmPlugins.length === 0 && mktPlugins.length === 0) {
|
|
571
|
+
if (npmPlugins.length === 0 && mktPlugins.length === 0 && skcBundles.length === 0) {
|
|
529
572
|
console.log(chalk.dim("No plugins installed"));
|
|
530
573
|
console.log(chalk.dim(`\nInstall plugins with: ${APP_NAME} plugin install <package>`));
|
|
531
574
|
return;
|
|
@@ -567,6 +610,26 @@ async function handleList(manager: PluginManager, flags: { json?: boolean }): Pr
|
|
|
567
610
|
console.log(` ${plugin.id} (${version})${scopeLabel}${shadowLabel}`);
|
|
568
611
|
}
|
|
569
612
|
}
|
|
613
|
+
|
|
614
|
+
if (skcBundles.length > 0) {
|
|
615
|
+
if (npmPlugins.length > 0 || mktPlugins.length > 0) console.log();
|
|
616
|
+
console.log(chalk.bold("SKC Plugin Bundles:\n"));
|
|
617
|
+
for (const plugin of skcBundles) {
|
|
618
|
+
const status = plugin.enabled ? chalk.green(theme.status.enabled) : chalk.dim(theme.status.disabled);
|
|
619
|
+
const scopeLabel = chalk.dim(` (${plugin.scope})`);
|
|
620
|
+
const disabledCount = plugin.disabledSurfaceIds.length;
|
|
621
|
+
const quarantineCount = plugin.quarantine?.length ?? 0;
|
|
622
|
+
const detail = [
|
|
623
|
+
disabledCount > 0 ? `${disabledCount} disabled` : null,
|
|
624
|
+
quarantineCount > 0 ? `${quarantineCount} quarantined` : null,
|
|
625
|
+
]
|
|
626
|
+
.filter((v): v is string => Boolean(v))
|
|
627
|
+
.join(", ");
|
|
628
|
+
console.log(
|
|
629
|
+
`${status} ${plugin.name}@${plugin.version}${scopeLabel}${detail ? chalk.dim(` — ${detail}`) : ""}`,
|
|
630
|
+
);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
570
633
|
}
|
|
571
634
|
|
|
572
635
|
async function handleLink(manager: PluginManager, paths: string[], flags: { json?: boolean }): Promise<void> {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
type SearchQueryParams,
|
|
16
16
|
} from "../web/search/index";
|
|
17
17
|
import { SEARCH_PROVIDER_ORDER, setPreferredSearchProvider, setSearchFallbackProviders } from "../web/search/provider";
|
|
18
|
+
import { setSearchHardTimeoutMs } from "../web/search/providers/utils";
|
|
18
19
|
import { renderSearchResult } from "../web/search/render";
|
|
19
20
|
import type { SearchProviderId } from "../web/search/types";
|
|
20
21
|
|
|
@@ -179,6 +180,10 @@ export async function runSearchCommand(cmd: SearchCommandArgs): Promise<void> {
|
|
|
179
180
|
configuredFallback.filter(value => typeof value === "string" && isConfigurableSearchProviderId(value)),
|
|
180
181
|
);
|
|
181
182
|
}
|
|
183
|
+
const configuredTimeout = settings.get("web_search.timeout");
|
|
184
|
+
if (typeof configuredTimeout === "number" && Number.isFinite(configuredTimeout) && configuredTimeout > 0) {
|
|
185
|
+
setSearchHardTimeoutMs(configuredTimeout * 1000);
|
|
186
|
+
}
|
|
182
187
|
|
|
183
188
|
const params: SearchQueryParams = {
|
|
184
189
|
query: cmd.query,
|
package/src/cli.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* CLI entry point
|
|
4
|
+
* CLI entry point — registers all commands explicitly and delegates to the
|
|
5
5
|
* lightweight CLI runner from pi-utils.
|
|
6
6
|
*/
|
|
7
7
|
import { Args, type CliConfig, Command, type CommandEntry, Flags, run } from "@sayknow-cli/utils/cli";
|
|
@@ -22,7 +22,7 @@ process.title = APP_NAME;
|
|
|
22
22
|
const rootHelpFlags = ["--help", "-h", "help"];
|
|
23
23
|
const versionFlags = ["--version", "-v"];
|
|
24
24
|
|
|
25
|
-
const commands: CommandEntry[] = [
|
|
25
|
+
export const commands: CommandEntry[] = [
|
|
26
26
|
{ name: "codex-native-hook", load: () => import("./commands/codex-native-hook").then(m => m.default) },
|
|
27
27
|
{ name: "state", load: () => import("./commands/state").then(m => m.default) },
|
|
28
28
|
{ name: "setup", load: () => import("./commands/setup").then(m => m.default) },
|
|
@@ -39,6 +39,7 @@ const commands: CommandEntry[] = [
|
|
|
39
39
|
{ name: "daemon", load: () => import("./commands/daemon").then(m => m.default) },
|
|
40
40
|
{ name: "web-search", aliases: ["q"], load: () => import("./commands/web-search").then(m => m.default) },
|
|
41
41
|
{ name: "mcp-serve", load: () => import("./commands/mcp-serve").then(m => m.default) },
|
|
42
|
+
{ name: "mcp", load: () => import("./commands/mcp").then(m => m.default) },
|
|
42
43
|
{
|
|
43
44
|
name: "contribute-pr",
|
|
44
45
|
aliases: ["contribution-prep"],
|
|
@@ -48,6 +49,7 @@ const commands: CommandEntry[] = [
|
|
|
48
49
|
{ name: "migrate", load: () => import("./commands/migrate").then(m => m.default) },
|
|
49
50
|
{ name: "rlm", load: () => import("./commands/rlm").then(m => m.default) },
|
|
50
51
|
{ name: "update", load: () => import("./commands/update").then(m => m.default) },
|
|
52
|
+
{ name: "plugin", load: () => import("./commands/plugin").then(m => m.default) },
|
|
51
53
|
{ name: "launch", load: () => import("./commands/launch").then(m => m.default) },
|
|
52
54
|
];
|
|
53
55
|
|
|
@@ -62,10 +64,7 @@ async function showHelp(config: CliConfig): Promise<void> {
|
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
async function installRuntimeGlobals(): Promise<void> {
|
|
65
|
-
const
|
|
66
|
-
import("@sayknow-cli/ai"),
|
|
67
|
-
import("@sayknow-cli/utils"),
|
|
68
|
-
]);
|
|
67
|
+
const { installH2Fetch } = await import("@sayknow-cli/ai/utils/h2-fetch");
|
|
69
68
|
// Activate HTTP/2 for all `fetch()` calls (provider streams, OAuth, model
|
|
70
69
|
// discovery, web tools). Bun's HTTP/2 client is gated on a startup flag we
|
|
71
70
|
// can't toggle from JS, so we patch globalThis.fetch to pass
|
|
@@ -75,8 +74,25 @@ async function installRuntimeGlobals(): Promise<void> {
|
|
|
75
74
|
|
|
76
75
|
// Strip macOS malloc-stack-logging env vars before any subprocess is spawned.
|
|
77
76
|
// Otherwise every child bun process (subagents, plugin installs, ptree spawns,
|
|
78
|
-
// etc.) prints a `MallocStackLogging: can't turn off
|
|
79
|
-
|
|
77
|
+
// etc.) prints a `MallocStackLogging: can't turn off …` warning to stderr.
|
|
78
|
+
delete process.env.MallocStackLogging;
|
|
79
|
+
delete process.env.MallocStackLoggingNoCompact;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function hasRootFastFlag(argv: string[], flags: readonly string[]): boolean {
|
|
83
|
+
for (const arg of argv) {
|
|
84
|
+
if (isSubcommand(arg)) return false;
|
|
85
|
+
if (flags.includes(arg)) return true;
|
|
86
|
+
}
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function hasRootHelpFlag(argv: string[]): boolean {
|
|
91
|
+
return hasRootFastFlag(argv, rootHelpFlags);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function hasRootVersionFlag(argv: string[]): boolean {
|
|
95
|
+
return hasRootFastFlag(argv, versionFlags);
|
|
80
96
|
}
|
|
81
97
|
|
|
82
98
|
class RootHelpCommand extends Command {
|
|
@@ -165,7 +181,7 @@ function isSubcommand(first: string | undefined): boolean {
|
|
|
165
181
|
*
|
|
166
182
|
* Purpose: catch the silent worker-load regressions that hit compiled
|
|
167
183
|
* binaries (issues #1011 and #1027). Neither `--version` nor
|
|
168
|
-
* `stats --summary` actually spawns a Worker on a fresh install
|
|
184
|
+
* `stats --summary` actually spawns a Worker on a fresh install — the
|
|
169
185
|
* sync path early-returns when no session files exist. This probe is the
|
|
170
186
|
* minimal end-to-end test that proves `new Worker(...)` resolves and the
|
|
171
187
|
* bundled worker module evaluates successfully. Wired into
|
|
@@ -196,7 +212,7 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
196
212
|
await runSmokeTest();
|
|
197
213
|
return;
|
|
198
214
|
}
|
|
199
|
-
if (
|
|
215
|
+
if (hasRootHelpFlag(argv)) {
|
|
200
216
|
const { renderRootHelp } = await import("@sayknow-cli/utils/cli");
|
|
201
217
|
const { getExtraHelpText } = await import("./cli/fast-help");
|
|
202
218
|
renderRootHelp({ bin: APP_NAME, version: VERSION, commands: new Map([["launch", RootHelpCommand]]) });
|
|
@@ -206,7 +222,7 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
206
222
|
}
|
|
207
223
|
return;
|
|
208
224
|
}
|
|
209
|
-
if (
|
|
225
|
+
if (hasRootVersionFlag(argv)) {
|
|
210
226
|
process.stdout.write(`${APP_NAME}/${VERSION}\n`);
|
|
211
227
|
return;
|
|
212
228
|
}
|
|
@@ -223,4 +239,6 @@ export async function runCli(argv: string[]): Promise<void> {
|
|
|
223
239
|
return run({ bin: APP_NAME, version: VERSION, argv: runArgv, commands, help: showHelp });
|
|
224
240
|
}
|
|
225
241
|
|
|
226
|
-
|
|
242
|
+
if (import.meta.main) {
|
|
243
|
+
await runCli(process.argv.slice(2));
|
|
244
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Direct MCP server registration for standalone SKC.
|
|
3
|
+
*/
|
|
4
|
+
import { Args, Command, Flags } from "@sayknow-cli/utils/cli";
|
|
5
|
+
import { type MCPAction, type MCPCommandArgs, runMCPCommand } from "../cli/mcp-cli";
|
|
6
|
+
|
|
7
|
+
const ACTIONS: MCPAction[] = ["add", "list", "remove"];
|
|
8
|
+
|
|
9
|
+
export default class MCP extends Command {
|
|
10
|
+
static description = "Register standalone MCP servers explicitly in SKC config";
|
|
11
|
+
static delegateHelp = true;
|
|
12
|
+
|
|
13
|
+
static examples = [
|
|
14
|
+
"skc mcp add context7 npx -y @upstash/context7-mcp",
|
|
15
|
+
"skc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN",
|
|
16
|
+
"skc mcp list --json",
|
|
17
|
+
"skc mcp remove context7",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
static args = {
|
|
21
|
+
action: Args.string({ description: "MCP action", required: false, options: ACTIONS }),
|
|
22
|
+
name: Args.string({ description: "Server name", required: false }),
|
|
23
|
+
commandArgs: Args.string({
|
|
24
|
+
description: "Command/URL and trailing args for add",
|
|
25
|
+
required: false,
|
|
26
|
+
multiple: true,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
static flags = {
|
|
31
|
+
project: Flags.boolean({ description: "Write/read project scope (./.skc/mcp.json) instead of user scope" }),
|
|
32
|
+
force: Flags.boolean({ description: "Overwrite an existing server during add", default: false }),
|
|
33
|
+
json: Flags.boolean({
|
|
34
|
+
char: "j",
|
|
35
|
+
description: "Emit machine-readable JSON with sensitive values redacted",
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
type: Flags.string({ description: "Server transport type", options: ["stdio", "http", "sse"] }),
|
|
39
|
+
command: Flags.string({ description: "Stdio server command for add" }),
|
|
40
|
+
url: Flags.string({ description: "HTTP/SSE server URL for add" }),
|
|
41
|
+
arg: Flags.string({ description: "Argument passed to a stdio server (repeatable)", multiple: true }),
|
|
42
|
+
env: Flags.string({
|
|
43
|
+
description: "Environment variable for stdio server as KEY=VALUE (repeatable)",
|
|
44
|
+
multiple: true,
|
|
45
|
+
}),
|
|
46
|
+
header: Flags.string({
|
|
47
|
+
description: "HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)",
|
|
48
|
+
multiple: true,
|
|
49
|
+
}),
|
|
50
|
+
cwd: Flags.string({ description: "Working directory for stdio server" }),
|
|
51
|
+
timeout: Flags.integer({ description: "Connection timeout in milliseconds" }),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
async run(): Promise<void> {
|
|
55
|
+
if (this.argv.includes("--help") || this.argv.includes("-h")) {
|
|
56
|
+
this.printHelp();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const { args, flags } = await this.parse(MCP);
|
|
61
|
+
const action = (args.action ?? "list") as MCPAction;
|
|
62
|
+
const cmd: MCPCommandArgs = {
|
|
63
|
+
action,
|
|
64
|
+
name: args.name,
|
|
65
|
+
commandArgs: args.commandArgs,
|
|
66
|
+
flags: {
|
|
67
|
+
project: flags.project,
|
|
68
|
+
force: flags.force,
|
|
69
|
+
json: flags.json,
|
|
70
|
+
type: flags.type as MCPCommandArgs["flags"]["type"],
|
|
71
|
+
command: flags.command,
|
|
72
|
+
url: flags.url,
|
|
73
|
+
arg: flags.arg,
|
|
74
|
+
env: flags.env,
|
|
75
|
+
header: flags.header,
|
|
76
|
+
cwd: flags.cwd,
|
|
77
|
+
timeout: flags.timeout,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
await runMCPCommand(cmd);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
private printHelp(): void {
|
|
84
|
+
process.stdout.write(`Register standalone MCP servers explicitly in SKC config
|
|
85
|
+
|
|
86
|
+
USAGE
|
|
87
|
+
$ skc mcp [add|list|remove] [NAME] [COMMAND_OR_URL] [ARGS...] [FLAGS]
|
|
88
|
+
|
|
89
|
+
COMMANDS
|
|
90
|
+
add Add an explicit user-provided MCP server definition
|
|
91
|
+
list List registered servers with env/header/auth values redacted
|
|
92
|
+
remove Remove a registered server and print the removed definition redacted
|
|
93
|
+
|
|
94
|
+
FLAGS
|
|
95
|
+
--project Use project scope (./.skc/mcp.json) instead of user scope
|
|
96
|
+
--force Overwrite an existing server during add
|
|
97
|
+
-j, --json Emit machine-readable JSON with sensitive values redacted
|
|
98
|
+
--type=<value> stdio | http | sse (default: stdio, or http when --url is set)
|
|
99
|
+
--command=<value> Stdio server command for add
|
|
100
|
+
--url=<value> HTTP/SSE server URL for add
|
|
101
|
+
--arg=<value> Stdio server argument (repeatable)
|
|
102
|
+
--env=<value> Stdio env var as KEY=VALUE (repeatable; redacted in output)
|
|
103
|
+
--header=<value> HTTP/SSE header as KEY=VALUE (repeatable; redacted in output)
|
|
104
|
+
--cwd=<value> Working directory for stdio server
|
|
105
|
+
--timeout=<int> Connection timeout in milliseconds
|
|
106
|
+
|
|
107
|
+
EXAMPLES
|
|
108
|
+
$ skc mcp add context7 npx -y @upstash/context7-mcp
|
|
109
|
+
$ skc mcp add docs --type http --url https://example.test/mcp --header Authorization=Bearer_TOKEN
|
|
110
|
+
$ skc mcp list --json
|
|
111
|
+
$ skc mcp remove context7
|
|
112
|
+
|
|
113
|
+
SECURITY
|
|
114
|
+
This command writes only the server definition supplied on this invocation. It does not import or inherit Claude Code, Codex, OpenCode, or other live MCP configs. Public output redacts env, header, auth, and OAuth credential values.
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
}
|
package/src/commands/plugin.ts
CHANGED
|
@@ -49,6 +49,8 @@ export default class Plugin extends Command {
|
|
|
49
49
|
description: 'Install scope: "user" (default) or "project"',
|
|
50
50
|
options: ["user", "project"],
|
|
51
51
|
}),
|
|
52
|
+
user: Flags.boolean({ description: "Install SKC plugin bundle into the user root" }),
|
|
53
|
+
project: Flags.boolean({ description: "Install SKC plugin bundle into the project root" }),
|
|
52
54
|
};
|
|
53
55
|
|
|
54
56
|
async run(): Promise<void> {
|
|
@@ -69,6 +71,8 @@ export default class Plugin extends Command {
|
|
|
69
71
|
disable: flags.disable,
|
|
70
72
|
set: flags.set,
|
|
71
73
|
scope: flags.scope as "user" | "project" | undefined,
|
|
74
|
+
user: flags.user,
|
|
75
|
+
project: flags.project,
|
|
72
76
|
},
|
|
73
77
|
};
|
|
74
78
|
|