@oh-my-pi/pi-coding-agent 1.340.0 → 2.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 (153) hide show
  1. package/CHANGELOG.md +115 -1
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +5 -3
  5. package/src/cli/args.ts +13 -6
  6. package/src/cli/file-processor.ts +3 -3
  7. package/src/cli/list-models.ts +2 -2
  8. package/src/cli/plugin-cli.ts +1 -1
  9. package/src/cli/session-picker.ts +2 -2
  10. package/src/cli.ts +1 -1
  11. package/src/config.ts +3 -3
  12. package/src/core/agent-session.ts +189 -29
  13. package/src/core/bash-executor.ts +50 -10
  14. package/src/core/compaction/branch-summarization.ts +5 -5
  15. package/src/core/compaction/compaction.ts +3 -3
  16. package/src/core/compaction/index.ts +3 -3
  17. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  18. package/src/core/custom-commands/index.ts +15 -0
  19. package/src/core/custom-commands/loader.ts +232 -0
  20. package/src/core/custom-commands/types.ts +112 -0
  21. package/src/core/custom-tools/index.ts +3 -3
  22. package/src/core/custom-tools/loader.ts +10 -8
  23. package/src/core/custom-tools/types.ts +11 -6
  24. package/src/core/custom-tools/wrapper.ts +2 -1
  25. package/src/core/exec.ts +22 -12
  26. package/src/core/export-html/index.ts +5 -5
  27. package/src/core/file-mentions.ts +54 -0
  28. package/src/core/hooks/index.ts +5 -5
  29. package/src/core/hooks/loader.ts +21 -16
  30. package/src/core/hooks/runner.ts +6 -6
  31. package/src/core/hooks/tool-wrapper.ts +2 -2
  32. package/src/core/hooks/types.ts +12 -15
  33. package/src/core/index.ts +6 -6
  34. package/src/core/logger.ts +112 -0
  35. package/src/core/mcp/client.ts +3 -3
  36. package/src/core/mcp/config.ts +1 -1
  37. package/src/core/mcp/index.ts +12 -12
  38. package/src/core/mcp/loader.ts +2 -2
  39. package/src/core/mcp/manager.ts +6 -6
  40. package/src/core/mcp/tool-bridge.ts +3 -3
  41. package/src/core/mcp/transports/http.ts +1 -1
  42. package/src/core/mcp/transports/index.ts +2 -2
  43. package/src/core/mcp/transports/stdio.ts +1 -1
  44. package/src/core/messages.ts +22 -0
  45. package/src/core/model-registry.ts +2 -2
  46. package/src/core/model-resolver.ts +103 -2
  47. package/src/core/plugins/doctor.ts +1 -1
  48. package/src/core/plugins/index.ts +6 -6
  49. package/src/core/plugins/installer.ts +4 -4
  50. package/src/core/plugins/loader.ts +4 -9
  51. package/src/core/plugins/manager.ts +5 -5
  52. package/src/core/plugins/paths.ts +3 -3
  53. package/src/core/sdk.ts +127 -52
  54. package/src/core/session-manager.ts +123 -20
  55. package/src/core/settings-manager.ts +106 -22
  56. package/src/core/skills.ts +5 -5
  57. package/src/core/slash-commands.ts +60 -45
  58. package/src/core/system-prompt.ts +6 -6
  59. package/src/core/title-generator.ts +94 -0
  60. package/src/core/tools/bash.ts +33 -157
  61. package/src/core/tools/context.ts +2 -2
  62. package/src/core/tools/edit-diff.ts +5 -5
  63. package/src/core/tools/edit.ts +60 -9
  64. package/src/core/tools/exa/company.ts +3 -3
  65. package/src/core/tools/exa/index.ts +16 -17
  66. package/src/core/tools/exa/linkedin.ts +3 -3
  67. package/src/core/tools/exa/mcp-client.ts +9 -9
  68. package/src/core/tools/exa/render.ts +5 -5
  69. package/src/core/tools/exa/researcher.ts +3 -3
  70. package/src/core/tools/exa/search.ts +6 -5
  71. package/src/core/tools/exa/types.ts +5 -6
  72. package/src/core/tools/exa/websets.ts +3 -3
  73. package/src/core/tools/find.ts +3 -3
  74. package/src/core/tools/grep.ts +6 -5
  75. package/src/core/tools/index.ts +114 -40
  76. package/src/core/tools/ls.ts +4 -4
  77. package/src/core/tools/lsp/client.ts +204 -108
  78. package/src/core/tools/lsp/config.ts +709 -35
  79. package/src/core/tools/lsp/edits.ts +2 -2
  80. package/src/core/tools/lsp/index.ts +432 -30
  81. package/src/core/tools/lsp/render.ts +2 -2
  82. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  83. package/src/core/tools/lsp/types.ts +5 -0
  84. package/src/core/tools/lsp/utils.ts +1 -1
  85. package/src/core/tools/notebook.ts +1 -1
  86. package/src/core/tools/output.ts +175 -0
  87. package/src/core/tools/read.ts +7 -7
  88. package/src/core/tools/renderers.ts +92 -13
  89. package/src/core/tools/review.ts +268 -0
  90. package/src/core/tools/task/agents.ts +1 -1
  91. package/src/core/tools/task/bundled-agents/explore.md +1 -1
  92. package/src/core/tools/task/bundled-agents/reviewer.md +53 -38
  93. package/src/core/tools/task/discovery.ts +2 -2
  94. package/src/core/tools/task/executor.ts +145 -28
  95. package/src/core/tools/task/index.ts +78 -30
  96. package/src/core/tools/task/model-resolver.ts +72 -13
  97. package/src/core/tools/task/parallel.ts +1 -1
  98. package/src/core/tools/task/render.ts +219 -30
  99. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  100. package/src/core/tools/task/types.ts +36 -2
  101. package/src/core/tools/web-fetch.ts +5 -3
  102. package/src/core/tools/web-search/auth.ts +1 -1
  103. package/src/core/tools/web-search/index.ts +17 -15
  104. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  105. package/src/core/tools/web-search/providers/exa.ts +3 -5
  106. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  107. package/src/core/tools/web-search/render.ts +3 -3
  108. package/src/core/tools/write.ts +70 -7
  109. package/src/index.ts +33 -17
  110. package/src/main.ts +60 -34
  111. package/src/migrations.ts +3 -3
  112. package/src/modes/index.ts +5 -5
  113. package/src/modes/interactive/components/armin.ts +1 -1
  114. package/src/modes/interactive/components/assistant-message.ts +1 -1
  115. package/src/modes/interactive/components/bash-execution.ts +4 -4
  116. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  117. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  118. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  119. package/src/modes/interactive/components/diff.ts +1 -1
  120. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  121. package/src/modes/interactive/components/footer.ts +5 -5
  122. package/src/modes/interactive/components/hook-editor.ts +2 -2
  123. package/src/modes/interactive/components/hook-input.ts +2 -2
  124. package/src/modes/interactive/components/hook-message.ts +3 -3
  125. package/src/modes/interactive/components/hook-selector.ts +2 -2
  126. package/src/modes/interactive/components/model-selector.ts +341 -41
  127. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  128. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  129. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  130. package/src/modes/interactive/components/session-selector.ts +24 -11
  131. package/src/modes/interactive/components/settings-defs.ts +51 -3
  132. package/src/modes/interactive/components/settings-selector.ts +13 -16
  133. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  134. package/src/modes/interactive/components/theme-selector.ts +2 -2
  135. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  136. package/src/modes/interactive/components/tool-execution.ts +44 -8
  137. package/src/modes/interactive/components/tree-selector.ts +5 -5
  138. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  139. package/src/modes/interactive/components/user-message.ts +1 -1
  140. package/src/modes/interactive/components/welcome.ts +42 -5
  141. package/src/modes/interactive/interactive-mode.ts +169 -48
  142. package/src/modes/interactive/theme/theme.ts +8 -7
  143. package/src/modes/print-mode.ts +4 -3
  144. package/src/modes/rpc/rpc-client.ts +4 -4
  145. package/src/modes/rpc/rpc-mode.ts +21 -11
  146. package/src/modes/rpc/rpc-types.ts +3 -3
  147. package/src/utils/changelog.ts +2 -2
  148. package/src/utils/clipboard.ts +1 -1
  149. package/src/utils/shell-snapshot.ts +218 -0
  150. package/src/utils/shell.ts +93 -13
  151. package/src/utils/tools-manager.ts +1 -1
  152. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  153. package/src/core/tools/exa/logger.ts +0 -56
