@oh-my-pi/pi-coding-agent 1.341.0 → 2.1.1337

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. package/CHANGELOG.md +86 -0
  2. package/README.md +1 -1
  3. package/examples/custom-tools/subagent/index.ts +1 -1
  4. package/package.json +10 -9
  5. package/src/bun-imports.d.ts +16 -0
  6. package/src/cli/args.ts +5 -6
  7. package/src/cli/file-processor.ts +3 -3
  8. package/src/cli/list-models.ts +2 -2
  9. package/src/cli/plugin-cli.ts +1 -1
  10. package/src/cli/session-picker.ts +2 -2
  11. package/src/cli/update-cli.ts +273 -0
  12. package/src/cli.ts +1 -1
  13. package/src/config.ts +23 -75
  14. package/src/core/agent-session.ts +158 -16
  15. package/src/core/auth-storage.ts +2 -3
  16. package/src/core/bash-executor.ts +50 -10
  17. package/src/core/compaction/branch-summarization.ts +5 -5
  18. package/src/core/compaction/compaction.ts +3 -3
  19. package/src/core/compaction/index.ts +3 -3
  20. package/src/core/custom-commands/bundled/review/index.ts +156 -0
  21. package/src/core/custom-commands/index.ts +15 -0
  22. package/src/core/custom-commands/loader.ts +232 -0
  23. package/src/core/custom-commands/types.ts +112 -0
  24. package/src/core/custom-tools/index.ts +3 -3
  25. package/src/core/custom-tools/loader.ts +10 -8
  26. package/src/core/custom-tools/types.ts +11 -6
  27. package/src/core/custom-tools/wrapper.ts +2 -1
  28. package/src/core/exec.ts +22 -12
  29. package/src/core/export-html/index.ts +38 -123
  30. package/src/core/export-html/template.css +0 -7
  31. package/src/core/export-html/template.html +3 -4
  32. package/src/core/export-html/template.macro.ts +24 -0
  33. package/src/core/file-mentions.ts +54 -0
  34. package/src/core/hooks/index.ts +5 -5
  35. package/src/core/hooks/loader.ts +21 -16
  36. package/src/core/hooks/runner.ts +6 -6
  37. package/src/core/hooks/tool-wrapper.ts +2 -2
  38. package/src/core/hooks/types.ts +12 -15
  39. package/src/core/index.ts +6 -6
  40. package/src/core/logger.ts +112 -0
  41. package/src/core/mcp/client.ts +3 -3
  42. package/src/core/mcp/config.ts +1 -1
  43. package/src/core/mcp/index.ts +12 -12
  44. package/src/core/mcp/loader.ts +2 -2
  45. package/src/core/mcp/manager.ts +6 -6
  46. package/src/core/mcp/tool-bridge.ts +3 -3
  47. package/src/core/mcp/transports/http.ts +1 -1
  48. package/src/core/mcp/transports/index.ts +2 -2
  49. package/src/core/mcp/transports/stdio.ts +1 -1
  50. package/src/core/messages.ts +22 -0
  51. package/src/core/model-registry.ts +2 -2
  52. package/src/core/model-resolver.ts +2 -2
  53. package/src/core/plugins/doctor.ts +1 -1
  54. package/src/core/plugins/index.ts +6 -6
  55. package/src/core/plugins/installer.ts +4 -4
  56. package/src/core/plugins/loader.ts +4 -9
  57. package/src/core/plugins/manager.ts +5 -5
  58. package/src/core/plugins/paths.ts +3 -3
  59. package/src/core/sdk.ts +77 -35
  60. package/src/core/session-manager.ts +6 -6
  61. package/src/core/settings-manager.ts +16 -3
  62. package/src/core/skills.ts +5 -5
  63. package/src/core/slash-commands.ts +60 -45
  64. package/src/core/system-prompt.ts +6 -6
  65. package/src/core/title-generator.ts +2 -2
  66. package/src/core/tools/bash.ts +32 -155
  67. package/src/core/tools/context.ts +2 -2
  68. package/src/core/tools/edit-diff.ts +3 -3
  69. package/src/core/tools/edit.ts +18 -5
  70. package/src/core/tools/exa/company.ts +3 -3
  71. package/src/core/tools/exa/index.ts +16 -17
  72. package/src/core/tools/exa/linkedin.ts +3 -3
  73. package/src/core/tools/exa/mcp-client.ts +9 -9
  74. package/src/core/tools/exa/render.ts +5 -5
  75. package/src/core/tools/exa/researcher.ts +3 -3
  76. package/src/core/tools/exa/search.ts +6 -5
  77. package/src/core/tools/exa/types.ts +5 -6
  78. package/src/core/tools/exa/websets.ts +3 -3
  79. package/src/core/tools/find.ts +3 -3
  80. package/src/core/tools/grep.ts +3 -3
  81. package/src/core/tools/index.ts +48 -34
  82. package/src/core/tools/ls.ts +4 -4
  83. package/src/core/tools/lsp/client.ts +161 -90
  84. package/src/core/tools/lsp/config.ts +1 -1
  85. package/src/core/tools/lsp/edits.ts +2 -2
  86. package/src/core/tools/lsp/index.ts +15 -13
  87. package/src/core/tools/lsp/render.ts +2 -2
  88. package/src/core/tools/lsp/rust-analyzer.ts +3 -3
  89. package/src/core/tools/lsp/utils.ts +1 -1
  90. package/src/core/tools/notebook.ts +1 -1
  91. package/src/core/tools/output.ts +175 -0
  92. package/src/core/tools/read.ts +7 -7
  93. package/src/core/tools/renderers.ts +92 -13
  94. package/src/core/tools/review.ts +268 -0
  95. package/src/core/tools/task/agents.ts +22 -38
  96. package/src/core/tools/task/bundled-agents/reviewer.md +52 -37
  97. package/src/core/tools/task/commands.ts +31 -10
  98. package/src/core/tools/task/discovery.ts +2 -2
  99. package/src/core/tools/task/executor.ts +145 -28
  100. package/src/core/tools/task/index.ts +78 -30
  101. package/src/core/tools/task/model-resolver.ts +30 -20
  102. package/src/core/tools/task/parallel.ts +1 -1
  103. package/src/core/tools/task/render.ts +219 -30
  104. package/src/core/tools/task/subprocess-tool-registry.ts +89 -0
  105. package/src/core/tools/task/types.ts +36 -2
  106. package/src/core/tools/web-fetch.ts +5 -3
  107. package/src/core/tools/web-search/auth.ts +1 -1
  108. package/src/core/tools/web-search/index.ts +17 -15
  109. package/src/core/tools/web-search/providers/anthropic.ts +2 -2
  110. package/src/core/tools/web-search/providers/exa.ts +3 -5
  111. package/src/core/tools/web-search/providers/perplexity.ts +1 -1
  112. package/src/core/tools/web-search/render.ts +3 -3
  113. package/src/core/tools/write.ts +4 -4
  114. package/src/index.ts +29 -18
  115. package/src/main.ts +50 -33
  116. package/src/migrations.ts +3 -3
  117. package/src/modes/index.ts +5 -5
  118. package/src/modes/interactive/components/armin.ts +1 -1
  119. package/src/modes/interactive/components/assistant-message.ts +1 -1
  120. package/src/modes/interactive/components/bash-execution.ts +4 -4
  121. package/src/modes/interactive/components/bordered-loader.ts +2 -2
  122. package/src/modes/interactive/components/branch-summary-message.ts +2 -2
  123. package/src/modes/interactive/components/compaction-summary-message.ts +2 -2
  124. package/src/modes/interactive/components/diff.ts +1 -1
  125. package/src/modes/interactive/components/dynamic-border.ts +1 -1
  126. package/src/modes/interactive/components/footer.ts +5 -5
  127. package/src/modes/interactive/components/hook-editor.ts +2 -2
  128. package/src/modes/interactive/components/hook-input.ts +2 -2
  129. package/src/modes/interactive/components/hook-message.ts +3 -3
  130. package/src/modes/interactive/components/hook-selector.ts +2 -2
  131. package/src/modes/interactive/components/model-selector.ts +281 -59
  132. package/src/modes/interactive/components/oauth-selector.ts +3 -3
  133. package/src/modes/interactive/components/plugin-settings.ts +4 -4
  134. package/src/modes/interactive/components/queue-mode-selector.ts +2 -2
  135. package/src/modes/interactive/components/session-selector.ts +4 -4
  136. package/src/modes/interactive/components/settings-defs.ts +1 -1
  137. package/src/modes/interactive/components/settings-selector.ts +5 -5
  138. package/src/modes/interactive/components/show-images-selector.ts +2 -2
  139. package/src/modes/interactive/components/theme-selector.ts +2 -2
  140. package/src/modes/interactive/components/thinking-selector.ts +2 -2
  141. package/src/modes/interactive/components/tool-execution.ts +26 -8
  142. package/src/modes/interactive/components/tree-selector.ts +3 -3
  143. package/src/modes/interactive/components/user-message-selector.ts +2 -2
  144. package/src/modes/interactive/components/user-message.ts +1 -1
  145. package/src/modes/interactive/components/welcome.ts +2 -2
  146. package/src/modes/interactive/interactive-mode.ts +86 -42
  147. package/src/modes/interactive/theme/theme.ts +15 -17
  148. package/src/modes/print-mode.ts +4 -3
  149. package/src/modes/rpc/rpc-client.ts +4 -4
  150. package/src/modes/rpc/rpc-mode.ts +22 -12
  151. package/src/modes/rpc/rpc-types.ts +3 -3
  152. package/src/utils/changelog.ts +2 -2
  153. package/src/utils/clipboard.ts +1 -1
  154. package/src/utils/shell-snapshot.ts +218 -0
  155. package/src/utils/shell.ts +93 -13
  156. package/src/utils/tools-manager.ts +1 -1
  157. package/examples/custom-tools/subagent/agents/reviewer.md +0 -35
  158. package/src/core/tools/exa/logger.ts +0 -56
