Author SHA1 Message Date
james.maes a279c3e5de fix(exec): resolve handoff executable 2026-05-26 16:25:45 -05:00
james.maes 60a7e26eac fix(ui): use prefix picker filtering 2026-05-26 16:19:19 -05:00
james.maes 5376dc561e feat(ui): add picker typeahead 2026-05-26 16:09:46 -05:00
james.maes b7880fffab feat(ui): add terminal picker
Closes #4
2026-05-26 15:52:57 -05:00
9 changed files with 71 additions and 372 deletions

No files matched your search

-202
View File
@@ -1,202 +0,0 @@
name: Release
on:
push:
tags:
- "v*"
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
test:
name: Go checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: go vet
run: go vet ./...
- name: go test
run: go test -race -count=1 ./...
- name: go build
run: go build ./...
build:
name: Build ${{ matrix.artifact }}
needs: test
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- artifact: limen-linux-amd64
goos: linux
goarch: amd64
- artifact: limen-linux-arm64
goos: linux
goarch: arm64
- artifact: limen-macos-amd64
goos: darwin
goarch: amd64
- artifact: limen-macos-arm64
goos: darwin
goarch: arm64
steps:
- uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
cache: true
- name: Resolve version
id: version
run: |
VERSION="${GITHUB_REF_NAME#v}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
- name: Build binary
env:
CGO_ENABLED: "0"
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
VERSION: ${{ steps.version.outputs.version }}
run: |
mkdir -p dist
go build -trimpath -ldflags "-s -w -X main.version=${VERSION}" -o "dist/${{ matrix.artifact }}" .
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
path: dist/${{ matrix.artifact }}
release:
name: Publish GitHub release
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Prepare release assets
run: |
mkdir -p release
find artifacts -type f -exec cp {} release/ \;
cd release
shasum -a 256 ./limen-* > checksums.txt
ls -la
- name: Create release
uses: softprops/action-gh-release@v2
with:
files: release/*
generate_release_notes: true
update-homebrew:
name: Update Homebrew tap
needs: release
runs-on: ubuntu-latest
env:
HOMEBREW_TAP_TOKEN_SET: ${{ secrets.HOMEBREW_TAP_TOKEN != '' }}
steps:
- name: Verify Homebrew tap token
run: |
if [ "${HOMEBREW_TAP_TOKEN_SET}" != "true" ]; then
echo "::error::HOMEBREW_TAP_TOKEN repo secret is required to update KofTwentyTwo/homebrew-tap."
exit 1
fi
- name: Download all artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Checkout Homebrew tap
uses: actions/checkout@v4
with:
repository: KofTwentyTwo/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-tap
- name: Update formula
env:
TAG_NAME: ${{ github.ref_name }}
run: |
VERSION="${TAG_NAME#v}"
SHA_MACOS_ARM64=$(shasum -a 256 artifacts/limen-macos-arm64/limen-macos-arm64 | cut -d' ' -f1)
SHA_MACOS_AMD64=$(shasum -a 256 artifacts/limen-macos-amd64/limen-macos-amd64 | cut -d' ' -f1)
SHA_LINUX_ARM64=$(shasum -a 256 artifacts/limen-linux-arm64/limen-linux-arm64 | cut -d' ' -f1)
SHA_LINUX_AMD64=$(shasum -a 256 artifacts/limen-linux-amd64/limen-linux-amd64 | cut -d' ' -f1)
FORMULA="homebrew-tap/Formula/limen.rb"
mkdir -p "$(dirname "$FORMULA")"
cat > "$FORMULA" <<RUBY
class Limen < Formula
desc "Terminal launcher TUI for tmux and SSH"
homepage "https://github.com/KofTwentyTwo/limen"
version "${VERSION}"
license "GPL-3.0-only"
depends_on "tmux"
host_cpu = RbConfig::CONFIG.fetch("host_cpu", "")
macos_arm = OS.mac? && (Hardware::CPU.arm? || host_cpu == "arm64" || RUBY_PLATFORM.start_with?("arm64-darwin"))
linux_arm = OS.linux? && (Hardware::CPU.arm? || host_cpu == "aarch64" || host_cpu == "arm64")
if macos_arm
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-macos-arm64"
sha256 "${SHA_MACOS_ARM64}"
elsif OS.mac?
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-macos-amd64"
sha256 "${SHA_MACOS_AMD64}"
elsif linux_arm
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-linux-arm64"
sha256 "${SHA_LINUX_ARM64}"
else
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-linux-amd64"
sha256 "${SHA_LINUX_AMD64}"
end
def install
binary = Dir["limen-*"].first
chmod 0755, binary
bin.install binary => "limen"
end
test do
assert_match version.to_s, shell_output("#{bin}/limen --version")
end
end
RUBY
- name: Commit and push
run: |
cd homebrew-tap
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add Formula/limen.rb
if git status -s | grep -q "Formula/limen.rb"; then
git commit -m "Update limen to ${GITHUB_REF_NAME}"
git push
else
echo "Formula already current."
fi
+11 -12
View File
@@ -8,30 +8,29 @@ Project memory for AI assistants working in this repo. Read this first.
## Current status
**Early implementation.** The repository contains:
**Design phase.** No Go source yet. The repository contains:
- `docs/DESIGN.md` — full design and requirements document.
- `internal/` packages for config, state, probing, SSH, tmux parsing, exec planning, formatting, and UI.
- `main.go`, `go.mod`, and `flake.nix` for the working binary and Nix package.
- `.github/` — CI and release workflows, issue templates, PR template.
- Standard repo scaffolding (README, LICENSE, CONTRIBUTING, etc.).
- `.github/` — CI workflow placeholder, issue templates, PR template.
The next implementation milestones are to:
Implementation work has not started. The next milestone is to:
1. Harden the terminal UI with real-world tmux and SSH smoke testing.
2. Keep release automation green for tagged releases and downstream package managers.
3. Continue from `docs/DESIGN.md`; if behavior changes, update the design doc in the same PR.
1. Add `go.mod`, `main.go`, and a minimal `internal/` package skeleton matching §6.3 of `DESIGN.md`.
2. Wire up the Nix flake (`flake.nix`) so `nix run` produces a binary.
3. Implement stages incrementally — config parsing first, then probing, then UI.
## Key conventions
| Topic | Convention |
| ------------------ | ------------------------------------------------------------------------- |
| Language | Go 1.24+, no CGO |
| Language | Go 1.21+, no CGO |
| TUI framework | `bubbletea` + `lipgloss` |
| Default branch | `develop` (PRs target this); `main` reserved for tagged releases |
| Commit style | Conventional Commits (`feat:`, `fix:`, `docs:` …) — see `CONTRIBUTING.md` |
| License | GPL-3.0 |
| Build | Nix flake (`packages.default`) + plain `go build` for dev |
| Release | GitHub Actions tag workflow, direct Go cross-builds, GitHub Releases, Homebrew tap |
| Release | goreleaser via GitHub Actions on tag push |
| Test framework | Standard `testing` package; `teatest` for TUI integration |
| Go modules | One `internal/` per concern (config, state, probe, ssh, ui, exec, format) |
@@ -48,8 +47,8 @@ The next implementation milestones are to:
Targets in order of priority:
1. **Nix flake** (`nix run github:KofTwentyTwo/limen`) — primary distribution channel.
2. **Homebrew tap** at `KofTwentyTwo/homebrew-tap` — `brew install KofTwentyTwo/tap/limen`.
3. **GitHub Releases** with direct Go-built binaries for macOS/Linux × amd64/arm64.
2. **Homebrew tap** at `KofTwentyTwo/homebrew-tap` (not yet created) — `brew install KofTwentyTwo/tap/limen`.
3. **GitHub Releases** with goreleaser-built binaries for darwin/linux × amd64/arm64.
Eventual goal: submit to `homebrew-core` once the tool stabilizes.
+1 -7
View File
@@ -39,7 +39,6 @@ Examples:
- Go: standard `gofmt`. `golangci-lint` runs in CI (will be added once Go code lands).
- Markdown: 100-column soft wrap; tables and code blocks may exceed.
- Comments: explain *why*, not *what*. Code shows what.
- Local tool dependencies are listed in [`docs/DEV-TOOLS.md`](docs/DEV-TOOLS.md).
## Tests
@@ -61,12 +60,7 @@ Examples:
## Releases
Tags trigger GitHub Actions, which runs the Go checks, builds macOS/Linux binaries for amd64/arm64, publishes GitHub Release assets, and updates the `KofTwentyTwo/homebrew-tap` formula. See [`docs/DESIGN.md` §10.4](docs/DESIGN.md).
Release prerequisites:
- `KofTwentyTwo/homebrew-tap` must exist.
- The `limen` repository must have a `HOMEBREW_TAP_TOKEN` Actions secret with contents read/write access to `KofTwentyTwo/homebrew-tap`.
- The release tag must use the `vMAJOR.MINOR.PATCH` form, for example `v0.1.0`.
Tags trigger goreleaser via GitHub Actions, which cross-compiles for darwin/linux × amd64/arm64 and publishes binaries to the GitHub Releases page. See [`docs/DESIGN.md` §10.4](docs/DESIGN.md).
## Code of conduct
+51 -63
View File
@@ -2,83 +2,51 @@
> *Latin: "threshold". The doorsill one crosses when entering.*
A two-stage terminal launcher TUI for tmux and SSH. The first thing you see when opening a new terminal: pick a host, pick a session, get to work.
A terminal launcher TUI for tmux and SSH. The first thing you see when you open a new terminal: pick a host, pick a session, get to work.
**Status**: Active implementation. The TUI launches host and session pickers. See [`docs/DESIGN.md`](docs/DESIGN.md) for the canonical architecture specification.
**Status:** Design phase. No working binary yet — see [`docs/DESIGN.md`](docs/DESIGN.md) for the spec we're building toward.
---
## What It Does
## What it does
`limen` replaces tmux's default name-prompt-on-launch with an interactive, keyboard-driven TUI:
Replaces tmux's name-prompt-on-launch with a two-stage TUI:
1. **Host Picker**: Choose `localhost` or one of your declared SSH targets. Live status indicators show reachability and active session counts.
2. **Session Picker**: Pick an existing tmux session to attach to, or spawn a new named session. Behaves consistently across local and remote targets.
1. **Host picker** — choose `localhost` or one of your declared SSH targets. Live status dots show which hosts are reachable and how many sessions each has.
2. **Session picker** — pick an existing tmux session to attach to, or create a new one with a name. Works the same whether the host is local or remote.
Pressing `Esc` at any stage drops you into a fresh unnamed tmux session at the selected host with zero deliberation tax.
Press `Esc` at any point and `limen` drops you into a fresh unnamed tmux session at the appropriate host. No deliberation tax.
![limen Launcher Architecture & Session Flow](docs/architecture.png)
---
## Tech Stack & Architecture
- **Language & Runtime**: Go 1.22+
- **TUI Engine**: [Charm Bubble Tea](https://github.com/charmbracelet/bubbletea) (Elm Architecture) & [Lip Gloss](https://github.com/charmbracelet/lipgloss) styling
- **Orchestration**: `tmux` IPC & native OpenSSH background client stream
- **Configuration**: Single JSON fleet declaration at `~/.config/limen/hosts.json`
---
## Installation
### Homebrew (macOS / Linux)
```bash
brew install KofTwentyTwo/tap/limen
```
╭─ limen ─────────────────────────╮ ╭─ DETAILS ──────────────────────────╮
│ │ │ │
│ ● localhost (renova) 3 sess. │ │ prod │
│ ● prod 2 sess. │ ◀ │ ──────────────────────────────────│
│ ● dev 1 sess. │ │ deploy@prod-server-01.example.com │
│ ○ builder unreach. │ │ │
│ ● sandbox — │ │ Status: ● online │
│ │ │ Sessions: 2 (api, ops) │
│ │ │ Last attached: 2 hours ago │
│ │ │ │
│ │ │ Production application servers. │
╰─────────────────────────────────╯ ╰────────────────────────────────────╯
### Nix Flake Run
```bash
nix run github:KofTwentyTwo/limen
```
### Binary Releases
Pre-built binaries are available on the [Releases](https://github.com/KofTwentyTwo/limen/releases) page:
| Platform | Architecture | Binary Name |
| :--- | :--- | :--- |
| **Linux** | x86_64 | `limen-linux-amd64` |
| **Linux** | ARM64 | `limen-linux-arm64` |
| **macOS** | Intel | `limen-macos-amd64` |
| **macOS** | Apple Silicon | `limen-macos-arm64` |
---
## Local Build & Verification
### Build from Source
```bash
# Build binary
go build -ldflags="-s -w" -o bin/limen ./cmd/limen
# Or build via Nix
nix build
```
### Quality Gates & Verification
```bash
# 1. Run Unit & Integration Tests
go test -v ./...
# 2. Run Go Linter & Vet
go vet ./...
↑↓ navigate · enter connect · / search · ? help · esc skip
```
---
## Fleet Configuration
## Install
Declare target hosts in `~/.config/limen/hosts.json`:
Coming soon. The design targets these distribution channels:
- **Nix flake** — `nix run github:KofTwentyTwo/limen`
- **Homebrew tap** — `brew install KofTwentyTwo/tap/limen` (planned)
- **Binary releases** — GitHub Releases page (planned)
## Configuration
A single JSON file at `~/.config/limen/hosts.json` declares your fleet. Example:
```json
{
@@ -90,10 +58,30 @@ Declare target hosts in `~/.config/limen/hosts.json`:
}
```
`localhost` is implicit and managed automatically by `limen`. See [`docs/DESIGN.md`](docs/DESIGN.md) for full schema specifications.
`localhost` is implicit and always available — never declare it in this file.
See [`docs/DESIGN.md` §7.1](docs/DESIGN.md) for the full schema.
---
## Design
The full design and requirements document lives at [`docs/DESIGN.md`](docs/DESIGN.md). That document is the source of truth for what `limen` is and should be; implementation work proceeds from there.
## Status & roadmap
This repository currently contains only the design document and standard repo scaffolding. Implementation will land on the `develop` branch.
## Contributing
See [`CONTRIBUTING.md`](CONTRIBUTING.md).
## License
[GPL-3.0](LICENSE).
## See also
- [tmux](https://github.com/tmux/tmux/wiki) — the multiplexer `limen` orchestrates
- [bubbletea](https://github.com/charmbracelet/bubbletea) — the TUI framework `limen` is built on
- [WezTerm](https://wezfurlong.org/wezterm/) — the terminal `limen` is designed to live inside
+7 -6
View File
@@ -616,7 +616,7 @@ limen/
├── .github/
│ └── workflows/
│ ├── ci.yml # go test + go vet on push/PR
│ └── release.yml # tag -> Go cross-builds -> releases + Homebrew tap
│ └── release.yml # tag → goreleaser → binaries + checksums
├── docs/
│ ├── DESIGN.md # This document (or a slightly edited copy)
│ └── README.md # User-facing docs
@@ -626,6 +626,7 @@ limen/
├── go.mod
├── go.sum
├── main.go
├── .goreleaser.yaml # Cross-compile darwin + linux, amd64 + arm64
├── LICENSE # GPL-3.0 (matches KofTwentyTwo OSS standard)
└── README.md # Repo top-level readme
```
@@ -678,7 +679,7 @@ home.packages = [ inputs.limen.packages.${system}.default ];
Path A — **personal tap (preferred for initial release):**
The user creates `github.com/<owner>/homebrew-tap` with a `Formula/limen.rb` that fetches the pre-built binary published to GitHub Releases. Users install via `brew install <owner>/tap/limen`.
The user creates `github.com/<owner>/homebrew-tap` with a `Formula/limen.rb` that fetches the GitHub release tarball and `go build`s, OR (better) fetches the pre-built binary published by goreleaser. Users install via `brew install <owner>/tap/limen`.
Path B — **homebrew-core (later):**
@@ -699,10 +700,10 @@ git push origin v0.1.0
GitHub Actions runs `release.yml`:
1. Checks out at the tag.
2. Runs `go vet`, `go test -race -count=1 ./...`, and `go build ./...`.
3. Cross-compiles with CGO disabled for `darwin/amd64`, `darwin/arm64`, `linux/amd64`, `linux/arm64`.
4. Uploads release artifacts and `checksums.txt` to the GitHub release page.
5. Updates the `KofTwentyTwo/homebrew-tap` formula with release URLs and SHA256 hashes using `HOMEBREW_TAP_TOKEN`.
2. Runs `goreleaser release --clean`.
3. goreleaser cross-compiles for `darwin/amd64`, `darwin/arm64`, `linux/amd64`, `linux/arm64`.
4. Uploads release artifacts (tarballs + `checksums.txt`) to the GitHub release page.
5. (Optional) Updates the homebrew tap formula via a goreleaser hook.
---
-43
View File
@@ -1,43 +0,0 @@
# Development Tools
This repo is intentionally light on local tooling. The following packages cover
normal development, release verification, and runtime smoke testing.
## Home Manager Package Set
```nix
home.packages = with pkgs; [
go_1_24
gopls
gotools
git
gh
ripgrep
nix
actionlint
shellcheck
ruby
tmux
openssh
];
```
## Why Each Tool Is Here
| Tool | Use |
|------|-----|
| `go_1_24` | Builds and tests the Go module declared in `go.mod`. |
| `gopls` | Go language-server support for editors. |
| `gotools` | Standard Go helper tools used by editors and local workflows. |
| `git` | Source control, branches, tags, and tap repo updates. |
| `gh` | GitHub issue, PR, repository, and release workflows. |
| `ripgrep` | Fast repo search while developing and reviewing. |
| `nix` | Flake checks, package builds, and downstream Nix integration testing. |
| `actionlint` | Static validation for GitHub Actions workflows. |
| `shellcheck` | Shell validation used directly and by `actionlint`. |
| `ruby` | Local syntax checks for the Homebrew formula. |
| `tmux` | Runtime dependency for local session creation and attachment. |
| `openssh` | Runtime dependency for remote host connections. |
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

+1 -3
View File
@@ -10,19 +10,17 @@
flake-utils.lib.eachDefaultSystem (system:
let
pkgs = nixpkgs.legacyPackages.${system};
version = "0.1.0";
in
{
packages.default = pkgs.buildGoModule {
pname = "limen";
inherit version;
version = "0.1.0";
src = ./.;
vendorHash = "sha256-zYQ6hMFBFDPNlViB7EmnxbXozardyYeHiUSiG4uS/9Y=";
ldflags = [
"-s"
"-w"
"-X main.version=${version}"
];
meta = {
-36
View File
@@ -1,36 +0,0 @@
class Limen < Formula
desc "Terminal launcher TUI for tmux and SSH"
homepage "https://github.com/KofTwentyTwo/limen"
version "0.1.0"
license "GPL-3.0-only"
depends_on "tmux"
host_cpu = RbConfig::CONFIG.fetch("host_cpu", "")
macos_arm = OS.mac? && (Hardware::CPU.arm? || host_cpu == "arm64" || RUBY_PLATFORM.start_with?("arm64-darwin"))
linux_arm = OS.linux? && (Hardware::CPU.arm? || host_cpu == "aarch64" || host_cpu == "arm64")
if macos_arm
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-macos-arm64"
sha256 "PLACEHOLDER_MACOS_ARM64_SHA256"
elsif OS.mac?
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-macos-amd64"
sha256 "PLACEHOLDER_MACOS_AMD64_SHA256"
elsif linux_arm
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-linux-arm64"
sha256 "PLACEHOLDER_LINUX_ARM64_SHA256"
else
url "https://github.com/KofTwentyTwo/limen/releases/download/v#{version}/limen-linux-amd64"
sha256 "PLACEHOLDER_LINUX_AMD64_SHA256"
end
def install
binary = Dir["limen-*"].first
chmod 0755, binary
bin.install binary => "limen"
end
test do
assert_match version.to_s, shell_output("#{bin}/limen --version")
end
end