@oh-my-pi/pi-coding-agent 2.3.1337 → 3.0.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 (114) hide show
  1. package/CHANGELOG.md +62 -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 +222 -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/artifacts.ts +1 -1
  79. package/src/core/tools/task/commands.ts +30 -107
  80. package/src/core/tools/task/discovery.ts +54 -66
  81. package/src/core/tools/task/executor.ts +9 -9
  82. package/src/core/tools/task/index.ts +10 -10
  83. package/src/core/tools/task/model-resolver.ts +27 -25
  84. package/src/core/tools/task/types.ts +2 -2
  85. package/src/core/tools/web-fetch.ts +3 -3
  86. package/src/core/tools/web-search/auth.ts +40 -34
  87. package/src/core/tools/web-search/index.ts +1 -1
  88. package/src/core/tools/web-search/providers/anthropic.ts +1 -1
  89. package/src/discovery/agents-md.ts +75 -0
  90. package/src/discovery/builtin.ts +646 -0
  91. package/src/discovery/claude.ts +623 -0
  92. package/src/discovery/cline.ts +102 -0
  93. package/src/discovery/codex.ts +571 -0
  94. package/src/discovery/cursor.ts +264 -0
  95. package/src/discovery/gemini.ts +368 -0
  96. package/src/discovery/github.ts +120 -0
  97. package/src/discovery/helpers.test.ts +127 -0
  98. package/src/discovery/helpers.ts +249 -0
  99. package/src/discovery/index.ts +84 -0
  100. package/src/discovery/mcp-json.ts +127 -0
  101. package/src/discovery/vscode.ts +99 -0
  102. package/src/discovery/windsurf.ts +216 -0
  103. package/src/main.ts +14 -13
  104. package/src/migrations.ts +24 -3
  105. package/src/modes/interactive/components/hook-editor.ts +1 -1
  106. package/src/modes/interactive/components/plugin-settings.ts +1 -1
  107. package/src/modes/interactive/components/settings-defs.ts +38 -2
  108. package/src/modes/interactive/components/settings-selector.ts +1 -0
  109. package/src/modes/interactive/components/welcome.ts +2 -2
  110. package/src/modes/interactive/interactive-mode.ts +211 -16
  111. package/src/modes/interactive/theme/theme-schema.json +1 -1
  112. package/src/utils/clipboard.ts +1 -1
  113. package/src/utils/shell-snapshot.ts +2 -2
  114. package/src/utils/shell.ts +7 -7
@@ -5,11 +5,11 @@
5
5
  * to avoid import resolution issues with custom tools loaded from user directories.
6
6
  */
7
7
 
8
- import * as fs from "node:fs";
9
8
  import * as os from "node:os";
10
9
  import * as path from "node:path";
11
10
  import * as typebox from "@sinclair/typebox";
12
- import { getAgentDir } from "../../config";
11
+ import { toolCapability } from "../../capability/tool";
12
+ import { type CustomTool, loadSync } from "../../discovery";
13
13
  import * as piCodingAgent from "../../index";
14
14
  import { theme } from "../../modes/interactive/theme/theme";
15
15
  import type { ExecOptions } from "../exec";
@@ -73,6 +73,13 @@ function createNoOpUIContext(): HookUIContext {
73
73
  };
74
74
  }
75
75
 
76
+ /** Error with source metadata */
77
+ interface ToolLoadError {
78
+ path: string;
79
+ error: string;
80
+ source?: { provider: string; providerName: string; level: "user" | "project" };
81
+ }
82
+
76
83
  /**
77
84
  * Load a single tool module using native Bun import.
78
85
  */