package/CHANGELOG.md CHANGED
@@ -2,6 +2,92 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.1.1337] - 2026-01-03
6
+
7
+ ### Added
8
+
9
+ - Added `pi update` command to check for and install updates from GitHub releases or via bun
10
+
11
+ ### Changed
12
+
13
+ - Changed HTML export to use compile-time bundled templates via Bun macros for improved performance
14
+ - Changed `exportToHtml` and `exportFromFile` functions to be async
15
+ - Simplified build process by embedding assets (themes, templates, agents, commands) directly into the binary at compile time
16
+ - Removed separate asset copying steps from build scripts
17
+
18
+ ## [2.0.1337] - 2026-01-03
19
+ ### Added
20
+
21
+ - Added shell environment snapshot to preserve user aliases, functions, and shell options when executing bash commands
22
+ - Added support for `PI_BASH_NO_CI`, `PI_BASH_NO_LOGIN`, and `PI_SHELL_PREFIX` environment variables for shell customization
23
+ - Added zsh support alongside bash for shell detection and configuration
24
+
25
+ ### Changed
26
+
27
+ - Changed shell detection to prefer user's `$SHELL` when it's bash or zsh, with improved fallback path resolution
28
+ - Changed Edit tool to reject `.ipynb` files with guidance to use NotebookEdit tool instead
29
+
30
+ ## [1.500.0] - 2026-01-03
31
+ ### Added
32
+
33
+ - Added provider tabs to model selector with Tab/Arrow navigation for filtering models by provider
34
+ - Added context menu to model selector for choosing model role (Default, Smol, Slow) instead of keyboard shortcuts
35
+ - Added LSP diagnostics display in tool execution output showing errors and warnings after file edits
36
+ - Added centralized file logger with daily rotation to `~/.pi/logs/` for debugging production issues
37
+ - Added `logger` property to hook and custom tool APIs for error/warning/debug logging
38
+ - Added `output` tool to read full agent/task outputs by ID when truncated previews are insufficient
39
+ - Added `task` tool to reviewer agent, enabling parallel exploration of large codebases during reviews
40
+ - Added subprocess tool registry for extracting and rendering tool data from subprocess agents in real-time
41
+ - Added combined review result rendering showing verdict and findings in a tree structure
42
+ - Auto-read file mentions: Reference files with `@path/to/file.ext` syntax in prompts to automatically inject their contents, eliminating manual Read tool calls
43
+ - Added `hidden` property for custom tools to exclude them from default tool list unless explicitly requested
44
+ - Added `explicitTools` option to `createAgentSession` for enabling hidden tools by name
45
+ - Added example review tools (`report_finding`, `submit_review`) with structured findings accumulation and verdict rendering
46
+ - Added `/review` example command for interactive code review with branch comparison, uncommitted changes, and commit review modes
47
+ - Custom TypeScript slash commands: Create programmable commands at `~/.pi/agent/commands/[name]/index.ts` or `.pi/commands/[name]/index.ts`. Commands export a factory returning `{ name, description, execute(args, ctx) }`. Return a string to send as LLM prompt, or void for fire-and-forget actions. Full access to `HookCommandContext` for UI dialogs, session control, and shell execution.
48
+ - Claude command directories: Markdown slash commands now also load from `~/.claude/commands/` and `.claude/commands/` (parallel to existing `.pi/commands/` support)
49
+ - `commands.enableClaudeUser` and `commands.enableClaudeProject` settings to disable Claude command directory loading
50
+ - `/export --copy` option to copy entire session as formatted text to clipboard
51
+
52
+ ### Changed
53
+
54
+ - Changed model selector keyboard shortcuts from S/L keys to a context menu opened with Enter
55
+ - Changed model role indicators from symbols (✓ ⚡ 🧠) to labeled badges ([ DEFAULT ] [ SMOL ] [ SLOW ])
56
+ - Changed model list sorting to include secondary sort by model ID within each provider
57
+ - Changed silent error suppression to log warnings and debug info for tool errors, theme loading, and command loading failures
58
+ - Changed Task tool progress display to show agent index (e.g., `reviewer(0)`) for easier Output tool ID derivation
59
+ - Changed Task tool output to only include file paths when Output tool is unavailable, providing Read tool fallback
60
+ - Changed Task tool output references to use simpler ID format (e.g., `reviewer_0`) with line/char counts for Output tool integration
61
+ - Changed subagent recursion prevention from blanket blocking to same-agent blocking. Non-recursive agents can now spawn other agent types (e.g., reviewer can spawn explore agents) but cannot spawn themselves.
62
+ - Changed `/review` command from markdown to interactive TypeScript with mode selection menu (branch comparison, uncommitted changes, commit review, custom)
63
+ - Changed bundled commands to be overridable by user/project commands with same name
64
+ - Changed subprocess termination to wait for message_end event to capture accurate token counts
65
+ - Changed token counting in subprocess to accumulate across messages instead of overwriting
66
+ - Updated bundled `reviewer` agent to use structured review tools with priority-based findings (P0-P3) and formal verdict submission
67
+ - Task tool now streams artifacts in real-time: input written before spawn, session jsonl written by subprocess, output written at completion
68
+
69
+ ### Removed
70
+
71
+ - Removed separate Exa error logger in favor of centralized logging system
72
+ - Removed `findings_count` parameter from `submit_review` tool - findings are now counted automatically
73
+ - Removed artifacts location display from task tool output
74
+
75
+ ### Fixed
76
+
77
+ - Fixed race condition in event listener iteration by copying array before iteration to prevent mutation during callbacks
78
+ - Fixed potential memory leak from orphaned abort controllers by properly aborting existing controllers before replacement
79
+ - Fixed stream reader resource leak by adding proper `releaseLock()` calls in finally blocks
80
+ - Fixed hook API methods throwing clear errors when handlers are not initialized instead of silently failing
81
+ - Fixed LSP client race conditions with concurrent client creation and file operations using proper locking
82
+ - Fixed Task tool progress display showing stale data by cloning progress objects before passing to callbacks
83
+ - Fixed Task tool missing final progress events by waiting for readline to close before resolving
84
+ - Fixed RPC mode race condition with concurrent prompt commands by serializing execution
85
+ - Fixed pre-commit hook race condition causing `index.lock` errors when GitKraken/IDE git integrations detect file changes during formatting
86
+ - Fixed Task tool output artifacts (`out.md`) containing duplicated text from streaming updates
87
+ - Fixed Task tool progress display showing repeated nearly-identical lines during streaming
88
+ - Fixed Task tool subprocess model selection ignoring agent's configured model and falling back to settings default. The `--model` flag now accepts `provider/model` format directly.
89
+ - Fixed Task tool showing "done + succeeded" when aborted; now correctly displays "⊘ aborted" status
90
+
5
91
  ## [1.341.0] - 2026-01-03
