@oh-my-pi/pi-coding-agent 5.5.0 → 5.6.7
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.
- package/CHANGELOG.md +98 -0
- package/docs/python-repl.md +77 -0
- package/examples/hooks/snake.ts +7 -7
- package/package.json +5 -5
- package/src/bun-imports.d.ts +6 -0
- package/src/cli/args.ts +7 -0
- package/src/cli/setup-cli.ts +231 -0
- package/src/cli.ts +2 -0
- package/src/core/agent-session.ts +118 -15
- package/src/core/bash-executor.ts +3 -84
- package/src/core/compaction/compaction.ts +10 -5
- package/src/core/extensions/index.ts +2 -0
- package/src/core/extensions/loader.ts +13 -1
- package/src/core/extensions/runner.ts +50 -2
- package/src/core/extensions/types.ts +67 -2
- package/src/core/keybindings.ts +51 -1
- package/src/core/prompt-templates.ts +15 -0
- package/src/core/python-executor-display.test.ts +42 -0
- package/src/core/python-executor-lifecycle.test.ts +99 -0
- package/src/core/python-executor-mapping.test.ts +41 -0
- package/src/core/python-executor-per-call.test.ts +49 -0
- package/src/core/python-executor-session.test.ts +103 -0
- package/src/core/python-executor-streaming.test.ts +77 -0
- package/src/core/python-executor-timeout.test.ts +35 -0
- package/src/core/python-executor.lifecycle.test.ts +139 -0
- package/src/core/python-executor.result.test.ts +49 -0
- package/src/core/python-executor.test.ts +180 -0
- package/src/core/python-executor.ts +313 -0
- package/src/core/python-gateway-coordinator.ts +832 -0
- package/src/core/python-kernel-display.test.ts +54 -0
- package/src/core/python-kernel-env.test.ts +138 -0
- package/src/core/python-kernel-session.test.ts +87 -0
- package/src/core/python-kernel-ws.test.ts +104 -0
- package/src/core/python-kernel.lifecycle.test.ts +249 -0
- package/src/core/python-kernel.test.ts +549 -0
- package/src/core/python-kernel.ts +1178 -0
- package/src/core/python-prelude.py +889 -0
- package/src/core/python-prelude.test.ts +140 -0
- package/src/core/python-prelude.ts +3 -0
- package/src/core/sdk.ts +24 -6
- package/src/core/session-manager.ts +174 -82
- package/src/core/settings-manager-python.test.ts +23 -0
- package/src/core/settings-manager.ts +202 -0
- package/src/core/streaming-output.test.ts +26 -0
- package/src/core/streaming-output.ts +100 -0
- package/src/core/system-prompt.python.test.ts +17 -0
- package/src/core/system-prompt.ts +3 -1
- package/src/core/timings.ts +1 -1
- package/src/core/tools/bash.ts +13 -2
- package/src/core/tools/edit-diff.ts +9 -1
- package/src/core/tools/index.test.ts +50 -23
- package/src/core/tools/index.ts +83 -1
- package/src/core/tools/python-execution.test.ts +68 -0
- package/src/core/tools/python-fallback.test.ts +72 -0
- package/src/core/tools/python-renderer.test.ts +36 -0
- package/src/core/tools/python-tool-mode.test.ts +43 -0
- package/src/core/tools/python.test.ts +121 -0
- package/src/core/tools/python.ts +760 -0
- package/src/core/tools/renderers.ts +2 -0
- package/src/core/tools/schema-validation.test.ts +1 -0
- package/src/core/tools/task/executor.ts +146 -3
- package/src/core/tools/task/worker-protocol.ts +32 -2
- package/src/core/tools/task/worker.ts +182 -15
- package/src/index.ts +6 -0
- package/src/main.ts +136 -40
- package/src/modes/interactive/components/custom-editor.ts +16 -31
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +5 -16
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -13
- package/src/modes/interactive/components/history-search.ts +5 -8
- package/src/modes/interactive/components/hook-editor.ts +3 -4
- package/src/modes/interactive/components/hook-input.ts +3 -3
- package/src/modes/interactive/components/hook-selector.ts +5 -15
- package/src/modes/interactive/components/index.ts +1 -0
- package/src/modes/interactive/components/keybinding-hints.ts +66 -0
- package/src/modes/interactive/components/model-selector.ts +53 -66
- package/src/modes/interactive/components/oauth-selector.ts +5 -5
- package/src/modes/interactive/components/session-selector.ts +29 -23
- package/src/modes/interactive/components/settings-defs.ts +404 -196
- package/src/modes/interactive/components/settings-selector.ts +14 -10
- package/src/modes/interactive/components/status-line-segment-editor.ts +7 -7
- package/src/modes/interactive/components/tool-execution.ts +8 -0
- package/src/modes/interactive/components/tree-selector.ts +29 -23
- package/src/modes/interactive/components/user-message-selector.ts +6 -17
- package/src/modes/interactive/controllers/command-controller.ts +86 -37
- package/src/modes/interactive/controllers/event-controller.ts +8 -0
- package/src/modes/interactive/controllers/extension-ui-controller.ts +51 -0
- package/src/modes/interactive/controllers/input-controller.ts +42 -6
- package/src/modes/interactive/interactive-mode.ts +56 -30
- package/src/modes/interactive/theme/theme-schema.json +2 -2
- package/src/modes/interactive/types.ts +6 -1
- package/src/modes/interactive/utils/ui-helpers.ts +2 -1
- package/src/modes/print-mode.ts +23 -0
- package/src/modes/rpc/rpc-mode.ts +21 -0
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/system/system-prompt.md +32 -1
- package/src/prompts/tools/python.md +91 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,104 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.6.7] - 2026-01-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Added Python shared gateway setting to enable resource-efficient kernel reuse across sessions
|
|
10
|
+
- Added Python tool cancellation support with proper timeout and cleanup handling
|
|
11
|
+
- Added enhanced Python prelude helpers including file operations, text processing, and Git utilities
|
|
12
|
+
- Added Python tool documentation rendering with categorized helper functions
|
|
13
|
+
- Added session-scoped Python kernel isolation with workdir-aware session IDs
|
|
14
|
+
- Added structured status events for Python prelude functions with TUI rendering
|
|
15
|
+
- Added status event display system with operation icons and formatted descriptions
|
|
16
|
+
- Added support for rich output using IPython.display.display() in Python tool
|
|
17
|
+
- Added setup subcommand to install dependencies for optional features
|
|
18
|
+
- Added Python setup component to install Jupyter kernel dependencies
|
|
19
|
+
- Added setup command help with component and option documentation
|
|
20
|
+
- Added Python tool dependency check in help output
|
|
21
|
+
- Added file locking mechanism for shared Python gateway to prevent race conditions
|
|
22
|
+
- Added Python gateway status monitoring with URL, PID, client count, and uptime information
|
|
23
|
+
- Added comprehensive Git helpers to Python prelude including status, diff, log, show, branch, and file operations
|
|
24
|
+
- Added line-based operations to Python prelude including line extraction, deletion, insertion, and pattern matching
|
|
25
|
+
- Added automatic categorization system for Python prelude functions with discoverable documentation
|
|
26
|
+
- Added enhanced `/status` command display showing Python gateway, LSP servers, and MCP server connections
|
|
27
|
+
- Added shared Python gateway coordinator for resource-efficient kernel management across sessions
|
|
28
|
+
- Added Python shared gateway setting with session-scoped kernel reuse and fallback behavior
|
|
29
|
+
- Added automatic idle shutdown for shared Python gateway after 30 seconds of inactivity
|
|
30
|
+
- Added environment filtering for shared Python gateway to exclude sensitive API keys
|
|
31
|
+
- Added virtual environment detection and automatic PATH configuration for Python gateway
|
|
32
|
+
- Added IPython-backed Python tool with streaming output, image/JSON rendering, and Jupyter kernel gateway integration
|
|
33
|
+
- Added Python prelude with 30+ shell-like utility functions for file operations
|
|
34
|
+
- Added Python tool exposure settings with session-scoped kernel reuse and fallback behavior
|
|
35
|
+
- Added streaming output system with automatic spill-to-disk for large outputs
|
|
36
|
+
- Added extension input interception with source metadata and command argument completion
|
|
37
|
+
- Added extension command context `compact()` helper plus context usage accessors
|
|
38
|
+
- Added ExtensionAPI `setLabel()` for extension and entry labels
|
|
39
|
+
- Added startup quiet setting to suppress welcome screen and startup messages
|
|
40
|
+
- Added support for auto-discovering APPEND_SYSTEM.md files
|
|
41
|
+
- Added support for piped input in non-interactive mode (auto-print mode)
|
|
42
|
+
- Added global session listing across all project directories with enhanced search metadata
|
|
43
|
+
- Added session fork prompt when resolving sessions from other projects
|
|
44
|
+
- Added key hint formatting utilities plus public exports for getShellConfig/getAgentDir/VERSION
|
|
45
|
+
- Added bash tool timeout display in tool output
|
|
46
|
+
- Added fuzzy text normalization for improved edit diff matching
|
|
47
|
+
- Added $@ argument slicing syntax in prompt templates
|
|
48
|
+
- Added configurable keybindings for expand tools and dequeue actions
|
|
49
|
+
- Added process title update on CLI startup
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
|
|
53
|
+
- Updated Python tool description to display categorized helper functions with improved formatting
|
|
54
|
+
- Enhanced Python kernel startup to use shared gateway by default for better resource utilization
|
|
55
|
+
- Improved Python prelude functions to emit structured status events instead of text output
|
|
56
|
+
- Updated agent prompts to use bash tool instead of exec for git operations
|
|
57
|
+
- Changed default Python tool mode from ipy-only to both to enable shell execution
|
|
58
|
+
- Enhanced Python gateway coordination with Windows environment support and stale process cleanup
|
|
59
|
+
- Updated Python prelude functions to emit structured status events instead of text output
|
|
60
|
+
- Enhanced Python tool renderer to display status events alongside output
|
|
61
|
+
- Improved Python tool output formatting with status event integration
|
|
62
|
+
- Improved shared Python gateway coordination with environment validation and stale process cleanup
|
|
63
|
+
- Updated Python prelude to rename `bash()` function to `sh()` for consistency
|
|
64
|
+
- Changed default Python tool mode from "ipy-only" to "both" to enable both IPython and shell execution
|
|
65
|
+
- Enhanced Python gateway metadata tracking to include Python path and virtual environment information
|
|
66
|
+
- Improved Python kernel startup to use shared gateway by default for better resource utilization
|
|
67
|
+
- Updated Python tool to support proxy execution mode for worker processes
|
|
68
|
+
- Enhanced Python kernel availability checking with faster validation
|
|
69
|
+
- Optimized Python environment warming to avoid blocking during tool initialization
|
|
70
|
+
- Reorganized settings interface into behavior, tools, display, voice, status, lsp, and exa tabs
|
|
71
|
+
- Migrated environment variables from PI_ to OMP_ prefix with automatic migration
|
|
72
|
+
- Updated model selector to use TabBar component for provider navigation
|
|
73
|
+
- Changed role badges to inverted style with colored backgrounds
|
|
74
|
+
- Added support for /models command alias in addition to /model
|
|
75
|
+
- Improved error retry detection to include fetch failures
|
|
76
|
+
- Enhanced session selector search and overflow handling
|
|
77
|
+
- Updated skill command execution to include skill path metadata
|
|
78
|
+
- Surfaced loaded prompt templates during initialization
|
|
79
|
+
- Updated compaction summarization to use serialized prompt text
|
|
80
|
+
- Cleaned up Python prelude `sh()` and `run()` output to only show stdout/stderr without noisy metadata
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- Fixed Python kernel cancellation handling and WebSocket cleanup for in-flight executions
|
|
85
|
+
- Fixed Python tool session scoping to include workdir and honor sharedGateway settings
|
|
86
|
+
- Fixed gist sharing output draining to avoid truncated URLs
|
|
87
|
+
- Fixed streaming output byte accounting and UTF-8 decoder flushing
|
|
88
|
+
- Fixed Python prelude integration tests to detect virtual environments and cover helper exports
|
|
89
|
+
- Fixed Python kernel cancellation/timeout handling and WebSocket close cleanup for in-flight executions
|
|
90
|
+
- Fixed Python output byte accounting and UTF-8 decoder flushing in streaming output
|
|
91
|
+
- Fixed shared Python gateway coordination (Windows env allowlist, lock staleness, refcount recovery)
|
|
92
|
+
- Fixed Python tool session scoping to include workdir and honor sharedGateway settings
|
|
93
|
+
- Fixed subagent Python proxy session isolation and cancellation/timeout propagation
|
|
94
|
+
- Fixed print-mode cleanup to dispose Python sessions before exit
|
|
95
|
+
- Fixed gist share output draining to avoid truncated URLs
|
|
96
|
+
- Fixed explore agent tool list to use bash for git operations
|
|
97
|
+
- Fixed Python prelude integration tests to detect venv-only Python and cover helper exports
|
|
98
|
+
|
|
99
|
+
### Security
|
|
100
|
+
|
|
101
|
+
- Enhanced Python gateway environment filtering to exclude sensitive API keys and Windows system paths
|
|
102
|
+
|
|
5
103
|
## [5.5.0] - 2026-01-18
|
|
6
104
|
### Changed
|
|
7
105
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Python REPL (Jupyter Kernel Gateway)
|
|
2
|
+
|
|
3
|
+
## Requirements
|
|
4
|
+
|
|
5
|
+
- Python 3 available on PATH (or via an active virtualenv)
|
|
6
|
+
- `jupyter-kernel-gateway` (`kernel_gateway` module) and `ipykernel` installed in the selected Python environment
|
|
7
|
+
|
|
8
|
+
Install:
|
|
9
|
+
```bash
|
|
10
|
+
python -m pip install jupyter_kernel_gateway ipykernel
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
The Python tool starts a Jupyter Kernel Gateway process locally, which manages an IPython kernel. All code execution goes through the gateway's REST and WebSocket APIs.
|
|
16
|
+
|
|
17
|
+
Startup flow:
|
|
18
|
+
1. Spawn `python -m kernel_gateway` on a random available port
|
|
19
|
+
2. Wait for gateway to become ready (`GET /api/kernelspecs`)
|
|
20
|
+
3. Create a kernel (`POST /api/kernels`)
|
|
21
|
+
4. Connect WebSocket for execution messages
|
|
22
|
+
5. Run prelude code (helper functions)
|
|
23
|
+
|
|
24
|
+
## External Gateway Support
|
|
25
|
+
|
|
26
|
+
Instead of spawning a local gateway, you can connect to an already-running Jupyter Kernel Gateway:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# Connect to external gateway
|
|
30
|
+
export OMP_PYTHON_GATEWAY_URL="http://127.0.0.1:8888"
|
|
31
|
+
|
|
32
|
+
# Optional: auth token if gateway requires it (KG_AUTH_TOKEN)
|
|
33
|
+
export OMP_PYTHON_GATEWAY_TOKEN="your-token-here"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
When `OMP_PYTHON_GATEWAY_URL` is set:
|
|
37
|
+
- No local gateway process is spawned
|
|
38
|
+
- Kernels are created on the external gateway
|
|
39
|
+
- The gateway process is not killed on shutdown
|
|
40
|
+
- Availability check uses `/api/kernelspecs` endpoint instead of local module check
|
|
41
|
+
|
|
42
|
+
This is useful for:
|
|
43
|
+
- Remote kernel execution
|
|
44
|
+
- Shared kernel environments
|
|
45
|
+
- Pre-configured gateway setups
|
|
46
|
+
|
|
47
|
+
## Environment Propagation
|
|
48
|
+
|
|
49
|
+
- The kernel inherits a filtered environment (explicit allowlist + denylist)
|
|
50
|
+
- `PYTHONPATH` includes the working directory and any existing `PYTHONPATH` value
|
|
51
|
+
- Virtual environments are detected via `VIRTUAL_ENV`, `.venv/`, or `venv/` and preferred when present
|
|
52
|
+
|
|
53
|
+
## Kernel Modes
|
|
54
|
+
|
|
55
|
+
Settings under `python` control exposure and reuse:
|
|
56
|
+
- `toolMode`: `ipy-only` (default), `bash-only`, `both`
|
|
57
|
+
- `kernelMode`: `session` (default, queued), `per-call`
|
|
58
|
+
|
|
59
|
+
## Shell Bridge
|
|
60
|
+
|
|
61
|
+
The Python prelude exposes `bash()` which:
|
|
62
|
+
- Sources the shell snapshot when `OMP_SHELL_SNAPSHOT` is set
|
|
63
|
+
- Runs via `bash -lc` when available, with OS fallbacks
|
|
64
|
+
|
|
65
|
+
## Output Handling
|
|
66
|
+
|
|
67
|
+
- Streams `stdout`/`stderr` as text
|
|
68
|
+
- `image/png` display data renders inline in TUI
|
|
69
|
+
- `application/json` display data renders as a collapsible tree
|
|
70
|
+
- `text/html` display data is converted to basic markdown
|
|
71
|
+
|
|
72
|
+
## Troubleshooting
|
|
73
|
+
|
|
74
|
+
- **Kernel unavailable**: Ensure `python` + `jupyter-kernel-gateway` + `ipykernel` are installed; the session will fall back to bash-only.
|
|
75
|
+
- **External gateway unreachable**: Check the URL is correct and the gateway is running. If auth is required, set `OMP_PYTHON_GATEWAY_TOKEN`.
|
|
76
|
+
- **IPC tracing**: Set `OMP_PYTHON_IPC_TRACE=1` to log kernel message flow.
|
|
77
|
+
- **Stdin requests**: Interactive input is not supported; refactor code to avoid `input()` or provide data programmatically.
|
package/examples/hooks/snake.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { HookAPI } from "@oh-my-pi/pi-coding-agent";
|
|
6
|
-
import {
|
|
6
|
+
import { matchesKey, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
7
7
|
|
|
8
8
|
const GAME_WIDTH = 40;
|
|
9
9
|
const GAME_HEIGHT = 15;
|
|
@@ -150,7 +150,7 @@ class SnakeComponent {
|
|
|
150
150
|
handleInput(data: string): void {
|
|
151
151
|
// If paused (resuming), wait for any key
|
|
152
152
|
if (this.paused) {
|
|
153
|
-
if (
|
|
153
|
+
if (matchesKey(data, "escape") || matchesKey(data, "esc") || data === "q" || data === "Q") {
|
|
154
154
|
// Quit without clearing save
|
|
155
155
|
this.dispose();
|
|
156
156
|
this.onClose();
|
|
@@ -163,7 +163,7 @@ class SnakeComponent {
|
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
// ESC to pause and save
|
|
166
|
-
if (
|
|
166
|
+
if (matchesKey(data, "escape") || matchesKey(data, "esc")) {
|
|
167
167
|
this.dispose();
|
|
168
168
|
this.onSave(this.state);
|
|
169
169
|
this.onClose();
|
|
@@ -179,13 +179,13 @@ class SnakeComponent {
|
|
|
179
179
|
}
|
|
180
180
|
|
|
181
181
|
// Arrow keys or WASD
|
|
182
|
-
if (
|
|
182
|
+
if (matchesKey(data, "up") || data === "w" || data === "W") {
|
|
183
183
|
if (this.state.direction !== "down") this.state.nextDirection = "up";
|
|
184
|
-
} else if (
|
|
184
|
+
} else if (matchesKey(data, "down") || data === "s" || data === "S") {
|
|
185
185
|
if (this.state.direction !== "up") this.state.nextDirection = "down";
|
|
186
|
-
} else if (
|
|
186
|
+
} else if (matchesKey(data, "right") || data === "d" || data === "D") {
|
|
187
187
|
if (this.state.direction !== "left") this.state.nextDirection = "right";
|
|
188
|
-
} else if (
|
|
188
|
+
} else if (matchesKey(data, "left") || data === "a" || data === "A") {
|
|
189
189
|
if (this.state.direction !== "right") this.state.nextDirection = "left";
|
|
190
190
|
}
|
|
191
191
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.6.7",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-agent-core": "5.
|
|
43
|
-
"@oh-my-pi/pi-ai": "5.
|
|
44
|
-
"@oh-my-pi/pi-git-tool": "5.
|
|
45
|
-
"@oh-my-pi/pi-tui": "5.
|
|
42
|
+
"@oh-my-pi/pi-agent-core": "5.6.7",
|
|
43
|
+
"@oh-my-pi/pi-ai": "5.6.7",
|
|
44
|
+
"@oh-my-pi/pi-git-tool": "5.6.7",
|
|
45
|
+
"@oh-my-pi/pi-tui": "5.6.7",
|
|
46
46
|
"@openai/agents": "^0.3.7",
|
|
47
47
|
"@silvia-odwyer/photon-node": "^0.3.4",
|
|
48
48
|
"@sinclair/typebox": "^0.34.46",
|
package/src/bun-imports.d.ts
CHANGED
package/src/cli/args.ts
CHANGED
|
@@ -181,6 +181,12 @@ export function printHelp(): void {
|
|
|
181
181
|
${chalk.bold("Usage:")}
|
|
182
182
|
${APP_NAME} [options] [@files...] [messages...]
|
|
183
183
|
|
|
184
|
+
${chalk.bold("Subcommands:")}
|
|
185
|
+
plugin Manage plugins (install, uninstall, list, etc.)
|
|
186
|
+
update Check for and install updates
|
|
187
|
+
config Manage configuration settings
|
|
188
|
+
setup Install dependencies for optional features
|
|
189
|
+
|
|
184
190
|
${chalk.bold("Options:")}
|
|
185
191
|
--model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
|
|
186
192
|
--smol <id> Smol/fast model for lightweight tasks (or OMP_SMOL_MODEL env)
|
|
@@ -284,6 +290,7 @@ ${chalk.bold("Available Tools (all enabled by default):")}
|
|
|
284
290
|
find - Find files by glob pattern
|
|
285
291
|
ls - List directory contents
|
|
286
292
|
lsp - Language server protocol (code intelligence)
|
|
293
|
+
python - Execute Python code (requires: ${APP_NAME} setup python)
|
|
287
294
|
notebook - Edit Jupyter notebooks
|
|
288
295
|
task - Launch sub-agents for parallel tasks
|
|
289
296
|
web_fetch - Fetch and process web pages
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup CLI command handler.
|
|
3
|
+
*
|
|
4
|
+
* Handles `omp setup <component>` to install dependencies for optional features.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import chalk from "chalk";
|
|
8
|
+
import { APP_NAME } from "../config";
|
|
9
|
+
import { theme } from "../modes/interactive/theme/theme";
|
|
10
|
+
|
|
11
|
+
export type SetupComponent = "python";
|
|
12
|
+
|
|
13
|
+
export interface SetupCommandArgs {
|
|
14
|
+
component: SetupComponent;
|
|
15
|
+
flags: {
|
|
16
|
+
json?: boolean;
|
|
17
|
+
check?: boolean;
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const VALID_COMPONENTS: SetupComponent[] = ["python"];
|
|
22
|
+
|
|
23
|
+
const PYTHON_PACKAGES = ["jupyter_kernel_gateway", "ipykernel"];
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Parse setup subcommand arguments.
|
|
27
|
+
* Returns undefined if not a setup command.
|
|
28
|
+
*/
|
|
29
|
+
export function parseSetupArgs(args: string[]): SetupCommandArgs | undefined {
|
|
30
|
+
if (args.length === 0 || args[0] !== "setup") {
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (args.length < 2) {
|
|
35
|
+
console.error(chalk.red(`Usage: ${APP_NAME} setup <component>`));
|
|
36
|
+
console.error(`Valid components: ${VALID_COMPONENTS.join(", ")}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const component = args[1];
|
|
41
|
+
if (!VALID_COMPONENTS.includes(component as SetupComponent)) {
|
|
42
|
+
console.error(chalk.red(`Unknown component: ${component}`));
|
|
43
|
+
console.error(`Valid components: ${VALID_COMPONENTS.join(", ")}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const flags: SetupCommandArgs["flags"] = {};
|
|
48
|
+
for (let i = 2; i < args.length; i++) {
|
|
49
|
+
const arg = args[i];
|
|
50
|
+
if (arg === "--json") {
|
|
51
|
+
flags.json = true;
|
|
52
|
+
} else if (arg === "--check" || arg === "-c") {
|
|
53
|
+
flags.check = true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
component: component as SetupComponent,
|
|
59
|
+
flags,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface PythonCheckResult {
|
|
64
|
+
available: boolean;
|
|
65
|
+
pythonPath?: string;
|
|
66
|
+
uvPath?: string;
|
|
67
|
+
pipPath?: string;
|
|
68
|
+
missingPackages: string[];
|
|
69
|
+
installedPackages: string[];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Check Python environment and kernel dependencies.
|
|
74
|
+
*/
|
|
75
|
+
async function checkPythonSetup(): Promise<PythonCheckResult> {
|
|
76
|
+
const result: PythonCheckResult = {
|
|
77
|
+
available: false,
|
|
78
|
+
missingPackages: [],
|
|
79
|
+
installedPackages: [],
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const pythonPath = Bun.which("python") ?? Bun.which("python3");
|
|
83
|
+
if (!pythonPath) {
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
result.pythonPath = pythonPath;
|
|
87
|
+
result.uvPath = Bun.which("uv") ?? undefined;
|
|
88
|
+
result.pipPath = Bun.which("pip3") ?? Bun.which("pip") ?? undefined;
|
|
89
|
+
|
|
90
|
+
for (const pkg of PYTHON_PACKAGES) {
|
|
91
|
+
const moduleName = pkg === "jupyter_kernel_gateway" ? "kernel_gateway" : pkg;
|
|
92
|
+
const check = Bun.spawnSync(
|
|
93
|
+
[pythonPath, "-c", `import importlib.util; exit(0 if importlib.util.find_spec('${moduleName}') else 1)`],
|
|
94
|
+
{ stdin: "ignore", stdout: "pipe", stderr: "pipe" },
|
|
95
|
+
);
|
|
96
|
+
if (check.exitCode === 0) {
|
|
97
|
+
result.installedPackages.push(pkg);
|
|
98
|
+
} else {
|
|
99
|
+
result.missingPackages.push(pkg);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
result.available = result.missingPackages.length === 0;
|
|
104
|
+
return result;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Install Python packages using uv (preferred) or pip.
|
|
109
|
+
*/
|
|
110
|
+
function installPythonPackages(packages: string[], uvPath?: string, pipPath?: string): boolean {
|
|
111
|
+
if (uvPath) {
|
|
112
|
+
console.log(chalk.dim(`Installing via uv: ${packages.join(" ")}`));
|
|
113
|
+
const result = Bun.spawnSync([uvPath, "pip", "install", ...packages], {
|
|
114
|
+
stdin: "ignore",
|
|
115
|
+
stdout: "inherit",
|
|
116
|
+
stderr: "inherit",
|
|
117
|
+
});
|
|
118
|
+
return result.exitCode === 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (pipPath) {
|
|
122
|
+
console.log(chalk.dim(`Installing via pip: ${packages.join(" ")}`));
|
|
123
|
+
const result = Bun.spawnSync([pipPath, "install", ...packages], {
|
|
124
|
+
stdin: "ignore",
|
|
125
|
+
stdout: "inherit",
|
|
126
|
+
stderr: "inherit",
|
|
127
|
+
});
|
|
128
|
+
return result.exitCode === 0;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Run the setup command.
|
|
136
|
+
*/
|
|
137
|
+
export async function runSetupCommand(cmd: SetupCommandArgs): Promise<void> {
|
|
138
|
+
switch (cmd.component) {
|
|
139
|
+
case "python":
|
|
140
|
+
await handlePythonSetup(cmd.flags);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function handlePythonSetup(flags: { json?: boolean; check?: boolean }): Promise<void> {
|
|
146
|
+
const check = await checkPythonSetup();
|
|
147
|
+
|
|
148
|
+
if (flags.json) {
|
|
149
|
+
console.log(JSON.stringify(check, null, 2));
|
|
150
|
+
if (!check.available) process.exit(1);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!check.pythonPath) {
|
|
155
|
+
console.error(chalk.red(`${theme.status.error} Python not found`));
|
|
156
|
+
console.error(chalk.dim("Install Python 3.8+ and ensure it's in your PATH"));
|
|
157
|
+
process.exit(1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.log(chalk.dim(`Python: ${check.pythonPath}`));
|
|
161
|
+
|
|
162
|
+
if (check.uvPath) {
|
|
163
|
+
console.log(chalk.dim(`uv: ${check.uvPath}`));
|
|
164
|
+
} else if (check.pipPath) {
|
|
165
|
+
console.log(chalk.dim(`pip: ${check.pipPath}`));
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (check.installedPackages.length > 0) {
|
|
169
|
+
console.log(chalk.green(`${theme.status.success} Installed: ${check.installedPackages.join(", ")}`));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (check.missingPackages.length === 0) {
|
|
173
|
+
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
console.log(chalk.yellow(`${theme.status.warning} Missing: ${check.missingPackages.join(", ")}`));
|
|
178
|
+
|
|
179
|
+
if (flags.check) {
|
|
180
|
+
process.exit(1);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!check.uvPath && !check.pipPath) {
|
|
184
|
+
console.error(chalk.red(`\n${theme.status.error} No package manager found`));
|
|
185
|
+
console.error(chalk.dim("Install uv (recommended) or pip:"));
|
|
186
|
+
console.error(chalk.dim(" curl -LsSf https://astral.sh/uv/install.sh | sh"));
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
console.log("");
|
|
191
|
+
const success = installPythonPackages(check.missingPackages, check.uvPath, check.pipPath);
|
|
192
|
+
|
|
193
|
+
if (!success) {
|
|
194
|
+
console.error(chalk.red(`\n${theme.status.error} Installation failed`));
|
|
195
|
+
console.error(chalk.dim("Try installing manually:"));
|
|
196
|
+
console.error(chalk.dim(` ${check.uvPath ? "uv pip" : "pip"} install ${check.missingPackages.join(" ")}`));
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const recheck = await checkPythonSetup();
|
|
201
|
+
if (recheck.available) {
|
|
202
|
+
console.log(chalk.green(`\n${theme.status.success} Python execution is ready`));
|
|
203
|
+
} else {
|
|
204
|
+
console.error(chalk.red(`\n${theme.status.error} Setup incomplete`));
|
|
205
|
+
console.error(chalk.dim(`Still missing: ${recheck.missingPackages.join(", ")}`));
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Print setup command help.
|
|
212
|
+
*/
|
|
213
|
+
export function printSetupHelp(): void {
|
|
214
|
+
console.log(`${chalk.bold(`${APP_NAME} setup`)} - Install dependencies for optional features
|
|
215
|
+
|
|
216
|
+
${chalk.bold("Usage:")}
|
|
217
|
+
${APP_NAME} setup <component> [options]
|
|
218
|
+
|
|
219
|
+
${chalk.bold("Components:")}
|
|
220
|
+
python Install Jupyter kernel dependencies for Python code execution
|
|
221
|
+
Packages: ${PYTHON_PACKAGES.join(", ")}
|
|
222
|
+
|
|
223
|
+
${chalk.bold("Options:")}
|
|
224
|
+
-c, --check Check if dependencies are installed without installing
|
|
225
|
+
--json Output status as JSON
|
|
226
|
+
|
|
227
|
+
${chalk.bold("Examples:")}
|
|
228
|
+
${APP_NAME} setup python Install Python execution dependencies
|
|
229
|
+
${APP_NAME} setup python --check Check if Python execution is available
|
|
230
|
+
`);
|
|
231
|
+
}
|