@oh-my-pi/pi-coding-agent 1.337.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 (224) hide show
  1. package/CHANGELOG.md +1228 -0
  2. package/README.md +1041 -0
  3. package/docs/compaction.md +403 -0
  4. package/docs/custom-tools.md +541 -0
  5. package/docs/extension-loading.md +1004 -0
  6. package/docs/hooks.md +867 -0
  7. package/docs/rpc.md +1040 -0
  8. package/docs/sdk.md +994 -0
  9. package/docs/session-tree-plan.md +441 -0
  10. package/docs/session.md +240 -0
  11. package/docs/skills.md +290 -0
  12. package/docs/theme.md +637 -0
  13. package/docs/tree.md +197 -0
  14. package/docs/tui.md +341 -0
  15. package/examples/README.md +21 -0
  16. package/examples/custom-tools/README.md +124 -0
  17. package/examples/custom-tools/hello/index.ts +20 -0
  18. package/examples/custom-tools/question/index.ts +84 -0
  19. package/examples/custom-tools/subagent/README.md +172 -0
  20. package/examples/custom-tools/subagent/agents/planner.md +37 -0
  21. package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
  22. package/examples/custom-tools/subagent/agents/scout.md +50 -0
  23. package/examples/custom-tools/subagent/agents/worker.md +24 -0
  24. package/examples/custom-tools/subagent/agents.ts +156 -0
  25. package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
  26. package/examples/custom-tools/subagent/commands/implement.md +10 -0
  27. package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
  28. package/examples/custom-tools/subagent/index.ts +1002 -0
  29. package/examples/custom-tools/todo/index.ts +212 -0
  30. package/examples/hooks/README.md +56 -0
  31. package/examples/hooks/auto-commit-on-exit.ts +49 -0
  32. package/examples/hooks/confirm-destructive.ts +59 -0
  33. package/examples/hooks/custom-compaction.ts +116 -0
  34. package/examples/hooks/dirty-repo-guard.ts +52 -0
  35. package/examples/hooks/file-trigger.ts +41 -0
  36. package/examples/hooks/git-checkpoint.ts +53 -0
  37. package/examples/hooks/handoff.ts +150 -0
  38. package/examples/hooks/permission-gate.ts +34 -0
  39. package/examples/hooks/protected-paths.ts +30 -0
  40. package/examples/hooks/qna.ts +119 -0
  41. package/examples/hooks/snake.ts +343 -0
  42. package/examples/hooks/status-line.ts +40 -0
  43. package/examples/sdk/01-minimal.ts +22 -0
  44. package/examples/sdk/02-custom-model.ts +49 -0
  45. package/examples/sdk/03-custom-prompt.ts +44 -0
  46. package/examples/sdk/04-skills.ts +44 -0
  47. package/examples/sdk/05-tools.ts +90 -0
  48. package/examples/sdk/06-hooks.ts +61 -0
  49. package/examples/sdk/07-context-files.ts +36 -0
  50. package/examples/sdk/08-slash-commands.ts +42 -0
  51. package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
  52. package/examples/sdk/10-settings.ts +38 -0
  53. package/examples/sdk/11-sessions.ts +48 -0
  54. package/examples/sdk/12-full-control.ts +95 -0
  55. package/examples/sdk/README.md +154 -0
  56. package/package.json +81 -0
  57. package/src/cli/args.ts +246 -0
  58. package/src/cli/file-processor.ts +72 -0
  59. package/src/cli/list-models.ts +104 -0
  60. package/src/cli/plugin-cli.ts +650 -0
  61. package/src/cli/session-picker.ts +41 -0
  62. package/src/cli.ts +10 -0
  63. package/src/commands/init.md +20 -0
  64. package/src/config.ts +159 -0
  65. package/src/core/agent-session.ts +1900 -0
  66. package/src/core/auth-storage.ts +236 -0
  67. package/src/core/bash-executor.ts +196 -0
  68. package/src/core/compaction/branch-summarization.ts +343 -0
  69. package/src/core/compaction/compaction.ts +742 -0
  70. package/src/core/compaction/index.ts +7 -0
  71. package/src/core/compaction/utils.ts +154 -0
  72. package/src/core/custom-tools/index.ts +21 -0
  73. package/src/core/custom-tools/loader.ts +248 -0
  74. package/src/core/custom-tools/types.ts +169 -0
  75. package/src/core/custom-tools/wrapper.ts +28 -0
  76. package/src/core/exec.ts +129 -0
  77. package/src/core/export-html/index.ts +211 -0
  78. package/src/core/export-html/template.css +781 -0
  79. package/src/core/export-html/template.html +54 -0
  80. package/src/core/export-html/template.js +1185 -0
  81. package/src/core/export-html/vendor/highlight.min.js +1213 -0
  82. package/src/core/export-html/vendor/marked.min.js +6 -0
  83. package/src/core/hooks/index.ts +16 -0
  84. package/src/core/hooks/loader.ts +312 -0
  85. package/src/core/hooks/runner.ts +434 -0
  86. package/src/core/hooks/tool-wrapper.ts +99 -0
  87. package/src/core/hooks/types.ts +773 -0
  88. package/src/core/index.ts +52 -0
  89. package/src/core/mcp/client.ts +158 -0
  90. package/src/core/mcp/config.ts +154 -0
  91. package/src/core/mcp/index.ts +45 -0
  92. package/src/core/mcp/loader.ts +68 -0
  93. package/src/core/mcp/manager.ts +181 -0
  94. package/src/core/mcp/tool-bridge.ts +148 -0
  95. package/src/core/mcp/transports/http.ts +316 -0
  96. package/src/core/mcp/transports/index.ts +6 -0
  97. package/src/core/mcp/transports/stdio.ts +252 -0
  98. package/src/core/mcp/types.ts +220 -0
  99. package/src/core/messages.ts +189 -0
  100. package/src/core/model-registry.ts +317 -0
  101. package/src/core/model-resolver.ts +393 -0
  102. package/src/core/plugins/doctor.ts +59 -0
  103. package/src/core/plugins/index.ts +38 -0
  104. package/src/core/plugins/installer.ts +189 -0
  105. package/src/core/plugins/loader.ts +338 -0
  106. package/src/core/plugins/manager.ts +672 -0
  107. package/src/core/plugins/parser.ts +105 -0
  108. package/src/core/plugins/paths.ts +32 -0
  109. package/src/core/plugins/types.ts +190 -0
  110. package/src/core/sdk.ts +760 -0
  111. package/src/core/session-manager.ts +1128 -0
  112. package/src/core/settings-manager.ts +443 -0
  113. package/src/core/skills.ts +437 -0
  114. package/src/core/slash-commands.ts +248 -0
  115. package/src/core/system-prompt.ts +439 -0
  116. package/src/core/timings.ts +25 -0
  117. package/src/core/tools/ask.ts +211 -0
  118. package/src/core/tools/bash-interceptor.ts +120 -0
  119. package/src/core/tools/bash.ts +250 -0
  120. package/src/core/tools/context.ts +32 -0
  121. package/src/core/tools/edit-diff.ts +475 -0
  122. package/src/core/tools/edit.ts +208 -0
  123. package/src/core/tools/exa/company.ts +59 -0
  124. package/src/core/tools/exa/index.ts +64 -0
  125. package/src/core/tools/exa/linkedin.ts +59 -0
  126. package/src/core/tools/exa/logger.ts +56 -0
  127. package/src/core/tools/exa/mcp-client.ts +368 -0
  128. package/src/core/tools/exa/render.ts +196 -0
  129. package/src/core/tools/exa/researcher.ts +90 -0
  130. package/src/core/tools/exa/search.ts +337 -0
  131. package/src/core/tools/exa/types.ts +168 -0
  132. package/src/core/tools/exa/websets.ts +248 -0
  133. package/src/core/tools/find.ts +261 -0
  134. package/src/core/tools/grep.ts +555 -0
  135. package/src/core/tools/index.ts +202 -0
  136. package/src/core/tools/ls.ts +140 -0
  137. package/src/core/tools/lsp/client.ts +605 -0
  138. package/src/core/tools/lsp/config.ts +147 -0
  139. package/src/core/tools/lsp/edits.ts +101 -0
  140. package/src/core/tools/lsp/index.ts +804 -0
  141. package/src/core/tools/lsp/render.ts +447 -0
  142. package/src/core/tools/lsp/rust-analyzer.ts +145 -0
  143. package/src/core/tools/lsp/types.ts +463 -0
  144. package/src/core/tools/lsp/utils.ts +486 -0
  145. package/src/core/tools/notebook.ts +229 -0
  146. package/src/core/tools/path-utils.ts +61 -0
  147. package/src/core/tools/read.ts +240 -0
  148. package/src/core/tools/renderers.ts +540 -0
  149. package/src/core/tools/task/agents.ts +153 -0
  150. package/src/core/tools/task/artifacts.ts +114 -0
  151. package/src/core/tools/task/bundled-agents/browser.md +71 -0
  152. package/src/core/tools/task/bundled-agents/explore.md +82 -0
  153. package/src/core/tools/task/bundled-agents/plan.md +54 -0
  154. package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
  155. package/src/core/tools/task/bundled-agents/task.md +53 -0
  156. package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
  157. package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
  158. package/src/core/tools/task/bundled-commands/implement.md +11 -0
  159. package/src/core/tools/task/commands.ts +213 -0
  160. package/src/core/tools/task/discovery.ts +208 -0
  161. package/src/core/tools/task/executor.ts +367 -0
  162. package/src/core/tools/task/index.ts +388 -0
  163. package/src/core/tools/task/model-resolver.ts +115 -0
  164. package/src/core/tools/task/parallel.ts +38 -0
  165. package/src/core/tools/task/render.ts +232 -0
  166. package/src/core/tools/task/types.ts +99 -0
  167. package/src/core/tools/truncate.ts +265 -0
  168. package/src/core/tools/web-fetch.ts +2370 -0
  169. package/src/core/tools/web-search/auth.ts +193 -0
  170. package/src/core/tools/web-search/index.ts +537 -0
  171. package/src/core/tools/web-search/providers/anthropic.ts +198 -0
  172. package/src/core/tools/web-search/providers/exa.ts +302 -0
  173. package/src/core/tools/web-search/providers/perplexity.ts +195 -0
  174. package/src/core/tools/web-search/render.ts +182 -0
  175. package/src/core/tools/web-search/types.ts +180 -0
  176. package/src/core/tools/write.ts +99 -0
  177. package/src/index.ts +176 -0
  178. package/src/main.ts +464 -0
  179. package/src/migrations.ts +135 -0
  180. package/src/modes/index.ts +43 -0
  181. package/src/modes/interactive/components/armin.ts +382 -0
  182. package/src/modes/interactive/components/assistant-message.ts +86 -0
  183. package/src/modes/interactive/components/bash-execution.ts +196 -0
  184. package/src/modes/interactive/components/bordered-loader.ts +41 -0
  185. package/src/modes/interactive/components/branch-summary-message.ts +42 -0
  186. package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
  187. package/src/modes/interactive/components/custom-editor.ts +122 -0
  188. package/src/modes/interactive/components/diff.ts +147 -0
  189. package/src/modes/interactive/components/dynamic-border.ts +25 -0
  190. package/src/modes/interactive/components/footer.ts +381 -0
  191. package/src/modes/interactive/components/hook-editor.ts +117 -0
  192. package/src/modes/interactive/components/hook-input.ts +64 -0
  193. package/src/modes/interactive/components/hook-message.ts +96 -0
  194. package/src/modes/interactive/components/hook-selector.ts +91 -0
  195. package/src/modes/interactive/components/model-selector.ts +247 -0
  196. package/src/modes/interactive/components/oauth-selector.ts +120 -0
  197. package/src/modes/interactive/components/plugin-settings.ts +479 -0
  198. package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
  199. package/src/modes/interactive/components/session-selector.ts +204 -0
  200. package/src/modes/interactive/components/settings-selector.ts +453 -0
  201. package/src/modes/interactive/components/show-images-selector.ts +45 -0
  202. package/src/modes/interactive/components/theme-selector.ts +62 -0
  203. package/src/modes/interactive/components/thinking-selector.ts +64 -0
  204. package/src/modes/interactive/components/tool-execution.ts +675 -0
  205. package/src/modes/interactive/components/tree-selector.ts +866 -0
  206. package/src/modes/interactive/components/user-message-selector.ts +159 -0
  207. package/src/modes/interactive/components/user-message.ts +18 -0
  208. package/src/modes/interactive/components/visual-truncate.ts +50 -0
  209. package/src/modes/interactive/components/welcome.ts +183 -0
  210. package/src/modes/interactive/interactive-mode.ts +2516 -0
  211. package/src/modes/interactive/theme/dark.json +101 -0
  212. package/src/modes/interactive/theme/light.json +98 -0
  213. package/src/modes/interactive/theme/theme-schema.json +308 -0
  214. package/src/modes/interactive/theme/theme.ts +998 -0
  215. package/src/modes/print-mode.ts +128 -0
  216. package/src/modes/rpc/rpc-client.ts +527 -0
  217. package/src/modes/rpc/rpc-mode.ts +483 -0
  218. package/src/modes/rpc/rpc-types.ts +203 -0
  219. package/src/utils/changelog.ts +99 -0
  220. package/src/utils/clipboard.ts +265 -0
  221. package/src/utils/fuzzy.ts +108 -0
  222. package/src/utils/mime.ts +30 -0
  223. package/src/utils/shell.ts +276 -0
  224. package/src/utils/tools-manager.ts +274 -0