6
92
  ### Added
7
93
 
package/README.md CHANGED
@@ -192,7 +192,7 @@ The agent reads, writes, and edits files, and executes commands via bash.
192
192
  | ------------------------- | --------------------------------------------------------------------------- |
193
193
  | `/settings` | Open settings menu (thinking, theme, queue mode, toggles) |
194
194
  | `/model` | Switch models mid-session (fuzzy search, arrow keys, Enter to select) |
195
- | `/export [file]` | Export session to self-contained HTML |
195
+ | `/export [file\|--copy]` | Export session to HTML file or copy to clipboard |
196
196
  | `/share` | Upload session as secret GitHub gist, get shareable URL (requires `gh` CLI) |
197
197
  | `/session` | Show session info: path, message counts, token usage, cost |
198
198
  | `/hotkeys` | Show all keyboard shortcuts |
@@ -18,7 +18,7 @@ import * as path from "node:path";
18
18
  import type { AgentToolResult } from "@oh-my-pi/pi-agent-core";
19
19
  import type { Message } from "@oh-my-pi/pi-ai";
20
20
  import type { CustomTool, CustomToolAPI, CustomToolFactory } from "@oh-my-pi/pi-coding-agent";
21
- import { type AgentConfig, type AgentScope, discoverAgents, formatAgentList } from "./agents.js";
21
+ import { type AgentConfig, type AgentScope, discoverAgents, formatAgentList } from "./agents";
22
22
 
