@oh-my-pi/pi-coding-agent 1.341.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 (151) hide show
  1. package/CHANGELOG.md +73 -0
  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 +5 -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 +157 -15
  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 +2 -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 +77 -35
  54. package/src/core/session-manager.ts +6 -6
  55. package/src/core/settings-manager.ts +16 -3
  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 +2 -2
  60. package/src/core/tools/bash.ts +32 -155
  61. package/src/core/tools/context.ts +2 -2
  62. package/src/core/tools/edit-diff.ts +3 -3
  63. package/src/core/tools/edit.ts +18 -5
  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 +3 -3
  75. package/src/core/tools/index.ts +48 -34
  76. package/src/core/tools/ls.ts +4 -4
  77. package/src/core/tools/lsp/client.ts +161 -90
  78. package/src/core/tools/lsp/config.ts +1 -1
  79. package/src/core/tools/lsp/edits.ts +2 -2
  80. package/src/core/tools/lsp/index.ts +15 -13
  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/utils.ts +1 -1
  84. package/src/core/tools/notebook.ts +1 -1
  85. package/src/core/tools/output.ts +175 -0
  86. package/src/core/tools/read.ts +7 -7
  87. package/src/core/tools/renderers.ts +92 -13
  88. package/src/core/tools/review.ts +268 -0
  89. package/src/core/tools/task/agents.ts +1 -1
  90. package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
  91. package/src/core/tools/task/discovery.ts +2 -2
  92. package/src/core/tools/task/executor.ts +145 -28
  93. package/src/core/tools/task/index.ts +78 -30
  94. package/src/core/tools/task/model-resolver.ts +30 -20
  95. package/src/core/tools/task/parallel.ts +1 -1
  96. package/src/core/tools/task/render.ts +219 -30
  97. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  98. package/src/core/tools/task/types.ts +36 -2
  99. package/src/core/tools/web-fetch.ts +5 -3
  100. package/src/core/tools/web-search/auth.ts +1 -1
  101. package/src/core/tools/web-search/index.ts +17 -15
  102. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  103. package/src/core/tools/web-search/providers/exa.ts +3 -5
  104. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  105. package/src/core/tools/web-search/render.ts +3 -3
  106. package/src/core/tools/write.ts +4 -4
  107. package/src/index.ts +29 -18
  108. package/src/main.ts +37 -32
  109. package/src/migrations.ts +3 -3
  110. package/src/modes/index.ts +5 -5
  111. package/src/modes/interactive/components/armin.ts +1 -1
  112. package/src/modes/interactive/components/assistant-message.ts +1 -1
  113. package/src/modes/interactive/components/bash-execution.ts +4 -4
  114. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  115. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  116. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  117. package/src/modes/interactive/components/diff.ts +1 -1
  118. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  119. package/src/modes/interactive/components/footer.ts +5 -5
  120. package/src/modes/interactive/components/hook-editor.ts +2 -2
  121. package/src/modes/interactive/components/hook-input.ts +2 -2
  122. package/src/modes/interactive/components/hook-message.ts +3 -3
  123. package/src/modes/interactive/components/hook-selector.ts +2 -2
  124. package/src/modes/interactive/components/model-selector.ts +281 -59
  125. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  126. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  127. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  128. package/src/modes/interactive/components/session-selector.ts +4 -4
  129. package/src/modes/interactive/components/settings-defs.ts +1 -1
  130. package/src/modes/interactive/components/settings-selector.ts +5 -5
  131. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  132. package/src/modes/interactive/components/theme-selector.ts +2 -2
  133. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  134. package/src/modes/interactive/components/tool-execution.ts +26 -8
  135. package/src/modes/interactive/components/tree-selector.ts +3 -3
  136. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  137. package/src/modes/interactive/components/user-message.ts +1 -1
  138. package/src/modes/interactive/components/welcome.ts +2 -2
  139. package/src/modes/interactive/interactive-mode.ts +85 -41
  140. package/src/modes/interactive/theme/theme.ts +8 -7
  141. package/src/modes/print-mode.ts +4 -3
  142. package/src/modes/rpc/rpc-client.ts +4 -4
  143. package/src/modes/rpc/rpc-mode.ts +21 -11
  144. package/src/modes/rpc/rpc-types.ts +3 -3
  145. package/src/utils/changelog.ts +2 -2
  146. package/src/utils/clipboard.ts +1 -1
  147. package/src/utils/shell-snapshot.ts +218 -0
  148. package/src/utils/shell.ts +93 -13
  149. package/src/utils/tools-manager.ts +1 -1
  150. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  151. package/src/core/tools/exa/logger.ts +0 -56
