omegon 0.6.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/.gitattributes +3 -0
- package/AGENTS.md +16 -0
- package/LICENSE +15 -0
- package/README.md +289 -0
- package/bin/pi.mjs +30 -0
- package/extensions/00-secrets/index.ts +1126 -0
- package/extensions/01-auth/auth.ts +401 -0
- package/extensions/01-auth/index.ts +289 -0
- package/extensions/auto-compact.ts +42 -0
- package/extensions/bootstrap/deps.ts +291 -0
- package/extensions/bootstrap/index.ts +811 -0
- package/extensions/chronos/chronos.sh +487 -0
- package/extensions/chronos/index.ts +148 -0
- package/extensions/cleave/assessment.ts +754 -0
- package/extensions/cleave/bridge.ts +31 -0
- package/extensions/cleave/conflicts.ts +250 -0
- package/extensions/cleave/dispatcher.ts +808 -0
- package/extensions/cleave/guardrails.ts +426 -0
- package/extensions/cleave/index.ts +3121 -0
- package/extensions/cleave/lifecycle-emitter.ts +20 -0
- package/extensions/cleave/openspec.ts +811 -0
- package/extensions/cleave/planner.ts +260 -0
- package/extensions/cleave/review.ts +579 -0
- package/extensions/cleave/skills.ts +355 -0
- package/extensions/cleave/types.ts +261 -0
- package/extensions/cleave/workspace.ts +861 -0
- package/extensions/cleave/worktree.ts +243 -0
- package/extensions/core-renderers.ts +253 -0
- package/extensions/dashboard/context-gauge.ts +58 -0
- package/extensions/dashboard/file-watch.ts +14 -0
- package/extensions/dashboard/footer.ts +1145 -0
- package/extensions/dashboard/git.ts +185 -0
- package/extensions/dashboard/index.ts +478 -0
- package/extensions/dashboard/memory-audit.ts +34 -0
- package/extensions/dashboard/overlay-data.ts +705 -0
- package/extensions/dashboard/overlay.ts +365 -0
- package/extensions/dashboard/render-utils.ts +54 -0
- package/extensions/dashboard/types.ts +191 -0
- package/extensions/dashboard/uri-helper.ts +45 -0
- package/extensions/debug.ts +69 -0
- package/extensions/defaults.ts +282 -0
- package/extensions/design-tree/dashboard-state.ts +161 -0
- package/extensions/design-tree/design-card.ts +362 -0
- package/extensions/design-tree/index.ts +2130 -0
- package/extensions/design-tree/lifecycle-emitter.ts +41 -0
- package/extensions/design-tree/tree.ts +1607 -0
- package/extensions/design-tree/types.ts +163 -0
- package/extensions/distill.ts +127 -0
- package/extensions/effort/index.ts +395 -0
- package/extensions/effort/tiers.ts +146 -0
- package/extensions/effort/types.ts +105 -0
- package/extensions/lib/git-state.ts +227 -0
- package/extensions/lib/local-models.ts +157 -0
- package/extensions/lib/model-preferences.ts +51 -0
- package/extensions/lib/model-routing.ts +720 -0
- package/extensions/lib/operator-fallback.ts +205 -0
- package/extensions/lib/operator-profile.ts +360 -0
- package/extensions/lib/slash-command-bridge.ts +253 -0
- package/extensions/lib/typebox-helpers.ts +16 -0
- package/extensions/local-inference/index.ts +727 -0
- package/extensions/mcp-bridge/README.md +220 -0
- package/extensions/mcp-bridge/index.ts +951 -0
- package/extensions/mcp-bridge/lib.ts +365 -0
- package/extensions/mcp-bridge/mcp.json +3 -0
- package/extensions/mcp-bridge/package.json +11 -0
- package/extensions/model-budget.ts +752 -0
- package/extensions/offline-driver.ts +403 -0
- package/extensions/openspec/archive-gate.ts +164 -0
- package/extensions/openspec/branch-cleanup.ts +64 -0
- package/extensions/openspec/dashboard-state.ts +50 -0
- package/extensions/openspec/index.ts +1917 -0
- package/extensions/openspec/lifecycle-emitter.ts +65 -0
- package/extensions/openspec/lifecycle-files.ts +70 -0
- package/extensions/openspec/lifecycle.ts +50 -0
- package/extensions/openspec/reconcile.ts +187 -0
- package/extensions/openspec/spec.ts +1385 -0
- package/extensions/openspec/types.ts +98 -0
- package/extensions/project-memory/DESIGN-global-mind.md +198 -0
- package/extensions/project-memory/README.md +202 -0
- package/extensions/project-memory/api-types.ts +382 -0
- package/extensions/project-memory/compaction-policy.ts +29 -0
- package/extensions/project-memory/core.ts +164 -0
- package/extensions/project-memory/embeddings.ts +230 -0
- package/extensions/project-memory/extraction-v2.ts +861 -0
- package/extensions/project-memory/factstore.ts +2177 -0
- package/extensions/project-memory/index.ts +3459 -0
- package/extensions/project-memory/injection-metrics.ts +91 -0
- package/extensions/project-memory/jsonl-io.ts +12 -0
- package/extensions/project-memory/lifecycle.ts +331 -0
- package/extensions/project-memory/migration.ts +293 -0
- package/extensions/project-memory/package.json +9 -0
- package/extensions/project-memory/sci-renderers.ts +7 -0
- package/extensions/project-memory/template.ts +103 -0
- package/extensions/project-memory/triggers.ts +52 -0
- package/extensions/project-memory/types.ts +102 -0
- package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
- package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
- package/extensions/render/composition/package-lock.json +534 -0
- package/extensions/render/composition/package.json +22 -0
- package/extensions/render/composition/render.mjs +246 -0
- package/extensions/render/composition/test-comp.tsx +87 -0
- package/extensions/render/composition/types.ts +24 -0
- package/extensions/render/excalidraw/UPSTREAM.md +81 -0
- package/extensions/render/excalidraw/elements.ts +764 -0
- package/extensions/render/excalidraw/index.ts +66 -0
- package/extensions/render/excalidraw/types.ts +223 -0
- package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
- package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
- package/extensions/render/excalidraw-renderer/render_template.html +59 -0
- package/extensions/render/index.ts +830 -0
- package/extensions/render/native-diagrams/index.ts +57 -0
- package/extensions/render/native-diagrams/motifs.ts +542 -0
- package/extensions/render/native-diagrams/raster.ts +8 -0
- package/extensions/render/native-diagrams/scene.ts +75 -0
- package/extensions/render/native-diagrams/spec.ts +204 -0
- package/extensions/render/native-diagrams/svg.ts +116 -0
- package/extensions/sci-ui.ts +304 -0
- package/extensions/session-log.ts +174 -0
- package/extensions/shared-state.ts +146 -0
- package/extensions/spinner-verbs.ts +91 -0
- package/extensions/style.ts +281 -0
- package/extensions/terminal-title.ts +191 -0
- package/extensions/tool-profile/index.ts +291 -0
- package/extensions/tool-profile/profiles.ts +290 -0
- package/extensions/types.d.ts +9 -0
- package/extensions/vault/index.ts +185 -0
- package/extensions/version-check.ts +90 -0
- package/extensions/view/index.ts +859 -0
- package/extensions/view/uri-resolver.ts +148 -0
- package/extensions/web-search/index.ts +182 -0
- package/extensions/web-search/providers.ts +121 -0
- package/extensions/web-ui/index.ts +110 -0
- package/extensions/web-ui/server.ts +265 -0
- package/extensions/web-ui/state.ts +462 -0
- package/extensions/web-ui/static/index.html +145 -0
- package/extensions/web-ui/types.ts +284 -0
- package/package.json +76 -0
- package/prompts/init.md +75 -0
- package/prompts/new-repo.md +54 -0
- package/prompts/oci-login.md +56 -0
- package/prompts/status.md +50 -0
- package/settings.json +4 -0
- package/skills/cleave/SKILL.md +218 -0
- package/skills/git/SKILL.md +209 -0
- package/skills/git/_reference/ci-validation.md +204 -0
- package/skills/oci/SKILL.md +338 -0
- package/skills/openspec/SKILL.md +346 -0
- package/skills/pi-extensions/SKILL.md +191 -0
- package/skills/pi-tui/SKILL.md +517 -0
- package/skills/python/SKILL.md +189 -0
- package/skills/rust/SKILL.md +268 -0
- package/skills/security/SKILL.md +206 -0
- package/skills/style/SKILL.md +264 -0
- package/skills/typescript/SKILL.md +225 -0
- package/skills/vault/SKILL.md +102 -0
- package/themes/alpharius-legacy.json +85 -0
- package/themes/alpharius.conf +59 -0
- package/themes/alpharius.json +88 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions extracted for testability.
|
|
3
|
+
* The main index.ts imports from here; tests import directly.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Config discrimination
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
|
|
13
|
+
export interface StdioServerConfig {
|
|
14
|
+
command: string;
|
|
15
|
+
args?: string[];
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HttpServerConfig {
|
|
20
|
+
url: string;
|
|
21
|
+
headers?: Record<string, string>;
|
|
22
|
+
timeout?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type ServerConfig = StdioServerConfig | HttpServerConfig;
|
|
26
|
+
|
|
27
|
+
export function isHttpConfig(config: ServerConfig): config is HttpServerConfig {
|
|
28
|
+
return "url" in config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
// Config validation
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
export interface ConfigError {
|
|
36
|
+
server: string;
|
|
37
|
+
message: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Validate an mcp.json config object. Returns an array of errors (empty = valid).
|
|
42
|
+
* Does not throw — caller decides how to surface problems.
|
|
43
|
+
*/
|
|
44
|
+
export function validateConfig(
|
|
45
|
+
raw: any
|
|
46
|
+
): { servers: Record<string, ServerConfig>; errors: ConfigError[] } {
|
|
47
|
+
const errors: ConfigError[] = [];
|
|
48
|
+
const servers: Record<string, ServerConfig> = {};
|
|
49
|
+
|
|
50
|
+
if (!raw || typeof raw !== "object" || !raw.servers || typeof raw.servers !== "object") {
|
|
51
|
+
return { servers, errors: [{ server: "(root)", message: "missing or invalid 'servers' object" }] };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
for (const [name, config] of Object.entries(raw.servers) as [string, any][]) {
|
|
55
|
+
if (!config || typeof config !== "object") {
|
|
56
|
+
errors.push({ server: name, message: "server config must be an object" });
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const hasUrl = typeof config.url === "string" && config.url.length > 0;
|
|
61
|
+
const hasCommand = typeof config.command === "string" && config.command.length > 0;
|
|
62
|
+
|
|
63
|
+
if (!hasUrl && !hasCommand) {
|
|
64
|
+
errors.push({ server: name, message: "must have either 'url' (HTTP) or 'command' (stdio)" });
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (hasUrl && hasCommand) {
|
|
69
|
+
errors.push({ server: name, message: "has both 'url' and 'command' — pick one transport" });
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (hasUrl) {
|
|
74
|
+
try {
|
|
75
|
+
new URL(config.url);
|
|
76
|
+
} catch {
|
|
77
|
+
errors.push({ server: name, message: `invalid url: ${config.url}` });
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (config.headers && typeof config.headers !== "object") {
|
|
81
|
+
errors.push({ server: name, message: "'headers' must be an object" });
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (config.timeout !== undefined && (typeof config.timeout !== "number" || config.timeout <= 0)) {
|
|
85
|
+
errors.push({ server: name, message: "'timeout' must be a positive number" });
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (hasCommand) {
|
|
91
|
+
if (config.args !== undefined && !Array.isArray(config.args)) {
|
|
92
|
+
errors.push({ server: name, message: "'args' must be an array" });
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (config.env !== undefined && typeof config.env !== "object") {
|
|
96
|
+
errors.push({ server: name, message: "'env' must be an object" });
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
servers[name] = config as ServerConfig;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return { servers, errors };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
// Env var resolution
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
|
|
111
|
+
export function resolveEnvVars(
|
|
112
|
+
value: string,
|
|
113
|
+
env: Record<string, string | undefined> = process.env
|
|
114
|
+
): string {
|
|
115
|
+
return value.replace(/\$\{(\w+)\}/g, (_, key) => env[key] ?? "");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function resolveEnvObj(
|
|
119
|
+
obj: Record<string, string>,
|
|
120
|
+
env: Record<string, string | undefined> = process.env
|
|
121
|
+
): Record<string, string> {
|
|
122
|
+
const resolved: Record<string, string> = {};
|
|
123
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
124
|
+
resolved[k] = resolveEnvVars(v, env);
|
|
125
|
+
}
|
|
126
|
+
return resolved;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Auth error detection
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
export const AUTH_REMEDIATION =
|
|
134
|
+
"Your GitHub token may be expired or invalid.\n" +
|
|
135
|
+
"Run `gh auth login` to re-authenticate, then restart your pi session.";
|
|
136
|
+
|
|
137
|
+
export function isAuthError(err: any): boolean {
|
|
138
|
+
if (err?.code === 401 || err?.code === 403) return true;
|
|
139
|
+
const msg = err?.message ?? "";
|
|
140
|
+
if (/HTTP\s+40[13]\b/.test(msg)) return true;
|
|
141
|
+
if (/unauthorized|forbidden|invalid.*token|expired.*token|token.*expired/i.test(msg)) return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
// Transport error detection
|
|
147
|
+
// ---------------------------------------------------------------------------
|
|
148
|
+
|
|
149
|
+
export function isTransportError(err: any): boolean {
|
|
150
|
+
const msg = err?.message ?? "";
|
|
151
|
+
return (
|
|
152
|
+
msg.includes("not connected") ||
|
|
153
|
+
msg.includes("aborted") ||
|
|
154
|
+
msg.includes("ECONNREFUSED") ||
|
|
155
|
+
msg.includes("fetch failed") ||
|
|
156
|
+
msg.includes("network") ||
|
|
157
|
+
err?.code === "ECONNRESET"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ---------------------------------------------------------------------------
|
|
162
|
+
// Response text extraction
|
|
163
|
+
// ---------------------------------------------------------------------------
|
|
164
|
+
|
|
165
|
+
export function extractText(result: any): string {
|
|
166
|
+
const content = Array.isArray(result?.content) ? result.content : [];
|
|
167
|
+
return content
|
|
168
|
+
.filter((c: any) => c.type === "text")
|
|
169
|
+
.map((c: any) => c.text)
|
|
170
|
+
.join("\n") || "(empty response)";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// Layered config loading
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
|
|
177
|
+
/** Where a server config was found */
|
|
178
|
+
export type ConfigSource = "project" | "user" | "bundled";
|
|
179
|
+
|
|
180
|
+
export interface SourcedConfig {
|
|
181
|
+
servers: Record<string, ServerConfig>;
|
|
182
|
+
sources: Record<string, ConfigSource>;
|
|
183
|
+
errors: ConfigError[];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Load a single mcp.json file. Returns validated servers or empty on failure.
|
|
188
|
+
*/
|
|
189
|
+
function loadConfigFile(filePath: string): {
|
|
190
|
+
servers: Record<string, ServerConfig>;
|
|
191
|
+
errors: ConfigError[];
|
|
192
|
+
} {
|
|
193
|
+
if (!existsSync(filePath)) return { servers: {}, errors: [] };
|
|
194
|
+
try {
|
|
195
|
+
const raw = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
196
|
+
return validateConfig(raw);
|
|
197
|
+
} catch (err: any) {
|
|
198
|
+
return {
|
|
199
|
+
servers: {},
|
|
200
|
+
errors: [{ server: "(config)", message: `invalid JSON in ${filePath}: ${err.message}` }],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Merge configs from project, user, and bundled sources.
|
|
207
|
+
* Higher-priority sources (project > user > bundled) win on name collision.
|
|
208
|
+
* All errors from all files are aggregated.
|
|
209
|
+
*/
|
|
210
|
+
export function loadMergedConfig(
|
|
211
|
+
projectDir: string | null,
|
|
212
|
+
userDir: string,
|
|
213
|
+
extensionDir: string,
|
|
214
|
+
): SourcedConfig {
|
|
215
|
+
const layers: Array<{ source: ConfigSource; path: string }> = [
|
|
216
|
+
{ source: "bundled", path: join(extensionDir, "mcp.json") },
|
|
217
|
+
{ source: "user", path: join(userDir, "mcp.json") },
|
|
218
|
+
];
|
|
219
|
+
if (projectDir) {
|
|
220
|
+
layers.push({ source: "project", path: join(projectDir, ".pi", "mcp.json") });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const merged: Record<string, ServerConfig> = {};
|
|
224
|
+
const sources: Record<string, ConfigSource> = {};
|
|
225
|
+
const allErrors: ConfigError[] = [];
|
|
226
|
+
|
|
227
|
+
for (const layer of layers) {
|
|
228
|
+
const { servers, errors } = loadConfigFile(layer.path);
|
|
229
|
+
for (const err of errors) {
|
|
230
|
+
allErrors.push({ server: err.server, message: `[${layer.source}] ${err.message}` });
|
|
231
|
+
}
|
|
232
|
+
// Higher-priority layers overwrite lower ones
|
|
233
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
234
|
+
merged[name] = config;
|
|
235
|
+
sources[name] = layer.source;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return { servers: merged, sources, errors: allErrors };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Find which config file a server should be written to / removed from.
|
|
244
|
+
*/
|
|
245
|
+
export function configFileForScope(
|
|
246
|
+
scope: "project" | "user",
|
|
247
|
+
projectDir: string | null,
|
|
248
|
+
userDir: string,
|
|
249
|
+
): string | null {
|
|
250
|
+
if (scope === "project") {
|
|
251
|
+
if (!projectDir) return null;
|
|
252
|
+
return join(projectDir, ".pi", "mcp.json");
|
|
253
|
+
}
|
|
254
|
+
return join(userDir, "mcp.json");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
// URL / name helpers
|
|
259
|
+
// ---------------------------------------------------------------------------
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Derive a slug name from a URL hostname.
|
|
263
|
+
* "https://scribe.recrocog.com/mcp/transport/" → "scribe"
|
|
264
|
+
* "https://api.example.com/mcp/" → "api-example"
|
|
265
|
+
*/
|
|
266
|
+
export function slugifyUrl(url: string): string {
|
|
267
|
+
try {
|
|
268
|
+
const hostname = new URL(url).hostname;
|
|
269
|
+
const parts = hostname.split(".");
|
|
270
|
+
// Drop common TLDs and "com", "io", etc.
|
|
271
|
+
const significant = parts.filter(
|
|
272
|
+
(p) => !["com", "io", "org", "net", "dev", "app", "co", "www"].includes(p)
|
|
273
|
+
);
|
|
274
|
+
if (significant.length === 0) return parts[0] || "server";
|
|
275
|
+
// If subdomain is something meaningful (not "api", "mcp"), prefer it
|
|
276
|
+
if (significant.length >= 2 && !["api", "mcp"].includes(significant[0])) {
|
|
277
|
+
return significant[0];
|
|
278
|
+
}
|
|
279
|
+
return significant.join("-");
|
|
280
|
+
} catch {
|
|
281
|
+
return "server";
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Build an HttpServerConfig from user inputs.
|
|
287
|
+
*/
|
|
288
|
+
export function buildHttpConfig(
|
|
289
|
+
url: string,
|
|
290
|
+
authType: "bearer" | "api-key" | "none",
|
|
291
|
+
secretName?: string,
|
|
292
|
+
headerName?: string,
|
|
293
|
+
): HttpServerConfig {
|
|
294
|
+
const config: HttpServerConfig = { url };
|
|
295
|
+
|
|
296
|
+
if (authType === "bearer" && secretName) {
|
|
297
|
+
config.headers = { Authorization: `Bearer \${${secretName}}` };
|
|
298
|
+
} else if (authType === "api-key" && secretName && headerName) {
|
|
299
|
+
config.headers = { [headerName]: `\${${secretName}}` };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
return config;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Build a StdioServerConfig from user inputs.
|
|
307
|
+
*/
|
|
308
|
+
export function buildStdioConfig(
|
|
309
|
+
command: string,
|
|
310
|
+
args?: string[],
|
|
311
|
+
env?: Record<string, string>,
|
|
312
|
+
): StdioServerConfig {
|
|
313
|
+
const config: StdioServerConfig = { command };
|
|
314
|
+
if (args && args.length > 0) config.args = args;
|
|
315
|
+
if (env && Object.keys(env).length > 0) config.env = env;
|
|
316
|
+
return config;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Parse a command string into command + args.
|
|
321
|
+
* "npx -y @example/mcp-server --port 3000" → { command: "npx", args: ["-y", "@example/mcp-server", "--port", "3000"] }
|
|
322
|
+
*/
|
|
323
|
+
export function parseCommand(input: string): { command: string; args: string[] } {
|
|
324
|
+
// Simple shell-word splitting (doesn't handle quotes, but good enough for typical cases)
|
|
325
|
+
const parts = input.trim().split(/\s+/).filter(Boolean);
|
|
326
|
+
return {
|
|
327
|
+
command: parts[0] || "",
|
|
328
|
+
args: parts.slice(1),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Extract secret names referenced in a server config via ${VAR} patterns.
|
|
334
|
+
*/
|
|
335
|
+
export function extractSecretRefs(config: ServerConfig): string[] {
|
|
336
|
+
const refs: string[] = [];
|
|
337
|
+
const pattern = /\$\{(\w+)\}/g;
|
|
338
|
+
|
|
339
|
+
if (isHttpConfig(config)) {
|
|
340
|
+
if (config.headers) {
|
|
341
|
+
for (const value of Object.values(config.headers)) {
|
|
342
|
+
let match;
|
|
343
|
+
while ((match = pattern.exec(value)) !== null) {
|
|
344
|
+
refs.push(match[1]);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// Check URL too (unlikely but possible)
|
|
349
|
+
let match;
|
|
350
|
+
while ((match = pattern.exec(config.url)) !== null) {
|
|
351
|
+
refs.push(match[1]);
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
if (config.env) {
|
|
355
|
+
for (const value of Object.values(config.env)) {
|
|
356
|
+
let match;
|
|
357
|
+
while ((match = pattern.exec(value)) !== null) {
|
|
358
|
+
refs.push(match[1]);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return [...new Set(refs)];
|
|
365
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-bridge",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "MCP server bridge for pi — exposes MCP tools (stdio + Streamable HTTP) as native pi tools",
|
|
5
|
+
"dependencies": {
|
|
6
|
+
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
7
|
+
},
|
|
8
|
+
"pi": {
|
|
9
|
+
"extensions": ["./index.ts"]
|
|
10
|
+
}
|
|
11
|
+
}
|