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,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Profile definitions and project detection logic.
|
|
3
|
+
*
|
|
4
|
+
* A profile is a named group of tools that should be active together.
|
|
5
|
+
* Detection functions scan the cwd for project signals to determine
|
|
6
|
+
* which profiles apply.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { execSync } from "node:child_process";
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
|
|
14
|
+
// ── Profile Definitions ─────────────────────────────────────────
|
|
15
|
+
|
|
16
|
+
export interface Profile {
|
|
17
|
+
/** Profile identifier */
|
|
18
|
+
id: string;
|
|
19
|
+
/** Human-readable label */
|
|
20
|
+
label: string;
|
|
21
|
+
/** Description shown in /profile list */
|
|
22
|
+
description: string;
|
|
23
|
+
/** Tool name patterns to include. Exact match or glob-like prefix (e.g. "mcp_scribe_*") */
|
|
24
|
+
tools: string[];
|
|
25
|
+
/** Detection function: returns true if this profile should be auto-activated */
|
|
26
|
+
detect: (cwd: string) => boolean;
|
|
27
|
+
/** If true, always included regardless of detection */
|
|
28
|
+
alwaysOn?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Check if a command exists on PATH */
|
|
32
|
+
function hasCmd(name: string): boolean {
|
|
33
|
+
try {
|
|
34
|
+
execSync(`command -v ${name}`, { stdio: "ignore" });
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function fileExists(cwd: string, ...paths: string[]): boolean {
|
|
42
|
+
return paths.some((p) => existsSync(join(cwd, p)));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Check if any file with the given extension exists in common content directories (shallow). */
|
|
46
|
+
function dirHasExt(cwd: string, ext: string): boolean {
|
|
47
|
+
// Only check top-level and common content dirs — never walk node_modules/.git/etc.
|
|
48
|
+
const SCAN_DIRS = [".", "docs", "assets", "images", "diagrams", "design", "src", "lib"];
|
|
49
|
+
for (const dir of SCAN_DIRS) {
|
|
50
|
+
try {
|
|
51
|
+
const fullDir = join(cwd, dir);
|
|
52
|
+
const entries = readdirSync(fullDir);
|
|
53
|
+
if (entries.some((e) => e.endsWith(ext))) return true;
|
|
54
|
+
} catch {
|
|
55
|
+
// Directory doesn't exist, skip
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readJsonField(cwd: string, file: string, field: string): unknown {
|
|
62
|
+
try {
|
|
63
|
+
const raw = readFileSync(join(cwd, file), "utf8");
|
|
64
|
+
const json = JSON.parse(raw);
|
|
65
|
+
return json[field];
|
|
66
|
+
} catch {
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export const PROFILES: Profile[] = [
|
|
72
|
+
{
|
|
73
|
+
id: "core",
|
|
74
|
+
label: "Core",
|
|
75
|
+
description: "Essential tools: built-in file/shell ops, memory, chronos, auth, model control",
|
|
76
|
+
alwaysOn: true,
|
|
77
|
+
tools: [
|
|
78
|
+
// pi built-in tools (lowercase — these are pi's native tools, not Claude Code's
|
|
79
|
+
// PascalCase variants). Must be included or setActiveTools() deactivates them.
|
|
80
|
+
"read", "write", "edit", "bash",
|
|
81
|
+
// Memory
|
|
82
|
+
"memory_query", "memory_recall", "memory_episodes", "memory_focus",
|
|
83
|
+
"memory_release", "memory_store", "memory_supersede", "memory_search_archive",
|
|
84
|
+
"memory_connect", "memory_archive", "memory_compact",
|
|
85
|
+
// Utilities
|
|
86
|
+
"chronos", "whoami",
|
|
87
|
+
// Model control
|
|
88
|
+
"set_model_tier", "set_thinking_level", "switch_to_offline_driver",
|
|
89
|
+
// Tool management
|
|
90
|
+
"manage_tools",
|
|
91
|
+
],
|
|
92
|
+
detect: () => true,
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "coding",
|
|
96
|
+
label: "Coding",
|
|
97
|
+
description: "Cleave decomposition, OpenSpec, design tree",
|
|
98
|
+
tools: [
|
|
99
|
+
"cleave_assess", "cleave_run",
|
|
100
|
+
"openspec_manage",
|
|
101
|
+
"design_tree", "design_tree_update",
|
|
102
|
+
],
|
|
103
|
+
detect: (cwd) => fileExists(cwd, ".git"),
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
id: "visual",
|
|
107
|
+
label: "Visual",
|
|
108
|
+
description: "Image generation, D2 diagrams, Excalidraw rendering",
|
|
109
|
+
tools: [
|
|
110
|
+
"generate_image_local", "render_diagram", "render_excalidraw",
|
|
111
|
+
],
|
|
112
|
+
detect: (cwd) =>
|
|
113
|
+
fileExists(cwd, "images") ||
|
|
114
|
+
dirHasExt(cwd, ".excalidraw") ||
|
|
115
|
+
dirHasExt(cwd, ".d2"),
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "local-ai",
|
|
119
|
+
label: "Local AI",
|
|
120
|
+
description: "Ollama local inference (ask_local_model, manage_ollama)",
|
|
121
|
+
tools: [
|
|
122
|
+
"ask_local_model", "list_local_models", "manage_ollama",
|
|
123
|
+
],
|
|
124
|
+
detect: () => hasCmd("ollama"),
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
id: "web",
|
|
128
|
+
label: "Web & View",
|
|
129
|
+
description: "Web search and file viewing",
|
|
130
|
+
alwaysOn: true,
|
|
131
|
+
tools: ["web_search", "view"],
|
|
132
|
+
detect: () => true,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
id: "scribe",
|
|
136
|
+
label: "Scribe",
|
|
137
|
+
description: "Partnership tracking via MCP bridge (mcp_scribe_*)",
|
|
138
|
+
tools: ["mcp_scribe_*"],
|
|
139
|
+
detect: (cwd) => {
|
|
140
|
+
// Detect if scribe MCP is configured — check for .pi/mcp.json or similar
|
|
141
|
+
return fileExists(cwd, ".pi/mcp.json") ||
|
|
142
|
+
existsSync(join(homedir(), ".pi", "mcp.json"));
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
id: "pi-dev",
|
|
147
|
+
label: "Pi Development",
|
|
148
|
+
description: "All tools enabled — for working on Omegon itself",
|
|
149
|
+
tools: ["*"],
|
|
150
|
+
detect: (cwd) => {
|
|
151
|
+
const piExts = readJsonField(cwd, "package.json", "pi") as { extensions?: string[] } | undefined;
|
|
152
|
+
return !!piExts?.extensions;
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
];
|
|
156
|
+
|
|
157
|
+
// ── Profile Config (persisted) ──────────────────────────────────
|
|
158
|
+
|
|
159
|
+
export interface ProfileConfig {
|
|
160
|
+
/** Profiles to force-include regardless of detection */
|
|
161
|
+
include?: string[];
|
|
162
|
+
/** Profiles to force-exclude regardless of detection */
|
|
163
|
+
exclude?: string[];
|
|
164
|
+
/** Individual tool overrides */
|
|
165
|
+
tools?: {
|
|
166
|
+
enable?: string[];
|
|
167
|
+
disable?: string[];
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function loadProfileConfig(cwd: string): ProfileConfig {
|
|
172
|
+
const configPath = join(cwd, ".pi", "profile.json");
|
|
173
|
+
try {
|
|
174
|
+
return JSON.parse(readFileSync(configPath, "utf8")) as ProfileConfig;
|
|
175
|
+
} catch {
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── Detection & Merge ───────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/** Detect which profiles should be active for the given cwd */
|
|
183
|
+
export function detectProfiles(cwd: string): string[] {
|
|
184
|
+
return PROFILES
|
|
185
|
+
.filter((p) => p.alwaysOn || p.detect(cwd))
|
|
186
|
+
.map((p) => p.id);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Match a tool name against a pattern (exact or wildcard suffix) */
|
|
190
|
+
export function matchTool(toolName: string, pattern: string): boolean {
|
|
191
|
+
if (pattern === "*") return true;
|
|
192
|
+
if (pattern.endsWith("*")) {
|
|
193
|
+
return toolName.startsWith(pattern.slice(0, -1));
|
|
194
|
+
}
|
|
195
|
+
return toolName === pattern;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Given detected profiles + config overrides, compute the active tool set */
|
|
199
|
+
export function resolveActiveTools(
|
|
200
|
+
allToolNames: string[],
|
|
201
|
+
detectedProfileIds: string[],
|
|
202
|
+
config: ProfileConfig,
|
|
203
|
+
): string[] {
|
|
204
|
+
// Apply include/exclude overrides to detected profiles
|
|
205
|
+
let activeProfileIds = new Set(detectedProfileIds);
|
|
206
|
+
|
|
207
|
+
for (const id of config.include ?? []) {
|
|
208
|
+
activeProfileIds.add(id);
|
|
209
|
+
}
|
|
210
|
+
for (const id of config.exclude ?? []) {
|
|
211
|
+
activeProfileIds.delete(id);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Collect tool patterns from active profiles
|
|
215
|
+
const activeProfiles = PROFILES.filter((p) => activeProfileIds.has(p.id));
|
|
216
|
+
const patterns = activeProfiles.flatMap((p) => p.tools);
|
|
217
|
+
|
|
218
|
+
// Check for wildcard — if any profile has "*", enable all
|
|
219
|
+
if (patterns.includes("*")) {
|
|
220
|
+
let tools = new Set(allToolNames);
|
|
221
|
+
for (const name of config.tools?.disable ?? []) {
|
|
222
|
+
tools.delete(name);
|
|
223
|
+
}
|
|
224
|
+
return [...tools];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Match tools against patterns
|
|
228
|
+
let enabledTools = new Set<string>();
|
|
229
|
+
for (const toolName of allToolNames) {
|
|
230
|
+
if (patterns.some((p) => matchTool(toolName, p))) {
|
|
231
|
+
enabledTools.add(toolName);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Apply individual tool overrides
|
|
236
|
+
for (const name of config.tools?.enable ?? []) {
|
|
237
|
+
if (allToolNames.includes(name)) {
|
|
238
|
+
enabledTools.add(name);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
for (const name of config.tools?.disable ?? []) {
|
|
242
|
+
enabledTools.delete(name);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return [...enabledTools];
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Format a summary of active/inactive profiles for display */
|
|
249
|
+
export function formatProfileSummary(
|
|
250
|
+
detectedIds: string[],
|
|
251
|
+
config: ProfileConfig,
|
|
252
|
+
allToolNames: string[],
|
|
253
|
+
): string {
|
|
254
|
+
const activeTools = resolveActiveTools(allToolNames, detectedIds, config);
|
|
255
|
+
const lines: string[] = [];
|
|
256
|
+
|
|
257
|
+
lines.push("## Tool Profiles\n");
|
|
258
|
+
|
|
259
|
+
for (const profile of PROFILES) {
|
|
260
|
+
const detected = detectedIds.includes(profile.id);
|
|
261
|
+
const included = config.include?.includes(profile.id);
|
|
262
|
+
const excluded = config.exclude?.includes(profile.id);
|
|
263
|
+
|
|
264
|
+
let status: string;
|
|
265
|
+
if (excluded) {
|
|
266
|
+
status = "⊘ excluded";
|
|
267
|
+
} else if (detected || included) {
|
|
268
|
+
status = included && !detected ? "✓ forced" : "✓ active";
|
|
269
|
+
} else {
|
|
270
|
+
status = "○ inactive";
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const toolCount = profile.tools.includes("*")
|
|
274
|
+
? "all"
|
|
275
|
+
: `${profile.tools.length} tools`;
|
|
276
|
+
|
|
277
|
+
lines.push(`- **${profile.label}** (${profile.id}): ${status} — ${toolCount}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
lines.push("");
|
|
281
|
+
lines.push(`**Active:** ${activeTools.length}/${allToolNames.length} tools`);
|
|
282
|
+
|
|
283
|
+
if (config.tools?.enable?.length || config.tools?.disable?.length) {
|
|
284
|
+
lines.push("\n**Overrides:**");
|
|
285
|
+
for (const t of config.tools?.enable ?? []) lines.push(` + ${t}`);
|
|
286
|
+
for (const t of config.tools?.disable ?? []) lines.push(` - ${t}`);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return lines.join("\n");
|
|
290
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import "@cwilson613/pi-coding-agent";
|
|
2
|
+
import type { SlashCommandBridgeMetadata, SlashCommandBridgeResult, SlashCommandExecutionContext } from "./lib/slash-command-bridge.js";
|
|
3
|
+
|
|
4
|
+
declare module "@cwilson613/pi-coding-agent" {
|
|
5
|
+
interface RegisteredCommand {
|
|
6
|
+
bridge?: SlashCommandBridgeMetadata;
|
|
7
|
+
structuredExecutor?: (args: string, ctx: SlashCommandExecutionContext) => Promise<SlashCommandBridgeResult>;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vault — Markdown viewport extension
|
|
3
|
+
*
|
|
4
|
+
* Spawns mdserve to render interlinked project markdown as a navigable
|
|
5
|
+
* web UI with wikilink resolution, graph view, and live reload.
|
|
6
|
+
*
|
|
7
|
+
* Auto-starts mdserve on session_start if the binary is on $PATH.
|
|
8
|
+
* Stores port in sharedState for URI resolver consumption.
|
|
9
|
+
*
|
|
10
|
+
* Commands:
|
|
11
|
+
* /vault — Show status (running/stopped, port, PID)
|
|
12
|
+
* /vault [path] — Start mdserve on a specific directory
|
|
13
|
+
* /vault stop — Stop the running mdserve instance
|
|
14
|
+
* /vault graph — Open the graph view in the browser
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execSync, spawn, type ChildProcess } from "node:child_process";
|
|
18
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
19
|
+
|
|
20
|
+
const DEFAULT_PORT = 3333;
|
|
21
|
+
const BINARY_NAME = "mdserve";
|
|
22
|
+
|
|
23
|
+
let mdserveProcess: ChildProcess | null = null;
|
|
24
|
+
let mdservePort: number | null = null;
|
|
25
|
+
let mdserveDir: string | null = null;
|
|
26
|
+
|
|
27
|
+
/** Get the current mdserve port, or null if not running. Used by uri-resolver. */
|
|
28
|
+
export function getMdservePort(): number | null {
|
|
29
|
+
return mdservePort;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function hasBinary(): boolean {
|
|
33
|
+
try {
|
|
34
|
+
execSync(`which ${BINARY_NAME}`, { stdio: "ignore" });
|
|
35
|
+
return true;
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function openBrowser(url: string): void {
|
|
42
|
+
try {
|
|
43
|
+
const cmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
44
|
+
spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
45
|
+
} catch { /* user can open manually */ }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// No shared state update needed — getMdservePort() is the public API
|
|
49
|
+
|
|
50
|
+
function stopMdserve(): string {
|
|
51
|
+
if (mdserveProcess) {
|
|
52
|
+
const pid = mdserveProcess.pid;
|
|
53
|
+
mdserveProcess.kill("SIGTERM");
|
|
54
|
+
mdserveProcess = null;
|
|
55
|
+
const msg = `Stopped mdserve (PID ${pid}, was serving ${mdserveDir} on port ${mdservePort})`;
|
|
56
|
+
mdservePort = null;
|
|
57
|
+
mdserveDir = null;
|
|
58
|
+
|
|
59
|
+
return msg;
|
|
60
|
+
}
|
|
61
|
+
return "mdserve is not running.";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function spawnMdserve(dir: string, port: number, options?: { silent?: boolean }): string {
|
|
65
|
+
if (mdserveProcess) {
|
|
66
|
+
if (mdserveDir === dir) {
|
|
67
|
+
return `mdserve already running at http://127.0.0.1:${mdservePort} (PID ${mdserveProcess.pid})\n` +
|
|
68
|
+
`Serving: ${mdserveDir}\n` +
|
|
69
|
+
`Use \`/vault stop\` to stop, or \`/vault graph\` to open graph view.`;
|
|
70
|
+
}
|
|
71
|
+
stopMdserve();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const child = spawn(BINARY_NAME, [dir, "--port", String(port)], {
|
|
75
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
76
|
+
detached: false,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
mdserveProcess = child;
|
|
80
|
+
mdservePort = port;
|
|
81
|
+
mdserveDir = dir;
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
child.stdout?.on("data", (data: Buffer) => {
|
|
85
|
+
const match = data.toString().match(/using (\d+) instead/);
|
|
86
|
+
if (match) {
|
|
87
|
+
mdservePort = parseInt(match[1], 10);
|
|
88
|
+
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
child.on("exit", () => {
|
|
93
|
+
if (mdserveProcess === child) {
|
|
94
|
+
mdserveProcess = null;
|
|
95
|
+
mdservePort = null;
|
|
96
|
+
mdserveDir = null;
|
|
97
|
+
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!options?.silent) {
|
|
102
|
+
openBrowser(`http://127.0.0.1:${port}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const prefix = options?.silent ? "Auto-started" : "Started";
|
|
106
|
+
return `${prefix} mdserve at http://127.0.0.1:${port} (PID ${child.pid})\n` +
|
|
107
|
+
`Serving: ${dir}\n` +
|
|
108
|
+
`Graph view: http://127.0.0.1:${port}/graph\n` +
|
|
109
|
+
`Use \`/vault stop\` to stop.`;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const NOT_INSTALLED = "`mdserve` is not installed. Run `/bootstrap` to set up Omegon dependencies.";
|
|
113
|
+
|
|
114
|
+
export default function (pi: ExtensionAPI) {
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
// Auto-start mdserve on session start if binary is available
|
|
118
|
+
pi.on("session_start", () => {
|
|
119
|
+
if (hasBinary()) {
|
|
120
|
+
spawnMdserve(process.cwd(), DEFAULT_PORT, { silent: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
pi.on("session_shutdown", () => {
|
|
125
|
+
if (mdserveProcess) {
|
|
126
|
+
mdserveProcess.kill("SIGTERM");
|
|
127
|
+
mdserveProcess = null;
|
|
128
|
+
mdservePort = null;
|
|
129
|
+
mdserveDir = null;
|
|
130
|
+
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
pi.registerCommand("vault", {
|
|
135
|
+
description: "Markdown viewport — serve project docs with wikilinks and graph view",
|
|
136
|
+
handler: async (args, ctx) => {
|
|
137
|
+
const subcommand = args.trim().split(/\s+/)[0]?.toLowerCase() || "";
|
|
138
|
+
|
|
139
|
+
switch (subcommand) {
|
|
140
|
+
case "stop":
|
|
141
|
+
ctx.ui.notify(stopMdserve(), "info");
|
|
142
|
+
return;
|
|
143
|
+
|
|
144
|
+
case "status":
|
|
145
|
+
case "": {
|
|
146
|
+
// Default: show status
|
|
147
|
+
if (mdserveProcess) {
|
|
148
|
+
ctx.ui.notify(
|
|
149
|
+
`mdserve is running (PID ${mdserveProcess.pid})\n` +
|
|
150
|
+
`URL: http://127.0.0.1:${mdservePort}\n` +
|
|
151
|
+
`Serving: ${mdserveDir}`,
|
|
152
|
+
"info",
|
|
153
|
+
);
|
|
154
|
+
} else if (!hasBinary()) {
|
|
155
|
+
ctx.ui.notify(NOT_INSTALLED, "warning");
|
|
156
|
+
} else {
|
|
157
|
+
ctx.ui.notify("mdserve is not running. Use `/vault [path]` to start.", "info");
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
case "graph":
|
|
163
|
+
if (!hasBinary()) { ctx.ui.notify(NOT_INSTALLED, "warning"); return; }
|
|
164
|
+
if (mdserveProcess && mdservePort) {
|
|
165
|
+
openBrowser(`http://127.0.0.1:${mdservePort}/graph`);
|
|
166
|
+
ctx.ui.notify(`Opened graph view at http://127.0.0.1:${mdservePort}/graph`, "info");
|
|
167
|
+
} else {
|
|
168
|
+
const dir = process.cwd();
|
|
169
|
+
ctx.ui.notify(spawnMdserve(dir, DEFAULT_PORT), "info");
|
|
170
|
+
setTimeout(() => {
|
|
171
|
+
if (mdservePort) openBrowser(`http://127.0.0.1:${mdservePort}/graph`);
|
|
172
|
+
}, 1000);
|
|
173
|
+
}
|
|
174
|
+
return;
|
|
175
|
+
|
|
176
|
+
default: {
|
|
177
|
+
if (!hasBinary()) { ctx.ui.notify(NOT_INSTALLED, "warning"); return; }
|
|
178
|
+
const dir = subcommand;
|
|
179
|
+
ctx.ui.notify(spawnMdserve(dir, DEFAULT_PORT), "info");
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* version-check — Polls GitHub for new Omegon releases and notifies the operator.
|
|
3
|
+
*
|
|
4
|
+
* Checks on session start, then hourly. Compares the installed version
|
|
5
|
+
* (from package.json) against the latest GitHub release tag. If a newer
|
|
6
|
+
* version is found, sends a notification suggesting `pi update`.
|
|
7
|
+
*
|
|
8
|
+
* Respects PI_SKIP_VERSION_CHECK and PI_OFFLINE environment variables.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
|
|
12
|
+
import { readFileSync } from "node:fs";
|
|
13
|
+
import { join, dirname } from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
const REPO_OWNER = "cwilson613";
|
|
17
|
+
const REPO_NAME = "omegon";
|
|
18
|
+
const CHECK_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
|
|
19
|
+
const FETCH_TIMEOUT_MS = 10_000;
|
|
20
|
+
|
|
21
|
+
/** Read installed version from package.json */
|
|
22
|
+
function getInstalledVersion(): string {
|
|
23
|
+
const pkgPath = join(dirname(fileURLToPath(import.meta.url)), "..", "package.json");
|
|
24
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
25
|
+
return pkg.version;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Fetch the latest release tag from GitHub. Returns version string or null. */
|
|
29
|
+
async function fetchLatestRelease(): Promise<string | null> {
|
|
30
|
+
if (process.env.PI_SKIP_VERSION_CHECK || process.env.PI_OFFLINE) return null;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await fetch(
|
|
34
|
+
`https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`,
|
|
35
|
+
{
|
|
36
|
+
headers: { Accept: "application/vnd.github+json" },
|
|
37
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
38
|
+
},
|
|
39
|
+
);
|
|
40
|
+
if (!response.ok) return null;
|
|
41
|
+
const data = (await response.json()) as { tag_name?: string };
|
|
42
|
+
return data.tag_name?.replace(/^v/, "") ?? null;
|
|
43
|
+
} catch {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Simple semver comparison. Returns true if latest > current. */
|
|
49
|
+
function isNewer(latest: string, current: string): boolean {
|
|
50
|
+
const parse = (v: string) => v.split(".").map((n) => parseInt(n, 10) || 0);
|
|
51
|
+
const l = parse(latest);
|
|
52
|
+
const c = parse(current);
|
|
53
|
+
for (let i = 0; i < 3; i++) {
|
|
54
|
+
if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
|
|
55
|
+
if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
|
|
56
|
+
}
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function versionCheck(pi: ExtensionAPI) {
|
|
61
|
+
let timer: ReturnType<typeof setInterval> | null = null;
|
|
62
|
+
let notifiedVersion: string | null = null;
|
|
63
|
+
|
|
64
|
+
async function check() {
|
|
65
|
+
const installed = getInstalledVersion();
|
|
66
|
+
const latest = await fetchLatestRelease();
|
|
67
|
+
if (!latest || !isNewer(latest, installed)) return;
|
|
68
|
+
if (latest === notifiedVersion) return; // don't spam
|
|
69
|
+
|
|
70
|
+
notifiedVersion = latest;
|
|
71
|
+
pi.sendMessage({
|
|
72
|
+
customType: "view",
|
|
73
|
+
content: `**Omegon update available:** v${installed} → v${latest}\n\nRun \`pi update\` to upgrade.`,
|
|
74
|
+
display: true,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pi.on("session_start", async () => {
|
|
79
|
+
// Fire-and-forget — don't block session start
|
|
80
|
+
check();
|
|
81
|
+
timer = setInterval(check, CHECK_INTERVAL_MS);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
pi.on("session_shutdown", async () => {
|
|
85
|
+
if (timer) {
|
|
86
|
+
clearInterval(timer);
|
|
87
|
+
timer = null;
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|