@oh-my-pi/pi-coding-agent 2.3.1337 → 3.1.1337

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 (117) hide show
  1. package/CHANGELOG.md +72 -34
  2. package/README.md +100 -100
  3. package/docs/compaction.md +8 -8
  4. package/docs/config-usage.md +113 -0
  5. package/docs/custom-tools.md +8 -8
  6. package/docs/extension-loading.md +58 -58
  7. package/docs/hooks.md +11 -11
  8. package/docs/rpc.md +4 -4
  9. package/docs/sdk.md +14 -14
  10. package/docs/session-tree-plan.md +1 -1
  11. package/docs/session.md +2 -2
  12. package/docs/skills.md +16 -16
  13. package/docs/theme.md +9 -9
  14. package/docs/tui.md +1 -1
  15. package/examples/README.md +1 -1
  16. package/examples/custom-tools/README.md +4 -4
  17. package/examples/custom-tools/subagent/README.md +13 -13
  18. package/examples/custom-tools/subagent/agents.ts +2 -2
  19. package/examples/custom-tools/subagent/index.ts +5 -5
  20. package/examples/hooks/README.md +3 -3
  21. package/examples/hooks/auto-commit-on-exit.ts +1 -1
  22. package/examples/hooks/custom-compaction.ts +1 -1
  23. package/examples/sdk/01-minimal.ts +1 -1
  24. package/examples/sdk/04-skills.ts +1 -1
  25. package/examples/sdk/05-tools.ts +1 -1
  26. package/examples/sdk/08-slash-commands.ts +1 -1
  27. package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
  28. package/examples/sdk/README.md +2 -2
  29. package/package.json +13 -11
  30. package/src/capability/context-file.ts +40 -0
  31. package/src/capability/extension.ts +48 -0
  32. package/src/capability/hook.ts +40 -0
  33. package/src/capability/index.ts +616 -0
  34. package/src/capability/instruction.ts +37 -0
  35. package/src/capability/mcp.ts +52 -0
  36. package/src/capability/prompt.ts +35 -0
  37. package/src/capability/rule.ts +52 -0
  38. package/src/capability/settings.ts +35 -0
  39. package/src/capability/skill.ts +49 -0
  40. package/src/capability/slash-command.ts +40 -0
  41. package/src/capability/system-prompt.ts +35 -0
  42. package/src/capability/tool.ts +38 -0
  43. package/src/capability/types.ts +166 -0
  44. package/src/cli/args.ts +2 -2
  45. package/src/cli/plugin-cli.ts +24 -19
  46. package/src/cli/update-cli.ts +10 -10
  47. package/src/config.ts +290 -6
  48. package/src/core/auth-storage.ts +32 -9
  49. package/src/core/bash-executor.ts +1 -1
  50. package/src/core/custom-commands/loader.ts +44 -50
  51. package/src/core/custom-tools/index.ts +1 -0
  52. package/src/core/custom-tools/loader.ts +67 -69
  53. package/src/core/custom-tools/types.ts +10 -1
  54. package/src/core/hooks/loader.ts +13 -42
  55. package/src/core/index.ts +0 -1
  56. package/src/core/logger.ts +7 -7
  57. package/src/core/mcp/client.ts +1 -1
  58. package/src/core/mcp/config.ts +94 -146
  59. package/src/core/mcp/index.ts +0 -4
  60. package/src/core/mcp/loader.ts +26 -22
  61. package/src/core/mcp/manager.ts +18 -23
  62. package/src/core/mcp/tool-bridge.ts +9 -1
  63. package/src/core/mcp/types.ts +2 -0
  64. package/src/core/model-registry.ts +25 -8
  65. package/src/core/plugins/installer.ts +1 -1
  66. package/src/core/plugins/loader.ts +17 -11
  67. package/src/core/plugins/manager.ts +2 -2
  68. package/src/core/plugins/paths.ts +12 -7
  69. package/src/core/plugins/types.ts +3 -3
  70. package/src/core/sdk.ts +48 -27
  71. package/src/core/session-manager.ts +4 -4
  72. package/src/core/settings-manager.ts +45 -21
  73. package/src/core/skills.ts +208 -293
  74. package/src/core/slash-commands.ts +34 -165
  75. package/src/core/system-prompt.ts +58 -65
  76. package/src/core/timings.ts +2 -2
  77. package/src/core/tools/lsp/config.ts +38 -17
  78. package/src/core/tools/task/agents.ts +21 -0
  79. package/src/core/tools/task/artifacts.ts +1 -1
  80. package/src/core/tools/task/bundled-agents/reviewer.md +2 -1
  81. package/src/core/tools/task/bundled-agents/task.md +1 -0
  82. package/src/core/tools/task/commands.ts +30 -107
  83. package/src/core/tools/task/discovery.ts +75 -66
  84. package/src/core/tools/task/executor.ts +25 -10
  85. package/src/core/tools/task/index.ts +35 -10
  86. package/src/core/tools/task/model-resolver.ts +27 -25
  87. package/src/core/tools/task/types.ts +6 -2
  88. package/src/core/tools/web-fetch.ts +3 -3
  89. package/src/core/tools/web-search/auth.ts +40 -34
  90. package/src/core/tools/web-search/index.ts +1 -1
  91. package/src/core/tools/web-search/providers/anthropic.ts +1 -1
  92. package/src/discovery/agents-md.ts +75 -0
  93. package/src/discovery/builtin.ts +646 -0
  94. package/src/discovery/claude.ts +623 -0
  95. package/src/discovery/cline.ts +102 -0
  96. package/src/discovery/codex.ts +571 -0
  97. package/src/discovery/cursor.ts +264 -0
  98. package/src/discovery/gemini.ts +368 -0
  99. package/src/discovery/github.ts +120 -0
  100. package/src/discovery/helpers.test.ts +127 -0
  101. package/src/discovery/helpers.ts +249 -0
  102. package/src/discovery/index.ts +84 -0
  103. package/src/discovery/mcp-json.ts +127 -0
  104. package/src/discovery/vscode.ts +99 -0
  105. package/src/discovery/windsurf.ts +216 -0
  106. package/src/main.ts +14 -13
  107. package/src/migrations.ts +24 -3
  108. package/src/modes/interactive/components/hook-editor.ts +1 -1
  109. package/src/modes/interactive/components/plugin-settings.ts +1 -1
  110. package/src/modes/interactive/components/settings-defs.ts +38 -2
  111. package/src/modes/interactive/components/settings-selector.ts +1 -0
  112. package/src/modes/interactive/components/welcome.ts +2 -2
  113. package/src/modes/interactive/interactive-mode.ts +233 -16
  114. package/src/modes/interactive/theme/theme-schema.json +1 -1
  115. package/src/utils/clipboard.ts +1 -1
  116. package/src/utils/shell-snapshot.ts +2 -2
  117. package/src/utils/shell.ts +7 -7