23
23
  const MAX_PARALLEL_TASKS = 8;
24
24
  const MAX_CONCURRENCY = 4;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "1.341.0",
3
+ "version": "2.1.1337",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -32,16 +32,15 @@
32
32
  "check": "tsgo --noEmit",
33
33
  "clean": "rm -rf dist",
34
34
  "build": "tsgo -p tsconfig.build.json && chmod +x dist/cli.js && npm run copy-assets",
35
- "build:binary": "npm run build && bun build --compile ./dist/cli.js --outfile dist/pi && npm run copy-binary-assets",
36
- "copy-assets": "mkdir -p dist/modes/interactive/theme && cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && mkdir -p dist/core/export-html && cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/",
37
- "copy-binary-assets": "cp package.json dist/ && cp README.md dist/ && cp CHANGELOG.md dist/ && mkdir -p dist/theme && cp src/modes/interactive/theme/*.json dist/theme/ && mkdir -p dist/export-html && cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/export-html/ && cp -r docs dist/ && cp -r examples dist/",
38
- "test": "vitest --run",
35
+ "copy-assets": "cp -r src/core/tools/task/bundled-agents dist/core/tools/task/ && cp -r src/core/tools/task/bundled-commands dist/core/tools/task/ && cp src/core/export-html/template.* dist/core/export-html/",
36
+ "build:binary": "bun build --compile ./src/cli.ts --outfile dist/pi",
37
+ "test": "bun test",
39
38
  "prepublishOnly": "npm run clean && npm run build"