package/src/core/exec.ts CHANGED
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { Subprocess } from "bun";
6
+ import { logger } from "./logger";
6
7
 
7
8
  /**
8
9
  * Options for executing shell commands.
@@ -86,38 +87,47 @@ export async function execCommand(
86
87
  const stdoutReader = (proc.stdout as ReadableStream<Uint8Array>).getReader();
87
88
  const stderrReader = (proc.stderr as ReadableStream<Uint8Array>).getReader();
88
89
 
89
- const [stdoutResult, stderrResult] = await Promise.all([
90
+ // Read both streams and wait for process exit
91
+ const [stdoutResult, stderrResult, exitCode] = await Promise.all([
90
92
  (async () => {
91
93
  const chunks: Uint8Array[] = [];
92
- while (true) {
93
- const { done, value } = await stdoutReader.read();
94
- if (done) break;
95
- chunks.push(value);
94
+ try {
95
+ while (true) {
96
+ const { done, value } = await stdoutReader.read();
97
+ if (done) break;
98
+ chunks.push(value);
99
+ }
100
+ } finally {
101
+ stdoutReader.releaseLock();
96
102
  }
97
103
  return Buffer.concat(chunks).toString();
98
104
  })(),
99
105
  (async () => {
100
106
  const chunks: Uint8Array[] = [];
101
- while (true) {
102
- const { done, value } = await stderrReader.read();
103
- if (done) break;
104
- chunks.push(value);
107
+ try {
108
+ while (true) {
109
+ const { done, value } = await stderrReader.read();
110
+ if (done) break;
111
+ chunks.push(value);
112
+ }
113
+ } finally {
114
+ stderrReader.releaseLock();
105
115
  }
106
116
  return Buffer.concat(chunks).toString();
107
117
  })(),
118
+ proc.exited,
108
119
  ]);
109
120
 
110
121
  stdout = stdoutResult;
111
122
  stderr = stderrResult;
112
123
 
113
- const exitCode = await proc.exited;
114
-
115
124
  if (timeoutId) clearTimeout(timeoutId);
116
125
  if (options?.signal) {
117
126
  options.signal.removeEventListener("abort", killProcess);
118
127
  }
119
128
  resolve({ stdout, stderr, code: exitCode ?? 0, killed });
120
- } catch (_err) {
129
+ } catch (err) {
130
+ logger.debug("Process stream error", { error: String(err) });
121
131
  if (timeoutId) clearTimeout(timeoutId);
122
132
  if (options?.signal) {
123
133
  options.signal.removeEventListener("abort", killProcess);
@@ -1,9 +1,9 @@
1
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { basename, join } from "node:path";
1
3
  import type { AgentState } from "@oh-my-pi/pi-agent-core";
2
- import { existsSync, readFileSync, writeFileSync } from "fs";
3
- import { basename, join } from "path";
4
- import { APP_NAME, getExportTemplateDir } from "../../config.js";
5
- import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme.js";
6
- import { SessionManager } from "../session-manager.js";
4
+ import { APP_NAME, getExportTemplateDir } from "../../config";
5
+ import { getResolvedThemeColors, getThemeExportColors } from "../../modes/interactive/theme/theme";
6
+ import { SessionManager } from "../session-manager";
7
7
 
8
8
  // Cached minified assets (populated on first use)
9
9
  let cachedTemplate: string | null = null;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Auto-read file mentions from user prompts.
3
+ *
4
+ * When users reference files with @path syntax (e.g., "@src/foo.ts"),
5
+ * we automatically inject the file contents as a FileMentionMessage
6
+ * so the agent doesn't need to read them manually.
7
+ */
8
+
9
+ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
10
+ import type { FileMentionMessage } from "./messages";
11
+ import { createReadTool } from "./tools/read";
12
+
13
+ /** Regex to match @filepath patterns in text */
14
+ const FILE_MENTION_REGEX = /@((?:[^\s@]+\/)*[^\s@]+\.[a-zA-Z0-9]+)/g;
15
+
16
+ /** Extract all @filepath mentions from text */
17
+ export function extractFileMentions(text: string): string[] {
18
+ const matches = [...text.matchAll(FILE_MENTION_REGEX)];
19
+ return [...new Set(matches.map((m) => m[1]))];
20
+ }
21
+
22
+ /**
23
+ * Generate a FileMentionMessage containing the contents of mentioned files.
24
+ * Returns empty array if no files could be read.
25
+ */
26
+ export async function generateFileMentionMessages(filePaths: string[], cwd: string): Promise<AgentMessage[]> {
27
+ if (filePaths.length === 0) return [];
28
+
29
+ const readTool = createReadTool(cwd);
30
+ const files: FileMentionMessage["files"] = [];
31
+
32
+ for (const filePath of filePaths) {
33
+ try {
34
+ const result = await readTool.execute("auto-read", { path: filePath });
35
+ const textContent = result.content.find((c) => c.type === "text");
36
+ if (textContent && textContent.type === "text") {
37
+ const lineCount = textContent.text.split("\n").length;
38
+ files.push({ path: filePath, content: textContent.text, lineCount });
39
+ }
40
+ } catch {
41
+ // File doesn't exist or isn't readable - skip silently
42
+ }
43
+ }
44
+
45
+ if (files.length === 0) return [];
46
+
47
+ const message: FileMentionMessage = {
48
+ role: "fileMention",
49
+ files,
50
+ timestamp: Date.now(),
51
+ };
52
+
53
+ return [message];
54
+ }
@@ -9,8 +9,8 @@ export {
9
9
  type NavigateTreeHandler,
10
10
  type NewSessionHandler,
11
11
  type SendMessageHandler,
12
- } from "./loader.js";
13
- export { execCommand, HookRunner, type HookErrorListener } from "./runner.js";
14
- export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper.js";
15
- export * from "./types.js";
16
- export type { ReadonlySessionManager } from "../session-manager.js";
12
+ } from "./loader";
13
+ export { execCommand, HookRunner, type HookErrorListener } from "./runner";
14
+ export { wrapToolsWithHooks, wrapToolWithHooks } from "./tool-wrapper";
15
+ export * from "./types";
16
+ export type { ReadonlySessionManager } from "../session-manager";
@@ -6,13 +6,14 @@ import * as fs from "node:fs";
6
6
  import * as os from "node:os";