@@ -80,15 +87,28 @@ async function loadTool(
80
87
  toolPath: string,
81
88
  cwd: string,
82
89
  sharedApi: CustomToolAPI,
83
- ): Promise<{ tools: LoadedCustomTool[] | null; error: string | null }> {
90
+ source?: { provider: string; providerName: string; level: "user" | "project" },
91
+ ): Promise<{ tools: LoadedCustomTool[] | null; error: ToolLoadError | null }> {
84
92
  const resolvedPath = resolveToolPath(toolPath, cwd);
85
93
 
94
+ // Skip declarative tool files (.md, .json) - these are metadata only, not executable modules
95
+ if (resolvedPath.endsWith(".md") || resolvedPath.endsWith(".json")) {
96
+ return {
97
+ tools: null,
98
+ error: {
99
+ path: toolPath,
100
+ error: "Declarative tool files (.md, .json) cannot be loaded as executable modules",
101
+ source,
102
+ },
103
+ };
104
+ }
105
+
86
106
  try {
87
107
  const module = await import(resolvedPath);
88
108
  const factory = (module.default ?? module) as CustomToolFactory;
89
109
 
90
110
  if (typeof factory !== "function") {
91
- return { tools: null, error: "Tool must export a default function" };
111
+ return { tools: null, error: { path: toolPath, error: "Tool must export a default function", source } };
92
112
  }
93
113
 
94
114
  const toolResult = await factory(sharedApi);
@@ -98,28 +118,35 @@ async function loadTool(
98
118
  path: toolPath,
99
119
  resolvedPath,
100
120
  tool,
121
+ source,
101
122
  }));
102
123
 
103
124
  return { tools: loadedTools, error: null };
104
125
  } catch (err) {
105
126
  const message = err instanceof Error ? err.message : String(err);
106
- return { tools: null, error: `Failed to load tool: ${message}` };
127
+ return { tools: null, error: { path: toolPath, error: `Failed to load tool: ${message}`, source } };
107
128
  }
108
129
  }
109
130
 
131
+ /** Tool path with optional source metadata */
132
+ interface ToolPathWithSource {
133
+ path: string;
134
+ source?: { provider: string; providerName: string; level: "user" | "project" };
135
+ }
136
+
110
137
  /**
111
138
  * Load all tools from configuration.
112
- * @param paths - Array of tool file paths
139
+ * @param pathsWithSources - Array of tool paths with optional source metadata
113
140
  * @param cwd - Current working directory for resolving relative paths
114
141
  * @param builtInToolNames - Names of built-in tools to check for conflicts
115
142
  */
