@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.
Files changed (96) hide show
  1. package/CHANGELOG.md +98 -0
  2. package/docs/python-repl.md +77 -0
  3. package/examples/hooks/snake.ts +7 -7
  4. package/package.json +5 -5
  5. package/src/bun-imports.d.ts +6 -0
  6. package/src/cli/args.ts +7 -0
  7. package/src/cli/setup-cli.ts +231 -0
  8. package/src/cli.ts +2 -0
  9. package/src/core/agent-session.ts +118 -15
  10. package/src/core/bash-executor.ts +3 -84
  11. package/src/core/compaction/compaction.ts +10 -5
  12. package/src/core/extensions/index.ts +2 -0
  13. package/src/core/extensions/loader.ts +13 -1
  14. package/src/core/extensions/runner.ts +50 -2
  15. package/src/core/extensions/types.ts +67 -2
  16. package/src/core/keybindings.ts +51 -1
  17. package/src/core/prompt-templates.ts +15 -0
  18. package/src/core/python-executor-display.test.ts +42 -0
  19. package/src/core/python-executor-lifecycle.test.ts +99 -0
  20. package/src/core/python-executor-mapping.test.ts +41 -0
  21. package/src/core/python-executor-per-call.test.ts +49 -0
  22. package/src/core/python-executor-session.test.ts +103 -0
  23. package/src/core/python-executor-streaming.test.ts +77 -0
  24. package/src/core/python-executor-timeout.test.ts +35 -0
  25. package/src/core/python-executor.lifecycle.test.ts +139 -0
  26. package/src/core/python-executor.result.test.ts +49 -0
  27. package/src/core/python-executor.test.ts +180 -0
  28. package/src/core/python-executor.ts +313 -0
  29. package/src/core/python-gateway-coordinator.ts +832 -0
  30. package/src/core/python-kernel-display.test.ts +54 -0
  31. package/src/core/python-kernel-env.test.ts +138 -0
  32. package/src/core/python-kernel-session.test.ts +87 -0
  33. package/src/core/python-kernel-ws.test.ts +104 -0
  34. package/src/core/python-kernel.lifecycle.test.ts +249 -0
  35. package/src/core/python-kernel.test.ts +549 -0
  36. package/src/core/python-kernel.ts +1178 -0
  37. package/src/core/python-prelude.py +889 -0
  38. package/src/core/python-prelude.test.ts +140 -0
  39. package/src/core/python-prelude.ts +3 -0
  40. package/src/core/sdk.ts +24 -6
  41. package/src/core/session-manager.ts +174 -82
  42. package/src/core/settings-manager-python.test.ts +23 -0
  43. package/src/core/settings-manager.ts +202 -0
  44. package/src/core/streaming-output.test.ts +26 -0
  45. package/src/core/streaming-output.ts +100 -0
  46. package/src/core/system-prompt.python.test.ts +17 -0
  47. package/src/core/system-prompt.ts +3 -1
  48. package/src/core/timings.ts +1 -1
  49. package/src/core/tools/bash.ts +13 -2
  50. package/src/core/tools/edit-diff.ts +9 -1
  51. package/src/core/tools/index.test.ts +50 -23
  52. package/src/core/tools/index.ts +83 -1
  53. package/src/core/tools/python-execution.test.ts +68 -0
  54. package/src/core/tools/python-fallback.test.ts +72 -0
  55. package/src/core/tools/python-renderer.test.ts +36 -0
  56. package/src/core/tools/python-tool-mode.test.ts +43 -0
  57. package/src/core/tools/python.test.ts +121 -0
  58. package/src/core/tools/python.ts +760 -0
  59. package/src/core/tools/renderers.ts +2 -0
  60. package/src/core/tools/schema-validation.test.ts +1 -0
  61. package/src/core/tools/task/executor.ts +146 -3
  62. package/src/core/tools/task/worker-protocol.ts +32 -2
  63. package/src/core/tools/task/worker.ts +182 -15
  64. package/src/index.ts +6 -0
  65. package/src/main.ts +136 -40
  66. package/src/modes/interactive/components/custom-editor.ts +16 -31
  67. package/src/modes/interactive/components/extensions/extension-dashboard.ts +5 -16
  68. package/src/modes/interactive/components/extensions/extension-list.ts +5 -13
  69. package/src/modes/interactive/components/history-search.ts +5 -8
  70. package/src/modes/interactive/components/hook-editor.ts +3 -4
  71. package/src/modes/interactive/components/hook-input.ts +3 -3
  72. package/src/modes/interactive/components/hook-selector.ts +5 -15
  73. package/src/modes/interactive/components/index.ts +1 -0
  74. package/src/modes/interactive/components/keybinding-hints.ts +66 -0
  75. package/src/modes/interactive/components/model-selector.ts +53 -66
  76. package/src/modes/interactive/components/oauth-selector.ts +5 -5
  77. package/src/modes/interactive/components/session-selector.ts +29 -23
  78. package/src/modes/interactive/components/settings-defs.ts +404 -196
  79. package/src/modes/interactive/components/settings-selector.ts +14 -10
  80. package/src/modes/interactive/components/status-line-segment-editor.ts +7 -7
  81. package/src/modes/interactive/components/tool-execution.ts +8 -0
  82. package/src/modes/interactive/components/tree-selector.ts +29 -23
  83. package/src/modes/interactive/components/user-message-selector.ts +6 -17
  84. package/src/modes/interactive/controllers/command-controller.ts +86 -37
  85. package/src/modes/interactive/controllers/event-controller.ts +8 -0
  86. package/src/modes/interactive/controllers/extension-ui-controller.ts +51 -0
  87. package/src/modes/interactive/controllers/input-controller.ts +42 -6
  88. package/src/modes/interactive/interactive-mode.ts +56 -30
  89. package/src/modes/interactive/theme/theme-schema.json +2 -2
  90. package/src/modes/interactive/types.ts +6 -1
  91. package/src/modes/interactive/utils/ui-helpers.ts +2 -1
  92. package/src/modes/print-mode.ts +23 -0
  93. package/src/modes/rpc/rpc-mode.ts +21 -0
  94. package/src/prompts/agents/reviewer.md +1 -1
  95. package/src/prompts/system/system-prompt.md +32 -1
  96. 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.