7
7
  import * as path from "node:path";
8
8
  import * as typebox from "@sinclair/typebox";
9
- import { getAgentDir } from "../../config.js";
10
- import * as piCodingAgent from "../../index.js";
11
- import type { HookMessage } from "../messages.js";
12
- import { getAllPluginHookPaths } from "../plugins/loader.js";
13
- import type { SessionManager } from "../session-manager.js";
14
- import { execCommand } from "./runner.js";
15
- import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types.js";
9
+ import { getAgentDir } from "../../config";
10
+ import * as piCodingAgent from "../../index";
11
+ import { logger } from "../logger";
12
+ import type { HookMessage } from "../messages";
13
+ import { getAllPluginHookPaths } from "../plugins/loader";
14
+ import type { SessionManager } from "../session-manager";
15
+ import { execCommand } from "./runner";
16
+ import type { ExecOptions, HookAPI, HookFactory, HookMessageRenderer, RegisteredCommand } from "./types";
16
17
 
17
18
  /**
18
19
  * Generic handler function type.
@@ -131,12 +132,8 @@ function createHookAPI(
131
132
  setSendMessageHandler: (handler: SendMessageHandler) => void;
132
133
  setAppendEntryHandler: (handler: AppendEntryHandler) => void;
133
134
  } {
134
- let sendMessageHandler: SendMessageHandler = () => {
135
- // Default no-op until mode sets the handler
136
- };
137
- let appendEntryHandler: AppendEntryHandler = () => {
138
- // Default no-op until mode sets the handler
139
- };
135
+ let sendMessageHandler: SendMessageHandler | null = null;
136
+ let appendEntryHandler: AppendEntryHandler | null = null;
140
137
  const messageRenderers = new Map<string, HookMessageRenderer>();
141
138
  const commands = new Map<string, RegisteredCommand>();
142
139
 
@@ -144,14 +141,21 @@ function createHookAPI(
144
141
  // but the interface has specific overloads for type safety in hooks
145
142
  const api = {
146
143
  on(event: string, handler: HandlerFn): void {
147
- const list = handlers.get(event) ?? [];
148
- list.push(handler);
149
- handlers.set(event, list);
144
+ if (!handlers.has(event)) {
145
+ handlers.set(event, []);
146
+ }
147
+ handlers.get(event)!.push(handler);
150
148
  },
151
149
  sendMessage<T = unknown>(message: HookMessage<T>, triggerTurn?: boolean): void {
150
+ if (!sendMessageHandler) {
151
+ throw new Error("sendMessage handler not initialized");
152
+ }
152
153
  sendMessageHandler(message, triggerTurn);
153
154
  },
154
155
  appendEntry<T = unknown>(customType: string, data?: T): void {
156
+ if (!appendEntryHandler) {
157
+ throw new Error("appendEntry handler not initialized");
158
+ }
155
159
  appendEntryHandler(customType, data);
156
160
  },
157
161
  registerMessageRenderer<T = unknown>(customType: string, renderer: HookMessageRenderer<T>): void {
@@ -163,6 +167,7 @@ function createHookAPI(
163
167
  exec(command: string, args: string[], options?: ExecOptions) {
164
168
  return execCommand(command, args, options?.cwd ?? cwd, options);
165
169
  },
170
+ logger,
166
171
  typebox,
167
172
  pi: piCodingAgent,
168
173
  } as HookAPI;
@@ -4,9 +4,9 @@
4
4
 
5
5
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
6
6
  import type { Model } from "@oh-my-pi/pi-ai";
7
- import { theme } from "../../modes/interactive/theme/theme.js";
8
- import type { ModelRegistry } from "../model-registry.js";
9
- import type { SessionManager } from "../session-manager.js";
7
+ import { theme } from "../../modes/interactive/theme/theme";
8
+ import type { ModelRegistry } from "../model-registry";
9
+ import type { SessionManager } from "../session-manager";
10
10
  import type {
11
11
  AppendEntryHandler,
12
12
  BranchHandler,
@@ -14,7 +14,7 @@ import type {
14
14
  NavigateTreeHandler,
15
15
  NewSessionHandler,
16
16
  SendMessageHandler,
17
- } from "./loader.js";
17
+ } from "./loader";
18
18
  import type {
19
19
  BeforeAgentStartEvent,
20
20
  BeforeAgentStartEventResult,
@@ -32,7 +32,7 @@ import type {
32
32
  ToolCallEvent,
33
33
  ToolCallEventResult,
34
34
  ToolResultEventResult,
35
- } from "./types.js";
35
+ } from "./types";
36
36
 
37
37
  /**
38
38
  * Listener for hook errors.
@@ -40,7 +40,7 @@ import type {
40
40
  export type HookErrorListener = (error: HookError) => void;
41
41
 
42
42
  // Re-export execCommand for backward compatibility
43
- export { execCommand } from "../exec.js";
43
+ export { execCommand } from "../exec";
44
44
 
45
45
  /** No-op UI context used when no UI is available */