116
143
  export async function loadCustomTools(
117
- paths: string[],
144
+ pathsWithSources: ToolPathWithSource[],
118
145
  cwd: string,
119
146
  builtInToolNames: string[],
120
147
  ): Promise<CustomToolsLoadResult> {
121
148
  const tools: LoadedCustomTool[] = [];
122
- const errors: Array<{ path: string; error: string }> = [];
149
+ const errors: ToolLoadError[] = [];
123
150
  const seenNames = new Set<string>(builtInToolNames);
124
151
 
125
152
  // Shared API object - all tools get the same instance
@@ -134,11 +161,11 @@ export async function loadCustomTools(
134
161
  pi: piCodingAgent,
135
162
  };
136
163
 
137
- for (const toolPath of paths) {
138
- const { tools: loadedTools, error } = await loadTool(toolPath, cwd, sharedApi);
164
+ for (const { path: toolPath, source } of pathsWithSources) {
165
+ const { tools: loadedTools, error } = await loadTool(toolPath, cwd, sharedApi, source);
139
166
 
140
167
  if (error) {
141
- errors.push({ path: toolPath, error });
168
+ errors.push(error);
142
169
  continue;
143
170
  }
144
171
 
@@ -149,6 +176,7 @@ export async function loadCustomTools(
149
176
  errors.push({
150
177
  path: toolPath,
151
178
  error: `Tool name "${loadedTool.tool.name}" conflicts with existing tool`,
179
+ source,
152
180
  });
153
181
  continue;
154
182
  }
@@ -170,81 +198,51 @@ export async function loadCustomTools(
170
198
  }
171
199
 
172
200
  /**
173
- * Discover tool files from a directory.
174
- * Only loads index.ts files from subdirectories (e.g., tools/mytool/index.ts).
175
- */
176
- function discoverToolsInDir(dir: string): string[] {
177
- if (!fs.existsSync(dir)) {
178
- return [];
179
- }
180
-
181
- const tools: string[] = [];
182
-
183
- try {
184
- const entries = fs.readdirSync(dir, { withFileTypes: true });
185
-
186
- for (const entry of entries) {
187
- if (entry.isDirectory() || entry.isSymbolicLink()) {
188
- // Check for index.ts in subdirectory
189
- const indexPath = path.join(dir, entry.name, "index.ts");
190
- if (fs.existsSync(indexPath)) {
191
- tools.push(indexPath);
192
- }
193
- }
194
- }
195
- } catch {
196
- return [];
197
- }
198
-
199
- return tools;
200
- }
201
-
202
- /**
203
- * Discover and load tools from standard locations:
204
- * 1. agentDir/tools/*.ts (global)
205
- * 2. cwd/.pi/tools/*.ts (project-local)
206
- * 3. Installed plugins (~/.pi/plugins/node_modules/*)
207
- *
208
- * Plus any explicitly configured paths from settings or CLI.
201
+ * Discover and load tools from standard locations via capability system:
202
+ * 1. User and project tools discovered by capability providers
203
+ * 2. Installed plugins (~/.omp/plugins/node_modules/*)
204
+ * 3. Explicitly configured paths from settings or CLI
209
205
  *
210
206
  * @param configuredPaths - Explicit paths from settings.json and CLI --tool flags
211
207
  * @param cwd - Current working directory
212
208
  * @param builtInToolNames - Names of built-in tools to check for conflicts
213
- * @param agentDir - Agent config directory. Default: from getAgentDir()
214
209
  */
215
210
  export async function discoverAndLoadCustomTools(
216
211
  configuredPaths: string[],
217
212
  cwd: string,
218
213
  builtInToolNames: string[],
219
- agentDir: string = getAgentDir(),
220
214
  ): Promise<CustomToolsLoadResult> {
221
- const allPaths: string[] = [];
215
+ const allPathsWithSources: ToolPathWithSource[] = [];
222
216
  const seen = new Set<string>();
223
217
 
224
218
  // Helper to add paths without duplicates
225
- const addPaths = (paths: string[]) => {
226
- for (const p of paths) {
227
- const resolved = path.resolve(p);
228
- if (!seen.has(resolved)) {
229
- seen.add(resolved);
230
- allPaths.push(p);
231
- }
219
+ const addPath = (p: string, source?: { provider: string; providerName: string; level: "user" | "project" }) => {
220
+ const resolved = path.resolve(p);
221
+ if (!seen.has(resolved)) {
222
+ seen.add(resolved);
223
+ allPathsWithSources.push({ path: p, source });
232
224
  }
233
225
  };
234
226
 
235
- // 1. Global tools: agentDir/tools/
236
- const globalToolsDir = path.join(agentDir, "tools");
237
- addPaths(discoverToolsInDir(globalToolsDir));
238
-
239
- // 2. Project-local tools: cwd/.pi/tools/
240
- const localToolsDir = path.join(cwd, ".pi", "tools");
241
- addPaths(discoverToolsInDir(localToolsDir));
227
+ // 1. Discover tools via capability system (user + project from all providers)
228
+ const discoveredTools = loadSync<CustomTool>(toolCapability.id, { cwd });
229
+ for (const tool of discoveredTools.items) {
230
+ addPath(tool.path, {
231
+ provider: tool._source.provider,
232
+ providerName: tool._source.providerName,
233
+ level: tool.level,
234
+ });
235
+ }
242
236
 
243
- // 3. Plugin tools: ~/.pi/plugins/node_modules/*/
244
- addPaths(getAllPluginToolPaths(cwd));
237
+ // 2. Plugin tools: ~/.omp/plugins/node_modules/*/
238
+ for (const pluginPath of getAllPluginToolPaths(cwd)) {
239
+ addPath(pluginPath, { provider: "plugin", providerName: "Plugin", level: "user" });
240
+ }
245
241
 
246
- // 4. Explicitly configured paths (can override/add)
247
- addPaths(configuredPaths.map((p) => resolveToolPath(p, cwd)));
242
+ // 3. Explicitly configured paths (can override/add)
243
+ for (const configPath of configuredPaths) {
244
+ addPath(resolveToolPath(configPath, cwd), { provider: "config", providerName: "Config", level: "project" });
245
+ }
248
246
 
249
- return loadCustomTools(allPaths, cwd, builtInToolNames);
247
+ return loadCustomTools(allPathsWithSources, cwd, builtInToolNames);
250
248
  }
@@ -163,12 +163,21 @@ export interface LoadedCustomTool {
163
163
  resolvedPath: string;
164
164
  /** The original custom tool instance */
165
165
  tool: CustomTool;
166
+ /** Source metadata (provider and level) */
167
+ source?: { provider: string; providerName: string; level: "user" | "project" };
168
+ }
169
+
170
+ /** Error with source metadata */
171
+ export interface ToolLoadError {
172
+ path: string;
173
+ error: string;
174
+ source?: { provider: string; providerName: string; level: "user" | "project" };
166
175
  }
167
176
 
168
177
  /** Result from loading custom tools */
169
178
  export interface CustomToolsLoadResult {
170
179
  tools: LoadedCustomTool[];
171
- errors: Array<{ path: string; error: string }>;
180
+ errors: ToolLoadError[];
172
181
  /** Update the UI context for all loaded tools. Call when mode initializes. */
173
182
  setUIContext(uiContext: CustomToolUIContext, hasUI: boolean): void;
174
183
  }
@@ -2,15 +2,15 @@
2
2
  * Hook loader - loads TypeScript hook modules using native Bun import.
3
3
  */
4
4
 
5
- import * as fs from "node:fs";
6
5
  import * as os from "node:os";
7
6
  import * as path from "node:path";
8
7
  import * as typebox from "@sinclair/typebox";
9
- import { getAgentDir } from "../../config";
8
+ import { hookCapability } from "../../capability/hook";
9
+ import type { Hook } from "../../discovery";
10
+ import { loadSync } from "../../discovery";
10
11
  import * as piCodingAgent from "../../index";
11
12
  import { logger } from "../logger";
12
13
  import type { HookMessage } from "../messages";
13
- import { getAllPluginHookPaths } from "../plugins/loader";
14
14
  import type { SessionManager } from "../session-manager";
15
15
  import { execCommand } from "./runner";
16
16
  import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
@@ -254,37 +254,15 @@ export async function loadHooks(paths: string[], cwd: string): Promise<LoadHooks
254
254
  }
255
255
 
256
256
  /**
257
- * Discover hook files from a directory.
258
- * Returns all .ts files (and symlinks to .ts files) in the directory (non-recursive).
259
- */
260
- function discoverHooksInDir(dir: string): string[] {
261
- if (!fs.existsSync(dir)) {
262
- return [];
263
- }
264
-
265
- try {
266
- const entries = fs.readdirSync(dir, { withFileTypes: true });
267
- return entries
268
- .filter((e) => (e.isFile() || e.isSymbolicLink()) && e.name.endsWith(".ts"))
269
- .map((e) => path.join(dir, e.name));
270
- } catch {
271
- return [];
272
- }
273
- }
274
-
275
- /**
276
- * Discover and load hooks from standard locations:
277
- * 1. agentDir/hooks/*.ts (global)
278
- * 2. cwd/.pi/hooks/*.ts (project-local)
279
- * 3. Installed plugins (~/.pi/plugins/node_modules/*)
257
+ * Discover and load hooks from all registered providers.
258
+ * Uses the capability API to discover hook paths from:
259
+ * 1. OMP native configs (.omp/.pi hooks/)
260
+ * 2. Installed plugins
261
+ * 3. Other editor/IDE configurations
280
262
  *
281
263
  * Plus any explicitly configured paths from settings.
282
264
  */
283
- export async function discoverAndLoadHooks(
284
- configuredPaths: string[],
285
- cwd: string,
286
- agentDir: string = getAgentDir(),
287
- ): Promise<LoadHooksResult> {
265
+ export async function discoverAndLoadHooks(configuredPaths: string[], cwd: string): Promise<LoadHooksResult> {
288
266
  const allPaths: string[] = [];
289
267
  const seen = new Set<string>();
290
268
 
@@ -299,18 +277,11 @@ export async function discoverAndLoadHooks(
299
277
  }
300
278
  };
301
279
 
302
- // 1. Global hooks: agentDir/hooks/
303
- const globalHooksDir = path.join(agentDir, "hooks");
304
- addPaths(discoverHooksInDir(globalHooksDir));
305
-
306
- // 2. Project-local hooks: cwd/.pi/hooks/
307
- const localHooksDir = path.join(cwd, ".pi", "hooks");
308
- addPaths(discoverHooksInDir(localHooksDir));
309
-
310
- // 3. Plugin hooks: ~/.pi/plugins/node_modules/*/
311
- addPaths(getAllPluginHookPaths(cwd));
280
+ // 1. Discover hooks via capability API
281
+ const discovered = loadSync<Hook>(hookCapability.id, { cwd });
282
+ addPaths(discovered.items.map((hook) => hook.path));
312
283
 
313
- // 4. Explicitly configured paths (can override/add)
284
+ // 2. Explicitly configured paths (can override/add)
314
285
  addPaths(configuredPaths.map((p) => resolveHookPath(p, cwd)));
315
286
 
316
287
  return loadHooks(allPaths, cwd);
package/src/core/index.ts CHANGED
@@ -38,7 +38,6 @@ export {
38
38
  export {
39
39
  createMCPManager,
40
40
  discoverAndLoadMCPTools,
41
- expandEnvVars,
42
41
  loadAllMCPConfigs,
43
42
  type MCPConfigFile,
44
43
  type MCPLoadResult,
@@ -1,7 +1,7 @@
1
1
  /**
2
- * Centralized file logger for pi.
2
+ * Centralized file logger for omp.
3
3
  *
4
- * Logs to ~/.pi/logs/ with size-based rotation, supporting concurrent pi instances.
4
+ * Logs to ~/.omp/logs/ with size-based rotation, supporting concurrent omp instances.
5
5
  * Each log entry includes process.pid for traceability.
6
6
  */
7
7
 
@@ -12,7 +12,7 @@ import winston from "winston";
12
12
  import DailyRotateFile from "winston-daily-rotate-file";
13
13
  import { CONFIG_DIR_NAME } from "../config";
14
14
 
15
- /** Get the logs directory (~/.pi/logs/) */
15
+ /** Get the logs directory (~/.omp/logs/) */
16
16
  function getLogsDir(): string {
17
17
  return join(homedir(), CONFIG_DIR_NAME, "logs");
18
18
  }
@@ -49,7 +49,7 @@ const logFormat = winston.format.combine(
49
49
  /** Size-based rotating file transport */
50
50
  const fileTransport = new DailyRotateFile({
51
51
  dirname: ensureLogsDir(),
52
- filename: "pi.%DATE%.log",
52
+ filename: "omp.%DATE%.log",
53
53
  datePattern: "YYYY-MM-DD",
54
54
  maxSize: "10m",
55
55
  maxFiles: 5,
@@ -73,10 +73,10 @@ export interface Logger {
73
73
  }
74
74
 
75
75
  /**
76
- * Centralized logger for pi.
76
+ * Centralized logger for omp.
77
77
  *
78
- * Logs to ~/.pi/logs/pi.YYYY-MM-DD.log with size-based rotation.
79
- * Safe for concurrent access from multiple pi instances.
78
+ * Logs to ~/.omp/logs/omp.YYYY-MM-DD.log with size-based rotation.
79
+ * Safe for concurrent access from multiple omp instances.
80
80
  *
81
81
  * @example
82
82
  * ```typescript
@@ -30,7 +30,7 @@ const CONNECTION_TIMEOUT_MS = 30_000;
30
30
 
31
31
  /** Client info sent during initialization */
32
32
  const CLIENT_INFO = {
33
- name: "pi-coding-agent",
33
+ name: "omp-coding-agent",
34
34
  version: "1.0.0",
35
35
  };
36
36