Skip to content

Commit bceebba

Browse files
committed
feat(shell): TUI redesign, update orchestrator, release-orchestration docs
Broad checkpoint of the welcome-banner-redesign branch: - Shell/TUI: welcome banner footer-chip layout, design system, glyphs, motion, prompt, slash, tool renderers, MCP status, activity tree, live view, worklog, theme tokens. - Update flow: new update_orchestrator module + CLI wiring; terminal capability detection (terminal_capabilities) with tests. - Feedback: structured feedback repo (feedback_repo) and slash command. - Auth/config/llm: OpenAI auth + platform/config/llm adjustments. - Docs: welcome-banner spec/plan and P0–P2 release-orchestration plans. - Web: subagent-steps component update. - Address CodeRabbit review: -> None on update CLI handlers, drop a duplicate test assertion, correct the backoff verification budget (7 retries / 364s), and de-hardcode uv paths in the P2 plan.
1 parent 822267e commit bceebba

60 files changed

Lines changed: 2604 additions & 359 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ static/
5656
.memo/
5757
.entire
5858
.claude
59+
.pythinker/
5960
.worktrees/
6061
blackbox/
6162

PRODUCT.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Product
2+
3+
## Register
4+
5+
Use a product-engineering register: concise, practical, and confident. Write for developers who value clear terminal workflows; prefer precise verbs, concrete outcomes, and short examples over marketing slogans.
6+
7+
## Users
8+
9+
Developers using Pythinker inside terminal workflows, including PowerShell, Windows Terminal, macOS Terminal, iTerm2, GNOME Terminal, CI logs, SSH sessions, and low-color or no-color shells. They are reviewing code, diagnosing failures, running commands, approving changes, and switching between AI and shell work.
10+
11+
## Product Purpose
12+
13+
Pythinker Code is a review-first AI engineering CLI. The interface should help users understand what the agent is doing, trust approvals and results, move quickly through repeated terminal actions, and keep context visible without leaving the shell.
14+
15+
## Brand Personality
16+
17+
Disciplined, terminal-native, precise.
18+
19+
## Anti-references
20+
21+
Do not make the TUI feel like a neon dashboard, glassmorphism mockup, emoji-heavy chat toy, or novelty terminal skin. Avoid fragile color-only semantics, excessive borders, box-heavy layouts, decorative motion, AI-purple gradients, and UI effects that slow repeated keyboard workflows.
22+
23+
## Design Principles
24+
25+
1. Standardize first: core structure, labels, states, and colors must behave consistently across shells and terminal capabilities.
26+
2. Text is the UI: layout, copy, hierarchy, and semantic labels should remain usable in no-color and narrow-width environments.
27+
3. Speed over spectacle: feedback should be immediate, short, and interruptible; repeated keyboard actions should not animate.
28+
4. State must be explicit: approvals, background work, errors, warnings, and tool execution need clear shape and wording, not just color.
29+
5. Compatibility is craft: PowerShell, Windows Terminal, Unix terminals, SSH, CI, and screen readers should all get a coherent experience.
30+
31+
## Accessibility & Inclusion
32+
33+
Target WCAG-minded contrast within ANSI limitations, colorblind-safe state semantics, reduced-motion behavior, keyboard-first operation, graceful no-color fallback, and responsive rendering from narrow split panes to wide terminals.

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,4 +801,8 @@ Distributed under the **Apache-2.0 License**. See [`LICENSE`](https://github.com
801801
[🧩 ACP](https://github.com/agentclientprotocol/agent-client-protocol)  · 
802802
[🔌 MCP](https://modelcontextprotocol.io/)
803803

804+
<p align="center">
805+
<em>Thanks for visiting ✨ Pythinker! This README does not collect user-level telemetry.</em>
806+
</p>
807+
804808
</div>

docs/superpowers/plans/2026-05-31-release-orchestration-p0-quick-wins.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ The current loop (lines 229-238) is flat 6×10s. Replace with exponential backof
233233
while [ "$elapsed" -lt "$max" ]; do seq="$seq $delay"; elapsed=$((elapsed+delay)); delay=$((delay*2)); [ "$delay" -gt 120 ] && delay=120; done
234234
echo "delays:$seq total:${elapsed}s"
235235
```
236-
Expected: `delays: 4 8 16 32 64 120 120 120 total:480s` (≈8 retries, geometric early then 120s-capped; the loop stops once `elapsed >= 360`).
236+
Expected: `delays: 4 8 16 32 64 120 120 total:364s` (7 retries, geometric early then 120s-capped; the loop stops once `elapsed >= 360`).
237237
- [ ] 4.3 Lint the whole file and confirm it is clean:
238238
```bash
239239
shellcheck /home/ai/Projects/pythinker-code-main/scripts/install-native.sh; echo "exit=$?"

docs/superpowers/plans/2026-05-31-release-orchestration-p2-distribution-channels.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -555,7 +555,7 @@ Expected: `success`. Read its summary + any "Actionable comments posted: N" befo
555555

556556
- [ ] 2. Validate it is well-formed JSON before relying on it as a template fixture:
557557
```bash
558-
/home/ai/.local/bin/uv run python -c "import json,pathlib; json.loads(pathlib.Path('packages/scoop-bucket/pythinker-code.json.tmpl').read_text())"
558+
uv run python -c "import json,pathlib; json.loads(pathlib.Path('packages/scoop-bucket/pythinker-code.json.tmpl').read_text())"
559559
```
560560
Expected: no output, exit 0 (the `__VERSION__` etc. are valid JSON string values, so it parses as-is).
561561

@@ -651,7 +651,7 @@ def test_windows_zip_asset_name_matches_release_workflow() -> None:
651651

652652
- [ ] 2. Run it and watch it fail for the right reason (the generator does not exist yet):
653653
```bash
654-
/home/ai/.local/bin/uv run pytest tests/test_scoop_manifest.py -vv
654+
uv run pytest tests/test_scoop_manifest.py -vv
655655
```
656656
Expected: collection/import error — `FileNotFoundError`/`spec is None` because `packages/scoop-bucket/generate-manifest.py` does not exist. (Red.)
657657

@@ -798,13 +798,13 @@ if __name__ == "__main__":
798798

799799
- [ ] 4. Run the test and watch it pass (Green):
800800
```bash
801-
/home/ai/.local/bin/uv run pytest tests/test_scoop_manifest.py -vv
801+
uv run pytest tests/test_scoop_manifest.py -vv
802802
```
803803
Expected: `3 passed`.
804804

805805
- [ ] 5. Run the repo lint on the new files so the PR's `check` job will be green (matches `make check-pythinker-code`):
806806
```bash
807-
/home/ai/.local/bin/uv run ruff check tests/test_scoop_manifest.py
807+
uv run ruff check tests/test_scoop_manifest.py
808808
```
809809
Expected: `All checks passed!` (the generator lives under `packages/scoop-bucket/` which is generator code; if ruff flags it, match the homebrew generator's style — it passes today.)
810810

@@ -1319,7 +1319,7 @@ Expected: `OK: no versioned strings added`.
13191319

13201320
- [ ] 4. If P1 is merged, run the lockstep test to prove the README edits didn't break it:
13211321
```bash
1322-
/home/ai/.local/bin/uv run pytest tests/test_version_lockstep.py -vv
1322+
uv run pytest tests/test_version_lockstep.py -vv
13231323
```
13241324
Expected: `passed` (only run if P1's test exists; if P1 isn't merged yet, skip and note it).
13251325

docs/superpowers/plans/2026-05-31-welcome-banner-redesign.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,11 @@ def test_welcome_strapline_and_help_on_separate_lines(monkeypatch):
8585
shell_module._print_welcome_info("Pythinker Code", [])
8686

8787
out = console.export_text()
88-
assert "then Create." in out
89-
assert "Send /help for help." in out
88+
assert "Build with confidence." in out
89+
assert "Type /help for commands." in out
9090
# The strapline and the help line must not share one rendered line.
9191
assert not any(
92-
"then Create." in ln and "Send /help" in ln for ln in out.splitlines()
92+
"Build with confidence." in ln and "Type /help" in ln for ln in out.splitlines()
9393
)
9494
```
9595

@@ -103,7 +103,7 @@ uv run pytest tests/ui_and_conv/test_shell_welcome_info.py -q -k "footer or pipe
103103
Expected: all three FAIL.
104104
- `test_welcome_chip_renders_in_footer_not_header` fails because today the chip is in the header (top), so `lines[-1]` has no "changelog" and `lines[:3]` contains it.
105105
- `test_welcome_info_grid_has_no_pipe_separator` fails because today the row is `│ Directory │ /tmp/proj │` → 3 pipes, not 2.
106-
- `test_welcome_strapline_and_help_on_separate_lines` fails because today "then Create." and "Send /help" share one line.
106+
- `test_welcome_strapline_and_help_on_separate_lines` fails if "Build with confidence." and "Type /help for commands." share one line.
107107

108108
- [ ] **Step 3: Confirm the 5 existing tests still pass (no regression introduced by the new tests)**
109109

@@ -139,9 +139,9 @@ def _print_welcome_info(
139139
_t = _get_tui_tokens()
140140
head = Text.from_markup("Welcome to Pythinker — think first, then code.")
141141
strapline = Text.from_markup(
142-
f"[{_t.muted}]Review · Secure · Diagnose · then Create.[/]"
142+
f"[{_t.muted}]Review · Secure · Diagnose · Build with confidence.[/]"
143143
)
144-
help_text = Text.from_markup(f"[{_t.muted}]Send /help for help.[/]")
144+
help_text = Text.from_markup(f"[{_t.muted}]Type /help for commands.[/]")
145145
help_text.highlight_regex(r"/help\b", f"bold {_t.warning}")
146146

147147
# Logo on the left; the 3-line text block bottom-aligns against the 5-line
@@ -338,4 +338,3 @@ rm -f /tmp/verify_banner.py
338338
- **Spec coverage:** §3 changes 1–5 each map to a Task-2 step (chip→footer = `subtitle=`; bottom-align = `vertical="bottom"`; split strapline = three `Text` lines; drop pipe = 2-col info grid; token border = `tui_rich_style`). §5 new tests = Task 1. §6 verification = Task 3.
339339
- **Placeholder scan:** no TBD/TODO; every code step shows complete code; every command shows expected output.
340340
- **Type/name consistency:** `WelcomeInfoItem`, `_value_style_for_label`, `_get_tui_tokens`, `tui_rich_style`, `get_version`, `Group`, `RenderableType`, `Table`, `Text`, `Panel`, `box` are all already imported in the target file; no new imports needed. `_print_welcome_info` keeps its exact signature, so the `Shell.run()` call site is unaffected.
341-
```

docs/superpowers/specs/2026-05-31-release-orchestration-design.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ provenance: multi-agent design workflow wf_620b356a-d6e (5 subsystem designs ->
1414

1515
## 1. Target Architecture Overview
1616

17-
```
17+
```text
1818
┌──────────────────────────────────────────────────────────────┐
1919
│ SSOT = pyproject.toml:3 (the ONLY authoritative version) │
2020
│ scripts/release.py rewrites all derived files + uv.lock, │
@@ -103,7 +103,8 @@ After P0+P1: **zero long-lived cross-repo PATs** on the release path. The only r
103103
**Source:** `pyproject.toml:3` `[project].version`. The installed package reads `importlib.metadata.version("pythinker-code")` (`constant.py:14`) — there is no `__version__` constant, so this is genuinely the only authoritative store.
104104

105105
**The SSOT→site chain (proves distribution + site consume the orchestrator's source):**
106-
```
106+
107+
```text
107108
pyproject.toml:3 ─(release.py rewrite)→ release PR ─(check_version_tag gate)→ merge
108109
→ human tag vX.Y.Z ─(CI)→ GitHub Release ─→ /releases/latest.tag_name
109110
→ site sync writes public/version.json {pythinkerCode: X.Y.Z} AND brew formula version "X.Y.Z" AND scoop manifest
@@ -157,7 +158,7 @@ pyproject.toml:3 ─(release.py rewrite)→ release PR ─(check_version_tag gat
157158

158159
**Terminal states:** `PROMOTED` (success), `FAILED_STUCK` (loud failure, release retained as prerelease, tracking issue open, red CI). `DEGRADED` is transient (promoted but best-effort channel reconciling) and **never un-promotes**.
159160

160-
```
161+
```text
161162
tag push → PRERELEASE (/latest = last good)
162163
→ CHECKING (poll BLOCKING set: GH assets + PyPI(pythinker-code) + PyPI(core,host,review pins))
163164
├─ all blocking ready → PATCH prerelease=false, make_latest → PROMOTED (/latest = X.Y.Z; pip fully resolvable)
@@ -206,7 +207,7 @@ tag push → PRERELEASE (/latest = last good)
206207
| **WinGet** P2-late | `wingetcreate update ... --submit` | `microsoft/winget-pkgs` fork | **MANUAL `workflow_dispatch` only** (hard gate) | fine-grained PAT `WINGET_SUBMIT_TOKEN` (isolated; can't be an App for external-repo PRs) | never |
207208
| **AUR** | DEFER ||| SSH key (re-introduces non-App secret) ||
208209

209-
**Required code change (don't ship non-self-updating channels without it):** `src/pythinker_code/ui/shell/update.py` — add a `PYTHINKER_MANAGED=<channel>` env read at the top of `_detect_upgrade_command()` (mirrors hermes `HERMES_MANAGED`); Docker/Nix set it, Scoop/WinGet manifests set it → channel-native upgrade hint. **Brew must NOT set `PYTHINKER_MANAGED`** (keep its existing path-sniff so behavior is unchanged); **mandatory regression test** that brew still maps to `['brew','upgrade','pythinker-code']` (the `.pythinker-native` marker means brew also trips `is_native_build()`; precedence is load-bearing). This change ships in P1 as prep so P2 channels are not released non-self-updating.
210+
**Required code change (don't ship supported non-self-updating channels without it):** `src/pythinker_code/ui/shell/update.py` — add a `PYTHINKER_MANAGED=<channel>` env read at the top of `_detect_upgrade_command()` (mirrors hermes `HERMES_MANAGED`); Docker/Nix set it and Scoop manifests set it → channel-native upgrade hint. **WinGet does not set `PYTHINKER_MANAGED`** because its workflow submits installer metadata only and the WinGet manifest cannot inject a process env var, so it uses the generic native-updater path. **Brew must NOT set `PYTHINKER_MANAGED`** (keep its existing path-sniff so behavior is unchanged); **mandatory regression test** that brew still maps to `['brew','upgrade','pythinker-code']` (the `.pythinker-native` marker means brew also trips `is_native_build()`; precedence is load-bearing). This change ships in P1 as prep so P2 channels are not released non-self-updating where markers are supported.
210211

211212
**C4 for new channels:** README install snippets MUST be **version-less** (`scoop install pythinker-code`, `docker run ghcr.io/techmatrix-labs/pythinker-code`, `nix run github:TechMatrix-labs/pythinker-code`) so they never enter the F3 sprawl set.
212213

@@ -230,13 +231,16 @@ The discriminator that matters in `sync-upstream-products.ts`:
230231
- **Release-asset download URLs** (`/releases/download/<tag>/...`, plus the deb/rpm/exe URLs + shas in `pythinkerCodeRelease.ts`) **must be built from the live API's `release.tag_name`, never from the payload ref** — a SHA there 404s. The sync already derives release data from `/releases/latest`; this codifies that the payload `tag` is used only for raw-source pinning and **never** for constructing asset URLs.
231232

232233
**Deploy chain (the commit-push IS the deploy trigger):**
233-
```
234+
235+
```text
234236
release → promote → dispatch → sync-upstream-products.ts writes files → git push main
235237
→ Dokploy build-from-source (nixpacks → bun run server.ts: serves dist/ + bun:sqlite install-counter) → pythinker.com
236238
```
237239

238240
**Deploy resolution (F5):** **Canonical = Dokploy build-from-source.** GitHub Pages is **disqualified**`server.ts` needs a runtime + SQLite + POST endpoint. GHCR+Watchtower path is **provably dead** (no image since `docker.yml` deleted; `deploy/.env.example` `SITE_IMAGE` points at the wrong org `mohamed-elkholy95`). **Retire** (after confirming the live host runs Dokploy, not compose): `docker-compose.yml`, `docker-compose.private-ghcr.yml`, `deploy/traefik/`, Watchtower + all GHCR refs, rewrite `deploy/README.md` around Dokploy. **Keep:** `Dockerfile` (documented single-container fallback), `nixpacks.toml`, `server.ts`. **State the dependency:** the deploy chain relies on `pythinker-home` main being unprotected (verified) so the workflow can push; if it's ever protected, exempt `github-actions[bot]` or the chain breaks.
239241

242+
**Operational branch-protection note:** if `pythinker-home` main is protected, the sync workflow cannot push the generated site files and Dokploy never sees a new build-from-source commit. Symptom: the sync workflow fails at its `git push main` step; check the `sync-upstream-products.yml` CI logs in `pythinker-home`. Remediation is either (1) remove main-branch protection for this repository, or (2) keep protection but add `github-actions[bot]` as an allowed actor/bypass for pushes from the workflow. Preserve one of those two settings whenever branch protection is changed.
243+
240244
**Install-script locations — distinguish code-repo sources from site mirrors (critique item 7):**
241245
- **Code repo (KEEP — they are dispatch triggers):** `scripts/install.ps1` + `scripts/install-native.sh` are the **source** scripts; `dispatch-pythinker-home-sync.yml` watches them (`paths:` lines 16-17). Do **not** delete these.
242246
- **Site repo (`git rm` the dead mirrors):** the canonical served pair is **`public/install.sh` + `public/install.ps1`** (the sole Vite-served dir, confirmed kept). The 3 dead, git-tracked mirrors — `scripts/install.ps1`, `web/public/install.ps1`, `docs/public/install.ps1` (all verified tracked + byte-identical) — get `git rm`'d and dropped from `installMirrors[].targetPaths`.
@@ -290,7 +294,7 @@ Add a **`.sha256` sidecar existence check** (right-sized: defense-in-depth for t
290294
**Risk:** Low-medium (tool runs locally before any push; gates mirror CI; no workflow topology change). **Reversibility:** High (additive; manual bump still works if tool unused). **Effort:** ~2–3 days incl. tests + a dry-run release rehearsal.
291295

292296
### P2 — Breadth (new channels; broaden distribution)
293-
**Scope, in order:** (1) Docker/GHCR (`Dockerfile` + `docker.yml`, ancestor-check, `GITHUB_TOKEN`); (2) Scoop (`scoop-pythinker` **org repo** + `pythinker-scoop-publisher` **org App** + `generate-manifest.py` polling the windows zip + `scoop-bucket.yml`); (3) Nix (`apps.default` net-new + `nix build .#default`/`nix run` CI + monthly `update-flake-lock` PR); (4) WinGet (manual `workflow_dispatch`, isolated PAT) — last; AUR deferred. Each new channel: version-less README snippet (C4), `PYTHINKER_MANAGED` set, best-effort (never gates promote).
297+
**Scope, in order:** (1) Docker/GHCR (`Dockerfile` + `docker.yml`, ancestor-check, `GITHUB_TOKEN`); (2) Scoop (`scoop-pythinker` **org repo** + `pythinker-scoop-publisher` **org App** + `generate-manifest.py` polling the windows zip + `scoop-bucket.yml`); (3) Nix (`apps.default` net-new + `nix build .#default`/`nix run` CI + monthly `update-flake-lock` PR); (4) WinGet (manual `workflow_dispatch`, isolated PAT) — last; AUR deferred. Each new channel: version-less README snippet (C4), best-effort (never gates promote); Docker/Scoop/Nix set `PYTHINKER_MANAGED`, while WinGet's no-marker limitation is documented.
294298
**Risk:** Medium (new repos/Apps/secrets; all best-effort so they can't worsen F1). **Reversibility:** High (each channel is an independent additive workflow; delete to remove). **Effort:** Docker ~1 day, Scoop ~1.5 days, Nix ~0.5 day, WinGet ~0.5 day — adopt incrementally, one PR each.
295299

296300
**Cross-phase invariants:** every PR goes branch → PR → checks pass → CodeRabbit `success` (C2) → merge → (release PRs) tag (C1); no direct main push; no new agent runtime dep (C3); README/badges move with the bump (C4); authored CHANGELOG narrative preserved, contributor footer only as release-notes addendum (C5).

0 commit comments

Comments
 (0)