Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Set up locked Komizo
- name: Set up locked komizo
uses: nicodes/komizo/setup@40086036977a5fc0119d6c4c1d5eb6ae3ab3b4b9
env:
GH_TOKEN: ${{ github.token }}
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
persist-credentials: false
- name: Set up locked Komizo
- name: Set up locked komizo
uses: nicodes/komizo/setup@40086036977a5fc0119d6c4c1d5eb6ae3ab3b4b9
env:
GH_TOKEN: ${{ github.token }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/dependency-policy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
persist-credentials: false
ref: main
timeout-minutes: 3
- name: Set up locked Komizo
- name: Set up locked komizo
uses: nicodes/komizo/setup@40086036977a5fc0119d6c4c1d5eb6ae3ab3b4b9
env:
GH_TOKEN: ${{ github.token }}
Expand Down
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Repository instructions

# Product naming

Use `termcade` in lowercase in user-facing text. Keep language identifiers, environment variables and protocol fields in their technical spelling. Usage documentation is generated from private `nicodes/docs`.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- Generated from private documentation source. Do not edit directly. Source SHA256: 409b6c0b9be470e71a679e85eb2b2d51d2a0d4da678f797aca34b589f6aabc79 -->
<!-- Generated from private documentation source. Do not edit directly. Source SHA256: 2230568effdc3fa062e56fd1f29bdff2a901e2086acd330f287ff9f31dac1c83 -->

# Termcade
# termcade

A terminal arcade with sandboxed WebAssembly games. It includes Asteroid, Tetris and Brickough. Use a truecolor terminal at least 80×24 cells.

Expand All @@ -16,7 +16,7 @@ To create a game, run `termcade dev new you/mygame`, then use `termcade dev buil

## Development

Install the [Komizo release](https://github.com/nicodes/komizo#install) matching `komizo.lock`.
Install the [komizo release](https://github.com/nicodes/komizo#install) matching `komizo.lock`.

```sh
komizo check # Complete project checks
Expand Down
2 changes: 1 addition & 1 deletion account.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func cmdLoginContext(ctx context.Context, args []string, out io.Writer) error {
return fmt.Errorf("usage: termcade login")
}
client := registry.New(registry.URL(nil), "")
session, err := client.DeviceLogin(ctx, "Termcade CLI", func(uri, code string) {
session, err := client.DeviceLogin(ctx, "termcade CLI", func(uri, code string) {
fmt.Fprintf(out, "\nUsing a browser, visit:\n %s\n\nAnd enter this code:\n %s\n\nWaiting for approval (Ctrl+C to cancel)…\n", uri, code)
})
if err != nil {
Expand Down
2 changes: 1 addition & 1 deletion internal/registry/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ func PairingURLWithCode(base, code string) string {
func (c *Client) StartDevice(ctx context.Context, deviceName string) (DeviceRound, error) {
deviceName = safeText(deviceName, 200)
if deviceName == "" {
deviceName = "Termcade CLI"
deviceName = "termcade CLI"
}
var out deviceStartResponse
err := c.doContext(ctx, http.MethodPost, "/v1/device/start", map[string]string{
Expand Down
14 changes: 7 additions & 7 deletions internal/registry/device_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func TestDeviceRoundPendingThenApproved(t *testing.T) {
case "/v1/device/start":
var body map[string]string
json.NewDecoder(r.Body).Decode(&body)
if body["client_id"] != clientID || body["device_name"] != "Termcade CLI" {
if body["client_id"] != clientID || body["device_name"] != "termcade CLI" {
t.Errorf("start body = %#v", body)
}
json.NewEncoder(w).Encode(startResponse(nil))
Expand All @@ -74,7 +74,7 @@ func TestDeviceRoundPendingThenApproved(t *testing.T) {
defer srv.Close()

client := New(srv.URL, "")
round, err := client.StartDevice(context.Background(), "Termcade CLI")
round, err := client.StartDevice(context.Background(), "termcade CLI")
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -115,7 +115,7 @@ func TestExpiredRoundRestartsWithBackoff(t *testing.T) {
defer srv.Close()

displays := 0
session, err := New(srv.URL, "").deviceLogin(context.Background(), "Termcade CLI", func(uri, code string) {
session, err := New(srv.URL, "").deviceLogin(context.Background(), "termcade CLI", func(uri, code string) {
displays++
if uri != PairingURI || !userCodeRE.MatchString(code) {
t.Errorf("unsafe display %q %q", uri, code)
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestServerErrorTextIsTerminalSafe(t *testing.T) {
json.NewEncoder(w).Encode(map[string]string{"message": "bad\x1b[2J\nrequest"})
}))
defer srv.Close()
_, err := New(srv.URL, "").StartDevice(context.Background(), "Termcade CLI")
_, err := New(srv.URL, "").StartDevice(context.Background(), "termcade CLI")
if err == nil || strings.ContainsAny(err.Error(), "\x1b\n\r") || err.Error() != "bad[2Jrequest" {
t.Fatalf("unsafe server error = %q", err)
}
Expand All @@ -165,7 +165,7 @@ func TestOversizedDeviceResponseIsRejected(t *testing.T) {
w.Write([]byte(`{"device_code":"` + strings.Repeat("A", maxJSONResponse) + `"}`))
}))
defer srv.Close()
_, err := New(srv.URL, "").StartDevice(context.Background(), "Termcade CLI")
_, err := New(srv.URL, "").StartDevice(context.Background(), "termcade CLI")
if err == nil || err.Error() != "the marketplace returned malformed data" {
t.Fatalf("oversized response error = %v", err)
}
Expand All @@ -191,7 +191,7 @@ func TestHostileDeviceStartFieldsAreRejected(t *testing.T) {
json.NewEncoder(w).Encode(startResponse(map[string]any{test.field: test.value}))
}))
defer srv.Close()
_, err := New(srv.URL, "").StartDevice(context.Background(), "Termcade CLI")
_, err := New(srv.URL, "").StartDevice(context.Background(), "termcade CLI")
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("error=%v, want %q", err, test.want)
}
Expand Down Expand Up @@ -253,7 +253,7 @@ func TestRevokedCredentialIsNotReturnedAsSession(t *testing.T) {
}
}))
defer srv.Close()
_, err := New(srv.URL, "").deviceLogin(context.Background(), "Termcade CLI", func(string, string) {}, fakePolicy())
_, err := New(srv.URL, "").deviceLogin(context.Background(), "termcade CLI", func(string, string) {}, fakePolicy())
if !errors.Is(err, ErrLoginRequired) {
t.Fatalf("revocation error = %v, want ErrLoginRequired", err)
}
Expand Down
4 changes: 2 additions & 2 deletions internal/shell/product.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,7 +860,7 @@ func (m Model) productItems() []productItem {
case "pairing":
items = append(items, productItem{Label: "Secure browser approval", Detail: "Never enter an account password or email verification code here."}, productItem{Label: m.app.pairingURL, Kind: "open-url", ID: m.app.pairingURL}, productItem{Label: "Pairing code: " + m.app.round.UserCode, Detail: "Waiting for approval. No browser is opened automatically."})
case "docs":
items = append(items, productItem{Label: "TERMCADE", Detail: "One arcade in your terminal and browser."}, productItem{Label: "Library", Detail: "Games you add appear in the navigation. Play installs a missing package. Local installations remain playable offline."}, productItem{Label: "Two removal actions", Detail: "Remove from Library changes your account. Uninstall here removes only this machine's copy."}, productItem{Label: "Build a game", Detail: "termcade dev new author/slug\ntermcade dev build\ntermcade dev install game.tcade"}, productItem{Label: "Publish", Detail: "termcade publish <github-repo> <tag> [asset]"}, productItem{Label: "Keyboard", Detail: "Ctrl+N toggles navigation. Tab is next and Shift+Tab is previous in lists, actions, forms, and dialogs. Enter activates; Esc goes back/pauses; Ctrl+C quits. PgUp/PgDown scrolls long text."}, productItem{Label: "Gameplay", Detail: "Arrows/WASD to move · Space/Z action A · X action B · Enter start. Pause retains keyboard navigation and pixel selection."})
items = append(items, productItem{Label: "termcade", Detail: "One arcade in your terminal and browser."}, productItem{Label: "Library", Detail: "Games you add appear in the navigation. Play installs a missing package. Local installations remain playable offline."}, productItem{Label: "Two removal actions", Detail: "Remove from Library changes your account. Uninstall here removes only this machine's copy."}, productItem{Label: "Build a game", Detail: "termcade dev new author/slug\ntermcade dev build\ntermcade dev install game.tcade"}, productItem{Label: "Publish", Detail: "termcade publish <github-repo> <tag> [asset]"}, productItem{Label: "Keyboard", Detail: "Ctrl+N toggles navigation. Tab is next and Shift+Tab is previous in lists, actions, forms, and dialogs. Enter activates; Esc goes back/pauses; Ctrl+C quits. PgUp/PgDown scrolls long text."}, productItem{Label: "Gameplay", Detail: "Arrows/WASD to move · Space/Z action A · X action B · Enter start. Pause retains keyboard navigation and pixel selection."})
}
return items
}
Expand Down Expand Up @@ -1661,7 +1661,7 @@ func (m Model) productNavView(width, height int) string {
accountIndex := len(items) - 1
identity := renderItem(accountIndex)
bodyHeight := max(0, height-lipgloss.Height(identity))
lines := []string{lipgloss.NewStyle().Foreground(lipgloss.Color("#e6c945")).Bold(true).Render("TERMCADE"), ""}
lines := []string{lipgloss.NewStyle().Foreground(lipgloss.Color("#e6c945")).Bold(true).Render("termcade"), ""}
visibleRows := max(0, bodyHeight-len(lines))
start := 0
if m.app.nav < accountIndex {
Expand Down
8 changes: 4 additions & 4 deletions internal/shell/product_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ func TestProductCtrlNTogglesNavigationWithoutFooterStop(t *testing.T) {
if cmd != nil || m.app.loc != location || m.app.selection != 1 || m.app.gen != generation || len(fixture.calls) != calls {
t.Fatal("navigation toggle changed page, selection, or service state")
}
if width < sidebarBreakpoint && strings.Contains(ansi.Strip(view(m)), "TERMCADE") != (want == 0) {
if width < sidebarBreakpoint && strings.Contains(ansi.Strip(view(m)), "termcade") != (want == 0) {
t.Fatal("compact navigation did not open/close with the toggle")
}
}
Expand Down Expand Up @@ -1094,7 +1094,7 @@ func TestProductSidebarBreakpointAndContentCap(t *testing.T) {
m, _ = step(t, m, tea.WindowSizeMsg{Width: 80, Height: 24})
m.app.focus = 0
first = strings.Split(ansi.Strip(view(m)), "\n")[0]
if !strings.HasPrefix(first, "TERMCADE") || []rune(first)[21] != '│' {
if !strings.HasPrefix(first, "termcade") || []rune(first)[21] != '│' {
t.Fatal("compact navigation is not an overlay")
}
}
Expand All @@ -1117,7 +1117,7 @@ func TestProductNavigationLeavesMainFooterUncovered(t *testing.T) {
t.Fatal("open navigation missing the shared footer")
}
for i := openCut; i < len(opened); i++ {
if strings.Contains(opened[i], "TERMCADE") || (len([]rune(opened[i])) > sidebarWidth-1 && []rune(opened[i])[sidebarWidth-1] == '│' && strings.Count(opened[i], "─") >= 8) {
if strings.Contains(opened[i], "termcade") || (len([]rune(opened[i])) > sidebarWidth-1 && []rune(opened[i])[sidebarWidth-1] == '│' && strings.Count(opened[i], "─") >= 8) {
t.Fatalf("navigation covered main footer row %d: %q", i, opened[i])
}
}
Expand Down Expand Up @@ -1150,7 +1150,7 @@ func TestProductNavigationOverlaysSmallAndPushesLargeOnlyWhenOpen(t *testing.T)
t.Fatalf("overlay reflowed content on row %d", i)
}
}
if !strings.HasPrefix(opened[0], "TERMCADE") {
if !strings.HasPrefix(opened[0], "termcade") {
t.Fatal("small navigation is not an overlay")
}
} else {
Expand Down
2 changes: 1 addition & 1 deletion product_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func (p *productBackend) request(ctx context.Context, req shell.ProductRequest)
}
c := registry.New(p.store.Registry, "").WithContext(ctx)
if req.Kind == "pair-start" {
round, err := c.StartDevice(ctx, "Termcade TUI")
round, err := c.StartDevice(ctx, "termcade TUI")
return shell.ProductReply{Round: round, PairingURL: p.store.PairingURL}, err
}
s, err := c.CompleteDevice(ctx, req.Round)
Expand Down