@mariozechner/pi-coding-agent 0.22.4 → 0.23.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 (72) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +54 -2
  3. package/dist/cli/args.d.ts +1 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +5 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/core/agent-session.d.ts +9 -0
  8. package/dist/core/agent-session.d.ts.map +1 -1
  9. package/dist/core/agent-session.js +64 -11
  10. package/dist/core/agent-session.js.map +1 -1
  11. package/dist/core/custom-tools/index.d.ts +6 -0
  12. package/dist/core/custom-tools/index.d.ts.map +1 -0
  13. package/dist/core/custom-tools/index.js +5 -0
  14. package/dist/core/custom-tools/index.js.map +1 -0
  15. package/dist/core/custom-tools/loader.d.ts +24 -0
  16. package/dist/core/custom-tools/loader.d.ts.map +1 -0
  17. package/dist/core/custom-tools/loader.js +210 -0
  18. package/dist/core/custom-tools/loader.js.map +1 -0
  19. package/dist/core/custom-tools/types.d.ts +81 -0
  20. package/dist/core/custom-tools/types.d.ts.map +1 -0
  21. package/dist/core/custom-tools/types.js +8 -0
  22. package/dist/core/custom-tools/types.js.map +1 -0
  23. package/dist/core/hooks/index.d.ts +1 -1
  24. package/dist/core/hooks/index.d.ts.map +1 -1
  25. package/dist/core/hooks/index.js.map +1 -1
  26. package/dist/core/hooks/types.d.ts +14 -19
  27. package/dist/core/hooks/types.d.ts.map +1 -1
  28. package/dist/core/hooks/types.js.map +1 -1
  29. package/dist/core/index.d.ts +1 -0
  30. package/dist/core/index.d.ts.map +1 -1
  31. package/dist/core/index.js +1 -0
  32. package/dist/core/index.js.map +1 -1
  33. package/dist/core/session-manager.d.ts.map +1 -1
  34. package/dist/core/session-manager.js +4 -0
  35. package/dist/core/session-manager.js.map +1 -1
  36. package/dist/core/settings-manager.d.ts +3 -0
  37. package/dist/core/settings-manager.d.ts.map +1 -1
  38. package/dist/core/settings-manager.js +7 -0
  39. package/dist/core/settings-manager.js.map +1 -1
  40. package/dist/index.d.ts +4 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +3 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/main.d.ts.map +1 -1
  45. package/dist/main.js +22 -3
  46. package/dist/main.js.map +1 -1
  47. package/dist/modes/interactive/components/tool-execution.d.ts +4 -1
  48. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  49. package/dist/modes/interactive/components/tool-execution.js +64 -20
  50. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  51. package/dist/modes/interactive/interactive-mode.d.ts +11 -2
  52. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  53. package/dist/modes/interactive/interactive-mode.js +66 -12
  54. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  55. package/dist/modes/print-mode.d.ts.map +1 -1
  56. package/dist/modes/print-mode.js +35 -9
  57. package/dist/modes/print-mode.js.map +1 -1
  58. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  59. package/dist/modes/rpc/rpc-mode.js +27 -2
  60. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  61. package/docs/custom-tools.md +341 -0
  62. package/docs/hooks.md +54 -34
  63. package/examples/custom-tools/README.md +101 -0
  64. package/examples/custom-tools/hello.ts +20 -0
  65. package/examples/custom-tools/question.ts +83 -0
  66. package/examples/custom-tools/todo.ts +192 -0
  67. package/examples/hooks/README.md +79 -0
  68. package/examples/hooks/file-trigger.ts +36 -0
  69. package/examples/hooks/git-checkpoint.ts +48 -0
  70. package/examples/hooks/permission-gate.ts +38 -0
  71. package/examples/hooks/protected-paths.ts +30 -0
  72. package/package.json +7 -6
