chore(phase-0): struktur repo + hello TUI Bubble Tea v2 (T-0.1, T-0.2)
This commit is contained in:
1 parent
ca45f67b0c
commit
db75653e3d
8 files changed
+314
No files matched your search
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 oxpoe
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,21 @@
|
||||
BINARY := tia
|
||||
|
||||
.PHONY: build run test vet fmt clean
|
||||
|
||||
build:
|
||||
go build -o $(BINARY) ./cmd/tia
|
||||
|
||||
run: build
|
||||
./$(BINARY)
|
||||
|
||||
test:
|
||||
go test ./...
|
||||
|
||||
vet:
|
||||
go vet ./...
|
||||
|
||||
fmt:
|
||||
gofmt -w .
|
||||
|
||||
clean:
|
||||
rm -f $(BINARY)
|
||||
@@ -0,0 +1,41 @@
|
||||
# tia — Terminal Is A My Way
|
||||
|
||||
> "Satu binary untuk semua kebutuhan Docker kamu." · Tagline: **Just Work**
|
||||
|
||||
TUI modern (Go + Charm v2) + CLI pendamping untuk manajemen Docker:
|
||||
stack & container, konverter `docker run` → compose, multi-host, mouse-first,
|
||||
audio feedback, animasi ASCII.
|
||||
|
||||
Status: **Phase 0 — fondasi** (lihat roadmap di `docs/PRD.md` §8).
|
||||
|
||||
## Build & jalankan
|
||||
|
||||
```bash
|
||||
make build # menghasilkan ./tia
|
||||
./tia # buka TUI
|
||||
./tia version # versi & build info
|
||||
make test # unit test
|
||||
```
|
||||
|
||||
## Struktur repo
|
||||
|
||||
```
|
||||
cmd/tia/ entry point — router TUI default vs CLI companion
|
||||
internal/app/ root Bubble Tea model
|
||||
internal/ (menyusul) docker, stack, config, ui, i18n, host, cli, audio
|
||||
docs/PRD.md product requirements
|
||||
docs/rea/ laporan analisis proyek referensi (playbook + modul)
|
||||
reference/ repo referensi untuk REA — git-ignored, jangan di-commit
|
||||
```
|
||||
|
||||
## Prinsip
|
||||
|
||||
- **Satu binary** (`CGO_ENABLED=0`), tanpa dependency runtime selain Docker.
|
||||
- **Core tanpa UI**: logika dibagi TUI & CLI → hasil identik (F-028).
|
||||
- **Mouse & keyboard setara** sejak hari pertama (F-022).
|
||||
- **i18n dari awal** — Indonesia default, English fallback (F-023).
|
||||
|
||||
## Lisensi
|
||||
|
||||
MIT — lihat `LICENSE`. Atribusi pihak ketiga: `THIRD_PARTY_NOTICES.md`,
|
||||
register aset: `docs/ASSETS.md`.
|
||||
@@ -0,0 +1,28 @@
|
||||
# Third-Party Notices
|
||||
|
||||
## Proyek referensi (analisis perilaku — tanpa copy kode verbatim)
|
||||
|
||||
Sesuai aturan REA (`docs/rea/README.md`): kami mempelajari **perilaku** proyek berikut lalu
|
||||
mengimplementasikan ulang dalam Go. Tidak ada kode yang disalin. Tabel pemetaan flag di
|
||||
`docs/rea/composerize.md` diadaptasi dari perilaku proyek aslinya (lisensi MIT).
|
||||
|
||||
| Proyek | URL | Commit dianalisis | Lisensi |
|
||||
|---|---|---|---|
|
||||
| composerize | https://github.com/composerize/composerize | `3e39eb7` | MIT |
|
||||
| Dockge | https://github.com/louislam/dockge | `f809ae1` | MIT |
|
||||
| lazydocker | https://github.com/jesseduffield/lazydocker | `7e7aadc` | MIT |
|
||||
|
||||
Repo referensi hanya ada di `reference/` (git-ignored).
|
||||
|
||||
## Library Go yang dipakai
|
||||
|
||||
| Library | Versi | Lisensi |
|
||||
|---|---|---|
|
||||
| bubbletea | v2.1.0 | MIT |
|
||||
| lipgloss | v2.0.6 | MIT |
|
||||
|
||||
*(Daftar bertambah setiap dependency baru diadopsi; wajib selaras dengan kebijakan aset PRD Appendix C.)*
|
||||
|
||||
## Aset
|
||||
|
||||
Lihat `docs/ASSETS.md`.
|
||||
@@ -0,0 +1,71 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime/debug"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
|
||||
"github.com/oxpoe/tia/internal/app"
|
||||
)
|
||||
|
||||
const usage = `tia — Terminal Is A My Way (Just Work)
|
||||
|
||||
Pemakaian:
|
||||
tia Buka TUI interaktif
|
||||
tia version Tampilkan versi & build info
|
||||
|
||||
Subcommand CLI pendamping (convert, ps, up, down, update, doctor)
|
||||
menyusul di Phase 1–3 — lihat docs/PRD.md §F-028.
|
||||
`
|
||||
|
||||
func main() {
|
||||
if len(os.Args) > 1 {
|
||||
switch os.Args[1] {
|
||||
case "version", "--version":
|
||||
fmt.Println(versionString())
|
||||
return
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "perintah tidak dikenal: %q\n\n%s", os.Args[1], usage)
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := tea.NewProgram(app.New()).Run(); err != nil {
|
||||
fmt.Fprintln(os.Stderr, "tia:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
// versionString memakai build info VCS — tanpa ldflags.
|
||||
func versionString() string {
|
||||
info, ok := debug.ReadBuildInfo()
|
||||
if !ok {
|
||||
return "tia dev"
|
||||
}
|
||||
ver := info.Main.Version
|
||||
if ver == "" || ver == "(devel)" {
|
||||
ver = "dev"
|
||||
}
|
||||
rev, dirty := "", false
|
||||
for _, s := range info.Settings {
|
||||
switch s.Key {
|
||||
case "vcs.revision":
|
||||
if len(s.Value) >= 7 {
|
||||
rev = s.Value[:7]
|
||||
}
|
||||
case "vcs.modified":
|
||||
dirty = s.Value == "true"
|
||||
}
|
||||
}
|
||||
out := "tia " + ver
|
||||
if rev != "" {
|
||||
out += " (" + rev
|
||||
if dirty {
|
||||
out += "-dirty"
|
||||
}
|
||||
out += ")"
|
||||
}
|
||||
return out
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
module github.com/oxpoe/tia
|
||||
|
||||
go 1.27.1
|
||||
|
||||
require (
|
||||
charm.land/bubbletea/v2 v2.1.0
|
||||
charm.land/lipgloss/v2 v2.0.6
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/colorprofile v0.4.3 // indirect
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 // indirect
|
||||
github.com/charmbracelet/x/ansi v0.11.9 // indirect
|
||||
github.com/charmbracelet/x/term v0.2.2 // indirect
|
||||
github.com/charmbracelet/x/termios v0.1.1 // indirect
|
||||
github.com/charmbracelet/x/windows v0.2.2 // indirect
|
||||
github.com/clipperhouse/displaywidth v0.11.0 // indirect
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.4.1 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.30 // indirect
|
||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/xo/terminfo v1.0.0 // indirect
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/sys v0.47.0 // indirect
|
||||
)
|
||||
@@ -0,0 +1,40 @@
|
||||
charm.land/bubbletea/v2 v2.1.0 h1:A9fj9h5LFrvQhQHFNggp8K8rvY6CUfgn9dMbb6p2HIw=
|
||||
charm.land/bubbletea/v2 v2.1.0/go.mod h1:xWHeyxIX9/wiFU2kgPW04ojOZyTzrWf+8lGjUCaftiU=
|
||||
charm.land/lipgloss/v2 v2.0.6 h1:EaGKeuA8FvF+v2BT5VmZd2LoYLaMZJXA5n34th8nCIQ=
|
||||
charm.land/lipgloss/v2 v2.0.6/go.mod h1:ipDDJNSGa1hlwDtSfW1s2/xR8Vdhbut4PXh2zEKZd0Q=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1 h1:OEIrQ8maEeDBXQDoGCbbTTXYJMYRCRO1fnodZ12Gv5o=
|
||||
github.com/aymanbagabas/go-udiff v0.4.1/go.mod h1:0L9PGwj20lrtmEMeyw4WKJ/TMyDtvAoK9bf2u/mNo3w=
|
||||
github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q=
|
||||
github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4 h1:hISLzUbwcvJbGoNR6A3L/TpguRRnACBmZOee8LcHLJw=
|
||||
github.com/charmbracelet/ultraviolet v0.0.0-20261008173134-6b8d4baf91b4/go.mod h1:aJDVk+WOVM1K04rdc/71PMPoUGfjxjhvXfP3Pjz1Zio=
|
||||
github.com/charmbracelet/x/ansi v0.11.9 h1:kIrqLBxtTVbLyyHO2Evoi93EjQo1mylWmpNEsfPxXAM=
|
||||
github.com/charmbracelet/x/ansi v0.11.9/go.mod h1:DG/hpOnaI2QJ/KMMqLL2h0KVhluuJLazf8973P+Vy5w=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f h1:pk6gmGpCE7F3FcjaOEKYriCvpmIN4+6OS/RD0vm4uIA=
|
||||
github.com/charmbracelet/x/exp/golden v0.0.0-20250806222409-83e3a29d542f/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
|
||||
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
|
||||
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
|
||||
github.com/charmbracelet/x/termios v0.1.1 h1:o3Q2bT8eqzGnGPOYheoYS8eEleT5ZVNYNy8JawjaNZY=
|
||||
github.com/charmbracelet/x/termios v0.1.1/go.mod h1:rB7fnv1TgOPOyyKRJ9o+AsTU/vK5WHJ2ivHeut/Pcwo=
|
||||
github.com/charmbracelet/x/windows v0.2.2 h1:IofanmuvaxnKHuV04sC0eBy/smG6kIKrWG2/jYn2GuM=
|
||||
github.com/charmbracelet/x/windows v0.2.2/go.mod h1:/8XtdKZzedat74NQFn0NGlGL4soHB0YQZrETF96h75k=
|
||||
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
|
||||
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
|
||||
github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.1 h1:1EO+WB73+EH8EVbzlrG3KLAfEypQWVHIBqlTf+2hNss=
|
||||
github.com/lucasb-eyer/go-colorful v1.4.1/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.30 h1:+KUuiDA4fF0R1p5FeueHefjDm+GIM+kWfFnDjybOPgk=
|
||||
github.com/mattn/go-runewidth v0.0.30/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||
github.com/xo/terminfo v1.0.0 h1:2ZpYzqWzyyytjk3TP6aJVDhkMAkc99/1xKQdA3TDTBY=
|
||||
github.com/xo/terminfo v1.0.0/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
|
||||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
@@ -0,0 +1,66 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
tea "charm.land/bubbletea/v2"
|
||||
"charm.land/lipgloss/v2"
|
||||
)
|
||||
|
||||
// Model root sementara (T-0.2, Phase 0).
|
||||
// Tujuannya membuktikan Bubble Tea v2 (View struct, mouse, resize) sebelum
|
||||
// layout penuh Phase 1 menggantikannya.
|
||||
type Model struct {
|
||||
width, height int
|
||||
clicks int
|
||||
lastClick string
|
||||
}
|
||||
|
||||
func New() Model {
|
||||
return Model{lastClick: "belum ada klik"}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd { return nil }
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
switch msg := msg.(type) {
|
||||
case tea.WindowSizeMsg:
|
||||
m.width, m.height = msg.Width, msg.Height
|
||||
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c", "esc":
|
||||
return m, tea.Quit
|
||||
}
|
||||
|
||||
case tea.MouseClickMsg:
|
||||
if msg.Button == tea.MouseLeft {
|
||||
m.clicks++
|
||||
m.lastClick = fmt.Sprintf("klik #%d di (%d, %d)", m.clicks, msg.X, msg.Y)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (m Model) View() tea.View {
|
||||
title := lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#cba6f7")).
|
||||
Render("tia — Terminal Is A My Way")
|
||||
tagline := lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#a6adc8")).
|
||||
Render("Just Work · Phase 0 · skeleton TUI")
|
||||
box := lipgloss.NewStyle().
|
||||
Border(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#6c7086")).
|
||||
Padding(0, 2).
|
||||
Render(m.lastClick + "\n\nklik mouse di mana saja · tekan q untuk keluar")
|
||||
|
||||
content := fmt.Sprintf("%s\n%s\n\n%s\n\nukuran jendela: %dx%d",
|
||||
title, tagline, box, m.width, m.height)
|
||||
|
||||
v := tea.NewView(content)
|
||||
v.AltScreen = true
|
||||
v.MouseMode = tea.MouseModeCellMotion
|
||||
return v
|
||||
}
|
||||
Reference in new issue
Block a user