Skip to content

feat: advance Go-first TUI and npm wrapper - #57

Merged
Vasanthdev2004 merged 5 commits into
mainfrom
feat/m1-go-command-model-ui
Jun 4, 2026
Merged

feat: advance Go-first TUI and npm wrapper#57
Vasanthdev2004 merged 5 commits into
mainfrom
feat/m1-go-command-model-ui

Conversation

@Vasanthdev2004

@Vasanthdev2004 Vasanthdev2004 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • ported the Go TUI slash-command shell to a registry with aliases and help formatting
  • added Go handlers for /provider, /model status, /context, /config, /debug, and shell-only registered commands
  • moved the npm bin/module entrypoint to a thin wrapper that prefers the built Go binary and falls back to the transitional TS CLI for local dev
  • packaged the wrapper files in release archives and updated wrapper smoke docs

Tests

  • git diff --check
  • bun run typecheck
  • bun test ./tests --timeout 15000
  • bun run test:go
  • bun run build
  • bun run smoke:build
  • bun run build:go
  • bun run smoke:go
  • bun bin/zero.ts --version
  • bun run package:release
  • bun run verify:release

Summary by CodeRabbit

  • New Features

    • Interactive TUI: many new slash commands, dynamic footer/help, model catalog and plan views.
    • CLI/packaging: updated CLI entrypoint and npm-wrapper that prefers a native binary and falls back to the TypeScript CLI.
    • Added --max-turns option to the exec command.
  • Refactor

    • Centralized command parsing and registry with alias support and formatted help.
  • Tests

    • Expanded coverage for commands, footer/help, plan/model views, and wrapper/entrypoint behavior.
  • Documentation

    • Expanded npm-wrapper smoke checklist.
  • Chores

    • Packaging updated to include executable and wrapper artifacts.

@github-actions

github-actions Bot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Zero automated PR review

Verdict: No blockers found

Blockers

  • None found.

Validation

  • [pass] Diff hygiene: git diff --check
  • [pass] Typecheck: bun run typecheck
  • [pass] Tests: bun run test
  • [pass] Build: bun run build
  • [pass] Smoke build: bun run smoke:build

Scope

Head: 14dc4e71a193
Changed files (14): bin/zero.ts, docs/NPM_WRAPPER_SMOKE.md, internal/cli/app.go, internal/cli/exec.go, internal/cli/exec_test.go, internal/tui/commands.go, internal/tui/model.go, internal/tui/model_catalog.go, internal/tui/model_test.go, internal/tui/plan_command.go, package.json, scripts/package-release.ts, and 2 more

This deterministic review checks validation status and basic diff hygiene. A human reviewer still owns product judgment and design quality.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ddb6e6cf-2a57-40c1-a414-761af3c6614a

📥 Commits

Reviewing files that changed from the base of the PR and between c6e5df9 and 14dc4e7.

📒 Files selected for processing (4)
  • internal/cli/exec.go
  • internal/cli/exec_test.go
  • internal/tui/model_catalog.go
  • internal/tui/model_test.go

Walkthrough

Adds an npm wrapper and Bun-executable CLI entrypoint; updates package/release wiring and tests; adds exec --max-turns parsing and propagation; refactors TUI slash-command parsing to a centralized registry with help/footer, handler helpers, model/plan helpers, and expanded tests.

Changes

npm wrapper entrypoint and target resolution

Layer / File(s) Summary
npm wrapper module: types and target resolution
src/npm-wrapper.ts
NpmWrapperTarget, resolve/exec types and functions; zeroBinaryName; resolveNpmWrapperTarget prefers native binary in repo root, falls back to src/index.ts via bun, or returns null. runNpmWrapper spawns resolved command, inherits stdio, or writes stderr and returns exit code 1 on failures.
CLI entrypoint and package wiring
bin/zero.ts, package.json, scripts/package-release.ts, docs/NPM_WRAPPER_SMOKE.md
Adds Bun shebang bin/zero.ts that computes package root and calls runNpmWrapper. Updates package.json module/bin.zero to bin/zero.ts. Stages bin/zero.ts and src/npm-wrapper.ts into release package. Updates smoke checklist to include src/npm-wrapper.ts.
npm wrapper target resolution tests
tests/build-scripts.test.ts
Adds tests asserting package.json wiring and resolveNpmWrapperTarget/runNpmWrapper behaviors: prefer native binary, fallback to TypeScript via Bun, return null when none, and failure stderr/exit handling.

