snow-flow 10.0.100 → 10.0.102
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/package.json +1 -1
- package/src/cli/cmd/tui/app.tsx +3 -15
- package/src/server/routes/tui-client.ts +1 -131
- package/src/server/server.ts +12 -15
package/package.json
CHANGED
package/src/cli/cmd/tui/app.tsx
CHANGED
|
@@ -117,18 +117,14 @@ export function tui(input: {
|
|
|
117
117
|
_onUpgrade = input.onUpgrade
|
|
118
118
|
// promise to prevent immediate exit
|
|
119
119
|
return new Promise<void>(async (resolve) => {
|
|
120
|
-
console.log("[snow-flow] detecting terminal background...")
|
|
121
120
|
const mode = await getTerminalBackgroundColor()
|
|
122
|
-
console.log("[snow-flow] background:", mode)
|
|
123
121
|
const onExit = async () => {
|
|
124
122
|
await input.onExit?.()
|
|
125
123
|
resolve()
|
|
126
124
|
}
|
|
127
125
|
|
|
128
|
-
const isRemote = !!process.env.OPENCODE_REMOTE_TUI
|
|
129
|
-
console.log("[snow-flow] starting render...", isRemote ? "(remote mode)" : "(local mode)")
|
|
130
126
|
try {
|
|
131
|
-
render(
|
|
127
|
+
await render(
|
|
132
128
|
() => {
|
|
133
129
|
return (
|
|
134
130
|
<ErrorBoundary
|
|
@@ -179,7 +175,6 @@ export function tui(input: {
|
|
|
179
175
|
targetFps: 60,
|
|
180
176
|
gatherStats: false,
|
|
181
177
|
exitOnCtrlC: false,
|
|
182
|
-
...(isRemote ? { remote: true } : {}),
|
|
183
178
|
useKittyKeyboard: process.env.OPENCODE_DISABLE_KITTY_KEYBOARD ? undefined : {},
|
|
184
179
|
consoleOptions: {
|
|
185
180
|
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
|
|
@@ -191,18 +186,15 @@ export function tui(input: {
|
|
|
191
186
|
},
|
|
192
187
|
},
|
|
193
188
|
)
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
console.log(`[snow-flow] render() threw: ${e instanceof Error ? e.message : e}`)
|
|
189
|
+
} catch {
|
|
190
|
+
// render failure handled by ErrorBoundary
|
|
197
191
|
}
|
|
198
192
|
})
|
|
199
193
|
}
|
|
200
194
|
|
|
201
195
|
function App() {
|
|
202
|
-
console.log("[snow-flow] App component mounting...")
|
|
203
196
|
const route = useRoute()
|
|
204
197
|
const dimensions = useTerminalDimensions()
|
|
205
|
-
console.log("[snow-flow] dimensions:", dimensions().width, "x", dimensions().height)
|
|
206
198
|
const renderer = useRenderer()
|
|
207
199
|
renderer.disableStdoutInterception()
|
|
208
200
|
const dialog = useDialog()
|
|
@@ -227,10 +219,6 @@ function App() {
|
|
|
227
219
|
}
|
|
228
220
|
const [terminalTitleEnabled, setTerminalTitleEnabled] = createSignal(kv.get("terminal_title_enabled", true))
|
|
229
221
|
|
|
230
|
-
createEffect(() => {
|
|
231
|
-
console.log(JSON.stringify(route.data))
|
|
232
|
-
})
|
|
233
|
-
|
|
234
222
|
// Update terminal window title based on current route and session
|
|
235
223
|
createEffect(() => {
|
|
236
224
|
if (!terminalTitleEnabled() || Flag.OPENCODE_DISABLE_TERMINAL_TITLE) return
|
|
@@ -1,138 +1,8 @@
|
|
|
1
1
|
import { Hono } from "hono"
|
|
2
2
|
import { lazy } from "../../util/lazy"
|
|
3
3
|
|
|
4
|
-
const HTML = `<!doctype html>
|
|
5
|
-
<html lang="en">
|
|
6
|
-
<head>
|
|
7
|
-
<meta charset="UTF-8">
|
|
8
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
9
|
-
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
10
|
-
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
|
|
11
|
-
<meta name="mobile-web-app-capable" content="yes">
|
|
12
|
-
<meta name="theme-color" content="#0a0a1a">
|
|
13
|
-
<title>Snow-Flow TUI</title>
|
|
14
|
-
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3ClinearGradient id='m' x1='16' y1='9' x2='16' y2='23' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0%25' stop-color='%23FFFFFF'/%3E%3Cstop offset='100%25' stop-color='%2300D9FF'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M4 23 L10 9 L16 15 L22 9 L28 23 Z' fill='url(%23m)' stroke='%2300D9FF' stroke-width='1.2' stroke-linejoin='round'/%3E%3C/svg%3E">
|
|
15
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css">
|
|
16
|
-
<style>
|
|
17
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
18
|
-
html, body { height: 100%; overflow: hidden; background: #0a0a1a; color: #e2e8f0; font-family: -apple-system, system-ui, sans-serif; }
|
|
19
|
-
body { display: flex; flex-direction: column; padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); }
|
|
20
|
-
#status { height: 32px; display: flex; align-items: center; justify-content: space-between; padding: 0 12px; background: #111127; border-bottom: 1px solid #1e1e3a; font-size: 12px; flex-shrink: 0; }
|
|
21
|
-
#status .left { display: flex; align-items: center; gap: 8px; }
|
|
22
|
-
#status .dot { width: 8px; height: 8px; border-radius: 50%; background: #444; }
|
|
23
|
-
#status .dot.connected { background: #22c55e; }
|
|
24
|
-
#status .dot.connecting { background: #eab308; animation: pulse 1s infinite; }
|
|
25
|
-
#status .dot.error { background: #ef4444; }
|
|
26
|
-
#status .label { color: #94a3b8; }
|
|
27
|
-
#terminal-container { flex: 1; min-height: 0; }
|
|
28
|
-
#terminal-container .xterm { height: 100%; padding: 4px; }
|
|
29
|
-
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
|
|
30
|
-
@media (max-width: 600px) {
|
|
31
|
-
#status { height: 28px; font-size: 11px; padding: 0 8px; }
|
|
32
|
-
}
|
|
33
|
-
</style>
|
|
34
|
-
</head>
|
|
35
|
-
<body>
|
|
36
|
-
<div id="status">
|
|
37
|
-
<div class="left">
|
|
38
|
-
<div id="dot" class="dot connecting"></div>
|
|
39
|
-
<span id="status-text" class="label">Connecting...</span>
|
|
40
|
-
</div>
|
|
41
|
-
<span class="label">Snow-Flow TUI</span>
|
|
42
|
-
</div>
|
|
43
|
-
<div id="terminal-container"></div>
|
|
44
|
-
<script type="module">
|
|
45
|
-
import { Terminal } from "https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/+esm"
|
|
46
|
-
import { FitAddon } from "https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/+esm"
|
|
47
|
-
import { WebLinksAddon } from "https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@0.11.0/+esm"
|
|
48
|
-
|
|
49
|
-
const dot = document.getElementById("dot")
|
|
50
|
-
const statusText = document.getElementById("status-text")
|
|
51
|
-
|
|
52
|
-
function setStatus(state, text) {
|
|
53
|
-
dot.className = "dot " + state
|
|
54
|
-
statusText.textContent = text
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
|
|
58
|
-
|
|
59
|
-
const term = new Terminal({
|
|
60
|
-
fontSize: isMobile ? 13 : 15,
|
|
61
|
-
fontFamily: "Menlo, 'Courier New', monospace",
|
|
62
|
-
theme: {
|
|
63
|
-
background: "#0a0a1a",
|
|
64
|
-
foreground: "#e2e8f0",
|
|
65
|
-
cursor: "#4f8ff7",
|
|
66
|
-
cursorAccent: "#0a0a1a",
|
|
67
|
-
selectionBackground: "rgba(79, 143, 247, 0.3)",
|
|
68
|
-
black: "#1a1a2e",
|
|
69
|
-
red: "#ef4444",
|
|
70
|
-
green: "#22c55e",
|
|
71
|
-
yellow: "#eab308",
|
|
72
|
-
blue: "#3b82f6",
|
|
73
|
-
magenta: "#a855f7",
|
|
74
|
-
cyan: "#06b6d4",
|
|
75
|
-
white: "#e2e8f0",
|
|
76
|
-
},
|
|
77
|
-
cursorBlink: true,
|
|
78
|
-
convertEol: true,
|
|
79
|
-
allowProposedApi: true,
|
|
80
|
-
scrollback: 5000,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const fit = new FitAddon()
|
|
84
|
-
term.loadAddon(fit)
|
|
85
|
-
term.loadAddon(new WebLinksAddon())
|
|
86
|
-
term.open(document.getElementById("terminal-container"))
|
|
87
|
-
fit.fit()
|
|
88
|
-
|
|
89
|
-
let ws
|
|
90
|
-
let reconnectTimer
|
|
91
|
-
|
|
92
|
-
function connect() {
|
|
93
|
-
setStatus("connecting", "Connecting...")
|
|
94
|
-
const proto = location.protocol === "https:" ? "wss:" : "ws:"
|
|
95
|
-
ws = new WebSocket(proto + "//" + location.host + "/tui-ws/ws?cols=" + term.cols + "&rows=" + term.rows)
|
|
96
|
-
|
|
97
|
-
ws.onopen = () => setStatus("connected", "Connected")
|
|
98
|
-
|
|
99
|
-
ws.onmessage = (e) => term.write(typeof e.data === "string" ? e.data : new Uint8Array(e.data))
|
|
100
|
-
|
|
101
|
-
ws.onclose = (e) => {
|
|
102
|
-
setStatus("error", "Disconnected — reconnecting...")
|
|
103
|
-
clearTimeout(reconnectTimer)
|
|
104
|
-
reconnectTimer = setTimeout(connect, 2000)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
ws.onerror = () => {
|
|
108
|
-
setStatus("error", "Connection error")
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
term.onData((data) => {
|
|
113
|
-
if (ws && ws.readyState === WebSocket.OPEN) ws.send(data)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
function doFit() {
|
|
117
|
-
fit.fit()
|
|
118
|
-
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
119
|
-
ws.send(JSON.stringify({ type: "resize", cols: term.cols, rows: term.rows }))
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
window.addEventListener("resize", doFit)
|
|
124
|
-
if (window.visualViewport) {
|
|
125
|
-
window.visualViewport.addEventListener("resize", doFit)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
connect()
|
|
129
|
-
term.focus()
|
|
130
|
-
</script>
|
|
131
|
-
</body>
|
|
132
|
-
</html>`
|
|
133
|
-
|
|
134
4
|
export const TuiClientRoutes = lazy(() =>
|
|
135
5
|
new Hono().get("/", (c) => {
|
|
136
|
-
return c.
|
|
6
|
+
return c.redirect("/")
|
|
137
7
|
}),
|
|
138
8
|
)
|
package/src/server/server.ts
CHANGED
|
@@ -5,7 +5,8 @@ import { describeRoute, generateSpecs, validator, resolver, openAPIRouteHandler
|
|
|
5
5
|
import { Hono } from "hono"
|
|
6
6
|
import { cors } from "hono/cors"
|
|
7
7
|
import { streamSSE } from "hono/streaming"
|
|
8
|
-
import {
|
|
8
|
+
import { serveStatic } from "hono/bun"
|
|
9
|
+
import path from "path"
|
|
9
10
|
import { basicAuth } from "hono/basic-auth"
|
|
10
11
|
import z from "zod"
|
|
11
12
|
import { Provider } from "../provider/provider"
|
|
@@ -531,21 +532,17 @@ export namespace Server {
|
|
|
531
532
|
})
|
|
532
533
|
},
|
|
533
534
|
)
|
|
534
|
-
.
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
const
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
535
|
+
.use("/assets/*", serveStatic({ root: "./packages/app/dist" }))
|
|
536
|
+
.get("/*", async (c) => {
|
|
537
|
+
const filePath = path.join(process.cwd(), "packages/app/dist", c.req.path)
|
|
538
|
+
const file = Bun.file(filePath)
|
|
539
|
+
if (await file.exists()) {
|
|
540
|
+
return new Response(file)
|
|
541
|
+
}
|
|
542
|
+
// SPA fallback — serve index.html for all non-asset routes
|
|
543
|
+
return new Response(Bun.file(path.join(process.cwd(), "packages/app/dist/index.html")), {
|
|
544
|
+
headers: { "Content-Type": "text/html" },
|
|
543
545
|
})
|
|
544
|
-
response.headers.set(
|
|
545
|
-
"Content-Security-Policy",
|
|
546
|
-
"default-src 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' data:",
|
|
547
|
-
)
|
|
548
|
-
return response
|
|
549
546
|
}) as unknown as Hono,
|
|
550
547
|
)
|
|
551
548
|
|