@@ -3,7 +3,7 @@
3
3
  */
4
4
 
5
5
  import type { HookAPI } from "@oh-my-pi/pi-coding-agent";
6
- import { isArrowDown, isArrowLeft, isArrowRight, isArrowUp, isEscape, visibleWidth } from "@oh-my-pi/pi-tui";
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 (isEscape(data) || data === "q" || data === "Q") {
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 (isEscape(data)) {
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 (isArrowUp(data) || data === "w" || data === "W") {
182
+ if (matchesKey(data, "up") || data === "w" || data === "W") {
183
183
  if (this.state.direction !== "down") this.state.nextDirection = "up";
184
- } else if (isArrowDown(data) || data === "s" || data === "S") {
184
+ } else if (matchesKey(data, "down") || data === "s" || data === "S") {
185
185
  if (this.state.direction !== "up") this.state.nextDirection = "down";
186
- } else if (isArrowRight(data) || data === "d" || data === "D") {
186
+ } else if (matchesKey(data, "right") || data === "d" || data === "D") {
187
187
  if (this.state.direction !== "left") this.state.nextDirection = "right";
188
- } else if (isArrowLeft(data) || data === "a" || data === "A") {
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.5.0",
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.5.0",
43
- "@oh-my-pi/pi-ai": "5.5.0",
44
- "@oh-my-pi/pi-git-tool": "5.5.0",
45
- "@oh-my-pi/pi-tui": "5.5.0",
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",
@@ -14,3 +14,9 @@ declare module "*.txt" {
14
14
  const content: string;
15
15
  export default content;
16
16
  }
17
+
18
+ // Python files imported as text
19
+ declare module "*.py" {
20
+ const content: string;
21
+ export default content;
22
+ }
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
+ }
package/src/cli.ts CHANGED
@@ -5,6 +5,8 @@
5
5
  *
6
6
  * Test with: npx tsx src/cli-new.ts [args...]
7
7
  */
8
+ import { APP_NAME } from "./config";
8
9
  import { main } from "./main";
9
10
 
11
+ process.title = APP_NAME;
10
12
  main(process.argv.slice(2));