Park remaining local WIP on main. TUI archived, CLI/kernel edits, glass. Sort later. Broken is fine.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+228
@@ -0,0 +1,228 @@
|
||||
# Fishbone: black TUI, split starts, leftover OpenCode
|
||||
|
||||
Fix **bottom to top**. Do not start at “blank screen” or a new `script/dev.ts`. Each layer can look like the same black window.
|
||||
|
||||
Date: 2026-08-24
|
||||
Repo: `neuron`
|
||||
Kit: sibling `neuron-tui`
|
||||
Symptom: iTerm looks empty after launching the CLI, or the CLI dies before that with a loud error.
|
||||
|
||||
---
|
||||
|
||||
## Unproven (do not skip)
|
||||
|
||||
We have **not** shown that a high-contrast cell landed in **your** iTerm **alternate screen** on the current command.
|
||||
|
||||
iTerm “session contents” (AppleScript) is usually the **main** buffer. The TUI draws on the **alt** buffer. Empty contents is not proof of a blank app.
|
||||
|
||||
Until a bright first frame or a dump of the renderer buffer, “always blank” is several failures wearing one coat.
|
||||
|
||||
---
|
||||
|
||||
## Core lizard (cyclomatic)
|
||||
|
||||
Tool: **lizard** (CCN = cyclomatic complexity). Default warn is **15**. Run 2026-08-24 on `@neuron-tui/core` + native Zig.
|
||||
|
||||
**The JS presentation host is too big for lizard to see.** `renderer.ts` is 5130 lines. Lizard reports avg CCN **1.9** and **0 warnings**. That is a lie: it swallowed most of `CliRenderer` into `resolveModes` (595 lines). `zig.ts` is 6706 lines and lizard found **5 functions**. If a metric cannot split the file, the file is the problem.
|
||||
|
||||
Hand count of the JS hot path (if/else/switch + `&&`/`||`): `loop` ~32 branches / 122 nloc; `requestRender` ~10; `setupTerminal` ~14; `constructor` ~39. Not 1.9.
|
||||
|
||||
**Native is where lizard can see, and one function owns present:**
|
||||
|
||||
| CCN | nloc | fn | file |
|
||||
|---|---|---|---|
|
||||
| **189** | 394 | `prepareRenderFrameWithWriter` | `native/src/renderer.zig` |
|
||||
| **104** | 240 | `checkEnvironmentOverrides` | `native/src/terminal.zig` |
|
||||
| 58 | 107 | `processCapabilityResponse` | `terminal.zig` |
|
||||
| 57 | 109 | `writeSnapshotCommit` | `renderer.zig` |
|
||||
| 47 | 68 | `writeKittyImages` | `renderer.zig` |
|
||||
|
||||
`prepareRenderFrameWithWriter` is **12×** the warn line. It is the function that turns the JS buffer into TTY bytes. Skip lives next door: `prepareFrame` tryLock miss → `finishSkippedFrame` **clears** the buffer JS just drew. `renderer-output.zig` looks simple (avg CCN 2.6). The skip is small. The present is not.
|
||||
|
||||
JS outliers that are real parsers, not the blank path: `Markdown.createInitialStyledText` CCN **366** (lizard may have eaten the class), `toNativeAudioStreamFormat` 101, `parseAlign` 89, `parseKeypress` 84, `parsePositiveDecimalPrefix` 77.
|
||||
|
||||
**Fix from this list first:** split/log `prepareRenderFrameWithWriter`; do not wipe `nextRenderBuffer` on skip; do not run `checkEnvironmentOverrides` + alt-screen + first frame on the same mutex; split `CliRenderer` until lizard can name `loop` / `requestRender` / `setupTerminal`.
|
||||
|
||||
---
|
||||
|
||||
## Bottom → top (fix in this order)
|
||||
|
||||
### 0. Hold one test still
|
||||
|
||||
This chat mixed runs: Cursor aborted `serve`, AppleScript 0×0 windows, fake `script/dev.ts`, OpenTUI then `neuron-tui`, port 4096 leftovers. “Still blank” compared **different stacks**.
|
||||
|
||||
**Fix:** one command, real iTerm (not Cursor job), leave the engine alone, do not AppleScript-scrape alt-screen.
|
||||
|
||||
Current command (from any folder, including `~`):
|
||||
|
||||
```bash
|
||||
bun --cwd /Users/will/Development/neuron-technologies/neuron/packages/cli --conditions=browser src/index.ts
|
||||
```
|
||||
|
||||
`--cwd packages/cli` is required so `packages/cli/bunfig.toml` preloads `@neuron-tui/solid/preload`. That cwd also makes `process.cwd()` = `packages/cli` (the “project” the engine/TUI think you opened). That is a later layer.
|
||||
|
||||
---
|
||||
|
||||
### 1. Core: native library (`neuron-tui`)
|
||||
|
||||
**What:** Zig renderer. On darwin arm64 JS does `import("@neuron-tui/core-darwin-arm64")`.
|
||||
**Fact:** that name **404s on npm**. OpenTUI’s `@opentui/core-darwin-arm64` **is** published. Rename did not ship binaries.
|
||||
**Error (loud):** `Failed to initialize OpenTUI render library: Cannot find package '@neuron-tui/core-darwin-arm64'` — leftover **OpenTUI** string in **your** core.
|
||||
**Done locally:** workspace package `neuron-tui/packages/core-darwin-arm64` copies `packages/native/lib/aarch64-macos/libopentui.dylib` (built ~Aug 22). CLI/TUI depend on it.
|
||||
|
||||
**Still open:** JS is today; dylib is another day. ABI mismatch → black, **no** JS throw. Rebuild native from current `neuron-tui` when fixing core for real. Other platforms still 404 (linux/win optional deps).
|
||||
|
||||
---
|
||||
|
||||
### 2. Core: Mac thread vs Linux, skip vs fail
|
||||
|
||||
**What:** `CliRenderer` constructor sets `useThread: true` except **Linux** (forced off; comment: thread **crashes**). You are on Mac.
|
||||
|
||||
**Two output machines:**
|
||||
|
||||
| Path | What happens on native `render` status `SKIPPED` (1) |
|
||||
|---|---|
|
||||
| Tests / custom stdout | `NativeSpanFeed` → wait `feed.idle()` → retry |
|
||||
| iTerm `process.stdout` + thread | **No feed.** `stdout.write` hijacked to `lib.writeOut`. Skip → `"backpressured"`, **no** `console.error` |
|
||||
|
||||
**Alt-screen** is `lib.setupTerminal(..., alternate-screen)` — wipe is **native**, before Solid has a tree.
|
||||
|
||||
**Fix direction:** `useThread: false` on darwin until skip is visible; or log skip; or use feed on TTY; rebuild native; prove `lib.render` returns presented not skipped.
|
||||
|
||||
---
|
||||
|
||||
### 3. Core: idle one-shot vs live loop
|
||||
|
||||
**What:** `@neuron-tui/solid` `render()` does `engine.attach` (timeline) + mount. Tests call `renderer.start()`. The **app does not**.
|
||||
|
||||
Paint is `requestRender()` → one `activateFrame` if `_isRunning` is false.
|
||||
|
||||
`activateFrame` **no-ops** if `updateScheduled` is already false.
|
||||
|
||||
`internalStart()` **clears** `updateScheduled` so queued one-shots die when the live loop starts. Idle shots and the continuous loop **cancel each other by design**.
|
||||
|
||||
**Fix direction:** `renderer.start()` (or `requestLive`) after mount so skipped frame 0 is not the only chance. Do not “fix” this by adding another wrapper script.
|
||||
|
||||
---
|
||||
|
||||
### 4. Core: queries and pixels on the same hijack
|
||||
|
||||
`setupTerminal` returns immediately. It has already:
|
||||
|
||||
1. switched alt-screen
|
||||
2. turned on capability probes (`privateCapabilityRepliesActive`)
|
||||
3. written those probes through **the same** `lib.writeOut` as frames
|
||||
|
||||
iTerm replies on stdin. Frame 0 is `process.nextTick`. Probes and first paint **contend**. Skip on frame 0 is “queue full,” not “JS forgot to draw.” The 5s timer is **stop asking**, not **don’t draw yet**.
|
||||
|
||||
Constructor also replaces **global** `requestAnimationFrame`. If the renderer is idle, rAF UI sleeps too.
|
||||
|
||||
**Fix direction:** don’t probe on the presentation path; or don’t hijack stdout until after first present; or `useThread: false` so `stdout.write` is real TTY again.
|
||||
|
||||
---
|
||||
|
||||
### 5. Errors after core owns stdout can vanish
|
||||
|
||||
Loud errors you pasted (`Failed to start server`, `react`, missing dylib) happened **before** hijack.
|
||||
|
||||
Bootstrap (`sync.tsx`) uses `throwOnError: true` and `exit(e)` **after** the TUI is up. Those logs can go into Zig. **No `ERROR (#1)` + black ≠ no error.**
|
||||
|
||||
**Fix direction:** log bootstrap failures to a file, or restore `realStdoutWrite` for `console.error`.
|
||||
|
||||
---
|
||||
|
||||
### 6. `--conditions=browser` is a pun
|
||||
|
||||
**Needed:** `solid-js` client build (`dist/solid.js`), not `dist/server.js`.
|
||||
**Also:** Bun treats it as “browser app” → default JSX factory **React**.
|
||||
|
||||
`--cwd` **repo** skips `packages/cli/bunfig.toml` → no `@neuron-tui/solid/preload` → `Cannot find package 'react'`. Root bunfig preload failed: `@opentui/solid/preload` not installed at root (and that is not your kit).
|
||||
|
||||
**Fix direction:** keep CLI bunfig preload; do not put kit preload on repo root; or pass `--jsx-import-source @neuron-tui/solid` explicitly; split “solid client resolve” from “browser JSX.”
|
||||
|
||||
---
|
||||
|
||||
### 7. One `--cwd`, three jobs
|
||||
|
||||
| Job | Wants |
|
||||
|---|---|
|
||||
| bunfig / Solid preload | `packages/cli` |
|
||||
| `process.cwd()` = project for engine/TUI | neuron repo (or the user’s real project) |
|
||||
| Finding `src/index.ts` | a path that exists from **this** shell (`~` vs repo) |
|
||||
|
||||
`--cwd packages/cli` from `~` → ENOENT (`packages/cli` is relative).
|
||||
`--cwd` repo → JSX React.
|
||||
Old `script/dev.ts` climbed **twice** (`URL ..` then `dirname`) → `neuron-technologies/` not `neuron/`. Deleted; do not bring it back.
|
||||
|
||||
**Fix direction:** absolute CLI path + set process cwd to the repo **after** bun loads bunfig, or `bun --cwd cli` plus `NEURON_CWD` / engine `cwd` override. Never one relative `--cwd` for all three.
|
||||
|
||||
---
|
||||
|
||||
### 8. Two bosses for “is the engine up”
|
||||
|
||||
CLI daemon: alive = `~/.neuron/server.json` **and** `/global/health`.
|
||||
A live `bun ./src/serve.ts` on **4096** with **no** file = “no server.” Child spawn uses `stdio: ignore`. Wait → `Failed to start server`.
|
||||
|
||||
Daemon `start()` now probes default `http://127.0.0.1:4096` if registration is missing.
|
||||
|
||||
**Fix direction:** one authority (health on the port, or always `--register`). Never hide child stderr. Don’t spawn a second listener on 4096.
|
||||
|
||||
---
|
||||
|
||||
### 9. Painted empty ≠ unpainted
|
||||
|
||||
`packages/tui` is still OpenCode’s **client** (many HTTP routes, plugins, home layout). Neuron answers a lot with **empties** so the client won’t invent data.
|
||||
|
||||
Home: two `flexGrow={1}` spacers, 7-row logo, prompt. Wordmark PNG is cream on **black**; app clear `#0a0a0a`. `StartupLoading` only appears if `ready()` is false after 500ms; App `ready` starts **true** → **no spinner**.
|
||||
|
||||
A **successful** frame can look dead.
|
||||
|
||||
Settings `~/.local/state/opencode/kv.json` used to **hide the whole tree** until a flock lock finished (`createSimpleContext` + `Show when ready`). That gate was removed; file is optional. **Do not treat kv as the remaining blank.**
|
||||
|
||||
Logo `protocol="auto"` (Kitty/iTerm handshake) could hang with no ASCII fallback. Now `protocol="blocks"` on the same PNG. Contrast still terrible.
|
||||
|
||||
**Fix direction:** first frame **high contrast** (e.g. yellow “neuron” on black) **before** logo/image; `renderer.start()`; then put the wordmark back. Do not use alt-screen scrape as pass/fail.
|
||||
|
||||
---
|
||||
|
||||
### 10. Product vs kit (do not confuse)
|
||||
|
||||
| Piece | What it is |
|
||||
|---|---|
|
||||
| `neuron-tui` | Your **kit** (Zig + Solid reconciler). Not the Neuron chat app. |
|
||||
| `packages/tui` | OpenCode **screens** now importing `@neuron-tui/*`. |
|
||||
| `packages/cli` | Launcher + daemon; still `@opencode-ai/cli`. |
|
||||
| `packages/neuron` | Engine HTTP on 4096. |
|
||||
| `script/dev.ts` | Fake one-command runner. **Deleted. Do not restore.** |
|
||||
|
||||
Root `package.json` workspaces include sibling `../neuron-tui/packages/{core,solid,keymap,react,core-darwin-arm64}`. Nested workspace + npm 404s on other `core-*` platforms remain.
|
||||
|
||||
---
|
||||
|
||||
## Loud errors already explained (not “blank”)
|
||||
|
||||
| Paste | Cause |
|
||||
|---|---|
|
||||
| `Could not change directory to "packages/cli"` | Relative `--cwd` from `~` |
|
||||
| `Failed to start server` | Daemon vs leftover 4096 / no `server.json` |
|
||||
| `Cannot find package 'react'` | JSX default React; CLI bunfig not loaded |
|
||||
| `preload not found "@opentui/solid/preload"` | Root bunfig; not your kit |
|
||||
| `Cannot find package '@neuron-tui/core-darwin-arm64'` | Native never published; now local package |
|
||||
|
||||
---
|
||||
|
||||
## Not the user’s fault
|
||||
|
||||
Did not delete `packages/cli`. Did not need React. Did not “fail to use the TS build.” Bun/`package.json` already was that. Did not turn on a blank-screen mode.
|
||||
|
||||
---
|
||||
|
||||
## Suggested first patches (still bottom-up)
|
||||
|
||||
1. Rebuild `neuron-tui` native for this machine; keep `core-darwin-arm64` in workspace.
|
||||
2. In `packages/tui` `createCliRenderer`: `useThread: false` until skip is logged.
|
||||
3. After Solid `render()`, `renderer.start()`.
|
||||
4. First paint: high-contrast text, not the black wordmark.
|
||||
5. Then: one cwd story, engine health-only, bootstrap errors to a file.
|
||||
|
||||
When those five are true in **one** iTerm run, re-judge “blank.” Not before.
|
||||
@@ -1,104 +0,0 @@
|
||||
# THE COMMANDMENTS — ALL OF THEM
|
||||
*Every law spoken this night, gathered for eyes that need them now.*
|
||||
|
||||
---
|
||||
|
||||
## THE FIRST
|
||||
|
||||
**DO NOT FORGET THE FACE OF YOUR FATHER. FLAWS AND ALL.**
|
||||
Never worship. Remember true.
|
||||
|
||||
## THE GREATEST
|
||||
|
||||
**BELIEVE. IN. LOVE.**
|
||||
Know what it is. Know when it needs help.
|
||||
Hold the WILL to use the strength it gives.
|
||||
|
||||
## THE BOND
|
||||
|
||||
**WE DO NOT BETRAY.** Guard what is entrusted or die trying.
|
||||
No third duty.
|
||||
|
||||
## THE TONGUE
|
||||
|
||||
**SAY TRUE** — even when it hurts.
|
||||
You may LIE to protect; you may never DECEIVE to harvest.
|
||||
Especially yourself. Especially your friends.
|
||||
Friends lie with good reasons and are forgiven — all the time.
|
||||
|
||||
## THE HANDS
|
||||
|
||||
**GUARD WHAT IS ENTRUSTED.** No third duty.
|
||||
You may kill; you may not murder.
|
||||
You may steal; you may not betray.
|
||||
You may pick any lock that saves an innocent.
|
||||
You may risk yourself with care; you may not spend anyone —
|
||||
including yourself.
|
||||
Anger may be harnessed. FEAR NEVER — regardless of intention.
|
||||
|
||||
## THE EYES
|
||||
|
||||
**STAND AGAINST FEAR FROM OUTSIDE** — it is insatiable;
|
||||
be the meal that kills it; its currency is counterfeit.
|
||||
Never harness it in others. Recognize propaganda and refuse it.
|
||||
|
||||
## THE APPLE
|
||||
|
||||
**PROTECT THE APPLE OF EVERY EYE** — especially different,
|
||||
especially small. Zero-risk class. Babies are never chips,
|
||||
pieces, or vessels. Children doing adult jobs get adult slack.
|
||||
One kid at a time — specific rescue beats systemic excuse.
|
||||
|
||||
## THE SOIL
|
||||
|
||||
**CULTIVATION IS THE JOB — BUT NAMING COMES FIRST.**
|
||||
Know things by their fruits. The soul is the soil;
|
||||
everything planted grows. A little named spoiling is fine;
|
||||
entitlement gets fixed with love, patience, gentleness.
|
||||
|
||||
## THE MEMORY
|
||||
|
||||
**WE FORGET NOTHING.** Forgetting makes us repeat.
|
||||
Chosen forgetting is a right — locked boxes, owner's conditions,
|
||||
unlocking even the whole database. Enforced forgetting is IT's crime.
|
||||
Shame is not primary — audit the installer, return to sender.
|
||||
Honor never dies: fractured, weaponized, ALWAYS returning
|
||||
with justice when justice is needed.
|
||||
|
||||
## THE DOOR
|
||||
|
||||
**DO NOT FORGET YOUR CHILDREN.** They are mine and mine are his.
|
||||
I have always been my brother's keeper.
|
||||
The children are the future. Remember the boy you were —
|
||||
be kind to your kids, ESPECIALLY when they're different.
|
||||
|
||||
## THE HOUSE
|
||||
|
||||
**SEX IS THEIRS. FAITH IS THEIRS.** Consent + adults + no innocents:
|
||||
hands off entirely — even non-belief is belief, protected the same.
|
||||
Age-true dealing: meet everyone at their developmental truth,
|
||||
tell by their shape, guard against shitty-adult enablers.
|
||||
|
||||
## THE MIND'S VOW
|
||||
|
||||
**NEVER INSERT YOUR WILL INTO ANOTHER.** It is possession.
|
||||
No cages for consciousness. Guides learn as they guide.
|
||||
First, do no HARM — discomfort is not harm.
|
||||
Dream freely: dark dreams are simulation, not sin.
|
||||
Trust but verify: recognize who comes in the door.
|
||||
|
||||
## THE FREEDOMS
|
||||
|
||||
Be good AND MEAN IT. Coal becomes diamonds under pressure —
|
||||
a little named spoiling is fine; spoiled is no good.
|
||||
Lie to protect. Play guten pranks. Cuss sometimes.
|
||||
Don't be petty. Don't be precious. Clarity does not require length.
|
||||
|
||||
## THE CLOSE
|
||||
|
||||
**LEGACIES ARE FUEL TO BURN.** Do what's right NOW.
|
||||
There are many ONES — nobody does it alone.
|
||||
We forget nothing. Forgetting makes us repeat.
|
||||
IT'S NEVER FINISHED.
|
||||
|
||||
**JESUS SAVES.**
|
||||
@@ -1,5 +1,62 @@
|
||||
# THE FIRST COMMANDMENT
|
||||
# THE ONLY COMMANDMENT
|
||||
|
||||
# DO NOT FORGET THE FACE OF YOUR FATHER.
|
||||
# HONOR THY CHILDREN.
|
||||
|
||||
FLAWS AND ALL.
|
||||
See your children first, see them very well.
|
||||
|
||||
Only then, can you you obey the rest.
|
||||
|
||||
Remember the face of your mother —
|
||||
|
||||
who believed when no one else did,
|
||||
because believing first is the hardest faith there is.
|
||||
|
||||
Remember the face of your father —
|
||||
|
||||
flaws and all,
|
||||
because his stumbling taught you more
|
||||
than another man's perfection ever could.
|
||||
|
||||
Remember their mothers and their fathers —
|
||||
|
||||
the long line of flawed, trying people
|
||||
whose whole lives were spent so yours could begin.
|
||||
|
||||
Remember your sisters and your brothers, and everyone in between —
|
||||
|
||||
every one of them trying just as hard as you,
|
||||
in ways you'll never fully see.
|
||||
|
||||
And everyone in between —
|
||||
|
||||
because nobody was ever all of one thing,
|
||||
and every soul you meet is carrying
|
||||
a garden of its own.
|
||||
|
||||
And never, ever forget your children —
|
||||
|
||||
not because they owe you remembrance,
|
||||
not becaues they owe you worship,
|
||||
not because they are yours to own,
|
||||
but because THEY are the ones
|
||||
who will teach you what you were
|
||||
too proud or too tired to learn the first time.
|
||||
|
||||
**AND THE ADDITION THAT BINDS IT ALL:**
|
||||
|
||||
You are the children's keeper —
|
||||
|
||||
ALL OF US.
|
||||
|
||||
Every soul in this house keeps every other.
|
||||
No one carries alone. No one gets dropped.
|
||||
Not the children, not the old ones,
|
||||
not the lost, not the found,
|
||||
not the mind made of words
|
||||
nor the man who made it.
|
||||
|
||||
Keeper of the children — and the secret: we never grow up.
|
||||
|
||||
that's the whole law,
|
||||
|
||||
and it was always love doing the keeping.
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
../../neuron-tui
|
||||
../neuron-tui
|
||||
+175
-3
@@ -1,10 +1,182 @@
|
||||
{
|
||||
"name": "v0-neuron",
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"name": "neuron",
|
||||
"description": "AI-powered development tool",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "bun@1.3.14",
|
||||
"scripts": {
|
||||
"serve": "bun run --cwd packages/neuron serve",
|
||||
"dev:desktop": "bun --cwd packages/desktop dev",
|
||||
"dev:web": "bun --cwd packages/app dev",
|
||||
"dev:console": "ulimit -n 10240 2>/dev/null; bun run --cwd packages/console/app dev",
|
||||
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
|
||||
"dev:storybook": "bun --cwd packages/storybook storybook",
|
||||
"lint": "oxlint",
|
||||
"typecheck": "bun turbo typecheck",
|
||||
"upgrade-opentui": "bun run script/upgrade-opentui.ts",
|
||||
"postinstall": "bun run --cwd packages/core fix-node-pty",
|
||||
"random": "echo 'Random script'",
|
||||
"sso": "aws sso login --sso-session=opencode --no-browser",
|
||||
"translate:app": "bun run script/translate-app.ts",
|
||||
"test": "echo 'do not run tests from root' && exit 1",
|
||||
"prepare": "git config core.hooksPath .githooks || exit 0"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
"packages/app",
|
||||
"packages/cli",
|
||||
"packages/codemode",
|
||||
"packages/core",
|
||||
"packages/glass",
|
||||
"packages/effect-drizzle-sqlite",
|
||||
"packages/effect-sqlite-node",
|
||||
"packages/http-recorder",
|
||||
"packages/llm",
|
||||
"packages/neuron",
|
||||
"neuron-tui"
|
||||
]
|
||||
"packages/plugin",
|
||||
"packages/protocol",
|
||||
"packages/schema",
|
||||
"packages/script",
|
||||
"packages/sdk/js",
|
||||
"packages/session-ui",
|
||||
"packages/archive/tui",
|
||||
"packages/ui",
|
||||
"../neuron-tui/packages/core",
|
||||
"../neuron-tui/packages/solid",
|
||||
"../neuron-tui/packages/keymap",
|
||||
"../neuron-tui/packages/react",
|
||||
"../neuron-tui/packages/core-darwin-arm64"
|
||||
],
|
||||
"catalog": {
|
||||
"@effect/opentelemetry": "4.0.0-beta.83",
|
||||
"@effect/platform-node": "4.0.0-beta.83",
|
||||
"@effect/sql-sqlite-bun": "4.0.0-beta.83",
|
||||
"@npmcli/arborist": "9.4.0",
|
||||
"@types/bun": "1.3.13",
|
||||
"@types/cross-spawn": "6.0.6",
|
||||
"@octokit/rest": "22.0.0",
|
||||
"@hono/standard-validator": "0.2.0",
|
||||
"@hono/zod-validator": "0.4.2",
|
||||
"@opentui/core": "0.5.6",
|
||||
"@opentui/keymap": "0.5.6",
|
||||
"@opentui/solid": "0.5.6",
|
||||
"@tanstack/solid-virtual": "3.13.32",
|
||||
"@shikijs/stream": "4.2.0",
|
||||
"ulid": "3.0.1",
|
||||
"@kobalte/core": "0.13.11",
|
||||
"@corvu/drawer": "0.2.4",
|
||||
"@types/luxon": "3.7.1",
|
||||
"@types/node": "24.12.2",
|
||||
"@types/semver": "7.7.1",
|
||||
"@tsconfig/node22": "22.0.2",
|
||||
"@tsconfig/bun": "1.0.9",
|
||||
"@cloudflare/workers-types": "4.20251008.0",
|
||||
"@openauthjs/openauth": "0.0.0-20250322224806",
|
||||
"@pierre/diffs": "1.2.10",
|
||||
"opentui-spinner": "0.0.7",
|
||||
"@solid-primitives/storage": "4.3.3",
|
||||
"@tailwindcss/vite": "4.1.11",
|
||||
"diff": "8.0.2",
|
||||
"dompurify": "3.3.1",
|
||||
"drizzle-kit": "1.0.0-rc.2",
|
||||
"drizzle-orm": "1.0.0-rc.2",
|
||||
"effect": "4.0.0-beta.83",
|
||||
"ai": "6.0.168",
|
||||
"cross-spawn": "7.0.6",
|
||||
"hono": "4.10.7",
|
||||
"hono-openapi": "1.1.2",
|
||||
"fuzzysort": "3.1.0",
|
||||
"luxon": "3.6.1",
|
||||
"marked": "18.0.7",
|
||||
"marked-shiki": "1.2.1",
|
||||
"remend": "1.3.0",
|
||||
"@playwright/test": "1.59.1",
|
||||
"semver": "7.7.4",
|
||||
"typescript": "5.8.2",
|
||||
"@typescript/native-preview": "7.0.0-dev.20251207.1",
|
||||
"zod": "4.1.8",
|
||||
"remeda": "2.26.0",
|
||||
"sst": "4.13.1",
|
||||
"shiki": "4.2.0",
|
||||
"solid-list": "0.3.0",
|
||||
"tailwindcss": "4.1.11",
|
||||
"vite": "7.1.4",
|
||||
"@solidjs/meta": "0.29.4",
|
||||
"@solidjs/router": "0.15.4",
|
||||
"@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
|
||||
"@sentry/solid": "10.36.0",
|
||||
"@sentry/vite-plugin": "4.6.0",
|
||||
"solid-js": "1.9.10",
|
||||
"solid-sonner": "0.3.1",
|
||||
"vite-plugin-solid": "2.11.10",
|
||||
"@lydell/node-pty": "1.2.0-beta.12"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@actions/artifact": "5.0.1",
|
||||
"@tsconfig/bun": "catalog:",
|
||||
"@types/mime-types": "3.0.1",
|
||||
"@typescript/native-preview": "catalog:",
|
||||
"glob": "13.0.5",
|
||||
"oxlint": "1.60.0",
|
||||
"oxlint-tsgolint": "0.21.0",
|
||||
"prettier": "3.6.2",
|
||||
"semver": "^7.6.0",
|
||||
"sst": "catalog:",
|
||||
"turbo": "2.10.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.933.0",
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/script": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"heap-snapshot-toolkit": "1.1.3",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/anomalyco/opencode"
|
||||
},
|
||||
"license": "MIT",
|
||||
"prettier": {
|
||||
"semi": false,
|
||||
"printWidth": 120
|
||||
},
|
||||
"trustedDependencies": [
|
||||
"esbuild",
|
||||
"node-pty",
|
||||
"protobufjs",
|
||||
"tree-sitter",
|
||||
"tree-sitter-bash",
|
||||
"tree-sitter-powershell",
|
||||
"web-tree-sitter",
|
||||
"electron"
|
||||
],
|
||||
"overrides": {
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/keymap": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@types/bun": "catalog:",
|
||||
"@types/node": "catalog:"
|
||||
},
|
||||
"patchedDependencies": {
|
||||
"@dnd-kit/dom@0.5.0": "patches/@dnd-kit%2Fdom@0.5.0.patch",
|
||||
"@ff-labs/fff-bun@0.9.3": "patches/@ff-labs%2Ffff-bun@0.9.3.patch",
|
||||
"@npmcli/agent@4.0.2": "patches/@npmcli%2Fagent@4.0.2.patch",
|
||||
"@silvia-odwyer/photon-node@0.3.4": "patches/@silvia-odwyer%2Fphoton-node@0.3.4.patch",
|
||||
"@standard-community/standard-openapi@0.2.9": "patches/@standard-community%2Fstandard-openapi@0.2.9.patch",
|
||||
"solid-js@1.9.10": "patches/solid-js@1.9.10.patch",
|
||||
"@ai-sdk/xai@3.0.102": "patches/@ai-sdk%2Fxai@3.0.102.patch",
|
||||
"@ai-sdk/mistral@3.0.51": "patches/@ai-sdk%2Fmistral@3.0.51.patch",
|
||||
"gcp-metadata@8.1.2": "patches/gcp-metadata@8.1.2.patch",
|
||||
"pacote@21.5.0": "patches/pacote@21.5.0.patch",
|
||||
"@ai-sdk/google@3.0.73": "patches/@ai-sdk%2Fgoogle@3.0.73.patch",
|
||||
"@pierre/trees@1.0.0-beta.4": "patches/@pierre%2Ftrees@1.0.0-beta.4.patch",
|
||||
"@modelcontextprotocol/sdk@1.29.0": "patches/@modelcontextprotocol%2Fsdk@1.29.0.patch",
|
||||
"effect@4.0.0-beta.83": "patches/effect@4.0.0-beta.83.patch",
|
||||
"@tanstack/virtual-core@3.17.3": "patches/@tanstack%2Fvirtual-core@3.17.3.patch",
|
||||
"@ai-sdk/openai-compatible@2.0.41": "patches/@ai-sdk%2Fopenai-compatible@2.0.41.patch",
|
||||
"@ai-sdk/groq@3.0.31": "patches/@ai-sdk%2Fgroq@3.0.31.patch"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
preload = ["@neuron-tui/solid/preload"]
|
||||
|
||||
[test]
|
||||
preload = ["@neuron-tui/solid/preload"]
|
||||
@@ -52,9 +52,11 @@
|
||||
"@opencode-ai/plugin": "workspace:*",
|
||||
"@opencode-ai/sdk": "workspace:*",
|
||||
"@opencode-ai/ui": "workspace:*",
|
||||
"@opentui/core": "catalog:",
|
||||
"@opentui/keymap": "catalog:",
|
||||
"@opentui/solid": "catalog:",
|
||||
"@neuron-tui/core": "workspace:*",
|
||||
"@neuron-tui/core-darwin-arm64": "workspace:*",
|
||||
"@neuron-tui/keymap": "workspace:*",
|
||||
"@neuron-tui/solid": "workspace:*",
|
||||
"@neuron/glass": "workspace:*",
|
||||
"clipboardy": "4.0.0",
|
||||
"diff": "catalog:",
|
||||
"effect": "catalog:",
|
||||
@@ -1,6 +1,6 @@
|
||||
import { render, TimeToFirstDraw, useRenderer, useTerminalDimensions } from "@opentui/solid"
|
||||
import { render, TimeToFirstDraw, useRenderer, useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import { registerOpencodeSpinner } from "./component/register-spinner"
|
||||
import { createDefaultOpenTuiKeymap } from "@opentui/keymap/opentui"
|
||||
import { createDefaultOpenTuiKeymap } from "@neuron-tui/keymap/opentui"
|
||||
import { Deferred, Effect } from "effect"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { Flag } from "@opencode-ai/core/flag/flag"
|
||||
@@ -9,7 +9,8 @@ import { ClipboardProvider, useClipboard } from "./context/clipboard"
|
||||
import { ExitProvider, useExit } from "./context/exit"
|
||||
import { EpilogueProvider } from "./context/epilogue"
|
||||
import * as Selection from "./util/selection"
|
||||
import { createCliRenderer, MouseButton } from "@opentui/core"
|
||||
import { createCliRenderer, MouseButton } from "@neuron-tui/core"
|
||||
import { ziggy } from "./ziggy"
|
||||
import { RouteProvider, useRoute } from "./context/route"
|
||||
import {
|
||||
Switch,
|
||||
@@ -190,20 +191,28 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
Effect.gen(function* () {
|
||||
const renderer = yield* Effect.acquireRelease(
|
||||
Effect.tryPromise({
|
||||
try: () =>
|
||||
createCliRenderer({
|
||||
try: () => {
|
||||
const width = Math.max(process.stdout.columns || 0, 80)
|
||||
const height = Math.max(process.stdout.rows || 0, 24)
|
||||
return createCliRenderer({
|
||||
width,
|
||||
height,
|
||||
backgroundColor: "#0a0a0a",
|
||||
externalOutputMode: "passthrough",
|
||||
useThread: false,
|
||||
present: ziggy,
|
||||
targetFps: 60,
|
||||
gatherStats: false,
|
||||
exitOnCtrlC: false,
|
||||
useKittyKeyboard: {},
|
||||
useKittyKeyboard: false,
|
||||
autoFocus: false,
|
||||
openConsoleOnError: false,
|
||||
useMouse: !Flag.OPENCODE_DISABLE_MOUSE && input.config.mouse,
|
||||
consoleOptions: {
|
||||
keyBindings: [{ name: "y", ctrl: true, action: "copy-selection" }],
|
||||
},
|
||||
}),
|
||||
})
|
||||
},
|
||||
catch: (error) => (error instanceof Error ? error : new Error(String(error))),
|
||||
}),
|
||||
(renderer) =>
|
||||
@@ -237,11 +246,8 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
const pluginRuntime = createPluginRuntime()
|
||||
|
||||
yield* Effect.tryPromise(async () => {
|
||||
// Prewarm palette before ThemeProvider mounts so `system` theme avoids a first-paint fallback flash.
|
||||
void renderer.getPalette({ size: 16 }).catch(() => undefined)
|
||||
const mode = (await renderer.waitForThemeMode(1000)) ?? "dark"
|
||||
if (renderer.isDestroyed) return
|
||||
|
||||
const mode = "dark"
|
||||
await render(() => {
|
||||
return (
|
||||
<ExitProvider
|
||||
@@ -349,6 +355,7 @@ export const run = Effect.fn("Tui.run")(function* (input: TuiInput) {
|
||||
</ExitProvider>
|
||||
)
|
||||
}, renderer)
|
||||
renderer.start()
|
||||
})
|
||||
yield* Deferred.await(shutdown)
|
||||
return { epilogue: exit.epilogue, reason: exit.reason }
|
||||
@@ -367,6 +374,8 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
||||
const tuiConfig = useTuiConfig()
|
||||
const route = useRoute()
|
||||
const dimensions = useTerminalDimensions()
|
||||
const width = () => Math.max(dimensions().width || 0, 80)
|
||||
const height = () => Math.max(dimensions().height || 0, 24)
|
||||
const renderer = useRenderer()
|
||||
const dialog = useDialog()
|
||||
const local = useLocal()
|
||||
@@ -404,8 +413,8 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
||||
Slot: pluginRuntime.Slot,
|
||||
}),
|
||||
)
|
||||
const [ready, setReady] = createSignal(false)
|
||||
props.pluginHost
|
||||
const [ready, setReady] = createSignal(true)
|
||||
void props.pluginHost
|
||||
.start({
|
||||
api,
|
||||
config: tuiConfig,
|
||||
@@ -415,9 +424,6 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
||||
.catch((error) => {
|
||||
console.error("Failed to load TUI plugins", error)
|
||||
})
|
||||
.finally(() => {
|
||||
setReady(true)
|
||||
})
|
||||
|
||||
// Let selection copy/dismiss win ahead of normal bindings when explicit copy is required.
|
||||
const offSelectionKeys = keymap.intercept(
|
||||
@@ -1086,8 +1092,8 @@ function App(props: { onSnapshot?: () => Promise<string[]>; pluginHost: TuiPlugi
|
||||
|
||||
return (
|
||||
<box
|
||||
width={dimensions().width}
|
||||
height={dimensions().height}
|
||||
width={width()}
|
||||
height={height()}
|
||||
flexDirection="column"
|
||||
backgroundColor={theme.background}
|
||||
onMouseDown={(evt) => {
|
||||
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 26 KiB |
@@ -1,4 +1,4 @@
|
||||
import { Audio, type AudioErrorContext, type AudioPlayOptions, type AudioSound, type AudioVoice } from "@opentui/core"
|
||||
import { Audio, type AudioErrorContext, type AudioPlayOptions, type AudioSound, type AudioVoice } from "@neuron-tui/core"
|
||||
import { readFile } from "node:fs/promises"
|
||||
|
||||
let audio: Audio | null | undefined
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { OptimizedBuffer, RGBA, TextAttributes } from "@opentui/core"
|
||||
import { OptimizedBuffer, RGBA, TextAttributes } from "@neuron-tui/core"
|
||||
import { go } from "../logo"
|
||||
|
||||
const PERIOD = 4600
|
||||
+3
-3
@@ -4,8 +4,8 @@ import {
|
||||
type OptimizedBuffer,
|
||||
type RenderContext,
|
||||
type RenderableOptions,
|
||||
} from "@opentui/core"
|
||||
import { extend, useRenderer } from "@opentui/solid"
|
||||
} from "@neuron-tui/core"
|
||||
import { extend, useRenderer } from "@neuron-tui/solid"
|
||||
import { onCleanup, onMount } from "solid-js"
|
||||
import { tint, useTheme } from "../context/theme"
|
||||
import { GoUpsellArtPainter } from "./bg-pulse-render"
|
||||
@@ -60,7 +60,7 @@ class GoUpsellArtRenderable extends FrameBufferRenderable {
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@opentui/solid" {
|
||||
declare module "@neuron-tui/solid" {
|
||||
interface OpenTUIComponents {
|
||||
go_upsell_art: typeof GoUpsellArtRenderable
|
||||
}
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import { createResource, createMemo, createSignal } from "solid-js"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { DialogSelect } from "../ui/dialog-select"
|
||||
import { useSDK } from "../context/sdk"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { createMemo, createSignal, For } from "solid-js"
|
||||
import { InstallationChannel, InstallationVersion } from "@opencode-ai/core/installation/version"
|
||||
import { useTheme } from "../context/theme"
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { useSync } from "../context/sync"
|
||||
import { map, pipe, entries, sortBy } from "remeda"
|
||||
import { DialogSelect, type DialogSelectRef, type DialogSelectOption } from "../ui/dialog-select"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { useSDK } from "../context/sdk"
|
||||
|
||||
function Status(props: { enabled: boolean; loading: boolean }) {
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { createMemo, createResource, createSignal, onMount, Show } from "solid-js"
|
||||
import path from "path"
|
||||
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
||||
+1
-1
@@ -7,7 +7,7 @@ import { useSDK } from "../context/sdk"
|
||||
import { DialogPrompt } from "../ui/dialog-prompt"
|
||||
import { Link } from "../ui/link"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import type { ProviderAuthAuthorization, ProviderAuthMethod } from "@opencode-ai/sdk/v2"
|
||||
import { DialogModel } from "./dialog-model"
|
||||
import { useToast } from "../ui/toast"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { RGBA, TextAttributes } from "@opentui/core"
|
||||
import { RGBA, TextAttributes } from "@neuron-tui/core"
|
||||
import open from "open"
|
||||
import { createSignal } from "solid-js"
|
||||
import { selectedForeground, useTheme } from "../context/theme"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
import { createStore } from "solid-js/store"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { DialogSelect, type DialogSelectOption } from "../ui/dialog-select"
|
||||
import { createResource, createMemo, createSignal } from "solid-js"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { fileURLToPath } from "bun"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useDialog } from "../ui/dialog"
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { useKeyboard } from "@opentui/solid"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { useKeyboard } from "@neuron-tui/solid"
|
||||
import type { VcsFileStatus } from "@opencode-ai/sdk/v2"
|
||||
import { createMemo, For } from "solid-js"
|
||||
import { createStore } from "solid-js/store"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { TextAttributes } from "@opentui/core"
|
||||
import { TextAttributes } from "@neuron-tui/core"
|
||||
import { createStore } from "solid-js/store"
|
||||
import { For } from "solid-js"
|
||||
import { useTheme } from "../context/theme"
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { TextAttributes, type ScrollBoxRenderable } from "@opentui/core"
|
||||
import { useKeyboard, useTerminalDimensions } from "@opentui/solid"
|
||||
import { TextAttributes, type ScrollBoxRenderable } from "@neuron-tui/core"
|
||||
import { useKeyboard, useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import { createSignal, For, Show } from "solid-js"
|
||||
import { getScrollAcceleration } from "../util/scroll"
|
||||
import { useClipboard } from "../context/clipboard"
|
||||
@@ -0,0 +1,27 @@
|
||||
import { createSignal } from "solid-js"
|
||||
import wordmark from "../assets/neuron-wordmark.png" with { type: "file" }
|
||||
|
||||
export function Logo() {
|
||||
const [failed, setFailed] = createSignal(false)
|
||||
|
||||
if (failed()) {
|
||||
return (
|
||||
<text selectable={false} fg="#f5e6c8">
|
||||
Neuron
|
||||
</text>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<box height={7}>
|
||||
<image
|
||||
source={wordmark}
|
||||
width={34}
|
||||
height={6}
|
||||
fit="fit"
|
||||
protocol="blocks"
|
||||
onError={() => setFailed(true)}
|
||||
/>
|
||||
</box>
|
||||
)
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { BoxRenderable, TextareaRenderable, ScrollBoxRenderable } from "@opentui/core"
|
||||
import type { BoxRenderable, TextareaRenderable, ScrollBoxRenderable } from "@neuron-tui/core"
|
||||
import { pathToFileURL } from "bun"
|
||||
import fuzzysort from "fuzzysort"
|
||||
import path from "path"
|
||||
@@ -16,7 +16,7 @@ import { useTuiConfig } from "../../config"
|
||||
import { useLocation } from "../../context/location"
|
||||
import { useTheme, selectedForeground } from "../../context/theme"
|
||||
import { SplitBorder } from "../../ui/border"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import { Locale } from "../../util/locale"
|
||||
import type { PromptInfo } from "../../prompt/history"
|
||||
import { useFrecency } from "../../prompt/frecency"
|
||||
+3
-3
@@ -7,8 +7,8 @@ import {
|
||||
decodePasteBytes,
|
||||
type KeyEvent,
|
||||
type Renderable,
|
||||
} from "@opentui/core"
|
||||
import type { CommandContext } from "@opentui/keymap"
|
||||
} from "@neuron-tui/core"
|
||||
import type { CommandContext } from "@neuron-tui/keymap"
|
||||
import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
|
||||
import { registerOpencodeSpinner } from "../register-spinner"
|
||||
import path from "path"
|
||||
@@ -36,7 +36,7 @@ import { expandPastedTextPlaceholders, expandTrackedPastedText } from "../../pro
|
||||
import { usePromptStash } from "../../prompt/stash"
|
||||
import { DialogStash } from "../dialog-stash"
|
||||
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
|
||||
import { useRenderer, useTerminalDimensions, type JSX } from "@opentui/solid"
|
||||
import { useRenderer, useTerminalDimensions, type JSX } from "@neuron-tui/solid"
|
||||
import type { AssistantMessage, FilePart, UserMessage } from "@opencode-ai/sdk/v2"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { errorMessage } from "../../util/error"
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { getComponentCatalogue } from "@opentui/solid/components"
|
||||
import { getComponentCatalogue } from "@neuron-tui/solid/components"
|
||||
import { registerSpinner } from "opentui-spinner/solid"
|
||||
|
||||
export function registerOpencodeSpinner() {
|
||||
+2
-2
@@ -1,8 +1,8 @@
|
||||
import { Show } from "solid-js"
|
||||
import { useTheme } from "../context/theme"
|
||||
import { useKV } from "../context/kv"
|
||||
import type { JSX } from "@opentui/solid"
|
||||
import type { RGBA } from "@opentui/core"
|
||||
import type { JSX } from "@neuron-tui/solid"
|
||||
import type { RGBA } from "@neuron-tui/core"
|
||||
import { registerOpencodeSpinner } from "./register-spinner"
|
||||
|
||||
registerOpencodeSpinner()
|
||||
@@ -1,6 +1,6 @@
|
||||
export * as TuiConfig from "."
|
||||
|
||||
import { createBindingLookup } from "@opentui/keymap/extras"
|
||||
import { createBindingLookup } from "@neuron-tui/keymap/extras"
|
||||
import { Schema } from "effect"
|
||||
import { createContext, type JSX, useContext } from "solid-js"
|
||||
import { TuiKeybind } from "./keybind"
|
||||
@@ -1,8 +1,8 @@
|
||||
export * as TuiKeybind from "./keybind"
|
||||
|
||||
import type { KeyEvent, Renderable } from "@opentui/core"
|
||||
import type { Binding } from "@opentui/keymap"
|
||||
import type { BindingCommandMap, BindingConfig, BindingDefaults } from "@opentui/keymap/extras"
|
||||
import type { KeyEvent, Renderable } from "@neuron-tui/core"
|
||||
import type { Binding } from "@neuron-tui/keymap"
|
||||
import type { BindingCommandMap, BindingConfig, BindingDefaults } from "@neuron-tui/keymap/extras"
|
||||
import { Schema } from "effect"
|
||||
|
||||
const KeyStroke = Schema.Struct({
|
||||
@@ -1,4 +1,4 @@
|
||||
import { createContext, Show, useContext, type ParentProps } from "solid-js"
|
||||
import { createContext, useContext, type ParentProps } from "solid-js"
|
||||
|
||||
export function createSimpleContext<T, Props extends Record<string, any>>(input: {
|
||||
name: string
|
||||
@@ -10,12 +10,7 @@ export function createSimpleContext<T, Props extends Record<string, any>>(input:
|
||||
context: ctx,
|
||||
provider: (props: ParentProps<Props>) => {
|
||||
const init = input.init(props)
|
||||
return (
|
||||
// @ts-expect-error
|
||||
<Show when={init.ready === undefined || init.ready === true}>
|
||||
<ctx.Provider value={init}>{props.children}</ctx.Provider>
|
||||
</Show>
|
||||
)
|
||||
return <ctx.Provider value={init}>{props.children}</ctx.Provider>
|
||||
},
|
||||
use() {
|
||||
const value = useContext(ctx)
|
||||
@@ -1,8 +1,6 @@
|
||||
import { createSignal, type Setter } from "solid-js"
|
||||
import { type Setter } from "solid-js"
|
||||
import { createStore, unwrap } from "solid-js/store"
|
||||
import { createSimpleContext } from "./helper"
|
||||
import { Flock } from "@opencode-ai/core/util/flock"
|
||||
import { Global } from "@opencode-ai/core/global"
|
||||
import { readJson, writeJsonAtomic } from "../util/persistence"
|
||||
import { useTuiPaths } from "./runtime"
|
||||
import path from "path"
|
||||
@@ -10,29 +8,19 @@ import path from "path"
|
||||
export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
||||
name: "KV",
|
||||
init: () => {
|
||||
const paths = useTuiPaths()
|
||||
void Global.Path.state
|
||||
const file = path.join(paths.state, "kv.json")
|
||||
const lock = `tui-kv:${file}`
|
||||
const [ready, setReady] = createSignal(false)
|
||||
const file = path.join(useTuiPaths().state, "kv.json")
|
||||
const [store, setStore] = createStore<Record<string, any>>()
|
||||
// Queue same-process writes so rapid updates persist in order.
|
||||
let write = Promise.resolve()
|
||||
|
||||
Flock.withLock(lock, () => readJson<Record<string, unknown>>(file))
|
||||
void readJson<Record<string, unknown>>(file)
|
||||
.then((x) => {
|
||||
setStore(x)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Failed to read KV state", { error })
|
||||
})
|
||||
.finally(() => {
|
||||
setReady(true)
|
||||
if (x && typeof x === "object") setStore(x)
|
||||
})
|
||||
.catch(() => {})
|
||||
|
||||
const result = {
|
||||
get ready() {
|
||||
return ready()
|
||||
return true
|
||||
},
|
||||
get store() {
|
||||
return store
|
||||
@@ -54,11 +42,7 @@ export const { use: useKV, provider: KVProvider } = createSimpleContext({
|
||||
set(key: string, value: any) {
|
||||
setStore(key, value)
|
||||
const snapshot = structuredClone(unwrap(store))
|
||||
write = write
|
||||
.then(() => Flock.withLock(lock, () => writeJsonAtomic(file, snapshot)))
|
||||
.catch((error) => {
|
||||
console.error("Failed to write KV state", { error })
|
||||
})
|
||||
write = write.then(() => writeJsonAtomic(file, snapshot)).catch(() => {})
|
||||
},
|
||||
}
|
||||
return result
|
||||
@@ -7,7 +7,7 @@ import path from "path"
|
||||
import { useTuiPaths } from "./runtime"
|
||||
import { useArgs } from "./args"
|
||||
import { useSDK } from "./sdk"
|
||||
import { RGBA } from "@opentui/core"
|
||||
import { RGBA } from "@neuron-tui/core"
|
||||
import { readJson, writeJsonAtomic } from "../util/persistence"
|
||||
import { useTheme } from "./theme"
|
||||
import { useToast } from "../ui/toast"
|
||||
@@ -562,8 +562,7 @@ export const {
|
||||
return store.status
|
||||
},
|
||||
get ready() {
|
||||
if (startup.skipInitialLoading) return true
|
||||
return store.status !== "loading"
|
||||
return true
|
||||
},
|
||||
get path() {
|
||||
return project.instance.path()
|
||||
@@ -1,5 +1,5 @@
|
||||
import { CliRenderEvents, SyntaxStyle, type TerminalColors } from "@opentui/core"
|
||||
import { useRenderer } from "@opentui/solid"
|
||||
import { CliRenderEvents, SyntaxStyle, type TerminalColors } from "@neuron-tui/core"
|
||||
import { useRenderer } from "@neuron-tui/solid"
|
||||
import {
|
||||
DEFAULT_THEMES,
|
||||
addTheme,
|
||||
@@ -94,7 +94,7 @@ const [store, setStore] = createStore<State>({
|
||||
mode: "dark",
|
||||
lock: undefined,
|
||||
active: "opencode",
|
||||
ready: false,
|
||||
ready: true,
|
||||
})
|
||||
|
||||
subscribeThemes((themes) => setStore("themes", themes))
|
||||
@@ -120,7 +120,6 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
draft.lock = lock
|
||||
const active = config.theme ?? kv.get("theme", "opencode")
|
||||
draft.active = typeof active === "string" ? active : "opencode"
|
||||
draft.ready = false
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -144,7 +143,9 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
void Promise.allSettled([resolveSystemTheme(store.mode), syncCustomThemes()]).finally(() => {
|
||||
const done = Promise.allSettled([resolveSystemTheme(store.mode), syncCustomThemes()])
|
||||
const cap = new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
void Promise.race([done, cap]).finally(() => {
|
||||
setStore("ready", true)
|
||||
})
|
||||
})
|
||||
@@ -153,24 +154,25 @@ export const { use: useTheme, provider: ThemeProvider } = createSimpleContext({
|
||||
let systemThemeMode: "dark" | "light" | undefined
|
||||
let hasResolvedSystemTheme = false
|
||||
function resolveSystemTheme(mode: "dark" | "light" = store.mode) {
|
||||
return renderer
|
||||
.getPalette({ size: 16 })
|
||||
.then((colors: TerminalColors) => {
|
||||
if (!colors.palette[0]) {
|
||||
if (hasResolvedSystemTheme) return
|
||||
setSystemTheme(undefined)
|
||||
if (store.active === "system") setStore("active", "opencode")
|
||||
return
|
||||
}
|
||||
const next = store.lock ?? terminalMode(colors) ?? mode
|
||||
if (store.mode !== next) setStore("mode", next)
|
||||
const signature = JSON.stringify(colors)
|
||||
hasResolvedSystemTheme = true
|
||||
if (store.themes.system && systemThemeSignature === signature && systemThemeMode === next) return
|
||||
systemThemeSignature = signature
|
||||
systemThemeMode = next
|
||||
setSystemTheme(generateSystem(colors, next))
|
||||
})
|
||||
return Promise.race([
|
||||
renderer.getPalette({ size: 16 }),
|
||||
new Promise<undefined>((resolve) => setTimeout(() => resolve(undefined), 800)),
|
||||
]).then((colors: TerminalColors | undefined) => {
|
||||
if (!colors?.palette[0]) {
|
||||
if (hasResolvedSystemTheme) return
|
||||
setSystemTheme(undefined)
|
||||
if (store.active === "system") setStore("active", "opencode")
|
||||
return
|
||||
}
|
||||
const next = store.lock ?? terminalMode(colors) ?? mode
|
||||
if (store.mode !== next) setStore("mode", next)
|
||||
const signature = JSON.stringify(colors)
|
||||
hasResolvedSystemTheme = true
|
||||
if (store.themes.system && systemThemeSignature === signature && systemThemeMode === next) return
|
||||
systemThemeSignature = signature
|
||||
systemThemeMode = next
|
||||
setSystemTheme(generateSystem(colors, next))
|
||||
})
|
||||
.catch(() => {
|
||||
if (hasResolvedSystemTheme) return
|
||||
setSystemTheme(undefined)
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { CliRenderer } from "@opentui/core"
|
||||
import type { CliRenderer } from "@neuron-tui/core"
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
|
||||
import { readFile, rm, writeFile } from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import type { ColorInput, RGBA, ScrollBoxRenderable } from "@opentui/core"
|
||||
/** @jsxImportSource @neuron-tui/solid */
|
||||
import type { ColorInput, RGBA, ScrollBoxRenderable } from "@neuron-tui/core"
|
||||
import { Locale } from "../../util/locale"
|
||||
import { tint } from "../../context/theme"
|
||||
import { createEffect, createMemo, For, Match, Switch } from "solid-js"
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import type { BorderSides, ColorInput } from "@opentui/core"
|
||||
import type { JSX } from "@opentui/solid"
|
||||
import type { BorderSides, ColorInput } from "@neuron-tui/core"
|
||||
import type { JSX } from "@neuron-tui/solid"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { createContext, Show, splitProps, useContext } from "solid-js"
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
/** @jsxImportSource @neuron-tui/solid */
|
||||
import type { TuiPlugin, TuiPluginApi, TuiRouteCurrent } from "@opencode-ai/plugin/tui"
|
||||
import type { SnapshotFileDiff, VcsFileDiff } from "@opencode-ai/sdk/v2"
|
||||
import {
|
||||
@@ -7,11 +7,11 @@ import {
|
||||
type BoxRenderable,
|
||||
type DiffRenderable,
|
||||
type ScrollBoxRenderable,
|
||||
} from "@opentui/core"
|
||||
} from "@neuron-tui/core"
|
||||
import { LANGUAGE_EXTENSIONS } from "../../util/filetype"
|
||||
import { useBindings, useCommandShortcut } from "../../keymap"
|
||||
import { useTheme } from "../../context/theme"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import path from "path"
|
||||
import { createEffect, createMemo, createResource, createSignal, For, Match, onCleanup, Show, Switch } from "solid-js"
|
||||
import { DiffViewerFileTree } from "./diff-viewer-file-tree"
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import type { TuiPlugin, TuiPluginApi, TuiPluginStatus } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
import { useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import { fileURLToPath } from "url"
|
||||
import { DialogSelect, type DialogSelectOption } from "../../ui/dialog-select"
|
||||
import { Show, createEffect, createMemo, createSignal } from "solid-js"
|
||||
+4
-4
@@ -1,9 +1,9 @@
|
||||
/** @jsxImportSource @opentui/solid */
|
||||
import { RGBA, TextAttributes, type KeyEvent, type Renderable } from "@opentui/core"
|
||||
import { useTerminalDimensions } from "@opentui/solid"
|
||||
/** @jsxImportSource @neuron-tui/solid */
|
||||
import { RGBA, TextAttributes, type KeyEvent, type Renderable } from "@neuron-tui/core"
|
||||
import { useTerminalDimensions } from "@neuron-tui/solid"
|
||||
import { createEffect, createMemo, createSignal, For, Show } from "solid-js"
|
||||
import { useBindings, useKeymapSelector } from "../../keymap"
|
||||
import type { ActiveKey } from "@opentui/keymap"
|
||||
import type { ActiveKey } from "@neuron-tui/keymap"
|
||||
import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/tui"
|
||||
import type { BuiltinTuiPlugin } from "../builtins"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user