@@ -1 +1 @@
1
- {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;GAEG;AAyIH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBAwRxC","sourcesContent":["/**\n * Main entry point for the coding agent\n */\n\nimport { Agent, type Attachment, ProviderTransport, type ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport chalk from \"chalk\";\nimport { type Args, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { getModelsPath, VERSION } from \"./config.js\";\nimport { AgentSession } from \"./core/agent-session.js\";\nimport { exportFromFile } from \"./core/export-html.js\";\nimport { discoverAndLoadHooks, HookRunner, wrapToolsWithHooks } from \"./core/hooks/index.js\";\nimport { messageTransformer } from \"./core/messages.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./core/model-config.js\";\nimport { resolveModelScope, restoreModelFromSession, type ScopedModel } from \"./core/model-resolver.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { loadSlashCommands } from \"./core/slash-commands.js\";\nimport { buildSystemPrompt } from \"./core/system-prompt.js\";\nimport { allTools, codingTools } from \"./core/tools/index.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { getChangelogPath, getNewEntries, parseChangelog } from \"./utils/changelog.js\";\nimport { ensureTool } from \"./utils/tools-manager.js\";\n\n/** Check npm registry for new version (non-blocking) */\nasync function checkForNewVersion(currentVersion: string): Promise<string | null> {\n\ttry {\n\t\tconst response = await fetch(\"https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest\");\n\t\tif (!response.ok) return null;\n\n\t\tconst data = (await response.json()) as { version?: string };\n\t\tconst latestVersion = data.version;\n\n\t\tif (latestVersion && latestVersion !== currentVersion) {\n\t\t\treturn latestVersion;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\t// Silently fail - don't disrupt the user experience\n\t\treturn null;\n\t}\n}\n\n/** Run interactive mode with TUI */\nasync function runInteractiveMode(\n\tsession: AgentSession,\n\tversion: string,\n\tchangelogMarkdown: string | null,\n\tmodelFallbackMessage: string | null,\n\tversionCheckPromise: Promise<string | null>,\n\tinitialMessages: string[],\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n\tfdPath: string | null = null,\n): Promise<void> {\n\tconst mode = new InteractiveMode(session, version, changelogMarkdown, fdPath);\n\n\t// Initialize TUI (subscribes to agent events internally)\n\tawait mode.init();\n\n\t// Handle version check result when it completes (don't block)\n\tversionCheckPromise.then((newVersion) => {\n\t\tif (newVersion) {\n\t\t\tmode.showNewVersionNotification(newVersion);\n\t\t}\n\t});\n\n\t// Render any existing messages (from --continue mode)\n\tmode.renderInitialMessages(session.state);\n\n\t// Show model fallback warning at the end of the chat if applicable\n\tif (modelFallbackMessage) {\n\t\tmode.showWarning(modelFallbackMessage);\n\t}\n\n\t// Process initial message with attachments if provided (from @file args)\n\tif (initialMessage) {\n\t\ttry {\n\t\t\tawait session.prompt(initialMessage, { attachments: initialAttachments });\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Process remaining initial messages if provided (from CLI args)\n\tfor (const message of initialMessages) {\n\t\ttry {\n\t\t\tawait session.prompt(message);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Interactive loop\n\twhile (true) {\n\t\tconst userInput = await mode.getUserInput();\n\n\t\t// Process the message\n\t\ttry {\n\t\t\tawait session.prompt(userInput);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n}\n\n/** Prepare initial message from @file arguments */\nfunction prepareInitialMessage(parsed: Args): {\n\tinitialMessage?: string;\n\tinitialAttachments?: Attachment[];\n} {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn {};\n\t}\n\n\tconst { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);\n\n\t// Combine file content with first plain text message (if any)\n\tlet initialMessage: string;\n\tif (parsed.messages.length > 0) {\n\t\tinitialMessage = textContent + parsed.messages[0];\n\t\tparsed.messages.shift(); // Remove first message as it's been combined\n\t} else {\n\t\tinitialMessage = textContent;\n\t}\n\n\treturn {\n\t\tinitialMessage,\n\t\tinitialAttachments: imageAttachments.length > 0 ? imageAttachments : undefined,\n\t};\n}\n\nexport async function main(args: string[]) {\n\tconst parsed = parseArgs(args);\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\treturn;\n\t}\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\t// Handle --list-models flag: list available models and exit\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(searchPattern);\n\t\treturn;\n\t}\n\n\t// Handle --export flag: convert session file to HTML and exit\n\tif (parsed.export) {\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tconst result = exportFromFile(parsed.export, outputPath);\n\t\t\tconsole.log(`Exported to: ${result}`);\n\t\t\treturn;\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Validate: RPC mode doesn't support @file arguments\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Process @file arguments\n\tconst { initialMessage, initialAttachments } = prepareInitialMessage(parsed);\n\n\t// Determine if we're in interactive mode (needed for theme watcher)\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\n\t// Initialize theme (before any TUI rendering)\n\tconst settingsManager = new SettingsManager();\n\tconst themeName = settingsManager.getTheme();\n\tinitTheme(themeName, isInteractive);\n\n\t// Setup session manager\n\tconst sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);\n\n\tif (parsed.noSession) {\n\t\tsessionManager.disable();\n\t}\n\n\t// Handle --resume flag: show session selector\n\tif (parsed.resume) {\n\t\tconst selectedSession = await selectSession(sessionManager);\n\t\tif (!selectedSession) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\treturn;\n\t\t}\n\t\tsessionManager.setSessionFile(selectedSession);\n\t}\n\n\t// Resolve model scope early if provided\n\tlet scopedModels: ScopedModel[] = [];\n\tif (parsed.models && parsed.models.length > 0) {\n\t\tscopedModels = await resolveModelScope(parsed.models);\n\t}\n\n\t// Determine mode and output behavior\n\tconst mode = parsed.mode || \"text\";\n\tconst shouldPrintMessages = isInteractive;\n\n\t// Find initial model\n\tlet initialModel = await findInitialModelForSession(parsed, scopedModels, settingsManager);\n\tlet initialThinking: ThinkingLevel = \"off\";\n\n\t// Get thinking level from scoped models if applicable\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\tinitialThinking = scopedModels[0].thinkingLevel;\n\t} else {\n\t\t// Try saved thinking level\n\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\tif (savedThinking) {\n\t\t\tinitialThinking = savedThinking;\n\t\t}\n\t}\n\n\t// Non-interactive mode: fail early if no model available\n\tif (!isInteractive && !initialModel) {\n\t\tconsole.error(chalk.red(\"No models available.\"));\n\t\tconsole.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tconsole.error(chalk.yellow(`\\nOr create ${getModelsPath()}`));\n\t\tprocess.exit(1);\n\t}\n\n\t// Non-interactive mode: validate API key exists\n\tif (!isInteractive && initialModel) {\n\t\tconst apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));\n\t\tif (!apiKey) {\n\t\t\tconsole.error(chalk.red(`No API key found for ${initialModel.provider}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Build system prompt\n\tconst skillsEnabled = !parsed.noSkills && settingsManager.getSkillsEnabled();\n\tconst systemPrompt = buildSystemPrompt({\n\t\tcustomPrompt: parsed.systemPrompt,\n\t\tselectedTools: parsed.tools,\n\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\tskillsEnabled,\n\t});\n\n\t// Handle session restoration\n\tlet modelFallbackMessage: string | null = null;\n\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel) {\n\t\t\tconst result = await restoreModelFromSession(\n\t\t\t\tsavedModel.provider,\n\t\t\t\tsavedModel.modelId,\n\t\t\t\tinitialModel,\n\t\t\t\tshouldPrintMessages,\n\t\t\t);\n\n\t\t\tif (result.model) {\n\t\t\t\tinitialModel = result.model;\n\t\t\t}\n\t\t\tmodelFallbackMessage = result.fallbackMessage;\n\t\t}\n\n\t\t// Load and restore thinking level\n\t\tconst thinkingLevel = sessionManager.loadThinkingLevel() as ThinkingLevel;\n\t\tif (thinkingLevel) {\n\t\t\tinitialThinking = thinkingLevel;\n\t\t\tif (shouldPrintMessages) {\n\t\t\t\tconsole.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));\n\t\t\t}\n\t\t}\n\t}\n\n\t// CLI --thinking flag takes highest priority\n\tif (parsed.thinking) {\n\t\tinitialThinking = parsed.thinking;\n\t}\n\n\t// Determine which tools to use\n\tlet selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;\n\n\t// Discover and load hooks from:\n\t// 1. ~/.pi/agent/hooks/*.ts (global)\n\t// 2. cwd/.pi/hooks/*.ts (project-local)\n\t// 3. Explicit paths in settings.json\n\t// 4. CLI --hook flags\n\tlet hookRunner: HookRunner | null = null;\n\tconst cwd = process.cwd();\n\tconst configuredHookPaths = [...settingsManager.getHookPaths(), ...(parsed.hooks ?? [])];\n\tconst { hooks, errors } = await discoverAndLoadHooks(configuredHookPaths, cwd);\n\n\t// Report hook loading errors\n\tfor (const { path, error } of errors) {\n\t\tconsole.error(chalk.red(`Failed to load hook \"${path}\": ${error}`));\n\t}\n\n\tif (hooks.length > 0) {\n\t\tconst timeout = settingsManager.getHookTimeout();\n\t\thookRunner = new HookRunner(hooks, cwd, timeout);\n\n\t\t// Wrap tools with hook callbacks\n\t\tselectedTools = wrapToolsWithHooks(selectedTools, hookRunner);\n\t}\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: initialModel as any, // Can be null in interactive mode\n\t\t\tthinkingLevel: initialThinking,\n\t\t\ttools: selectedTools,\n\t\t},\n\t\tmessageTransformer,\n\t\tqueueMode: settingsManager.getQueueMode(),\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => {\n\t\t\t\tconst currentModel = agent.state.model;\n\t\t\t\tif (!currentModel) {\n\t\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t\t}\n\n\t\t\t\tif (parsed.apiKey) {\n\t\t\t\t\treturn parsed.apiKey;\n\t\t\t\t}\n\n\t\t\t\tconst key = await getApiKeyForModel(currentModel);\n\t\t\t\tif (!key) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No API key found for provider \"${currentModel.provider}\". Please set the appropriate environment variable or update ${getModelsPath()}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t\t},\n\t\t}),\n\t});\n\n\t// If initial thinking was requested but model doesn't support it, reset to off\n\tif (initialThinking !== \"off\" && initialModel && !initialModel.reasoning) {\n\t\tagent.setThinkingLevel(\"off\");\n\t}\n\n\t// Load previous messages if continuing, resuming, or using --session\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst messages = sessionManager.loadMessages();\n\t\tif (messages.length > 0) {\n\t\t\tagent.replaceMessages(messages);\n\t\t}\n\t}\n\n\t// Load file commands for slash command expansion\n\tconst fileCommands = loadSlashCommands();\n\n\t// Create session\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tscopedModels,\n\t\tfileCommands,\n\t\thookRunner,\n\t});\n\n\t// Route to appropriate mode\n\tif (mode === \"rpc\") {\n\t\tawait runRpcMode(session);\n\t} else if (isInteractive) {\n\t\t// Check for new version in the background\n\t\tconst versionCheckPromise = checkForNewVersion(VERSION).catch(() => null);\n\n\t\t// Check if we should show changelog\n\t\tconst changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);\n\n\t\t// Show model scope if provided\n\t\tif (scopedModels.length > 0) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel !== \"off\" ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\t// Ensure fd tool is available for file autocomplete\n\t\tconst fdPath = await ensureTool(\"fd\");\n\n\t\tawait runInteractiveMode(\n\t\t\tsession,\n\t\t\tVERSION,\n\t\t\tchangelogMarkdown,\n\t\t\tmodelFallbackMessage,\n\t\t\tversionCheckPromise,\n\t\t\tparsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialAttachments,\n\t\t\tfdPath,\n\t\t);\n\t} else {\n\t\t// Non-interactive mode (--print flag or --mode flag)\n\t\tawait runPrintMode(session, mode, parsed.messages, initialMessage, initialAttachments);\n\t\t// Clean up and exit (file watchers keep process alive)\n\t\tstopThemeWatcher();\n\t\tprocess.exit(0);\n\t}\n}\n\n/** Find initial model based on CLI args, scoped models, settings, or available models */\nasync function findInitialModelForSession(parsed: Args, scopedModels: ScopedModel[], settingsManager: SettingsManager) {\n\t// 1. CLI args take priority\n\tif (parsed.provider && parsed.model) {\n\t\tconst { model, error } = findModel(parsed.provider, parsed.model);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!model) {\n\t\t\tconsole.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\treturn model;\n\t}\n\n\t// 2. Use first model from scoped models (skip if continuing/resuming)\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\treturn scopedModels[0].model;\n\t}\n\n\t// 3. Try saved default from settings\n\tconst defaultProvider = settingsManager.getDefaultProvider();\n\tconst defaultModelId = settingsManager.getDefaultModel();\n\tif (defaultProvider && defaultModelId) {\n\t\tconst { model, error } = findModel(defaultProvider, defaultModelId);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (model) {\n\t\t\treturn model;\n\t\t}\n\t}\n\n\t// 4. Try first available model with valid API key\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.error(chalk.red(error));\n\t\tprocess.exit(1);\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn null;\n}\n\n/** Get changelog markdown to display (only for new sessions with updates) */\nfunction getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | null {\n\tif (parsed.continue || parsed.resume) {\n\t\treturn null;\n\t}\n\n\tconst lastVersion = settingsManager.getLastChangelogVersion();\n\tconst changelogPath = getChangelogPath();\n\tconst entries = parseChangelog(changelogPath);\n\n\tif (!lastVersion) {\n\t\t// First run - show all entries\n\t\tif (entries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn entries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t} else {\n\t\t// Check for new entries since last version\n\t\tconst newEntries = getNewEntries(entries, lastVersion);\n\t\tif (newEntries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn newEntries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t}\n\n\treturn null;\n}\n"]}
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA;;GAEG;AA4IH,wBAAsB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,iBAmTxC","sourcesContent":["/**\n * Main entry point for the coding agent\n */\n\nimport { Agent, type Attachment, ProviderTransport, type ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport chalk from \"chalk\";\nimport { type Args, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { getModelsPath, VERSION } from \"./config.js\";\nimport { AgentSession } from \"./core/agent-session.js\";\nimport { discoverAndLoadCustomTools, type LoadedCustomTool } from \"./core/custom-tools/index.js\";\nimport { exportFromFile } from \"./core/export-html.js\";\nimport { discoverAndLoadHooks, HookRunner, wrapToolsWithHooks } from \"./core/hooks/index.js\";\nimport { messageTransformer } from \"./core/messages.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./core/model-config.js\";\nimport { resolveModelScope, restoreModelFromSession, type ScopedModel } from \"./core/model-resolver.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { loadSlashCommands } from \"./core/slash-commands.js\";\nimport { buildSystemPrompt } from \"./core/system-prompt.js\";\nimport { allTools, codingTools } from \"./core/tools/index.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { getChangelogPath, getNewEntries, parseChangelog } from \"./utils/changelog.js\";\nimport { ensureTool } from \"./utils/tools-manager.js\";\n\n/** Check npm registry for new version (non-blocking) */\nasync function checkForNewVersion(currentVersion: string): Promise<string | null> {\n\ttry {\n\t\tconst response = await fetch(\"https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest\");\n\t\tif (!response.ok) return null;\n\n\t\tconst data = (await response.json()) as { version?: string };\n\t\tconst latestVersion = data.version;\n\n\t\tif (latestVersion && latestVersion !== currentVersion) {\n\t\t\treturn latestVersion;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\t// Silently fail - don't disrupt the user experience\n\t\treturn null;\n\t}\n}\n\n/** Run interactive mode with TUI */\nasync function runInteractiveMode(\n\tsession: AgentSession,\n\tversion: string,\n\tchangelogMarkdown: string | null,\n\tmodelFallbackMessage: string | null,\n\tversionCheckPromise: Promise<string | null>,\n\tinitialMessages: string[],\n\tcustomTools: LoadedCustomTool[],\n\tsetToolUIContext: (uiContext: import(\"./core/hooks/types.js\").HookUIContext, hasUI: boolean) => void,\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n\tfdPath: string | null = null,\n): Promise<void> {\n\tconst mode = new InteractiveMode(session, version, changelogMarkdown, customTools, setToolUIContext, fdPath);\n\n\t// Initialize TUI (subscribes to agent events internally)\n\tawait mode.init();\n\n\t// Handle version check result when it completes (don't block)\n\tversionCheckPromise.then((newVersion) => {\n\t\tif (newVersion) {\n\t\t\tmode.showNewVersionNotification(newVersion);\n\t\t}\n\t});\n\n\t// Render any existing messages (from --continue mode)\n\tmode.renderInitialMessages(session.state);\n\n\t// Show model fallback warning at the end of the chat if applicable\n\tif (modelFallbackMessage) {\n\t\tmode.showWarning(modelFallbackMessage);\n\t}\n\n\t// Process initial message with attachments if provided (from @file args)\n\tif (initialMessage) {\n\t\ttry {\n\t\t\tawait session.prompt(initialMessage, { attachments: initialAttachments });\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Process remaining initial messages if provided (from CLI args)\n\tfor (const message of initialMessages) {\n\t\ttry {\n\t\t\tawait session.prompt(message);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Interactive loop\n\twhile (true) {\n\t\tconst userInput = await mode.getUserInput();\n\n\t\t// Process the message\n\t\ttry {\n\t\t\tawait session.prompt(userInput);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n}\n\n/** Prepare initial message from @file arguments */\nfunction prepareInitialMessage(parsed: Args): {\n\tinitialMessage?: string;\n\tinitialAttachments?: Attachment[];\n} {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn {};\n\t}\n\n\tconst { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);\n\n\t// Combine file content with first plain text message (if any)\n\tlet initialMessage: string;\n\tif (parsed.messages.length > 0) {\n\t\tinitialMessage = textContent + parsed.messages[0];\n\t\tparsed.messages.shift(); // Remove first message as it's been combined\n\t} else {\n\t\tinitialMessage = textContent;\n\t}\n\n\treturn {\n\t\tinitialMessage,\n\t\tinitialAttachments: imageAttachments.length > 0 ? imageAttachments : undefined,\n\t};\n}\n\nexport async function main(args: string[]) {\n\tconst parsed = parseArgs(args);\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\treturn;\n\t}\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\t// Handle --list-models flag: list available models and exit\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(searchPattern);\n\t\treturn;\n\t}\n\n\t// Handle --export flag: convert session file to HTML and exit\n\tif (parsed.export) {\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tconst result = exportFromFile(parsed.export, outputPath);\n\t\t\tconsole.log(`Exported to: ${result}`);\n\t\t\treturn;\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Validate: RPC mode doesn't support @file arguments\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Process @file arguments\n\tconst { initialMessage, initialAttachments } = prepareInitialMessage(parsed);\n\n\t// Determine if we're in interactive mode (needed for theme watcher)\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\n\t// Initialize theme (before any TUI rendering)\n\tconst settingsManager = new SettingsManager();\n\tconst themeName = settingsManager.getTheme();\n\tinitTheme(themeName, isInteractive);\n\n\t// Setup session manager\n\tconst sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);\n\n\tif (parsed.noSession) {\n\t\tsessionManager.disable();\n\t}\n\n\t// Handle --resume flag: show session selector\n\tif (parsed.resume) {\n\t\tconst selectedSession = await selectSession(sessionManager);\n\t\tif (!selectedSession) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\treturn;\n\t\t}\n\t\tsessionManager.setSessionFile(selectedSession);\n\t}\n\n\t// Resolve model scope early if provided\n\tlet scopedModels: ScopedModel[] = [];\n\tif (parsed.models && parsed.models.length > 0) {\n\t\tscopedModels = await resolveModelScope(parsed.models);\n\t}\n\n\t// Determine mode and output behavior\n\tconst mode = parsed.mode || \"text\";\n\tconst shouldPrintMessages = isInteractive;\n\n\t// Find initial model\n\tlet initialModel = await findInitialModelForSession(parsed, scopedModels, settingsManager);\n\tlet initialThinking: ThinkingLevel = \"off\";\n\n\t// Get thinking level from scoped models if applicable\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\tinitialThinking = scopedModels[0].thinkingLevel;\n\t} else {\n\t\t// Try saved thinking level\n\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\tif (savedThinking) {\n\t\t\tinitialThinking = savedThinking;\n\t\t}\n\t}\n\n\t// Non-interactive mode: fail early if no model available\n\tif (!isInteractive && !initialModel) {\n\t\tconsole.error(chalk.red(\"No models available.\"));\n\t\tconsole.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tconsole.error(chalk.yellow(`\\nOr create ${getModelsPath()}`));\n\t\tprocess.exit(1);\n\t}\n\n\t// Non-interactive mode: validate API key exists\n\tif (!isInteractive && initialModel) {\n\t\tconst apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));\n\t\tif (!apiKey) {\n\t\t\tconsole.error(chalk.red(`No API key found for ${initialModel.provider}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Build system prompt\n\tconst skillsEnabled = !parsed.noSkills && settingsManager.getSkillsEnabled();\n\tconst systemPrompt = buildSystemPrompt({\n\t\tcustomPrompt: parsed.systemPrompt,\n\t\tselectedTools: parsed.tools,\n\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\tskillsEnabled,\n\t});\n\n\t// Handle session restoration\n\tlet modelFallbackMessage: string | null = null;\n\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel) {\n\t\t\tconst result = await restoreModelFromSession(\n\t\t\t\tsavedModel.provider,\n\t\t\t\tsavedModel.modelId,\n\t\t\t\tinitialModel,\n\t\t\t\tshouldPrintMessages,\n\t\t\t);\n\n\t\t\tif (result.model) {\n\t\t\t\tinitialModel = result.model;\n\t\t\t}\n\t\t\tmodelFallbackMessage = result.fallbackMessage;\n\t\t}\n\n\t\t// Load and restore thinking level\n\t\tconst thinkingLevel = sessionManager.loadThinkingLevel() as ThinkingLevel;\n\t\tif (thinkingLevel) {\n\t\t\tinitialThinking = thinkingLevel;\n\t\t\tif (shouldPrintMessages) {\n\t\t\t\tconsole.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));\n\t\t\t}\n\t\t}\n\t}\n\n\t// CLI --thinking flag takes highest priority\n\tif (parsed.thinking) {\n\t\tinitialThinking = parsed.thinking;\n\t}\n\n\t// Determine which tools to use\n\tlet selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;\n\n\t// Discover and load hooks from:\n\t// 1. ~/.pi/agent/hooks/*.ts (global)\n\t// 2. cwd/.pi/hooks/*.ts (project-local)\n\t// 3. Explicit paths in settings.json\n\t// 4. CLI --hook flags\n\tlet hookRunner: HookRunner | null = null;\n\tconst cwd = process.cwd();\n\tconst configuredHookPaths = [...settingsManager.getHookPaths(), ...(parsed.hooks ?? [])];\n\tconst { hooks, errors } = await discoverAndLoadHooks(configuredHookPaths, cwd);\n\n\t// Report hook loading errors\n\tfor (const { path, error } of errors) {\n\t\tconsole.error(chalk.red(`Failed to load hook \"${path}\": ${error}`));\n\t}\n\n\tif (hooks.length > 0) {\n\t\tconst timeout = settingsManager.getHookTimeout();\n\t\thookRunner = new HookRunner(hooks, cwd, timeout);\n\n\t\t// Wrap tools with hook callbacks\n\t\tselectedTools = wrapToolsWithHooks(selectedTools, hookRunner);\n\t}\n\n\t// Discover and load custom tools from:\n\t// 1. ~/.pi/agent/tools/*.ts (global)\n\t// 2. cwd/.pi/tools/*.ts (project-local)\n\t// 3. Explicit paths in settings.json\n\t// 4. CLI --tool flags\n\tconst configuredToolPaths = [...settingsManager.getCustomToolPaths(), ...(parsed.customTools ?? [])];\n\tconst builtInToolNames = Object.keys(allTools);\n\tconst {\n\t\ttools: loadedCustomTools,\n\t\terrors: toolErrors,\n\t\tsetUIContext: setToolUIContext,\n\t} = await discoverAndLoadCustomTools(configuredToolPaths, cwd, builtInToolNames);\n\n\t// Report custom tool loading errors\n\tfor (const { path, error } of toolErrors) {\n\t\tconsole.error(chalk.red(`Failed to load custom tool \"${path}\": ${error}`));\n\t}\n\n\t// Add custom tools to selected tools\n\tif (loadedCustomTools.length > 0) {\n\t\tconst customToolInstances = loadedCustomTools.map((lt) => lt.tool);\n\t\tselectedTools = [...selectedTools, ...customToolInstances] as typeof selectedTools;\n\t}\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: initialModel as any, // Can be null in interactive mode\n\t\t\tthinkingLevel: initialThinking,\n\t\t\ttools: selectedTools,\n\t\t},\n\t\tmessageTransformer,\n\t\tqueueMode: settingsManager.getQueueMode(),\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => {\n\t\t\t\tconst currentModel = agent.state.model;\n\t\t\t\tif (!currentModel) {\n\t\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t\t}\n\n\t\t\t\tif (parsed.apiKey) {\n\t\t\t\t\treturn parsed.apiKey;\n\t\t\t\t}\n\n\t\t\t\tconst key = await getApiKeyForModel(currentModel);\n\t\t\t\tif (!key) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No API key found for provider \"${currentModel.provider}\". Please set the appropriate environment variable or update ${getModelsPath()}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t\t},\n\t\t}),\n\t});\n\n\t// If initial thinking was requested but model doesn't support it, reset to off\n\tif (initialThinking !== \"off\" && initialModel && !initialModel.reasoning) {\n\t\tagent.setThinkingLevel(\"off\");\n\t}\n\n\t// Load previous messages if continuing, resuming, or using --session\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst messages = sessionManager.loadMessages();\n\t\tif (messages.length > 0) {\n\t\t\tagent.replaceMessages(messages);\n\t\t}\n\t}\n\n\t// Load file commands for slash command expansion\n\tconst fileCommands = loadSlashCommands();\n\n\t// Create session\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tscopedModels,\n\t\tfileCommands,\n\t\thookRunner,\n\t\tcustomTools: loadedCustomTools,\n\t});\n\n\t// Route to appropriate mode\n\tif (mode === \"rpc\") {\n\t\tawait runRpcMode(session);\n\t} else if (isInteractive) {\n\t\t// Check for new version in the background\n\t\tconst versionCheckPromise = checkForNewVersion(VERSION).catch(() => null);\n\n\t\t// Check if we should show changelog\n\t\tconst changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);\n\n\t\t// Show model scope if provided\n\t\tif (scopedModels.length > 0) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel !== \"off\" ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\t// Ensure fd tool is available for file autocomplete\n\t\tconst fdPath = await ensureTool(\"fd\");\n\n\t\tawait runInteractiveMode(\n\t\t\tsession,\n\t\t\tVERSION,\n\t\t\tchangelogMarkdown,\n\t\t\tmodelFallbackMessage,\n\t\t\tversionCheckPromise,\n\t\t\tparsed.messages,\n\t\t\tloadedCustomTools,\n\t\t\tsetToolUIContext,\n\t\t\tinitialMessage,\n\t\t\tinitialAttachments,\n\t\t\tfdPath,\n\t\t);\n\t} else {\n\t\t// Non-interactive mode (--print flag or --mode flag)\n\t\tawait runPrintMode(session, mode, parsed.messages, initialMessage, initialAttachments);\n\t\t// Clean up and exit (file watchers keep process alive)\n\t\tstopThemeWatcher();\n\t\tprocess.exit(0);\n\t}\n}\n\n/** Find initial model based on CLI args, scoped models, settings, or available models */\nasync function findInitialModelForSession(parsed: Args, scopedModels: ScopedModel[], settingsManager: SettingsManager) {\n\t// 1. CLI args take priority\n\tif (parsed.provider && parsed.model) {\n\t\tconst { model, error } = findModel(parsed.provider, parsed.model);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!model) {\n\t\t\tconsole.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\treturn model;\n\t}\n\n\t// 2. Use first model from scoped models (skip if continuing/resuming)\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\treturn scopedModels[0].model;\n\t}\n\n\t// 3. Try saved default from settings\n\tconst defaultProvider = settingsManager.getDefaultProvider();\n\tconst defaultModelId = settingsManager.getDefaultModel();\n\tif (defaultProvider && defaultModelId) {\n\t\tconst { model, error } = findModel(defaultProvider, defaultModelId);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (model) {\n\t\t\treturn model;\n\t\t}\n\t}\n\n\t// 4. Try first available model with valid API key\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.error(chalk.red(error));\n\t\tprocess.exit(1);\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn null;\n}\n\n/** Get changelog markdown to display (only for new sessions with updates) */\nfunction getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | null {\n\tif (parsed.continue || parsed.resume) {\n\t\treturn null;\n\t}\n\n\tconst lastVersion = settingsManager.getLastChangelogVersion();\n\tconst changelogPath = getChangelogPath();\n\tconst entries = parseChangelog(changelogPath);\n\n\tif (!lastVersion) {\n\t\t// First run - show all entries\n\t\tif (entries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn entries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t} else {\n\t\t// Check for new entries since last version\n\t\tconst newEntries = getNewEntries(entries, lastVersion);\n\t\tif (newEntries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn newEntries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t}\n\n\treturn null;\n}\n"]}
package/dist/main.js CHANGED
@@ -9,6 +9,7 @@ import { listModels } from "./cli/list-models.js";
9
9
  import { selectSession } from "./cli/session-picker.js";