@@ -0,0 +1,99 @@
1
+ /**
2
+ * VS Code Provider
3
+ *
4
+ * Loads config from `.vscode` directory (project-only).
5
+ * Supports MCP server discovery from `mcp.json` with nested `mcp.servers` structure.
6
+ */
7
+
8
+ import { registerProvider } from "../capability/index";
9
+ import { type MCPServer, mcpCapability } from "../capability/mcp";
10
+ import type { LoadContext, LoadResult } from "../capability/types";
11
+ import { createSourceMeta, expandEnvVarsDeep, getProjectPath, parseJSON } from "./helpers";
12
+
13
+ const PROVIDER_ID = "vscode";
14
+ const DISPLAY_NAME = "VS Code";
15
+ const PRIORITY = 20;
16
+
17
+ // =============================================================================
18
+ // MCP Servers
19
+ // =============================================================================
20
+
21
+ registerProvider<MCPServer>(mcpCapability.id, {
22
+ id: PROVIDER_ID,
23
+ displayName: DISPLAY_NAME,
24
+ description: "Load MCP servers from .vscode/mcp.json",
25
+ priority: PRIORITY,
26
+ load(ctx: LoadContext): LoadResult<MCPServer> {
27
+ const items: MCPServer[] = [];
28
+ const warnings: string[] = [];
29
+
30
+ // Project-only (VS Code doesn't support user-level MCP config)
31
+ const projectPath = getProjectPath(ctx, "vscode", "mcp.json");
32
+ if (projectPath && ctx.fs.isFile(projectPath)) {
33
+ const result = loadMCPConfig(ctx, projectPath, "project");
34
+ items.push(...result.items);
35
+ if (result.warnings) warnings.push(...result.warnings);
36
+ }
37
+
38
+ return { items, warnings };
39
+ },
40
+ });
41
+
42
+ /**
43
+ * Load MCP servers from a mcp.json file.
44
+ * VS Code uses nested structure: { "mcp": { "servers": { ... } } }
45
+ */
46
+ function loadMCPConfig(ctx: LoadContext, path: string, level: "user" | "project"): LoadResult<MCPServer> {
47
+ const items: MCPServer[] = [];
48
+ const warnings: string[] = [];
49
+
50
+ const content = ctx.fs.readFile(path);
51
+ if (!content) {
52
+ warnings.push(`Failed to read ${path}`);
53
+ return { items, warnings };
54
+ }
55
+
56
+ const parsed = parseJSON<{ mcp?: { servers?: Record<string, unknown> } }>(content);
57
+ if (!parsed) {
58
+ warnings.push(`Invalid JSON in ${path}`);
59
+ return { items, warnings };
60
+ }
61
+
62
+ // VS Code uses nested structure: mcp.servers
63
+ const servers = parsed.mcp?.servers;
64
+ if (!servers || typeof servers !== "object") {
65
+ return { items, warnings };
66
+ }
67
+
68
+ for (const [name, config] of Object.entries(servers)) {
69
+ if (!config || typeof config !== "object") {
70
+ warnings.push(`Invalid config for server "${name}" in ${path}`);
71
+ continue;
72
+ }
73
+
74
+ const raw = config as Record<string, unknown>;
75
+
76
+ // Expand environment variables
77
+ const expanded = expandEnvVarsDeep(raw);
78
+
79
+ const server: MCPServer = {
80
+ name,
81
+ command: typeof expanded.command === "string" ? expanded.command : undefined,
82
+ args: Array.isArray(expanded.args) ? (expanded.args as string[]) : undefined,
83
+ env: expanded.env && typeof expanded.env === "object" ? (expanded.env as Record<string, string>) : undefined,
84
+ url: typeof expanded.url === "string" ? expanded.url : undefined,
85
+ headers:
86
+ expanded.headers && typeof expanded.headers === "object"
87
+ ? (expanded.headers as Record<string, string>)
88
+ : undefined,
89
+ transport: ["stdio", "sse", "http"].includes(expanded.transport as string)
90
+ ? (expanded.transport as "stdio" | "sse" | "http")
91
+ : undefined,
92
+ _source: createSourceMeta(PROVIDER_ID, path, level),
93
+ };
94
+
95
+ items.push(server);
96
+ }
97
+
98
+ return { items, warnings };
99
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Windsurf (Codeium) Provider
3
+ *
4
+ * Loads configuration from Windsurf's config locations:
5
+ * - User: ~/.codeium/windsurf
6
+ * - Project: .windsurf
7
+ *
8
+ * Supports:
9
+ * - MCP servers from mcp_config.json
10
+ * - Rules from .windsurf/rules/*.md and ~/.codeium/windsurf/memories/global_rules.md
11
+ * - Legacy .windsurfrules file
12
+ */
13
+
14
+ import { registerProvider } from "../capability/index";
15
+ import { type MCPServer, mcpCapability } from "../capability/mcp";
16
+ import { type Rule, ruleCapability } from "../capability/rule";
17
+ import type { LoadContext, LoadResult } from "../capability/types";
18
+ import {
19
+ createSourceMeta,
20
+ expandEnvVarsDeep,
21
+ getProjectPath,
22
+ getUserPath,
23
+ loadFilesFromDir,
24
+ parseFrontmatter,
25
+ parseJSON,
26
+ } from "./helpers";
27
+
28
+ const PROVIDER_ID = "windsurf";
29
+ const DISPLAY_NAME = "Windsurf";
30
+ const PRIORITY = 50;
31
+
32
+ // =============================================================================
33
+ // MCP Servers
34
+ // =============================================================================
35
+
36
+ function loadMCPServers(ctx: LoadContext): LoadResult<MCPServer> {
37
+ const items: MCPServer[] = [];
38
+ const warnings: string[] = [];
39
+
40
+ // User-level: ~/.codeium/windsurf/mcp_config.json
41
+ const userPath = getUserPath(ctx, "windsurf", "mcp_config.json");
42
+ if (userPath && ctx.fs.isFile(userPath)) {
43
+ const content = ctx.fs.readFile(userPath);
44
+ if (content) {
45
+ const config = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
46
+ if (config?.mcpServers) {
47
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
48
+ if (typeof serverConfig !== "object" || serverConfig === null) {
49
+ warnings.push(`Invalid server config for "${name}" in ${userPath}`);
50
+ continue;
51
+ }
52
+
53
+ const server = expandEnvVarsDeep(serverConfig as Record<string, unknown>);
54
+ items.push({
55
+ name,
56
+ command: server.command as string | undefined,
57
+ args: server.args as string[] | undefined,
58
+ env: server.env as Record<string, string> | undefined,
59
+ url: server.url as string | undefined,
60
+ headers: server.headers as Record<string, string> | undefined,
61
+ transport: server.type as "stdio" | "sse" | "http" | undefined,
62
+ _source: createSourceMeta(PROVIDER_ID, userPath, "user"),
63
+ });
64
+ }
65
+ }
66
+ }
67
+ }
68
+
69
+ // Project-level: .windsurf/mcp_config.json
70
+ const projectPath = getProjectPath(ctx, "windsurf", "mcp_config.json");
71
+ if (projectPath && ctx.fs.isFile(projectPath)) {
72
+ const content = ctx.fs.readFile(projectPath);
73
+ if (content) {
74
+ const config = parseJSON<{ mcpServers?: Record<string, unknown> }>(content);
75
+ if (config?.mcpServers) {
76
+ for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
77
+ if (typeof serverConfig !== "object" || serverConfig === null) {
78
+ warnings.push(`Invalid server config for "${name}" in ${projectPath}`);
79
+ continue;
80
+ }
81
+
82
+ const server = expandEnvVarsDeep(serverConfig as Record<string, unknown>);
83
+ items.push({
84
+ name,
85
+ command: server.command as string | undefined,
86
+ args: server.args as string[] | undefined,
87
+ env: server.env as Record<string, string> | undefined,
88
+ url: server.url as string | undefined,
89
+ headers: server.headers as Record<string, string> | undefined,
90
+ transport: server.type as "stdio" | "sse" | "http" | undefined,
91
+ _source: createSourceMeta(PROVIDER_ID, projectPath, "project"),
92
+ });
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ return { items, warnings };
99
+ }
100
+
101
+ // =============================================================================
102
+ // Rules
103
+ // =============================================================================
104
+
105
+ function loadRules(ctx: LoadContext): LoadResult<Rule> {
106
+ const items: Rule[] = [];
107
+ const warnings: string[] = [];
108
+
109
+ // User-level: ~/.codeium/windsurf/memories/global_rules.md
110
+ const userPath = getUserPath(ctx, "windsurf", "memories/global_rules.md");
111
+ if (userPath && ctx.fs.isFile(userPath)) {
112
+ const content = ctx.fs.readFile(userPath);
113
+ if (content) {
114
+ const { frontmatter, body } = parseFrontmatter(content);
115
+
116
+ // Validate and normalize globs
117
+ let globs: string[] | undefined;
118
+ if (Array.isArray(frontmatter.globs)) {
119
+ globs = frontmatter.globs.filter((g): g is string => typeof g === "string");
120
+ } else if (typeof frontmatter.globs === "string") {
121
+ globs = [frontmatter.globs];
122
+ }
123
+
124
+ items.push({
125
+ name: "global_rules",
126
+ path: userPath,
127
+ content: body,
128
+ globs,
129
+ alwaysApply: frontmatter.alwaysApply as boolean | undefined,
130
+ description: frontmatter.description as string | undefined,
131
+ _source: createSourceMeta(PROVIDER_ID, userPath, "user"),
132
+ });
133
+ }
134
+ }
135
+
136
+ // Project-level: .windsurf/rules/*.md
137
+ const projectRulesDir = getProjectPath(ctx, "windsurf", "rules");
138
+ if (projectRulesDir) {
139
+ const result = loadFilesFromDir<Rule>(ctx, projectRulesDir, PROVIDER_ID, "project", {
140
+ extensions: ["md"],
141
+ transform: (name, content, path, source) => {
142
+ const { frontmatter, body } = parseFrontmatter(content);
143
+ const ruleName = name.replace(/\.md$/, "");
144
+
145
+ // Validate and normalize globs
146
+ let globs: string[] | undefined;
147
+ if (Array.isArray(frontmatter.globs)) {
148
+ globs = frontmatter.globs.filter((g): g is string => typeof g === "string");
149
+ } else if (typeof frontmatter.globs === "string") {
150
+ globs = [frontmatter.globs];
151
+ }
152
+
153
+ return {
154
+ name: ruleName,
155
+ path,
156
+ content: body,
157
+ globs,
158
+ alwaysApply: frontmatter.alwaysApply as boolean | undefined,
159
+ description: frontmatter.description as string | undefined,
160
+ _source: source,
161
+ };
162
+ },
163
+ });
164
+ items.push(...result.items);
165
+ if (result.warnings) warnings.push(...result.warnings);
166
+ }
167
+
168
+ // Legacy: .windsurfrules in project root
169
+ const legacyPath = ctx.fs.walkUp(".windsurfrules", { file: true });
170
+ if (legacyPath) {
171
+ const content = ctx.fs.readFile(legacyPath);
172
+ if (content) {
173
+ const { frontmatter, body } = parseFrontmatter(content);
174
+
175
+ // Validate and normalize globs
176
+ let globs: string[] | undefined;
177
+ if (Array.isArray(frontmatter.globs)) {
178
+ globs = frontmatter.globs.filter((g): g is string => typeof g === "string");
179
+ } else if (typeof frontmatter.globs === "string") {
180
+ globs = [frontmatter.globs];
181
+ }
182
+
183
+ items.push({
184
+ name: "windsurfrules",
185
+ path: legacyPath,
186
+ content: body,
187
+ globs,
188
+ alwaysApply: frontmatter.alwaysApply as boolean | undefined,
189
+ description: frontmatter.description as string | undefined,
190
+ _source: createSourceMeta(PROVIDER_ID, legacyPath, "project"),
191
+ });
192
+ }
193
+ }
194
+
195
+ return { items, warnings };
196
+ }
197
+
198
+ // =============================================================================
199
+ // Provider Registration
200
+ // =============================================================================
201
+
202
+ registerProvider<MCPServer>(mcpCapability.id, {
203
+ id: PROVIDER_ID,
204
+ displayName: DISPLAY_NAME,
205
+ description: "Load MCP servers from Windsurf config (mcp_config.json)",
206
+ priority: PRIORITY,
207
+ load: loadMCPServers,
208
+ });
209
+
210
+ registerProvider<Rule>(ruleCapability.id, {
211
+ id: PROVIDER_ID,
212
+ displayName: DISPLAY_NAME,
213
+ description: "Load rules from Windsurf (.windsurf/rules/*.md, memories/global_rules.md, .windsurfrules)",
214
+ priority: PRIORITY,
215
+ load: loadRules,
216
+ });
package/src/main.ts CHANGED
@@ -5,8 +5,6 @@
5
5
  * createAgentSession() options. The SDK does the heavy lifting.
6
6
  */
7
7
 
8
- import { existsSync } from "node:fs";
9
- import { join } from "node:path";
10
8
  import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
11
9
  import chalk from "chalk";
12
10
  import { type Args, parseArgs, printHelp } from "./cli/args";
@@ -15,7 +13,7 @@ import { listModels } from "./cli/list-models";
15
13
  import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
16
14
  import { selectSession } from "./cli/session-picker";
17
15
  import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
18
- import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config";
16
+ import { findConfigFile, getModelsPath, VERSION } from "./config";
19
17
  import type { AgentSession } from "./core/agent-session";
20
18
  import type { LoadedCustomTool } from "./core/custom-tools/index";
21
19
  import { exportFromFile } from "./core/export-html/index";
@@ -199,18 +197,16 @@ function createSessionManager(parsed: Args, cwd: string): SessionManager | undef
199
197
 
200
198
  /** Discover SYSTEM.md file if no CLI system prompt was provided */
201
199
  function discoverSystemPromptFile(): string | undefined {
202
- // Check project-local first: .pi/SYSTEM.md
203
- const projectPath = join(process.cwd(), CONFIG_DIR_NAME, "SYSTEM.md");
204
- if (existsSync(projectPath)) {
200
+ // Check project-local first (.omp/SYSTEM.md, .pi/SYSTEM.md legacy)
201
+ const projectPath = findConfigFile("SYSTEM.md", { user: false });
202
+ if (projectPath) {
205
203
  return projectPath;
206
204
  }
207
-
208
- // Fall back to global: ~/.pi/agent/SYSTEM.md
209
- const globalPath = join(getAgentDir(), "SYSTEM.md");
210
- if (existsSync(globalPath)) {
205
+ // If not found, check SYSTEM.md file in the global directory.
206
+ const globalPath = findConfigFile("SYSTEM.md", { user: true });
207
+ if (globalPath) {
211
208
  return globalPath;
212
209
  }
213
-
214
210
  return undefined;
215
211
  }
216
212
 
@@ -374,9 +370,14 @@ export async function main(args: string[]) {
374
370
  const settingsManager = SettingsManager.create(cwd);
375
371
  time("SettingsManager.create");
376
372
 
373
+ // Initialize discovery system with settings for provider persistence
374
+ const { initializeWithSettings } = await import("./discovery");
375
+ initializeWithSettings(settingsManager);
376
+ time("initializeWithSettings");
377
+
377
378
  // Apply model role overrides from CLI args or env vars (ephemeral, not persisted)
378
- const smolModel = parsed.smol ?? process.env.PI_SMOL_MODEL;
379
- const slowModel = parsed.slow ?? process.env.PI_SLOW_MODEL;
379
+ const smolModel = parsed.smol ?? process.env.OMP_SMOL_MODEL;
380
+ const slowModel = parsed.slow ?? process.env.OMP_SLOW_MODEL;
380
381
  if (smolModel || slowModel) {
381
382
  const roleOverrides: Record<string, string> = {};
382
383
  if (smolModel) roleOverrides.smol = smolModel;
package/src/migrations.ts CHANGED
@@ -6,6 +6,22 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFile
6
6
  import { dirname, join } from "node:path";
7
7
  import { getAgentDir } from "./config";
8
8
 
9
+ /**
10
+ * Migrate PI_* environment variables to OMP_* equivalents.
11
+ * If PI_XX is set and OMP_XX is not, set OMP_XX to PI_XX's value.
12
+ * This provides backwards compatibility for users with existing PI_* env vars.
13
+ */
14
+ export function migrateEnvVars(): void {
15
+ for (const [key, value] of Object.entries(process.env)) {
16
+ if (key.startsWith("PI_") && value !== undefined) {
17
+ const ompKey = `OMP_${key.slice(3)}`; // PI_FOO -> OMP_FOO
18
+ if (process.env[ompKey] === undefined) {
19
+ process.env[ompKey] = value;
20
+ }
21
+ }
22
+ }
23
+ }
24
+
9
25
  /**
10
26
  * Migrate legacy oauth.json and settings.json apiKeys to auth.json.
11
27
  *
@@ -66,10 +82,10 @@ export function migrateAuthToAuthJson(): string[] {
66
82
  }
67
83
 
68
84
  /**
69
- * Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.
85
+ * Migrate sessions from ~/.omp/agent/*.jsonl to proper session directories.
70
86
  *
71
- * Bug in v0.30.0: Sessions were saved to ~/.pi/agent/ instead of
72
- * ~/.pi/agent/sessions/<encoded-cwd>/. This migration moves them
87
+ * Bug in v0.30.0: Sessions were saved to ~/.omp/agent/ instead of
88
+ * ~/.omp/agent/sessions/<encoded-cwd>/. This migration moves them
73
89
  * to the correct location based on the cwd in their session header.
74
90
  *
75
91
  * See: https://github.com/badlogic/pi-mono/issues/320
@@ -129,7 +145,12 @@ export function migrateSessionsFromAgentRoot(): void {
129
145
  * @returns Object with migration results
130
146
  */
131
147
  export function runMigrations(): { migratedAuthProviders: string[] } {
148
+ // First: migrate env vars (before anything else reads them)
149
+ migrateEnvVars();
150
+
151
+ // Then: run data migrations
132
152
  const migratedAuthProviders = migrateAuthToAuthJson();
133
153
  migrateSessionsFromAgentRoot();
154
+
134
155
  return { migratedAuthProviders };
135
156
  }
@@ -89,7 +89,7 @@ export class HookEditorComponent extends Container {
89
89
  }
90
90
 
91
91
  const currentText = this.editor.getText();
92
- const tmpFile = path.join(os.tmpdir(), `pi-hook-editor-${Date.now()}.md`);
92
+ const tmpFile = path.join(os.tmpdir(), `omp-hook-editor-${Date.now()}.md`);
93
93
 
94
94
  try {
95
95
  fs.writeFileSync(tmpFile, currentText, "utf-8");
@@ -52,7 +52,7 @@ export class PluginListComponent extends Container {
52
52
  if (plugins.length === 0) {
53
53
  this.addChild(new Text(theme.fg("muted", " No plugins installed"), 0, 0));
54
54
  this.addChild(new Spacer(1));
55
- this.addChild(new Text(theme.fg("dim", " Install with: pi plugin install <package>"), 0, 0));
55
+ this.addChild(new Text(theme.fg("dim", " Install with: omp plugin install <package>"), 0, 0));
56
56
  this.addChild(new Spacer(1));
57
57
  this.addChild(new DynamicBorder());
58
58
 
@@ -11,6 +11,7 @@
11
11
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
12
12
  import { getCapabilities } from "@oh-my-pi/pi-tui";
13
13
  import type { SettingsManager } from "../../../core/settings-manager";
14
+ import { getAllProvidersInfo, isProviderEnabled } from "../../../discovery";
14
15
 
15
16
  // Setting value types
16
17
  export type SettingValue = boolean | string;
@@ -266,12 +267,47 @@ export const SETTINGS_DEFS: SettingDef[] = [
266
267
  },
267
268
  ];
268
269
 
270
+ /**
271
+ * Get discovery provider settings dynamically.
272
+ * These are generated at runtime from getAllProvidersInfo().
273
+ */
274
+ function getDiscoverySettings(): SettingDef[] {
275
+ const providers = getAllProvidersInfo();
276
+ const settings: SettingDef[] = [];
277
+
278
+ for (const provider of providers) {
279
+ // Skip native provider - it can't be disabled
280
+ if (provider.id === "native") {
281
+ continue;
282
+ }
283
+
284
+ settings.push({
285
+ id: `discovery.${provider.id}`,
286
+ tab: "discovery",
287
+ type: "boolean",
288
+ label: provider.displayName,
289
+ description: provider.description,
290
+ get: () => isProviderEnabled(provider.id),
291
+ set: () => {}, // Handled in interactive-mode.ts
292
+ });
293
+ }
294
+
295
+ return settings;
296
+ }
297
+
298
+ /**
299
+ * All settings with dynamic discovery settings merged in.
300
+ */
301
+ function getAllSettings(): SettingDef[] {
302
+ return [...SETTINGS_DEFS, ...getDiscoverySettings()];
303
+ }
304
+
269
305
  /** Get settings for a specific tab */
270
306
  export function getSettingsForTab(tab: string): SettingDef[] {
271
- return SETTINGS_DEFS.filter((def) => def.tab === tab);
307
+ return getAllSettings().filter((def) => def.tab === tab);
272
308
  }
273
309
 
274
310
  /** Get a setting definition by id */
275
311
  export function getSettingDef(id: string): SettingDef | undefined {
276
- return SETTINGS_DEFS.find((def) => def.id === id);
312
+ return getAllSettings().find((def) => def.id === id);
277
313
  }
@@ -99,6 +99,7 @@ const SETTINGS_TABS: Tab[] = [
99
99
  { id: "config", label: "Config" },
100
100
  { id: "lsp", label: "LSP" },
101
101
  { id: "exa", label: "Exa" },
102
+ { id: "discovery", label: "Discovery" },
102
103
  { id: "plugins", label: "Plugins" },
103
104
  ];
104
105
 
@@ -14,7 +14,7 @@ export interface LspServerInfo {
14
14
  }
15
15
 
16
16
  /**
17
- * Premium welcome screen with block-based Pi logo and two-column layout.
17
+ * Premium welcome screen with block-based OMP logo and two-column layout.
18
18
  */
19
19
  export class WelcomeComponent implements Component {
20
20
  private version: string;
@@ -60,7 +60,7 @@ export class WelcomeComponent implements Component {
60
60
  const leftCol = 26;
61
61
  const rightCol = boxWidth - leftCol - 3; // 3 = │ + │ + │
62
62
 
63
- // Block-based Pi logo (gradient: magenta → cyan)
63
+ // Block-based OMP logo (gradient: magenta → cyan)
64
64
  // biome-ignore format: preserve ASCII art layout
65
65
  const piLogo = ["▀████████████▀", " ╘███ ███ ", " ███ ███ ", " ███ ███ ", " ▄███▄ ▄███▄ "];
66
66