CLI exec --max-turns

Layer / File(s) Summary
exec flag parsing and propagation
internal/cli/exec.go
Adds maxTurns to execOptions, parses --max-turns/--max-turns=<value> with validation (positive integer), and forwards positive values into config.Overrides.MaxTurns during runExec.
exec help and tests
internal/cli/app.go, internal/cli/exec_test.go
Documents --max-turns in exec help; adds tests that invalid values are rejected before runtime and that valid values reach config overrides.

TUI command system refactor

Layer / File(s) Summary
Command registry and parsing system
internal/tui/commands.go
Extends commandKind/commandGroup; defines commandDefinition and parsedCommand (adds name); populates commandDefinitions; refactors parseCommand to trim, split, and resolve slash commands via registry; adds splitCommand, resolveCommand, listCommandNames, formatCommandHelpLines.
Command execution, footer and helper texts
internal/tui/model.go
Footer now uses commandFooterText(); handleSubmit appends provider/model/context/config/debug or "registered but not wired" messages; adds providerText, modelText, contextText, configText, debugText, displayValue, shellOnlyCommandText; refactors help/footer formatting from registry.
Model catalog and plan helpers
internal/tui/model_catalog.go, internal/tui/plan_command.go
Adds model.modelListText() and activeModelID() for model listings; introduces currentPlanReader and model.planText() to render current plan text from model tools.
Command registry and handler tests
internal/tui/model_test.go
Expands parsing tests (/quit, /context, /model, aliases), adds registry/help/footer tests, updates help expectations, and adds plan/context/model tests ensuring these commands render transcripts without launching agent runs; includes transcript helper functions.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TUI
  participant Parser
  participant CommandRegistry
  User->>TUI: submit input
  TUI->>Parser: parseCommand(input)
  Parser->>CommandRegistry: lookup name/aliases
  CommandRegistry-->>Parser: definition or not found
  Parser-->>TUI: parsedCommand (prompt / unknown / resolved)
  TUI->>TUI: handleSubmit -> append transcript or run agent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Gitlawb/zero#46: touches the npm wrapper smoke documentation; related checklist evolution.
  • Gitlawb/zero#54: overlaps on internal/cli/app.go / internal/cli/exec.go changes to exec behavior.
  • Gitlawb/zero#51: related agent loop logic that consumes Options.MaxTurns.

Suggested reviewers

  • anandh8x
  • gnanam1990
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: advancing the Go-first TUI (with command registry) and npm wrapper (with native binary fallback).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/m1-go-command-model-ui

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
tests/build-scripts.test.ts (1)

113-149: ⚡ Quick win

Add a no-target test to cover the final safety branch.

You verify native-first and TS-fallback, but not the “nothing available” outcome (null). Adding that case locks down the wrapper’s final decision path.

Suggested test case
 describe('npm wrapper entrypoint', () => {
@@
   it('prefers the Go binary and keeps the TS CLI as a local fallback', () => {
@@
   });
+
+  it('returns null when neither native nor TS fallback target exists', () => {
+    const target = resolveNpmWrapperTarget({
+      root: join('repo'),
+      platform: 'linux',
+      bunPath: 'bun',
+      args: ['--version'],
+      exists: () => false,
+    });
+
+    expect(target).toBeNull();
+  });
 });

