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,243 @@
1
+ /**
2
+ * cleave/worktree — Git worktree operations for child isolation.
3
+ *
4
+ * Each child gets its own git worktree on a separate branch,
5
+ * preventing file conflicts during parallel execution.
6
+ * Completed branches are merged back to base after harvesting.
7
+ */
8
+
9
+ import { mkdirSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
13
+
14
+ /** Base directory for cleave worktrees. */
15
+ const WORKTREE_HOME = join(homedir(), ".pi", "cleave", "wt");
16
+
17
+ export interface WorktreeInfo {
18
+ path: string;
19
+ branch: string;
20
+ }
21
+
22
+ /**
23
+ * Get the current git branch name.
24
+ */
25
+ export async function getCurrentBranch(
26
+ pi: ExtensionAPI,
27
+ repoPath: string,
28
+ ): Promise<string> {
29
+ const result = await pi.exec("git", ["branch", "--show-current"], {
30
+ cwd: repoPath,
31
+ timeout: 5_000,
32
+ });
33
+ const branch = result.stdout.trim();
34
+ if (!branch) {
35
+ // Detached HEAD — get the SHA
36
+ const sha = await pi.exec("git", ["rev-parse", "--short", "HEAD"], {
37
+ cwd: repoPath,
38
+ timeout: 5_000,
39
+ });
40
+ return sha.stdout.trim();
41
+ }
42
+ return branch;
43
+ }
44
+
45
+ /**
46
+ * Ensure the working tree is clean (no uncommitted changes).
47
+ * Throws if dirty.
48
+ */
49
+ export async function ensureCleanWorktree(
50
+ pi: ExtensionAPI,
51
+ repoPath: string,
52
+ ): Promise<void> {
53
+ const result = await pi.exec("git", ["status", "--porcelain"], {
54
+ cwd: repoPath,
55
+ timeout: 5_000,
56
+ });
57
+ if (result.stdout.trim()) {
58
+ throw new Error(
59
+ "Working tree has uncommitted changes. Commit or stash before cleaving.\n" +
60
+ result.stdout.trim(),
61
+ );
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Create a git worktree for a child task.
67
+ *
68
+ * Creates a new branch from baseBranch and checks it out in a
69
+ * worktree directory adjacent to the repo.
70
+ */
71
+ export async function createWorktree(
72
+ pi: ExtensionAPI,
73
+ repoPath: string,
74
+ childLabel: string,
75
+ childId: number,
76
+ baseBranch: string,
77
+ ): Promise<WorktreeInfo> {
78
+ // Sanitize childLabel to prevent path traversal via / or ..
79
+ const safeLabel = childLabel.replace(/[^a-zA-Z0-9_-]/g, "-").replace(/^\.+/, "") || "task";
80
+ const branch = `cleave/${childId}-${safeLabel}`;
81
+ // Worktree goes in ~/.pi/cleave/wt/ to avoid polluting the repo or its parent
82
+ mkdirSync(WORKTREE_HOME, { recursive: true });
83
+ const worktreePath = join(WORKTREE_HOME, `${childId}-${safeLabel}`);
84
+
85
+ // Delete branch if it already exists (leftover from a previous run)
86
+ await pi.exec("git", ["branch", "-D", branch], {
87
+ cwd: repoPath,
88
+ timeout: 5_000,
89
+ }).catch(() => {}); // ignore if it doesn't exist
90
+
91
+ // Remove stale worktree path if it exists
92
+ await pi.exec("rm", ["-rf", worktreePath], { timeout: 5_000 }).catch(() => {});
93
+
94
+ // Create worktree with new branch from base
95
+ const result = await pi.exec(
96
+ "git",
97
+ ["worktree", "add", "-b", branch, worktreePath, baseBranch],
98
+ { cwd: repoPath, timeout: 30_000 },
99
+ );
100
+
101
+ if (result.code !== 0) {
102
+ throw new Error(`Failed to create worktree: ${result.stderr}`);
103
+ }
104
+
105
+ return { path: worktreePath, branch };
106
+ }
107
+
108
+ /**
109
+ * Merge a child's branch back into the base branch.
110
+ *
111
+ * Returns { success, conflictFiles } — does NOT abort on conflict,
112
+ * leaving the merge state for manual resolution.
113
+ */
114
+ export async function mergeBranch(
115
+ pi: ExtensionAPI,
116
+ repoPath: string,
117
+ childBranch: string,
118
+ baseBranch: string,
119
+ ): Promise<{ success: boolean; conflictFiles: string[]; error?: string }> {
120
+ // Checkout base branch
121
+ let result = await pi.exec("git", ["checkout", baseBranch], {
122
+ cwd: repoPath,
123
+ timeout: 10_000,
124
+ });
125
+ if (result.code !== 0) {
126
+ return { success: false, conflictFiles: [], error: `Failed to checkout ${baseBranch}: ${result.stderr}` };
127
+ }
128
+
129
+ // Attempt merge
130
+ result = await pi.exec("git", ["merge", "--no-ff", childBranch, "-m", `merge: cleave child ${childBranch}`], {
131
+ cwd: repoPath,
132
+ timeout: 30_000,
133
+ });
134
+
135
+ if (result.code === 0) {
136
+ return { success: true, conflictFiles: [] };
137
+ }
138
+
139
+ // Merge conflict — detect which files
140
+ const statusResult = await pi.exec("git", ["diff", "--name-only", "--diff-filter=U"], {
141
+ cwd: repoPath,
142
+ timeout: 5_000,
143
+ });
144
+ const conflictFiles = statusResult.stdout.trim().split("\n").filter(Boolean);
145
+
146
+ // Abort the merge to leave repo in clean state
147
+ await pi.exec("git", ["merge", "--abort"], { cwd: repoPath, timeout: 5_000 }).catch(() => {});
148
+
149
+ return {
150
+ success: false,
151
+ conflictFiles,
152
+ error: `Merge conflict in ${conflictFiles.length} file(s)`,
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Remove cleave worktree directories without deleting branches.
158
+ * Used on merge failure to free disk space while preserving branches
159
+ * for manual conflict resolution.
160
+ */
161
+ export async function pruneWorktreeDirs(
162
+ pi: ExtensionAPI,
163
+ repoPath: string,
164
+ ): Promise<void> {
165
+ const result = await pi.exec("git", ["worktree", "list", "--porcelain"], {
166
+ cwd: repoPath,
167
+ timeout: 5_000,
168
+ });
169
+
170
+ for (const line of result.stdout.split("\n")) {
171
+ if (line.startsWith("worktree ")) {
172
+ const wtPath = line.replace("worktree ", "").trim();
173
+ if (wtPath.startsWith(WORKTREE_HOME) || wtPath.includes(".cleave-wt-")) {
174
+ await pi.exec("git", ["worktree", "remove", "--force", wtPath], {
175
+ cwd: repoPath,
176
+ timeout: 10_000,
177
+ }).catch(() => {});
178
+ }
179
+ }
180
+ }
181
+
182
+ await pi.exec("git", ["worktree", "prune"], {
183
+ cwd: repoPath,
184
+ timeout: 5_000,
185
+ }).catch(() => {});
186
+ }
187
+
188
+ /**
189
+ * Clean up all cleave worktrees and their branches.
190
+ */
191
+ export async function cleanupWorktrees(
192
+ pi: ExtensionAPI,
193
+ repoPath: string,
194
+ ): Promise<void> {
195
+ // List worktrees
196
+ const result = await pi.exec("git", ["worktree", "list", "--porcelain"], {
197
+ cwd: repoPath,
198
+ timeout: 5_000,
199
+ });
200
+
201
+ const lines = result.stdout.split("\n");
202
+ const worktreePaths: string[] = [];
203
+
204
+ for (const line of lines) {
205
+ if (line.startsWith("worktree ")) {
206
+ const wtPath = line.replace("worktree ", "").trim();
207
+ // Match worktrees in the cleave wt directory or legacy .cleave-wt- locations
208
+ if (wtPath.startsWith(WORKTREE_HOME) || wtPath.includes(".cleave-wt-")) {
209
+ worktreePaths.push(wtPath);
210
+ }
211
+ }
212
+ }
213
+
214
+ // Remove each worktree
215
+ for (const wtPath of worktreePaths) {
216
+ await pi.exec("git", ["worktree", "remove", "--force", wtPath], {
217
+ cwd: repoPath,
218
+ timeout: 10_000,
219
+ }).catch(() => {});
220
+ }
221
+
222
+ // Prune stale worktree references
223
+ await pi.exec("git", ["worktree", "prune"], {
224
+ cwd: repoPath,
225
+ timeout: 5_000,
226
+ }).catch(() => {});
227
+
228
+ // Delete orphaned cleave/* branches
229
+ const branchResult = await pi.exec(
230
+ "git", ["branch", "--list", "cleave/*"],
231
+ { cwd: repoPath, timeout: 5_000 },
232
+ );
233
+ const branches = branchResult.stdout
234
+ .split("\n")
235
+ .map((b) => b.trim().replace(/^\*\s*/, ""))
236
+ .filter(Boolean);
237
+ for (const branch of branches) {
238
+ await pi.exec("git", ["branch", "-D", branch], {
239
+ cwd: repoPath,
240
+ timeout: 5_000,
241
+ }).catch(() => {});
242
+ }
243
+ }
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Core Tool Renderers — Sci-UI rendering for pi built-in tools.
3
+ *
4
+ * Uses registerToolRenderer() to attach renderCall/renderResult
5
+ * to built-in tools (bash, read, edit, write) without replacing them.
6
+ *
7
+ * The built-in renderers handle syntax highlighting, diffs, and streaming —
8
+ * we only override the COLLAPSED view to match the Sci-UI visual language.
9
+ * Expanded views fall through to the built-in renderer.
10
+ */
11
+ import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
12
+ import { sciCall, sciOk, sciErr, sciLoading } from "./sci-ui.ts";
13
+
14
+ /** Shorten a file path for display — keep last 2-3 segments. */
15
+ function shortenPath(p: string | null | undefined, maxLen = 55): string {
16
+ if (!p) return "…";
17
+ if (p.length <= maxLen) return p;
18
+ const parts = p.split("/");
19
+ // Show last 3 segments at most
20
+ const tail = parts.slice(-3).join("/");
21
+ return tail.length <= maxLen ? tail : "…" + p.slice(-(maxLen - 1));
22
+ }
23
+
24
+ export default function coreRenderers(pi: ExtensionAPI): void {
25
+ // ── Read ──────────────────────────────────────────────────────────────
26
+ pi.registerToolRenderer("read", {
27
+ renderCall(args: any, theme: any) {
28
+ const p = shortenPath(args?.file_path ?? args?.path);
29
+ let range = "";
30
+ if (args?.offset != null || args?.limit != null) {
31
+ const start = args.offset ?? 1;
32
+ const end = args.limit != null ? start + args.limit - 1 : "";
33
+ range = `:${start}${end ? `-${end}` : ""}`;
34
+ }
35
+ return sciCall("read", `${p}${range}`, theme);
36
+ },
37
+ // renderResult omitted — built-in handles syntax highlighting + truncation
38
+ });
39
+
40
+ // ── Edit ──────────────────────────────────────────────────────────────
41
+ pi.registerToolRenderer("edit", {
42
+ renderCall(args: any, theme: any) {
43
+ const p = shortenPath(args?.file_path ?? args?.path);
44
+ // Show the size of the change: lines changed
45
+ const oldLines = (args?.old_text ?? args?.oldText ?? "").split("\n").length;
46
+ const newLines = (args?.new_text ?? args?.newText ?? "").split("\n").length;
47
+ const delta = newLines - oldLines;
48
+ const deltaStr = delta === 0 ? `${oldLines}L` : delta > 0 ? `+${delta}L` : `${delta}L`;
49
+ return sciCall("edit", `${p} (${deltaStr})`, theme);
50
+ },
51
+ // renderResult omitted — built-in handles diff rendering
52
+ });
53
+
54
+ // ── Write ─────────────────────────────────────────────────────────────
55
+ pi.registerToolRenderer("write", {
56
+ renderCall(args: any, theme: any) {
57
+ const p = shortenPath(args?.file_path ?? args?.path);
58
+ const content = args?.content ?? "";
59
+ const lines = content.split("\n").length;
60
+ return sciCall("write", `${p} (${lines}L)`, theme);
61
+ },
62
+ // renderResult omitted — built-in handles syntax highlighting
63
+ });
64
+
65
+ // ── Bash ──────────────────────────────────────────────────────────────
66
+ pi.registerToolRenderer("bash", {
67
+ renderCall(args: any, theme: any) {
68
+ const cmd = args?.command ?? "";
69
+ // Truncate long commands
70
+ const display = cmd.length > 70 ? cmd.slice(0, 67) + "…" : cmd;
71
+ return sciCall("bash", display, theme);
72
+ },
73
+ // renderResult omitted — built-in handles output display + truncation
74
+ });
75
+
76
+ // ── Grep ──────────────────────────────────────────────────────────────
77
+ pi.registerToolRenderer("grep", {
78
+ renderCall(args: any, theme: any) {
79
+ const pattern = args?.pattern ?? "";
80
+ const p = shortenPath(args?.path);
81
+ const glob = args?.glob ? ` (${args.glob})` : "";
82
+ return sciCall("grep", `/${pattern}/ in ${p}${glob}`, theme);
83
+ },
84
+ });
85
+
86
+ // ── Find ──────────────────────────────────────────────────────────────
87
+ pi.registerToolRenderer("find", {
88
+ renderCall(args: any, theme: any) {
89
+ const pattern = args?.pattern ?? "";
90
+ const p = shortenPath(args?.path);
91
+ return sciCall("find", `${pattern} in ${p}`, theme);
92
+ },
93
+ });
94
+
95
+ // ── Ls ─────────────────────────────────────────────────────────────────
96
+ pi.registerToolRenderer("ls", {
97
+ renderCall(args: any, theme: any) {
98
+ const p = shortenPath(args?.path || ".");
99
+ return sciCall("ls", p, theme);
100
+ },
101
+ });
102
+
103
+ // ── View ──────────────────────────────────────────────────────────────
104
+ pi.registerToolRenderer("view", {
105
+ renderCall(args: any, theme: any) {
106
+ const p = shortenPath(args?.path);
107
+ const page = args?.page ? ` p${args.page}` : "";
108
+ return sciCall("view", `${p}${page}`, theme);
109
+ },
110
+ });
111
+
112
+ // ── Web Search ────────────────────────────────────────────────────────
113
+ pi.registerToolRenderer("web_search", {
114
+ renderCall(args: any, theme: any) {
115
+ const query = args?.query ?? "";
116
+ const mode = args?.mode ?? "quick";
117
+ const display = query.length > 55 ? query.slice(0, 52) + "…" : query;
118
+ const modeTag = mode !== "quick" ? ` [${mode}]` : "";
119
+ return sciCall("web_search", `${display}${modeTag}`, theme);
120
+ },
121
+ });
122
+
123
+ // ── Chronos ───────────────────────────────────────────────────────────
124
+ pi.registerToolRenderer("chronos", {
125
+ renderCall(args: any, theme: any) {
126
+ const sub = args?.subcommand ?? "week";
127
+ const expr = args?.expression ? ` "${args.expression}"` : "";
128
+ return sciCall("chronos", `${sub}${expr}`, theme);
129
+ },
130
+ });
131
+
132
+ // ── Render Diagram (D2) ───────────────────────────────────────────────
133
+ pi.registerToolRenderer("render_diagram", {
134
+ renderCall(args: any, theme: any) {
135
+ const title = args?.title ?? "diagram";
136
+ return sciCall("render_diagram", title, theme);
137
+ },
138
+ });
139
+
140
+ // ── Render Native Diagram ─────────────────────────────────────────────
141
+ pi.registerToolRenderer("render_native_diagram", {
142
+ renderCall(args: any, theme: any) {
143
+ const title = args?.title ?? "diagram";
144
+ return sciCall("render_native_diagram", title, theme);
145
+ },
146
+ });
147
+
148
+ // ── Render Excalidraw ─────────────────────────────────────────────────
149
+ pi.registerToolRenderer("render_excalidraw", {
150
+ renderCall(args: any, theme: any) {
151
+ const p = shortenPath(args?.path);
152
+ return sciCall("render_excalidraw", p, theme);
153
+ },
154
+ });
155
+
156
+ // ── Generate Image Local ──────────────────────────────────────────────
157
+ pi.registerToolRenderer("generate_image_local", {
158
+ renderCall(args: any, theme: any) {
159
+ const prompt = args?.prompt ?? "";
160
+ const preset = args?.preset ?? "schnell";
161
+ const display = prompt.length > 50 ? prompt.slice(0, 47) + "…" : prompt;
162
+ return sciCall("generate_image_local", `${display} [${preset}]`, theme);
163
+ },
164
+ });
165
+
166
+ // ── Render Composition Still ──────────────────────────────────────────
167
+ pi.registerToolRenderer("render_composition_still", {
168
+ renderCall(args: any, theme: any) {
169
+ const p = shortenPath(args?.composition_path);
170
+ const frame = args?.frame != null ? ` f${args.frame}` : "";
171
+ return sciCall("render_composition_still", `${p}${frame}`, theme);
172
+ },
173
+ });
174
+
175
+ // ── Render Composition Video ──────────────────────────────────────────
176
+ pi.registerToolRenderer("render_composition_video", {
177
+ renderCall(args: any, theme: any) {
178
+ const p = shortenPath(args?.composition_path);
179
+ const frames = args?.duration_in_frames ?? "?";
180
+ const fmt = args?.format ?? "gif";
181
+ return sciCall("render_composition_video", `${p} (${frames}f, ${fmt})`, theme);
182
+ },
183
+ });
184
+
185
+ // ── Model Tier ────────────────────────────────────────────────────────
186
+ pi.registerToolRenderer("set_model_tier", {
187
+ renderCall(args: any, theme: any) {
188
+ const tier = args?.tier ?? "?";
189
+ return sciCall("set_model_tier", `→ ${tier}`, theme);
190
+ },
191
+ });
192
+
193
+ // ── Thinking Level ────────────────────────────────────────────────────
194
+ pi.registerToolRenderer("set_thinking_level", {
195
+ renderCall(args: any, theme: any) {
196
+ const level = args?.level ?? "?";
197
+ return sciCall("set_thinking_level", `→ ${level}`, theme);
198
+ },
199
+ });
200
+
201
+ // ── Ask Local Model ───────────────────────────────────────────────────
202
+ pi.registerToolRenderer("ask_local_model", {
203
+ renderCall(args: any, theme: any) {
204
+ const model = args?.model ?? "auto";
205
+ const prompt = args?.prompt ?? "";
206
+ const display = prompt.length > 45 ? prompt.slice(0, 42) + "…" : prompt;
207
+ return sciCall("ask_local_model", `[${model}] ${display}`, theme);
208
+ },
209
+ });
210
+
211
+ // ── Manage Ollama ─────────────────────────────────────────────────────
212
+ pi.registerToolRenderer("manage_ollama", {
213
+ renderCall(args: any, theme: any) {
214
+ const action = args?.action ?? "?";
215
+ const model = args?.model ? ` ${args.model}` : "";
216
+ return sciCall("manage_ollama", `${action}${model}`, theme);
217
+ },
218
+ });
219
+
220
+ // ── List Local Models ─────────────────────────────────────────────────
221
+ pi.registerToolRenderer("list_local_models", {
222
+ renderCall(_args: any, theme: any) {
223
+ return sciCall("list_local_models", "inventory", theme);
224
+ },
225
+ });
226
+
227
+ // ── Switch Offline Driver ─────────────────────────────────────────────
228
+ pi.registerToolRenderer("switch_to_offline_driver", {
229
+ renderCall(args: any, theme: any) {
230
+ const reason = args?.reason ?? "";
231
+ const display = reason.length > 50 ? reason.slice(0, 47) + "…" : reason;
232
+ return sciCall("switch_to_offline_driver", display, theme);
233
+ },
234
+ });
235
+
236
+ // ── Manage Tools ──────────────────────────────────────────────────────
237
+ pi.registerToolRenderer("manage_tools", {
238
+ renderCall(args: any, theme: any) {
239
+ const action = args?.action ?? "list";
240
+ const tools = args?.tools?.join(", ") ?? "";
241
+ const profile = args?.profile ?? "";
242
+ const detail = tools || profile || "";
243
+ return sciCall("manage_tools", `${action}${detail ? ` ${detail}` : ""}`, theme);
244
+ },
245
+ });
246
+
247
+ // ── Whoami ────────────────────────────────────────────────────────────
248
+ pi.registerToolRenderer("whoami", {
249
+ renderCall(_args: any, theme: any) {
250
+ return sciCall("whoami", "check auth", theme);
251
+ },
252
+ });
253
+ }
@@ -0,0 +1,58 @@
1
+ import type { MemoryInjectionMetrics } from "../project-memory/injection-metrics.ts";
2
+
3
+ export interface ContextGaugeInput {
4
+ percent: number | null | undefined;
5
+ contextWindow: number;
6
+ memoryTokenEstimate: number;
7
+ turns: number;
8
+ }
9
+
10
+ export interface ContextGaugeModel {
11
+ state: "known" | "unknown";
12
+ turns: number;
13
+ contextWindow: number;
14
+ percent: number | null;
15
+ memoryPercent: number;
16
+ otherPercent: number;
17
+ memoryBlocks: number;
18
+ otherBlocks: number;
19
+ freeBlocks: number;
20
+ }
21
+
22
+ export function buildContextGaugeModel(input: ContextGaugeInput, barWidth: number): ContextGaugeModel {
23
+ const contextWindow = input.contextWindow;
24
+ const pct = input.percent ?? null;
25
+
26
+ if (pct === null) {
27
+ return {
28
+ state: "unknown",
29
+ turns: input.turns,
30
+ contextWindow,
31
+ percent: null,
32
+ memoryPercent: 0,
33
+ otherPercent: 0,
34
+ memoryBlocks: 0,
35
+ otherBlocks: 0,
36
+ freeBlocks: barWidth,
37
+ };
38
+ }
39
+
40
+ const memoryPercent = contextWindow > 0 ? (input.memoryTokenEstimate / contextWindow) * 100 : 0;
41
+ const otherPercent = Math.max(0, pct - memoryPercent);
42
+ const memoryBlocks = memoryPercent > 0 ? Math.ceil((memoryPercent / 100) * barWidth) : 0;
43
+ const otherBlocks = otherPercent > 0 ? Math.ceil((otherPercent / 100) * barWidth) : 0;
44
+ const totalFilled = Math.min(memoryBlocks + otherBlocks, barWidth);
45
+ const freeBlocks = barWidth - totalFilled;
46
+
47
+ return {
48
+ state: "known",
49
+ turns: input.turns,
50
+ contextWindow,
51
+ percent: pct,
52
+ memoryPercent,
53
+ otherPercent,
54
+ memoryBlocks,
55
+ otherBlocks,
56
+ freeBlocks,
57
+ };
58
+ }
@@ -0,0 +1,14 @@
1
+ import * as path from "node:path";
2
+
3
+ export function shouldRefreshDesignTreeForPath(filePath: string, docsDir: string): boolean {
4
+ const rel = path.relative(docsDir, filePath);
5
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return false;
6
+ return rel.endsWith(".md");
7
+ }
8
+
9
+ export function shouldRefreshOpenSpecForPath(filePath: string, repoRoot: string): boolean {
10
+ const openspecDir = path.join(repoRoot, "openspec");
11
+ const rel = path.relative(openspecDir, filePath);
12
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return false;
13
+ return rel.endsWith(".md");
14
+ }