@@ -14,16 +14,16 @@
14
14
 
15
15
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
16
16
  import { Type } from "@sinclair/typebox";
17
- import type { Theme } from "../../../modes/interactive/theme/theme.js";
18
- import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../custom-tools/types.js";
19
- import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../exa/mcp-client.js";
20
- import { renderExaCall, renderExaResult } from "../exa/render.js";
21
- import type { ExaRenderDetails } from "../exa/types.js";
22
- import { searchAnthropic } from "./providers/anthropic.js";
23
- import { searchExa } from "./providers/exa.js";
24
- import { findApiKey as findPerplexityKey, searchPerplexity } from "./providers/perplexity.js";
25
- import { formatAge, renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./render.js";
26
- import type { WebSearchProvider, WebSearchResponse } from "./types.js";
17
+ import type { Theme } from "../../../modes/interactive/theme/theme";
18
+ import type { CustomTool, CustomToolContext, RenderResultOptions } from "../../custom-tools/types";
19
+ import { callExaTool, findApiKey as findExaKey, formatSearchResults, isSearchResponse } from "../exa/mcp-client";
20
+ import { renderExaCall, renderExaResult } from "../exa/render";
21
+ import type { ExaRenderDetails } from "../exa/types";
22
+ import { searchAnthropic } from "./providers/anthropic";
23
+ import { searchExa } from "./providers/exa";
24
+ import { findApiKey as findPerplexityKey, searchPerplexity } from "./providers/perplexity";
25
+ import { formatAge, renderWebSearchCall, renderWebSearchResult, type WebSearchRenderDetails } from "./render";
26
+ import type { WebSearchProvider, WebSearchResponse } from "./types";
27
27
 
28
28
  /** Web search parameters schema */
29
29
  export const webSearchSchema = Type.Object({
@@ -345,7 +345,9 @@ Parameters:
345
345
  parameters: webSearchDeepSchema,
346
346
 
347
347
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
348
- return executeExaTool("deep_search_exa", params as Record<string, unknown>, "web_search_deep");
348
+ const { num_results, ...rest } = params as Record<string, unknown>;
349
+ const args = { ...rest, type: "deep", numResults: num_results ?? 10 };
350
+ return executeExaTool("web_search_exa", args, "web_search_deep");
349
351
  },
350
352
 
351
353
  renderCall(args, theme) {
@@ -404,7 +406,7 @@ Parameters:
404
406
  parameters: webSearchCrawlSchema,
405
407
 
406
408
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
407
- return executeExaTool("crawling_exa", params as Record<string, unknown>, "web_search_crawl");
409
+ return executeExaTool("crawling", params as Record<string, unknown>, "web_search_crawl");
408
410
  },
409
411
 
410
412
  renderCall(args, theme) {
@@ -435,7 +437,7 @@ Parameters:
435
437
  parameters: webSearchLinkedinSchema,
436
438
 
437
439
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
438
- return executeExaTool("linkedin_search_exa", params as Record<string, unknown>, "web_search_linkedin");
440
+ return executeExaTool("linkedin_search", params as Record<string, unknown>, "web_search_linkedin");
439
441
  },
440
442
 
441
443
  renderCall(args, theme) {
@@ -465,7 +467,7 @@ Parameters:
465
467
  parameters: webSearchCompanySchema,
466
468
 
467
469
  async execute(_toolCallId, params, _onUpdate, _ctx, _signal) {
468
- return executeExaTool("company_research_exa", params as Record<string, unknown>, "web_search_company");
470
+ return executeExaTool("company_research", params as Record<string, unknown>, "web_search_company");
469
471
  },
470
472
 
471
473
  renderCall(args, theme) {
@@ -534,4 +536,4 @@ export async function hasExaWebSearch(): Promise<boolean> {
534
536
  return exaKey !== null;
535
537
  }
536
538
 
537
- export type { WebSearchProvider, WebSearchResponse } from "./types.js";
539
+ export type { WebSearchProvider, WebSearchResponse } from "./types";
@@ -5,7 +5,7 @@
5
5
  * Returns synthesized answers with citations and source metadata.
6
6
  */
7
7
 
8
- import { buildAnthropicHeaders, buildAnthropicUrl, findAnthropicAuth, getEnv } from "../auth.js";
8
+ import { buildAnthropicHeaders, buildAnthropicUrl, findAnthropicAuth, getEnv } from "../auth";
9
9
  import type {
10
10
  AnthropicApiResponse,
11
11
  AnthropicAuthConfig,
@@ -13,7 +13,7 @@ import type {
13
13
  WebSearchCitation,
14
14
  WebSearchResponse,
15
15
  WebSearchSource,
16
- } from "../types.js";
16
+ } from "../types";
17
17
 
18
18
  const DEFAULT_MODEL = "claude-sonnet-4-5-20250514";
19
19
  const DEFAULT_MAX_TOKENS = 4096;
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import * as os from "node:os";
9
- import type { WebSearchResponse, WebSearchSource } from "../types.js";
9
+ import type { WebSearchResponse, WebSearchSource } from "../types";
10
10
 
11
11
  const EXA_MCP_URL = "https://mcp.exa.ai/mcp";
12
12
 
@@ -248,10 +248,8 @@ export async function searchExa(params: ExaSearchParams): Promise<WebSearchRespo
248
248
 
249
249
  const args: Record<string, unknown> = {
250
250
  query: params.query,
251
- num_results: params.num_results ?? 10,
251
+ numResults: params.num_results ?? 10,
252
252
  type: params.type ?? "auto",
253
- text: true, // Include text for richer results
254
- highlights: true,
255
253
  };
256
254
 
257
255
  if (params.include_domains?.length) {
@@ -267,7 +265,7 @@ export async function searchExa(params: ExaSearchParams): Promise<WebSearchRespo
267
265
  args.end_published_date = params.end_published_date;
268
266
  }
269
267
 
270
- const response = await callExaMCP(apiKey, "web_search", args);
268
+ const response = await callExaMCP(apiKey, "web_search_exa", args);
271
269
 
272
270
  if (response.error) {
273
271
  throw new Error(`Exa MCP error: ${response.error.message}`);
@@ -12,7 +12,7 @@ import type {
12
12
  WebSearchCitation,
13
13
  WebSearchResponse,
14
14
  WebSearchSource,
15
- } from "../types.js";
15
+ } from "../types";
16
16
 
17
17
  const PERPLEXITY_API_URL = "https://api.perplexity.ai/chat/completions";
18
18
 
@@ -6,9 +6,9 @@
6
6
 
7
7
  import type { Component } from "@oh-my-pi/pi-tui";
8
8
  import { Text } from "@oh-my-pi/pi-tui";
9
- import type { Theme } from "../../../modes/interactive/theme/theme.js";
10
- import type { RenderResultOptions } from "../../custom-tools/types.js";
11
- import type { WebSearchResponse } from "./types.js";
9
+ import type { Theme } from "../../../modes/interactive/theme/theme";
10
+ import type { RenderResultOptions } from "../../custom-tools/types";
11
+ import type { WebSearchResponse } from "./types";
12
12
 
13
13
  // Tree formatting constants
14
14
  const TREE_MID = "├─";
@@ -1,15 +1,39 @@
1
+ import { mkdir, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
1
3
  import type { AgentTool } from "@oh-my-pi/pi-agent-core";
2
4
  import { Type } from "@sinclair/typebox";
3
- import { mkdir, writeFile } from "fs/promises";
4
- import { dirname } from "path";
5
- import { resolveToCwd } from "./path-utils.js";
5
+ import type { FileDiagnosticsResult, FileFormatResult } from "./lsp/index";
6
+ import { resolveToCwd } from "./path-utils";
6
7
 
7
8
  const writeSchema = Type.Object({
8
9
  path: Type.String({ description: "Path to the file to write (relative or absolute)" }),
9
10
  content: Type.String({ description: "Content to write to the file" }),
10
11
  });
11
12
 
12
- export function createWriteTool(cwd: string): AgentTool<typeof writeSchema> {
13
+ /** Options for creating the write tool */
14
+ export interface WriteToolOptions {
15
+ /** Callback to format file using LSP after writing */
16
+ formatOnWrite?: (absolutePath: string) => Promise<FileFormatResult>;
17
+ /** Callback to get LSP diagnostics after writing a file */
18
+ getDiagnostics?: (absolutePath: string) => Promise<FileDiagnosticsResult>;
19
+ }
20
+
21
+ /** Details returned by the write tool for TUI rendering */
22
+ export interface WriteToolDetails {
23
+ /** Whether the file was formatted */
24
+ wasFormatted: boolean;
25
+ /** Format result (if available) */
26
+ formatResult?: FileFormatResult;
27
+ /** Whether LSP diagnostics were retrieved */
28
+ hasDiagnostics: boolean;
29
+ /** Diagnostic result (if available) */
30
+ diagnostics?: FileDiagnosticsResult;
31
+ }
32
+
33
+ export function createWriteTool(
34
+ cwd: string,
35
+ options: WriteToolOptions = {},
36
+ ): AgentTool<typeof writeSchema, WriteToolDetails> {
13
37
  return {
14
38
  name: "write",
15
39
  label: "Write",
@@ -30,7 +54,7 @@ Usage:
30
54
  const absolutePath = resolveToCwd(path, cwd);
31
55
  const dir = dirname(absolutePath);
32
56
 
33
- return new Promise<{ content: Array<{ type: "text"; text: string }>; details: undefined }>(
57
+ return new Promise<{ content: Array<{ type: "text"; text: string }>; details: WriteToolDetails }>(
34
58
  (resolve, reject) => {
35
59
  // Check if already aborted
36
60
  if (signal?.aborted) {
@@ -74,9 +98,48 @@ Usage:
74
98
  signal.removeEventListener("abort", onAbort);
75
99
  }
76
100
 
101
+ // Format file if callback provided (before diagnostics)
102
+ let formatResult: FileFormatResult | undefined;
103
+ if (options.formatOnWrite) {
104
+ try {
105
+ formatResult = await options.formatOnWrite(absolutePath);
106
+ } catch {
107
+ // Ignore formatting errors - don't fail the write
108
+ }
109
+ }
110
+
111
+ // Get LSP diagnostics if callback provided (after formatting)
112
+ let diagnosticsResult: FileDiagnosticsResult | undefined;
113
+ if (options.getDiagnostics) {
114
+ try {
115
+ diagnosticsResult = await options.getDiagnostics(absolutePath);
116
+ } catch {
117
+ // Ignore diagnostics errors - don't fail the write
118
+ }
119
+ }
120
+
121
+ // Build result text
122
+ let resultText = `Successfully wrote ${content.length} bytes to ${path}`;
123
+
124
+ // Note if file was formatted
125
+ if (formatResult?.formatted) {
126
+ resultText += ` (formatted by ${formatResult.serverName})`;
127
+ }
128
+
129
+ // Append diagnostics if available and there are issues
130
+ if (diagnosticsResult?.available && diagnosticsResult.diagnostics.length > 0) {
131
+ resultText += `\n\nLSP Diagnostics (${diagnosticsResult.summary}):\n`;
132
+ resultText += diagnosticsResult.diagnostics.map((d) => ` ${d}`).join("\n");
133
+ }
134
+
77
135
  resolve({
78
- content: [{ type: "text", text: `Successfully wrote ${content.length} bytes to ${path}` }],
79
- details: undefined,
136
+ content: [{ type: "text", text: resultText }],
137
+ details: {
138
+ wasFormatted: formatResult?.formatted ?? false,
139
+ formatResult,
140
+ hasDiagnostics: diagnosticsResult?.available ?? false,
141
+ diagnostics: diagnosticsResult,
142
+ },
80
143
  });
81
144
  } catch (error: any) {
82
145
  // Clean up abort handler
package/src/index.ts CHANGED
@@ -10,9 +10,9 @@ export {
10
10
  type ModelCycleResult,
11
11
  type PromptOptions,
12
12
  type SessionStats,
13
- } from "./core/agent-session.js";
13
+ } from "./core/agent-session";
14
14
  // Auth and model registry
15
- export { type ApiKeyCredential, type AuthCredential, AuthStorage, type OAuthCredential } from "./core/auth-storage.js";
15
+ export { type ApiKeyCredential, type AuthCredential, AuthStorage, type OAuthCredential } from "./core/auth-storage";
16
16
  // Compaction
17
17
  export {
18
18
  type BranchPreparation,
@@ -35,7 +35,16 @@ export {
35
35
  prepareBranchEntries,
36
36
  serializeConversation,
37
37
  shouldCompact,
38
- } from "./core/compaction/index.js";
38
+ } from "./core/compaction/index";
39
+ // Custom commands
40
+ export type {
41
+ CustomCommand,
42
+ CustomCommandAPI,
43
+ CustomCommandFactory,
44
+ CustomCommandSource,
45
+ CustomCommandsLoadResult,
46
+ LoadedCustomCommand,
47
+ } from "./core/custom-commands/types";
39
48
  // Custom tools
40
49
  export type {
41
50
  AgentToolUpdateCallback,
@@ -49,9 +58,9 @@ export type {
49
58
  ExecResult,
50
59
  LoadedCustomTool,
51
60
  RenderResultOptions,
52
- } from "./core/custom-tools/index.js";
53
- export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools/index.js";
54
- export type * from "./core/hooks/index.js";
61
+ } from "./core/custom-tools/index";
62
+ export { discoverAndLoadCustomTools, loadCustomTools } from "./core/custom-tools/index";
63
+ export type * from "./core/hooks/index";
55
64
  // Hook system types and type guards
56
65
  export {
57
66
  isBashToolResult,
@@ -61,9 +70,11 @@ export {
61
70
  isLsToolResult,
62
71
  isReadToolResult,
63
72
  isWriteToolResult,
64
- } from "./core/hooks/index.js";
65
- export { convertToLlm } from "./core/messages.js";
66
- export { ModelRegistry } from "./core/model-registry.js";
73
+ } from "./core/hooks/index";
74
+ // Logging
75
+ export { type Logger, logger } from "./core/logger";
76
+ export { convertToLlm } from "./core/messages";
77
+ export { ModelRegistry } from "./core/model-registry";
67
78
  // SDK for programmatic usage
68
79
  export {
69
80
  type BuildSystemPromptOptions,
@@ -98,7 +109,7 @@ export {
98
109
  loadSettings,
99
110
  // Pre-built tools (use process.cwd())
100
111
  readOnlyTools,
101
- } from "./core/sdk.js";
112
+ } from "./core/sdk";
102
113
  export {
103
114
  type BranchSummaryEntry,
104
115
  buildSessionContext,
@@ -120,14 +131,15 @@ export {
120
131
  SessionManager,
121
132
  type SessionMessageEntry,
122
133
  type ThinkingLevelChangeEntry,
123
- } from "./core/session-manager.js";
134
+ } from "./core/session-manager";
124
135
  export {
125
136
  type CompactionSettings,
137
+ type LspSettings,
126
138
  type RetrySettings,
127
139
  type Settings,
128
140
  SettingsManager,
129
141
  type SkillsSettings,
130
- } from "./core/settings-manager.js";
142
+ } from "./core/settings-manager";
131
143
  // Skills
132
144
  export {
133
145
  formatSkillsForPrompt,
@@ -138,11 +150,12 @@ export {
138
150
  type Skill,
139
151
  type SkillFrontmatter,
140
152
  type SkillWarning,
141
- } from "./core/skills.js";
153
+ } from "./core/skills";
142
154
  // Tools
143
155
  export {
144
156
  type BashToolDetails,
145
157
  bashTool,
158
+ type CodingToolsOptions,
146
159
  codingTools,
147
160
  editTool,
148
161
  type FindToolDetails,
@@ -154,14 +167,17 @@ export {
154
167
  type ReadToolDetails,
155
168
  readTool,
156
169
  type TruncationResult,
170
+ type WriteToolDetails,
171
+ type WriteToolOptions,
157
172
  writeTool,
158
- } from "./core/tools/index.js";
173
+ } from "./core/tools/index";
174
+ export type { FileDiagnosticsResult } from "./core/tools/lsp/index";
159
175
  // Main entry point
160
- export { main } from "./main.js";
176
+ export { main } from "./main";
161
177
  // UI components for hooks and custom tools
162
- export { BorderedLoader } from "./modes/interactive/components/bordered-loader.js";
178
+ export { BorderedLoader } from "./modes/interactive/components/bordered-loader";
163
179
  // Theme utilities for custom tools
164
- export { getMarkdownTheme } from "./modes/interactive/theme/theme.js";
180
+ export { getMarkdownTheme } from "./modes/interactive/theme/theme";
165
181
 
166
182
  // TypeBox helper for string enums (convenience for custom tools)
167
183
  import { type TSchema, Type } from "@sinclair/typebox";
package/src/main.ts CHANGED
@@ -5,33 +5,33 @@
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";
8
10
  import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
9
11
  import chalk from "chalk";
10
- import { existsSync } from "fs";
11
- import { join } from "path";
12
- import { type Args, parseArgs, printHelp } from "./cli/args.js";
13
- import { processFileArguments } from "./cli/file-processor.js";
14
- import { listModels } from "./cli/list-models.js";
15
- import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli.js";
16
- import { selectSession } from "./cli/session-picker.js";
17
- import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config.js";
18
- import type { AgentSession } from "./core/agent-session.js";
19
- import type { LoadedCustomTool } from "./core/custom-tools/index.js";
20
- import { exportFromFile } from "./core/export-html/index.js";
21
- import type { HookUIContext } from "./core/index.js";
22
- import type { ModelRegistry } from "./core/model-registry.js";
23
- import { resolveModelScope, type ScopedModel } from "./core/model-resolver.js";
24
- import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk.js";
25
- import { SessionManager } from "./core/session-manager.js";
26
- import { SettingsManager } from "./core/settings-manager.js";
27
- import { resolvePromptInput } from "./core/system-prompt.js";
28
- import { printTimings, time } from "./core/timings.js";
29
- import { allTools } from "./core/tools/index.js";
30
- import { runMigrations } from "./migrations.js";
31
- import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index.js";
32
- import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js";
33
- import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog.js";
34
- import { ensureTool } from "./utils/tools-manager.js";
12
+ import { type Args, parseArgs, printHelp } from "./cli/args";
13
+ import { processFileArguments } from "./cli/file-processor";
14
+ import { listModels } from "./cli/list-models";
15
+ import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
16
+ import { selectSession } from "./cli/session-picker";
17
+ import { CONFIG_DIR_NAME, getAgentDir, getModelsPath, VERSION } from "./config";
18
+ import type { AgentSession } from "./core/agent-session";
19
+ import type { LoadedCustomTool } from "./core/custom-tools/index";
20
+ import { exportFromFile } from "./core/export-html/index";
21
+ import type { HookUIContext } from "./core/index";
22
+ import type { ModelRegistry } from "./core/model-registry";
23
+ import { parseModelPattern, resolveModelScope, type ScopedModel } from "./core/model-resolver";
24
+ import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk";
25
+ import { SessionManager } from "./core/session-manager";
26
+ import { SettingsManager } from "./core/settings-manager";
27
+ import { resolvePromptInput } from "./core/system-prompt";
28
+ import { printTimings, time } from "./core/timings";
29
+ import { allTools } from "./core/tools/index";
30
+ import { runMigrations } from "./migrations";
31
+ import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index";
32
+ import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme";
33
+ import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
34
+ import { ensureTool } from "./utils/tools-manager";
35
35
 
36
36
  async function checkForNewVersion(currentVersion: string): Promise<string | undefined> {
37
37
  try {
@@ -62,11 +62,20 @@ async function runInteractiveMode(
62
62
  initialMessages: string[],
63
63
  customTools: LoadedCustomTool[],
64
64
  setToolUIContext: (uiContext: HookUIContext, hasUI: boolean) => void,
65
+ lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
65
66
  initialMessage?: string,
66
67
  initialImages?: ImageContent[],
67
68
  fdPath: string | undefined = undefined,
68
69
  ): Promise<void> {
69
- const mode = new InteractiveMode(session, version, changelogMarkdown, customTools, setToolUIContext, fdPath);
70
+ const mode = new InteractiveMode(
71
+ session,
72
+ version,
73
+ changelogMarkdown,
74
+ customTools,
75
+ setToolUIContext,
76
+ lspServers,
77
+ fdPath,
78
+ );
70
79
 
71
80
  await mode.init();
72
81
 
@@ -204,12 +213,12 @@ function discoverSystemPromptFile(): string | undefined {
204
213
  return undefined;
205
214
  }
206
215
 
207
- function buildSessionOptions(
216
+ async function buildSessionOptions(
208
217
  parsed: Args,
209
218
  scopedModels: ScopedModel[],
210
219
  sessionManager: SessionManager | undefined,
211
220
  modelRegistry: ModelRegistry,
212
- ): CreateAgentSessionOptions {
221
+ ): Promise<CreateAgentSessionOptions> {
213
222
  const options: CreateAgentSessionOptions = {};
214
223
 
215
224
  // Auto-discover SYSTEM.md if no CLI system prompt provided
@@ -221,11 +230,15 @@ function buildSessionOptions(
221
230
  options.sessionManager = sessionManager;
222
231
  }
223
232
 
224
- // Model from CLI
225
- if (parsed.provider && parsed.model) {
226
- const model = modelRegistry.find(parsed.provider, parsed.model);
233
+ // Model from CLI (--model) - uses same fuzzy matching as --models
234
+ if (parsed.model) {
235
+ const available = await modelRegistry.getAvailable();
236
+ const { model, warning } = parseModelPattern(parsed.model, available);
237
+ if (warning) {
238
+ console.warn(chalk.yellow(`Warning: ${warning}`));
239
+ }
227
240
  if (!model) {
228
- console.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));
241
+ console.error(chalk.red(`Model "${parsed.model}" not found`));
229
242
  process.exit(1);
230
243
  }
231
244
  options.model = model;
@@ -260,6 +273,7 @@ function buildSessionOptions(
260
273
  // Tools
261
274
  if (parsed.tools) {
262
275
  options.tools = parsed.tools.map((name) => allTools[name]);
276
+ options.explicitTools = parsed.tools;
263
277
  }
264
278
 
265
279
  // Skills
@@ -347,6 +361,17 @@ export async function main(args: string[]) {
347
361
 
348
362
  const settingsManager = SettingsManager.create(cwd);
349
363
  time("SettingsManager.create");
364
+
365
+ // Apply model role overrides from CLI args or env vars (ephemeral, not persisted)
366
+ const smolModel = parsed.smol ?? process.env.PI_SMOL_MODEL;
367
+ const slowModel = parsed.slow ?? process.env.PI_SLOW_MODEL;
368
+ if (smolModel || slowModel) {
369
+ const roleOverrides: Record<string, string> = {};
370
+ if (smolModel) roleOverrides.smol = smolModel;
371
+ if (slowModel) roleOverrides.slow = slowModel;
372
+ settingsManager.applyOverrides({ modelRoles: roleOverrides });
373
+ }
374
+
350
375
  initTheme(settingsManager.getTheme(), isInteractive);
351
376
  time("initTheme");
352
377
 
@@ -378,7 +403,7 @@ export async function main(args: string[]) {
378
403
  sessionManager = SessionManager.open(selectedPath);
379
404
  }
380
405
 
381
- const sessionOptions = buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry);
406
+ const sessionOptions = await buildSessionOptions(parsed, scopedModels, sessionManager, modelRegistry);
382
407
  sessionOptions.authStorage = authStorage;
383
408
  sessionOptions.modelRegistry = modelRegistry;
384
409
  sessionOptions.hasUI = isInteractive;
@@ -393,7 +418,7 @@ export async function main(args: string[]) {
393
418
  }
394
419
 
395
420
  time("buildSessionOptions");
396
- const { session, customToolsResult, modelFallbackMessage } = await createAgentSession(sessionOptions);
421
+ const { session, customToolsResult, modelFallbackMessage, lspServers } = await createAgentSession(sessionOptions);
397
422
  time("createAgentSession");
398
423
 
399
424
  if (!isInteractive && !session.model) {
@@ -449,6 +474,7 @@ export async function main(args: string[]) {
449
474
  parsed.messages,
450
475
  customToolsResult.tools,
451
476
  customToolsResult.setUIContext,
477
+ lspServers,
452
478
  initialMessage,
453
479
  initialImages,
454
480
  fdPath,
package/src/migrations.ts CHANGED
@@ -2,9 +2,9 @@
2
2
  * One-time migrations that run on startup.
3
3
  */
4
4
 
5
- import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "fs";
6
- import { dirname, join } from "path";
7
- import { getAgentDir } from "./config.js";
5
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ import { getAgentDir } from "./config";
8
8
 
9
9
  /**
10
10
  * Migrate legacy oauth.json and settings.json apiKeys to auth.json.
@@ -36,8 +36,8 @@ export function installTerminalCrashHandlers(): void {
36
36
  });
37
37
  }
38
38
 
39
- export { InteractiveMode } from "./interactive/interactive-mode.js";
40
- export { runPrintMode } from "./print-mode.js";
41
- export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client.js";
42
- export { runRpcMode } from "./rpc/rpc-mode.js";
43
- export type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc/rpc-types.js";
39
+ export { InteractiveMode } from "./interactive/interactive-mode";
40
+ export { runPrintMode } from "./print-mode";
41
+ export { type ModelInfo, RpcClient, type RpcClientOptions, type RpcEventListener } from "./rpc/rpc-client";
42
+ export { runRpcMode } from "./rpc/rpc-mode";
43
+ export type { RpcCommand, RpcResponse, RpcSessionState } from "./rpc/rpc-types";
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { Component, TUI } from "@oh-my-pi/pi-tui";
6
- import { theme } from "../theme/theme.js";
6
+ import { theme } from "../theme/theme";
7
7
 
8
8
  // XBM image: 31x36 pixels, LSB first, 1=background, 0=foreground
9
9
  const WIDTH = 31;
@@ -1,6 +1,6 @@
1
1
  import type { AssistantMessage } from "@oh-my-pi/pi-ai";
2
2
  import { Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
3
- import { getMarkdownTheme, theme } from "../theme/theme.js";
3
+ import { getMarkdownTheme, theme } from "../theme/theme";
4
4
 
5
5
  /**
6
6
  * Component that renders a complete assistant message
@@ -9,10 +9,10 @@ import {
9
9
  DEFAULT_MAX_LINES,
10
10
  type TruncationResult,
11
11
  truncateTail,
12
- } from "../../../core/tools/truncate.js";
13
- import { theme } from "../theme/theme.js";
14
- import { DynamicBorder } from "./dynamic-border.js";
15
- import { truncateToVisualLines } from "./visual-truncate.js";
12
+ } from "../../../core/tools/truncate";
13
+ import { theme } from "../theme/theme";
14
+ import { DynamicBorder } from "./dynamic-border";
15
+ import { truncateToVisualLines } from "./visual-truncate";
16
16
 
17
17
  // Preview line limit when not expanded (matches tool execution behavior)
18
18
  const PREVIEW_LINES = 20;
@@ -1,6 +1,6 @@
1
1
  import { CancellableLoader, Container, Spacer, Text, type TUI } from "@oh-my-pi/pi-tui";
2
- import type { Theme } from "../theme/theme.js";
3
- import { DynamicBorder } from "./dynamic-border.js";
2
+ import type { Theme } from "../theme/theme";
3
+ import { DynamicBorder } from "./dynamic-border";
4
4
 
5
5
  /** Loader wrapped with borders for hook UI */
6
6
  export class BorderedLoader extends Container {
@@ -1,6 +1,6 @@
1
1
  import { Box, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
2
- import type { BranchSummaryMessage } from "../../../core/messages.js";
3
- import { getMarkdownTheme, theme } from "../theme/theme.js";
2
+ import type { BranchSummaryMessage } from "../../../core/messages";
3
+ import { getMarkdownTheme, theme } from "../theme/theme";
4
4
 
5
5
  /**
6
6
  * Component that renders a branch summary message with collapsed/expanded state.
@@ -1,6 +1,6 @@
1
1
  import { Box, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
2
- import type { CompactionSummaryMessage } from "../../../core/messages.js";
3
- import { getMarkdownTheme, theme } from "../theme/theme.js";
2
+ import type { CompactionSummaryMessage } from "../../../core/messages";
3
+ import { getMarkdownTheme, theme } from "../theme/theme";
4
4
 
5
5
  /**
6
6
  * Component that renders a compaction message with collapsed/expanded state.
@@ -1,5 +1,5 @@
1
1
  import * as Diff from "diff";
2
- import { theme } from "../theme/theme.js";
2
+ import { theme } from "../theme/theme";
3
3
 
4
4
  /**
5
5
  * Parse diff line to extract prefix, line number, and content.
@@ -1,5 +1,5 @@
1
1
  import type { Component } from "@oh-my-pi/pi-tui";
2
- import { theme } from "../theme/theme.js";
2
+ import { theme } from "../theme/theme";
3
3
 
4
4
  /**
5
5
  * Dynamic border component that adjusts to viewport width.