46
46
  const noOpUIContext: HookUIContext = {
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { AgentTool, AgentToolContext, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
6
- import type { HookRunner } from "./runner.js";
7
- import type { ToolCallEventResult, ToolResultEventResult } from "./types.js";
6
+ import type { HookRunner } from "./runner";
7
+ import type { ToolCallEventResult, ToolResultEventResult } from "./types";
8
8
 
9
9
  /**
10
10
  * Wrap a tool with hook callbacks.
@@ -8,30 +8,25 @@
8
8
  import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
9
9
  import type { ImageContent, Message, Model, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
10
10
  import type { Component, TUI } from "@oh-my-pi/pi-tui";
11
- import type { Theme } from "../../modes/interactive/theme/theme.js";
12
- import type { CompactionPreparation, CompactionResult } from "../compaction/index.js";
13
- import type { ExecOptions, ExecResult } from "../exec.js";
14
- import type { HookMessage } from "../messages.js";
15
- import type { ModelRegistry } from "../model-registry.js";
11
+ import type { Theme } from "../../modes/interactive/theme/theme";
12
+ import type { CompactionPreparation, CompactionResult } from "../compaction/index";
13
+ import type { ExecOptions, ExecResult } from "../exec";
14
+ import type { Logger } from "../logger";
15
+ import type { HookMessage } from "../messages";
16
+ import type { ModelRegistry } from "../model-registry";
16
17
  import type {
17
18
  BranchSummaryEntry,
18
19
  CompactionEntry,
19
20
  ReadonlySessionManager,
20
21
  SessionEntry,
21
22
  SessionManager,
22
- } from "../session-manager.js";
23
+ } from "../session-manager";
23
24
 
24
- import type { EditToolDetails } from "../tools/edit.js";
25
- import type {
26
- BashToolDetails,
27
- FindToolDetails,
28
- GrepToolDetails,
29
- LsToolDetails,
30
- ReadToolDetails,
31
- } from "../tools/index.js";
25
+ import type { EditToolDetails } from "../tools/edit";
26
+ import type { BashToolDetails, FindToolDetails, GrepToolDetails, LsToolDetails, ReadToolDetails } from "../tools/index";
32
27
 
33
28
  // Re-export for backward compatibility
34
- export type { ExecOptions, ExecResult } from "../exec.js";
29
+ export type { ExecOptions, ExecResult } from "../exec";
35
30
 
36
31
  /**
37
32
  * UI context for hooks to request interactive UI from the harness.
@@ -747,6 +742,8 @@ export interface HookAPI {
747
742
  */
748
743
  exec(command: string, args: string[], options?: ExecOptions): Promise<ExecResult>;
749
744
 
745
+ /** File logger for error/warning/debug messages */
746
+ logger: Logger;
750
747
  /** Injected @sinclair/typebox module */
751
748
  typebox: typeof import("@sinclair/typebox");
752
749
  /** Injected pi-coding-agent exports */
package/src/core/index.ts CHANGED
@@ -10,9 +10,9 @@ export {
10
10
  type ModelCycleResult,
11
11
  type PromptOptions,
12
12
  type SessionStats,
13
- } from "./agent-session.js";
14
- export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor.js";
15
- export type { CompactionResult } from "./compaction/index.js";
13
+ } from "./agent-session";
14
+ export { type BashExecutorOptions, type BashResult, executeBash } from "./bash-executor";
15
+ export type { CompactionResult } from "./compaction/index";
16
16
  export {
17
17
  type CustomTool,
18
18
  type CustomToolAPI,
@@ -24,7 +24,7 @@ export {
24
24
  type LoadedCustomTool,
25
25
  loadCustomTools,
26
26
  type RenderResultOptions,
27
- } from "./custom-tools/index.js";
27
+ } from "./custom-tools/index";
28
28
  export {
29
29
  type HookAPI,
30
30
  type HookContext,
@@ -34,7 +34,7 @@ export {
34
34
  HookRunner,
35
35
  type HookUIContext,
36
36
  loadHooks,
37
- } from "./hooks/index.js";
37
+ } from "./hooks/index";
38
38
  export {
39
39
  createMCPManager,
40
40
  discoverAndLoadMCPTools,
@@ -49,4 +49,4 @@ export {
49
49
  type MCPToolDetails,
50
50
  type MCPToolsLoadResult,
51
51
  type MCPTransport,
52
- } from "./mcp/index.js";
52
+ } from "./mcp/index";
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Centralized file logger for pi.
3
+ *
4
+ * Logs to ~/.pi/logs/ with size-based rotation, supporting concurrent pi instances.
5
+ * Each log entry includes process.pid for traceability.
6
+ */
7
+
8
+ import { existsSync, mkdirSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ import winston from "winston";
12
+ import DailyRotateFile from "winston-daily-rotate-file";
13
+ import { CONFIG_DIR_NAME } from "../config";
14
+
15
+ /** Get the logs directory (~/.pi/logs/) */
16
+ function getLogsDir(): string {
17
+ return join(homedir(), CONFIG_DIR_NAME, "logs");
18
+ }
19
+
20
+ /** Ensure logs directory exists */
21
+ function ensureLogsDir(): string {
22
+ const logsDir = getLogsDir();
23
+ if (!existsSync(logsDir)) {
24
+ mkdirSync(logsDir, { recursive: true });
25
+ }
26
+ return logsDir;
27
+ }
28
+
29
+ /** Custom format that includes pid and flattens metadata */
30
+ const logFormat = winston.format.combine(
31
+ winston.format.timestamp({ format: "YYYY-MM-DDTHH:mm:ss.SSSZ" }),
32
+ winston.format.printf(({ timestamp, level, message, ...meta }) => {
33
+ const entry: Record<string, unknown> = {
34
+ timestamp,
35
+ level,
36
+ pid: process.pid,
37
+ message,
38
+ };
39
+ // Flatten metadata into entry
40
+ for (const [key, value] of Object.entries(meta)) {
41
+ if (key !== "level" && key !== "timestamp" && key !== "message") {
42
+ entry[key] = value;
43
+ }
44
+ }
45
+ return JSON.stringify(entry);
46
+ }),
47
+ );
48
+
49
+ /** Size-based rotating file transport */
50
+ const fileTransport = new DailyRotateFile({
51
+ dirname: ensureLogsDir(),
52
+ filename: "pi.%DATE%.log",
53
+ datePattern: "YYYY-MM-DD",
54
+ maxSize: "10m",
55
+ maxFiles: 5,
56
+ zippedArchive: true,
57
+ });
58
+
59
+ /** The winston logger instance */
60
+ const winstonLogger = winston.createLogger({
61
+ level: "debug",
62
+ format: logFormat,
63
+ transports: [fileTransport],
64
+ // Don't exit on error - logging failures shouldn't crash the app
65
+ exitOnError: false,
66
+ });
67
+
68
+ /** Logger type exposed to plugins and internal code */
69
+ export interface Logger {
70
+ error(message: string, context?: Record<string, unknown>): void;
71
+ warn(message: string, context?: Record<string, unknown>): void;
72
+ debug(message: string, context?: Record<string, unknown>): void;
73
+ }
74
+
75
+ /**
76
+ * Centralized logger for pi.
77
+ *
78
+ * Logs to ~/.pi/logs/pi.YYYY-MM-DD.log with size-based rotation.
79
+ * Safe for concurrent access from multiple pi instances.
80
+ *
81
+ * @example
82
+ * ```typescript
83
+ * import { logger } from "../core/logger";
84
+ *
85
+ * logger.error("MCP request failed", { url, method });
86
+ * logger.warn("Theme file invalid, using fallback", { path });
87
+ * logger.debug("LSP fallback triggered", { reason });
88
+ * ```
89
+ */
90
+ export const logger: Logger = {
91
+ error(message: string, context?: Record<string, unknown>): void {
92
+ try {
93
+ winstonLogger.error(message, context);
94
+ } catch {
95
+ // Silently ignore logging failures
96
+ }
97
+ },
98
+ warn(message: string, context?: Record<string, unknown>): void {
99
+ try {
100
+ winstonLogger.warn(message, context);
101
+ } catch {
102
+ // Silently ignore logging failures
103
+ }
104
+ },
105
+ debug(message: string, context?: Record<string, unknown>): void {
106
+ try {
107
+ winstonLogger.debug(message, context);
108
+ } catch {
109
+ // Silently ignore logging failures
110
+ }
111
+ },
112
+ };
@@ -4,8 +4,8 @@
4
4
  * Handles connection initialization, tool listing, and tool calling.
5
5
  */
6
6
 
7
- import { createHttpTransport } from "./transports/http.js";
8
- import { createStdioTransport } from "./transports/stdio.js";
7
+ import { createHttpTransport } from "./transports/http";
8
+ import { createStdioTransport } from "./transports/stdio";
9
9
  import type {
10
10
  MCPHttpServerConfig,
11
11
  MCPInitializeParams,
@@ -20,7 +20,7 @@ import type {
20
20
  MCPToolDefinition,
21
21
  MCPToolsListResult,
22
22
  MCPTransport,
23
- } from "./types.js";
23
+ } from "./types";
24
24
 
25
25
  /** MCP protocol version we support */
26
26
  const PROTOCOL_VERSION = "2025-03-26";
@@ -8,7 +8,7 @@
8
8
  import { existsSync, readFileSync } from "node:fs";
9
9
  import { homedir } from "node:os";
10
10
  import { join } from "node:path";
11
- import type { MCPConfigFile, MCPServerConfig } from "./types.js";
11
+ import type { MCPConfigFile, MCPServerConfig } from "./types";
12
12
 
13
13
  /** Environment variable expansion pattern: ${VAR} or ${VAR:-default} */
14
14
  const ENV_VAR_PATTERN = /\$\{([^}:]+)(?::-([^}]*))?\}/g;