10
10
  import { getModelsPath, VERSION } from "./config.js";
11
11
  import { AgentSession } from "./core/agent-session.js";
12
+ import { discoverAndLoadCustomTools } from "./core/custom-tools/index.js";
12
13
  import { exportFromFile } from "./core/export-html.js";
13
14
  import { discoverAndLoadHooks, HookRunner, wrapToolsWithHooks } from "./core/hooks/index.js";
14
15
  import { messageTransformer } from "./core/messages.js";
@@ -42,8 +43,8 @@ async function checkForNewVersion(currentVersion) {
42
43
  }
43
44
  }
44
45
  /** Run interactive mode with TUI */
45
- async function runInteractiveMode(session, version, changelogMarkdown, modelFallbackMessage, versionCheckPromise, initialMessages, initialMessage, initialAttachments, fdPath = null) {
46
- const mode = new InteractiveMode(session, version, changelogMarkdown, fdPath);
46
+ async function runInteractiveMode(session, version, changelogMarkdown, modelFallbackMessage, versionCheckPromise, initialMessages, customTools, setToolUIContext, initialMessage, initialAttachments, fdPath = null) {
47
+ const mode = new InteractiveMode(session, version, changelogMarkdown, customTools, setToolUIContext, fdPath);
47
48
  // Initialize TUI (subscribes to agent events internally)
48
49
  await mode.init();
49
50
  // Handle version check result when it completes (don't block)
@@ -259,6 +260,23 @@ export async function main(args) {
259
260
  // Wrap tools with hook callbacks
260
261
  selectedTools = wrapToolsWithHooks(selectedTools, hookRunner);
261
262
  }
263
+ // Discover and load custom tools from:
264
+ // 1. ~/.pi/agent/tools/*.ts (global)
265
+ // 2. cwd/.pi/tools/*.ts (project-local)
266
+ // 3. Explicit paths in settings.json
267
+ // 4. CLI --tool flags
268
+ const configuredToolPaths = [...settingsManager.getCustomToolPaths(), ...(parsed.customTools ?? [])];
269
+ const builtInToolNames = Object.keys(allTools);
270
+ const { tools: loadedCustomTools, errors: toolErrors, setUIContext: setToolUIContext, } = await discoverAndLoadCustomTools(configuredToolPaths, cwd, builtInToolNames);
271
+ // Report custom tool loading errors
272
+ for (const { path, error } of toolErrors) {
273
+ console.error(chalk.red(`Failed to load custom tool "${path}": ${error}`));
274
+ }
275
+ // Add custom tools to selected tools
276
+ if (loadedCustomTools.length > 0) {
277
+ const customToolInstances = loadedCustomTools.map((lt) => lt.tool);
278
+ selectedTools = [...selectedTools, ...customToolInstances];
279
+ }
262
280
  // Create agent
263
281
  const agent = new Agent({
264
282
  initialState: {
@@ -307,6 +325,7 @@ export async function main(args) {
307
325
  scopedModels,
308
326
  fileCommands,
309
327
  hookRunner,
328
+ customTools: loadedCustomTools,
310
329
  });
311
330
  // Route to appropriate mode
312
331
  if (mode === "rpc") {
@@ -329,7 +348,7 @@ export async function main(args) {
329
348
  }
330
349
  // Ensure fd tool is available for file autocomplete
331
350
  const fdPath = await ensureTool("fd");
332
- await runInteractiveMode(session, VERSION, changelogMarkdown, modelFallbackMessage, versionCheckPromise, parsed.messages, initialMessage, initialAttachments, fdPath);
351
+ await runInteractiveMode(session, VERSION, changelogMarkdown, modelFallbackMessage, versionCheckPromise, parsed.messages, loadedCustomTools, setToolUIContext, initialMessage, initialAttachments, fdPath);
333
352
  }
334
353
  else {
335
354
  // Non-interactive mode (--print flag or --mode flag)
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main entry point for the coding agent\n */\n\nimport { Agent, type Attachment, ProviderTransport, type ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport chalk from \"chalk\";\nimport { type Args, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { getModelsPath, VERSION } from \"./config.js\";\nimport { AgentSession } from \"./core/agent-session.js\";\nimport { exportFromFile } from \"./core/export-html.js\";\nimport { discoverAndLoadHooks, HookRunner, wrapToolsWithHooks } from \"./core/hooks/index.js\";\nimport { messageTransformer } from \"./core/messages.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./core/model-config.js\";\nimport { resolveModelScope, restoreModelFromSession, type ScopedModel } from \"./core/model-resolver.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { loadSlashCommands } from \"./core/slash-commands.js\";\nimport { buildSystemPrompt } from \"./core/system-prompt.js\";\nimport { allTools, codingTools } from \"./core/tools/index.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { getChangelogPath, getNewEntries, parseChangelog } from \"./utils/changelog.js\";\nimport { ensureTool } from \"./utils/tools-manager.js\";\n\n/** Check npm registry for new version (non-blocking) */\nasync function checkForNewVersion(currentVersion: string): Promise<string | null> {\n\ttry {\n\t\tconst response = await fetch(\"https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest\");\n\t\tif (!response.ok) return null;\n\n\t\tconst data = (await response.json()) as { version?: string };\n\t\tconst latestVersion = data.version;\n\n\t\tif (latestVersion && latestVersion !== currentVersion) {\n\t\t\treturn latestVersion;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\t// Silently fail - don't disrupt the user experience\n\t\treturn null;\n\t}\n}\n\n/** Run interactive mode with TUI */\nasync function runInteractiveMode(\n\tsession: AgentSession,\n\tversion: string,\n\tchangelogMarkdown: string | null,\n\tmodelFallbackMessage: string | null,\n\tversionCheckPromise: Promise<string | null>,\n\tinitialMessages: string[],\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n\tfdPath: string | null = null,\n): Promise<void> {\n\tconst mode = new InteractiveMode(session, version, changelogMarkdown, fdPath);\n\n\t// Initialize TUI (subscribes to agent events internally)\n\tawait mode.init();\n\n\t// Handle version check result when it completes (don't block)\n\tversionCheckPromise.then((newVersion) => {\n\t\tif (newVersion) {\n\t\t\tmode.showNewVersionNotification(newVersion);\n\t\t}\n\t});\n\n\t// Render any existing messages (from --continue mode)\n\tmode.renderInitialMessages(session.state);\n\n\t// Show model fallback warning at the end of the chat if applicable\n\tif (modelFallbackMessage) {\n\t\tmode.showWarning(modelFallbackMessage);\n\t}\n\n\t// Process initial message with attachments if provided (from @file args)\n\tif (initialMessage) {\n\t\ttry {\n\t\t\tawait session.prompt(initialMessage, { attachments: initialAttachments });\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Process remaining initial messages if provided (from CLI args)\n\tfor (const message of initialMessages) {\n\t\ttry {\n\t\t\tawait session.prompt(message);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Interactive loop\n\twhile (true) {\n\t\tconst userInput = await mode.getUserInput();\n\n\t\t// Process the message\n\t\ttry {\n\t\t\tawait session.prompt(userInput);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n}\n\n/** Prepare initial message from @file arguments */\nfunction prepareInitialMessage(parsed: Args): {\n\tinitialMessage?: string;\n\tinitialAttachments?: Attachment[];\n} {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn {};\n\t}\n\n\tconst { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);\n\n\t// Combine file content with first plain text message (if any)\n\tlet initialMessage: string;\n\tif (parsed.messages.length > 0) {\n\t\tinitialMessage = textContent + parsed.messages[0];\n\t\tparsed.messages.shift(); // Remove first message as it's been combined\n\t} else {\n\t\tinitialMessage = textContent;\n\t}\n\n\treturn {\n\t\tinitialMessage,\n\t\tinitialAttachments: imageAttachments.length > 0 ? imageAttachments : undefined,\n\t};\n}\n\nexport async function main(args: string[]) {\n\tconst parsed = parseArgs(args);\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\treturn;\n\t}\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\t// Handle --list-models flag: list available models and exit\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(searchPattern);\n\t\treturn;\n\t}\n\n\t// Handle --export flag: convert session file to HTML and exit\n\tif (parsed.export) {\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tconst result = exportFromFile(parsed.export, outputPath);\n\t\t\tconsole.log(`Exported to: ${result}`);\n\t\t\treturn;\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Validate: RPC mode doesn't support @file arguments\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Process @file arguments\n\tconst { initialMessage, initialAttachments } = prepareInitialMessage(parsed);\n\n\t// Determine if we're in interactive mode (needed for theme watcher)\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\n\t// Initialize theme (before any TUI rendering)\n\tconst settingsManager = new SettingsManager();\n\tconst themeName = settingsManager.getTheme();\n\tinitTheme(themeName, isInteractive);\n\n\t// Setup session manager\n\tconst sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);\n\n\tif (parsed.noSession) {\n\t\tsessionManager.disable();\n\t}\n\n\t// Handle --resume flag: show session selector\n\tif (parsed.resume) {\n\t\tconst selectedSession = await selectSession(sessionManager);\n\t\tif (!selectedSession) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\treturn;\n\t\t}\n\t\tsessionManager.setSessionFile(selectedSession);\n\t}\n\n\t// Resolve model scope early if provided\n\tlet scopedModels: ScopedModel[] = [];\n\tif (parsed.models && parsed.models.length > 0) {\n\t\tscopedModels = await resolveModelScope(parsed.models);\n\t}\n\n\t// Determine mode and output behavior\n\tconst mode = parsed.mode || \"text\";\n\tconst shouldPrintMessages = isInteractive;\n\n\t// Find initial model\n\tlet initialModel = await findInitialModelForSession(parsed, scopedModels, settingsManager);\n\tlet initialThinking: ThinkingLevel = \"off\";\n\n\t// Get thinking level from scoped models if applicable\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\tinitialThinking = scopedModels[0].thinkingLevel;\n\t} else {\n\t\t// Try saved thinking level\n\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\tif (savedThinking) {\n\t\t\tinitialThinking = savedThinking;\n\t\t}\n\t}\n\n\t// Non-interactive mode: fail early if no model available\n\tif (!isInteractive && !initialModel) {\n\t\tconsole.error(chalk.red(\"No models available.\"));\n\t\tconsole.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tconsole.error(chalk.yellow(`\\nOr create ${getModelsPath()}`));\n\t\tprocess.exit(1);\n\t}\n\n\t// Non-interactive mode: validate API key exists\n\tif (!isInteractive && initialModel) {\n\t\tconst apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));\n\t\tif (!apiKey) {\n\t\t\tconsole.error(chalk.red(`No API key found for ${initialModel.provider}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Build system prompt\n\tconst skillsEnabled = !parsed.noSkills && settingsManager.getSkillsEnabled();\n\tconst systemPrompt = buildSystemPrompt({\n\t\tcustomPrompt: parsed.systemPrompt,\n\t\tselectedTools: parsed.tools,\n\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\tskillsEnabled,\n\t});\n\n\t// Handle session restoration\n\tlet modelFallbackMessage: string | null = null;\n\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel) {\n\t\t\tconst result = await restoreModelFromSession(\n\t\t\t\tsavedModel.provider,\n\t\t\t\tsavedModel.modelId,\n\t\t\t\tinitialModel,\n\t\t\t\tshouldPrintMessages,\n\t\t\t);\n\n\t\t\tif (result.model) {\n\t\t\t\tinitialModel = result.model;\n\t\t\t}\n\t\t\tmodelFallbackMessage = result.fallbackMessage;\n\t\t}\n\n\t\t// Load and restore thinking level\n\t\tconst thinkingLevel = sessionManager.loadThinkingLevel() as ThinkingLevel;\n\t\tif (thinkingLevel) {\n\t\t\tinitialThinking = thinkingLevel;\n\t\t\tif (shouldPrintMessages) {\n\t\t\t\tconsole.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));\n\t\t\t}\n\t\t}\n\t}\n\n\t// CLI --thinking flag takes highest priority\n\tif (parsed.thinking) {\n\t\tinitialThinking = parsed.thinking;\n\t}\n\n\t// Determine which tools to use\n\tlet selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;\n\n\t// Discover and load hooks from:\n\t// 1. ~/.pi/agent/hooks/*.ts (global)\n\t// 2. cwd/.pi/hooks/*.ts (project-local)\n\t// 3. Explicit paths in settings.json\n\t// 4. CLI --hook flags\n\tlet hookRunner: HookRunner | null = null;\n\tconst cwd = process.cwd();\n\tconst configuredHookPaths = [...settingsManager.getHookPaths(), ...(parsed.hooks ?? [])];\n\tconst { hooks, errors } = await discoverAndLoadHooks(configuredHookPaths, cwd);\n\n\t// Report hook loading errors\n\tfor (const { path, error } of errors) {\n\t\tconsole.error(chalk.red(`Failed to load hook \"${path}\": ${error}`));\n\t}\n\n\tif (hooks.length > 0) {\n\t\tconst timeout = settingsManager.getHookTimeout();\n\t\thookRunner = new HookRunner(hooks, cwd, timeout);\n\n\t\t// Wrap tools with hook callbacks\n\t\tselectedTools = wrapToolsWithHooks(selectedTools, hookRunner);\n\t}\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: initialModel as any, // Can be null in interactive mode\n\t\t\tthinkingLevel: initialThinking,\n\t\t\ttools: selectedTools,\n\t\t},\n\t\tmessageTransformer,\n\t\tqueueMode: settingsManager.getQueueMode(),\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => {\n\t\t\t\tconst currentModel = agent.state.model;\n\t\t\t\tif (!currentModel) {\n\t\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t\t}\n\n\t\t\t\tif (parsed.apiKey) {\n\t\t\t\t\treturn parsed.apiKey;\n\t\t\t\t}\n\n\t\t\t\tconst key = await getApiKeyForModel(currentModel);\n\t\t\t\tif (!key) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No API key found for provider \"${currentModel.provider}\". Please set the appropriate environment variable or update ${getModelsPath()}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t\t},\n\t\t}),\n\t});\n\n\t// If initial thinking was requested but model doesn't support it, reset to off\n\tif (initialThinking !== \"off\" && initialModel && !initialModel.reasoning) {\n\t\tagent.setThinkingLevel(\"off\");\n\t}\n\n\t// Load previous messages if continuing, resuming, or using --session\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst messages = sessionManager.loadMessages();\n\t\tif (messages.length > 0) {\n\t\t\tagent.replaceMessages(messages);\n\t\t}\n\t}\n\n\t// Load file commands for slash command expansion\n\tconst fileCommands = loadSlashCommands();\n\n\t// Create session\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tscopedModels,\n\t\tfileCommands,\n\t\thookRunner,\n\t});\n\n\t// Route to appropriate mode\n\tif (mode === \"rpc\") {\n\t\tawait runRpcMode(session);\n\t} else if (isInteractive) {\n\t\t// Check for new version in the background\n\t\tconst versionCheckPromise = checkForNewVersion(VERSION).catch(() => null);\n\n\t\t// Check if we should show changelog\n\t\tconst changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);\n\n\t\t// Show model scope if provided\n\t\tif (scopedModels.length > 0) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel !== \"off\" ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\t// Ensure fd tool is available for file autocomplete\n\t\tconst fdPath = await ensureTool(\"fd\");\n\n\t\tawait runInteractiveMode(\n\t\t\tsession,\n\t\t\tVERSION,\n\t\t\tchangelogMarkdown,\n\t\t\tmodelFallbackMessage,\n\t\t\tversionCheckPromise,\n\t\t\tparsed.messages,\n\t\t\tinitialMessage,\n\t\t\tinitialAttachments,\n\t\t\tfdPath,\n\t\t);\n\t} else {\n\t\t// Non-interactive mode (--print flag or --mode flag)\n\t\tawait runPrintMode(session, mode, parsed.messages, initialMessage, initialAttachments);\n\t\t// Clean up and exit (file watchers keep process alive)\n\t\tstopThemeWatcher();\n\t\tprocess.exit(0);\n\t}\n}\n\n/** Find initial model based on CLI args, scoped models, settings, or available models */\nasync function findInitialModelForSession(parsed: Args, scopedModels: ScopedModel[], settingsManager: SettingsManager) {\n\t// 1. CLI args take priority\n\tif (parsed.provider && parsed.model) {\n\t\tconst { model, error } = findModel(parsed.provider, parsed.model);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!model) {\n\t\t\tconsole.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\treturn model;\n\t}\n\n\t// 2. Use first model from scoped models (skip if continuing/resuming)\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\treturn scopedModels[0].model;\n\t}\n\n\t// 3. Try saved default from settings\n\tconst defaultProvider = settingsManager.getDefaultProvider();\n\tconst defaultModelId = settingsManager.getDefaultModel();\n\tif (defaultProvider && defaultModelId) {\n\t\tconst { model, error } = findModel(defaultProvider, defaultModelId);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (model) {\n\t\t\treturn model;\n\t\t}\n\t}\n\n\t// 4. Try first available model with valid API key\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.error(chalk.red(error));\n\t\tprocess.exit(1);\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn null;\n}\n\n/** Get changelog markdown to display (only for new sessions with updates) */\nfunction getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | null {\n\tif (parsed.continue || parsed.resume) {\n\t\treturn null;\n\t}\n\n\tconst lastVersion = settingsManager.getLastChangelogVersion();\n\tconst changelogPath = getChangelogPath();\n\tconst entries = parseChangelog(changelogPath);\n\n\tif (!lastVersion) {\n\t\t// First run - show all entries\n\t\tif (entries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn entries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t} else {\n\t\t// Check for new entries since last version\n\t\tconst newEntries = getNewEntries(entries, lastVersion);\n\t\tif (newEntries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn newEntries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t}\n\n\treturn null;\n}\n"]}
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Main entry point for the coding agent\n */\n\nimport { Agent, type Attachment, ProviderTransport, type ThinkingLevel } from \"@mariozechner/pi-agent-core\";\nimport chalk from \"chalk\";\nimport { type Args, parseArgs, printHelp } from \"./cli/args.js\";\nimport { processFileArguments } from \"./cli/file-processor.js\";\nimport { listModels } from \"./cli/list-models.js\";\nimport { selectSession } from \"./cli/session-picker.js\";\nimport { getModelsPath, VERSION } from \"./config.js\";\nimport { AgentSession } from \"./core/agent-session.js\";\nimport { discoverAndLoadCustomTools, type LoadedCustomTool } from \"./core/custom-tools/index.js\";\nimport { exportFromFile } from \"./core/export-html.js\";\nimport { discoverAndLoadHooks, HookRunner, wrapToolsWithHooks } from \"./core/hooks/index.js\";\nimport { messageTransformer } from \"./core/messages.js\";\nimport { findModel, getApiKeyForModel, getAvailableModels } from \"./core/model-config.js\";\nimport { resolveModelScope, restoreModelFromSession, type ScopedModel } from \"./core/model-resolver.js\";\nimport { SessionManager } from \"./core/session-manager.js\";\nimport { SettingsManager } from \"./core/settings-manager.js\";\nimport { loadSlashCommands } from \"./core/slash-commands.js\";\nimport { buildSystemPrompt } from \"./core/system-prompt.js\";\nimport { allTools, codingTools } from \"./core/tools/index.js\";\nimport { InteractiveMode, runPrintMode, runRpcMode } from \"./modes/index.js\";\nimport { initTheme, stopThemeWatcher } from \"./modes/interactive/theme/theme.js\";\nimport { getChangelogPath, getNewEntries, parseChangelog } from \"./utils/changelog.js\";\nimport { ensureTool } from \"./utils/tools-manager.js\";\n\n/** Check npm registry for new version (non-blocking) */\nasync function checkForNewVersion(currentVersion: string): Promise<string | null> {\n\ttry {\n\t\tconst response = await fetch(\"https://registry.npmjs.org/@mariozechner/pi-coding-agent/latest\");\n\t\tif (!response.ok) return null;\n\n\t\tconst data = (await response.json()) as { version?: string };\n\t\tconst latestVersion = data.version;\n\n\t\tif (latestVersion && latestVersion !== currentVersion) {\n\t\t\treturn latestVersion;\n\t\t}\n\n\t\treturn null;\n\t} catch {\n\t\t// Silently fail - don't disrupt the user experience\n\t\treturn null;\n\t}\n}\n\n/** Run interactive mode with TUI */\nasync function runInteractiveMode(\n\tsession: AgentSession,\n\tversion: string,\n\tchangelogMarkdown: string | null,\n\tmodelFallbackMessage: string | null,\n\tversionCheckPromise: Promise<string | null>,\n\tinitialMessages: string[],\n\tcustomTools: LoadedCustomTool[],\n\tsetToolUIContext: (uiContext: import(\"./core/hooks/types.js\").HookUIContext, hasUI: boolean) => void,\n\tinitialMessage?: string,\n\tinitialAttachments?: Attachment[],\n\tfdPath: string | null = null,\n): Promise<void> {\n\tconst mode = new InteractiveMode(session, version, changelogMarkdown, customTools, setToolUIContext, fdPath);\n\n\t// Initialize TUI (subscribes to agent events internally)\n\tawait mode.init();\n\n\t// Handle version check result when it completes (don't block)\n\tversionCheckPromise.then((newVersion) => {\n\t\tif (newVersion) {\n\t\t\tmode.showNewVersionNotification(newVersion);\n\t\t}\n\t});\n\n\t// Render any existing messages (from --continue mode)\n\tmode.renderInitialMessages(session.state);\n\n\t// Show model fallback warning at the end of the chat if applicable\n\tif (modelFallbackMessage) {\n\t\tmode.showWarning(modelFallbackMessage);\n\t}\n\n\t// Process initial message with attachments if provided (from @file args)\n\tif (initialMessage) {\n\t\ttry {\n\t\t\tawait session.prompt(initialMessage, { attachments: initialAttachments });\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Process remaining initial messages if provided (from CLI args)\n\tfor (const message of initialMessages) {\n\t\ttry {\n\t\t\tawait session.prompt(message);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n\n\t// Interactive loop\n\twhile (true) {\n\t\tconst userInput = await mode.getUserInput();\n\n\t\t// Process the message\n\t\ttry {\n\t\t\tawait session.prompt(userInput);\n\t\t} catch (error: unknown) {\n\t\t\tconst errorMessage = error instanceof Error ? error.message : \"Unknown error occurred\";\n\t\t\tmode.showError(errorMessage);\n\t\t}\n\t}\n}\n\n/** Prepare initial message from @file arguments */\nfunction prepareInitialMessage(parsed: Args): {\n\tinitialMessage?: string;\n\tinitialAttachments?: Attachment[];\n} {\n\tif (parsed.fileArgs.length === 0) {\n\t\treturn {};\n\t}\n\n\tconst { textContent, imageAttachments } = processFileArguments(parsed.fileArgs);\n\n\t// Combine file content with first plain text message (if any)\n\tlet initialMessage: string;\n\tif (parsed.messages.length > 0) {\n\t\tinitialMessage = textContent + parsed.messages[0];\n\t\tparsed.messages.shift(); // Remove first message as it's been combined\n\t} else {\n\t\tinitialMessage = textContent;\n\t}\n\n\treturn {\n\t\tinitialMessage,\n\t\tinitialAttachments: imageAttachments.length > 0 ? imageAttachments : undefined,\n\t};\n}\n\nexport async function main(args: string[]) {\n\tconst parsed = parseArgs(args);\n\n\tif (parsed.version) {\n\t\tconsole.log(VERSION);\n\t\treturn;\n\t}\n\n\tif (parsed.help) {\n\t\tprintHelp();\n\t\treturn;\n\t}\n\n\t// Handle --list-models flag: list available models and exit\n\tif (parsed.listModels !== undefined) {\n\t\tconst searchPattern = typeof parsed.listModels === \"string\" ? parsed.listModels : undefined;\n\t\tawait listModels(searchPattern);\n\t\treturn;\n\t}\n\n\t// Handle --export flag: convert session file to HTML and exit\n\tif (parsed.export) {\n\t\ttry {\n\t\t\tconst outputPath = parsed.messages.length > 0 ? parsed.messages[0] : undefined;\n\t\t\tconst result = exportFromFile(parsed.export, outputPath);\n\t\t\tconsole.log(`Exported to: ${result}`);\n\t\t\treturn;\n\t\t} catch (error: unknown) {\n\t\t\tconst message = error instanceof Error ? error.message : \"Failed to export session\";\n\t\t\tconsole.error(chalk.red(`Error: ${message}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Validate: RPC mode doesn't support @file arguments\n\tif (parsed.mode === \"rpc\" && parsed.fileArgs.length > 0) {\n\t\tconsole.error(chalk.red(\"Error: @file arguments are not supported in RPC mode\"));\n\t\tprocess.exit(1);\n\t}\n\n\t// Process @file arguments\n\tconst { initialMessage, initialAttachments } = prepareInitialMessage(parsed);\n\n\t// Determine if we're in interactive mode (needed for theme watcher)\n\tconst isInteractive = !parsed.print && parsed.mode === undefined;\n\n\t// Initialize theme (before any TUI rendering)\n\tconst settingsManager = new SettingsManager();\n\tconst themeName = settingsManager.getTheme();\n\tinitTheme(themeName, isInteractive);\n\n\t// Setup session manager\n\tconst sessionManager = new SessionManager(parsed.continue && !parsed.resume, parsed.session);\n\n\tif (parsed.noSession) {\n\t\tsessionManager.disable();\n\t}\n\n\t// Handle --resume flag: show session selector\n\tif (parsed.resume) {\n\t\tconst selectedSession = await selectSession(sessionManager);\n\t\tif (!selectedSession) {\n\t\t\tconsole.log(chalk.dim(\"No session selected\"));\n\t\t\treturn;\n\t\t}\n\t\tsessionManager.setSessionFile(selectedSession);\n\t}\n\n\t// Resolve model scope early if provided\n\tlet scopedModels: ScopedModel[] = [];\n\tif (parsed.models && parsed.models.length > 0) {\n\t\tscopedModels = await resolveModelScope(parsed.models);\n\t}\n\n\t// Determine mode and output behavior\n\tconst mode = parsed.mode || \"text\";\n\tconst shouldPrintMessages = isInteractive;\n\n\t// Find initial model\n\tlet initialModel = await findInitialModelForSession(parsed, scopedModels, settingsManager);\n\tlet initialThinking: ThinkingLevel = \"off\";\n\n\t// Get thinking level from scoped models if applicable\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\tinitialThinking = scopedModels[0].thinkingLevel;\n\t} else {\n\t\t// Try saved thinking level\n\t\tconst savedThinking = settingsManager.getDefaultThinkingLevel();\n\t\tif (savedThinking) {\n\t\t\tinitialThinking = savedThinking;\n\t\t}\n\t}\n\n\t// Non-interactive mode: fail early if no model available\n\tif (!isInteractive && !initialModel) {\n\t\tconsole.error(chalk.red(\"No models available.\"));\n\t\tconsole.error(chalk.yellow(\"\\nSet an API key environment variable:\"));\n\t\tconsole.error(\" ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY, etc.\");\n\t\tconsole.error(chalk.yellow(`\\nOr create ${getModelsPath()}`));\n\t\tprocess.exit(1);\n\t}\n\n\t// Non-interactive mode: validate API key exists\n\tif (!isInteractive && initialModel) {\n\t\tconst apiKey = parsed.apiKey || (await getApiKeyForModel(initialModel));\n\t\tif (!apiKey) {\n\t\t\tconsole.error(chalk.red(`No API key found for ${initialModel.provider}`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t}\n\n\t// Build system prompt\n\tconst skillsEnabled = !parsed.noSkills && settingsManager.getSkillsEnabled();\n\tconst systemPrompt = buildSystemPrompt({\n\t\tcustomPrompt: parsed.systemPrompt,\n\t\tselectedTools: parsed.tools,\n\t\tappendSystemPrompt: parsed.appendSystemPrompt,\n\t\tskillsEnabled,\n\t});\n\n\t// Handle session restoration\n\tlet modelFallbackMessage: string | null = null;\n\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst savedModel = sessionManager.loadModel();\n\t\tif (savedModel) {\n\t\t\tconst result = await restoreModelFromSession(\n\t\t\t\tsavedModel.provider,\n\t\t\t\tsavedModel.modelId,\n\t\t\t\tinitialModel,\n\t\t\t\tshouldPrintMessages,\n\t\t\t);\n\n\t\t\tif (result.model) {\n\t\t\t\tinitialModel = result.model;\n\t\t\t}\n\t\t\tmodelFallbackMessage = result.fallbackMessage;\n\t\t}\n\n\t\t// Load and restore thinking level\n\t\tconst thinkingLevel = sessionManager.loadThinkingLevel() as ThinkingLevel;\n\t\tif (thinkingLevel) {\n\t\t\tinitialThinking = thinkingLevel;\n\t\t\tif (shouldPrintMessages) {\n\t\t\t\tconsole.log(chalk.dim(`Restored thinking level: ${thinkingLevel}`));\n\t\t\t}\n\t\t}\n\t}\n\n\t// CLI --thinking flag takes highest priority\n\tif (parsed.thinking) {\n\t\tinitialThinking = parsed.thinking;\n\t}\n\n\t// Determine which tools to use\n\tlet selectedTools = parsed.tools ? parsed.tools.map((name) => allTools[name]) : codingTools;\n\n\t// Discover and load hooks from:\n\t// 1. ~/.pi/agent/hooks/*.ts (global)\n\t// 2. cwd/.pi/hooks/*.ts (project-local)\n\t// 3. Explicit paths in settings.json\n\t// 4. CLI --hook flags\n\tlet hookRunner: HookRunner | null = null;\n\tconst cwd = process.cwd();\n\tconst configuredHookPaths = [...settingsManager.getHookPaths(), ...(parsed.hooks ?? [])];\n\tconst { hooks, errors } = await discoverAndLoadHooks(configuredHookPaths, cwd);\n\n\t// Report hook loading errors\n\tfor (const { path, error } of errors) {\n\t\tconsole.error(chalk.red(`Failed to load hook \"${path}\": ${error}`));\n\t}\n\n\tif (hooks.length > 0) {\n\t\tconst timeout = settingsManager.getHookTimeout();\n\t\thookRunner = new HookRunner(hooks, cwd, timeout);\n\n\t\t// Wrap tools with hook callbacks\n\t\tselectedTools = wrapToolsWithHooks(selectedTools, hookRunner);\n\t}\n\n\t// Discover and load custom tools from:\n\t// 1. ~/.pi/agent/tools/*.ts (global)\n\t// 2. cwd/.pi/tools/*.ts (project-local)\n\t// 3. Explicit paths in settings.json\n\t// 4. CLI --tool flags\n\tconst configuredToolPaths = [...settingsManager.getCustomToolPaths(), ...(parsed.customTools ?? [])];\n\tconst builtInToolNames = Object.keys(allTools);\n\tconst {\n\t\ttools: loadedCustomTools,\n\t\terrors: toolErrors,\n\t\tsetUIContext: setToolUIContext,\n\t} = await discoverAndLoadCustomTools(configuredToolPaths, cwd, builtInToolNames);\n\n\t// Report custom tool loading errors\n\tfor (const { path, error } of toolErrors) {\n\t\tconsole.error(chalk.red(`Failed to load custom tool \"${path}\": ${error}`));\n\t}\n\n\t// Add custom tools to selected tools\n\tif (loadedCustomTools.length > 0) {\n\t\tconst customToolInstances = loadedCustomTools.map((lt) => lt.tool);\n\t\tselectedTools = [...selectedTools, ...customToolInstances] as typeof selectedTools;\n\t}\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: initialModel as any, // Can be null in interactive mode\n\t\t\tthinkingLevel: initialThinking,\n\t\t\ttools: selectedTools,\n\t\t},\n\t\tmessageTransformer,\n\t\tqueueMode: settingsManager.getQueueMode(),\n\t\ttransport: new ProviderTransport({\n\t\t\tgetApiKey: async () => {\n\t\t\t\tconst currentModel = agent.state.model;\n\t\t\t\tif (!currentModel) {\n\t\t\t\t\tthrow new Error(\"No model selected\");\n\t\t\t\t}\n\n\t\t\t\tif (parsed.apiKey) {\n\t\t\t\t\treturn parsed.apiKey;\n\t\t\t\t}\n\n\t\t\t\tconst key = await getApiKeyForModel(currentModel);\n\t\t\t\tif (!key) {\n\t\t\t\t\tthrow new Error(\n\t\t\t\t\t\t`No API key found for provider \"${currentModel.provider}\". Please set the appropriate environment variable or update ${getModelsPath()}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\treturn key;\n\t\t\t},\n\t\t}),\n\t});\n\n\t// If initial thinking was requested but model doesn't support it, reset to off\n\tif (initialThinking !== \"off\" && initialModel && !initialModel.reasoning) {\n\t\tagent.setThinkingLevel(\"off\");\n\t}\n\n\t// Load previous messages if continuing, resuming, or using --session\n\tif (parsed.continue || parsed.resume || parsed.session) {\n\t\tconst messages = sessionManager.loadMessages();\n\t\tif (messages.length > 0) {\n\t\t\tagent.replaceMessages(messages);\n\t\t}\n\t}\n\n\t// Load file commands for slash command expansion\n\tconst fileCommands = loadSlashCommands();\n\n\t// Create session\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager,\n\t\tscopedModels,\n\t\tfileCommands,\n\t\thookRunner,\n\t\tcustomTools: loadedCustomTools,\n\t});\n\n\t// Route to appropriate mode\n\tif (mode === \"rpc\") {\n\t\tawait runRpcMode(session);\n\t} else if (isInteractive) {\n\t\t// Check for new version in the background\n\t\tconst versionCheckPromise = checkForNewVersion(VERSION).catch(() => null);\n\n\t\t// Check if we should show changelog\n\t\tconst changelogMarkdown = getChangelogForDisplay(parsed, settingsManager);\n\n\t\t// Show model scope if provided\n\t\tif (scopedModels.length > 0) {\n\t\t\tconst modelList = scopedModels\n\t\t\t\t.map((sm) => {\n\t\t\t\t\tconst thinkingStr = sm.thinkingLevel !== \"off\" ? `:${sm.thinkingLevel}` : \"\";\n\t\t\t\t\treturn `${sm.model.id}${thinkingStr}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \");\n\t\t\tconsole.log(chalk.dim(`Model scope: ${modelList} ${chalk.gray(\"(Ctrl+P to cycle)\")}`));\n\t\t}\n\n\t\t// Ensure fd tool is available for file autocomplete\n\t\tconst fdPath = await ensureTool(\"fd\");\n\n\t\tawait runInteractiveMode(\n\t\t\tsession,\n\t\t\tVERSION,\n\t\t\tchangelogMarkdown,\n\t\t\tmodelFallbackMessage,\n\t\t\tversionCheckPromise,\n\t\t\tparsed.messages,\n\t\t\tloadedCustomTools,\n\t\t\tsetToolUIContext,\n\t\t\tinitialMessage,\n\t\t\tinitialAttachments,\n\t\t\tfdPath,\n\t\t);\n\t} else {\n\t\t// Non-interactive mode (--print flag or --mode flag)\n\t\tawait runPrintMode(session, mode, parsed.messages, initialMessage, initialAttachments);\n\t\t// Clean up and exit (file watchers keep process alive)\n\t\tstopThemeWatcher();\n\t\tprocess.exit(0);\n\t}\n}\n\n/** Find initial model based on CLI args, scoped models, settings, or available models */\nasync function findInitialModelForSession(parsed: Args, scopedModels: ScopedModel[], settingsManager: SettingsManager) {\n\t// 1. CLI args take priority\n\tif (parsed.provider && parsed.model) {\n\t\tconst { model, error } = findModel(parsed.provider, parsed.model);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (!model) {\n\t\t\tconsole.error(chalk.red(`Model ${parsed.provider}/${parsed.model} not found`));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\treturn model;\n\t}\n\n\t// 2. Use first model from scoped models (skip if continuing/resuming)\n\tif (scopedModels.length > 0 && !parsed.continue && !parsed.resume) {\n\t\treturn scopedModels[0].model;\n\t}\n\n\t// 3. Try saved default from settings\n\tconst defaultProvider = settingsManager.getDefaultProvider();\n\tconst defaultModelId = settingsManager.getDefaultModel();\n\tif (defaultProvider && defaultModelId) {\n\t\tconst { model, error } = findModel(defaultProvider, defaultModelId);\n\t\tif (error) {\n\t\t\tconsole.error(chalk.red(error));\n\t\t\tprocess.exit(1);\n\t\t}\n\t\tif (model) {\n\t\t\treturn model;\n\t\t}\n\t}\n\n\t// 4. Try first available model with valid API key\n\tconst { models: availableModels, error } = await getAvailableModels();\n\n\tif (error) {\n\t\tconsole.error(chalk.red(error));\n\t\tprocess.exit(1);\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn null;\n}\n\n/** Get changelog markdown to display (only for new sessions with updates) */\nfunction getChangelogForDisplay(parsed: Args, settingsManager: SettingsManager): string | null {\n\tif (parsed.continue || parsed.resume) {\n\t\treturn null;\n\t}\n\n\tconst lastVersion = settingsManager.getLastChangelogVersion();\n\tconst changelogPath = getChangelogPath();\n\tconst entries = parseChangelog(changelogPath);\n\n\tif (!lastVersion) {\n\t\t// First run - show all entries\n\t\tif (entries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn entries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t} else {\n\t\t// Check for new entries since last version\n\t\tconst newEntries = getNewEntries(entries, lastVersion);\n\t\tif (newEntries.length > 0) {\n\t\t\tsettingsManager.setLastChangelogVersion(VERSION);\n\t\t\treturn newEntries.map((e) => e.content).join(\"\\n\\n\");\n\t\t}\n\t}\n\n\treturn null;\n}\n"]}
@@ -1,4 +1,5 @@
1
1
  import { Container } from "@mariozechner/pi-tui";
2
+ import type { CustomAgentTool } from "../../../core/custom-tools/types.js";
2
3
  export interface ToolExecutionOptions {
3
4
  showImages?: boolean;
4
5
  }
@@ -6,6 +7,7 @@ export interface ToolExecutionOptions {
6
7
  * Component that renders a tool call with its result (updateable)
7
8
  */
8
9
  export declare class ToolExecutionComponent extends Container {
10
+ private contentBox;
9
11
  private contentText;
10
12
  private imageComponents;
11
13
  private toolName;
@@ -13,8 +15,9 @@ export declare class ToolExecutionComponent extends Container {
13
15
  private expanded;
14
16
  private showImages;
15
17
  private isPartial;
18
+ private customTool?;
16
19
  private result?;
17
- constructor(toolName: string, args: any, options?: ToolExecutionOptions);
20
+ constructor(toolName: string, args: any, options?: ToolExecutionOptions, customTool?: CustomAgentTool);
18
21
  updateArgs(args: any): void;
19
22
  updateResult(result: {
20
23
  content: Array<{
@@ -1 +1 @@
1
- {"version":3,"file":"tool-execution.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-execution.ts"],"names":[],"mappings":"AACA,OAAO,EACN,SAAS,EAOT,MAAM,sBAAsB,CAAC;AAuB9B,MAAM,WAAW,oBAAoB;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,MAAM,CAAC,CAIb;IAEF,YAAY,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE,oBAAyB,EAS1E;IAED,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAG1B;IAED,YAAY,CACX,MAAM,EAAE;QACP,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClF,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;KACjB,EACD,SAAS,UAAQ,GACf,IAAI,CAIN;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAED,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAGjC;IAED,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,aAAa;IA8BrB,OAAO,CAAC,mBAAmB;CAwS3B","sourcesContent":["import * as os from \"node:os\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\tgetImageDimensions,\n\tImage,\n\timageFallback,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from \"../../../core/tools/truncate.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport interface ToolExecutionOptions {\n\tshowImages?: boolean; // default: true (only used if terminal supports images)\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentText: Text;\n\tprivate imageComponents: Image[] = [];\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate expanded = false;\n\tprivate showImages: boolean;\n\tprivate isPartial = true;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any, options: ToolExecutionOptions = {}) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.showImages = options.showImages ?? true;\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.contentText = new Text(\"\", 1, 1, (text: string) => theme.bg(\"toolPendingBg\", text));\n\t\tthis.addChild(this.contentText);\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(\n\t\tresult: {\n\t\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\t\tdetails?: any;\n\t\t\tisError: boolean;\n\t\t},\n\t\tisPartial = false,\n\t): void {\n\t\tthis.result = result;\n\t\tthis.isPartial = isPartial;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetShowImages(show: boolean): void {\n\t\tthis.showImages = show;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\tconst bgFn = this.isPartial\n\t\t\t? (text: string) => theme.bg(\"toolPendingBg\", text)\n\t\t\t: this.result?.isError\n\t\t\t\t? (text: string) => theme.bg(\"toolErrorBg\", text)\n\t\t\t\t: (text: string) => theme.bg(\"toolSuccessBg\", text);\n\n\t\tthis.contentText.setCustomBgFn(bgFn);\n\t\tthis.contentText.setText(this.formatToolExecution());\n\n\t\tfor (const img of this.imageComponents) {\n\t\t\tthis.removeChild(img);\n\t\t}\n\t\tthis.imageComponents = [];\n\n\t\tif (this.result) {\n\t\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\t\t\tconst caps = getCapabilities();\n\n\t\t\tfor (const img of imageBlocks) {\n\t\t\t\t// Show inline image only if terminal supports it AND user setting allows it\n\t\t\t\tif (caps.images && this.showImages && img.data && img.mimeType) {\n\t\t\t\t\tthis.addChild(new Spacer(1));\n\t\t\t\t\tconst imageComponent = new Image(\n\t\t\t\t\t\timg.data,\n\t\t\t\t\t\timg.mimeType,\n\t\t\t\t\t\t{ fallbackColor: (s: string) => theme.fg(\"toolOutput\", s) },\n\t\t\t\t\t\t{ maxWidthCells: 60 },\n\t\t\t\t\t);\n\t\t\t\t\tthis.imageComponents.push(imageComponent);\n\t\t\t\t\tthis.addChild(imageComponent);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\tlet output = textBlocks\n\t\t\t.map((c: any) => {\n\t\t\t\tlet text = stripAnsi(c.text || \"\").replace(/\\r/g, \"\");\n\t\t\t\ttext = text.replace(/\\x1b./g, \"\");\n\t\t\t\ttext = text.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\x9f]/g, \"\");\n\t\t\t\treturn text;\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\n\t\tconst caps = getCapabilities();\n\t\t// Show text fallback if terminal doesn't support images OR if user disabled inline images\n\t\tif (imageBlocks.length > 0 && (!caps.images || !this.showImages)) {\n\t\t\tconst imageIndicators = imageBlocks\n\t\t\t\t.map((img: any) => {\n\t\t\t\t\tconst dims = img.data ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\t\treturn imageFallback(img.mimeType, dims);\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\t// Format based on tool type\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(`$ ${command || theme.fg(\"toolOutput\", \"...\")}`));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 5;\n\t\t\t\t\tconst skipped = Math.max(0, lines.length - maxLines);\n\t\t\t\t\tconst displayLines = lines.slice(-maxLines);\n\n\t\t\t\t\tif (skipped > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n\\n... (${skipped} earlier lines)`);\n\t\t\t\t\t}\n\t\t\t\t\ttext +=\n\t\t\t\t\t\t(skipped > 0 ? \"\\n\" : \"\\n\\n\") +\n\t\t\t\t\t\tdisplayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t}\n\n\t\t\t\t// Show truncation warning at the bottom (outside collapsed area)\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tconst fullOutputPath = this.result.details?.fullOutputPath;\n\t\t\t\tif (truncation?.truncated || fullOutputPath) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (fullOutputPath) {\n\t\t\t\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[${warnings.join(\". \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst offset = this.args?.offset;\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\t// Build path display with offset/limit suffix (in warning color if offset/limit used)\n\t\t\tlet pathDisplay = path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\t\t\tif (offset !== undefined || limit !== undefined) {\n\t\t\t\tconst startLine = offset ?? 1;\n\t\t\t\tconst endLine = limit !== undefined ? startLine + limit - 1 : \"\";\n\t\t\t\tpathDisplay += theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n\t\t\t}\n\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(\"read\")) + \" \" + pathDisplay;\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\n\t\t\t\t// Show truncation warning at the bottom (outside collapsed area)\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\ttext +=\n\t\t\t\t\t\t\t\"\\n\" +\n\t\t\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t`[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext +=\n\t\t\t\t\t\t\t\"\\n\" +\n\t\t\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t`[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext +=\n\t\t\t\t\t\t\t\"\\n\" +\n\t\t\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t`[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"write\")) +\n\t\t\t\t\" \" +\n\t\t\t\t(path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\t// Show first 10 lines of content if available\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"edit\")) +\n\t\t\t\t\" \" +\n\t\t\t\t(path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\t// Show error message if it's an error\n\t\t\t\tif (this.result.isError) {\n\t\t\t\t\tconst errorText = this.getTextOutput();\n\t\t\t\t\tif (errorText) {\n\t\t\t\t\t\ttext += \"\\n\\n\" + theme.fg(\"error\", errorText);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.result.details?.diff) {\n\t\t\t\t\t// Show diff if available\n\t\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\t\treturn theme.fg(\"toolDiffAdded\", line);\n\t\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\t\treturn theme.fg(\"toolDiffRemoved\", line);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn theme.fg(\"toolDiffContext\", line);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"ls\") {\n\t\t\tconst path = shortenPath(this.args?.path || \".\");\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(\"ls\")) + \" \" + theme.fg(\"accent\", path);\n\t\t\tif (limit !== undefined) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t\t\t}\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 20;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Show truncation warning at the bottom (outside collapsed area)\n\t\t\t\tconst entryLimit = this.result.details?.entryLimitReached;\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tif (entryLimit || truncation?.truncated) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (entryLimit) {\n\t\t\t\t\t\twarnings.push(`${entryLimit} entries limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\twarnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"find\") {\n\t\t\tconst pattern = this.args?.pattern || \"\";\n\t\t\tconst path = shortenPath(this.args?.path || \".\");\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"find\")) +\n\t\t\t\t\" \" +\n\t\t\t\ttheme.fg(\"accent\", pattern) +\n\t\t\t\ttheme.fg(\"toolOutput\", ` in ${path}`);\n\t\t\tif (limit !== undefined) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t\t\t}\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 20;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Show truncation warning at the bottom (outside collapsed area)\n\t\t\t\tconst resultLimit = this.result.details?.resultLimitReached;\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tif (resultLimit || truncation?.truncated) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (resultLimit) {\n\t\t\t\t\t\twarnings.push(`${resultLimit} results limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\twarnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"grep\") {\n\t\t\tconst pattern = this.args?.pattern || \"\";\n\t\t\tconst path = shortenPath(this.args?.path || \".\");\n\t\t\tconst glob = this.args?.glob;\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\t\t\" \" +\n\t\t\t\ttheme.fg(\"accent\", `/${pattern}/`) +\n\t\t\t\ttheme.fg(\"toolOutput\", ` in ${path}`);\n\t\t\tif (glob) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` (${glob})`);\n\t\t\t}\n\t\t\tif (limit !== undefined) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\t\t\t}\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 15;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Show truncation warning at the bottom (outside collapsed area)\n\t\t\t\tconst matchLimit = this.result.details?.matchLimitReached;\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tconst linesTruncated = this.result.details?.linesTruncated;\n\t\t\t\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (matchLimit) {\n\t\t\t\t\t\twarnings.push(`${matchLimit} matches limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\twarnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\twarnings.push(\"some lines truncated\");\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(this.toolName));\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}
1
+ {"version":3,"file":"tool-execution.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/tool-execution.ts"],"names":[],"mappings":"AACA,OAAO,EAEN,SAAS,EAOT,MAAM,sBAAsB,CAAC;AAE9B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAsB3E,MAAM,WAAW,oBAAoB;IACpC,UAAU,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,SAAS;IACpD,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,WAAW,CAAO;IAC1B,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,IAAI,CAAM;IAClB,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,UAAU,CAAC,CAAkB;IACrC,OAAO,CAAC,MAAM,CAAC,CAIb;IAEF,YAAY,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,GAAE,oBAAyB,EAAE,UAAU,CAAC,EAAE,eAAe,EAiBxG;IAED,UAAU,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI,CAG1B;IAED,YAAY,CACX,MAAM,EAAE;QACP,OAAO,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAC;QAClF,OAAO,CAAC,EAAE,GAAG,CAAC;QACd,OAAO,EAAE,OAAO,CAAC;KACjB,EACD,SAAS,UAAQ,GACf,IAAI,CAIN;IAED,WAAW,CAAC,QAAQ,EAAE,OAAO,GAAG,IAAI,CAGnC;IAED,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAGjC;IAED,OAAO,CAAC,aAAa;IAsFrB,OAAO,CAAC,aAAa;IA6BrB,OAAO,CAAC,mBAAmB;CA8R3B","sourcesContent":["import * as os from \"node:os\";\nimport {\n\tBox,\n\tContainer,\n\tgetCapabilities,\n\tgetImageDimensions,\n\tImage,\n\timageFallback,\n\tSpacer,\n\tText,\n} from \"@mariozechner/pi-tui\";\nimport stripAnsi from \"strip-ansi\";\nimport type { CustomAgentTool } from \"../../../core/custom-tools/types.js\";\nimport { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize } from \"../../../core/tools/truncate.js\";\nimport { theme } from \"../theme/theme.js\";\n\n/**\n * Convert absolute path to tilde notation if it's in home directory\n */\nfunction shortenPath(path: string): string {\n\tconst home = os.homedir();\n\tif (path.startsWith(home)) {\n\t\treturn \"~\" + path.slice(home.length);\n\t}\n\treturn path;\n}\n\n/**\n * Replace tabs with spaces for consistent rendering\n */\nfunction replaceTabs(text: string): string {\n\treturn text.replace(/\\t/g, \" \");\n}\n\nexport interface ToolExecutionOptions {\n\tshowImages?: boolean; // default: true (only used if terminal supports images)\n}\n\n/**\n * Component that renders a tool call with its result (updateable)\n */\nexport class ToolExecutionComponent extends Container {\n\tprivate contentBox: Box;\n\tprivate contentText: Text; // For built-in tools\n\tprivate imageComponents: Image[] = [];\n\tprivate toolName: string;\n\tprivate args: any;\n\tprivate expanded = false;\n\tprivate showImages: boolean;\n\tprivate isPartial = true;\n\tprivate customTool?: CustomAgentTool;\n\tprivate result?: {\n\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\tisError: boolean;\n\t\tdetails?: any;\n\t};\n\n\tconstructor(toolName: string, args: any, options: ToolExecutionOptions = {}, customTool?: CustomAgentTool) {\n\t\tsuper();\n\t\tthis.toolName = toolName;\n\t\tthis.args = args;\n\t\tthis.showImages = options.showImages ?? true;\n\t\tthis.customTool = customTool;\n\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Box wraps content with padding and background\n\t\tthis.contentBox = new Box(1, 1, (text: string) => theme.bg(\"toolPendingBg\", text));\n\t\tthis.addChild(this.contentBox);\n\n\t\t// Text component for built-in tool rendering\n\t\tthis.contentText = new Text(\"\", 0, 0);\n\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateArgs(args: any): void {\n\t\tthis.args = args;\n\t\tthis.updateDisplay();\n\t}\n\n\tupdateResult(\n\t\tresult: {\n\t\t\tcontent: Array<{ type: string; text?: string; data?: string; mimeType?: string }>;\n\t\t\tdetails?: any;\n\t\t\tisError: boolean;\n\t\t},\n\t\tisPartial = false,\n\t): void {\n\t\tthis.result = result;\n\t\tthis.isPartial = isPartial;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetExpanded(expanded: boolean): void {\n\t\tthis.expanded = expanded;\n\t\tthis.updateDisplay();\n\t}\n\n\tsetShowImages(show: boolean): void {\n\t\tthis.showImages = show;\n\t\tthis.updateDisplay();\n\t}\n\n\tprivate updateDisplay(): void {\n\t\t// Set background based on state\n\t\tconst bgFn = this.isPartial\n\t\t\t? (text: string) => theme.bg(\"toolPendingBg\", text)\n\t\t\t: this.result?.isError\n\t\t\t\t? (text: string) => theme.bg(\"toolErrorBg\", text)\n\t\t\t\t: (text: string) => theme.bg(\"toolSuccessBg\", text);\n\n\t\tthis.contentBox.setBgFn(bgFn);\n\t\tthis.contentBox.clear();\n\n\t\t// Check for custom tool rendering\n\t\tif (this.customTool) {\n\t\t\t// Render call component\n\t\t\tif (this.customTool.renderCall) {\n\t\t\t\ttry {\n\t\t\t\t\tconst callComponent = this.customTool.renderCall(this.args, theme);\n\t\t\t\t\tif (callComponent) {\n\t\t\t\t\t\tthis.contentBox.addChild(callComponent);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Fall back to default on error\n\t\t\t\t\tthis.contentBox.addChild(new Text(theme.fg(\"toolTitle\", theme.bold(this.toolName)), 0, 0));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// No custom renderCall, show tool name\n\t\t\t\tthis.contentBox.addChild(new Text(theme.fg(\"toolTitle\", theme.bold(this.toolName)), 0, 0));\n\t\t\t}\n\n\t\t\t// Render result component if we have a result\n\t\t\tif (this.result && this.customTool.renderResult) {\n\t\t\t\ttry {\n\t\t\t\t\tconst resultComponent = this.customTool.renderResult(\n\t\t\t\t\t\t{ content: this.result.content as any, details: this.result.details },\n\t\t\t\t\t\t{ expanded: this.expanded, isPartial: this.isPartial },\n\t\t\t\t\t\ttheme,\n\t\t\t\t\t);\n\t\t\t\t\tif (resultComponent) {\n\t\t\t\t\t\tthis.contentBox.addChild(resultComponent);\n\t\t\t\t\t}\n\t\t\t\t} catch {\n\t\t\t\t\t// Fall back to showing raw output on error\n\t\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\t\tif (output) {\n\t\t\t\t\t\tthis.contentBox.addChild(new Text(theme.fg(\"toolOutput\", output), 0, 0));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (this.result) {\n\t\t\t\t// Has result but no custom renderResult\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tif (output) {\n\t\t\t\t\tthis.contentBox.addChild(new Text(theme.fg(\"toolOutput\", output), 0, 0));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Built-in tool: use existing formatToolExecution\n\t\t\tthis.contentText.setText(this.formatToolExecution());\n\t\t\tthis.contentBox.addChild(this.contentText);\n\t\t}\n\n\t\t// Handle images (same for both custom and built-in)\n\t\tfor (const img of this.imageComponents) {\n\t\t\tthis.removeChild(img);\n\t\t}\n\t\tthis.imageComponents = [];\n\n\t\tif (this.result) {\n\t\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\t\t\tconst caps = getCapabilities();\n\n\t\t\tfor (const img of imageBlocks) {\n\t\t\t\tif (caps.images && this.showImages && img.data && img.mimeType) {\n\t\t\t\t\tthis.addChild(new Spacer(1));\n\t\t\t\t\tconst imageComponent = new Image(\n\t\t\t\t\t\timg.data,\n\t\t\t\t\t\timg.mimeType,\n\t\t\t\t\t\t{ fallbackColor: (s: string) => theme.fg(\"toolOutput\", s) },\n\t\t\t\t\t\t{ maxWidthCells: 60 },\n\t\t\t\t\t);\n\t\t\t\t\tthis.imageComponents.push(imageComponent);\n\t\t\t\t\tthis.addChild(imageComponent);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate getTextOutput(): string {\n\t\tif (!this.result) return \"\";\n\n\t\tconst textBlocks = this.result.content?.filter((c: any) => c.type === \"text\") || [];\n\t\tconst imageBlocks = this.result.content?.filter((c: any) => c.type === \"image\") || [];\n\n\t\tlet output = textBlocks\n\t\t\t.map((c: any) => {\n\t\t\t\tlet text = stripAnsi(c.text || \"\").replace(/\\r/g, \"\");\n\t\t\t\ttext = text.replace(/\\x1b./g, \"\");\n\t\t\t\ttext = text.replace(/[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f-\\x9f]/g, \"\");\n\t\t\t\treturn text;\n\t\t\t})\n\t\t\t.join(\"\\n\");\n\n\t\tconst caps = getCapabilities();\n\t\tif (imageBlocks.length > 0 && (!caps.images || !this.showImages)) {\n\t\t\tconst imageIndicators = imageBlocks\n\t\t\t\t.map((img: any) => {\n\t\t\t\t\tconst dims = img.data ? (getImageDimensions(img.data, img.mimeType) ?? undefined) : undefined;\n\t\t\t\t\treturn imageFallback(img.mimeType, dims);\n\t\t\t\t})\n\t\t\t\t.join(\"\\n\");\n\t\t\toutput = output ? `${output}\\n${imageIndicators}` : imageIndicators;\n\t\t}\n\n\t\treturn output;\n\t}\n\n\tprivate formatToolExecution(): string {\n\t\tlet text = \"\";\n\n\t\tif (this.toolName === \"bash\") {\n\t\t\tconst command = this.args?.command || \"\";\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(`$ ${command || theme.fg(\"toolOutput\", \"...\")}`));\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 5;\n\t\t\t\t\tconst skipped = Math.max(0, lines.length - maxLines);\n\t\t\t\t\tconst displayLines = lines.slice(-maxLines);\n\n\t\t\t\t\tif (skipped > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n\\n... (${skipped} earlier lines)`);\n\t\t\t\t\t}\n\t\t\t\t\ttext +=\n\t\t\t\t\t\t(skipped > 0 ? \"\\n\" : \"\\n\\n\") +\n\t\t\t\t\t\tdisplayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t}\n\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tconst fullOutputPath = this.result.details?.fullOutputPath;\n\t\t\t\tif (truncation?.truncated || fullOutputPath) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (fullOutputPath) {\n\t\t\t\t\t\twarnings.push(`Full output: ${fullOutputPath}`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\tif (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\t\twarnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twarnings.push(\n\t\t\t\t\t\t\t\t`Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[${warnings.join(\". \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"read\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst offset = this.args?.offset;\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\tlet pathDisplay = path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\");\n\t\t\tif (offset !== undefined || limit !== undefined) {\n\t\t\t\tconst startLine = offset ?? 1;\n\t\t\t\tconst endLine = limit !== undefined ? startLine + limit - 1 : \"\";\n\t\t\t\tpathDisplay += theme.fg(\"warning\", `:${startLine}${endLine ? `-${endLine}` : \"\"}`);\n\t\t\t}\n\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(\"read\")) + \" \" + pathDisplay;\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput();\n\t\t\t\tconst lines = output.split(\"\\n\");\n\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\tif (truncation.firstLineExceedsLimit) {\n\t\t\t\t\t\ttext +=\n\t\t\t\t\t\t\t\"\\n\" +\n\t\t\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t`[First line exceeds ${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit]`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t} else if (truncation.truncatedBy === \"lines\") {\n\t\t\t\t\t\ttext +=\n\t\t\t\t\t\t\t\"\\n\" +\n\t\t\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t`[Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines (${truncation.maxLines ?? DEFAULT_MAX_LINES} line limit)]`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttext +=\n\t\t\t\t\t\t\t\"\\n\" +\n\t\t\t\t\t\t\ttheme.fg(\n\t\t\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t\t\t`[Truncated: ${truncation.outputLines} lines shown (${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit)]`,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"write\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\tconst fileContent = this.args?.content || \"\";\n\t\t\tconst lines = fileContent ? fileContent.split(\"\\n\") : [];\n\t\t\tconst totalLines = lines.length;\n\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"write\")) +\n\t\t\t\t\" \" +\n\t\t\t\t(path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\"));\n\t\t\tif (totalLines > 10) {\n\t\t\t\ttext += ` (${totalLines} lines)`;\n\t\t\t}\n\n\t\t\tif (fileContent) {\n\t\t\t\tconst maxLines = this.expanded ? lines.length : 10;\n\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", replaceTabs(line))).join(\"\\n\");\n\t\t\t\tif (remaining > 0) {\n\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"edit\") {\n\t\t\tconst path = shortenPath(this.args?.file_path || this.args?.path || \"\");\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"edit\")) +\n\t\t\t\t\" \" +\n\t\t\t\t(path ? theme.fg(\"accent\", path) : theme.fg(\"toolOutput\", \"...\"));\n\n\t\t\tif (this.result) {\n\t\t\t\tif (this.result.isError) {\n\t\t\t\t\tconst errorText = this.getTextOutput();\n\t\t\t\t\tif (errorText) {\n\t\t\t\t\t\ttext += \"\\n\\n\" + theme.fg(\"error\", errorText);\n\t\t\t\t\t}\n\t\t\t\t} else if (this.result.details?.diff) {\n\t\t\t\t\tconst diffLines = this.result.details.diff.split(\"\\n\");\n\t\t\t\t\tconst coloredLines = diffLines.map((line: string) => {\n\t\t\t\t\t\tif (line.startsWith(\"+\")) {\n\t\t\t\t\t\t\treturn theme.fg(\"toolDiffAdded\", line);\n\t\t\t\t\t\t} else if (line.startsWith(\"-\")) {\n\t\t\t\t\t\t\treturn theme.fg(\"toolDiffRemoved\", line);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\treturn theme.fg(\"toolDiffContext\", line);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext += \"\\n\\n\" + coloredLines.join(\"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"ls\") {\n\t\t\tconst path = shortenPath(this.args?.path || \".\");\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(\"ls\")) + \" \" + theme.fg(\"accent\", path);\n\t\t\tif (limit !== undefined) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t\t\t}\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 20;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst entryLimit = this.result.details?.entryLimitReached;\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tif (entryLimit || truncation?.truncated) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (entryLimit) {\n\t\t\t\t\t\twarnings.push(`${entryLimit} entries limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\twarnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"find\") {\n\t\t\tconst pattern = this.args?.pattern || \"\";\n\t\t\tconst path = shortenPath(this.args?.path || \".\");\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"find\")) +\n\t\t\t\t\" \" +\n\t\t\t\ttheme.fg(\"accent\", pattern) +\n\t\t\t\ttheme.fg(\"toolOutput\", ` in ${path}`);\n\t\t\tif (limit !== undefined) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` (limit ${limit})`);\n\t\t\t}\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 20;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst resultLimit = this.result.details?.resultLimitReached;\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tif (resultLimit || truncation?.truncated) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (resultLimit) {\n\t\t\t\t\t\twarnings.push(`${resultLimit} results limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\twarnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (this.toolName === \"grep\") {\n\t\t\tconst pattern = this.args?.pattern || \"\";\n\t\t\tconst path = shortenPath(this.args?.path || \".\");\n\t\t\tconst glob = this.args?.glob;\n\t\t\tconst limit = this.args?.limit;\n\n\t\t\ttext =\n\t\t\t\ttheme.fg(\"toolTitle\", theme.bold(\"grep\")) +\n\t\t\t\t\" \" +\n\t\t\t\ttheme.fg(\"accent\", `/${pattern}/`) +\n\t\t\t\ttheme.fg(\"toolOutput\", ` in ${path}`);\n\t\t\tif (glob) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` (${glob})`);\n\t\t\t}\n\t\t\tif (limit !== undefined) {\n\t\t\t\ttext += theme.fg(\"toolOutput\", ` limit ${limit}`);\n\t\t\t}\n\n\t\t\tif (this.result) {\n\t\t\t\tconst output = this.getTextOutput().trim();\n\t\t\t\tif (output) {\n\t\t\t\t\tconst lines = output.split(\"\\n\");\n\t\t\t\t\tconst maxLines = this.expanded ? lines.length : 15;\n\t\t\t\t\tconst displayLines = lines.slice(0, maxLines);\n\t\t\t\t\tconst remaining = lines.length - maxLines;\n\n\t\t\t\t\ttext += \"\\n\\n\" + displayLines.map((line: string) => theme.fg(\"toolOutput\", line)).join(\"\\n\");\n\t\t\t\t\tif (remaining > 0) {\n\t\t\t\t\t\ttext += theme.fg(\"toolOutput\", `\\n... (${remaining} more lines)`);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst matchLimit = this.result.details?.matchLimitReached;\n\t\t\t\tconst truncation = this.result.details?.truncation;\n\t\t\t\tconst linesTruncated = this.result.details?.linesTruncated;\n\t\t\t\tif (matchLimit || truncation?.truncated || linesTruncated) {\n\t\t\t\t\tconst warnings: string[] = [];\n\t\t\t\t\tif (matchLimit) {\n\t\t\t\t\t\twarnings.push(`${matchLimit} matches limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (truncation?.truncated) {\n\t\t\t\t\t\twarnings.push(`${formatSize(truncation.maxBytes ?? DEFAULT_MAX_BYTES)} limit`);\n\t\t\t\t\t}\n\t\t\t\t\tif (linesTruncated) {\n\t\t\t\t\t\twarnings.push(\"some lines truncated\");\n\t\t\t\t\t}\n\t\t\t\t\ttext += \"\\n\" + theme.fg(\"warning\", `[Truncated: ${warnings.join(\", \")}]`);\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\t// Generic tool (shouldn't reach here for custom tools)\n\t\t\ttext = theme.fg(\"toolTitle\", theme.bold(this.toolName));\n\n\t\t\tconst content = JSON.stringify(this.args, null, 2);\n\t\t\ttext += \"\\n\\n\" + content;\n\t\t\tconst output = this.getTextOutput();\n\t\t\tif (output) {\n\t\t\t\ttext += \"\\n\" + output;\n\t\t\t}\n\t\t}\n\n\t\treturn text;\n\t}\n}\n"]}