V1 CLI ship polish by jeremy · Pull Request #160 · basecamp/basecamp-cli

added 6 commits

February 25, 2026 14:17
Before: Stats: 298ms | 1 request
After:  298ms · 1 request
Add --hints/--no-hints flags (default: on in dev builds, off in release).
When disabled, breadcrumbs are stripped from output via WithoutBreadcrumbs().
Rename "Next:" label to "Hints:" across all output modes.
Remove ResolveProject() call that added ~200ms latency on every bare
`basecamp` invocation. Project ID from config is sufficient; name
resolution was nice-to-have but not worth a synchronous API roundtrip.
Show active --profile name in summary when set.
Styled output now shows category headers with aligned command names,
descriptions, and available actions. JSON/markdown still passes full
structure through app.OK() for machine consumption.
Transform raw SDK structs into clean table columns: id, title (truncated
to 60 chars), type (simplified: Chat::Lines::RichText → chat), project
(from Bucket.Name), and created (relative time). Drops noisy columns
like url, app_url, bookmark, visible_to_clients, raw timestamps.
Replace [name, status] columns with [id, name] — every project in the
list is active so the status column wasted space. Add bookmarked field
to detail view for completeness.

Copilot AI review requested due to automatic review settings

February 25, 2026 22:18
Checks GitHub for latest release, compares with current version.
Detects Homebrew installations and runs brew upgrade automatically;
otherwise directs to GitHub releases. Updates doctor hint to suggest
`basecamp upgrade` when an update is available.
submitQuery() no longer silently skips when the user re-submits the same
query (e.g., pressing Enter again to refresh results). The debounce path
still deduplicates to avoid redundant in-flight requests during typing.

Add height clamp to Search.View() to prevent the list from overflowing
its allocated viewport and pushing chrome off-screen.

chatgpt-codex-connector[bot]

Only humanize search output for styled terminal display; --json/--agent
get the full SDK SearchResult structs with all fields intact. Fall back
to Subject when Title is empty (some Basecamp record types use Subject).
- upgrade.go: use cmd.OutOrStdout() instead of fmt.Println/os.Stdout
- upgrade.go: extract versionChecker/homebrewChecker vars for testability
- upgrade_test.go: add 4 tests covering dev build, current, available, writer
- commands.go: accept io.Writer param, account for experimental prefix in alignment
- search.go: use rune-based truncation for UTF-8 safety

Copilot AI review requested due to automatic review settings

February 25, 2026 22:31
Pointer fields (*bool/*int) enable three-state semantics: nil = not set
(fall through to version.IsDev()), explicit true/false = user preference.

Load from config files and BASECAMP_HINTS/BASECAMP_STATS env vars,
with standard layering: env > local > repo > global > system > defaults.

Strict validation: env bools must be true/false/1/0 (invalid values
ignored to preserve nil semantics); verbose range-checked to 0-2.
Change --hints/--stats defaults to false so Changed() can detect explicit
flag usage. New resolvePreferences() in PersistentPreRunE checks:
explicit flag > config value > version.IsDev() fallback.

Negative flags (--no-stats, --no-hints) only suppress config resolution
when their value is true, not merely when Changed.
Add hints, stats, verbose to validKeys. Bool validation for hints/stats,
int 0-2 validation for verbose. Show preferences in config show when
explicitly set.

Copilot AI review requested due to automatic review settings

February 26, 2026 16:34

@jeremy

The OAuth server expects "full" for write access, not "write".
The wizard was sending an invalid scope, causing auth to silently
fall back to the default.
The config key is "account_id" not "account", so the suggested
command was producing a config error.
len(v) counts bytes, not characters. Multi-byte UTF-8 strings
(e.g. Japanese, emoji) could be sliced mid-rune, producing
invalid output. Use utf8.RuneCountInString and []rune conversion.
Passing theme.Primary (AdaptiveColor) instead of
lipgloss.Color(theme.Primary.Dark) lets lipgloss resolve
Light vs Dark at render time based on terminal background
detection. Fixes incorrect colors on light terminal themes.
Include Rename events so editors using temp-file+rename are detected.
Re-add the resolved directory on re-arm so symlink repoints are tracked.
Replaces byte-slicing loop with the existing widget.Truncate()
which handles multi-byte UTF-8 correctly.
Remove -no-fail from gosec so findings fail the build. Replace the
deferred TODO with an actual dependency-review job that runs on PRs.
Add lint, test-e2e, and race detection to the release gate. Install
golangci-lint and BATS (with cache) to support the expanded checks.
Drop the dogfooding banner from README, remove GOPRIVATE from go install
instructions, and clean up goreleaser release notes and deferred comments.
Download checksums.txt and verify SHA256 before extracting the archive.
When cosign is available, also verify the Sigstore signature against the
GitHub Actions OIDC issuer. Shared tmpdir between download and verify.

@jeremy

Use ::error:: so regressions surface in the PR checks UI with a red
indicator. Job-level continue-on-error keeps it non-blocking.

Copilot AI review requested due to automatic review settings

February 27, 2026 18:55
In mixed-tooling environments (e.g. Homebrew binary built with a
different Go version), command -v may find the wrong golangci-lint
first. Try GOBIN, then GOPATH/bin, then fall back to PATH to ensure
the binary matching the active Go toolchain wins.
The golangci-lint-action was running lint by default then make lint
ran it again. Set install-only: true so make lint is the single
invocation. Add govulncheck as a release-path security check.
The error annotation logged but the step exited 0, making the
regression invisible in the checks UI. The job already has
continue-on-error: true so exit 1 turns the step red without
blocking merge.
Add find_sha256_cmd helper that tries sha256sum before shasum for
Linux compatibility. Tighten cosign identity from a regexp matching
any workflow to an exact match on the release workflow at the
specific tag ref.
Tag pushes don't trigger security.yml (it runs on push-to-main and
PRs). Add workflow_call to security.yml and invoke it from
release.yml so gitleaks, gosec, and trivy must pass before a release
proceeds.
grep with a regex pattern treats dots as wildcards, which could match
unintended lines in checksums.txt. Use grep -F for a literal match.

Copilot AI review requested due to automatic review settings

February 27, 2026 19:09
CloseWatcher now sets w.themeWatcher = nil after closing, which
makes repeated calls safe and prevents the ThemeChangedMsg handler
from re-arming the watcher goroutine after shutdown.
The securego/gosec Docker action runs its own Go toolchain in a
container that cannot inherit the host's git config, so it fails to
resolve the private SDK module. Switch to go install + direct
invocation so gosec uses the host's Go and git credentials.

Mark dependency-review as continue-on-error since it requires GitHub
Advanced Security which is not enabled on this repo.

Copilot AI review requested due to automatic review settings

February 27, 2026 19:27

@jeremy

gosec now resolves private modules correctly (go install instead of
Docker), which means it actually scans the full codebase and finds
100+ pre-existing findings (G304 file inclusion, G104 unhandled
errors in TUI, G117 token fields). These are accepted patterns in a
CLI tool. Use -no-fail so findings are reported via SARIF without
blocking CI.

dependency-review requires GitHub Advanced Security which is not
enabled on this repo — mark continue-on-error so it does not block
the security workflow.