@@ -6,10 +6,10 @@
6
6
  */
7
7
 
8
8
  // Client
9
- export { callTool, connectToServer, disconnectServer, listTools, serverSupportsTools } from "./client.js";
9
+ export { callTool, connectToServer, disconnectServer, listTools, serverSupportsTools } from "./client";
10
10
 
11
11
  // Config
12
- export type { ExaFilterResult, LoadMCPConfigsOptions, LoadMCPConfigsResult } from "./config.js";
12
+ export type { ExaFilterResult, LoadMCPConfigsOptions, LoadMCPConfigsResult } from "./config";
13
13
  export {
14
14
  expandEnvVars,
15
15
  extractExaApiKey,
@@ -20,19 +20,19 @@ export {
20
20
  loadMCPConfigFile,
21
21
  mergeMCPConfigs,
22
22
  validateServerConfig,
23
- } from "./config.js";
23
+ } from "./config";
24
24
  // Loader (for SDK integration)
25
- export type { MCPToolsLoadOptions, MCPToolsLoadResult } from "./loader.js";
26
- export { discoverAndLoadMCPTools } from "./loader.js";
25
+ export type { MCPToolsLoadOptions, MCPToolsLoadResult } from "./loader";
26
+ export { discoverAndLoadMCPTools } from "./loader";
27
27
  // Manager
