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.
Files changed (160) hide show
  1. package/.gitattributes +3 -0
  2. package/AGENTS.md +16 -0
  3. package/LICENSE +15 -0
  4. package/README.md +289 -0
  5. package/bin/pi.mjs +30 -0
  6. package/extensions/00-secrets/index.ts +1126 -0
  7. package/extensions/01-auth/auth.ts +401 -0
  8. package/extensions/01-auth/index.ts +289 -0
  9. package/extensions/auto-compact.ts +42 -0
  10. package/extensions/bootstrap/deps.ts +291 -0
  11. package/extensions/bootstrap/index.ts +811 -0
  12. package/extensions/chronos/chronos.sh +487 -0
  13. package/extensions/chronos/index.ts +148 -0
  14. package/extensions/cleave/assessment.ts +754 -0
  15. package/extensions/cleave/bridge.ts +31 -0
  16. package/extensions/cleave/conflicts.ts +250 -0
  17. package/extensions/cleave/dispatcher.ts +808 -0
  18. package/extensions/cleave/guardrails.ts +426 -0
  19. package/extensions/cleave/index.ts +3121 -0
  20. package/extensions/cleave/lifecycle-emitter.ts +20 -0
  21. package/extensions/cleave/openspec.ts +811 -0
  22. package/extensions/cleave/planner.ts +260 -0
  23. package/extensions/cleave/review.ts +579 -0
  24. package/extensions/cleave/skills.ts +355 -0
  25. package/extensions/cleave/types.ts +261 -0
  26. package/extensions/cleave/workspace.ts +861 -0
  27. package/extensions/cleave/worktree.ts +243 -0
  28. package/extensions/core-renderers.ts +253 -0
  29. package/extensions/dashboard/context-gauge.ts +58 -0
  30. package/extensions/dashboard/file-watch.ts +14 -0
  31. package/extensions/dashboard/footer.ts +1145 -0
  32. package/extensions/dashboard/git.ts +185 -0
  33. package/extensions/dashboard/index.ts +478 -0
  34. package/extensions/dashboard/memory-audit.ts +34 -0
  35. package/extensions/dashboard/overlay-data.ts +705 -0
  36. package/extensions/dashboard/overlay.ts +365 -0
  37. package/extensions/dashboard/render-utils.ts +54 -0
  38. package/extensions/dashboard/types.ts +191 -0
  39. package/extensions/dashboard/uri-helper.ts +45 -0
  40. package/extensions/debug.ts +69 -0
  41. package/extensions/defaults.ts +282 -0
  42. package/extensions/design-tree/dashboard-state.ts +161 -0
  43. package/extensions/design-tree/design-card.ts +362 -0
  44. package/extensions/design-tree/index.ts +2130 -0
  45. package/extensions/design-tree/lifecycle-emitter.ts +41 -0
  46. package/extensions/design-tree/tree.ts +1607 -0
  47. package/extensions/design-tree/types.ts +163 -0
  48. package/extensions/distill.ts +127 -0
  49. package/extensions/effort/index.ts +395 -0
  50. package/extensions/effort/tiers.ts +146 -0
  51. package/extensions/effort/types.ts +105 -0
  52. package/extensions/lib/git-state.ts +227 -0
  53. package/extensions/lib/local-models.ts +157 -0
  54. package/extensions/lib/model-preferences.ts +51 -0
  55. package/extensions/lib/model-routing.ts +720 -0
  56. package/extensions/lib/operator-fallback.ts +205 -0
  57. package/extensions/lib/operator-profile.ts +360 -0
  58. package/extensions/lib/slash-command-bridge.ts +253 -0
  59. package/extensions/lib/typebox-helpers.ts +16 -0
  60. package/extensions/local-inference/index.ts +727 -0
  61. package/extensions/mcp-bridge/README.md +220 -0
  62. package/extensions/mcp-bridge/index.ts +951 -0
  63. package/extensions/mcp-bridge/lib.ts +365 -0
  64. package/extensions/mcp-bridge/mcp.json +3 -0
  65. package/extensions/mcp-bridge/package.json +11 -0
  66. package/extensions/model-budget.ts +752 -0
  67. package/extensions/offline-driver.ts +403 -0
  68. package/extensions/openspec/archive-gate.ts +164 -0
  69. package/extensions/openspec/branch-cleanup.ts +64 -0
  70. package/extensions/openspec/dashboard-state.ts +50 -0
  71. package/extensions/openspec/index.ts +1917 -0
  72. package/extensions/openspec/lifecycle-emitter.ts +65 -0
  73. package/extensions/openspec/lifecycle-files.ts +70 -0
  74. package/extensions/openspec/lifecycle.ts +50 -0
  75. package/extensions/openspec/reconcile.ts +187 -0
  76. package/extensions/openspec/spec.ts +1385 -0
  77. package/extensions/openspec/types.ts +98 -0
  78. package/extensions/project-memory/DESIGN-global-mind.md +198 -0
  79. package/extensions/project-memory/README.md +202 -0
  80. package/extensions/project-memory/api-types.ts +382 -0
  81. package/extensions/project-memory/compaction-policy.ts +29 -0
  82. package/extensions/project-memory/core.ts +164 -0
  83. package/extensions/project-memory/embeddings.ts +230 -0
  84. package/extensions/project-memory/extraction-v2.ts +861 -0
  85. package/extensions/project-memory/factstore.ts +2177 -0
  86. package/extensions/project-memory/index.ts +3459 -0
  87. package/extensions/project-memory/injection-metrics.ts +91 -0
  88. package/extensions/project-memory/jsonl-io.ts +12 -0
  89. package/extensions/project-memory/lifecycle.ts +331 -0
  90. package/extensions/project-memory/migration.ts +293 -0
  91. package/extensions/project-memory/package.json +9 -0
  92. package/extensions/project-memory/sci-renderers.ts +7 -0
  93. package/extensions/project-memory/template.ts +103 -0
  94. package/extensions/project-memory/triggers.ts +52 -0
  95. package/extensions/project-memory/types.ts +102 -0
  96. package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
  97. package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
  98. package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
  99. package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
  100. package/extensions/render/composition/package-lock.json +534 -0
  101. package/extensions/render/composition/package.json +22 -0
  102. package/extensions/render/composition/render.mjs +246 -0
  103. package/extensions/render/composition/test-comp.tsx +87 -0
  104. package/extensions/render/composition/types.ts +24 -0
  105. package/extensions/render/excalidraw/UPSTREAM.md +81 -0
  106. package/extensions/render/excalidraw/elements.ts +764 -0
  107. package/extensions/render/excalidraw/index.ts +66 -0
  108. package/extensions/render/excalidraw/types.ts +223 -0
  109. package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
  110. package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
  111. package/extensions/render/excalidraw-renderer/render_template.html +59 -0
  112. package/extensions/render/index.ts +830 -0
  113. package/extensions/render/native-diagrams/index.ts +57 -0
  114. package/extensions/render/native-diagrams/motifs.ts +542 -0
  115. package/extensions/render/native-diagrams/raster.ts +8 -0
  116. package/extensions/render/native-diagrams/scene.ts +75 -0
  117. package/extensions/render/native-diagrams/spec.ts +204 -0
  118. package/extensions/render/native-diagrams/svg.ts +116 -0
  119. package/extensions/sci-ui.ts +304 -0
  120. package/extensions/session-log.ts +174 -0
  121. package/extensions/shared-state.ts +146 -0
  122. package/extensions/spinner-verbs.ts +91 -0
  123. package/extensions/style.ts +281 -0
  124. package/extensions/terminal-title.ts +191 -0
  125. package/extensions/tool-profile/index.ts +291 -0
  126. package/extensions/tool-profile/profiles.ts +290 -0
  127. package/extensions/types.d.ts +9 -0
  128. package/extensions/vault/index.ts +185 -0
  129. package/extensions/version-check.ts +90 -0
  130. package/extensions/view/index.ts +859 -0
  131. package/extensions/view/uri-resolver.ts +148 -0
  132. package/extensions/web-search/index.ts +182 -0
  133. package/extensions/web-search/providers.ts +121 -0
  134. package/extensions/web-ui/index.ts +110 -0
  135. package/extensions/web-ui/server.ts +265 -0
  136. package/extensions/web-ui/state.ts +462 -0
  137. package/extensions/web-ui/static/index.html +145 -0
  138. package/extensions/web-ui/types.ts +284 -0
  139. package/package.json +76 -0
  140. package/prompts/init.md +75 -0
  141. package/prompts/new-repo.md +54 -0
  142. package/prompts/oci-login.md +56 -0
  143. package/prompts/status.md +50 -0
  144. package/settings.json +4 -0
  145. package/skills/cleave/SKILL.md +218 -0
  146. package/skills/git/SKILL.md +209 -0
  147. package/skills/git/_reference/ci-validation.md +204 -0
  148. package/skills/oci/SKILL.md +338 -0
  149. package/skills/openspec/SKILL.md +346 -0
  150. package/skills/pi-extensions/SKILL.md +191 -0
  151. package/skills/pi-tui/SKILL.md +517 -0
  152. package/skills/python/SKILL.md +189 -0
  153. package/skills/rust/SKILL.md +268 -0
  154. package/skills/security/SKILL.md +206 -0
  155. package/skills/style/SKILL.md +264 -0
  156. package/skills/typescript/SKILL.md +225 -0
  157. package/skills/vault/SKILL.md +102 -0
  158. package/themes/alpharius-legacy.json +85 -0
  159. package/themes/alpharius.conf +59 -0
  160. 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,3 @@
1
+ {
2
+ "servers": {}
3
+ }
@@ -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
+ }