Skip to content

Commit 4e73d79

Browse files
committed
feat: first-run UX — /connect flow, doctor hints, TUI polish, docs refresh
#449: /connect guides Ollama or OpenRouter key entry when no model is configured. Auth PUT discovers OpenRouter. Welcome and status bar cue /connect when no providers are found. #450: doctor treats local connection refused as warning when another provider works. Exit 1 only with no usable provider. Next-step hints. #451: README and install.sh lead with install script and Homebrew. First-prompt quickstart path. Go 1.27.1+ requirement. Config filenames. #452: TUI polish — help title, sessions key 'o', which-key 2s timeout, "New Session" in sidebar, Esc no longer rejects permissions. #453: /init documented as optional Red Hat plugin/role setup. Points users to /connect for model configuration.
1 parent c65e397 commit 4e73d79

37 files changed

Lines changed: 778 additions & 119 deletions

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Security, stability, and correctness fixes from two rounds of code review (61 is
3434
- Goal patterns expanded to multi-ecosystem support (Go, Python, Node, Rust, Java)
3535
- Readline preview for command history
3636
- Overflow false-positive detection in context window warnings
37+
- First-run `/connect` flow: empty-state Ollama/OpenRouter guidance, prompt short-circuit, and OpenRouter API key entry in the TUI
3738

3839
### Architecture
3940
- `Context.clone()` replaces manual copy sites in tool execution

‎README.md‎

Lines changed: 46 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,50 @@ Local-first AI coding assistant. Bring any model — single binary, no runtime d
66

77
[![Go CI](https://github.com/bobbyjohnstx/tinycode/actions/workflows/go-ci.yml/badge.svg)](https://github.com/bobbyjohnstx/tinycode/actions/workflows/go-ci.yml) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
88

9-
## What it is
9+
Local-first, model-agnostic AI coding assistant. A single Go binary embeds the HTTP server, terminal UI, session management, LLM client, and tool execution --- no Node.js, no separate server process. Works with any OpenAI-compatible endpoint (Ollama, vLLM, LM Studio, OpenRouter, and more). Your sessions stay in local SQLite; data leaves your machine only when you send a prompt to a provider you configured.
10+
11+
## Quick start
12+
13+
### Install
14+
15+
```bash
16+
# One-liner (macOS / Linux) — installs to ~/.local/bin
17+
curl -fsSL https://raw.githubusercontent.com/bobbyjohnstx/tinycode/main/install.sh | sh
18+
19+
# Or Homebrew
20+
brew install bobbyjohnstx/tap/tinycode
21+
```
22+
23+
See [docs/install.md](docs/install.md) for platform notes, PATH setup, and verifying the install.
24+
25+
### First prompt
26+
27+
Have a local model running (for example `ollama serve` and `ollama pull qwen3.5:9b`), then:
28+
29+
```bash
30+
tinycode # TUI in the current directory
31+
tinycode /path/to/project # TUI against a project
32+
```
33+
34+
Type a prompt and press Enter:
35+
36+
```
37+
Explain what this repository does in 2 sentences.
38+
```
1039

11-
tinycode is a local-first, model-agnostic AI coding assistant. A single Go binary embeds everything: HTTP server, terminal UI, session management, LLM client, and tool execution. No separate server process, no Node.js, no runtime dependencies.
40+
Press `Ctrl+X` then `m` to pick a model if needed. Full walkthrough: [docs/getting-started.md](docs/getting-started.md).
1241

13-
**Works with any OpenAI-compatible endpoint.** Connect to local models (Ollama, vLLM, LM Studio), cloud providers (OpenRouter, Anthropic, OpenAI), or your own infrastructure (RHOAI, Azure, custom endpoints). Swap models mid-session. No vendor lock-in.
42+
### Build from source
1443

15-
**Your data stays on your machine.** Sessions, config, and conversation history are stored locally in SQLite. No telemetry, no cloud calls, no sign-up. Data only leaves your machine when you explicitly send a prompt to a cloud provider you configured.
44+
```bash
45+
git clone https://github.com/bobbyjohnstx/tinycode.git && cd tinycode
46+
make build
47+
./dist/tinycode
48+
```
1649

17-
tinycode reads your files, runs commands, edits code, and works through multi-step tasks --- the same workflow as cloud AI coding tools, but you choose the model and control the data.
50+
Requires Go 1.27.1+. Other modes: `tinycode serve` (headless API), `tinycode acp` (IDE), `tinycode run` (non-interactive).
51+
52+
## What it is
1853

1954
### Key features
2055

@@ -60,24 +95,6 @@ tinycode also supports:
6095
- **Agent Client Protocol** (`tinycode acp`) --- stdio transport for IDE integration (VS Code, Zed, JetBrains)
6196
- **Non-interactive mode** (`tinycode run`) --- run a prompt and exit, for scripts and CI
6297

63-
## Quick start
64-
65-
```bash
66-
# Build from source
67-
git clone https://github.com/bobbyjohnstx/tinycode.git && cd tinycode
68-
make build
69-
./dist/tinycode
70-
71-
# Or with a specific project directory
72-
./dist/tinycode /path/to/project
73-
74-
# Headless API server
75-
./dist/tinycode serve
76-
77-
# IDE integration (Agent Client Protocol)
78-
./dist/tinycode acp
79-
```
80-
8198
## Architecture
8299

83100
Standard Go layout: `cmd/` for binaries, `internal/` for private packages, `pkg/` for public SDK.
@@ -98,7 +115,7 @@ Single entry point. Subcommands: `tui` (default), `serve`, `web`, `acp`, `run`,
98115
| `provider/` | Provider auto-discovery (Ollama, vLLM, LM Studio, OpenRouter) |
99116
| `agent/` | Agent definitions and prompt files |
100117
| `tool/` | Tool implementations (file ops, shell, grep, glob) |
101-
| `config/` | Config file parsing (`~/.config/tinycode/config.json`), JSONC support |
118+
| `config/` | Config file parsing (`tinycode.jsonc` → `tinycode.json` → `config.json`) |
102119
| `storage/` | SQLite via modernc.org/sqlite, migrations |
103120
| `bus/` | Event bus for inter-component communication |
104121
| `mcp/` | Model Context Protocol client |
@@ -206,9 +223,10 @@ Agents with a `.compact.md` variant automatically use a smaller prompt for model
206223

207224
## Configuration
208225

209-
Config lives at `~/.config/tinycode/config.json` (JSONC supported):
226+
Config is loaded from `~/.config/tinycode/` with a 3-name fallback per directory: `tinycode.jsonc` → `tinycode.json` → `config.json` (first file found wins; JSONC comments are supported). Project config uses the same names under `.tinycode/` (or walking up from the working directory); innermost wins.
210227

211228
```jsonc
229+
// ~/.config/tinycode/tinycode.jsonc
212230
{
213231
"model": "ollama/qwen3.5:9b",
214232
"small_model": "ollama/qwen3.5:1.7b",
@@ -286,7 +304,9 @@ The runtime is Go only. The `packages/` tree is legacy TypeScript retained for `
286304

287305
## Documentation
288306

289-
- **Spec index (start here):** [docs/spec/README.md](docs/spec/README.md)
307+
- **Install:** [docs/install.md](docs/install.md)
308+
- **Getting started:** [docs/getting-started.md](docs/getting-started.md)
309+
- **Spec index:** [docs/spec/README.md](docs/spec/README.md)
290310
- **Architecture:** [docs/architecture.md](docs/architecture.md)
291311
- **Build & embed web UI:** [docs/building.md](docs/building.md)
292312
- **TypeScript features not in Go:** [docs/spec/16-not-implemented.md](docs/spec/16-not-implemented.md)

‎cmd/tinycode/commands.go‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,8 @@ func runInit() {
443443
_ = json.Unmarshal(data, &result)
444444
}
445445

446-
fmt.Println("tinycode init")
446+
fmt.Println("tinycode init — Red Hat plugin/role setup")
447+
fmt.Println("(Not required for first run. For models: /connect in the TUI, OPENROUTER_API_KEY, or Ollama.)")
447448
fmt.Println()
448449

449450
// --- Step 1: Model selection ---

‎cmd/tinycode/doctor.go‎

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import (
1818
const (
1919
checkPass = "\033[32m✓\033[0m" // green check
2020
checkFail = "\033[31m✗\033[0m" // red X
21-
checkWarn = "\033[33m!\033[0m" // yellow !
21+
checkWarn = "\033[33m!\033[0m" // yellow !
2222
)
2323

2424
func runDoctor() {
@@ -160,21 +160,25 @@ func checkProviders(cfg *config.Info, failed *bool) {
160160
probes = append(probes, providerProbe{name: "vllm", url: v})
161161
}
162162

163+
anyLocal := false
163164
for _, p := range probes {
164165
if probeHTTP(p.url) {
165166
fmt.Printf("%s Provider: %s -- connected (%s)\n", checkPass, p.name, p.url)
166-
} else {
167-
fmt.Printf("%s Provider: %s -- connection refused (%s)\n", checkFail, p.name, p.url)
168-
*failed = true
167+
anyLocal = true
168+
continue
169169
}
170+
// Local refusal is a warning — fail only if no usable provider path remains.
171+
fmt.Printf("%s Provider: %s -- connection refused (%s)\n", checkWarn, p.name, p.url)
170172
}
171173

172-
// OpenRouter: check API key
174+
hasCloudKey := false
173175
if apiKey := os.Getenv("OPENROUTER_API_KEY"); apiKey != "" {
174176
fmt.Printf("%s Provider: openrouter -- API key set\n", checkPass)
177+
hasCloudKey = true
175178
}
176179

177180
// Config-defined providers
181+
anyConfig := false
178182
knownProbe := map[string]bool{"ollama": true, "vllm": true, "lm-studio": true, "openrouter": true}
179183
for id, pc := range cfg.Provider {
180184
if knownProbe[id] {
@@ -184,15 +188,35 @@ func checkProviders(cfg *config.Info, failed *bool) {
184188
if baseURL, ok := pc.Options["baseURL"].(string); ok && baseURL != "" {
185189
if probeHTTP(baseURL) {
186190
fmt.Printf("%s Provider: %s -- connected (%s)\n", checkPass, id, baseURL)
191+
anyConfig = true
187192
} else {
188-
fmt.Printf("%s Provider: %s -- connection refused (%s)\n", checkFail, id, baseURL)
189-
*failed = true
193+
fmt.Printf("%s Provider: %s -- connection refused (%s)\n", checkWarn, id, baseURL)
190194
}
191195
continue
192196
}
193197
}
194198
fmt.Printf("%s Provider: %s -- configured (no URL to probe)\n", checkWarn, id)
195199
}
200+
201+
if providerPathUsable(anyLocal, hasCloudKey, anyConfig) {
202+
return
203+
}
204+
205+
fmt.Printf("%s Provider: no usable provider path (no local provider connected and no cloud API key)\n", checkWarn)
206+
printProviderNextSteps()
207+
*failed = true
208+
}
209+
210+
// providerPathUsable reports whether doctor found at least one way to reach a model.
211+
func providerPathUsable(anyLocalConnected, hasCloudAPIKey, anyConfigConnected bool) bool {
212+
return anyLocalConnected || hasCloudAPIKey || anyConfigConnected
213+
}
214+
215+
func printProviderNextSteps() {
216+
fmt.Println(" next steps:")
217+
fmt.Println(" - start Ollama: ollama serve")
218+
fmt.Println(" - or set OPENROUTER_API_KEY for cloud models")
219+
fmt.Println(" - then run: tinycode models")
196220
}
197221

198222
func ollamaURL() string {

‎cmd/tinycode/doctor_test.go‎

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package main
2+
3+
import "testing"
4+
5+
func TestProviderPathUsable_Scenario(t *testing.T) {
6+
tests := []struct {
7+
name string
8+
anyLocalConnected bool
9+
hasCloudAPIKey bool
10+
anyConfigConnected bool
11+
want bool
12+
}{
13+
{
14+
name: "cloud only with openrouter key",
15+
hasCloudAPIKey: true,
16+
anyLocalConnected: false,
17+
want: true,
18+
},
19+
{
20+
name: "local only",
21+
anyLocalConnected: true,
22+
want: true,
23+
},
24+
{
25+
name: "config provider only",
26+
anyConfigConnected: true,
27+
want: true,
28+
},
29+
{
30+
name: "no local and no cloud key",
31+
anyLocalConnected: false,
32+
hasCloudAPIKey: false,
33+
want: false,
34+
},
35+
{
36+
name: "all paths available",
37+
anyLocalConnected: true,
38+
hasCloudAPIKey: true,
39+
anyConfigConnected: true,
40+
want: true,
41+
},
42+
}
43+
44+
for _, tt := range tests {
45+
t.Run(tt.name, func(t *testing.T) {
46+
got := providerPathUsable(tt.anyLocalConnected, tt.hasCloudAPIKey, tt.anyConfigConnected)
47+
if got != tt.want {
48+
t.Errorf("providerPathUsable(%v, %v, %v) = %v, want %v",
49+
tt.anyLocalConnected, tt.hasCloudAPIKey, tt.anyConfigConnected, got, tt.want)
50+
}
51+
})
52+
}
53+
}

‎cmd/tinycode/main.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ func printUsage() {
118118
fmt.Println(" status Show server health and status")
119119
fmt.Println(" export Export session messages as JSON")
120120
fmt.Println(" plugin Manage plugins (list, install, uninstall)")
121-
fmt.Println(" init Interactive plugin setup by role")
121+
fmt.Println(" init Red Hat plugin/role setup (optional; not first-run)")
122122
fmt.Println(" agent List available agents")
123123
fmt.Println(" doctor Run diagnostics and check system health")
124124
fmt.Println(" debug Debug info (config, paths)")

‎docs/install.md‎

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,50 @@
11
# Installation
22

3-
tinycode is a single static binary with no runtime dependencies. Works with any OpenAI-compatible LLM endpoint --- local or cloud. Build from source, then run.
3+
tinycode is a single static binary with no runtime dependencies. Works with any OpenAI-compatible LLM endpoint --- local or cloud.
44

5-
## Prerequisites
5+
## Binary install (recommended)
66

7-
- **Go 1.27+** -- [download](https://go.dev/dl/)
7+
### One-liner (`install.sh`)
8+
9+
Downloads the latest GitHub release for your platform and installs to `~/.local/bin`:
10+
11+
```bash
12+
curl -fsSL https://raw.githubusercontent.com/bobbyjohnstx/tinycode/main/install.sh | sh
13+
```
14+
15+
Supported platforms: macOS and Linux (`amd64` / `arm64`). Override defaults with environment variables:
16+
17+
| Variable | Default | Purpose |
18+
|----------|---------|---------|
19+
| `TINYCODE_INSTALL_DIR` | `$HOME/.local/bin` | Install destination |
20+
| `VERSION` | latest release | Pin a release tag (for example `v2.1.3`) |
21+
| `TINYCODE_REPO` | `bobbyjohnstx/tinycode` | GitHub `owner/repo` |
22+
| `TINYCODE_BASE_URL` | `https://github.com` | Release download host |
23+
| `TINYCODE_API_URL` | `https://api.github.com` | Releases API host |
24+
25+
If `~/.local/bin` is not on your `PATH`, the script prints shell-specific instructions.
26+
27+
### Homebrew
28+
29+
```bash
30+
brew install bobbyjohnstx/tap/tinycode
31+
```
32+
33+
### Manual download
34+
35+
Download a platform archive from [GitHub Releases](https://github.com/bobbyjohnstx/tinycode/releases) (for example `tinycode-darwin-arm64.tar.gz`), extract the `tinycode` binary, and place it on your `PATH`.
36+
37+
## Build from source
38+
39+
### Prerequisites
40+
41+
- **Go 1.27.1+** -- [download](https://go.dev/dl/) (matches `go.mod`)
842
- **make** -- included on macOS and most Linux distributions
943
- **git** -- to clone the repo
1044

1145
No C toolchain needed. SQLite is pure Go (`modernc.org/sqlite`), so CGO is not required.
1246

13-
## macOS
47+
### macOS
1448

1549
```bash
1650
# Clone and build
@@ -31,13 +65,10 @@ Go can be installed via Homebrew:
3165
brew install go
3266
```
3367

34-
## Linux
68+
### Linux
3569

3670
```bash
37-
# Install Go (Debian/Ubuntu)
38-
sudo apt install golang
39-
40-
# Or download from https://go.dev/dl/ for the latest version
71+
# Install Go 1.27.1+ from https://go.dev/dl/ (distro packages may be older)
4172

4273
# Clone and build
4374
git clone https://github.com/bobbyjohnstx/tinycode.git
@@ -64,15 +95,33 @@ Without a clipboard utility, clipboard commands will fail with an error message.
6495

6596
For `/paste-image` with image support, `xclip` must be compiled with image format support (the default package includes it). On Wayland, `wl-paste` handles images natively.
6697

67-
## Windows (WSL)
98+
### Windows (WSL)
6899

69100
Native Windows is not supported -- bubbletea requires a Unix terminal.
70101

71102
Use WSL2 instead:
72103

73104
1. Install WSL2: `wsl --install` (from PowerShell as admin)
74105
2. Open your WSL distribution (Ubuntu is the default)
75-
3. Follow the Linux instructions above
106+
3. Follow the Linux instructions above (binary install or build from source)
107+
108+
## Configuration
109+
110+
Config files are loaded with a 3-name fallback in each directory:
111+
112+
1. `tinycode.jsonc`
113+
2. `tinycode.json`
114+
3. `config.json`
115+
116+
Global config: `~/.config/tinycode/` (first matching name wins). Project config: `.tinycode/` walking up from the working directory (innermost wins). JSONC comments are supported.
117+
118+
```jsonc
119+
// ~/.config/tinycode/tinycode.jsonc
120+
{
121+
"model": "ollama/qwen3.5:9b",
122+
"default_agent": "build"
123+
}
124+
```
76125

77126
## Cross-compilation
78127

@@ -94,7 +143,7 @@ This produces binaries in `dist/` for:
94143

95144
| Requirement | When | Notes |
96145
|-------------|------|-------|
97-
| Go 1.22+ | Build only | Not needed at runtime |
146+
| Go 1.27.1+ | Build only | Not needed at runtime; matches `go.mod` |
98147
| make | Build only | For the Makefile targets |
99148
| xclip / wl-clipboard | Runtime (optional) | Linux clipboard support |
100149

@@ -124,6 +173,12 @@ tinycode models
124173

125174
## Updating
126175

176+
### Binary install
177+
178+
Re-run `install.sh`, or `brew upgrade tinycode` if you installed via Homebrew.
179+
180+
### From source
181+
127182
Pull the latest source and rebuild:
128183

129184
```bash

0 commit comments

Comments
 (0)