28
- export type { MCPDiscoverOptions, MCPLoadResult } from "./manager.js";
29
- export { createMCPManager, MCPManager } from "./manager.js";
28
+ export type { MCPDiscoverOptions, MCPLoadResult } from "./manager";
29
+ export { createMCPManager, MCPManager } from "./manager";
30
30
  // Tool bridge
31
- export type { MCPToolDetails } from "./tool-bridge.js";
32
- export { createMCPTool, createMCPToolName, createMCPTools, parseMCPToolName } from "./tool-bridge.js";
31
+ export type { MCPToolDetails } from "./tool-bridge";
32
+ export { createMCPTool, createMCPToolName, createMCPTools, parseMCPToolName } from "./tool-bridge";
33
33
  // Transports
34
- export { createHttpTransport, HttpTransport } from "./transports/http.js";
35
- export { createStdioTransport, StdioTransport } from "./transports/stdio.js";
34
+ export { createHttpTransport, HttpTransport } from "./transports/http";
35
+ export { createStdioTransport, StdioTransport } from "./transports/stdio";
36
36
  // Types
37
37
  export type {
38
38
  MCPConfigFile,
@@ -46,4 +46,4 @@ export type {
46
46
  MCPToolDefinition,
47
47
  MCPToolWithServer,
48
48
  MCPTransport,
49
- } from "./types.js";
49
+ } from "./types";
@@ -4,8 +4,8 @@
4
4
  * Integrates MCP tool discovery with the custom tools system.
