@oh-my-pi/pi-coding-agent 8.1.0 → 8.2.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 (402) hide show
  1. package/CHANGELOG.md +21 -1
  2. package/docs/session.md +111 -46
  3. package/examples/custom-tools/hello/index.ts +1 -1
  4. package/examples/custom-tools/todo/index.ts +3 -4
  5. package/examples/extensions/api-demo.ts +0 -1
  6. package/examples/extensions/chalk-logger.ts +2 -3
  7. package/examples/extensions/hello.ts +0 -1
  8. package/examples/extensions/pirate.ts +0 -1
  9. package/examples/extensions/plan-mode.ts +15 -16
  10. package/examples/extensions/todo.ts +3 -4
  11. package/examples/extensions/tools.ts +1 -2
  12. package/examples/extensions/with-deps/index.ts +0 -1
  13. package/examples/hooks/auto-commit-on-exit.ts +1 -2
  14. package/examples/hooks/confirm-destructive.ts +0 -1
  15. package/examples/hooks/custom-compaction.ts +1 -2
  16. package/examples/hooks/dirty-repo-guard.ts +0 -1
  17. package/examples/hooks/file-trigger.ts +3 -4
  18. package/examples/hooks/git-checkpoint.ts +0 -1
  19. package/examples/hooks/handoff.ts +3 -4
  20. package/examples/hooks/permission-gate.ts +1 -2
  21. package/examples/hooks/protected-paths.ts +1 -2
  22. package/examples/hooks/qna.ts +2 -3
  23. package/examples/hooks/snake.ts +4 -5
  24. package/examples/hooks/status-line.ts +0 -1
  25. package/examples/sdk/01-minimal.ts +2 -3
  26. package/examples/sdk/02-custom-model.ts +2 -3
  27. package/examples/sdk/03-custom-prompt.ts +3 -4
  28. package/examples/sdk/04-skills.ts +2 -3
  29. package/examples/sdk/06-extensions.ts +1 -2
  30. package/examples/sdk/06-hooks.ts +6 -7
  31. package/examples/sdk/07-context-files.ts +0 -1
  32. package/examples/sdk/08-prompt-templates.ts +0 -1
  33. package/examples/sdk/08-slash-commands.ts +0 -1
  34. package/examples/sdk/09-api-keys-and-oauth.ts +0 -1
  35. package/examples/sdk/10-settings.ts +0 -1
  36. package/examples/sdk/11-sessions.ts +0 -1
  37. package/package.json +51 -23
  38. package/scripts/format-prompts.ts +0 -1
  39. package/src/capability/context-file.ts +2 -3
  40. package/src/capability/extension-module.ts +2 -3
  41. package/src/capability/extension.ts +2 -3
  42. package/src/capability/fs.ts +20 -21
  43. package/src/capability/hook.ts +2 -3
  44. package/src/capability/index.ts +15 -16
  45. package/src/capability/instruction.ts +2 -3
  46. package/src/capability/mcp.ts +2 -3
  47. package/src/capability/prompt.ts +2 -3
  48. package/src/capability/rule.ts +2 -3
  49. package/src/capability/settings.ts +1 -2
  50. package/src/capability/skill.ts +2 -3
  51. package/src/capability/slash-command.ts +2 -3
  52. package/src/capability/ssh.ts +2 -3
  53. package/src/capability/system-prompt.ts +2 -3
  54. package/src/capability/tool.ts +2 -3
  55. package/src/cli/args.ts +5 -6
  56. package/src/cli/config-cli.ts +6 -7
  57. package/src/cli/file-processor.ts +19 -17
  58. package/src/cli/jupyter-cli.ts +105 -0
  59. package/src/cli/list-models.ts +10 -11
  60. package/src/cli/plugin-cli.ts +20 -21
  61. package/src/cli/session-picker.ts +2 -3
  62. package/src/cli/setup-cli.ts +2 -3
  63. package/src/cli/stats-cli.ts +2 -3
  64. package/src/cli/update-cli.ts +25 -22
  65. package/src/commit/agentic/agent.ts +21 -23
  66. package/src/commit/agentic/fallback.ts +9 -9
  67. package/src/commit/agentic/index.ts +30 -38
  68. package/src/commit/agentic/state.ts +1 -6
  69. package/src/commit/agentic/tools/analyze-file.ts +15 -15
  70. package/src/commit/agentic/tools/git-file-diff.ts +3 -3
  71. package/src/commit/agentic/tools/git-hunk.ts +7 -7
  72. package/src/commit/agentic/tools/git-overview.ts +5 -5
  73. package/src/commit/agentic/tools/index.ts +14 -14
  74. package/src/commit/agentic/tools/propose-changelog.ts +6 -6
  75. package/src/commit/agentic/tools/propose-commit.ts +8 -8
  76. package/src/commit/agentic/tools/recent-commits.ts +2 -2
  77. package/src/commit/agentic/tools/split-commit.ts +19 -23
  78. package/src/commit/agentic/topo-sort.ts +1 -1
  79. package/src/commit/agentic/trivial.ts +3 -3
  80. package/src/commit/agentic/validation.ts +12 -12
  81. package/src/commit/analysis/conventional.ts +7 -11
  82. package/src/commit/analysis/index.ts +4 -4
  83. package/src/commit/analysis/scope.ts +4 -4
  84. package/src/commit/analysis/summary.ts +7 -9
  85. package/src/commit/analysis/validation.ts +1 -1
  86. package/src/commit/changelog/detect.ts +6 -6
  87. package/src/commit/changelog/generate.ts +7 -9
  88. package/src/commit/changelog/index.ts +13 -13
  89. package/src/commit/changelog/parse.ts +2 -2
  90. package/src/commit/cli.ts +1 -1
  91. package/src/commit/git/diff.ts +3 -3
  92. package/src/commit/git/index.ts +19 -24
  93. package/src/commit/index.ts +1 -1
  94. package/src/commit/map-reduce/index.ts +9 -9
  95. package/src/commit/map-reduce/map-phase.ts +19 -34
  96. package/src/commit/map-reduce/reduce-phase.ts +9 -11
  97. package/src/commit/message.ts +2 -2
  98. package/src/commit/model-selection.ts +3 -7
  99. package/src/commit/pipeline.ts +20 -22
  100. package/src/commit/utils/exclusions.ts +3 -3
  101. package/src/config/file-lock.ts +17 -7
  102. package/src/config/keybindings.ts +6 -8
  103. package/src/config/model-registry.ts +55 -37
  104. package/src/config/model-resolver.ts +18 -19
  105. package/src/config/prompt-templates.ts +11 -11
  106. package/src/config/settings-manager.ts +50 -34
  107. package/src/config.ts +60 -62
  108. package/src/cursor.ts +11 -9
  109. package/src/discovery/agents-md.ts +11 -12
  110. package/src/discovery/builtin.ts +68 -73
  111. package/src/discovery/claude.ts +41 -42
  112. package/src/discovery/cline.ts +11 -12
  113. package/src/discovery/codex.ts +52 -53
  114. package/src/discovery/cursor.ts +9 -10
  115. package/src/discovery/gemini.ts +17 -22
  116. package/src/discovery/github.ts +13 -14
  117. package/src/discovery/helpers.ts +35 -34
  118. package/src/discovery/index.ts +16 -18
  119. package/src/discovery/mcp-json.ts +8 -9
  120. package/src/discovery/ssh.ts +8 -9
  121. package/src/discovery/vscode.ts +4 -5
  122. package/src/discovery/windsurf.ts +6 -7
  123. package/src/exa/company.ts +1 -2
  124. package/src/exa/index.ts +2 -3
  125. package/src/exa/linkedin.ts +1 -2
  126. package/src/exa/mcp-client.ts +14 -16
  127. package/src/exa/render.ts +10 -11
  128. package/src/exa/researcher.ts +1 -2
  129. package/src/exa/search.ts +1 -2
  130. package/src/exa/types.ts +0 -1
  131. package/src/exa/websets.ts +1 -2
  132. package/src/exec/bash-executor.ts +3 -4
  133. package/src/exec/exec.ts +0 -1
  134. package/src/export/custom-share.ts +5 -6
  135. package/src/export/html/index.ts +24 -21
  136. package/src/export/ttsr.ts +2 -3
  137. package/src/extensibility/custom-commands/bundled/review/index.ts +7 -8
  138. package/src/extensibility/custom-commands/loader.ts +17 -14
  139. package/src/extensibility/custom-commands/types.ts +1 -2
  140. package/src/extensibility/custom-tools/loader.ts +10 -11
  141. package/src/extensibility/custom-tools/types.ts +6 -7
  142. package/src/extensibility/custom-tools/wrapper.ts +2 -3
  143. package/src/extensibility/extensions/loader.ts +75 -53
  144. package/src/extensibility/extensions/runner.ts +11 -12
  145. package/src/extensibility/extensions/types.ts +19 -26
  146. package/src/extensibility/extensions/wrapper.ts +3 -4
  147. package/src/extensibility/hooks/index.ts +1 -1
  148. package/src/extensibility/hooks/loader.ts +8 -9
  149. package/src/extensibility/hooks/runner.ts +7 -8
  150. package/src/extensibility/hooks/tool-wrapper.ts +0 -1
  151. package/src/extensibility/hooks/types.ts +10 -17
  152. package/src/extensibility/plugins/doctor.ts +3 -3
  153. package/src/extensibility/plugins/installer.ts +27 -27
  154. package/src/extensibility/plugins/loader.ts +59 -56
  155. package/src/extensibility/plugins/manager.ts +211 -171
  156. package/src/extensibility/plugins/parser.ts +1 -1
  157. package/src/extensibility/plugins/paths.ts +8 -8
  158. package/src/extensibility/skills.ts +63 -60
  159. package/src/extensibility/slash-commands.ts +10 -10
  160. package/src/index.ts +46 -46
  161. package/src/internal-urls/agent-protocol.ts +21 -11
  162. package/src/internal-urls/artifact-protocol.ts +17 -13
  163. package/src/internal-urls/router.ts +1 -2
  164. package/src/internal-urls/rule-protocol.ts +3 -4
  165. package/src/internal-urls/skill-protocol.ts +3 -4
  166. package/src/ipy/executor.ts +14 -10
  167. package/src/ipy/gateway-coordinator.ts +79 -90
  168. package/src/ipy/kernel.ts +32 -30
  169. package/src/ipy/modules.ts +13 -13
  170. package/src/lsp/client.ts +21 -10
  171. package/src/lsp/clients/biome-client.ts +1 -2
  172. package/src/lsp/clients/index.ts +3 -3
  173. package/src/lsp/clients/lsp-linter-client.ts +4 -5
  174. package/src/lsp/config.ts +15 -15
  175. package/src/lsp/edits.ts +4 -5
  176. package/src/lsp/index.ts +43 -44
  177. package/src/lsp/lspmux.ts +8 -8
  178. package/src/lsp/render.ts +10 -16
  179. package/src/lsp/utils.ts +3 -3
  180. package/src/main.ts +55 -34
  181. package/src/mcp/client.ts +2 -3
  182. package/src/mcp/config.ts +5 -6
  183. package/src/mcp/json-rpc.ts +0 -1
  184. package/src/mcp/loader.ts +3 -4
  185. package/src/mcp/manager.ts +17 -18
  186. package/src/mcp/tool-bridge.ts +4 -9
  187. package/src/mcp/tool-cache.ts +2 -3
  188. package/src/mcp/transports/http.ts +2 -4
  189. package/src/mcp/transports/stdio.ts +1 -2
  190. package/src/migrations.ts +60 -49
  191. package/src/modes/components/armin.ts +4 -5
  192. package/src/modes/components/assistant-message.ts +6 -6
  193. package/src/modes/components/bash-execution.ts +7 -8
  194. package/src/modes/components/bordered-loader.ts +3 -3
  195. package/src/modes/components/branch-summary-message.ts +3 -3
  196. package/src/modes/components/compaction-summary-message.ts +3 -3
  197. package/src/modes/components/countdown-timer.ts +0 -1
  198. package/src/modes/components/custom-message.ts +5 -5
  199. package/src/modes/components/diff.ts +1 -1
  200. package/src/modes/components/dynamic-border.ts +2 -2
  201. package/src/modes/components/extensions/extension-dashboard.ts +6 -7
  202. package/src/modes/components/extensions/extension-list.ts +2 -3
  203. package/src/modes/components/extensions/inspector-panel.ts +3 -4
  204. package/src/modes/components/extensions/state-manager.ts +25 -26
  205. package/src/modes/components/extensions/types.ts +1 -2
  206. package/src/modes/components/footer.ts +47 -43
  207. package/src/modes/components/history-search.ts +2 -2
  208. package/src/modes/components/hook-editor.ts +3 -4
  209. package/src/modes/components/hook-input.ts +2 -3
  210. package/src/modes/components/hook-message.ts +5 -5
  211. package/src/modes/components/hook-selector.ts +2 -3
  212. package/src/modes/components/keybinding-hints.ts +2 -3
  213. package/src/modes/components/login-dialog.ts +2 -2
  214. package/src/modes/components/model-selector.ts +12 -12
  215. package/src/modes/components/oauth-selector.ts +2 -2
  216. package/src/modes/components/plugin-settings.ts +20 -20
  217. package/src/modes/components/python-execution.ts +7 -8
  218. package/src/modes/components/queue-mode-selector.ts +3 -3
  219. package/src/modes/components/read-tool-group.ts +2 -2
  220. package/src/modes/components/session-selector.ts +4 -4
  221. package/src/modes/components/settings-defs.ts +77 -69
  222. package/src/modes/components/settings-selector.ts +16 -16
  223. package/src/modes/components/show-images-selector.ts +2 -2
  224. package/src/modes/components/status-line/segments.ts +4 -4
  225. package/src/modes/components/status-line/separators.ts +1 -1
  226. package/src/modes/components/status-line/types.ts +2 -2
  227. package/src/modes/components/status-line-segment-editor.ts +7 -8
  228. package/src/modes/components/status-line.ts +12 -12
  229. package/src/modes/components/theme-selector.ts +8 -7
  230. package/src/modes/components/thinking-selector.ts +4 -4
  231. package/src/modes/components/todo-display.ts +2 -2
  232. package/src/modes/components/todo-reminder.ts +4 -4
  233. package/src/modes/components/tool-execution.ts +11 -16
  234. package/src/modes/components/tree-selector.ts +11 -11
  235. package/src/modes/components/ttsr-notification.ts +5 -5
  236. package/src/modes/components/user-message-selector.ts +1 -1
  237. package/src/modes/components/user-message.ts +1 -1
  238. package/src/modes/components/visual-truncate.ts +0 -1
  239. package/src/modes/components/welcome.ts +4 -4
  240. package/src/modes/controllers/command-controller.ts +46 -47
  241. package/src/modes/controllers/event-controller.ts +16 -20
  242. package/src/modes/controllers/extension-ui-controller.ts +40 -46
  243. package/src/modes/controllers/input-controller.ts +17 -18
  244. package/src/modes/controllers/selector-controller.ts +103 -91
  245. package/src/modes/index.ts +3 -3
  246. package/src/modes/interactive-mode.ts +27 -29
  247. package/src/modes/print-mode.ts +12 -13
  248. package/src/modes/rpc/rpc-client.ts +7 -8
  249. package/src/modes/rpc/rpc-mode.ts +24 -25
  250. package/src/modes/rpc/rpc-types.ts +3 -4
  251. package/src/modes/theme/mermaid-cache.ts +2 -2
  252. package/src/modes/theme/theme.ts +128 -53
  253. package/src/modes/types.ts +10 -10
  254. package/src/modes/utils/ui-helpers.ts +17 -17
  255. package/src/patch/applicator.ts +18 -19
  256. package/src/patch/diff.ts +1 -2
  257. package/src/patch/fuzzy.ts +1 -2
  258. package/src/patch/index.ts +10 -11
  259. package/src/patch/normalize.ts +4 -4
  260. package/src/patch/normative.ts +1 -2
  261. package/src/patch/parser.ts +8 -9
  262. package/src/patch/shared.ts +12 -13
  263. package/src/sdk.ts +60 -63
  264. package/src/session/agent-session.ts +83 -84
  265. package/src/session/agent-storage.ts +11 -11
  266. package/src/session/artifacts.ts +8 -9
  267. package/src/session/auth-storage.ts +25 -29
  268. package/src/session/compaction/branch-summarization.ts +7 -10
  269. package/src/session/compaction/compaction.ts +8 -19
  270. package/src/session/compaction/utils.ts +6 -9
  271. package/src/session/history-storage.ts +10 -10
  272. package/src/session/messages.ts +4 -5
  273. package/src/session/session-manager.ts +76 -65
  274. package/src/session/session-storage.ts +57 -69
  275. package/src/session/storage-migration.ts +2 -3
  276. package/src/session/streaming-output.ts +2 -2
  277. package/src/ssh/connection-manager.ts +43 -50
  278. package/src/ssh/ssh-executor.ts +2 -2
  279. package/src/ssh/sshfs-mount.ts +11 -18
  280. package/src/system-prompt.ts +27 -34
  281. package/src/task/agents.ts +45 -30
  282. package/src/task/commands.ts +6 -7
  283. package/src/task/discovery.ts +39 -76
  284. package/src/task/executor.ts +14 -15
  285. package/src/task/index.ts +33 -36
  286. package/src/task/output-manager.ts +3 -4
  287. package/src/task/parallel.ts +0 -1
  288. package/src/task/render.ts +19 -20
  289. package/src/task/subprocess-tool-registry.ts +1 -2
  290. package/src/task/worker-protocol.ts +3 -3
  291. package/src/task/worker.ts +32 -38
  292. package/src/task/worktree.ts +19 -19
  293. package/src/tools/ask.ts +8 -9
  294. package/src/tools/bash-interceptor.ts +1 -5
  295. package/src/tools/bash.ts +19 -18
  296. package/src/tools/calculator.ts +12 -12
  297. package/src/tools/complete.ts +3 -4
  298. package/src/tools/context.ts +2 -2
  299. package/src/tools/fetch.ts +23 -26
  300. package/src/tools/find.ts +15 -16
  301. package/src/tools/gemini-image.ts +14 -14
  302. package/src/tools/grep.ts +27 -27
  303. package/src/tools/index.ts +78 -56
  304. package/src/tools/list-limit.ts +1 -1
  305. package/src/tools/ls.ts +7 -7
  306. package/src/tools/notebook.ts +5 -5
  307. package/src/tools/output-meta.ts +3 -4
  308. package/src/tools/output-utils.ts +1 -1
  309. package/src/tools/path-utils.ts +5 -5
  310. package/src/tools/python.ts +36 -37
  311. package/src/tools/read.ts +23 -23
  312. package/src/tools/render-utils.ts +8 -9
  313. package/src/tools/renderers.ts +6 -7
  314. package/src/tools/review.ts +8 -11
  315. package/src/tools/ssh.ts +31 -30
  316. package/src/tools/todo-write.ts +13 -13
  317. package/src/tools/tool-errors.ts +3 -3
  318. package/src/tools/tool-result.ts +3 -8
  319. package/src/tools/write.ts +11 -16
  320. package/src/tui/code-cell.ts +3 -9
  321. package/src/tui/file-list.ts +3 -4
  322. package/src/tui/output-block.ts +1 -2
  323. package/src/tui/status-line.ts +2 -3
  324. package/src/tui/tree-list.ts +2 -3
  325. package/src/tui/types.ts +1 -2
  326. package/src/tui/utils.ts +2 -3
  327. package/src/utils/changelog.ts +9 -10
  328. package/src/utils/clipboard.ts +11 -11
  329. package/src/utils/file-mentions.ts +4 -10
  330. package/src/utils/frontmatter.ts +6 -3
  331. package/src/utils/fuzzy.ts +2 -2
  332. package/src/utils/image-convert.ts +1 -1
  333. package/src/utils/image-resize.ts +1 -1
  334. package/src/utils/mime.ts +2 -2
  335. package/src/utils/shell-snapshot.ts +11 -13
  336. package/src/utils/shell.ts +4 -5
  337. package/src/utils/title-generator.ts +8 -9
  338. package/src/utils/tools-manager.ts +23 -23
  339. package/src/vendor/photon/index.js +1099 -1059
  340. package/src/vendor/photon/photon_rs_bg.wasm +0 -0
  341. package/src/web/scrapers/artifacthub.ts +1 -1
  342. package/src/web/scrapers/arxiv.ts +2 -2
  343. package/src/web/scrapers/bluesky.ts +2 -2
  344. package/src/web/scrapers/cheatsh.ts +1 -1
  345. package/src/web/scrapers/chocolatey.ts +2 -2
  346. package/src/web/scrapers/choosealicense.ts +5 -5
  347. package/src/web/scrapers/cisa-kev.ts +1 -1
  348. package/src/web/scrapers/crossref.ts +2 -2
  349. package/src/web/scrapers/devto.ts +3 -3
  350. package/src/web/scrapers/discogs.ts +3 -4
  351. package/src/web/scrapers/discourse.ts +1 -1
  352. package/src/web/scrapers/dockerhub.ts +1 -1
  353. package/src/web/scrapers/fdroid.ts +2 -2
  354. package/src/web/scrapers/firefox-addons.ts +3 -3
  355. package/src/web/scrapers/flathub.ts +1 -1
  356. package/src/web/scrapers/github.ts +3 -3
  357. package/src/web/scrapers/gitlab.ts +4 -4
  358. package/src/web/scrapers/hackernews.ts +2 -2
  359. package/src/web/scrapers/huggingface.ts +1 -1
  360. package/src/web/scrapers/iacr.ts +2 -2
  361. package/src/web/scrapers/index.ts +0 -1
  362. package/src/web/scrapers/jetbrains-marketplace.ts +1 -1
  363. package/src/web/scrapers/lemmy.ts +2 -2
  364. package/src/web/scrapers/maven.ts +2 -2
  365. package/src/web/scrapers/mdn.ts +2 -4
  366. package/src/web/scrapers/metacpan.ts +2 -2
  367. package/src/web/scrapers/musicbrainz.ts +1 -2
  368. package/src/web/scrapers/npm.ts +1 -1
  369. package/src/web/scrapers/nuget.ts +2 -2
  370. package/src/web/scrapers/nvd.ts +3 -3
  371. package/src/web/scrapers/ollama.ts +7 -9
  372. package/src/web/scrapers/opencorporates.ts +2 -2
  373. package/src/web/scrapers/openlibrary.ts +6 -6
  374. package/src/web/scrapers/orcid.ts +0 -1
  375. package/src/web/scrapers/osv.ts +2 -2
  376. package/src/web/scrapers/packagist.ts +1 -1
  377. package/src/web/scrapers/pubmed.ts +1 -2
  378. package/src/web/scrapers/rawg.ts +2 -2
  379. package/src/web/scrapers/readthedocs.ts +1 -2
  380. package/src/web/scrapers/repology.ts +2 -2
  381. package/src/web/scrapers/rfc.ts +1 -1
  382. package/src/web/scrapers/searchcode.ts +2 -2
  383. package/src/web/scrapers/semantic-scholar.ts +1 -1
  384. package/src/web/scrapers/snapcraft.ts +2 -2
  385. package/src/web/scrapers/sourcegraph.ts +1 -1
  386. package/src/web/scrapers/spdx.ts +3 -3
  387. package/src/web/scrapers/spotify.ts +0 -1
  388. package/src/web/scrapers/twitter.ts +1 -1
  389. package/src/web/scrapers/types.ts +1 -2
  390. package/src/web/scrapers/utils.ts +5 -5
  391. package/src/web/scrapers/wikidata.ts +3 -3
  392. package/src/web/scrapers/youtube.ts +9 -14
  393. package/src/web/search/auth.ts +4 -9
  394. package/src/web/search/index.ts +11 -21
  395. package/src/web/search/providers/anthropic.ts +3 -9
  396. package/src/web/search/providers/exa.ts +6 -10
  397. package/src/web/search/providers/perplexity.ts +5 -5
  398. package/src/web/search/render.ts +16 -18
  399. package/scripts/generate-wasm-b64.ts +0 -24
  400. package/src/commit/map-reduce/.map-phase.ts.kate-swp +0 -0
  401. package/src/task/.executor.ts.kate-swp +0 -0
  402. package/src/vendor/photon/photon_rs_bg.wasm.b64.js +0 -1
