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,191 @@
1
+ import type { ExtensionAPI, ExtensionContext } from "@cwilson613/pi-coding-agent";
2
+ import { basename } from "path";
3
+ import { DASHBOARD_UPDATE_EVENT, sharedState } from "./shared-state.ts";
4
+ import type { CleaveState } from "./dashboard/types.ts";
5
+
6
+ /**
7
+ * Dynamic terminal tab title — rich status for multi-tab workflows.
8
+ *
9
+ * Shows agent state, current activity, tool execution, cleave dispatch,
10
+ * and dashboard mode in the terminal tab/window title bar.
11
+ *
12
+ * Format: π <project> [<status>] <activity> <flags>
13
+ *
14
+ * Examples:
15
+ * π omegon ✦ — idle, awaiting input
16
+ * π omegon ◆ fixing auth bug — thinking about user's request
17
+ * π omegon ⚙ Bash — executing a tool
18
+ * π omegon ⚙ Read → Edit — tool chain (last 2)
19
+ * π omegon ◆ fixing auth bug ✦ — done, awaiting next input
20
+ * π omegon ⚡ cleave 3/5 — cleave dispatch in progress
21
+ * π omegon ⚡ cleave ✓ — cleave complete
22
+ * π omegon T4 ◆ refactoring types — turn 4, thinking
23
+ */
24
+ export default function (pi: ExtensionAPI) {
25
+ const project = basename(process.cwd());
26
+
27
+ // ── State ──────────────────────────────────────────────────
28
+ let ctx: ExtensionContext | null = null;
29
+ let promptSnippet = "";
30
+ let idle = true;
31
+ let turnIndex = 0;
32
+
33
+ // Tool chain — show last 2 tools for pipeline visibility
34
+ let toolChain: string[] = [];
35
+ let toolActive = false;
36
+
37
+ // Cleave state from shared dashboard state
38
+ let cleaveStatus: CleaveState["status"] = "idle";
39
+ let cleaveDone = 0;
40
+ let cleaveTotal = 0;
41
+
42
+ // ── Helpers ────────────────────────────────────────────────
43
+
44
+ function truncate(text: string, max: number): string {
45
+ const clean = text.split("\n")[0]!.trim().replace(/\s+/g, " ");
46
+ if (clean.length <= max) return clean;
47
+ return clean.slice(0, max).trimEnd() + "…";
48
+ }
49
+
50
+ /** Read cleave state from shared dashboard state */
51
+ function syncCleaveState(): void {
52
+ const cleave = sharedState.cleave;
53
+ if (cleave) {
54
+ cleaveStatus = cleave.status;
55
+ const children = cleave.children ?? [];
56
+ cleaveTotal = children.length;
57
+ cleaveDone = children.filter(c => c.status === "done").length;
58
+ } else {
59
+ cleaveStatus = "idle";
60
+ cleaveDone = 0;
61
+ cleaveTotal = 0;
62
+ }
63
+ }
64
+
65
+ function render() {
66
+ if (!ctx?.ui?.setTitle) return;
67
+
68
+ const parts: string[] = [`π ${project}`];
69
+
70
+ // Cleave dispatch — takes priority when active
71
+ const cleaveActive = cleaveStatus !== "idle" && cleaveStatus !== "done" && cleaveStatus !== "failed";
72
+ if (cleaveActive) {
73
+ if (cleaveStatus === "dispatching" || cleaveStatus === "merging") {
74
+ parts.push(`⚡ cleave ${cleaveDone}/${cleaveTotal}`);
75
+ } else {
76
+ parts.push(`⚡ ${cleaveStatus}`);
77
+ }
78
+ } else if (cleaveStatus === "done") {
79
+ parts.push("⚡ cleave ✓");
80
+ } else if (cleaveStatus === "failed") {
81
+ parts.push("⚡ cleave ✗");
82
+ }
83
+
84
+ // Tool execution
85
+ if (toolActive && toolChain.length > 0) {
86
+ const display = toolChain.slice(-2).join(" → ");
87
+ parts.push(`⚙ ${display}`);
88
+ }
89
+ // Agent thinking (no active tool)
90
+ else if (!idle && promptSnippet && !cleaveActive) {
91
+ parts.push(`◆ ${promptSnippet}`);
92
+ }
93
+
94
+ // Turn counter when actively working (T2+)
95
+ if (!idle && turnIndex >= 2) {
96
+ parts.push(`T${turnIndex}`);
97
+ }
98
+
99
+ // Idle indicator
100
+ if (idle) {
101
+ parts.push("✦");
102
+ }
103
+
104
+ ctx.ui.setTitle(parts.join(" "));
105
+ }
106
+
107
+ // ── Session lifecycle ──────────────────────────────────────
108
+
109
+ function resetState(c: ExtensionContext) {
110
+ ctx = c;
111
+ promptSnippet = "";
112
+ toolChain = [];
113
+ toolActive = false;
114
+ idle = true;
115
+ turnIndex = 0;
116
+ cleaveStatus = "idle";
117
+ cleaveDone = 0;
118
+ cleaveTotal = 0;
119
+ setTimeout(render, 50);
120
+ }
121
+
122
+ pi.on("session_start", (_e, c) => resetState(c));
123
+ pi.on("session_switch", (_e, c) => resetState(c));
124
+ pi.on("session_fork", (_e, c) => resetState(c));
125
+
126
+ // ── Agent lifecycle ────────────────────────────────────────
127
+
128
+ pi.on("before_agent_start", (event) => {
129
+ if (event.prompt) {
130
+ promptSnippet = truncate(event.prompt, 30);
131
+ }
132
+ });
133
+
134
+ pi.on("agent_start", (_e, c) => {
135
+ ctx = c;
136
+ idle = false;
137
+ toolChain = [];
138
+ toolActive = false;
139
+ render();
140
+ });
141
+
142
+ pi.on("turn_start", (event) => {
143
+ turnIndex = event.turnIndex;
144
+ render();
145
+ });
146
+
147
+ pi.on("tool_execution_start", (event) => {
148
+ // Deduplicate consecutive same-tool calls
149
+ if (toolChain[toolChain.length - 1] !== event.toolName) {
150
+ toolChain.push(event.toolName);
151
+ }
152
+ toolActive = true;
153
+ render();
154
+ });
155
+
156
+ pi.on("tool_execution_end", () => {
157
+ toolActive = false;
158
+ render();
159
+ });
160
+
161
+ pi.on("agent_end", (_e, c) => {
162
+ ctx = c;
163
+ idle = true;
164
+ toolChain = [];
165
+ toolActive = false;
166
+ render();
167
+ });
168
+
169
+ // ── Session compaction ─────────────────────────────────────
170
+
171
+ pi.on("session_compact", () => {
172
+ // Brief flash during compaction
173
+ const prev = promptSnippet;
174
+ promptSnippet = "compacting…";
175
+ render();
176
+ setTimeout(() => {
177
+ promptSnippet = prev;
178
+ render();
179
+ }, 2000);
180
+ });
181
+
182
+ // ── Dashboard + cleave state updates ───────────────────────
183
+
184
+ pi.events.on(DASHBOARD_UPDATE_EVENT, (data) => {
185
+ const source = (data as Record<string, unknown>)?.source;
186
+ if (source === "cleave") {
187
+ syncCleaveState();
188
+ }
189
+ render();
190
+ });
191
+ }
@@ -0,0 +1,291 @@
1
+ /**
2
+ * tool-profile — Smart tool activation based on project context.
3
+ *
4
+ * On session_start, scans the cwd for project signals (package.json, .git,
5
+ * Cargo.toml, etc.) and activates only the relevant tool profiles. Saves
6
+ * ~12K tokens of context window by disabling irrelevant tools.
7
+ *
8
+ * Commands:
9
+ * /profile — Show active profiles and tool counts
10
+ * /profile <name> — Toggle a profile on/off
11
+ * /profile reset — Re-detect from project signals
12
+ *
13
+ * Tool (LLM-callable):
14
+ * manage_tools — List, enable, disable tools or switch profiles
15
+ */
16
+
17
+ import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
18
+ import { Type } from "@sinclair/typebox";
19
+ import { StringEnum } from "../lib/typebox-helpers.ts";
20
+ import {
21
+ PROFILES,
22
+ detectProfiles,
23
+ loadProfileConfig,
24
+ resolveActiveTools,
25
+ formatProfileSummary,
26
+ type ProfileConfig,
27
+ } from "./profiles.ts";
28
+
29
+ export default function (pi: ExtensionAPI) {
30
+ let currentDetected: string[] = [];
31
+ let currentConfig: ProfileConfig = {};
32
+ let allToolNames: string[] = [];
33
+
34
+ function applyProfile(ctx: { ui: { notify: (msg: string, type?: "info" | "warning" | "error") => void } } | null): void {
35
+ const activeTools = resolveActiveTools(allToolNames, currentDetected, currentConfig);
36
+ pi.setActiveTools(activeTools);
37
+ if (ctx) {
38
+ const inactive = allToolNames.length - activeTools.length;
39
+ if (inactive > 0) {
40
+ ctx.ui.notify(`Profile: ${activeTools.length} tools active (${inactive} disabled)`, "info");
41
+ }
42
+ }
43
+ }
44
+
45
+ // ── Session Start: Auto-detect ──────────────────────────────
46
+
47
+ pi.on("session_start", async (_event, ctx) => {
48
+ allToolNames = pi.getAllTools().map((t) => t.name);
49
+ currentDetected = detectProfiles(ctx.cwd);
50
+ currentConfig = loadProfileConfig(ctx.cwd);
51
+
52
+ // Only apply if we'd actually disable something.
53
+ // If pi-dev profile detected, everything stays on anyway.
54
+ if (currentDetected.includes("pi-dev")) return;
55
+
56
+ applyProfile(ctx);
57
+ });
58
+
59
+ // ── /profile Command ────────────────────────────────────────
60
+
61
+ pi.registerCommand("profile", {
62
+ description: "Show or toggle tool profiles (/profile [name|reset])",
63
+ handler: async (args, ctx) => {
64
+ const arg = args.trim().toLowerCase();
65
+
66
+ if (arg === "reset") {
67
+ allToolNames = pi.getAllTools().map((t) => t.name);
68
+ // Re-read getAllTools before detection since we may have reset
69
+ // Actually, getAllTools returns ALL registered (not just active), so this is fine
70
+ currentDetected = detectProfiles(ctx.cwd);
71
+ currentConfig = loadProfileConfig(ctx.cwd);
72
+ applyProfile(ctx);
73
+ ctx.ui.notify("Profiles re-detected from project context", "info");
74
+ return;
75
+ }
76
+
77
+ if (arg && arg !== "status") {
78
+ // Toggle a specific profile
79
+ const profile = PROFILES.find((p) => p.id === arg);
80
+ if (!profile) {
81
+ ctx.ui.notify(`Unknown profile: ${arg}. Available: ${PROFILES.map((p) => p.id).join(", ")}`, "warning");
82
+ return;
83
+ }
84
+
85
+ const isActive = currentDetected.includes(arg) ||
86
+ currentConfig.include?.includes(arg);
87
+ const isExcluded = currentConfig.exclude?.includes(arg);
88
+
89
+ if (isActive && !isExcluded) {
90
+ // Disable it
91
+ if (!currentConfig.exclude) currentConfig.exclude = [];
92
+ currentConfig.exclude.push(arg);
93
+ // Remove from include if manually added
94
+ if (currentConfig.include) {
95
+ currentConfig.include = currentConfig.include.filter((id) => id !== arg);
96
+ }
97
+ applyProfile(ctx);
98
+ ctx.ui.notify(`Profile '${profile.label}' disabled`, "info");
99
+ } else {
100
+ // Enable it
101
+ if (!currentConfig.include) currentConfig.include = [];
102
+ currentConfig.include.push(arg);
103
+ // Remove from exclude
104
+ if (currentConfig.exclude) {
105
+ currentConfig.exclude = currentConfig.exclude.filter((id) => id !== arg);
106
+ }
107
+ applyProfile(ctx);
108
+ ctx.ui.notify(`Profile '${profile.label}' enabled`, "info");
109
+ }
110
+ return;
111
+ }
112
+
113
+ // Show status
114
+ // Refresh allToolNames to show full catalog
115
+ const fullToolNames = pi.getAllTools().map((t) => t.name);
116
+ const summary = formatProfileSummary(currentDetected, currentConfig, fullToolNames);
117
+ ctx.ui.notify(summary, "info");
118
+ },
119
+ getArgumentCompletions: (prefix) => {
120
+ const options = [...PROFILES.map((p) => p.id), "reset", "status"];
121
+ return options
122
+ .filter((o) => o.startsWith(prefix))
123
+ .map((o) => ({ value: o, label: o, description: PROFILES.find((p) => p.id === o)?.description ?? o }));
124
+ },
125
+ });
126
+
127
+ // ── manage_tools Tool (LLM-callable) ────────────────────────
128
+
129
+ pi.registerTool({
130
+ name: "manage_tools",
131
+ label: "Manage Tools",
132
+ description: "List, enable, or disable tools and tool profiles. Use to activate tools the user requests or disable irrelevant ones to save context window space.",
133
+ promptSnippet: "manage_tools: list/enable/disable tools and profiles",
134
+ promptGuidelines: [
135
+ "Use manage_tools to enable tools when the user asks for a capability that's currently disabled",
136
+ "Use manage_tools with action 'list' to see what's available before trying to use a tool that might be disabled",
137
+ ],
138
+ parameters: Type.Object({
139
+ action: StringEnum(["list", "enable", "disable", "profiles", "apply_profile"], {
140
+ description: "Action: list (show tools), enable/disable (toggle tools), profiles (show profiles), apply_profile (switch profile)",
141
+ }),
142
+ tools: Type.Optional(Type.Array(Type.String(), {
143
+ description: "Tool names to enable/disable (for enable/disable actions)",
144
+ })),
145
+ profile: Type.Optional(Type.String({
146
+ description: "Profile id to apply (for apply_profile action)",
147
+ })),
148
+ }),
149
+ execute: async (_toolCallId, params, _signal, _onUpdate, ctx) => {
150
+ // Refresh tool catalog
151
+ const allTools = pi.getAllTools();
152
+ const activeToolSet = new Set(pi.getActiveTools());
153
+
154
+ switch (params.action) {
155
+ case "list": {
156
+ const lines: string[] = [];
157
+ const active = allTools.filter((t) => activeToolSet.has(t.name));
158
+ const inactive = allTools.filter((t) => !activeToolSet.has(t.name));
159
+
160
+ lines.push(`Active tools (${active.length}):`);
161
+ for (const t of active) {
162
+ lines.push(` ✓ ${t.name}`);
163
+ }
164
+ if (inactive.length > 0) {
165
+ lines.push(`\nInactive tools (${inactive.length}):`);
166
+ for (const t of inactive) {
167
+ lines.push(` ○ ${t.name} — ${t.description?.slice(0, 80) ?? ""}`);
168
+ }
169
+ }
170
+ return { content: [{ type: "text", text: lines.join("\n") }], details: undefined };
171
+ }
172
+
173
+ case "enable": {
174
+ if (!params.tools?.length) {
175
+ return { content: [{ type: "text", text: "Error: provide tool names to enable" }], details: undefined } as any;
176
+ }
177
+ const currentActive = new Set(pi.getActiveTools());
178
+ const allNames = new Set(allTools.map((t) => t.name));
179
+ const added: string[] = [];
180
+ const notFound: string[] = [];
181
+
182
+ for (const name of params.tools) {
183
+ if (!allNames.has(name)) {
184
+ notFound.push(name);
185
+ } else if (!currentActive.has(name)) {
186
+ currentActive.add(name);
187
+ added.push(name);
188
+ }
189
+ }
190
+
191
+ pi.setActiveTools([...currentActive]);
192
+
193
+ // Also track in config overrides so re-detect doesn't undo it
194
+ if (!currentConfig.tools) currentConfig.tools = {};
195
+ if (!currentConfig.tools.enable) currentConfig.tools.enable = [];
196
+ for (const name of added) {
197
+ if (!currentConfig.tools.enable.includes(name)) {
198
+ currentConfig.tools.enable.push(name);
199
+ }
200
+ // Remove from disable if present
201
+ if (currentConfig.tools.disable) {
202
+ currentConfig.tools.disable = currentConfig.tools.disable.filter((n) => n !== name);
203
+ }
204
+ }
205
+
206
+ const parts: string[] = [];
207
+ if (added.length) parts.push(`Enabled: ${added.join(", ")}`);
208
+ if (notFound.length) parts.push(`Not found: ${notFound.join(", ")}`);
209
+ return { content: [{ type: "text", text: parts.join(". ") || "No changes" }], details: undefined };
210
+ }
211
+
212
+ case "disable": {
213
+ if (!params.tools?.length) {
214
+ return { content: [{ type: "text", text: "Error: provide tool names to disable" }], details: undefined } as any;
215
+ }
216
+ const currentActive = new Set(pi.getActiveTools());
217
+ const removed: string[] = [];
218
+
219
+ for (const name of params.tools) {
220
+ if (currentActive.has(name)) {
221
+ currentActive.delete(name);
222
+ removed.push(name);
223
+ }
224
+ }
225
+
226
+ pi.setActiveTools([...currentActive]);
227
+
228
+ // Track in config
229
+ if (!currentConfig.tools) currentConfig.tools = {};
230
+ if (!currentConfig.tools.disable) currentConfig.tools.disable = [];
231
+ for (const name of removed) {
232
+ if (!currentConfig.tools.disable.includes(name)) {
233
+ currentConfig.tools.disable.push(name);
234
+ }
235
+ if (currentConfig.tools.enable) {
236
+ currentConfig.tools.enable = currentConfig.tools.enable.filter((n) => n !== name);
237
+ }
238
+ }
239
+
240
+ return { content: [{ type: "text", text: removed.length ? `Disabled: ${removed.join(", ")}` : "No changes" }], details: undefined };
241
+ }
242
+
243
+ case "profiles": {
244
+ const fullNames = allTools.map((t) => t.name);
245
+ const summary = formatProfileSummary(currentDetected, currentConfig, fullNames);
246
+ return { content: [{ type: "text", text: summary }], details: undefined };
247
+ }
248
+
249
+ case "apply_profile": {
250
+ if (!params.profile) {
251
+ return { content: [{ type: "text", text: "Error: provide a profile id" }], details: undefined } as any;
252
+ }
253
+ const profile = PROFILES.find((p) => p.id === params.profile);
254
+ if (!profile) {
255
+ const available = PROFILES.map((p) => p.id).join(", ");
256
+ return { content: [{ type: "text", text: `Unknown profile: ${params.profile}. Available: ${available}` }], details: undefined } as any;
257
+ }
258
+
259
+ // Toggle the profile
260
+ const isExcluded = currentConfig.exclude?.includes(params.profile);
261
+ const isIncluded = currentDetected.includes(params.profile) || currentConfig.include?.includes(params.profile);
262
+
263
+ if (isIncluded && !isExcluded) {
264
+ // Already active — disable it
265
+ if (!currentConfig.exclude) currentConfig.exclude = [];
266
+ currentConfig.exclude.push(params.profile);
267
+ if (currentConfig.include) {
268
+ currentConfig.include = currentConfig.include.filter((id) => id !== params.profile);
269
+ }
270
+ } else {
271
+ // Activate it
272
+ if (!currentConfig.include) currentConfig.include = [];
273
+ currentConfig.include.push(params.profile);
274
+ if (currentConfig.exclude) {
275
+ currentConfig.exclude = currentConfig.exclude.filter((id) => id !== params.profile);
276
+ }
277
+ }
278
+
279
+ allToolNames = allTools.map((t) => t.name);
280
+ applyProfile(null);
281
+ const activeCount = pi.getActiveTools().length;
282
+ const action = (isIncluded && !isExcluded) ? "disabled" : "enabled";
283
+ return { content: [{ type: "text", text: `Profile '${profile.label}' ${action}. ${activeCount}/${allToolNames.length} tools now active.` }], details: undefined };
284
+ }
285
+
286
+ default:
287
+ return { content: [{ type: "text", text: `Unknown action: ${params.action}` }], details: undefined } as any;
288
+ }
289
+ },
290
+ });
291
+ }