As per coding guidelines, tests under tests/**/*.ts should provide meaningful coverage of safety decisions and runtime flows.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/build-scripts.test.ts` around lines 113 - 149, Add a unit test that
exercises the final safety branch of resolveNpmWrapperTarget by simulating no
available targets and asserting it returns null; specifically, after the
existing native-first and typescript-fallback checks, call
resolveNpmWrapperTarget with platform 'win32', bunPath 'bun', args ['--version']
and an exists function that always returns false (or a Set that contains neither
the native binary nor the TS entry), then expect the result to be null to lock
down the "nothing available" outcome.
internal/tui/model.go (1)

387-389: ⚡ Quick win

Derive footer commands from the registry to prevent drift.

At Line 388, the footer command list is hardcoded while command metadata now lives in commandDefinitions. This will eventually diverge during renames/additions; generate footer commands from registry data (optionally with a small allowlist for brevity).

Suggested refactor
 func commandFooterText() string {
-	return "/help  /model  /provider  /context  /tools  /permissions  /clear  /exit  Esc clear  Ctrl+C quit"
+	featured := map[commandKind]bool{
+		commandHelp:        true,
+		commandModel:       true,
+		commandProvider:    true,
+		commandContext:     true,
+		commandTools:       true,
+		commandPermissions: true,
+		commandClear:       true,
+		commandExit:        true,
+	}
+	parts := make([]string, 0, len(commandDefinitions)+2)
+	for _, command := range commandDefinitions {
+		if featured[command.kind] {
+			parts = append(parts, command.name)
+		}
+	}
+	parts = append(parts, "Esc clear", "Ctrl+C quit")
+	return strings.Join(parts, "  ")
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model.go` around lines 387 - 389, Replace the hardcoded footer
string in commandFooterText with a generated list built from the command
registry (commandDefinitions): iterate the registry keys (or a small allowlist
of the most common commands) to produce the short names, map them into the
display tokens (e.g. "/name" and special tokens like "Esc clear" / "Ctrl+C quit"
kept literal), join with double spaces to match current spacing, and return that
string; ensure commandFooterText falls back to the original hardcoded string if
commandDefinitions is empty or missing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/npm-wrapper.ts`:
- Around line 60-63: Update the error message emitted when target == null to
reflect that neither the native binary nor the TypeScript fallback is available:
change the stderr.write call that currently says "[zero] No native zero binary
found..." to a clearer message that mentions both the native binary and the TS
fallback are unavailable and suggests running `bun run build` (or another
recovery step); modify the string passed to stderr.write in the block
referencing target, options.stderr, and stderr.write so callers see the accurate
failure cause.
- Around line 66-73: The runNpmWrapper function currently calls
Bun.spawn(target.command, ...) unguarded and can throw synchronously; wrap the
Bun.spawn call in a try/catch so synchronous spawn failures are caught and the
function returns a clean numeric exit code (e.g., Promise.resolve(1) or
equivalent) instead of crashing; also update the error/message emitted when
target is null in resolveNpmWrapperTarget/runNpmWrapper to correctly state that
neither the native binary nor src/index.ts was found (instead of the misleading
"No native zero binary found…"), and ensure you still await/return child.exited
when spawn succeeds.

---

Nitpick comments:
In `@internal/tui/model.go`:
- Around line 387-389: Replace the hardcoded footer string in commandFooterText
with a generated list built from the command registry (commandDefinitions):
iterate the registry keys (or a small allowlist of the most common commands) to
produce the short names, map them into the display tokens (e.g. "/name" and
special tokens like "Esc clear" / "Ctrl+C quit" kept literal), join with double
spaces to match current spacing, and return that string; ensure
commandFooterText falls back to the original hardcoded string if
commandDefinitions is empty or missing.

In `@tests/build-scripts.test.ts`:
- Around line 113-149: Add a unit test that exercises the final safety branch of
resolveNpmWrapperTarget by simulating no available targets and asserting it
returns null; specifically, after the existing native-first and
typescript-fallback checks, call resolveNpmWrapperTarget with platform 'win32',
bunPath 'bun', args ['--version'] and an exists function that always returns
false (or a Set that contains neither the native binary nor the TS entry), then
expect the result to be null to lock down the "nothing available" outcome.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 79d9063e-af1e-4907-8e3e-2497290fb244

📥 Commits

Reviewing files that changed from the base of the PR and between d833802 and fd3600c.

📒 Files selected for processing (9)
  • bin/zero.ts
  • docs/NPM_WRAPPER_SMOKE.md
  • internal/tui/commands.go
  • internal/tui/model.go
  • internal/tui/model_test.go
  • package.json
  • scripts/package-release.ts
  • src/npm-wrapper.ts
  • tests/build-scripts.test.ts

Comment thread src/npm-wrapper.ts
Comment thread src/npm-wrapper.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
internal/cli/exec_test.go (1)

55-80: ⚡ Quick win

Add explicit regression coverage for 0 and empty --max-turns input.

This path is easy to regress. Add cases for 0 and --max-turns= so CLI behavior is explicit and enforced.

Suggested test additions
 func TestRunExecRejectsInvalidMaxTurnsBeforeRuntime(t *testing.T) {
 	for _, tc := range []struct {
 		value string
 		want  string
 	}{
 		{value: "nope", want: "invalid --max-turns"},
 		{value: "-1", want: "invalid --max-turns"},
+		{value: "0", want: "invalid --max-turns"},
 	} {
 		t.Run(tc.value, func(t *testing.T) {
 			var stdout bytes.Buffer
 			var stderr bytes.Buffer
@@
 		})
 	}
+
+	t.Run("equals-empty", func(t *testing.T) {
+		var stdout bytes.Buffer
+		var stderr bytes.Buffer
+
+		exitCode := Run([]string{"exec", "--max-turns=", "hello"}, &stdout, &stderr)
+		if exitCode != exitUsage {
+			t.Fatalf("expected exit code %d, got %d", exitUsage, exitCode)
+		}
+		if got := stderr.String(); !strings.Contains(got, "--max-turns requires a value") {
+			t.Fatalf("expected empty-value max-turns error, got %q", got)
+		}
+	})
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/cli/exec_test.go` around lines 55 - 80, Add regression cases for "0"
and the empty `--max-turns=` input to
TestRunExecRejectsInvalidMaxTurnsBeforeRuntime: extend the test table with
entries for value "0" and value "" (expecting the same "invalid --max-turns"
message), and adjust how the test builds args so an empty value is passed as the
single token "--max-turns=" instead of separate flag+value; keep using Run(...)
and asserting exitUsage, stdout empty, and stderr contains the expected message.
internal/tui/model_catalog.go (1)