5
5
  */
6
6
 
7
- import type { LoadedCustomTool } from "../custom-tools/types.js";
8
- import { type MCPLoadResult, MCPManager } from "./manager.js";
7
+ import type { LoadedCustomTool } from "../custom-tools/types";
8
+ import { type MCPLoadResult, MCPManager } from "./manager";
9
9
 
10
10
  /** Result from loading MCP tools */
11
11
  export interface MCPToolsLoadResult {
@@ -6,12 +6,12 @@
6
6
  */
7
7
 
8
8
  import type { TSchema } from "@sinclair/typebox";
9
- import type { CustomTool } from "../custom-tools/types.js";
10
- import { connectToServer, disconnectServer, listTools } from "./client.js";
11
- import { type LoadMCPConfigsOptions, loadAllMCPConfigs, validateServerConfig } from "./config.js";
12
- import type { MCPToolDetails } from "./tool-bridge.js";
13
- import { createMCPTools } from "./tool-bridge.js";
14
- import type { MCPServerConfig, MCPServerConnection } from "./types.js";
9
+ import type { CustomTool } from "../custom-tools/types";
10
+ import { connectToServer, disconnectServer, listTools } from "./client";
11
+ import { type LoadMCPConfigsOptions, loadAllMCPConfigs, validateServerConfig } from "./config";
12
+ import type { MCPToolDetails } from "./tool-bridge";
13
+ import { createMCPTools } from "./tool-bridge";
14
+ import type { MCPServerConfig, MCPServerConnection } from "./types";
15
15
 
16
16
  /** Result of loading MCP tools */
17
17
  export interface MCPLoadResult {
@@ -5,9 +5,9 @@
5
5
  */
6
6
 
7
7
  import type { TSchema } from "@sinclair/typebox";
8
- import type { CustomTool, CustomToolResult } from "../custom-tools/types.js";
9
- import { callTool } from "./client.js";
10
- import type { MCPContent, MCPServerConnection, MCPToolDefinition } from "./types.js";
8
+ import type { CustomTool, CustomToolResult } from "../custom-tools/types";
9
+ import { callTool } from "./client";
10
+ import type { MCPContent, MCPServerConnection, MCPToolDefinition } from "./types";
11
11
 
12
12
  /** Details included in MCP tool results for rendering */
13
13
  export interface MCPToolDetails {
@@ -5,7 +5,7 @@
5
5
  * Based on MCP spec 2025-03-26.
6
6
  */
7
7
 
8
- import type { JsonRpcResponse, MCPHttpServerConfig, MCPSseServerConfig, MCPTransport } from "../types.js";
8
+ import type { JsonRpcResponse, MCPHttpServerConfig, MCPSseServerConfig, MCPTransport } from "../types";
9
9
 
10
10
  /** Generate unique request ID */
11
11
  function generateId(): string {
@@ -2,5 +2,5 @@
2
2
  * MCP transport exports.
3
3
  */
4
4
 
5
- export { createHttpTransport, HttpTransport } from "./http.js";
6
- export { createStdioTransport, StdioTransport } from "./stdio.js";
5
+ export { createHttpTransport, HttpTransport } from "./http";
6
+ export { createStdioTransport, StdioTransport } from "./stdio";
@@ -6,7 +6,7 @@
6
6
  */
7
7
 
8
8
  import { type Subprocess, spawn } from "bun";
9
- import type { JsonRpcResponse, MCPStdioServerConfig, MCPTransport } from "../types.js";
9
+ import type { JsonRpcResponse, MCPStdioServerConfig, MCPTransport } from "../types";
10
10
 
11
11
  /** Generate unique request ID */
12
12
  function generateId(): string {