cerebras-cli 1.0.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.
- package/AGENTS.md +27 -0
- package/Dockerfile +10 -0
- package/README.md +15 -0
- package/bin/opencode +84 -0
- package/bunfig.toml +4 -0
- package/package.json +128 -0
- package/parsers-config.ts +239 -0
- package/script/build.ts +151 -0
- package/script/postinstall.mjs +122 -0
- package/script/publish.ts +256 -0
- package/script/schema.ts +47 -0
- package/src/acp/README.md +164 -0
- package/src/acp/agent.ts +812 -0
- package/src/acp/session.ts +70 -0
- package/src/acp/types.ts +22 -0
- package/src/agent/agent.ts +310 -0
- package/src/agent/generate.txt +75 -0
- package/src/auth/index.ts +70 -0
- package/src/bun/index.ts +152 -0
- package/src/bus/global.ts +10 -0
- package/src/bus/index.ts +142 -0
- package/src/cli/bootstrap.ts +17 -0
- package/src/cli/cmd/acp.ts +88 -0
- package/src/cli/cmd/agent.ts +165 -0
- package/src/cli/cmd/auth.ts +369 -0
- package/src/cli/cmd/cmd.ts +7 -0
- package/src/cli/cmd/debug/config.ts +15 -0
- package/src/cli/cmd/debug/file.ts +91 -0
- package/src/cli/cmd/debug/index.ts +41 -0
- package/src/cli/cmd/debug/lsp.ts +47 -0
- package/src/cli/cmd/debug/ripgrep.ts +83 -0
- package/src/cli/cmd/debug/scrap.ts +15 -0
- package/src/cli/cmd/debug/snapshot.ts +48 -0
- package/src/cli/cmd/export.ts +88 -0
- package/src/cli/cmd/generate.ts +38 -0
- package/src/cli/cmd/github.ts +1200 -0
- package/src/cli/cmd/import.ts +98 -0
- package/src/cli/cmd/mcp.ts +400 -0
- package/src/cli/cmd/models.ts +77 -0
- package/src/cli/cmd/pr.ts +112 -0
- package/src/cli/cmd/run.ts +342 -0
- package/src/cli/cmd/serve.ts +31 -0
- package/src/cli/cmd/session.ts +106 -0
- package/src/cli/cmd/stats.ts +298 -0
- package/src/cli/cmd/tui/app.tsx +732 -0
- package/src/cli/cmd/tui/attach.ts +25 -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 +124 -0
- package/src/cli/cmd/tui/component/dialog-feedback.tsx +160 -0
- package/src/cli/cmd/tui/component/dialog-mcp.tsx +86 -0
- package/src/cli/cmd/tui/component/dialog-model.tsx +223 -0
- package/src/cli/cmd/tui/component/dialog-notification.tsx +78 -0
- package/src/cli/cmd/tui/component/dialog-provider.tsx +222 -0
- package/src/cli/cmd/tui/component/dialog-session-list.tsx +97 -0
- package/src/cli/cmd/tui/component/dialog-session-rename.tsx +31 -0
- package/src/cli/cmd/tui/component/dialog-status.tsx +114 -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/logo.tsx +37 -0
- package/src/cli/cmd/tui/component/notification-banner.tsx +58 -0
- package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +530 -0
- package/src/cli/cmd/tui/component/prompt/history.tsx +107 -0
- package/src/cli/cmd/tui/component/prompt/index.tsx +931 -0
- package/src/cli/cmd/tui/context/args.tsx +14 -0
- package/src/cli/cmd/tui/context/directory.ts +12 -0
- package/src/cli/cmd/tui/context/exit.tsx +23 -0
- package/src/cli/cmd/tui/context/helper.tsx +25 -0
- package/src/cli/cmd/tui/context/keybind.tsx +111 -0
- package/src/cli/cmd/tui/context/kv.tsx +49 -0
- package/src/cli/cmd/tui/context/local.tsx +339 -0
- package/src/cli/cmd/tui/context/prompt.tsx +18 -0
- package/src/cli/cmd/tui/context/route.tsx +45 -0
- package/src/cli/cmd/tui/context/sdk.tsx +75 -0
- package/src/cli/cmd/tui/context/sync.tsx +374 -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/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/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 +95 -0
- package/src/cli/cmd/tui/context/theme/kanagawa.json +77 -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 +245 -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 +1077 -0
- package/src/cli/cmd/tui/event.ts +39 -0
- package/src/cli/cmd/tui/routes/home.tsx +104 -0
- package/src/cli/cmd/tui/routes/session/dialog-message.tsx +93 -0
- package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +37 -0
- package/src/cli/cmd/tui/routes/session/footer.tsx +76 -0
- package/src/cli/cmd/tui/routes/session/header.tsx +183 -0
- package/src/cli/cmd/tui/routes/session/index.tsx +1703 -0
- package/src/cli/cmd/tui/routes/session/sidebar.tsx +586 -0
- package/src/cli/cmd/tui/spawn.ts +60 -0
- package/src/cli/cmd/tui/thread.ts +120 -0
- package/src/cli/cmd/tui/ui/dialog-alert.tsx +55 -0
- package/src/cli/cmd/tui/ui/dialog-confirm.tsx +81 -0
- package/src/cli/cmd/tui/ui/dialog-help.tsx +36 -0
- package/src/cli/cmd/tui/ui/dialog-prompt.tsx +75 -0
- package/src/cli/cmd/tui/ui/dialog-select.tsx +317 -0
- package/src/cli/cmd/tui/ui/dialog.tsx +170 -0
- package/src/cli/cmd/tui/ui/spinner.ts +368 -0
- package/src/cli/cmd/tui/ui/toast.tsx +100 -0
- package/src/cli/cmd/tui/util/clipboard.ts +127 -0
- package/src/cli/cmd/tui/util/editor.ts +32 -0
- package/src/cli/cmd/tui/util/terminal.ts +114 -0
- package/src/cli/cmd/tui/worker.ts +63 -0
- package/src/cli/cmd/uninstall.ts +344 -0
- package/src/cli/cmd/upgrade.ts +67 -0
- package/src/cli/cmd/web.ts +84 -0
- package/src/cli/error.ts +55 -0
- package/src/cli/ui.ts +84 -0
- package/src/cli/upgrade.ts +25 -0
- package/src/command/index.ts +79 -0
- package/src/command/template/initialize.txt +10 -0
- package/src/command/template/review.txt +73 -0
- package/src/config/config.ts +886 -0
- package/src/config/markdown.ts +41 -0
- package/src/env/index.ts +26 -0
- package/src/file/fzf.ts +124 -0
- package/src/file/ignore.ts +83 -0
- package/src/file/index.ts +326 -0
- package/src/file/ripgrep.ts +391 -0
- package/src/file/time.ts +38 -0
- package/src/file/watcher.ts +89 -0
- package/src/flag/flag.ts +28 -0
- package/src/format/formatter.ts +277 -0
- package/src/format/index.ts +137 -0
- package/src/global/index.ts +52 -0
- package/src/id/id.ts +73 -0
- package/src/ide/index.ts +75 -0
- package/src/index.ts +158 -0
- package/src/installation/index.ts +194 -0
- package/src/lsp/client.ts +215 -0
- package/src/lsp/index.ts +370 -0
- package/src/lsp/language.ts +111 -0
- package/src/lsp/server.ts +1327 -0
- package/src/mcp/auth.ts +82 -0
- package/src/mcp/index.ts +576 -0
- package/src/mcp/oauth-callback.ts +203 -0
- package/src/mcp/oauth-provider.ts +132 -0
- package/src/notification/index.ts +101 -0
- package/src/patch/index.ts +622 -0
- package/src/permission/index.ts +198 -0
- package/src/plugin/index.ts +95 -0
- package/src/project/bootstrap.ts +31 -0
- package/src/project/instance.ts +68 -0
- package/src/project/project.ts +133 -0
- package/src/project/state.ts +65 -0
- package/src/project/vcs.ts +77 -0
- package/src/provider/auth.ts +143 -0
- package/src/provider/models-macro.ts +11 -0
- package/src/provider/models.ts +93 -0
- package/src/provider/provider.ts +996 -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 +27 -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 +1713 -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 +406 -0
- package/src/pty/index.ts +226 -0
- package/src/ratelimit/index.ts +185 -0
- package/src/server/error.ts +36 -0
- package/src/server/project.ts +50 -0
- package/src/server/server.ts +2463 -0
- package/src/server/tui.ts +71 -0
- package/src/session/compaction.ts +257 -0
- package/src/session/index.ts +470 -0
- package/src/session/message-v2.ts +641 -0
- package/src/session/message.ts +189 -0
- package/src/session/processor.ts +443 -0
- package/src/session/prompt/anthropic-20250930.txt +166 -0
- package/src/session/prompt/anthropic.txt +105 -0
- package/src/session/prompt/anthropic_spoof.txt +1 -0
- package/src/session/prompt/beast.txt +147 -0
- package/src/session/prompt/build-switch.txt +5 -0
- package/src/session/prompt/codex.txt +318 -0
- package/src/session/prompt/compaction.txt +12 -0
- package/src/session/prompt/copilot-gpt-5.txt +143 -0
- package/src/session/prompt/gemini.txt +155 -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/polaris.txt +107 -0
- package/src/session/prompt/qwen.txt +109 -0
- package/src/session/prompt/summarize.txt +4 -0
- package/src/session/prompt/title.txt +36 -0
- package/src/session/prompt.ts +1541 -0
- package/src/session/retry.ts +82 -0
- package/src/session/revert.ts +108 -0
- package/src/session/status.ts +75 -0
- package/src/session/summary.ts +203 -0
- package/src/session/system.ts +148 -0
- package/src/session/todo.ts +36 -0
- package/src/share/share-next.ts +195 -0
- package/src/share/share.ts +87 -0
- package/src/snapshot/index.ts +197 -0
- package/src/storage/storage.ts +226 -0
- package/src/telemetry/index.ts +232 -0
- package/src/tool/bash.ts +365 -0
- package/src/tool/bash.txt +128 -0
- package/src/tool/batch.ts +173 -0
- package/src/tool/batch.txt +28 -0
- package/src/tool/codesearch.ts +138 -0
- package/src/tool/codesearch.txt +12 -0
- package/src/tool/edit.ts +674 -0
- package/src/tool/edit.txt +10 -0
- package/src/tool/glob.ts +65 -0
- package/src/tool/glob.txt +6 -0
- package/src/tool/grep.ts +120 -0
- package/src/tool/grep.txt +8 -0
- package/src/tool/invalid.ts +17 -0
- package/src/tool/ls.ts +110 -0
- package/src/tool/ls.txt +1 -0
- package/src/tool/lsp-diagnostics.ts +26 -0
- package/src/tool/lsp-diagnostics.txt +1 -0
- package/src/tool/lsp-hover.ts +31 -0
- package/src/tool/lsp-hover.txt +1 -0
- package/src/tool/multiedit.ts +46 -0
- package/src/tool/multiedit.txt +41 -0
- package/src/tool/patch.ts +233 -0
- package/src/tool/patch.txt +1 -0
- package/src/tool/read.ts +217 -0
- package/src/tool/read.txt +12 -0
- package/src/tool/registry.ts +148 -0
- package/src/tool/task.ts +135 -0
- package/src/tool/task.txt +60 -0
- package/src/tool/todo.ts +39 -0
- package/src/tool/todoread.txt +14 -0
- package/src/tool/todowrite.txt +167 -0
- package/src/tool/tool.ts +66 -0
- package/src/tool/webfetch.ts +187 -0
- package/src/tool/webfetch.txt +14 -0
- package/src/tool/websearch.ts +150 -0
- package/src/tool/websearch.txt +11 -0
- package/src/tool/write.ts +99 -0
- package/src/tool/write.txt +8 -0
- package/src/types/shims.d.ts +3 -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 +69 -0
- package/src/util/fn.ts +11 -0
- package/src/util/iife.ts +3 -0
- package/src/util/keybind.ts +79 -0
- package/src/util/lazy.ts +11 -0
- package/src/util/locale.ts +81 -0
- package/src/util/lock.ts +98 -0
- package/src/util/log.ts +177 -0
- package/src/util/queue.ts +32 -0
- package/src/util/rpc.ts +42 -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 +54 -0
- package/sst-env.d.ts +9 -0
- package/test/bun.test.ts +53 -0
- package/test/config/agent-color.test.ts +66 -0
- package/test/config/config.test.ts +503 -0
- package/test/config/markdown.test.ts +89 -0
- package/test/file/ignore.test.ts +10 -0
- package/test/fixture/fixture.ts +28 -0
- package/test/fixture/lsp/fake-lsp-server.js +77 -0
- package/test/ide/ide.test.ts +82 -0
- package/test/keybind.test.ts +317 -0
- package/test/lsp/client.test.ts +95 -0
- package/test/patch/patch.test.ts +348 -0
- package/test/preload.ts +38 -0
- package/test/project/project.test.ts +42 -0
- package/test/provider/provider.test.ts +1809 -0
- package/test/provider/transform.test.ts +305 -0
- package/test/session/retry.test.ts +61 -0
- package/test/session/session.test.ts +71 -0
- package/test/snapshot/snapshot.test.ts +939 -0
- package/test/tool/__snapshots__/tool.test.ts.snap +9 -0
- package/test/tool/bash.test.ts +55 -0
- package/test/tool/patch.test.ts +259 -0
- package/test/util/iife.test.ts +36 -0
- package/test/util/lazy.test.ts +50 -0
- package/test/util/timeout.test.ts +21 -0
- package/test/util/wildcard.test.ts +55 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import { useSync } from "@tui/context/sync"
|
|
2
|
+
import { createMemo, createEffect, createSignal, For, Show, Switch, Match } from "solid-js"
|
|
3
|
+
import { createStore } from "solid-js/store"
|
|
4
|
+
import { useTheme } from "../../context/theme"
|
|
5
|
+
import { useToast } from "../../ui/toast"
|
|
6
|
+
import { useLocal } from "../../context/local"
|
|
7
|
+
import { Locale } from "@/util/locale"
|
|
8
|
+
import path from "path"
|
|
9
|
+
import type { AssistantMessage } from "@opencode-ai/sdk/v2"
|
|
10
|
+
import { Installation } from "@/installation"
|
|
11
|
+
import { useKeybind } from "../../context/keybind"
|
|
12
|
+
import { useDirectory } from "../../context/directory"
|
|
13
|
+
|
|
14
|
+
// Threshold for low cache hit rate warning
|
|
15
|
+
const LOW_CACHE_HIT_THRESHOLD = 40
|
|
16
|
+
const CONSECUTIVE_LOW_COUNT = 3
|
|
17
|
+
|
|
18
|
+
// Convert percentage (0-100) to block character (8 levels)
|
|
19
|
+
function percentToBar(percent: number): string {
|
|
20
|
+
const blocks = [" ", "▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"]
|
|
21
|
+
const index = Math.round((percent / 100) * 8)
|
|
22
|
+
return blocks[Math.min(8, Math.max(0, index))]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Row with label and horizontal bar for rate limits
|
|
26
|
+
function RateLimitRow(props: {
|
|
27
|
+
remaining: number
|
|
28
|
+
limit: number
|
|
29
|
+
window: string
|
|
30
|
+
}) {
|
|
31
|
+
const { theme } = useTheme()
|
|
32
|
+
|
|
33
|
+
const percentRemaining = createMemo(() =>
|
|
34
|
+
props.limit > 0 ? (props.remaining / props.limit) * 100 : 100
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
// Bar width fits in column
|
|
38
|
+
const barWidth = 10
|
|
39
|
+
const filledBlocks = createMemo(() => Math.round((percentRemaining() / 100) * barWidth))
|
|
40
|
+
const progressBar = createMemo(() => {
|
|
41
|
+
const filled = filledBlocks()
|
|
42
|
+
const empty = barWidth - filled
|
|
43
|
+
return "█".repeat(filled) + "░".repeat(empty)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const barColor = createMemo(() => {
|
|
47
|
+
const percent = percentRemaining()
|
|
48
|
+
if (percent >= 50) return theme.success
|
|
49
|
+
if (percent >= 20) return theme.warning
|
|
50
|
+
return theme.error
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Window label
|
|
54
|
+
const windowLabel = () => {
|
|
55
|
+
if (props.window === "minute") return "min"
|
|
56
|
+
if (props.window === "hour") return "hour"
|
|
57
|
+
if (props.window === "day") return "day"
|
|
58
|
+
return props.window
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<box flexDirection="row" gap={1}>
|
|
63
|
+
<text fg={theme.textMuted} width={4}>{windowLabel()}</text>
|
|
64
|
+
<text>
|
|
65
|
+
<span style={{ fg: barColor() }}>{progressBar()}</span>
|
|
66
|
+
</text>
|
|
67
|
+
</box>
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Visual representation of cache hit rate
|
|
72
|
+
function CacheVisual(props: {
|
|
73
|
+
hitRate: number
|
|
74
|
+
cachedTokens: number
|
|
75
|
+
promptTokens: number
|
|
76
|
+
recentRates: number[] // Last 10 message hit rates
|
|
77
|
+
}) {
|
|
78
|
+
const { theme } = useTheme()
|
|
79
|
+
|
|
80
|
+
// Progress bar using block characters
|
|
81
|
+
const barWidth = 20
|
|
82
|
+
const filledBlocks = createMemo(() => Math.round((props.hitRate / 100) * barWidth))
|
|
83
|
+
const progressBar = createMemo(() => {
|
|
84
|
+
const filled = filledBlocks()
|
|
85
|
+
const empty = barWidth - filled
|
|
86
|
+
return "█".repeat(filled) + "░".repeat(empty)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// Pie/wheel indicator using circle segments
|
|
90
|
+
const pieIndicator = createMemo(() => {
|
|
91
|
+
const rate = props.hitRate
|
|
92
|
+
if (rate >= 87.5) return "●" // Full
|
|
93
|
+
if (rate >= 62.5) return "◕" // 3/4
|
|
94
|
+
if (rate >= 37.5) return "◑" // Half
|
|
95
|
+
if (rate >= 12.5) return "◔" // 1/4
|
|
96
|
+
return "○" // Empty
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
// Minesweeper-style face indicator
|
|
100
|
+
const faceIndicator = createMemo(() => {
|
|
101
|
+
const rate = props.hitRate
|
|
102
|
+
if (rate >= 70) return "😊" // Happy - good cache
|
|
103
|
+
if (rate >= 40) return "😐" // Neutral - okay cache
|
|
104
|
+
return "😟" // Worried - bad cache
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
// Color based on hit rate (gradient from red to green)
|
|
108
|
+
const rateColor = createMemo(() => {
|
|
109
|
+
const rate = props.hitRate
|
|
110
|
+
if (rate >= 70) return theme.success
|
|
111
|
+
if (rate >= 40) return theme.warning
|
|
112
|
+
return theme.error
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
// Get color for a rate
|
|
116
|
+
const getRateColor = (rate: number) => {
|
|
117
|
+
if (rate >= 70) return theme.success
|
|
118
|
+
if (rate >= 40) return theme.warning
|
|
119
|
+
return theme.error
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Last 10 rates as a memo for proper reactivity
|
|
123
|
+
const recentRates = createMemo(() => {
|
|
124
|
+
const rates = props.recentRates || []
|
|
125
|
+
return rates.slice(-10)
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
// Pre-compute the sparkline to avoid For reactivity issues
|
|
129
|
+
const sparkline = createMemo(() => {
|
|
130
|
+
return recentRates().map((rate, i) => ({
|
|
131
|
+
key: i,
|
|
132
|
+
rate,
|
|
133
|
+
char: percentToBar(rate),
|
|
134
|
+
color: getRateColor(rate),
|
|
135
|
+
}))
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<>
|
|
140
|
+
{/* Wheel indicator with percentage and face */}
|
|
141
|
+
<box flexDirection="row" gap={1}>
|
|
142
|
+
<text style={{ fg: rateColor() }}>{pieIndicator()}</text>
|
|
143
|
+
<text fg={theme.textMuted}>
|
|
144
|
+
{props.hitRate.toFixed(1)}% hit rate
|
|
145
|
+
</text>
|
|
146
|
+
<text>{faceIndicator()}</text>
|
|
147
|
+
</box>
|
|
148
|
+
{/* Progress bar with sparkline bar chart */}
|
|
149
|
+
<box flexDirection="row" gap={1}>
|
|
150
|
+
<text>
|
|
151
|
+
<span style={{ fg: rateColor() }}>{progressBar()}</span>
|
|
152
|
+
</text>
|
|
153
|
+
<text>
|
|
154
|
+
<For each={sparkline()}>
|
|
155
|
+
{(item) => (
|
|
156
|
+
<span style={{ fg: item.color }}>{item.char}</span>
|
|
157
|
+
)}
|
|
158
|
+
</For>
|
|
159
|
+
</text>
|
|
160
|
+
</box>
|
|
161
|
+
{/* Token counts */}
|
|
162
|
+
<text fg={theme.textMuted}>
|
|
163
|
+
{props.cachedTokens.toLocaleString()} / {props.promptTokens.toLocaleString()} tokens
|
|
164
|
+
</text>
|
|
165
|
+
</>
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function Sidebar(props: { sessionID: string }) {
|
|
170
|
+
const sync = useSync()
|
|
171
|
+
const { theme } = useTheme()
|
|
172
|
+
const toast = useToast()
|
|
173
|
+
const local = useLocal()
|
|
174
|
+
const session = createMemo(() => sync.session.get(props.sessionID)!)
|
|
175
|
+
const diff = createMemo(() => sync.data.session_diff[props.sessionID] ?? [])
|
|
176
|
+
const todo = createMemo(() => sync.data.todo[props.sessionID] ?? [])
|
|
177
|
+
const messages = createMemo(() => sync.data.message[props.sessionID] ?? [])
|
|
178
|
+
|
|
179
|
+
// Track whether we've shown the low cache warning for this session
|
|
180
|
+
const [hasShownCacheWarning, setHasShownCacheWarning] = createSignal(false)
|
|
181
|
+
const [lastMessageCount, setLastMessageCount] = createSignal(0)
|
|
182
|
+
|
|
183
|
+
const [expanded, setExpanded] = createStore({
|
|
184
|
+
mcp: true,
|
|
185
|
+
diff: true,
|
|
186
|
+
todo: true,
|
|
187
|
+
lsp: true,
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// Sort MCP servers alphabetically for consistent display order
|
|
191
|
+
const mcpEntries = createMemo(() => Object.entries(sync.data.mcp).sort(([a], [b]) => a.localeCompare(b)))
|
|
192
|
+
|
|
193
|
+
const usage = createMemo(() => {
|
|
194
|
+
const now = Date.now()
|
|
195
|
+
const assistants = messages().filter((m) => m.role === "assistant")
|
|
196
|
+
const total = assistants.length
|
|
197
|
+
const countWithin = (ms: number) =>
|
|
198
|
+
assistants.filter((m) => {
|
|
199
|
+
const t = m.time?.completed ?? m.time?.created ?? 0
|
|
200
|
+
return now - t <= ms
|
|
201
|
+
}).length
|
|
202
|
+
return {
|
|
203
|
+
total,
|
|
204
|
+
min1: countWithin(60_000),
|
|
205
|
+
hour1: countWithin(60 * 60_000),
|
|
206
|
+
day1: countWithin(24 * 60 * 60_000),
|
|
207
|
+
}
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
const context = createMemo(() => {
|
|
211
|
+
const last = messages().findLast((x) => x.role === "assistant" && x.tokens.output > 0) as AssistantMessage
|
|
212
|
+
if (!last) return
|
|
213
|
+
const total =
|
|
214
|
+
last.tokens.input + last.tokens.output + last.tokens.reasoning + last.tokens.cache.read + last.tokens.cache.write
|
|
215
|
+
const model = sync.data.provider.find((x) => x.id === last.providerID)?.models[last.modelID]
|
|
216
|
+
return {
|
|
217
|
+
tokens: total.toLocaleString(),
|
|
218
|
+
percentage: model?.limit.context ? Math.round((total / model.limit.context) * 100) : null,
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
// Get rate limit info from sync store (populated via SSE events)
|
|
223
|
+
const rateLimitInfo = createMemo(() => {
|
|
224
|
+
const currentModel = local.model.current()
|
|
225
|
+
if (!currentModel?.providerID) return undefined
|
|
226
|
+
return sync.data.ratelimit[currentModel.providerID]
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
const cacheStats = createMemo(() => {
|
|
231
|
+
const assistants = messages().filter((m) => m.role === "assistant") as AssistantMessage[]
|
|
232
|
+
let totalCachedTokens = 0
|
|
233
|
+
let totalPromptTokens = 0
|
|
234
|
+
for (const msg of assistants) {
|
|
235
|
+
// Total prompt = input + cached (input may be non-cached portion only)
|
|
236
|
+
const cached = msg.tokens.cache.read
|
|
237
|
+
const total = msg.tokens.input + cached
|
|
238
|
+
totalCachedTokens += cached
|
|
239
|
+
totalPromptTokens += total
|
|
240
|
+
}
|
|
241
|
+
const hitRate = totalPromptTokens > 0 ? (totalCachedTokens / totalPromptTokens) * 100 : 0
|
|
242
|
+
return {
|
|
243
|
+
promptTokens: totalPromptTokens,
|
|
244
|
+
cachedTokens: totalCachedTokens,
|
|
245
|
+
hitRate: hitRate.toFixed(1),
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
// Calculate per-message cache hit rates for completed assistant messages
|
|
250
|
+
const perMessageCacheRates = createMemo(() => {
|
|
251
|
+
const assistants = messages().filter(
|
|
252
|
+
(m) => m.role === "assistant" && m.time.completed
|
|
253
|
+
) as AssistantMessage[]
|
|
254
|
+
return assistants.map((msg) => {
|
|
255
|
+
const cached = msg.tokens.cache.read
|
|
256
|
+
const total = msg.tokens.input + cached
|
|
257
|
+
return total > 0 ? (cached / total) * 100 : 0
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
// Monitor for consecutive low cache hit rates
|
|
262
|
+
createEffect(() => {
|
|
263
|
+
const rates = perMessageCacheRates()
|
|
264
|
+
const currentCount = rates.length
|
|
265
|
+
|
|
266
|
+
// Only check when we have new completed messages
|
|
267
|
+
if (currentCount <= lastMessageCount()) {
|
|
268
|
+
return
|
|
269
|
+
}
|
|
270
|
+
setLastMessageCount(currentCount)
|
|
271
|
+
|
|
272
|
+
if (rates.length < CONSECUTIVE_LOW_COUNT) {
|
|
273
|
+
return
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const lastNRates = rates.slice(-CONSECUTIVE_LOW_COUNT)
|
|
277
|
+
const allBelowThreshold = lastNRates.every((rate) => rate < LOW_CACHE_HIT_THRESHOLD)
|
|
278
|
+
|
|
279
|
+
if (allBelowThreshold && !hasShownCacheWarning()) {
|
|
280
|
+
setHasShownCacheWarning(true)
|
|
281
|
+
toast.show({
|
|
282
|
+
variant: "warning",
|
|
283
|
+
title: "Low Cache Hit Rate",
|
|
284
|
+
message: `Cache hit rate has been below ${LOW_CACHE_HIT_THRESHOLD}% for the last ${CONSECUTIVE_LOW_COUNT} requests. This may increase costs and latency.`,
|
|
285
|
+
duration: 8000,
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (!allBelowThreshold && hasShownCacheWarning()) {
|
|
290
|
+
const lastNAboveThreshold = lastNRates.every((rate) => rate >= LOW_CACHE_HIT_THRESHOLD)
|
|
291
|
+
if (lastNAboveThreshold) {
|
|
292
|
+
setHasShownCacheWarning(false)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
const keybind = useKeybind()
|
|
298
|
+
const directory = useDirectory()
|
|
299
|
+
|
|
300
|
+
const hasProviders = createMemo(() =>
|
|
301
|
+
sync.data.provider.some((x) => x.id !== "opencode" || Object.values(x.models).some((y) => y.cost?.input !== 0)),
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<Show when={session()}>
|
|
306
|
+
<box
|
|
307
|
+
backgroundColor={theme.backgroundPanel}
|
|
308
|
+
width={42}
|
|
309
|
+
paddingTop={1}
|
|
310
|
+
paddingBottom={1}
|
|
311
|
+
paddingLeft={2}
|
|
312
|
+
paddingRight={2}
|
|
313
|
+
>
|
|
314
|
+
<scrollbox flexGrow={1}>
|
|
315
|
+
<box flexShrink={0} gap={1} paddingRight={1}>
|
|
316
|
+
<box>
|
|
317
|
+
<text fg={theme.text}>
|
|
318
|
+
<b>{session().title}</b>
|
|
319
|
+
</text>
|
|
320
|
+
<Show when={session().share?.url}>
|
|
321
|
+
<text fg={theme.textMuted}>{session().share!.url}</text>
|
|
322
|
+
</Show>
|
|
323
|
+
</box>
|
|
324
|
+
<box>
|
|
325
|
+
<text fg={theme.text}>
|
|
326
|
+
<b>Context</b>
|
|
327
|
+
</text>
|
|
328
|
+
<text fg={theme.textMuted}>{context()?.tokens ?? 0} tokens</text>
|
|
329
|
+
<text fg={theme.textMuted}>{context()?.percentage ?? 0}% used</text>
|
|
330
|
+
<text fg={theme.textMuted}>
|
|
331
|
+
Requests: {usage().total} (1m {usage().min1} / 1h {usage().hour1} / 24h {usage().day1})
|
|
332
|
+
</text>
|
|
333
|
+
</box>
|
|
334
|
+
<Show when={cacheStats().promptTokens > 0}>
|
|
335
|
+
<box>
|
|
336
|
+
<text fg={theme.text}>
|
|
337
|
+
<b>Cache</b>
|
|
338
|
+
</text>
|
|
339
|
+
<CacheVisual
|
|
340
|
+
hitRate={parseFloat(cacheStats().hitRate)}
|
|
341
|
+
cachedTokens={cacheStats().cachedTokens}
|
|
342
|
+
promptTokens={cacheStats().promptTokens}
|
|
343
|
+
recentRates={perMessageCacheRates()}
|
|
344
|
+
/>
|
|
345
|
+
</box>
|
|
346
|
+
</Show>
|
|
347
|
+
<Show when={rateLimitInfo()}>
|
|
348
|
+
<box>
|
|
349
|
+
<text fg={theme.text}>
|
|
350
|
+
<b>Rate Limits</b>
|
|
351
|
+
</text>
|
|
352
|
+
<box flexDirection="row" gap={2}>
|
|
353
|
+
{/* Tokens column */}
|
|
354
|
+
<box flexGrow={1} gap={1}>
|
|
355
|
+
<text fg={theme.textMuted}>Tokens</text>
|
|
356
|
+
<For each={rateLimitInfo()?.tokenLimits || []}>
|
|
357
|
+
{(windowInfo) => (
|
|
358
|
+
<RateLimitRow
|
|
359
|
+
remaining={windowInfo.remaining}
|
|
360
|
+
limit={windowInfo.limit}
|
|
361
|
+
window={windowInfo.window}
|
|
362
|
+
/>
|
|
363
|
+
)}
|
|
364
|
+
</For>
|
|
365
|
+
</box>
|
|
366
|
+
{/* Requests column */}
|
|
367
|
+
<box flexGrow={1} gap={1}>
|
|
368
|
+
<text fg={theme.textMuted}>Requests</text>
|
|
369
|
+
<For each={rateLimitInfo()?.requestLimits || []}>
|
|
370
|
+
{(windowInfo) => (
|
|
371
|
+
<RateLimitRow
|
|
372
|
+
remaining={windowInfo.remaining}
|
|
373
|
+
limit={windowInfo.limit}
|
|
374
|
+
window={windowInfo.window}
|
|
375
|
+
/>
|
|
376
|
+
)}
|
|
377
|
+
</For>
|
|
378
|
+
</box>
|
|
379
|
+
</box>
|
|
380
|
+
</box>
|
|
381
|
+
</Show>
|
|
382
|
+
<Show when={mcpEntries().length > 0}>
|
|
383
|
+
<box>
|
|
384
|
+
<box
|
|
385
|
+
flexDirection="row"
|
|
386
|
+
gap={1}
|
|
387
|
+
onMouseDown={() => mcpEntries().length > 2 && setExpanded("mcp", !expanded.mcp)}
|
|
388
|
+
>
|
|
389
|
+
<Show when={mcpEntries().length > 2}>
|
|
390
|
+
<text fg={theme.text}>{expanded.mcp ? "▼" : "▶"}</text>
|
|
391
|
+
</Show>
|
|
392
|
+
<text fg={theme.text}>
|
|
393
|
+
<b>MCP</b>
|
|
394
|
+
</text>
|
|
395
|
+
</box>
|
|
396
|
+
<Show when={mcpEntries().length <= 2 || expanded.mcp}>
|
|
397
|
+
<For each={mcpEntries()}>
|
|
398
|
+
{([key, item]) => (
|
|
399
|
+
<box flexDirection="row" gap={1}>
|
|
400
|
+
<text
|
|
401
|
+
flexShrink={0}
|
|
402
|
+
style={{
|
|
403
|
+
fg: (
|
|
404
|
+
{
|
|
405
|
+
connected: theme.success,
|
|
406
|
+
failed: theme.error,
|
|
407
|
+
disabled: theme.textMuted,
|
|
408
|
+
needs_auth: theme.warning,
|
|
409
|
+
needs_client_registration: theme.error,
|
|
410
|
+
} as Record<string, typeof theme.success>
|
|
411
|
+
)[item.status],
|
|
412
|
+
}}
|
|
413
|
+
>
|
|
414
|
+
•
|
|
415
|
+
</text>
|
|
416
|
+
<text fg={theme.text} wrapMode="word">
|
|
417
|
+
{key}{" "}
|
|
418
|
+
<span style={{ fg: theme.textMuted }}>
|
|
419
|
+
<Switch fallback={item.status}>
|
|
420
|
+
<Match when={item.status === "connected"}>Connected</Match>
|
|
421
|
+
<Match when={item.status === "failed" && item}>{(val) => <i>{val().error}</i>}</Match>
|
|
422
|
+
<Match when={item.status === "disabled"}>Disabled</Match>
|
|
423
|
+
<Match when={(item.status as string) === "needs_auth"}>Needs auth</Match>
|
|
424
|
+
<Match when={(item.status as string) === "needs_client_registration"}>
|
|
425
|
+
Needs client ID
|
|
426
|
+
</Match>
|
|
427
|
+
</Switch>
|
|
428
|
+
</span>
|
|
429
|
+
</text>
|
|
430
|
+
</box>
|
|
431
|
+
)}
|
|
432
|
+
</For>
|
|
433
|
+
</Show>
|
|
434
|
+
</box>
|
|
435
|
+
</Show>
|
|
436
|
+
<box>
|
|
437
|
+
<box
|
|
438
|
+
flexDirection="row"
|
|
439
|
+
gap={1}
|
|
440
|
+
onMouseDown={() => sync.data.lsp.length > 2 && setExpanded("lsp", !expanded.lsp)}
|
|
441
|
+
>
|
|
442
|
+
<Show when={sync.data.lsp.length > 2}>
|
|
443
|
+
<text fg={theme.text}>{expanded.lsp ? "▼" : "▶"}</text>
|
|
444
|
+
</Show>
|
|
445
|
+
<text fg={theme.text}>
|
|
446
|
+
<b>LSP</b>
|
|
447
|
+
</text>
|
|
448
|
+
</box>
|
|
449
|
+
<Show when={sync.data.lsp.length <= 2 || expanded.lsp}>
|
|
450
|
+
<Show when={sync.data.lsp.length === 0}>
|
|
451
|
+
<text fg={theme.textMuted}>LSPs will activate as files are read</text>
|
|
452
|
+
</Show>
|
|
453
|
+
<For each={sync.data.lsp}>
|
|
454
|
+
{(item) => (
|
|
455
|
+
<box flexDirection="row" gap={1}>
|
|
456
|
+
<text
|
|
457
|
+
flexShrink={0}
|
|
458
|
+
style={{
|
|
459
|
+
fg: {
|
|
460
|
+
connected: theme.success,
|
|
461
|
+
error: theme.error,
|
|
462
|
+
}[item.status],
|
|
463
|
+
}}
|
|
464
|
+
>
|
|
465
|
+
•
|
|
466
|
+
</text>
|
|
467
|
+
<text fg={theme.textMuted}>
|
|
468
|
+
{item.id} {item.root}
|
|
469
|
+
</text>
|
|
470
|
+
</box>
|
|
471
|
+
)}
|
|
472
|
+
</For>
|
|
473
|
+
</Show>
|
|
474
|
+
</box>
|
|
475
|
+
<Show when={todo().length > 0 && todo().some((t) => t.status !== "completed")}>
|
|
476
|
+
<box>
|
|
477
|
+
<box
|
|
478
|
+
flexDirection="row"
|
|
479
|
+
gap={1}
|
|
480
|
+
onMouseDown={() => todo().length > 2 && setExpanded("todo", !expanded.todo)}
|
|
481
|
+
>
|
|
482
|
+
<Show when={todo().length > 2}>
|
|
483
|
+
<text fg={theme.text}>{expanded.todo ? "▼" : "▶"}</text>
|
|
484
|
+
</Show>
|
|
485
|
+
<text fg={theme.text}>
|
|
486
|
+
<b>Todo</b>
|
|
487
|
+
</text>
|
|
488
|
+
</box>
|
|
489
|
+
<Show when={todo().length <= 2 || expanded.todo}>
|
|
490
|
+
<For each={todo()}>
|
|
491
|
+
{(todo) => (
|
|
492
|
+
<text style={{ fg: todo.status === "in_progress" ? theme.success : theme.textMuted }}>
|
|
493
|
+
[{todo.status === "completed" ? "✓" : " "}] {todo.content}
|
|
494
|
+
</text>
|
|
495
|
+
)}
|
|
496
|
+
</For>
|
|
497
|
+
</Show>
|
|
498
|
+
</box>
|
|
499
|
+
</Show>
|
|
500
|
+
<Show when={diff().length > 0}>
|
|
501
|
+
<box>
|
|
502
|
+
<box
|
|
503
|
+
flexDirection="row"
|
|
504
|
+
gap={1}
|
|
505
|
+
onMouseDown={() => diff().length > 2 && setExpanded("diff", !expanded.diff)}
|
|
506
|
+
>
|
|
507
|
+
<Show when={diff().length > 2}>
|
|
508
|
+
<text fg={theme.text}>{expanded.diff ? "▼" : "▶"}</text>
|
|
509
|
+
</Show>
|
|
510
|
+
<text fg={theme.text}>
|
|
511
|
+
<b>Modified Files</b>
|
|
512
|
+
</text>
|
|
513
|
+
</box>
|
|
514
|
+
<Show when={diff().length <= 2 || expanded.diff}>
|
|
515
|
+
<For each={diff() || []}>
|
|
516
|
+
{(item) => {
|
|
517
|
+
const file = createMemo(() => {
|
|
518
|
+
const splits = item.file.split(path.sep).filter(Boolean)
|
|
519
|
+
const last = splits.at(-1)!
|
|
520
|
+
const rest = splits.slice(0, -1).join(path.sep)
|
|
521
|
+
if (!rest) return last
|
|
522
|
+
return Locale.truncateMiddle(rest, 30 - last.length) + "/" + last
|
|
523
|
+
})
|
|
524
|
+
return (
|
|
525
|
+
<box flexDirection="row" gap={1} justifyContent="space-between">
|
|
526
|
+
<text fg={theme.textMuted} wrapMode="char">
|
|
527
|
+
{file()}
|
|
528
|
+
</text>
|
|
529
|
+
<box flexDirection="row" gap={1} flexShrink={0}>
|
|
530
|
+
<Show when={item.additions}>
|
|
531
|
+
<text fg={theme.diffAdded}>+{item.additions}</text>
|
|
532
|
+
</Show>
|
|
533
|
+
<Show when={item.deletions}>
|
|
534
|
+
<text fg={theme.diffRemoved}>-{item.deletions}</text>
|
|
535
|
+
</Show>
|
|
536
|
+
</box>
|
|
537
|
+
</box>
|
|
538
|
+
)
|
|
539
|
+
}}
|
|
540
|
+
</For>
|
|
541
|
+
</Show>
|
|
542
|
+
</box>
|
|
543
|
+
</Show>
|
|
544
|
+
</box>
|
|
545
|
+
</scrollbox>
|
|
546
|
+
|
|
547
|
+
<box flexShrink={0} gap={1} paddingTop={1}>
|
|
548
|
+
<Show when={!hasProviders()}>
|
|
549
|
+
<box
|
|
550
|
+
backgroundColor={theme.backgroundElement}
|
|
551
|
+
paddingTop={1}
|
|
552
|
+
paddingBottom={1}
|
|
553
|
+
paddingLeft={2}
|
|
554
|
+
paddingRight={2}
|
|
555
|
+
flexDirection="row"
|
|
556
|
+
gap={1}
|
|
557
|
+
>
|
|
558
|
+
<text flexShrink={0}>⬖</text>
|
|
559
|
+
<box flexGrow={1} gap={1}>
|
|
560
|
+
<text>
|
|
561
|
+
<b>Getting started</b>
|
|
562
|
+
</text>
|
|
563
|
+
<text fg={theme.textMuted}>OpenCode includes free models so you can start immediately.</text>
|
|
564
|
+
<text fg={theme.textMuted}>
|
|
565
|
+
Connect from 75+ providers to use other models, including Claude, GPT, Gemini etc
|
|
566
|
+
</text>
|
|
567
|
+
<box flexDirection="row" gap={1} justifyContent="space-between">
|
|
568
|
+
<text>Connect provider</text>
|
|
569
|
+
<text fg={theme.textMuted}>/connect</text>
|
|
570
|
+
</box>
|
|
571
|
+
</box>
|
|
572
|
+
</box>
|
|
573
|
+
</Show>
|
|
574
|
+
<text fg={theme.text}>{directory()}</text>
|
|
575
|
+
<text fg={theme.textMuted}>
|
|
576
|
+
<span style={{ fg: theme.success }}>•</span> <b>Open</b>
|
|
577
|
+
<span style={{ fg: theme.text }}>
|
|
578
|
+
<b>Code</b>
|
|
579
|
+
</span>{" "}
|
|
580
|
+
<span>{Installation.VERSION}</span>
|
|
581
|
+
</text>
|
|
582
|
+
</box>
|
|
583
|
+
</box>
|
|
584
|
+
</Show>
|
|
585
|
+
)
|
|
586
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { cmd } from "@/cli/cmd/cmd"
|
|
2
|
+
import { Instance } from "@/project/instance"
|
|
3
|
+
import path from "path"
|
|
4
|
+
import { Server } from "@/server/server"
|
|
5
|
+
import { upgrade } from "@/cli/upgrade"
|
|
6
|
+
|
|
7
|
+
export const TuiSpawnCommand = cmd({
|
|
8
|
+
command: "spawn [project]",
|
|
9
|
+
builder: (yargs) =>
|
|
10
|
+
yargs
|
|
11
|
+
.positional("project", {
|
|
12
|
+
type: "string",
|
|
13
|
+
describe: "path to start opencode in",
|
|
14
|
+
})
|
|
15
|
+
.option("port", {
|
|
16
|
+
type: "number",
|
|
17
|
+
describe: "port to listen on",
|
|
18
|
+
default: 0,
|
|
19
|
+
})
|
|
20
|
+
.option("hostname", {
|
|
21
|
+
type: "string",
|
|
22
|
+
describe: "hostname to listen on",
|
|
23
|
+
default: "127.0.0.1",
|
|
24
|
+
}),
|
|
25
|
+
handler: async (args) => {
|
|
26
|
+
upgrade()
|
|
27
|
+
const server = Server.listen({
|
|
28
|
+
port: args.port,
|
|
29
|
+
hostname: "127.0.0.1",
|
|
30
|
+
})
|
|
31
|
+
const bin = process.execPath
|
|
32
|
+
const cmd = []
|
|
33
|
+
let cwd = process.cwd()
|
|
34
|
+
if (bin.endsWith("bun")) {
|
|
35
|
+
cmd.push(
|
|
36
|
+
process.execPath,
|
|
37
|
+
"run",
|
|
38
|
+
"--conditions",
|
|
39
|
+
"browser",
|
|
40
|
+
new URL("../../../index.ts", import.meta.url).pathname,
|
|
41
|
+
)
|
|
42
|
+
cwd = new URL("../../../../", import.meta.url).pathname
|
|
43
|
+
} else cmd.push(process.execPath)
|
|
44
|
+
cmd.push("attach", server.url.toString(), "--dir", args.project ? path.resolve(args.project) : process.cwd())
|
|
45
|
+
const proc = Bun.spawn({
|
|
46
|
+
cmd,
|
|
47
|
+
cwd,
|
|
48
|
+
stdout: "inherit",
|
|
49
|
+
stderr: "inherit",
|
|
50
|
+
stdin: "inherit",
|
|
51
|
+
env: {
|
|
52
|
+
...process.env,
|
|
53
|
+
BUN_OPTIONS: "",
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
await proc.exited
|
|
57
|
+
await Instance.disposeAll()
|
|
58
|
+
await server.stop(true)
|
|
59
|
+
},
|
|
60
|
+
})
|