@@ -1,16 +1,15 @@
1
- import type { Dirent } from "node:fs";
2
- import { readdir } from "node:fs/promises";
3
- import { dirname, join, resolve } from "node:path";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
4
3
 
5
4
  const contentCache = new Map<string, string | null>();
6
- const dirCache = new Map<string, Dirent[]>();
5
+ const dirCache = new Map<string, fs.Dirent[]>();
7
6
 
8
- function resolvePath(path: string): string {
9
- return resolve(path);
7
+ function resolvePath(filePath: string): string {
8
+ return path.resolve(filePath);
10
9
  }
11
10
 
12
- export async function readFile(path: string): Promise<string | null> {
13
- const abs = resolvePath(path);
11
+ export async function readFile(filePath: string): Promise<string | null> {
12
+ const abs = resolvePath(filePath);
14
13
  if (contentCache.has(abs)) {
15
14
  return contentCache.get(abs) ?? null;
16
15
  }
@@ -25,14 +24,14 @@ export async function readFile(path: string): Promise<string | null> {
25
24
  }
26
25
  }
27
26
 
28
- export async function readDirEntries(path: string): Promise<Dirent[]> {
29
- const abs = resolvePath(path);
27
+ export async function readDirEntries(dirPath: string): Promise<fs.Dirent[]> {
28
+ const abs = resolvePath(dirPath);
30
29
  if (dirCache.has(abs)) {
31
30
  return dirCache.get(abs) ?? [];
32
31
  }
33
32
 
34
33
  try {
35
- const entries = await readdir(abs, { withFileTypes: true });
34
+ const entries = await fs.promises.readdir(abs, { withFileTypes: true });
36
35
  dirCache.set(abs, entries);
37
36
  return entries;
38
37
  } catch {
@@ -41,9 +40,9 @@ export async function readDirEntries(path: string): Promise<Dirent[]> {
41
40
  }
42
41
  }
43
42
 
44
- export async function readDir(path: string): Promise<string[]> {
45
- const entries = await readDirEntries(path);
46
- return entries.map((entry) => entry.name);
43
+ export async function readDir(dirPath: string): Promise<string[]> {
44
+ const entries = await readDirEntries(dirPath);
45
+ return entries.map(entry => entry.name);
47
46
  }
48
47
 
49
48
  export async function walkUp(
@@ -56,12 +55,12 @@ export async function walkUp(
56
55
 
57
56
  while (true) {
58
57
  const entries = await readDirEntries(current);
59
- const entry = entries.find((e) => e.name === name);
58
+ const entry = entries.find(e => e.name === name);
60
59
  if (entry) {
61
- if (file && entry.isFile()) return join(current, name);
62
- if (dir && entry.isDirectory()) return join(current, name);
60
+ if (file && entry.isFile()) return path.join(current, name);
61
+ if (dir && entry.isDirectory()) return path.join(current, name);
63
62
  }
64
- const parent = dirname(current);
63
+ const parent = path.dirname(current);
65
64
  if (parent === current) return null;
66
65
  current = parent;
67
66
  }
@@ -79,11 +78,11 @@ export function clearCache(): void {
79
78
  dirCache.clear();
80
79
  }
81
80
 
82
- export function invalidate(path: string): void {
83
- const abs = resolvePath(path);
81
+ export function invalidate(filePath: string): void {
82
+ const abs = resolvePath(filePath);
84
83
  contentCache.delete(abs);
85
84
  dirCache.delete(abs);
86
- const parent = dirname(abs);
85
+ const parent = path.dirname(abs);
87
86
  if (parent !== abs) {
88
87
  dirCache.delete(parent);
89
88
  }
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Pre/post tool execution hooks defined as shell scripts.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -29,8 +28,8 @@ export const hookCapability = defineCapability<Hook>({
29
28
  id: "hooks",
30
29
  displayName: "Hooks",
31
30
  description: "Pre/post tool execution hooks",
32
- key: (hook) => `${hook.type}:${hook.tool}:${hook.name}`,
33
- validate: (hook) => {
31
+ key: hook => `${hook.type}:${hook.tool}:${hook.name}`,
32
+ validate: hook => {
34
33
  if (!hook.name) return "Missing name";
35
34
  if (!hook.path) return "Missing path";
36
35
  if (hook.type !== "pre" && hook.type !== "post") return "Invalid type (must be 'pre' or 'post')";
@@ -6,9 +6,8 @@
6
6
  * - Registering providers (where to find it)
7
7
  * - Loading items for a capability across all providers
8
8
  */
9
-
10
- import { homedir } from "node:os";
11
- import { resolve } from "node:path";
9
+ import * as os from "node:os";
10
+ import * as path from "node:path";
12
11
  import { clearCache as clearFsCache, cacheStats as fsCacheStats, invalidate as invalidateFs } from "./fs";
13
12
  import type {
14
13
  Capability,
@@ -81,7 +80,7 @@ export function registerProvider<T>(capabilityId: string, provider: Provider<T>)
81
80
 
82
81
  // Insert in priority order (highest first)
83
82
  const providers = capability.providers as Provider<T>[];
84
- const idx = providers.findIndex((p) => p.priority < provider.priority);
83
+ const idx = providers.findIndex(p => p.priority < provider.priority);
85
84
  if (idx === -1) {
86
85
  providers.push(provider);
87
86
  } else {
@@ -107,7 +106,7 @@ async function loadImpl<T>(
107
106
  const contributingProviders: string[] = [];
108
107
 
109
108
  const results = await Promise.all(
110
- providers.map(async (provider) => {
109
+ providers.map(async provider => {
111
110
  try {
112
111
  const result = await provider.load(ctx);
113
112
  return { provider, result };
@@ -128,7 +127,7 @@ async function loadImpl<T>(
128
127
  if (!result) continue;
129
128
 
130
129
  if (result.warnings) {
131
- allWarnings.push(...result.warnings.map((w) => `[${provider.displayName}] ${w}`));
130
+ allWarnings.push(...result.warnings.map(w => `[${provider.displayName}] ${w}`));
132
131
  }
133
132
 
134
133
  if (result.items.length > 0) {
@@ -190,15 +189,15 @@ async function loadImpl<T>(
190
189
  * Filter providers based on options and disabled state.
191
190
  */
192
191
  function filterProviders<T>(capability: Capability<T>, options: LoadOptions): Provider<T>[] {
193
- let providers = (capability.providers as Provider<T>[]).filter((p) => !disabledProviders.has(p.id));
192
+ let providers = (capability.providers as Provider<T>[]).filter(p => !disabledProviders.has(p.id));
194
193
 
195
194
  if (options.providers) {
196
195
  const allowed = new Set(options.providers);
197
- providers = providers.filter((p) => allowed.has(p.id));
196
+ providers = providers.filter(p => allowed.has(p.id));
198
197
  }
199
198
  if (options.excludeProviders) {
200
199
  const excluded = new Set(options.excludeProviders);
201
- providers = providers.filter((p) => !excluded.has(p.id));
200
+ providers = providers.filter(p => !excluded.has(p.id));
202
201
  }
203
202
 
204
203
  return providers;
@@ -214,7 +213,7 @@ export async function loadCapability<T>(capabilityId: string, options: LoadOptio
214
213
  }
215
214
 
216
215
  const cwd = options.cwd ?? process.cwd();
217
- const home = homedir();
216
+ const home = os.homedir();
218
217
  const ctx: LoadContext = { cwd, home };
219
218
  const providers = filterProviders(capability, options);
220
219
 
@@ -321,7 +320,7 @@ export function getCapabilityInfo(capabilityId: string): CapabilityInfo | undefi
321
320
  id: capability.id,
322
321
  displayName: capability.displayName,
323
322
  description: capability.description,
324
- providers: capability.providers.map((p) => ({
323
+ providers: capability.providers.map(p => ({
325
324
  id: p.id,
326
325
  displayName: p.displayName,
327
326
  description: p.description,
@@ -335,7 +334,7 @@ export function getCapabilityInfo(capabilityId: string): CapabilityInfo | undefi
335
334
  * Get all capabilities info for UI display.
336
335
  */
337
336
  export function getAllCapabilitiesInfo(): CapabilityInfo[] {
338
- return listCapabilities().map((id) => getCapabilityInfo(id)!);
337
+ return listCapabilities().map(id => getCapabilityInfo(id)!);
339
338
  }
340
339
 
341
340
  /**
@@ -350,7 +349,7 @@ export function getProviderInfo(providerId: string): ProviderInfo | undefined {
350
349
  let priority = 0;
351
350
  for (const capId of caps) {
352
351
  const cap = capabilities.get(capId);
353
- const provider = cap?.providers.find((p) => p.id === providerId);
352
+ const provider = cap?.providers.find(p => p.id === providerId);
354
353
  if (provider) {
355
354
  priority = provider.priority;
356
355
  break;
@@ -399,10 +398,10 @@ export function reset(): void {
399
398
 
400
399
  /**
401
400
  * Invalidate cache for a specific path.
402
- * @param path - Absolute or relative path to invalidate
401
+ * @param filePath - Absolute or relative path to invalidate
403
402
  */
404
- export function invalidate(path: string, cwd?: string): void {
405
- const resolved = cwd ? resolve(cwd, path) : path;
403
+ export function invalidate(filePath: string, cwd?: string): void {
404
+ const resolved = cwd ? path.resolve(cwd, filePath) : filePath;
406
405
  invalidateFs(resolved);
407
406
  }
408
407
 
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * GitHub Copilot-style instructions with optional file pattern matching.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -27,8 +26,8 @@ export const instructionCapability = defineCapability<Instruction>({
27
26
  id: "instructions",
28
27
  displayName: "Instructions",
29
28
  description: "File-specific instructions with glob pattern matching (GitHub Copilot format)",
30
- key: (inst) => inst.name,
31
- validate: (inst) => {
29
+ key: inst => inst.name,
30
+ validate: inst => {
32
31
  if (!inst.name) return "Missing name";
33
32
  if (!inst.path) return "Missing path";
34
33
  if (inst.content === undefined) return "Missing content";
@@ -4,7 +4,6 @@
4
4
  * Canonical shape for MCP server configurations, regardless of source format.
5
5
  * All providers translate their native format to this shape.
6
6
  */
7
-
8
7
  import { defineCapability } from ".";
9
8
  import type { SourceMeta } from "./types";
10
9
 
@@ -34,8 +33,8 @@ export const mcpCapability = defineCapability<MCPServer>({
34
33
  id: "mcps",
35
34
  displayName: "MCP Servers",
36
35
  description: "Model Context Protocol server configurations for external tool integrations",
37
- key: (server) => server.name,
38
- validate: (server) => {
36
+ key: server => server.name,
37
+ validate: server => {
39
38
  if (!server.name) return "Missing server name";
40
39
  if (!server.command && !server.url) return "Must have command or url";
41
40
 
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Reusable prompt templates (Codex format) available via /prompts: menu.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -25,8 +24,8 @@ export const promptCapability = defineCapability<Prompt>({
25
24
  id: "prompts",
26
25
  displayName: "Prompts",
27
26
  description: "Reusable prompt templates available via /prompts: menu",
28
- key: (prompt) => prompt.name,
29
- validate: (prompt) => {
27
+ key: prompt => prompt.name,
28
+ validate: prompt => {
30
29
  if (!prompt.name) return "Missing name";
31
30
  if (!prompt.path) return "Missing path";
32
31
  if (prompt.content === undefined) return "Missing content";
@@ -4,7 +4,6 @@
4
4
  * Project-specific rules from Cursor (.mdc), Windsurf (.md), and Cline formats.
5
5
  * Translated to a canonical shape regardless of source format.
6
6
  */
7
-
8
7
  import { defineCapability } from ".";
9
8
  import type { SourceMeta } from "./types";
10
9
 
@@ -46,8 +45,8 @@ export const ruleCapability = defineCapability<Rule>({
46
45
  id: "rules",
47
46
  displayName: "Rules",
48
47
  description: "Project-specific rules and constraints (Cursor MDC, Windsurf, Cline formats)",
49
- key: (rule) => rule.name,
50
- validate: (rule) => {
48
+ key: rule => rule.name,
49
+ validate: rule => {
51
50
  if (!rule.name) return "Missing rule name";
52
51
  if (!rule.path) return "Missing rule path";
53
52
  if (!rule.content || typeof rule.content !== "string") return "Rule must have content";
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Configuration settings from various sources (JSON, TOML, etc.)
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -27,7 +26,7 @@ export const settingsCapability = defineCapability<Settings>({
27
26
  description: "Configuration settings from various sources",
28
27
  // Settings are merged, not deduplicated by key
29
28
  key: () => undefined,
30
- validate: (settings) => {
29
+ validate: settings => {
31
30
  if (!settings.path) return "Missing path";
32
31
  if (!settings.data || typeof settings.data !== "object") return "Missing or invalid data";
33
32
  return undefined;
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Skills provide specialized knowledge or workflows that extend agent capabilities.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -40,8 +39,8 @@ export const skillCapability = defineCapability<Skill>({
40
39
  id: "skills",
41
40
  displayName: "Skills",
42
41
  description: "Specialized knowledge and workflow files that extend agent capabilities",
43
- key: (skill) => skill.name,
44
- validate: (skill) => {
42
+ key: skill => skill.name,
43
+ validate: skill => {
45
44
  if (!skill.name) return "Missing skill name";
46
45
  if (!skill.path) return "Missing skill path";
47
46
  return undefined;
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * File-based slash commands defined as markdown files.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -27,8 +26,8 @@ export const slashCommandCapability = defineCapability<SlashCommand>({
27
26
  id: "slash-commands",
28
27
  displayName: "Slash Commands",
29
28
  description: "Custom slash commands defined as markdown files",
30
- key: (cmd) => cmd.name,
31
- validate: (cmd) => {
29
+ key: cmd => cmd.name,
30
+ validate: cmd => {
32
31
  if (!cmd.name) return "Missing name";
33
32
  if (!cmd.path) return "Missing path";
34
33
  if (cmd.content === undefined) return "Missing content";
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * Canonical shape for SSH host entries, regardless of source format.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -33,8 +32,8 @@ export const sshCapability = defineCapability<SSHHost>({
33
32
  id: "ssh",
34
33
  displayName: "SSH Hosts",
35
34
  description: "SSH host entries for remote command execution",
36
- key: (host) => host.name,
37
- validate: (host) => {
35
+ key: host => host.name,
36
+ validate: host => {
38
37
  if (!host.name) return "Missing name";
39
38
  if (!host.host) return "Missing host";
40
39
  return undefined;
@@ -4,7 +4,6 @@
4
4
  * Custom system prompt files (SYSTEM.md) that modify the agent's base system prompt.
5
5
  * Distinct from context-files which are user instructions shown in conversation.
6
6
  */
7
-
8
7
  import { defineCapability } from ".";
9
8
  import type { SourceMeta } from "./types";
10
9
 
@@ -26,8 +25,8 @@ export const systemPromptCapability = defineCapability<SystemPrompt>({
26
25
  id: "system-prompt",
27
26
  displayName: "System Prompt",
28
27
  description: "Custom system prompt files (SYSTEM.md) that modify agent behavior",
29
- key: (sp) => sp.level,
30
- validate: (sp) => {
28
+ key: sp => sp.level,
29
+ validate: sp => {
31
30
  if (!sp.path) return "Missing path";
32
31
  if (sp.content === undefined) return "Missing content";
33
32
  return undefined;
@@ -3,7 +3,6 @@
3
3
  *
4
4
  * User-defined tools that extend agent capabilities.
5
5
  */
6
-
7
6
  import { defineCapability } from ".";
8
7
  import type { SourceMeta } from "./types";
9
8
 
@@ -29,8 +28,8 @@ export const toolCapability = defineCapability<CustomTool>({
29
28
  id: "tools",
30
29
  displayName: "Custom Tools",
31
30
  description: "User-defined tools that extend agent capabilities",
32
- key: (tool) => tool.name,
33
- validate: (tool) => {
31
+ key: tool => tool.name,
32
+ validate: tool => {
34
33
  if (!tool.name) return "Missing name";
35
34
  if (!tool.path) return "Missing path";
36
35
  return undefined;
package/src/cli/args.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * CLI argument parsing and help display
3
3
  */
4
-
5
4
  import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
6
- import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "@oh-my-pi/pi-coding-agent/config";
7
- import { BUILTIN_TOOLS } from "@oh-my-pi/pi-coding-agent/tools";
8
5
  import chalk from "chalk";
6
+ import { APP_NAME, CONFIG_DIR_NAME, ENV_AGENT_DIR } from "../config";
7
+ import { BUILTIN_TOOLS } from "../tools";
9
8
 
10
9
  export type Mode = "text" | "json" | "rpc";
11
10
 
@@ -99,13 +98,13 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
99
98
  } else if (arg === "--session-dir" && i + 1 < args.length) {
100
99
  result.sessionDir = args[++i];
101
100
  } else if (arg === "--models" && i + 1 < args.length) {
102
- result.models = args[++i].split(",").map((s) => s.trim());
101
+ result.models = args[++i].split(",").map(s => s.trim());
103
102
  } else if (arg === "--no-tools") {
104
103
  result.noTools = true;
105
104
  } else if (arg === "--no-lsp") {
106
105
  result.noLsp = true;
107
106
  } else if (arg === "--tools" && i + 1 < args.length) {
108
- const toolNames = args[++i].split(",").map((s) => s.trim());
107
+ const toolNames = args[++i].split(",").map(s => s.trim());
109
108
  const validTools: string[] = [];
110
109
  for (const name of toolNames) {
111
110
  if (name in BUILTIN_TOOLS) {
@@ -148,7 +147,7 @@ export function parseArgs(args: string[], extensionFlags?: Map<string, { type: "
148
147
  result.noTitle = true;
149
148
  } else if (arg === "--skills" && i + 1 < args.length) {
150
149
  // Comma-separated glob patterns for skill filtering
151
- result.skills = args[++i].split(",").map((s) => s.trim());
150
+ result.skills = args[++i].split(",").map(s => s.trim());
152
151
  } else if (arg === "--list-models") {
153
152
  // Check if next arg is a search pattern (not a flag or file arg)
154
153
  if (i + 1 < args.length && !args[i + 1].startsWith("-") && !args[i + 1].startsWith("@")) {
@@ -4,12 +4,11 @@
4
4
  * Handles `omp config <command>` subcommands for managing settings.
5
5
  * Uses SETTINGS_DEFS as the source of truth for available settings.
6
6
  */
7
-
8
- import { APP_NAME, getAgentDir } from "@oh-my-pi/pi-coding-agent/config";
9
- import { SettingsManager } from "@oh-my-pi/pi-coding-agent/config/settings-manager";
10
- import { SETTINGS_DEFS, type SettingDef } from "@oh-my-pi/pi-coding-agent/modes/components/settings-defs";
11
- import { theme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
12
7
  import chalk from "chalk";
8
+ import { APP_NAME, getAgentDir } from "../config";
9
+ import { SettingsManager } from "../config/settings-manager";
10
+ import { SETTINGS_DEFS, type SettingDef } from "../modes/components/settings-defs";
11
+ import { theme } from "../modes/theme/theme";
13
12
 
14
13
  // =============================================================================
15
14
  // Types
@@ -32,7 +31,7 @@ export interface ConfigCommandArgs {
32
31
 
33
32
  /** Find setting definition by ID */
34
33
  function findSettingDef(id: string): SettingDef | undefined {
35
- return SETTINGS_DEFS.find((def) => def.id === id);
34
+ return SETTINGS_DEFS.find(def => def.id === id);
36
35
  }
37
36
 
38
37
  /** Get available values for a setting */
@@ -43,7 +42,7 @@ function getSettingValues(def: SettingDef, sm: SettingsManager): readonly string
43
42
  if (def.type === "submenu") {
44
43
  const options = def.getOptions(sm);
45
44
  if (options.length > 0) {
46
- return options.map((o) => o.value);
45
+ return options.map(o => o.value);
47
46
  }
48
47
  }
49
48
  return undefined;
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Process @file CLI arguments into text content and image attachments
3
3
  */
4
-
5
- import { existsSync, readFileSync, statSync } from "node:fs";
6
- import { resolve } from "node:path";
4
+ import * as fs from "node:fs/promises";
5
+ import * as path from "node:path";
7
6
  import type { ImageContent } from "@oh-my-pi/pi-ai";
8
- import { resolveReadPath } from "@oh-my-pi/pi-coding-agent/tools/path-utils";
9
- import { formatDimensionNote, resizeImage } from "@oh-my-pi/pi-coding-agent/utils/image-resize";
10
- import { detectSupportedImageMimeTypeFromFile } from "@oh-my-pi/pi-coding-agent/utils/mime";
7
+ import { isEnoent } from "@oh-my-pi/pi-utils";
11
8
  import chalk from "chalk";
9
+ import { resolveReadPath } from "../tools/path-utils";
10
+ import { formatDimensionNote, resizeImage } from "../utils/image-resize";
11
+ import { detectSupportedImageMimeTypeFromFile } from "../utils/mime";
12
12
 
13
13
  export interface ProcessedFiles {
14
14
  text: string;
@@ -28,16 +28,20 @@ export async function processFileArguments(fileArgs: string[], options?: Process
28
28
 
29
29
  for (const fileArg of fileArgs) {
30
30
  // Expand and resolve path (handles ~ expansion and macOS screenshot Unicode spaces)
31
- const absolutePath = resolve(resolveReadPath(fileArg, process.cwd()));
31
+ const absolutePath = path.resolve(resolveReadPath(fileArg, process.cwd()));
32
32
 
33
- // Check if file exists and is not empty
34
- if (!existsSync(absolutePath)) {
35
- console.error(chalk.red(`Error: File not found: ${absolutePath}`));
36
- process.exit(1);
33
+ // Read file, handling not-found gracefully
34
+ let buffer: Buffer;
35
+ try {
36
+ buffer = await fs.readFile(absolutePath);
37
+ } catch (err) {
38
+ if (isEnoent(err)) {
39
+ console.error(chalk.red(`Error: File not found: ${absolutePath}`));
40
+ process.exit(1);
41
+ }
42
+ throw err;
37
43
  }
38
- const stats = statSync(absolutePath);
39
- if (stats.size === 0) {
40
- // Skip empty files
44
+ if (buffer.length === 0) {
41
45
  continue;
42
46
  }
43
47
 
@@ -45,9 +49,7 @@ export async function processFileArguments(fileArgs: string[], options?: Process
45
49
 
46
50
  if (mimeType) {
47
51
  // Handle image file
48
- const buffer = readFileSync(absolutePath);
49
52
  const base64Content = buffer.toString("base64");
50
-
51
53
  let attachment: ImageContent;
52
54
  let dimensionNote: string | undefined;
53
55
 
@@ -87,7 +89,7 @@ export async function processFileArguments(fileArgs: string[], options?: Process
87
89
  } else {
88
90
  // Handle text file
89
91
  try {
90
- const content = readFileSync(absolutePath, "utf-8");
92
+ const content = buffer.toString("utf-8");
91
93
  text += `<file name="${absolutePath}">\n${content}\n</file>\n`;
92
94
  } catch (error: unknown) {
93
95
  const message = error instanceof Error ? error.message : String(error);
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Jupyter CLI command handlers.
3
+ *
4
+ * Handles `omp jupyter` subcommand for managing the shared Python gateway.
5
+ */
6
+ import chalk from "chalk";
7
+ import { APP_NAME } from "../config";
8
+ import { getGatewayStatus, shutdownSharedGateway } from "../ipy/gateway-coordinator";
9
+
10
+ export type JupyterAction = "kill" | "status";
11
+
12
+ export interface JupyterCommandArgs {
13
+ action: JupyterAction;
14
+ }
15
+
16
+ export function parseJupyterArgs(args: string[]): JupyterCommandArgs | undefined {
17
+ if (args.length === 0 || args[0] !== "jupyter") {
18
+ return undefined;
19
+ }
20
+
21
+ const action = args[1] as JupyterAction | undefined;
22
+ if (!action || !["kill", "status"].includes(action)) {
23
+ return { action: "status" };
24
+ }
25
+
26
+ return { action };
27
+ }
28
+
29
+ export async function runJupyterCommand(cmd: JupyterCommandArgs): Promise<void> {
30
+ switch (cmd.action) {
31
+ case "kill":
32
+ await runKill();
33
+ break;
34
+ case "status":
35
+ await runStatus();
36
+ break;
37
+ }
38
+ }
39
+
40
+ async function runKill(): Promise<void> {
41
+ const status = await getGatewayStatus();
42
+
43
+ if (!status.active) {
44
+ console.log(chalk.dim("No Jupyter gateway is running"));
45
+ return;
46
+ }
47
+
48
+ console.log(`Killing Jupyter gateway (PID ${status.pid})...`);
49
+ await shutdownSharedGateway();
50
+ console.log(chalk.green("Jupyter gateway stopped"));
51
+ }
52
+
53
+ async function runStatus(): Promise<void> {
54
+ const status = await getGatewayStatus();
55
+
56
+ if (!status.active) {
57
+ console.log(chalk.dim("No Jupyter gateway is running"));
58
+ return;
59
+ }
60
+
61
+ console.log(chalk.bold("Jupyter Gateway Status\n"));
62
+ console.log(` ${chalk.green("●")} Running`);
63
+ console.log(` PID: ${status.pid}`);
64
+ console.log(` URL: ${status.url}`);
65
+ if (status.uptime !== null) {
66
+ console.log(` Uptime: ${formatUptime(status.uptime)}`);
67
+ }
68
+ if (status.pythonPath) {
69
+ console.log(` Python: ${status.pythonPath}`);
70
+ }
71
+ if (status.venvPath) {
72
+ console.log(` Venv: ${status.venvPath}`);
73
+ }
74
+ }
75
+
76
+ function formatUptime(ms: number): string {
77
+ const seconds = Math.floor(ms / 1000);
78
+ const minutes = Math.floor(seconds / 60);
79
+ const hours = Math.floor(minutes / 60);
80
+
81
+ if (hours > 0) {
82
+ return `${hours}h ${minutes % 60}m`;
83
+ }
84
+ if (minutes > 0) {
85
+ return `${minutes}m ${seconds % 60}s`;
86
+ }
87
+ return `${seconds}s`;
88
+ }
89
+
90
+ export function printJupyterHelp(): void {
91
+ console.log(`${chalk.bold(`${APP_NAME} jupyter`)} - Manage the shared Jupyter gateway
92
+
93
+ ${chalk.bold("Usage:")}
94
+ ${APP_NAME} jupyter <command>
95
+
96
+ ${chalk.bold("Commands:")}
97
+ status Show gateway status (default)
98
+ kill Stop the running gateway
99
+
100
+ ${chalk.bold("Examples:")}
101
+ ${APP_NAME} jupyter # Show status
102
+ ${APP_NAME} jupyter status # Show status
103
+ ${APP_NAME} jupyter kill # Stop the gateway
104
+ `);
105
+ }