40
39
  },
41
40
  "dependencies": {
42
- "@oh-my-pi/pi-agent-core": "workspace:*",
43
- "@oh-my-pi/pi-ai": "workspace:*",
44
- "@oh-my-pi/pi-tui": "workspace:*",
41
+ "@oh-my-pi/pi-agent-core": "1.5.0",
42
+ "@oh-my-pi/pi-ai": "1.5.0",
43
+ "@oh-my-pi/pi-tui": "1.5.0",
45
44
  "@sinclair/typebox": "^0.34.46",
46
45
  "ajv": "^8.17.1",
47
46
  "chalk": "^5.5.0",
@@ -53,7 +52,9 @@
53
52
  "highlight.js": "^11.11.1",
54
53
  "marked": "^15.0.12",
55
54
  "minimatch": "^10.1.1",
56
- "strip-ansi": "^7.1.2"
55
+ "strip-ansi": "^7.1.2",
56
+ "winston": "^3.17.0",
57
+ "winston-daily-rotate-file": "^5.0.0"
57
58
  },
58
59
  "devDependencies": {
59
60
  "@types/diff": "^7.0.2",
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Type declarations for Bun's import attributes.
3
+ * These allow importing non-JS files as text at build time.
4
+ */
5
+
6
+ // Markdown files imported as text
7
+ declare module "*.md" {
8
+ const content: string;
9
+ export default content;
10
+ }
11
+
12
+ // Text files imported as text
13
+ declare module "*.txt" {
14
+ const content: string;
15
+ export default content;
16
+ }
package/src/cli/args.ts CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
6
  import chalk from "chalk";
7
- import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config.js";
8
- import { allTools, type ToolName } from "../core/tools/index.js";
7
+ import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config";
8
+ import { allTools, type ToolName } from "../core/tools/index";
9
9
 
10
10
  export type Mode = "text" | "json" | "rpc";
11
11
 
@@ -152,8 +152,7 @@ ${chalk.bold("Usage:")}
152
152
  ${APP_NAME} [options] [@files...] [messages...]
153
153
 
154
154
  ${chalk.bold("Options:")}
155
- --provider <name> Provider name (default: google)
156
- --model <id> Model ID (default: gemini-2.5-flash)
155
+ --model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
157
156
  --smol <id> Smol/fast model for lightweight tasks (or PI_SMOL_MODEL env)
158
157
  --slow <id> Slow/reasoning model for thorough analysis (or PI_SLOW_MODEL env)
159
158
  --api-key <key> API key (defaults to env vars)
@@ -199,8 +198,8 @@ ${chalk.bold("Examples:")}
199
198
  # Continue previous session
200
199
  ${APP_NAME} --continue "What did we discuss?"
201
200
 
202
- # Use different model
203
- ${APP_NAME} --provider openai --model gpt-4o-mini "Help me refactor this code"
201
+ # Use different model (fuzzy matching)
202
+ ${APP_NAME} --model opus "Help me refactor this code"
204
203
 
205
204
  # Limit model cycling to specific models
206
205
  ${APP_NAME} --models claude-sonnet,claude-haiku,gpt-4o
@@ -3,11 +3,11 @@
3
3
  */
4
4
 
5
5
  import { access, readFile, stat } from "node:fs/promises";
6
+ import { resolve } from "node:path";
6
7
  import type { ImageContent } from "@oh-my-pi/pi-ai";
7
8
  import chalk from "chalk";
8
- import { resolve } from "path";
9
- import { resolveReadPath } from "../core/tools/path-utils.js";
10
- import { detectSupportedImageMimeTypeFromFile } from "../utils/mime.js";
9
+ import { resolveReadPath } from "../core/tools/path-utils";
10
+ import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
11
11
 
12
12
  export interface ProcessedFiles {
13
13
  text: string;
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import type { Api, Model } from "@oh-my-pi/pi-ai";
6
- import type { ModelRegistry } from "../core/model-registry.js";
7
- import { fuzzyFilter } from "../utils/fuzzy.js";
6
+ import type { ModelRegistry } from "../core/model-registry";
7
+ import { fuzzyFilter } from "../utils/fuzzy";
8
8
 
9
9
  /**
10
10
  * Format a number as human-readable (e.g., 200000 -> "200K", 1000000 -> "1M")
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import chalk from "chalk";
8
- import { PluginManager, parseSettingValue, validateSetting } from "../core/plugins/index.js";
8
+ import { PluginManager, parseSettingValue, validateSetting } from "../core/plugins/index";
9
9
 
10
10
  // =============================================================================
11
11
  // Types
@@ -3,8 +3,8 @@
3
3
  */
4
4
 
5
5
  import { ProcessTerminal, TUI } from "@oh-my-pi/pi-tui";
6
- import type { SessionInfo } from "../core/session-manager.js";
7
- import { SessionSelectorComponent } from "../modes/interactive/components/session-selector.js";
6
+ import type { SessionInfo } from "../core/session-manager";
7
+ import { SessionSelectorComponent } from "../modes/interactive/components/session-selector";
8
8
 
9
9
  /** Show TUI session selector and return selected session path or null if cancelled */
10
10
  export async function selectSession(sessions: SessionInfo[]): Promise<string | null> {
@@ -0,0 +1,273 @@
1
+ /**
2
+ * Update CLI command handler.
3
+ *
4
+ * Handles `pi update` to check for and install updates.
5
+ * Uses bun if available, otherwise downloads binary from GitHub releases.
6
+ */
7
+
8
+ import { execSync, spawnSync } from "node:child_process";
9
+ import { createWriteStream, existsSync, renameSync, unlinkSync } from "node:fs";
10
+ import { dirname } from "node:path";
11
+ import { Readable } from "node:stream";
12
+ import { pipeline } from "node:stream/promises";
13
+ import chalk from "chalk";
14
+ import { VERSION } from "../config";
15
+
16
+ /**
17
+ * Detect if we're running as a Bun compiled binary.
18
+ */
19
+ const isBunBinary =
20
+ import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
21
+
22
+ const REPO = "can1357/oh-my-pi";
23
+ const PACKAGE = "@oh-my-pi/pi-coding-agent";
24
+
25
+ interface ReleaseInfo {
26
+ tag: string;
27
+ version: string;
28
+ assets: Array<{ name: string; url: string }>;
29
+ }
30
+
31
+ /**
32
+ * Parse update subcommand arguments.
33
+ * Returns undefined if not an update command.
34
+ */
35
+ export function parseUpdateArgs(args: string[]): { force: boolean; check: boolean } | undefined {
36
+ if (args.length === 0 || args[0] !== "update") {
37
+ return undefined;
38
+ }
39
+
40
+ return {
41
+ force: args.includes("--force") || args.includes("-f"),
42
+ check: args.includes("--check") || args.includes("-c"),
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Check if bun is available in PATH.
48
+ */
49
+ function hasBun(): boolean {
50
+ try {
51
+ const result = spawnSync("bun", ["--version"], { encoding: "utf-8", stdio: "pipe" });
52
+ return result.status === 0;
53
+ } catch {
54
+ return false;
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Get the latest release info from GitHub.
60
+ */
61
+ async function getLatestRelease(): Promise<ReleaseInfo> {
62
+ const response = await fetch(`https://api.github.com/repos/${REPO}/releases/latest`);
63
+ if (!response.ok) {
64
+ throw new Error(`Failed to fetch release info: ${response.statusText}`);
65
+ }
66
+
67
+ const data = (await response.json()) as {
68
+ tag_name: string;
69
+ assets: Array<{ name: string; browser_download_url: string }>;
70
+ };
71
+
72
+ return {
73
+ tag: data.tag_name,
74
+ version: data.tag_name.replace(/^v/, ""),
75
+ assets: data.assets.map((a) => ({ name: a.name, url: a.browser_download_url })),
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Compare semver versions. Returns:
81
+ * - negative if a < b
82
+ * - 0 if a == b
83
+ * - positive if a > b
84
+ */
85
+ function compareVersions(a: string, b: string): number {
86
+ const pa = a.split(".").map(Number);
87
+ const pb = b.split(".").map(Number);
88
+
89
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
90
+ const na = pa[i] || 0;
91
+ const nb = pb[i] || 0;
92
+ if (na !== nb) return na - nb;
93
+ }
94
+ return 0;
95
+ }
96
+
97
+ /**
98
+ * Get the appropriate binary name for this platform.
99
+ */
100
+ function getBinaryName(): string {
101
+ const platform = process.platform;
102
+ const arch = process.arch;
103
+
104
+ let os: string;
105
+ switch (platform) {
106
+ case "linux":
107
+ os = "linux";
108
+ break;
109
+ case "darwin":
110
+ os = "darwin";
111
+ break;
112
+ case "win32":
113
+ os = "windows";
114
+ break;
115
+ default:
116
+ throw new Error(`Unsupported platform: ${platform}`);
117
+ }
118
+
119
+ let archName: string;
120
+ switch (arch) {
121
+ case "x64":
122
+ archName = "x64";
123
+ break;
124
+ case "arm64":
125
+ archName = "arm64";
126
+ break;
127
+ default:
128
+ throw new Error(`Unsupported architecture: ${arch}`);
129
+ }
130
+
131
+ if (os === "windows") {
132
+ return `pi-${os}-${archName}.exe`;
133
+ }
134
+ return `pi-${os}-${archName}`;
135
+ }
136
+
137
+ /**
138
+ * Update via bun package manager.
139
+ */
140
+ async function updateViaBun(): Promise<void> {
141
+ console.log(chalk.dim("Updating via bun..."));
142
+
143
+ try {
144
+ execSync(`bun update -g ${PACKAGE}`, { stdio: "inherit" });
145
+ console.log(chalk.green("\n✓ Update complete"));
146
+ } catch {
147
+ throw new Error("bun update failed");
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Update by downloading binary from GitHub releases.
153
+ */
154
+ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
155
+ const binaryName = getBinaryName();
156
+ const asset = release.assets.find((a) => a.name === binaryName);
157
+
158
+ if (!asset) {
159
+ throw new Error(`No binary found for ${binaryName}`);
160
+ }
161
+
162
+ const execPath = process.execPath;
163
+ const _execDir = dirname(execPath);
164
+ const tempPath = `${execPath}.new`;
165
+ const backupPath = `${execPath}.bak`;
166
+
167
+ console.log(chalk.dim(`Downloading ${binaryName}...`));
168
+
169
+ // Download to temp file
170
+ const response = await fetch(asset.url, { redirect: "follow" });
171
+ if (!response.ok || !response.body) {
172
+ throw new Error(`Download failed: ${response.statusText}`);
173
+ }
174
+
175
+ const fileStream = createWriteStream(tempPath, { mode: 0o755 });
176
+ const nodeStream = Readable.fromWeb(response.body as import("stream/web").ReadableStream);
177
+ await pipeline(nodeStream, fileStream);
178
+
179
+ // Replace current binary
180
+ console.log(chalk.dim("Installing update..."));
181
+
182
+ try {
183
+ // Backup current binary
184
+ if (existsSync(backupPath)) {
185
+ unlinkSync(backupPath);
186
+ }
187
+ renameSync(execPath, backupPath);
188
+
189
+ // Move new binary into place
190
+ renameSync(tempPath, execPath);
191
+
192
+ // Clean up backup
193
+ unlinkSync(backupPath);
194
+
195
+ console.log(chalk.green(`\n✓ Updated to ${release.version}`));
196
+ console.log(chalk.dim("Restart pi to use the new version"));
197
+ } catch (err) {
198
+ // Restore from backup if possible
199
+ if (existsSync(backupPath) && !existsSync(execPath)) {
200
+ renameSync(backupPath, execPath);
201
+ }
202
+ if (existsSync(tempPath)) {
203
+ unlinkSync(tempPath);
204
+ }
205
+ throw err;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Run the update command.
211
+ */
212
+ export async function runUpdateCommand(opts: { force: boolean; check: boolean }): Promise<void> {
213
+ console.log(chalk.dim(`Current version: ${VERSION}`));
214
+
215
+ // Check for updates
216
+ let release: ReleaseInfo;
217
+ try {
218
+ release = await getLatestRelease();
219
+ } catch (err) {
220
+ console.error(chalk.red(`Failed to check for updates: ${err}`));
221
+ process.exit(1);
222
+ }
223
+
224
+ const comparison = compareVersions(release.version, VERSION);
225
+
226
+ if (comparison <= 0 && !opts.force) {
227
+ console.log(chalk.green("✓ Already up to date"));
228
+ return;
229
+ }
230
+
231
+ if (comparison > 0) {
232
+ console.log(chalk.cyan(`New version available: ${release.version}`));
233
+ } else {
234
+ console.log(chalk.yellow(`Forcing reinstall of ${release.version}`));
235
+ }
236
+
237
+ if (opts.check) {
238
+ // Just check, don't install
239
+ return;
240
+ }
241
+
242
+ // Choose update method
243
+ try {
244
+ if (!isBunBinary && hasBun()) {
245
+ await updateViaBun();
246
+ } else {
247
+ await updateViaBinary(release);
248
+ }
249
+ } catch (err) {
250
+ console.error(chalk.red(`Update failed: ${err}`));
251
+ process.exit(1);
252
+ }
253
+ }
254
+
255
+ /**
256
+ * Print update command help.
257
+ */
258
+ export function printUpdateHelp(): void {
259
+ console.log(`${chalk.bold("pi update")} - Check for and install updates
260
+
261
+ ${chalk.bold("Usage:")}
262
+ pi update [options]
263
+
264
+ ${chalk.bold("Options:")}
265
+ -c, --check Check for updates without installing
266
+ -f, --force Force reinstall even if up to date
267
+
268
+ ${chalk.bold("Examples:")}
269
+ pi update Update to latest version
270
+ pi update --check Check if updates are available
271
+ pi update --force Force reinstall
272
+ `);
273
+ }
package/src/cli.ts CHANGED
@@ -5,6 +5,6 @@
5
5
  *
6
6
  * Test with: npx tsx src/cli-new.ts [args...]
7
7
  */
8
- import { main } from "./main.js";
8
+ import { main } from "./main";
9
9
 
10
10
  main(process.argv.slice(2));