auto(0129): agents_dashboard — secret_store_cpp_infra + CMakeLists register #4
@@ -0,0 +1,110 @@
|
||||
---
|
||||
name: init_cli_app
|
||||
kind: pipeline
|
||||
lang: bash
|
||||
domain: pipelines
|
||||
version: "1.0.0"
|
||||
purity: impure
|
||||
signature: "init_cli_app(nombre: string, [--with-tui]) -> void"
|
||||
description: "Scaffold de Go CLI app con subcomandos (version/status/help) routed via os.Args. Con --with-tui genera model.go con un modelo Bubbletea fullscreen (lista filtrable + spinner + dark theme) y main.go arranca la TUI en modo fullscreen. Sin dependencias de cobra/urfave — consistente con las apps del registry."
|
||||
tags: [init, scaffold, cli, tui, pipeline, bash, launcher]
|
||||
uses_functions:
|
||||
- assert_command_exists_bash_shell
|
||||
- new_base_model_go_tui
|
||||
- dark_styles_go_tui
|
||||
- run_fullscreen_go_tui
|
||||
- new_spinner_go_tui
|
||||
- new_filtered_list_go_tui
|
||||
uses_types: []
|
||||
returns: []
|
||||
returns_optional: false
|
||||
error_type: "error_go_core"
|
||||
imports: []
|
||||
params:
|
||||
- name: nombre
|
||||
desc: "nombre de la CLI app (apps/{nombre}/); se usa como binario y como modulo Go"
|
||||
- name: "--with-tui"
|
||||
desc: "anade model.go con modelo Bubbletea (lista + spinner + dark theme) y arranca TUI fullscreen al invocar sin args"
|
||||
output: "CLI app en apps/{nombre}/ con main.go (subcommand routing), cmd_version.go, cmd_status.go, Makefile con targets build/run/install/test/vet/clean. Con --with-tui anade model.go y el default sin args arranca TUI."
|
||||
tested: false
|
||||
tests: []
|
||||
test_file_path: ""
|
||||
example: "fn run init_cli_app my_cli --with-tui"
|
||||
file_path: "bash/functions/pipelines/init_cli_app.sh"
|
||||
---
|
||||
|
||||
## Sinopsis
|
||||
|
||||
```bash
|
||||
fn run init_cli_app <nombre> [--with-tui]
|
||||
```
|
||||
|
||||
## Ejemplo rapido
|
||||
|
||||
```bash
|
||||
# CLI clasica
|
||||
fn run init_cli_app mytool
|
||||
cd apps/mytool
|
||||
make build
|
||||
./mytool version
|
||||
./mytool status
|
||||
|
||||
# CLI + TUI fullscreen
|
||||
fn run init_cli_app deploy_helper --with-tui
|
||||
cd apps/deploy_helper
|
||||
make build
|
||||
./deploy_helper # arranca TUI
|
||||
./deploy_helper version # subcomando
|
||||
```
|
||||
|
||||
## Archivos generados
|
||||
|
||||
| Archivo | Descripcion |
|
||||
|---------|-------------|
|
||||
| `main.go` | Entry con `switch os.Args[1]`; subcomandos: version, status, help |
|
||||
| `cmd_version.go` | Imprime nombre + version |
|
||||
| `cmd_status.go` | Imprime app/version/go/os/arch |
|
||||
| `go.mod` | Modulo Go con replace `fn-registry`; con `--with-tui` agrega bubbletea/bubbles/lipgloss |
|
||||
| `Makefile` | Targets build, run (ARGS=...), install (~/.local/bin/), test, vet, clean |
|
||||
| `.gitignore` | Binario + IDE files |
|
||||
| `app.md` | Tag `cli` (o `cli,tui,bubbletea` con `--with-tui`) |
|
||||
|
||||
Con `--with-tui` anade:
|
||||
- `model.go` — `Model` con spinner (spinner.Dot), lista Bubbletea con 4 items de ejemplo, dark theme (lipgloss con colores 7D56F4 / 06B6D4), keys enter/q/ctrl+c
|
||||
- `main.go` arranca la TUI con `tea.NewProgram(model, tea.WithAltScreen())` si no hay args
|
||||
|
||||
## Flags
|
||||
|
||||
| Flag | Efecto |
|
||||
|------|--------|
|
||||
| `--with-tui` | Anade model.go con Bubbletea y TUI fullscreen como default |
|
||||
|
||||
## Post-setup
|
||||
|
||||
```bash
|
||||
cd apps/{nombre}
|
||||
make build # construye ./{nombre}
|
||||
./{nombre} version
|
||||
./{nombre} status
|
||||
make install # copia a ~/.local/bin/{nombre}
|
||||
make run ARGS="status" # build + run con argumentos
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
Pipeline impuro: genera archivos, ejecuta `go mod tidy` y `go vet` al final.
|
||||
|
||||
Los ejemplos del modelo TUI (items "Deploy", "Status", "Logs", "Exit") son
|
||||
placeholders — reemplazar con la logica real de la app. El modelo usa los
|
||||
componentes estandar de bubbles (`list`, `spinner`) y lipgloss para estilos.
|
||||
|
||||
Las funciones del registry `new_base_model_go_tui`, `dark_styles_go_tui`,
|
||||
`run_fullscreen_go_tui`, `new_spinner_go_tui`, `new_filtered_list_go_tui`
|
||||
son referenciadas en el frontmatter como deps conceptuales aunque el
|
||||
scaffold inline el codigo Bubbletea directamente (las funciones del registry
|
||||
son stubs que delegan a devfactory/tui).
|
||||
|
||||
Abort si `apps/{nombre}/` ya existe.
|
||||
|
||||
El tag `launcher` permite que aparezca en el Pipeline Launcher TUI (aunque
|
||||
una CLI con TUI interactiva normalmente no se lanza como subprocess).
|
||||
Executable
+460
@@ -0,0 +1,460 @@
|
||||
#!/usr/bin/env bash
|
||||
# init_cli_app
|
||||
# ------------
|
||||
# Scaffold de Go CLI app con subcomandos, opcionalmente con TUI Bubbletea.
|
||||
#
|
||||
# Genera main.go con routing de subcomandos (os.Args + switch), cmd_version.go,
|
||||
# cmd_status.go, Makefile, .gitignore, go.mod y app.md.
|
||||
#
|
||||
# Con --with-tui genera ademas model.go con un modelo Bubbletea base y main.go
|
||||
# arranca la TUI con tea.NewProgram().Run() en modo fullscreen.
|
||||
#
|
||||
# USO:
|
||||
# ./init_cli_app.sh <nombre> [--with-tui]
|
||||
#
|
||||
# EJEMPLOS:
|
||||
# ./init_cli_app.sh my_cli
|
||||
# ./init_cli_app.sh deploy_helper --with-tui
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REGISTRY_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
|
||||
source "$REGISTRY_ROOT/bash/functions/shell/assert_command_exists.sh"
|
||||
|
||||
NOMBRE=""
|
||||
WITH_TUI="false"
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--with-tui) WITH_TUI="true"; shift ;;
|
||||
-h|--help) grep "^#" "$0" | sed 's/^# \?//' ; exit 0 ;;
|
||||
-*) echo "Flag desconocido: $1" >&2 ; exit 1 ;;
|
||||
*)
|
||||
if [ -z "$NOMBRE" ]; then NOMBRE="$1"
|
||||
else echo "Argumento extra ignorado: $1" >&2
|
||||
fi
|
||||
shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [ -z "$NOMBRE" ]; then
|
||||
echo "Uso: $0 <nombre> [--with-tui]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
APP_DIR="${REGISTRY_ROOT}/apps/${NOMBRE}"
|
||||
if [ -d "$APP_DIR" ]; then
|
||||
echo "ERROR: ${APP_DIR} ya existe. Abortando." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " INIT CLI APP: ${NOMBRE}"
|
||||
echo " Directorio: ${APP_DIR}"
|
||||
echo " TUI: ${WITH_TUI}"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# ── 1. Verificar Go ──────────────────────────────────────────
|
||||
|
||||
echo "[1/5] Verificando herramientas..."
|
||||
assert_command_exists go
|
||||
echo " Go: $(go version)"
|
||||
|
||||
# ── 2. Crear estructura ──────────────────────────────────────
|
||||
|
||||
echo "[2/5] Creando estructura..."
|
||||
mkdir -p "$APP_DIR"
|
||||
|
||||
# go.mod
|
||||
if [ "$WITH_TUI" = "true" ]; then
|
||||
cat > "$APP_DIR/go.mod" <<EOF
|
||||
module ${NOMBRE}
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbles v1.0.0
|
||||
github.com/charmbracelet/bubbletea v1.3.10
|
||||
github.com/charmbracelet/lipgloss v1.1.0
|
||||
)
|
||||
|
||||
require (
|
||||
fn-registry v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
replace fn-registry => ${REGISTRY_ROOT}
|
||||
EOF
|
||||
else
|
||||
cat > "$APP_DIR/go.mod" <<EOF
|
||||
module ${NOMBRE}
|
||||
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
fn-registry v0.0.0-00010101000000-000000000000
|
||||
)
|
||||
|
||||
replace fn-registry => ${REGISTRY_ROOT}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ── 3. Archivos Go ───────────────────────────────────────────
|
||||
|
||||
echo "[3/5] Escribiendo archivos Go..."
|
||||
|
||||
# cmd_version.go — siempre existe
|
||||
cat > "$APP_DIR/cmd_version.go" <<EOF
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
var version = "0.1.0"
|
||||
|
||||
func cmdVersion() {
|
||||
fmt.Printf("${NOMBRE} %s\n", version)
|
||||
}
|
||||
EOF
|
||||
|
||||
# cmd_status.go — siempre existe
|
||||
cat > "$APP_DIR/cmd_status.go" <<EOF
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func cmdStatus() {
|
||||
fmt.Printf("app: ${NOMBRE}\n")
|
||||
fmt.Printf("version: %s\n", version)
|
||||
fmt.Printf("go: %s\n", runtime.Version())
|
||||
fmt.Printf("os/arch: %s/%s\n", runtime.GOOS, runtime.GOARCH)
|
||||
}
|
||||
EOF
|
||||
|
||||
if [ "$WITH_TUI" = "true" ]; then
|
||||
# model.go — Bubbletea BaseModel con spinner y lista
|
||||
cat > "$APP_DIR/model.go" <<EOF
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/charmbracelet/bubbles/list"
|
||||
"github.com/charmbracelet/bubbles/spinner"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// --- estilos (dark theme) ---
|
||||
|
||||
var (
|
||||
titleStyle = lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7D56F4"))
|
||||
helpStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#626262"))
|
||||
selectedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#06B6D4"))
|
||||
)
|
||||
|
||||
// --- item de la lista ---
|
||||
|
||||
type item struct {
|
||||
title, desc string
|
||||
}
|
||||
|
||||
func (i item) Title() string { return i.title }
|
||||
func (i item) Description() string { return i.desc }
|
||||
func (i item) FilterValue() string { return i.title }
|
||||
|
||||
// --- modelo raiz ---
|
||||
|
||||
type Model struct {
|
||||
spinner spinner.Model
|
||||
list list.Model
|
||||
status string
|
||||
quit bool
|
||||
}
|
||||
|
||||
func NewModel() Model {
|
||||
items := []list.Item{
|
||||
item{title: "Deploy", desc: "Subir codigo al VPS"},
|
||||
item{title: "Status", desc: "Ver estado de servicios"},
|
||||
item{title: "Logs", desc: "Tail de logs en tiempo real"},
|
||||
item{title: "Exit", desc: "Salir"},
|
||||
}
|
||||
|
||||
l := list.New(items, list.NewDefaultDelegate(), 0, 0)
|
||||
l.Title = "${NOMBRE} — elige una accion"
|
||||
l.Styles.Title = titleStyle
|
||||
|
||||
s := spinner.New()
|
||||
s.Spinner = spinner.Dot
|
||||
s.Style = selectedStyle
|
||||
|
||||
return Model{
|
||||
spinner: s,
|
||||
list: l,
|
||||
status: "listo",
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) Init() tea.Cmd {
|
||||
return tea.Batch(m.spinner.Tick)
|
||||
}
|
||||
|
||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmds []tea.Cmd
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c":
|
||||
m.quit = true
|
||||
return m, tea.Quit
|
||||
case "enter":
|
||||
if it, ok := m.list.SelectedItem().(item); ok {
|
||||
if it.title == "Exit" {
|
||||
m.quit = true
|
||||
return m, tea.Quit
|
||||
}
|
||||
m.status = fmt.Sprintf("seleccionado: %s", it.title)
|
||||
}
|
||||
}
|
||||
case tea.WindowSizeMsg:
|
||||
m.list.SetSize(msg.Width, msg.Height-4)
|
||||
case spinner.TickMsg:
|
||||
var cmd tea.Cmd
|
||||
m.spinner, cmd = m.spinner.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
}
|
||||
|
||||
var cmd tea.Cmd
|
||||
m.list, cmd = m.list.Update(msg)
|
||||
cmds = append(cmds, cmd)
|
||||
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
func (m Model) View() string {
|
||||
if m.quit {
|
||||
return "adios!\n"
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"%s\n\n%s %s\n%s",
|
||||
m.list.View(),
|
||||
m.spinner.View(),
|
||||
m.status,
|
||||
helpStyle.Render("enter: seleccionar · q: salir"),
|
||||
)
|
||||
}
|
||||
EOF
|
||||
|
||||
# main.go con TUI
|
||||
cat > "$APP_DIR/main.go" <<EOF
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
runTUI()
|
||||
return
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "tui":
|
||||
runTUI()
|
||||
case "version":
|
||||
cmdVersion()
|
||||
case "status":
|
||||
cmdStatus()
|
||||
case "help", "-h", "--help":
|
||||
printUsage()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "comando desconocido: %s\n", os.Args[1])
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func runTUI() {
|
||||
p := tea.NewProgram(NewModel(), tea.WithAltScreen())
|
||||
if _, err := p.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "tui error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println(\`${NOMBRE} — CLI tool
|
||||
|
||||
Uso:
|
||||
${NOMBRE} [comando]
|
||||
|
||||
Sin comando arranca la TUI fullscreen.
|
||||
|
||||
Comandos:
|
||||
tui Arranca la TUI fullscreen (default)
|
||||
version Imprime la version
|
||||
status Muestra info del sistema
|
||||
help Muestra esta ayuda\`)
|
||||
}
|
||||
EOF
|
||||
else
|
||||
# main.go sin TUI — subcommand routing clasico
|
||||
cat > "$APP_DIR/main.go" <<EOF
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "version":
|
||||
cmdVersion()
|
||||
case "status":
|
||||
cmdStatus()
|
||||
case "help", "-h", "--help":
|
||||
printUsage()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "comando desconocido: %s\n", os.Args[1])
|
||||
printUsage()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
fmt.Println(\`${NOMBRE} — CLI tool
|
||||
|
||||
Uso:
|
||||
${NOMBRE} <comando>
|
||||
|
||||
Comandos:
|
||||
version Imprime la version
|
||||
status Muestra info del sistema
|
||||
help Muestra esta ayuda\`)
|
||||
}
|
||||
EOF
|
||||
fi
|
||||
|
||||
# ── 4. Makefile, .gitignore, app.md ─────────────────────────
|
||||
|
||||
echo "[4/5] Escribiendo Makefile, .gitignore, app.md..."
|
||||
|
||||
cat > "$APP_DIR/Makefile" <<EOF
|
||||
.PHONY: build run install test vet clean
|
||||
|
||||
BIN=${NOMBRE}
|
||||
|
||||
build:
|
||||
CGO_ENABLED=1 go build -tags fts5 -o \$(BIN) .
|
||||
|
||||
run: build
|
||||
./\$(BIN) \$(ARGS)
|
||||
|
||||
install: build
|
||||
install -m755 \$(BIN) \$(HOME)/.local/bin/\$(BIN)
|
||||
@echo "instalado en \$(HOME)/.local/bin/\$(BIN)"
|
||||
|
||||
test:
|
||||
CGO_ENABLED=1 go test -tags fts5 -v ./...
|
||||
|
||||
vet:
|
||||
CGO_ENABLED=1 go vet -tags fts5 ./...
|
||||
|
||||
clean:
|
||||
rm -f \$(BIN)
|
||||
EOF
|
||||
|
||||
cat > "$APP_DIR/.gitignore" <<EOF
|
||||
# Binario
|
||||
${NOMBRE}
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
EOF
|
||||
|
||||
if [ "$WITH_TUI" = "true" ]; then
|
||||
FRAMEWORK="bubbletea"
|
||||
USES_FUNCTIONS=' - new_base_model_go_tui
|
||||
- dark_styles_go_tui
|
||||
- run_fullscreen_go_tui
|
||||
- new_spinner_go_tui
|
||||
- new_filtered_list_go_tui'
|
||||
TAGS='[cli, tui, bubbletea]'
|
||||
else
|
||||
FRAMEWORK=""
|
||||
USES_FUNCTIONS=' []'
|
||||
TAGS='[cli]'
|
||||
fi
|
||||
|
||||
cat > "$APP_DIR/app.md" <<EOF
|
||||
---
|
||||
name: ${NOMBRE}
|
||||
lang: go
|
||||
domain: tools
|
||||
description: "CLI app generada por init_cli_app."
|
||||
tags: ${TAGS}
|
||||
uses_functions:
|
||||
${USES_FUNCTIONS}
|
||||
uses_types: []
|
||||
framework: "${FRAMEWORK}"
|
||||
entry_point: "main.go"
|
||||
dir_path: "apps/${NOMBRE}"
|
||||
---
|
||||
|
||||
## Notas
|
||||
|
||||
CLI con routing de subcomandos (\`os.Args\` + switch). Sin cobra/urfave —
|
||||
consistente con las apps del registry.
|
||||
|
||||
Ejecutar: \`make run ARGS="version"\` o \`./\${NOMBRE} status\`.
|
||||
EOF
|
||||
|
||||
# ── 5. go mod tidy + go vet ─────────────────────────────────
|
||||
|
||||
echo "[5/5] Verificacion..."
|
||||
(
|
||||
cd "$APP_DIR"
|
||||
if CGO_ENABLED=1 go mod tidy 2>&1 | tail -5; then
|
||||
:
|
||||
fi
|
||||
if CGO_ENABLED=1 go vet -tags fts5 ./... 2>&1; then
|
||||
echo " go vet OK"
|
||||
else
|
||||
echo " WARN: go vet fallo" >&2
|
||||
fi
|
||||
)
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo " CLI APP '${NOMBRE}' LISTA"
|
||||
echo "════════════════════════════════════════════════════════════"
|
||||
echo ""
|
||||
echo " Pasos siguientes:"
|
||||
echo " cd apps/${NOMBRE}"
|
||||
echo " make build"
|
||||
if [ "$WITH_TUI" = "true" ]; then
|
||||
echo " ./${NOMBRE} # arranca la TUI fullscreen"
|
||||
echo " ./${NOMBRE} version # comando CLI"
|
||||
else
|
||||
echo " ./${NOMBRE} version"
|
||||
echo " ./${NOMBRE} status"
|
||||
fi
|
||||
echo ""
|
||||
Reference in New Issue
Block a user