feat(cli): add the boot chrome the dev TUI draws before the session starts - #15
Merged
Conversation
…tarts `please dev` will hand its session to `@ai-sdk/tui`, which takes the alternate screen buffer and gives it back untouched on exit. Everything either side of that hand-off belongs to the main screen, and has to survive the buffer being torn down — a run that never reaches the interactive UI has nothing to say from inside a buffer it never entered. So this is the main screen's half: a palette, a banner, aligned sections, grapheme-aware width measurement, an inline repaint engine, and a one-line pulsing boot row that names the phase a container start is currently in. Ported from vercel/eve's `cli/ui` (Apache-2.0, attributed in NOTICE), trimmed to what the boot needs and simplified where the runtime has caught up — `Intl.Segmenter` and Unicode property escapes replace eve's hand-written tables. The `dev` command itself is not here. It needs `stream` and `tools` off the agent, and `defineAgent` exposes neither; `docs/dev-tui.md` states the three ways out rather than picking one silently. `@pleaseai/cli` is private until it has a command — an empty `please` binary is worse than none — so it is registered in sonar but not yet in release-please.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
…ckage's coverage Three fixes from review of the boot chrome. `renderCliBanner` ruled its title with `String.length` and `renderCliTaggedLine` indented continuations by the same measure — the exact mistake `text.ts` exists to prevent. A 13-cell Korean title got a 7-character rule under it. Both now use `visibleLength`, pinned by tests that fail against the old measure. CI normalized and uploaded coverage for `packages/core` by hardcoded path, so the `packages/cli` lcov added in the previous commit was referenced by Sonar but never rewritten or uploaded: bun emits package-relative `SF:` paths, which Sonar resolves from the repo root and cannot match, landing every new file in new code at 0%. The normalize step now loops over `packages/*/coverage`, deriving each prefix from the path so the next package needs no change here, and both Codecov steps upload core's and cli's reports. Also: `commit()` repeated the row render already in `paint()`, and a `lastIndex` reset before `String.prototype.search` did nothing, since `search` runs from 0 and restores it.
main renamed the npm scope from `@pleaseai` to `@pleasedev` (#14) and pinned bun 1.4.0 (#17) after this branch was pushed. Only the two READMEs and the lockfile conflicted textually, but the rename reaches further than any merge could show: `packages/cli` is a directory main has never seen, so its `@pleaseai/cli` name and every `@pleaseai/*` reference in `docs/dev-tui.md` would have survived a clean-looking resolution. Both now follow the new scope. The lockfile was taken from main and regenerated rather than hand-merged. Left alone deliberately: `@pleaseai/sandbox-contract`, `@pleaseai/sandbox-e2b` and `@pleaseai/harness-cf-transport` in the sandbox sources name upstream packages in another repository, not this workspace, which is why #14 did not touch them either. The `pleaseai.dev` contact addresses are a domain, not a scope.
The scope rename landed after `bun install` had already written the workspace entry, so bun.lock still carried `@pleaseai/cli` and `bun install --frozen-lockfile` — what CI runs — refused it. Regenerated under the pinned bun 1.4.0 and verified frozen.
amondnet
marked this pull request as ready for review
August 28, 2026 08:43
There was a problem hiding this comment.
All reported issues were addressed across 23 files
Architecture diagram
sequenceDiagram
participant UI as Boot Chrome (packages/cli/src/ui)
participant Render as LiveRegion
participant Text as text.ts (Intl.Segmenter)
participant Sanitize as sanitize.ts
participant Main as Main Screen
participant Alternate as @ai-sdk/tui (Alternate Buffer)
participant Scroll as Native Scrollback
Note over UI,Alternate: TERMINAL SPLIT: Boot chrome owns main screen<br/>@ai-sdk/tui owns alternate buffer for session
UI->>UI: Start boot row
UI->>Render: Create LiveRegion
loop Each boot phase (image pull, container start)
UI->>Text: Measure phase text width
UI->>Sanitize: Sanitize external values (container id, docker output)
Sanitize-->>UI: Clean text
UI->>UI: Fit detail to column width (ellipsize if needed)
UI->>Render: update() with single row
Render->>Main: Paint row (sync markers, cursor controls)
Main-->>Render: Row rendered
alt Phase completes
UI->>Render: flush(committedLine, currentRow)
Render->>Scroll: Commit phase line to scrollback
Render->>Main: Repaint live row above scrollback
end
end
Note over UI,Alternate: HAND-OFF TO SESSION
UI->>UI: Stop boot row (erase live region)
UI->>Alternate: Enter alternate screen buffer (goes dark)
Alternate-->>Main: Session ends, buffer torn down
Main-->>UI: Main screen restored with committed scrollback
Note over UI,Alternate: Initial boot error path
alt Boot phase fails before TUI
UI->>Render: commit() error line
Render->>Scroll: Write error to scrollback
UI-->>UI: Exit without entering alternate buffer
end
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
cubic raised five findings on the boot chrome; four are fixed here and the fifth is tracked instead. - `theme.ts` passed a bare `process.stdout.isTTY` to picocolors, overriding the detection that honours `NO_COLOR`, `--no-color` and `TERM=dumb` — a terminal that asked for no colour got escapes anyway. Now delegated outright. The first attempt ANDed the two, which the local ocr pass caught as an over-correction: it re-breaks `FORCE_COLOR` and `CI`, the branches that exist to enable colour without a TTY. picocolors reads the environment once at import, so the tests pin this from child processes with controlled environments. - `assertPulseSequence` accepted an all-unlit sequence, which animates without ever drawing the glyph the row exists to show. - `sanitize.ts` matched only charset-designation introducers, so `ESC # 8` left its `8` behind as visible text. Generalised to the ECMA-48 shape, which covers the charset case as one instance. The local gpt pass then caught a regression in that generalisation: the scan ran past a byte that cannot legally continue a sequence, looking for a terminator that was not coming, and ate the text on the way — `ESC ( <emoji> visible` came out as `isible`. It now stops at the malformed byte and leaves the rest as text. - `boot-row.ts` kept its non-TTY dedupe state across `commit()`, so a phase name reused after a commit printed nothing at all. Left open: the live region miscounts its rows when the terminal is narrowed mid-paint. Real, but the fix needs a `SIGWINCH` listener and a teardown owner — the same design decision the cursor hide/restore gap is waiting on. Filed rather than guessed at.
…eAgent `defineAgent` landed on main (#13) — the API this note said the `dev` command was waiting for. Verified the two gaps against what actually shipped rather than assuming they carried over: `AgentSession` exposes `prompt` and no `stream`, and `Agent` exposes `createSession` and no `tools`, which is exactly what `runAgentTUI` reads. So the analysis holds and only the status framing was stale — "waiting for it to land" is now "the shape that landed does not expose these". The README layout conflict is resolved in main's favour, with `packages/cli` and `docs/dev-tui.md` re-added to the restructured tree. main also renamed project-layout.md from "the first argument" to "the argument", which left the dev-tui sentence calling itself "the second" without an antecedent; reworded in both READMEs. Lockfile taken from main and regenerated under the pinned bun, verified frozen.
There was a problem hiding this comment.
All reported issues were addressed across 8 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
…alsified The module header still claimed a CI log "prints the same text without escapes". Delegating to picocolors made that false — `CI` is one of the branches that enables colour without a terminal — and it contradicted a test in the same package that pins exactly that. Rewritten to say who decides and what each environment does.
amondnet
added a commit
that referenced
this pull request
Aug 28, 2026
Takes in `defineAgent` / `defineSandbox` (#13) and the dev-TUI boot chrome (#15). Both READMEs conflicted where main rewrote the Status section around the new public API while this branch added three backend rows and a note on where each backend's suite actually runs; the resolution keeps main's narrative and appends the coverage note, and folds the three new backend directories into main's deeper `src/agent` + `src/sandbox` layout tree. The three backends this branch adds ship no `SandboxBackendFactory` — the `docker({ image })` shape main introduced — because the adaptation is not mechanical for any of them: microsandbox takes ports as a guest→host map rather than a list, and just-bash publishes no ports at all, which `resolveSandbox` requires. Filed as a follow-up rather than decided in a merge.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds a new private workspace package
@pleaseai/cli(packages/cli) holding the terminal "boot chrome" that a futureplease devcommand will draw before handing the session to@ai-sdk/tui.The code is ported from vercel/eve's
packages/eve/src/cli/ui(Apache-2.0, the same license as this repo; attributed inNOTICEand in per-file headers), trimmed to what the boot needs and simplified where the runtime has caught up —Intl.Segmenterand Unicode property escapes replace eve's hand-written tables.Modules:
sanitize.tstext.tsIntl.Segmenter— a Hangul glyph measures two cellstheme.tslive-region.tsprogress-pulse.ts+boot-row.tsAlso included:
docs/dev-tui.md(the design record), theNOTICEattribution,sonar-project.propertiesregistration for the new package, and README / README.ko layout and docs-list updates.57 tests;
mise run cipasses.Why the split exists
@ai-sdk/tui@1.0.84renders the session in the alternate screen buffer — its dist contains the?1049h/?1049lpair — so everything it draws is gone on exit. eve's live region is built on the opposite premise: native scrollback, transcript survives.The two cannot be merged, and do not need to be.
@ai-sdk/tuiowns the session; this chrome owns the main screen on either side of it. A run that never reaches the interactive UI has nothing to say from inside a buffer it never entered.Why the
devcommand is not in this PRrunAgentTUIstructurally takes anAgent, but the shipped implementation reads exactly two members —agent.streamandagent.tools. That was verified by grepping the published bundle, not taken from the docs.The in-progress
defineAgentsurface exposes neither: itsAgentSessionhas onlyprompt(), which wrapsgenerate()and returns a finished turn — and incremental rendering is the whole point of the interactive UI.docs/dev-tui.mdrecords three ways out and deliberately picks none, because that is an API decision rather than an omission.@pleaseai/cliis thereforeprivate: true, and registered in sonar but not yet in release-please: publishing an emptypleasebinary would be worse than publishing none.Related issue
None — this work has no tracking issue.
Checklist
bun run test)bun run lint,bun run type-check)BREAKING CHANGE:note is includedSummary by cubic
Adds the boot chrome the future
please devcommand draws before handing its session to@ai-sdk/tui— palette, banner, grapheme-aware width fitting, an inline repaint region, and a pulsing boot row — as a new private package@pleasedev/cli(packages/cli). The code is ported from vercel/eve'scli/ui(Apache-2.0, attributed inNOTICE), owns the main screen while@ai-sdk/tuitakes the alternate buffer, and ships withdocs/dev-tui.mdand a passing test suite.Fixes from review
packages/core— so the new package's files are no longer reported at 0%.NO_COLOR,FORCE_COLOR, andCIbehave as intended; sanitization consumesESC # 8and stops at malformed input; the pulse sequence rejects all-unlit; and a reused phase name prints again after a commit.@pleasedevscope rename from main, and the lockfile was regenerated so CI's frozen install resolves it.Why
please devis absent@ai-sdk/tuitakes the alternate screen buffer for the session; this chrome owns the main screen on either side of it, and the two cannot merge.runAgentTUIconsumes exactlyagent.streamandagent.tools; the mergeddefineAgentstill exposes neither, so the interactive command waits on an API decision documented indocs/dev-tui.md.private: trueand out of release-please until it has a command, because publishing an emptypleasebinary is worse than none.Written for commit 5cf709e. Summary will update on new commits.