23-30: ⚡ Quick win

Render /model list entries in a stable order.

modelListText() currently renders whatever order Registry.List(...) returns, which can make catalog output jump between runs. Sorting before rendering keeps CLI UX predictable and avoids brittle downstream assertions.

Proposed patch
 import (
 	"fmt"
+	"sort"
 	"strings"
 
 	"github.com/Gitlawb/zero/internal/modelregistry"
 )
@@
-	for _, model := range registry.List(modelregistry.ListOptions{}) {
+	models := registry.List(modelregistry.ListOptions{})
+	sort.Slice(models, func(i, j int) bool {
+		if models[i].Provider == models[j].Provider {
+			return models[i].ID < models[j].ID
+		}
+		return models[i].Provider < models[j].Provider
+	})
+	for _, model := range models {
 		marker := " "
 		if activeID != "" && model.ID == activeID {
 			marker = "*"
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model_catalog.go` around lines 23 - 30, modelListText currently
iterates registry.List(modelregistry.ListOptions{}) and appends in whatever
order is returned; make the output stable by sorting the returned slice before
rendering (e.g., sort.SliceStable on the slice returned by registry.List using a
deterministic key like model.ID or model.DisplayName). Locate the slice produced
by registry.List in modelListText, apply the sort using sort.SliceStable(models,
func(i,j int) bool { return models[i].ID < models[j].ID }) (or DisplayName) and
then proceed to build lines (preserving the activeID check and marker logic).
internal/tui/model_test.go (1)

279-283: ⚡ Quick win

Avoid hard-coding evolving catalog entries in this TUI behavior test.

Asserting "claude-sonnet-4.5" and "gemini-2.5-pro" couples this test to registry contents rather than /model list behavior. This will fail on catalog updates even when command handling is correct.

Proposed patch
-	for _, want := range []string{"Active model: gpt-4.1", "provider: openai", "Available models", "* gpt-4.1", "claude-sonnet-4.5", "gemini-2.5-pro"} {
+	for _, want := range []string{"Active model: gpt-4.1", "provider: openai", "Available models", "* gpt-4.1"} {
 		if !transcriptContains(next.transcript, want) {
 			t.Fatalf("expected model transcript to contain %q, got %#v", want, next.transcript)
 		}
 	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/tui/model_test.go` around lines 279 - 283, The test currently
hard-codes specific catalog model names which will break when the registry
changes; update the assertions in the test around
transcriptContains/next.transcript so they verify behavior not exact entries:
assert the presence of "Available models" and the provider and active-model
lines (e.g., "Active model:" and "provider:"), and replace exact name checks
like "claude-sonnet-4.5" and "gemini-2.5-pro" with a more general assertion that
at least one model line exists (e.g., any transcript line matching the
model-list pattern such as lines starting with "* " or non-empty model entries),
using transcriptContains and scanning next.transcript to confirm the list format
rather than specific model names.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/cli/exec.go`:
- Around line 75-77: The CLI currently accepts --max-turns 0 but then silently
ignores it because exec.go only forwards options.maxTurns when > 0
(overrides.MaxTurns = options.maxTurns), so either validate and reject zero at
parse time or propagate an explicit zero; to match current behavior prefer
rejecting zero: add validation where options.maxTurns is parsed/validated (the
code that sets options.maxTurns) to return an error if value == 0, and/or in the
command run flow check options.maxTurns == 0 and return a user-facing error;
reference options.maxTurns and overrides.MaxTurns (and the flag parsing function
that populates options.maxTurns) when adding this validation so the explicit
zero is not silently ignored.

---

Nitpick comments:
In `@internal/cli/exec_test.go`:
- Around line 55-80: Add regression cases for "0" and the empty `--max-turns=`
input to TestRunExecRejectsInvalidMaxTurnsBeforeRuntime: extend the test table
with entries for value "0" and value "" (expecting the same "invalid
--max-turns" message), and adjust how the test builds args so an empty value is
passed as the single token "--max-turns=" instead of separate flag+value; keep
using Run(...) and asserting exitUsage, stdout empty, and stderr contains the
expected message.

In `@internal/tui/model_catalog.go`:
- Around line 23-30: modelListText currently iterates
registry.List(modelregistry.ListOptions{}) and appends in whatever order is
returned; make the output stable by sorting the returned slice before rendering
(e.g., sort.SliceStable on the slice returned by registry.List using a
deterministic key like model.ID or model.DisplayName). Locate the slice produced
by registry.List in modelListText, apply the sort using sort.SliceStable(models,
func(i,j int) bool { return models[i].ID < models[j].ID }) (or DisplayName) and
then proceed to build lines (preserving the activeID check and marker logic).

In `@internal/tui/model_test.go`:
- Around line 279-283: The test currently hard-codes specific catalog model
names which will break when the registry changes; update the assertions in the
test around transcriptContains/next.transcript so they verify behavior not exact
entries: assert the presence of "Available models" and the provider and
active-model lines (e.g., "Active model:" and "provider:"), and replace exact
name checks like "claude-sonnet-4.5" and "gemini-2.5-pro" with a more general
assertion that at least one model line exists (e.g., any transcript line
matching the model-list pattern such as lines starting with "* " or non-empty
model entries), using transcriptContains and scanning next.transcript to confirm
the list format rather than specific model names.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 95687138-0d7b-4911-a2e6-7ce22a83a15d

📥 Commits

Reviewing files that changed from the base of the PR and between d9f5fdc and c6e5df9.

📒 Files selected for processing (7)
  • internal/cli/app.go
  • internal/cli/exec.go
  • internal/cli/exec_test.go
  • internal/tui/model.go
  • internal/tui/model_catalog.go
  • internal/tui/model_test.go
  • internal/tui/plan_command.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/tui/model.go

Comment thread internal/cli/exec.go

@anandh8x anandh8x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's good

  • The npm wrapper is the right native-first shape. bin/zero.ts computes the package root from import.meta.url and delegates to runNpmWrapper, while src/npm-wrapper.ts prefers the built native zero/zero.exe and falls back to src/index.ts for local dev. The latest commits also fixed the no-target message and synchronous Bun.spawn panic path; tests/build-scripts.test.ts now covers native-first, TS fallback, no-target, and spawn-throws behavior.
  • Release packaging now includes the wrapper files. scripts/package-release.ts stages bin/zero.ts and src/npm-wrapper.ts alongside the native binary, README, package.json, and VERSION, so the npm bin entry no longer points at a file missing from release archives.
  • TUI command parsing moved from ad hoc switches to a registry. commandDefinitions gives each slash command a name, aliases, usage, group, description, and kind; parseCommand, resolveCommand, listCommandNames, and formatCommandHelpLines all flow through that single source. /quit and /debug-mode aliases are covered in tests.
  • Footer/help drift was addressed. commandFooterText() is now generated from commandDefinitions with a featured-kind allowlist and a fallback constant for empty input. This directly closes the earlier CodeRabbit footer comment while keeping the footer intentionally shorter than the full registry.
  • The Go TUI now has useful shell-only status commands. /provider, /model, /model list, /context, /config, /debug, and /plan all append system rows without starting an agent run. Tests assert each command stays local (cmd == nil) and that context/provider/model/plan state is rendered.
  • /plan correctly reuses the existing update-plan tool state. planText looks up update_plan, type-asserts a small CurrentPlan() []tools.PlanItem interface, and renders item status/content/notes. That keeps the TUI coupled to the existing tool contract instead of creating a second plan store.
  • --max-turns reaches config overrides. parseExecArgs parses --max-turns / --max-turns=<n>, runExec copies positive values into config.Overrides.MaxTurns, and TestRunExecMaxTurnsReachesConfigOverrides proves the value reaches resolveConfig before provider construction.

Observations (non-blocking)

  1. CodeRabbit's --max-turns 0 finding is real. parseExecMaxTurns accepts zero because it only rejects < 0, but runExec only forwards options.maxTurns when it is > 0. So zero exec --max-turns 0 hello silently behaves exactly like no override. Either reject zero as invalid --max-turns or intentionally propagate zero; rejecting is the safer CLI behavior.

  2. The flag-shaped-value parser gap from #54 still exists. nextFlagValue still accepts the next token as a value even when it starts with -, so zero exec --max-turns --cwd /tmp hello reports invalid --max-turns "--cwd" instead of --max-turns requires a value, and zero exec --prompt --model gpt-4 can still misparse prompt text. Not introduced here, but this PR adds another flag that goes through the same helper, so the surface area grows.

  3. /model list always loads modelregistry.DefaultRegistry(). That's fine for the current catalog shell, but it means a future custom registry passed through tui.Options cannot affect /model list. If provider factory starts accepting injected registries in tests or plugins, the TUI will need a registry field for models too.

  4. The model-list order is stable already. CodeRabbit suggested sorting modelListText, but Registry.List from #56 iterates the registry.models slice, not a map, so it preserves DefaultModelEntries() order. Sorting by ID/provider would change the curated display order rather than fixing nondeterminism. A short comment in modelListText would make that clear.

  5. The /model list test intentionally couples to the catalog. TestModelCommandShowsActiveModelWithoutRunningAgent asserts claude-sonnet-4.5 and gemini-2.5-pro. That is brittle if the catalog changes, but it also proves the TUI lists cross-provider catalog entries. If catalog churn becomes frequent, keep one behavior test generic and move the exact model assertions to modelregistry tests only.

  6. No duplicate command-name/alias test. resolveCommand linearly scans names and aliases; if a future command reuses /quit or /debug-mode, the first one silently wins. A small test that builds a map from commandDefinitions and fails on duplicate names/aliases would lock the registry invariant.

  7. shellOnlyCommandText is clear but intentionally temporary. /doctor, /search, /theme, and /input-style are registered but not wired. The placeholder text is honest; just make sure these commands get follow-up issues so they don't become permanent dead menu items.

No blockers. The wrapper path, TUI command registry, generated footer/help, plan display, and --max-turns override are all good Go-first CLI/TUI progress. The only bug worth fixing before merge is --max-turns 0 being accepted and ignored; everything else is polish or follow-up scope.

@gnanam1990 gnanam1990 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed current head 14dc4e7.

No blockers found. The latest commit fixes the --max-turns 0 regression path and adds explicit coverage for 0 and --max-turns=. The npm wrapper path is native-first with a TS fallback, release packaging includes the wrapper files, and the Go TUI command registry/status commands are covered.

Local validation passed: bun install --frozen-lockfile, go test ./..., bun run typecheck, bun test ./tests --timeout 15000 (290 pass), bun run build, bun run smoke:build, bun run build:go, bun run smoke:go, and git diff --check origin/main...HEAD.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants