jonsoc 1.1.50 → 1.1.51
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/AGENTS.md +27 -0
- package/Dockerfile +18 -0
- package/PUBLISHING_GUIDE.md +151 -0
- package/README.md +58 -0
- package/bin/jonsoc +256 -256
- package/bunfig.toml +7 -0
- package/package.json +142 -8
- package/package.json.placeholder +11 -0
- package/parsers-config.ts +253 -0
- package/script/build.ts +115 -0
- package/script/publish-registries.ts +197 -0
- package/script/publish.ts +149 -0
- package/script/schema.ts +47 -0
- package/script/seed-e2e.ts +50 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +1437 -0
- package/src/acp/session.ts +105 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +345 -0
- package/src/agent/generate.txt +75 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +18 -0
- package/src/agent/prompt/summary.txt +11 -0
- package/src/agent/prompt/title.txt +44 -0
- package/src/auth/index.ts +73 -0
- package/src/brand/index.ts +89 -0
- package/src/bun/index.ts +139 -0
- package/src/bus/bus-event.ts +43 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +105 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +69 -0
- package/src/cli/cmd/agent.ts +257 -0
- package/src/cli/cmd/auth.ts +405 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/agent.ts +166 -0
- package/src/cli/cmd/debug/config.ts +16 -0
- package/src/cli/cmd/debug/file.ts +97 -0
- package/src/cli/cmd/debug/index.ts +48 -0
- package/src/cli/cmd/debug/lsp.ts +52 -0
- package/src/cli/cmd/debug/ripgrep.ts +87 -0
- package/src/cli/cmd/debug/scrap.ts +16 -0
- package/src/cli/cmd/debug/skill.ts +16 -0
- package/src/cli/cmd/debug/snapshot.ts +52 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1547 -0
- package/src/cli/cmd/import.ts +99 -0
- package/src/cli/cmd/mcp.ts +765 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +395 -0
- package/src/cli/cmd/serve.ts +20 -0
- package/src/cli/cmd/session.ts +135 -0
- package/src/cli/cmd/stats.ts +402 -0
- package/src/cli/cmd/tui/app.tsx +923 -0
- package/src/cli/cmd/tui/attach.ts +39 -0
- package/src/cli/cmd/tui/component/border.tsx +21 -0
- package/src/cli/cmd/tui/component/dialog-agent.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-command.tsx +162 -0
- package/src/cli/cmd/tui/component/dialog-error-log.tsx +155 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +234 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +256 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +114 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-stash.tsx +87 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +164 -0
- package/src/cli/cmd/tui/component/dialog-tag.tsx +44 -0
- package/src/cli/cmd/tui/component/dialog-theme-list.tsx +50 -0
- package/src/cli/cmd/tui/component/dynamic-layout.tsx +86 -0
- package/src/cli/cmd/tui/component/inspector-overlay.tsx +247 -0
- package/src/cli/cmd/tui/component/logo.tsx +88 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +653 -0
- package/src/cli/cmd/tui/component/prompt/frecency.tsx +89 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +108 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +1347 -0
- package/src/cli/cmd/tui/component/prompt/stash.tsx +101 -0
- package/src/cli/cmd/tui/component/textarea-keybindings.ts +73 -0
- package/src/cli/cmd/tui/component/tips.tsx +153 -0
- package/src/cli/cmd/tui/component/todo-item.tsx +32 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +13 -0
- package/src/cli/cmd/tui/context/error-log.tsx +56 -0
- package/src/cli/cmd/tui/context/exit.tsx +26 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/inspector.tsx +57 -0
- package/src/cli/cmd/tui/context/keybind.tsx +108 -0
- package/src/cli/cmd/tui/context/kv.tsx +53 -0
- package/src/cli/cmd/tui/context/layout.tsx +240 -0
- package/src/cli/cmd/tui/context/local.tsx +402 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +51 -0
- package/src/cli/cmd/tui/context/sdk.tsx +94 -0
- package/src/cli/cmd/tui/context/sync.tsx +449 -0
- package/src/cli/cmd/tui/context/theme/aura.json +69 -0
- package/src/cli/cmd/tui/context/theme/ayu.json +80 -0
- package/src/cli/cmd/tui/context/theme/carbonfox.json +248 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-frappe.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin-macchiato.json +233 -0
- package/src/cli/cmd/tui/context/theme/catppuccin.json +112 -0
- package/src/cli/cmd/tui/context/theme/cobalt2.json +228 -0
- package/src/cli/cmd/tui/context/theme/cursor.json +249 -0
- package/src/cli/cmd/tui/context/theme/dracula.json +219 -0
- package/src/cli/cmd/tui/context/theme/everforest.json +241 -0
- package/src/cli/cmd/tui/context/theme/flexoki.json +237 -0
- package/src/cli/cmd/tui/context/theme/github.json +233 -0
- package/src/cli/cmd/tui/context/theme/gruvbox.json +242 -0
- package/src/cli/cmd/tui/context/theme/jonsoc.json +245 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -0
- package/src/cli/cmd/tui/context/theme/lucent-orng.json +237 -0
- package/src/cli/cmd/tui/context/theme/material.json +235 -0
- package/src/cli/cmd/tui/context/theme/matrix.json +77 -0
- package/src/cli/cmd/tui/context/theme/mercury.json +252 -0
- package/src/cli/cmd/tui/context/theme/monokai.json +221 -0
- package/src/cli/cmd/tui/context/theme/nightowl.json +221 -0
- package/src/cli/cmd/tui/context/theme/nord.json +223 -0
- package/src/cli/cmd/tui/context/theme/one-dark.json +84 -0
- package/src/cli/cmd/tui/context/theme/orng.json +249 -0
- package/src/cli/cmd/tui/context/theme/osaka-jade.json +93 -0
- package/src/cli/cmd/tui/context/theme/palenight.json +222 -0
- package/src/cli/cmd/tui/context/theme/rosepine.json +234 -0
- package/src/cli/cmd/tui/context/theme/solarized.json +223 -0
- package/src/cli/cmd/tui/context/theme/synthwave84.json +226 -0
- package/src/cli/cmd/tui/context/theme/tokyonight.json +243 -0
- package/src/cli/cmd/tui/context/theme/vercel.json +245 -0
- package/src/cli/cmd/tui/context/theme/vesper.json +218 -0
- package/src/cli/cmd/tui/context/theme/zenburn.json +223 -0
- package/src/cli/cmd/tui/context/theme.tsx +1152 -0
- package/src/cli/cmd/tui/event.ts +48 -0
- package/src/cli/cmd/tui/hooks/use-command-registry.tsx +184 -0
- package/src/cli/cmd/tui/routes/home.tsx +198 -0
- package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +64 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +109 -0
- package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +26 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +47 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +91 -0
- package/src/cli/cmd/tui/routes/session/git-commit.tsx +59 -0
- package/src/cli/cmd/tui/routes/session/git-history.tsx +122 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +185 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +2363 -0
- package/src/cli/cmd/tui/routes/session/navigator-ui.tsx +214 -0
- package/src/cli/cmd/tui/routes/session/navigator.tsx +1124 -0
- package/src/cli/cmd/tui/routes/session/panel-explorer.tsx +553 -0
- package/src/cli/cmd/tui/routes/session/panel-viewer.tsx +386 -0
- package/src/cli/cmd/tui/routes/session/permission.tsx +501 -0
- package/src/cli/cmd/tui/routes/session/question.tsx +507 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +365 -0
- package/src/cli/cmd/tui/routes/session/vcs-diff-viewer.tsx +37 -0
- package/src/cli/cmd/tui/routes/ui-settings.tsx +449 -0
- package/src/cli/cmd/tui/thread.ts +172 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +90 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +83 -0
- package/src/cli/cmd/tui/ui/dialog-export-options.tsx +204 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +38 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +77 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +384 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/link.tsx +28 -0
- package/src/cli/cmd/tui/ui/spinner.ts +375 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +255 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/signal.ts +7 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/util/transcript.ts +98 -0
- package/src/cli/cmd/tui/worker.ts +152 -0
- package/src/cli/cmd/uninstall.ts +362 -0
- package/src/cli/cmd/upgrade.ts +73 -0
- package/src/cli/cmd/web.ts +81 -0
- package/src/cli/error.ts +57 -0
- package/src/cli/network.ts +53 -0
- package/src/cli/ui.ts +119 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +131 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +99 -0
- package/src/config/config.ts +1404 -0
- package/src/config/markdown.ts +93 -0
- package/src/env/index.ts +26 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +432 -0
- package/src/file/ripgrep.ts +407 -0
- package/src/file/time.ts +69 -0
- package/src/file/watcher.ts +127 -0
- package/src/flag/flag.ts +80 -0
- package/src/format/formatter.ts +357 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +58 -0
- package/src/id/id.ts +83 -0
- package/src/ide/index.ts +76 -0
- package/src/index.ts +208 -0
- package/src/installation/index.ts +258 -0
- package/src/lsp/client.ts +252 -0
- package/src/lsp/index.ts +485 -0
- package/src/lsp/language.ts +119 -0
- package/src/lsp/server.ts +2046 -0
- package/src/mcp/auth.ts +135 -0
- package/src/mcp/index.ts +934 -0
- package/src/mcp/oauth-callback.ts +200 -0
- package/src/mcp/oauth-provider.ts +155 -0
- package/src/patch/index.ts +680 -0
- package/src/permission/arity.ts +163 -0
- package/src/permission/index.ts +210 -0
- package/src/permission/next.ts +280 -0
- package/src/plugin/codex.ts +500 -0
- package/src/plugin/copilot.ts +283 -0
- package/src/plugin/index.ts +135 -0
- package/src/project/bootstrap.ts +35 -0
- package/src/project/instance.ts +91 -0
- package/src/project/project.ts +371 -0
- package/src/project/state.ts +66 -0
- package/src/project/vcs.ts +151 -0
- package/src/provider/auth.ts +147 -0
- package/src/provider/models-macro.ts +14 -0
- package/src/provider/models.ts +114 -0
- package/src/provider/provider.ts +1220 -0
- package/src/provider/sdk/openai-compatible/src/README.md +5 -0
- package/src/provider/sdk/openai-compatible/src/index.ts +2 -0
- package/src/provider/sdk/openai-compatible/src/openai-compatible-provider.ts +100 -0
- package/src/provider/sdk/openai-compatible/src/responses/convert-to-openai-responses-input.ts +303 -0
- package/src/provider/sdk/openai-compatible/src/responses/map-openai-responses-finish-reason.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-config.ts +18 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-error.ts +22 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-api-types.ts +207 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-language-model.ts +1732 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-prepare-tools.ts +177 -0
- package/src/provider/sdk/openai-compatible/src/responses/openai-responses-settings.ts +1 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/code-interpreter.ts +88 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/file-search.ts +128 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/image-generation.ts +115 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/local-shell.ts +65 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search-preview.ts +104 -0
- package/src/provider/sdk/openai-compatible/src/responses/tool/web-search.ts +103 -0
- package/src/provider/transform.ts +742 -0
- package/src/pty/index.ts +241 -0
- package/src/question/index.ts +176 -0
- package/src/scheduler/index.ts +61 -0
- package/src/server/error.ts +36 -0
- package/src/server/event.ts +7 -0
- package/src/server/mdns.ts +59 -0
- package/src/server/routes/config.ts +92 -0
- package/src/server/routes/experimental.ts +208 -0
- package/src/server/routes/file.ts +227 -0
- package/src/server/routes/global.ts +135 -0
- package/src/server/routes/mcp.ts +225 -0
- package/src/server/routes/permission.ts +68 -0
- package/src/server/routes/project.ts +82 -0
- package/src/server/routes/provider.ts +165 -0
- package/src/server/routes/pty.ts +169 -0
- package/src/server/routes/question.ts +98 -0
- package/src/server/routes/session.ts +939 -0
- package/src/server/routes/tui.ts +379 -0
- package/src/server/server.ts +663 -0
- package/src/session/compaction.ts +225 -0
- package/src/session/index.ts +498 -0
- package/src/session/llm.ts +288 -0
- package/src/session/message-v2.ts +740 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +406 -0
- package/src/session/prompt/anthropic-20250930.txt +168 -0
- package/src/session/prompt/anthropic.txt +172 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +149 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex_header.txt +81 -0
- package/src/session/prompt/copilot-gpt-5.txt +145 -0
- package/src/session/prompt/gemini.txt +157 -0
- package/src/session/prompt/max-steps.txt +16 -0
- package/src/session/prompt/plan-reminder-anthropic.txt +67 -0
- package/src/session/prompt/plan.txt +26 -0
- package/src/session/prompt/qwen.txt +111 -0
- package/src/session/prompt.ts +1815 -0
- package/src/session/retry.ts +90 -0
- package/src/session/revert.ts +121 -0
- package/src/session/status.ts +76 -0
- package/src/session/summary.ts +150 -0
- package/src/session/system.ts +156 -0
- package/src/session/todo.ts +37 -0
- package/src/share/share-next.ts +205 -0
- package/src/share/share.ts +95 -0
- package/src/shell/shell.ts +67 -0
- package/src/skill/index.ts +1 -0
- package/src/skill/skill.ts +135 -0
- package/src/snapshot/index.ts +236 -0
- package/src/storage/storage.ts +227 -0
- package/src/tool/apply_patch.ts +279 -0
- package/src/tool/apply_patch.txt +33 -0
- package/src/tool/bash.ts +258 -0
- package/src/tool/bash.txt +115 -0
- package/src/tool/batch.ts +175 -0
- package/src/tool/batch.txt +24 -0
- package/src/tool/codesearch.ts +132 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +645 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/external-directory.ts +32 -0
- package/src/tool/glob.ts +77 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +154 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +121 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp.ts +96 -0
- package/src/tool/lsp.txt +19 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/plan-enter.txt +14 -0
- package/src/tool/plan-exit.txt +13 -0
- package/src/tool/plan.ts +130 -0
- package/src/tool/question.ts +33 -0
- package/src/tool/question.txt +10 -0
- package/src/tool/read.ts +202 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +162 -0
- package/src/tool/skill.ts +82 -0
- package/src/tool/task.ts +188 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +53 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +88 -0
- package/src/tool/truncation.ts +106 -0
- package/src/tool/webfetch.ts +182 -0
- package/src/tool/webfetch.txt +13 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +14 -0
- package/src/tool/write.ts +80 -0
- package/src/tool/write.txt +8 -0
- package/src/util/archive.ts +16 -0
- package/src/util/color.ts +19 -0
- package/src/util/context.ts +25 -0
- package/src/util/defer.ts +12 -0
- package/src/util/eventloop.ts +20 -0
- package/src/util/filesystem.ts +93 -0
- package/src/util/fn.ts +11 -0
- package/src/util/format.ts +20 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +103 -0
- package/src/util/lazy.ts +18 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +180 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +66 -0
- package/src/util/scrap.ts +10 -0
- package/src/util/signal.ts +12 -0
- package/src/util/timeout.ts +14 -0
- package/src/util/token.ts +7 -0
- package/src/util/wildcard.ts +56 -0
- package/src/worktree/index.ts +524 -0
- package/sst-env.d.ts +9 -0
- package/test/acp/agent-interface.test.ts +51 -0
- package/test/acp/event-subscription.test.ts +436 -0
- package/test/agent/agent.test.ts +638 -0
- package/test/bun.test.ts +53 -0
- package/test/cli/cmd/tui/fileref.test.ts +30 -0
- package/test/cli/github-action.test.ts +129 -0
- package/test/cli/github-remote.test.ts +80 -0
- package/test/cli/tui/navigator_logic.test.ts +99 -0
- package/test/cli/tui/transcript.test.ts +297 -0
- package/test/cli/ui.test.ts +80 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +1613 -0
- package/test/config/fixtures/empty-frontmatter.md +4 -0
- package/test/config/fixtures/frontmatter.md +28 -0
- package/test/config/fixtures/no-frontmatter.md +1 -0
- package/test/config/markdown.test.ts +192 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/file/path-traversal.test.ts +198 -0
- package/test/fixture/fixture.ts +45 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +421 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/mcp/headers.test.ts +153 -0
- package/test/mcp/oauth-browser.test.ts +261 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/permission/arity.test.ts +33 -0
- package/test/permission/next.test.ts +690 -0
- package/test/permission-task.test.ts +319 -0
- package/test/plugin/codex.test.ts +123 -0
- package/test/preload.ts +67 -0
- package/test/project/project.test.ts +120 -0
- package/test/provider/amazon-bedrock.test.ts +268 -0
- package/test/provider/gitlab-duo.test.ts +286 -0
- package/test/provider/provider.test.ts +2149 -0
- package/test/provider/transform.test.ts +1631 -0
- package/test/question/question.test.ts +300 -0
- package/test/scheduler.test.ts +73 -0
- package/test/server/session-list.test.ts +39 -0
- package/test/server/session-select.test.ts +78 -0
- package/test/session/compaction.test.ts +293 -0
- package/test/session/llm.test.ts +90 -0
- package/test/session/message-v2.test.ts +786 -0
- package/test/session/retry.test.ts +131 -0
- package/test/session/revert-compact.test.ts +285 -0
- package/test/session/session.test.ts +71 -0
- package/test/skill/skill.test.ts +185 -0
- package/test/snapshot/snapshot.test.ts +939 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/apply_patch.test.ts +499 -0
- package/test/tool/bash.test.ts +320 -0
- package/test/tool/external-directory.test.ts +126 -0
- package/test/tool/fixtures/large-image.png +0 -0
- package/test/tool/fixtures/models-api.json +33453 -0
- package/test/tool/grep.test.ts +109 -0
- package/test/tool/question.test.ts +105 -0
- package/test/tool/read.test.ts +332 -0
- package/test/tool/registry.test.ts +76 -0
- package/test/tool/truncation.test.ts +159 -0
- package/test/util/filesystem.test.ts +39 -0
- package/test/util/format.test.ts +59 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/lock.test.ts +72 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +75 -0
- package/tsconfig.json +16 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
import { createSignal, createMemo, For, Show } from "solid-js"
|
|
2
|
+
import { useRoute, useRouteData } from "@tui/context/route"
|
|
3
|
+
import { useLayout, type PanelType } from "@tui/context/layout"
|
|
4
|
+
import { useTheme } from "@tui/context/theme"
|
|
5
|
+
import { useTerminalDimensions } from "@opentui/solid"
|
|
6
|
+
import { TextAttributes, RGBA } from "@opentui/core"
|
|
7
|
+
import { useKeyboard } from "@opentui/solid"
|
|
8
|
+
|
|
9
|
+
const PANEL_COLORS: Record<PanelType, [number, number, number]> = {
|
|
10
|
+
chat: [100, 200, 100],
|
|
11
|
+
explorer: [255, 200, 100],
|
|
12
|
+
viewer: [100, 150, 255],
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getPanelColor(type: PanelType, alpha: number): RGBA {
|
|
16
|
+
const [r, g, b] = PANEL_COLORS[type]
|
|
17
|
+
return RGBA.fromInts(r, g, b, Math.floor(alpha * 255))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const PANEL_NAMES: Record<PanelType, string> = {
|
|
21
|
+
chat: "CHAT",
|
|
22
|
+
explorer: "EXPLORER",
|
|
23
|
+
viewer: "VIEWER",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const PANEL_DESC: Record<PanelType, string> = {
|
|
27
|
+
chat: "Conversation & Prompt",
|
|
28
|
+
explorer: "File Tree & Git Status",
|
|
29
|
+
viewer: "File Editor & Preview",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function UISettings() {
|
|
33
|
+
const route = useRouteData("ui-settings")
|
|
34
|
+
const { navigate } = useRoute()
|
|
35
|
+
const layout = useLayout()
|
|
36
|
+
const { theme } = useTheme()
|
|
37
|
+
const dimensions = useTerminalDimensions()
|
|
38
|
+
|
|
39
|
+
// Click-to-swap state
|
|
40
|
+
const [selectedPanel, setSelectedPanel] = createSignal<PanelType | null>(null)
|
|
41
|
+
|
|
42
|
+
// Width adjustment step
|
|
43
|
+
const WIDTH_STEP = 5
|
|
44
|
+
|
|
45
|
+
const safeDimensions = createMemo(() => {
|
|
46
|
+
const dims = dimensions()
|
|
47
|
+
return {
|
|
48
|
+
width: dims?.width ?? 80,
|
|
49
|
+
height: dims?.height ?? 24,
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Preview dimensions: match terminal aspect ratio exactly
|
|
54
|
+
const previewDimensions = createMemo(() => {
|
|
55
|
+
const termWidth = safeDimensions().width - 4 // Account for padding
|
|
56
|
+
const termHeight = safeDimensions().height - 8 // Account for header/footer
|
|
57
|
+
|
|
58
|
+
// Use 60% of available space for preview to leave room for controls
|
|
59
|
+
const maxWidth = Math.floor(termWidth * 0.6)
|
|
60
|
+
const maxHeight = Math.floor(termHeight * 0.6)
|
|
61
|
+
|
|
62
|
+
// Calculate aspect ratio of terminal
|
|
63
|
+
const aspectRatio = termWidth / termHeight
|
|
64
|
+
|
|
65
|
+
// Calculate preview dimensions maintaining exact aspect ratio
|
|
66
|
+
let previewWidth = maxWidth
|
|
67
|
+
let previewHeight = Math.floor(previewWidth / aspectRatio)
|
|
68
|
+
|
|
69
|
+
// If height exceeds max, scale down proportionally
|
|
70
|
+
if (previewHeight > maxHeight) {
|
|
71
|
+
previewHeight = maxHeight
|
|
72
|
+
previewWidth = Math.floor(previewHeight * aspectRatio)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Ensure minimum dimensions
|
|
76
|
+
previewWidth = Math.max(30, previewWidth)
|
|
77
|
+
previewHeight = Math.max(8, previewHeight)
|
|
78
|
+
|
|
79
|
+
return { width: previewWidth, height: previewHeight }
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Get visible panels sorted by position for layout preview
|
|
83
|
+
const visiblePanels = createMemo(() => layout.panels.filter((p) => p.visible).sort((a, b) => a.position - b.position))
|
|
84
|
+
|
|
85
|
+
// Calculate proportional width for each visible panel in the preview
|
|
86
|
+
const getPanelPreviewWidth = (panel: { visible: boolean; width: number }) => {
|
|
87
|
+
if (!panel.visible) return 0
|
|
88
|
+
const totalVisibleWidth = visiblePanels().reduce((sum, p) => sum + p.width, 0)
|
|
89
|
+
if (totalVisibleWidth === 0) return 0
|
|
90
|
+
// Proportional width based on visible panels only
|
|
91
|
+
return Math.floor((previewDimensions().width * panel.width) / totalVisibleWidth)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Handle click-to-swap panel selection
|
|
95
|
+
const handlePanelClick = (type: PanelType) => {
|
|
96
|
+
const currentSelected = selectedPanel()
|
|
97
|
+
|
|
98
|
+
if (currentSelected === null) {
|
|
99
|
+
// First click - select this panel
|
|
100
|
+
setSelectedPanel(type)
|
|
101
|
+
} else if (currentSelected === type) {
|
|
102
|
+
// Clicked same panel - deselect
|
|
103
|
+
setSelectedPanel(null)
|
|
104
|
+
} else {
|
|
105
|
+
// Clicked different panel - swap them
|
|
106
|
+
const panel1 = layout.getPanelByType(currentSelected)
|
|
107
|
+
const panel2 = layout.getPanelByType(type)
|
|
108
|
+
if (panel1 && panel2) {
|
|
109
|
+
layout.swapPanels(panel1.position, panel2.position)
|
|
110
|
+
}
|
|
111
|
+
setSelectedPanel(null)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle width adjustment with +/- buttons - transfers width between adjacent panels
|
|
116
|
+
const adjustWidth = (type: PanelType, direction: "minus" | "plus") => {
|
|
117
|
+
const panel = layout.getPanelByType(type)
|
|
118
|
+
if (!panel || !panel.visible) return
|
|
119
|
+
|
|
120
|
+
// Find adjacent visible panels
|
|
121
|
+
const sortedVisible = layout.panels.filter((p) => p.visible).sort((a, b) => a.position - b.position)
|
|
122
|
+
const panelIndex = sortedVisible.findIndex((p) => p.type === type)
|
|
123
|
+
|
|
124
|
+
if (panelIndex === -1) return
|
|
125
|
+
|
|
126
|
+
if (direction === "minus") {
|
|
127
|
+
// Decreasing: give space to the panel on the right (if exists), otherwise left
|
|
128
|
+
const recipientIndex = panelIndex < sortedVisible.length - 1 ? panelIndex + 1 : panelIndex - 1
|
|
129
|
+
if (recipientIndex < 0 || recipientIndex >= sortedVisible.length) return
|
|
130
|
+
|
|
131
|
+
const recipient = sortedVisible[recipientIndex]
|
|
132
|
+
const canDecrease = panel.width - 5 >= WIDTH_STEP
|
|
133
|
+
const canReceive = recipient.width + WIDTH_STEP <= 90
|
|
134
|
+
|
|
135
|
+
if (canDecrease && canReceive) {
|
|
136
|
+
layout.setPanelWidth(type, panel.width - WIDTH_STEP)
|
|
137
|
+
layout.setPanelWidth(recipient.type, recipient.width + WIDTH_STEP)
|
|
138
|
+
}
|
|
139
|
+
} else {
|
|
140
|
+
// Increasing: take space from the panel on the right (if exists), otherwise left
|
|
141
|
+
const donorIndex = panelIndex < sortedVisible.length - 1 ? panelIndex + 1 : panelIndex - 1
|
|
142
|
+
if (donorIndex < 0 || donorIndex >= sortedVisible.length) return
|
|
143
|
+
|
|
144
|
+
const donor = sortedVisible[donorIndex]
|
|
145
|
+
const canIncrease = panel.width + WIDTH_STEP <= 90
|
|
146
|
+
const canGive = donor.width - 5 >= WIDTH_STEP
|
|
147
|
+
|
|
148
|
+
if (canIncrease && canGive) {
|
|
149
|
+
layout.setPanelWidth(type, panel.width + WIDTH_STEP)
|
|
150
|
+
layout.setPanelWidth(donor.type, donor.width - WIDTH_STEP)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const totalPercent = createMemo(() => layout.panels.reduce((sum, p) => sum + (p.visible ? p.width : 0), 0))
|
|
156
|
+
|
|
157
|
+
const isLayoutValid = createMemo(() => totalPercent() === 100)
|
|
158
|
+
|
|
159
|
+
const handleReturn = () => {
|
|
160
|
+
// Only allow exit if layout totals exactly 100%
|
|
161
|
+
if (!isLayoutValid()) return
|
|
162
|
+
|
|
163
|
+
if (route.returnTo) {
|
|
164
|
+
navigate(route.returnTo)
|
|
165
|
+
} else {
|
|
166
|
+
navigate({ type: "home" })
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const handleReset = () => {
|
|
171
|
+
layout.resetToDefault()
|
|
172
|
+
setSelectedPanel(null)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
useKeyboard((e) => {
|
|
176
|
+
if (e.name === "escape") {
|
|
177
|
+
setSelectedPanel(null)
|
|
178
|
+
return
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<box
|
|
184
|
+
width={safeDimensions().width}
|
|
185
|
+
height={safeDimensions().height}
|
|
186
|
+
flexDirection="column"
|
|
187
|
+
backgroundColor={theme.background}
|
|
188
|
+
>
|
|
189
|
+
{/* Header */}
|
|
190
|
+
<box
|
|
191
|
+
flexShrink={0}
|
|
192
|
+
paddingTop={1}
|
|
193
|
+
paddingBottom={1}
|
|
194
|
+
paddingLeft={2}
|
|
195
|
+
paddingRight={2}
|
|
196
|
+
flexDirection="row"
|
|
197
|
+
justifyContent="space-between"
|
|
198
|
+
alignItems="center"
|
|
199
|
+
border={["bottom"]}
|
|
200
|
+
borderColor={theme.border}
|
|
201
|
+
>
|
|
202
|
+
<box flexDirection="row" gap={1} alignItems="center">
|
|
203
|
+
<text fg={theme.text} attributes={TextAttributes.BOLD}>
|
|
204
|
+
Layout Configuration
|
|
205
|
+
</text>
|
|
206
|
+
<Show when={selectedPanel()}>
|
|
207
|
+
<text fg={theme.warning}>(Selected: {PANEL_NAMES[selectedPanel()!]} - click another to swap)</text>
|
|
208
|
+
</Show>
|
|
209
|
+
</box>
|
|
210
|
+
<box flexDirection="row" gap={3}>
|
|
211
|
+
<text fg={theme.textMuted} onMouseUp={handleReset}>
|
|
212
|
+
[Reset]
|
|
213
|
+
</text>
|
|
214
|
+
<text
|
|
215
|
+
fg={isLayoutValid() ? theme.primary : theme.textMuted}
|
|
216
|
+
attributes={isLayoutValid() ? TextAttributes.BOLD : undefined}
|
|
217
|
+
onMouseUp={handleReturn}
|
|
218
|
+
>
|
|
219
|
+
{isLayoutValid() ? "[Done]" : "[Done] (needs 100%)"}
|
|
220
|
+
</text>
|
|
221
|
+
</box>
|
|
222
|
+
</box>
|
|
223
|
+
|
|
224
|
+
{/* Instructions */}
|
|
225
|
+
<box
|
|
226
|
+
flexShrink={0}
|
|
227
|
+
paddingTop={1}
|
|
228
|
+
paddingBottom={1}
|
|
229
|
+
paddingLeft={2}
|
|
230
|
+
paddingRight={2}
|
|
231
|
+
backgroundColor={theme.backgroundPanel}
|
|
232
|
+
>
|
|
233
|
+
<text fg={theme.textMuted}>
|
|
234
|
+
Click panels to select and swap positions. Use +/- to adjust widths. Toggle visibility below.
|
|
235
|
+
</text>
|
|
236
|
+
</box>
|
|
237
|
+
|
|
238
|
+
{/* Layout Preview */}
|
|
239
|
+
<box
|
|
240
|
+
flexShrink={0}
|
|
241
|
+
paddingTop={1}
|
|
242
|
+
paddingBottom={1}
|
|
243
|
+
paddingLeft={2}
|
|
244
|
+
paddingRight={2}
|
|
245
|
+
flexDirection="column"
|
|
246
|
+
gap={1}
|
|
247
|
+
>
|
|
248
|
+
<box
|
|
249
|
+
width={previewDimensions().width}
|
|
250
|
+
height={previewDimensions().height}
|
|
251
|
+
flexDirection="row"
|
|
252
|
+
border={["left", "right", "top", "bottom"]}
|
|
253
|
+
borderColor={theme.border}
|
|
254
|
+
padding={0}
|
|
255
|
+
gap={0}
|
|
256
|
+
>
|
|
257
|
+
<For each={visiblePanels()}>
|
|
258
|
+
{(panel) => {
|
|
259
|
+
const isSelected = createMemo(() => selectedPanel() === panel.type)
|
|
260
|
+
const canSwapHere = createMemo(() => {
|
|
261
|
+
const selected = selectedPanel()
|
|
262
|
+
if (!selected) return false
|
|
263
|
+
const selectedPanelObj = layout.getPanelByType(selected)
|
|
264
|
+
return selectedPanelObj && selectedPanelObj.position !== panel.position
|
|
265
|
+
})
|
|
266
|
+
const width = createMemo(() => getPanelPreviewWidth(panel))
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<box
|
|
270
|
+
width={width()}
|
|
271
|
+
height="100%"
|
|
272
|
+
backgroundColor={getPanelColor(panel.type, isSelected() ? 0.7 : 1.0)}
|
|
273
|
+
border={
|
|
274
|
+
isSelected()
|
|
275
|
+
? ["left", "right", "top", "bottom"]
|
|
276
|
+
: canSwapHere()
|
|
277
|
+
? ["left", "right", "top", "bottom"]
|
|
278
|
+
: []
|
|
279
|
+
}
|
|
280
|
+
borderColor={isSelected() ? theme.warning : canSwapHere() ? theme.success : theme.border}
|
|
281
|
+
flexDirection="column"
|
|
282
|
+
justifyContent="center"
|
|
283
|
+
alignItems="center"
|
|
284
|
+
gap={1}
|
|
285
|
+
onMouseUp={() => handlePanelClick(panel.type)}
|
|
286
|
+
>
|
|
287
|
+
{/* Panel Header */}
|
|
288
|
+
<box
|
|
289
|
+
paddingLeft={1}
|
|
290
|
+
paddingRight={1}
|
|
291
|
+
paddingTop={0}
|
|
292
|
+
paddingBottom={0}
|
|
293
|
+
backgroundColor={theme.background}
|
|
294
|
+
>
|
|
295
|
+
<text fg={getPanelColor(panel.type, 1.0)} attributes={TextAttributes.BOLD}>
|
|
296
|
+
{PANEL_NAMES[panel.type]}
|
|
297
|
+
</text>
|
|
298
|
+
</box>
|
|
299
|
+
|
|
300
|
+
{/* Description */}
|
|
301
|
+
<text fg={theme.text}>{PANEL_DESC[panel.type]}</text>
|
|
302
|
+
|
|
303
|
+
{/* Selected indicator */}
|
|
304
|
+
<Show when={isSelected()}>
|
|
305
|
+
<box marginTop={1} paddingLeft={1} paddingRight={1} backgroundColor={theme.warning}>
|
|
306
|
+
<text fg={theme.background} attributes={TextAttributes.BOLD}>
|
|
307
|
+
Selected (click another to swap)
|
|
308
|
+
</text>
|
|
309
|
+
</box>
|
|
310
|
+
</Show>
|
|
311
|
+
|
|
312
|
+
{/* Swap hint */}
|
|
313
|
+
<Show when={canSwapHere() && !isSelected()}>
|
|
314
|
+
<box marginTop={1} paddingLeft={1} paddingRight={1} backgroundColor={theme.success}>
|
|
315
|
+
<text fg={theme.background} attributes={TextAttributes.BOLD}>
|
|
316
|
+
Click to swap
|
|
317
|
+
</text>
|
|
318
|
+
</box>
|
|
319
|
+
</Show>
|
|
320
|
+
|
|
321
|
+
{/* Hide button */}
|
|
322
|
+
<box
|
|
323
|
+
marginTop={2}
|
|
324
|
+
paddingLeft={2}
|
|
325
|
+
paddingRight={2}
|
|
326
|
+
paddingTop={0}
|
|
327
|
+
paddingBottom={0}
|
|
328
|
+
backgroundColor={theme.backgroundElement}
|
|
329
|
+
onMouseUp={(e) => {
|
|
330
|
+
e.stopPropagation()
|
|
331
|
+
layout.togglePanel(panel.type)
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
<text fg={theme.textMuted}>[Hide]</text>
|
|
335
|
+
</box>
|
|
336
|
+
</box>
|
|
337
|
+
)
|
|
338
|
+
}}
|
|
339
|
+
</For>
|
|
340
|
+
|
|
341
|
+
{/* Show hidden panels as thin collapsed strips */}
|
|
342
|
+
<For each={layout.panels.filter((p) => !p.visible)}>
|
|
343
|
+
{(panel) => {
|
|
344
|
+
const isSelected = createMemo(() => selectedPanel() === panel.type)
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<box
|
|
348
|
+
width={3}
|
|
349
|
+
height="100%"
|
|
350
|
+
backgroundColor={theme.backgroundElement}
|
|
351
|
+
border={isSelected() ? ["left", "right", "top", "bottom"] : ["left"]}
|
|
352
|
+
borderColor={isSelected() ? theme.warning : theme.border}
|
|
353
|
+
flexDirection="column"
|
|
354
|
+
justifyContent="center"
|
|
355
|
+
alignItems="center"
|
|
356
|
+
onMouseUp={() => {
|
|
357
|
+
// Clicking hidden panel while another is selected just deselects
|
|
358
|
+
if (selectedPanel()) {
|
|
359
|
+
setSelectedPanel(null)
|
|
360
|
+
}
|
|
361
|
+
}}
|
|
362
|
+
>
|
|
363
|
+
{/* Vertical text for hidden panel */}
|
|
364
|
+
<text fg={theme.textMuted} attributes={TextAttributes.BOLD}>
|
|
365
|
+
{PANEL_NAMES[panel.type][0]}
|
|
366
|
+
</text>
|
|
367
|
+
<Show when={isSelected()}>
|
|
368
|
+
<box marginTop={1} backgroundColor={theme.warning}>
|
|
369
|
+
<text fg={theme.background}>!</text>
|
|
370
|
+
</box>
|
|
371
|
+
</Show>
|
|
372
|
+
</box>
|
|
373
|
+
)
|
|
374
|
+
}}
|
|
375
|
+
</For>
|
|
376
|
+
</box>
|
|
377
|
+
|
|
378
|
+
{/* Legend with Visibility Toggles - One panel per line */}
|
|
379
|
+
<box flexDirection="column" gap={1} paddingTop={1} paddingLeft={1} paddingRight={1}>
|
|
380
|
+
<For each={layout.panels}>
|
|
381
|
+
{(panel) => (
|
|
382
|
+
<box flexDirection="row" gap={2} alignItems="center">
|
|
383
|
+
{/* Panel header */}
|
|
384
|
+
<box flexDirection="row" gap={1} alignItems="center" width={16}>
|
|
385
|
+
<box width={3} height={1} backgroundColor={getPanelColor(panel.type, 1.0)} />
|
|
386
|
+
<text fg={theme.text}>{PANEL_NAMES[panel.type]}</text>
|
|
387
|
+
</box>
|
|
388
|
+
|
|
389
|
+
{/* Visibility toggle */}
|
|
390
|
+
<box
|
|
391
|
+
paddingLeft={1}
|
|
392
|
+
paddingRight={1}
|
|
393
|
+
backgroundColor={panel.visible ? theme.success : theme.backgroundElement}
|
|
394
|
+
onMouseUp={() => layout.togglePanel(panel.type)}
|
|
395
|
+
>
|
|
396
|
+
<text fg={panel.visible ? theme.background : theme.textMuted}>
|
|
397
|
+
{panel.visible ? "[show]" : "[hide]"}
|
|
398
|
+
</text>
|
|
399
|
+
</box>
|
|
400
|
+
|
|
401
|
+
{/* Width controls with +/- (only for visible panels) */}
|
|
402
|
+
<Show when={panel.visible}>
|
|
403
|
+
<box flexDirection="row" gap={1} alignItems="center">
|
|
404
|
+
<text
|
|
405
|
+
fg={theme.primary}
|
|
406
|
+
onMouseUp={(e) => {
|
|
407
|
+
e.stopPropagation()
|
|
408
|
+
adjustWidth(panel.type, "minus")
|
|
409
|
+
}}
|
|
410
|
+
>
|
|
411
|
+
[-]
|
|
412
|
+
</text>
|
|
413
|
+
<text
|
|
414
|
+
fg={theme.primary}
|
|
415
|
+
onMouseUp={(e) => {
|
|
416
|
+
e.stopPropagation()
|
|
417
|
+
adjustWidth(panel.type, "plus")
|
|
418
|
+
}}
|
|
419
|
+
>
|
|
420
|
+
[+]
|
|
421
|
+
</text>
|
|
422
|
+
</box>
|
|
423
|
+
</Show>
|
|
424
|
+
</box>
|
|
425
|
+
)}
|
|
426
|
+
</For>
|
|
427
|
+
</box>
|
|
428
|
+
</box>
|
|
429
|
+
|
|
430
|
+
{/* Footer */}
|
|
431
|
+
<box
|
|
432
|
+
flexShrink={0}
|
|
433
|
+
paddingTop={1}
|
|
434
|
+
paddingBottom={1}
|
|
435
|
+
paddingLeft={2}
|
|
436
|
+
paddingRight={2}
|
|
437
|
+
border={["top"]}
|
|
438
|
+
borderColor={theme.border}
|
|
439
|
+
flexDirection="row"
|
|
440
|
+
justifyContent="space-between"
|
|
441
|
+
>
|
|
442
|
+
<text fg={theme.textMuted}>Click panels to swap. [Done] to apply. ESC to cancel.</text>
|
|
443
|
+
<text fg={theme.textMuted}>
|
|
444
|
+
Total: {Math.round(layout.panels.reduce((sum, p) => sum + (p.visible ? p.width : 0), 0))}%
|
|
445
|
+
</text>
|
|
446
|
+
</box>
|
|
447
|
+
</box>
|
|
448
|
+
)
|
|
449
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { cmd } from "@/cli/cmd/cmd"
|
|
2
|
+
import { tui } from "./app"
|
|
3
|
+
import { Rpc } from "@/util/rpc"
|
|
4
|
+
import { type rpc } from "./worker"
|
|
5
|
+
import path from "path"
|
|
6
|
+
import { UI } from "@/cli/ui"
|
|
7
|
+
import { iife } from "@/util/iife"
|
|
8
|
+
import { Log } from "@/util/log"
|
|
9
|
+
import { withNetworkOptions, resolveNetworkOptions } from "@/cli/network"
|
|
10
|
+
import type { Event } from "@jonsoc/sdk/v2"
|
|
11
|
+
import type { EventSource } from "./context/sdk"
|
|
12
|
+
|
|
13
|
+
declare global {
|
|
14
|
+
const OPENCODE_WORKER_PATH: string
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
type RpcClient = ReturnType<typeof Rpc.client<typeof rpc>>
|
|
18
|
+
|
|
19
|
+
function createWorkerFetch(client: RpcClient): typeof fetch {
|
|
20
|
+
const fn = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
|
|
21
|
+
const request = new Request(input, init)
|
|
22
|
+
const body = request.body ? await request.text() : undefined
|
|
23
|
+
const result = await client.call("fetch", {
|
|
24
|
+
url: request.url,
|
|
25
|
+
method: request.method,
|
|
26
|
+
headers: Object.fromEntries(request.headers.entries()),
|
|
27
|
+
body,
|
|
28
|
+
})
|
|
29
|
+
return new Response(result.body, {
|
|
30
|
+
status: result.status,
|
|
31
|
+
headers: result.headers,
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
return fn as typeof fetch
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function createEventSource(client: RpcClient): EventSource {
|
|
38
|
+
return {
|
|
39
|
+
on: (handler) => client.on<Event>("event", handler),
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const TuiThreadCommand = cmd({
|
|
44
|
+
command: "$0 [project]",
|
|
45
|
+
describe: "start jonsoc tui",
|
|
46
|
+
builder: (yargs) =>
|
|
47
|
+
withNetworkOptions(yargs)
|
|
48
|
+
.positional("project", {
|
|
49
|
+
type: "string",
|
|
50
|
+
describe: "path to start jonsoc in",
|
|
51
|
+
})
|
|
52
|
+
.option("model", {
|
|
53
|
+
type: "string",
|
|
54
|
+
alias: ["m"],
|
|
55
|
+
describe: "model to use in the format of provider/model",
|
|
56
|
+
})
|
|
57
|
+
.option("continue", {
|
|
58
|
+
alias: ["c"],
|
|
59
|
+
describe: "continue the last session",
|
|
60
|
+
type: "boolean",
|
|
61
|
+
})
|
|
62
|
+
.option("session", {
|
|
63
|
+
alias: ["s"],
|
|
64
|
+
type: "string",
|
|
65
|
+
describe: "session id to continue",
|
|
66
|
+
})
|
|
67
|
+
.option("prompt", {
|
|
68
|
+
type: "string",
|
|
69
|
+
describe: "prompt to use",
|
|
70
|
+
})
|
|
71
|
+
.option("agent", {
|
|
72
|
+
type: "string",
|
|
73
|
+
describe: "agent to use",
|
|
74
|
+
}),
|
|
75
|
+
handler: async (args) => {
|
|
76
|
+
// Resolve relative paths against PWD to preserve behavior when using --cwd flag
|
|
77
|
+
const baseCwd = process.env.PWD ?? process.cwd()
|
|
78
|
+
|
|
79
|
+
// If args.project is "tui", it means no project was specified (tui was treated as positional arg)
|
|
80
|
+
const projectArg = args.project === "tui" ? undefined : args.project
|
|
81
|
+
|
|
82
|
+
const cwd = projectArg ? path.resolve(baseCwd, projectArg) : process.cwd()
|
|
83
|
+
const localWorker = new URL("./worker.ts", import.meta.url)
|
|
84
|
+
const distWorker = new URL("./cli/cmd/tui/worker.js", import.meta.url)
|
|
85
|
+
const workerPath = await iife(async () => {
|
|
86
|
+
if (typeof OPENCODE_WORKER_PATH !== "undefined") return OPENCODE_WORKER_PATH
|
|
87
|
+
if (await Bun.file(distWorker).exists()) return distWorker
|
|
88
|
+
return localWorker
|
|
89
|
+
})
|
|
90
|
+
try {
|
|
91
|
+
process.chdir(cwd)
|
|
92
|
+
} catch (e) {
|
|
93
|
+
UI.error("Failed to change directory to " + cwd)
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const worker = new Worker(workerPath, {
|
|
98
|
+
env: {
|
|
99
|
+
...Object.fromEntries(
|
|
100
|
+
Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined),
|
|
101
|
+
),
|
|
102
|
+
PRINT_LOGS: process.argv.includes("--print-logs") ? "1" : "0",
|
|
103
|
+
},
|
|
104
|
+
})
|
|
105
|
+
worker.onerror = (e) => {
|
|
106
|
+
Log.Default.error(e)
|
|
107
|
+
}
|
|
108
|
+
const client = Rpc.client<typeof rpc>(worker)
|
|
109
|
+
process.on("uncaughtException", (e) => {
|
|
110
|
+
Log.Default.error(e)
|
|
111
|
+
})
|
|
112
|
+
process.on("unhandledRejection", (e) => {
|
|
113
|
+
Log.Default.error(e)
|
|
114
|
+
})
|
|
115
|
+
process.on("SIGUSR2", async () => {
|
|
116
|
+
await client.call("reload", undefined)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
const prompt = await iife(async () => {
|
|
120
|
+
const piped = !process.stdin.isTTY ? await Bun.stdin.text() : undefined
|
|
121
|
+
if (!args.prompt) return piped
|
|
122
|
+
return piped ? piped + "\n" + args.prompt : args.prompt
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
// Check if server should be started (port or hostname explicitly set in CLI or config)
|
|
126
|
+
const networkOpts = await resolveNetworkOptions(args)
|
|
127
|
+
const shouldStartServer =
|
|
128
|
+
process.argv.includes("--port") ||
|
|
129
|
+
process.argv.includes("--hostname") ||
|
|
130
|
+
process.argv.includes("--mdns") ||
|
|
131
|
+
networkOpts.mdns ||
|
|
132
|
+
networkOpts.port !== 0 ||
|
|
133
|
+
networkOpts.hostname !== "127.0.0.1"
|
|
134
|
+
|
|
135
|
+
let url: string
|
|
136
|
+
let customFetch: typeof fetch | undefined
|
|
137
|
+
let events: EventSource | undefined
|
|
138
|
+
|
|
139
|
+
if (shouldStartServer) {
|
|
140
|
+
// Start HTTP server for external access
|
|
141
|
+
const server = await client.call("server", networkOpts)
|
|
142
|
+
url = server.url
|
|
143
|
+
} else {
|
|
144
|
+
// Use direct RPC communication (no HTTP)
|
|
145
|
+
url = "http://jonsoc.internal"
|
|
146
|
+
customFetch = createWorkerFetch(client)
|
|
147
|
+
events = createEventSource(client)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const tuiPromise = tui({
|
|
151
|
+
url,
|
|
152
|
+
fetch: customFetch,
|
|
153
|
+
events,
|
|
154
|
+
args: {
|
|
155
|
+
continue: args.continue,
|
|
156
|
+
sessionID: args.session,
|
|
157
|
+
agent: args.agent,
|
|
158
|
+
model: args.model,
|
|
159
|
+
prompt,
|
|
160
|
+
},
|
|
161
|
+
onExit: async () => {
|
|
162
|
+
await client.call("shutdown", undefined)
|
|
163
|
+
},
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
client.call("checkUpgrade", { directory: cwd }).catch(() => {})
|
|
168
|
+
}, 1000)
|
|
169
|
+
|
|
170
|
+
await tuiPromise
|
|
171
|
+
},
|
|
172
|
+
})
|