@@ -0,0 +1,168 @@
1
+ /**
2
+ * Exa MCP Types
3
+ *
4
+ * Types for the Exa MCP client and tool implementations.
5
+ */
6
+
7
+ import type { TSchema } from "@sinclair/typebox";
8
+
9
+ /** MCP endpoint URLs */
10
+ export const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
11
+ export const WEBSETS_MCP_URL = "https://websetsmcp.exa.ai/mcp";
12
+
13
+ /** MCP tool definition from server */
14
+ export interface MCPTool {
15
+ name: string;
16
+ description: string;
17
+ inputSchema: TSchema;
18
+ }
19
+
20
+ /** Tool wrapper config for dynamic MCP tool creation */
21
+ export interface MCPToolWrapperConfig {
22
+ /** Our tool name (e.g., "exa_search") */
23
+ name: string;
24
+ /** Display label for UI */
25
+ label: string;
26
+ /** MCP tool name to call (e.g., "web_search") */
27
+ mcpToolName: string;
28
+ /** Whether this is a websets tool (uses different MCP endpoint) */
29
+ isWebsetsTool?: boolean;
30
+ }
31
+
32
+ /** MCP tools/list response */
33
+ export interface MCPToolsResponse {
34
+ result?: {
35
+ tools: MCPTool[];
36
+ };
37
+ error?: {
38
+ code: number;
39
+ message: string;
40
+ };
41
+ }
42
+
43
+ /** MCP tools/call response */
44
+ export interface MCPCallResponse {
45
+ result?: {
46
+ content?: Array<{ type: string; text?: string }>;
47
+ };
48
+ error?: {
49
+ code: number;
50
+ message: string;
51
+ };
52
+ }
53
+
54
+ /** Search result from Exa */
55
+ export interface ExaSearchResult {
56
+ id?: string;
57
+ title?: string;
58
+ url?: string;
59
+ author?: string;
60
+ publishedDate?: string;
61
+ text?: string;
62
+ highlights?: string[];
63
+ image?: string;
64
+ favicon?: string;
65
+ }
66
+
67
+ /** Search response from Exa */
68
+ export interface ExaSearchResponse {
69
+ results?: ExaSearchResult[];
70
+ statuses?: Array<{ id: string; status: string; source?: string }>;
71
+ costDollars?: { total: number };
72
+ searchTime?: number;
73
+ requestId?: string;
74
+ }
75
+
76
+ /** Researcher task status */
77
+ export interface ResearcherStatus {
78
+ id: string;
79
+ status: "pending" | "running" | "completed" | "failed";
80
+ result?: string;
81
+ error?: string;
82
+ }
83
+
84
+ /** Webset definition */
85
+ export interface Webset {
86
+ id: string;
87
+ name: string;
88
+ description?: string;
89
+ createdAt?: string;
90
+ updatedAt?: string;
91
+ }
92
+
93
+ /** Webset item */
94
+ export interface WebsetItem {
95
+ id: string;
96
+ websetId: string;
97
+ url: string;
98
+ title?: string;
99
+ content?: string;
100
+ metadata?: Record<string, unknown>;
101
+ }
102
+
103
+ /** Webset search */
104
+ export interface WebsetSearch {
105
+ id: string;
106
+ websetId: string;
107
+ query: string;
108
+ status: "pending" | "running" | "completed" | "cancelled";
109
+ resultCount?: number;
110
+ }
111
+
112
+ /** Webset enrichment */
113
+ export interface WebsetEnrichment {
114
+ id: string;
115
+ websetId: string;
116
+ name: string;
117
+ prompt: string;
118
+ status: "pending" | "running" | "completed" | "cancelled";
119
+ }
120
+
121
+ /** Tool name mappings: MCP name -> our tool name */
122
+ export const EXA_TOOL_MAPPINGS = {
123
+ // Search tools
124
+ web_search: "exa_search",
125
+ deep_search_exa: "exa_search_deep",
126
+ get_code_context_exa: "exa_search_code",
127
+ crawling_exa: "exa_crawl",
128
+ // LinkedIn
129
+ linkedin_search_exa: "exa_linkedin",
130
+ // Company
131
+ company_research_exa: "exa_company",
132
+ // Researcher
133
+ deep_researcher_start: "exa_researcher_start",
134
+ deep_researcher_check: "exa_researcher_poll",
135
+ } as const;
136
+
137
+ export const WEBSETS_TOOL_MAPPINGS = {
138
+ create_webset: "webset_create",
139
+ list_websets: "webset_list",
140
+ get_webset: "webset_get",
141
+ update_webset: "webset_update",
142
+ delete_webset: "webset_delete",
143
+ list_webset_items: "webset_items_list",
144
+ get_item: "webset_item_get",
145
+ create_search: "webset_search_create",
146
+ get_search: "webset_search_get",
147
+ cancel_search: "webset_search_cancel",
148
+ create_enrichment: "webset_enrichment_create",
149
+ get_enrichment: "webset_enrichment_get",
150
+ update_enrichment: "webset_enrichment_update",
151
+ delete_enrichment: "webset_enrichment_delete",
152
+ cancel_enrichment: "webset_enrichment_cancel",
153
+ create_monitor: "webset_monitor_create",
154
+ } as const;
155
+
156
+ export type ExaMcpToolName = keyof typeof EXA_TOOL_MAPPINGS;
157
+ export type WebsetsMcpToolName = keyof typeof WEBSETS_TOOL_MAPPINGS;
158
+ export type ExaToolName = (typeof EXA_TOOL_MAPPINGS)[ExaMcpToolName];
159
+ export type WebsetsToolName = (typeof WEBSETS_TOOL_MAPPINGS)[WebsetsMcpToolName];
160
+
161
+ /** Render details for TUI */
162
+ export interface ExaRenderDetails {
163
+ response?: ExaSearchResponse;
164
+ error?: string;
165
+ toolName?: string;
166
+ /** Raw result for non-search responses */
167
+ raw?: unknown;
168
+ }
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Exa Websets Tools
3
+ *
4
+ * CRUD operations for websets, items, searches, enrichments, and monitoring.
5
+ */
6
+
7
+ import { Type } from "@sinclair/typebox";
8
+ import type { CustomTool } from "../../custom-tools/types.js";
9
+ import { callWebsetsTool, findApiKey } from "./mcp-client.js";
10
+ import type { ExaRenderDetails } from "./types.js";
11
+
12
+ /** Helper to create a websets tool with proper execute signature */
13
+ function createWebsetTool(
14
+ name: string,
15
+ label: string,
16
+ description: string,
17
+ parameters: ReturnType<typeof Type.Object>,
18
+ mcpToolName: string,
19
+ ): CustomTool<any, ExaRenderDetails> {
20
+ return {
21
+ name,
22
+ label,
23
+ description,
24
+ parameters,
25
+ async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
26
+ try {
27
+ const apiKey = await findApiKey();
28
+ if (!apiKey) {
29
+ return {
30
+ content: [{ type: "text" as const, text: "Error: EXA_API_KEY not found" }],
31
+ details: { error: "EXA_API_KEY not found", toolName: name },
32
+ };
33
+ }
34
+ const result = await callWebsetsTool(apiKey, mcpToolName, params as Record<string, unknown>);
35
+ return {
36
+ content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
37
+ details: { raw: result, toolName: name },
38
+ };
39
+ } catch (error) {
40
+ const message = error instanceof Error ? error.message : String(error);
41
+ return {
42
+ content: [{ type: "text" as const, text: `Error: ${message}` }],
43
+ details: { error: message, toolName: name },
44
+ };
45
+ }
46
+ },
47
+ };
48
+ }
49
+
50
+ // CRUD Operations
51
+ const websetCreateTool = createWebsetTool(
52
+ "webset_create",
53
+ "Create Webset",
54
+ "Create a new webset collection for organizing web content.",
55
+ Type.Object({
56
+ name: Type.String({ description: "Name of the webset" }),
57
+ description: Type.Optional(Type.String({ description: "Optional description" })),
58
+ }),
59
+ "create_webset",
60
+ );
61
+
62
+ const websetListTool = createWebsetTool(
63
+ "webset_list",
64
+ "List Websets",
65
+ "List all websets in your account.",
66
+ Type.Object({}),
67
+ "list_websets",
68
+ );
69
+
70
+ const websetGetTool = createWebsetTool(
71
+ "webset_get",
72
+ "Get Webset",
73
+ "Get details of a specific webset by ID.",
74
+ Type.Object({
75
+ id: Type.String({ description: "Webset ID" }),
76
+ }),
77
+ "get_webset",
78
+ );
79
+
80
+ const websetUpdateTool = createWebsetTool(
81
+ "webset_update",
82
+ "Update Webset",
83
+ "Update a webset's name or description.",
84
+ Type.Object({
85
+ id: Type.String({ description: "Webset ID" }),
86
+ name: Type.Optional(Type.String({ description: "New name" })),
87
+ description: Type.Optional(Type.String({ description: "New description" })),
88
+ }),
89
+ "update_webset",
90
+ );
91
+
92
+ const websetDeleteTool = createWebsetTool(
93
+ "webset_delete",
94
+ "Delete Webset",
95
+ "Delete a webset and all its contents.",
96
+ Type.Object({
97
+ id: Type.String({ description: "Webset ID" }),
98
+ }),
99
+ "delete_webset",
100
+ );
101
+
102
+ // Item Management
103
+ const websetItemsListTool = createWebsetTool(
104
+ "webset_items_list",
105
+ "List Webset Items",
106
+ "List items in a webset with optional pagination.",
107
+ Type.Object({
108
+ webset_id: Type.String({ description: "Webset ID" }),
109
+ limit: Type.Optional(Type.Number({ description: "Number of items to return" })),
110
+ offset: Type.Optional(Type.Number({ description: "Pagination offset" })),
111
+ }),
112
+ "list_webset_items",
113
+ );
114
+
115
+ const websetItemGetTool = createWebsetTool(
116
+ "webset_item_get",
117
+ "Get Webset Item",
118
+ "Get a specific item from a webset.",
119
+ Type.Object({
120
+ webset_id: Type.String({ description: "Webset ID" }),
121
+ item_id: Type.String({ description: "Item ID" }),
122
+ }),
123
+ "get_item",
124
+ );
125
+
126
+ // Search Operations
127
+ const websetSearchCreateTool = createWebsetTool(
128
+ "webset_search_create",
129
+ "Create Webset Search",
130
+ "Create a new search within a webset.",
131
+ Type.Object({
132
+ webset_id: Type.String({ description: "Webset ID" }),
133
+ query: Type.String({ description: "Search query" }),
134
+ }),
135
+ "create_search",
136
+ );
137
+
138
+ const websetSearchGetTool = createWebsetTool(
139
+ "webset_search_get",
140
+ "Get Webset Search",
141
+ "Get the status and results of a webset search.",
142
+ Type.Object({
143
+ webset_id: Type.String({ description: "Webset ID" }),
144
+ search_id: Type.String({ description: "Search ID" }),
145
+ }),
146
+ "get_search",
147
+ );
148
+
149
+ const websetSearchCancelTool = createWebsetTool(
150
+ "webset_search_cancel",
151
+ "Cancel Webset Search",
152
+ "Cancel a running webset search.",
153
+ Type.Object({
154
+ webset_id: Type.String({ description: "Webset ID" }),
155
+ search_id: Type.String({ description: "Search ID" }),
156
+ }),
157
+ "cancel_search",
158
+ );
159
+
160
+ // Enrichment Operations
161
+ const websetEnrichmentCreateTool = createWebsetTool(
162
+ "webset_enrichment_create",
163
+ "Create Enrichment",
164
+ "Create a new enrichment task for a webset.",
165
+ Type.Object({
166
+ webset_id: Type.String({ description: "Webset ID" }),
167
+ name: Type.String({ description: "Enrichment name" }),
168
+ prompt: Type.String({ description: "Enrichment prompt" }),
169
+ }),
170
+ "create_enrichment",
171
+ );
172
+
173
+ const websetEnrichmentGetTool = createWebsetTool(
174
+ "webset_enrichment_get",
175
+ "Get Enrichment",
176
+ "Get the status and results of an enrichment task.",
177
+ Type.Object({
178
+ webset_id: Type.String({ description: "Webset ID" }),
179
+ enrichment_id: Type.String({ description: "Enrichment ID" }),
180
+ }),
181
+ "get_enrichment",
182
+ );
183
+
184
+ const websetEnrichmentUpdateTool = createWebsetTool(
185
+ "webset_enrichment_update",
186
+ "Update Enrichment",
187
+ "Update an enrichment's name or prompt.",
188
+ Type.Object({
189
+ webset_id: Type.String({ description: "Webset ID" }),
190
+ enrichment_id: Type.String({ description: "Enrichment ID" }),
191
+ name: Type.Optional(Type.String({ description: "New name" })),
192
+ prompt: Type.Optional(Type.String({ description: "New prompt" })),
193
+ }),
194
+ "update_enrichment",
195
+ );
196
+
197
+ const websetEnrichmentDeleteTool = createWebsetTool(
198
+ "webset_enrichment_delete",
199
+ "Delete Enrichment",
200
+ "Delete an enrichment task.",
201
+ Type.Object({
202
+ webset_id: Type.String({ description: "Webset ID" }),
203
+ enrichment_id: Type.String({ description: "Enrichment ID" }),
204
+ }),
205
+ "delete_enrichment",
206
+ );
207
+
208
+ const websetEnrichmentCancelTool = createWebsetTool(
209
+ "webset_enrichment_cancel",
210
+ "Cancel Enrichment",
211
+ "Cancel a running enrichment task.",
212
+ Type.Object({
213
+ webset_id: Type.String({ description: "Webset ID" }),
214
+ enrichment_id: Type.String({ description: "Enrichment ID" }),
215
+ }),
216
+ "cancel_enrichment",
217
+ );
218
+
219
+ // Monitoring
220
+ const websetMonitorCreateTool = createWebsetTool(
221
+ "webset_monitor_create",
222
+ "Create Monitor",
223
+ "Create a monitoring task for a webset with optional webhook notifications.",
224
+ Type.Object({
225
+ webset_id: Type.String({ description: "Webset ID" }),
226
+ webhook_url: Type.Optional(Type.String({ description: "Webhook URL for notifications" })),
227
+ }),
228
+ "create_monitor",
229
+ );
230
+
231
+ export const websetsTools: CustomTool<any, ExaRenderDetails>[] = [
232
+ websetCreateTool,
233
+ websetListTool,
234
+ websetGetTool,
235
+ websetUpdateTool,
236
+ websetDeleteTool,
237
+ websetItemsListTool,
238
+ websetItemGetTool,
239
+ websetSearchCreateTool,
240
+ websetSearchGetTool,
241
+ websetSearchCancelTool,
242
+ websetEnrichmentCreateTool,
243
+ websetEnrichmentGetTool,
244
+ websetEnrichmentUpdateTool,
245
+ websetEnrichmentDeleteTool,
246
+ websetEnrichmentCancelTool,
247
+ websetMonitorCreateTool,
248
+ ];
@@ -0,0 +1,261 @@
1
+ import { existsSync, type Stats, statSync } from "node:fs";
2
+ import path from "node:path";
3
+ import type { AgentTool } from "@oh-my-pi/pi-agent-core";
4
+ import { Type } from "@sinclair/typebox";
5
+ import { globSync } from "glob";
6
+ import { ensureTool } from "../../utils/tools-manager.js";
7
+ import { resolveToCwd } from "./path-utils.js";
8
+ import { DEFAULT_MAX_BYTES, formatSize, type TruncationResult, truncateHead } from "./truncate.js";
9
+
10
+ const findSchema = Type.Object({
11
+ pattern: Type.String({
12
+ description: "Glob pattern to match files, e.g. '*.ts', '**/*.json', or 'src/**/*.spec.ts'",
13
+ }),
14
+ path: Type.Optional(Type.String({ description: "Directory to search in (default: current directory)" })),
15
+ limit: Type.Optional(Type.Number({ description: "Maximum number of results (default: 1000)" })),
16
+ hidden: Type.Optional(Type.Boolean({ description: "Include hidden files (default: false)" })),
17
+ sortByMtime: Type.Optional(
18
+ Type.Boolean({ description: "Sort results by modification time, most recent first (default: false)" }),
19
+ ),
20
+ type: Type.Optional(
21
+ Type.Union([Type.Literal("file"), Type.Literal("dir"), Type.Literal("all")], {
22
+ description:
23
+ "Filter by type: 'file' for files only, 'dir' for directories only, 'all' for both (default: 'all')",
24
+ }),
25
+ ),
26
+ });
27
+
28
+ const DEFAULT_LIMIT = 1000;
29
+
30
+ export interface FindToolDetails {
31
+ truncation?: TruncationResult;
32
+ resultLimitReached?: number;
33
+ // Fields for TUI rendering
34
+ fileCount?: number;
35
+ files?: string[];
36
+ truncated?: boolean;
37
+ error?: string;
38
+ }
39
+
40
+ export function createFindTool(cwd: string): AgentTool<typeof findSchema> {
41
+ return {
42
+ name: "find",
43
+ label: "Find",
44
+ description: `- Fast file pattern matching tool that works with any codebase size
45
+ - Supports glob patterns like "**/*.js" or "src/**/*.ts"
46
+ - Returns matching file paths sorted by modification time
47
+ - Use this tool when you need to find files by name patterns
48
+ - When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
49
+ - You can call multiple tools in a single response. It is always better to speculatively perform multiple searches in parallel if they are potentially useful.`,
50
+ parameters: findSchema,
51
+ execute: async (
52
+ _toolCallId: string,
53
+ {
54
+ pattern,
55
+ path: searchDir,
56
+ limit,
57
+ hidden,
58
+ sortByMtime,
59
+ type,
60
+ }: {
61
+ pattern: string;
62
+ path?: string;
63
+ limit?: number;
64
+ hidden?: boolean;
65
+ sortByMtime?: boolean;
66
+ type?: "file" | "dir" | "all";
67
+ },
68
+ signal?: AbortSignal,
69
+ ) => {
70
+ return new Promise((resolve, reject) => {
71
+ if (signal?.aborted) {
72
+ reject(new Error("Operation aborted"));
73
+ return;
74
+ }
75
+
76
+ const onAbort = () => reject(new Error("Operation aborted"));
77
+ signal?.addEventListener("abort", onAbort, { once: true });
78
+
79
+ (async () => {
80
+ try {
81
+ // Ensure fd is available
82
+ const fdPath = await ensureTool("fd", true);
83
+ if (!fdPath) {
84
+ reject(new Error("fd is not available and could not be downloaded"));
85
+ return;
86
+ }
87
+
88
+ const searchPath = resolveToCwd(searchDir || ".", cwd);
89
+ const effectiveLimit = limit ?? DEFAULT_LIMIT;
90
+ const effectiveType = type ?? "all";
91
+ const includeHidden = hidden ?? false;
92
+ const shouldSortByMtime = sortByMtime ?? false;
93
+
94
+ // Build fd arguments
95
+ const args: string[] = [
96
+ "--glob", // Use glob pattern
97
+ "--color=never", // No ANSI colors
98
+ "--max-results",
99
+ String(effectiveLimit),
100
+ ];
101
+
102
+ if (includeHidden) {
103
+ args.push("--hidden");
104
+ }
105
+
106
+ // Add type filter
107
+ if (effectiveType === "file") {
108
+ args.push("--type", "f");
109
+ } else if (effectiveType === "dir") {
110
+ args.push("--type", "d");
111
+ }
112
+
113
+ // Include .gitignore files (root + nested) so fd respects them even outside git repos
114
+ const gitignoreFiles = new Set<string>();
115
+ const rootGitignore = path.join(searchPath, ".gitignore");
116
+ if (existsSync(rootGitignore)) {
117
+ gitignoreFiles.add(rootGitignore);
118
+ }
119
+
120
+ try {
121
+ const nestedGitignores = globSync("**/.gitignore", {
122
+ cwd: searchPath,
123
+ dot: true,
124
+ absolute: true,
125
+ ignore: ["**/node_modules/**", "**/.git/**"],
126
+ });
127
+ for (const file of nestedGitignores) {
128
+ gitignoreFiles.add(file);
129
+ }
130
+ } catch {
131
+ // Ignore glob errors
132
+ }
133
+
134
+ for (const gitignorePath of gitignoreFiles) {
135
+ args.push("--ignore-file", gitignorePath);
136
+ }
137
+
138
+ // Pattern and path
139
+ args.push(pattern, searchPath);
140
+
141
+ // Run fd
142
+ const result = Bun.spawnSync([fdPath, ...args], {
143
+ stdin: "ignore",
144
+ stdout: "pipe",
145
+ stderr: "pipe",
146
+ });
147
+
148
+ signal?.removeEventListener("abort", onAbort);
149
+
150
+ const output = result.stdout.toString().trim();
151
+
152
+ if (result.exitCode !== 0) {
153
+ const errorMsg = result.stderr.toString().trim() || `fd exited with code ${result.exitCode}`;
154
+ // fd returns non-zero for some errors but may still have partial output
155
+ if (!output) {
156
+ reject(new Error(errorMsg));
157
+ return;
158
+ }
159
+ }
160
+
161
+ if (!output) {
162
+ resolve({
163
+ content: [{ type: "text", text: "No files found matching pattern" }],
164
+ details: { fileCount: 0, files: [], truncated: false },
165
+ });
166
+ return;
167
+ }
168
+
169
+ const lines = output.split("\n");
170
+ const relativized: string[] = [];
171
+ const mtimes: number[] = [];
172
+
173
+ for (const rawLine of lines) {
174
+ const line = rawLine.replace(/\r$/, "").trim();
175
+ if (!line) {
176
+ continue;
177
+ }
178
+
179
+ const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\");
180
+ let relativePath = line;
181
+ if (line.startsWith(searchPath)) {
182
+ relativePath = line.slice(searchPath.length + 1); // +1 for the /
183
+ } else {
184
+ relativePath = path.relative(searchPath, line);
185
+ }
186
+
187
+ if (hadTrailingSlash && !relativePath.endsWith("/")) {
188
+ relativePath += "/";
189
+ }
190
+
191
+ relativized.push(relativePath);
192
+
193
+ // Collect mtime if sorting is requested
194
+ if (shouldSortByMtime) {
195
+ try {
196
+ const fullPath = path.join(searchPath, relativePath);
197
+ const stat: Stats = statSync(fullPath);
198
+ mtimes.push(stat.mtimeMs);
199
+ } catch {
200
+ mtimes.push(0);
201
+ }
202
+ }
203
+ }
204
+
205
+ // Sort by mtime if requested (most recent first)
206
+ if (shouldSortByMtime && relativized.length > 0) {
207
+ const indexed = relativized.map((path, idx) => ({ path, mtime: mtimes[idx] || 0 }));
208
+ indexed.sort((a, b) => b.mtime - a.mtime);
209
+ relativized.length = 0;
210
+ relativized.push(...indexed.map((item) => item.path));
211
+ }
212
+
213
+ // Check if we hit the result limit
214
+ const resultLimitReached = relativized.length >= effectiveLimit;
215
+
216
+ // Apply byte truncation (no line limit since we already have result limit)
217
+ const rawOutput = relativized.join("\n");
218
+ const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
219
+
220
+ let resultOutput = truncation.content;
221
+ const details: FindToolDetails = {
222
+ fileCount: relativized.length,
223
+ files: relativized.slice(0, 50),
224
+ truncated: resultLimitReached || truncation.truncated,
225
+ };
226
+
227
+ // Build notices
228
+ const notices: string[] = [];
229
+
230
+ if (resultLimitReached) {
231
+ notices.push(
232
+ `${effectiveLimit} results limit reached. Use limit=${effectiveLimit * 2} for more, or refine pattern`,
233
+ );
234
+ details.resultLimitReached = effectiveLimit;
235
+ }
236
+
237
+ if (truncation.truncated) {
238
+ notices.push(`${formatSize(DEFAULT_MAX_BYTES)} limit reached`);
239
+ details.truncation = truncation;
240
+ }
241
+
242
+ if (notices.length > 0) {
243
+ resultOutput += `\n\n[${notices.join(". ")}]`;
244
+ }
245
+
246
+ resolve({
247
+ content: [{ type: "text", text: resultOutput }],
248
+ details: Object.keys(details).length > 0 ? details : undefined,
249
+ });
250
+ } catch (e: any) {
251
+ signal?.removeEventListener("abort", onAbort);
252
+ reject(e);
253
+ }
254
+ })();
255
+ });
256
+ },
257
+ };
258
+ }
259
+
260
+ /** Default find tool using process.cwd() - for backwards compatibility */
261
+ export const findTool = createFindTool(process.cwd());