feat(cli): add tinyforge terminal client
New zero-dependency Go CLI (cmd/cli) that drives the existing HTTP API: login/logout, apps list, deploy (synchronous, --timeout), logs (one-shot + -f SSE follow), and status. Caches a 24h JWT in ~/.tinyforge/config.json (0600, Chmod-enforced on overwrite); Bearer-header auth keeps the token out of server/proxy logs; no-echo password prompt (kernel32 on Windows, stty elsewhere). Server/token resolved via flags, TINYFORGE_URL/TINYFORGE_TOKEN env, or config. README CLI section + root-anchored .gitignore entries for the build output.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
// Command tinyforge is a terminal client for a Tinyforge server.
|
||||
//
|
||||
// It drives the existing HTTP API: log in to obtain a 24h JWT, then list
|
||||
// apps, trigger deploys, stream logs, and check status. The token is cached
|
||||
// in ~/.tinyforge/config.json (mode 0600) so subsequent commands reuse it.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// tinyforge login [--user U] [--password P]
|
||||
// tinyforge apps [list]
|
||||
// tinyforge deploy <app> [--ref TAG] [--note TEXT]
|
||||
// tinyforge logs <app> [-f] [--tail N] [--container CID]
|
||||
// tinyforge status [<app>]
|
||||
// tinyforge logout
|
||||
// tinyforge version
|
||||
//
|
||||
// The target server is resolved from --base-url, then $TINYFORGE_URL, then the
|
||||
// saved config, then http://localhost:8080.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
// version is the CLI build version. Overridable at build time via
|
||||
// -ldflags "-X main.version=...".
|
||||
var version = "dev"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
usage(os.Stderr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
cmd, args := os.Args[1], os.Args[2:]
|
||||
|
||||
var err error
|
||||
switch cmd {
|
||||
case "login":
|
||||
err = runLogin(args)
|
||||
case "logout":
|
||||
err = runLogout(args)
|
||||
case "apps":
|
||||
err = runApps(args)
|
||||
case "deploy":
|
||||
err = runDeploy(args)
|
||||
case "logs":
|
||||
err = runLogs(args)
|
||||
case "status":
|
||||
err = runStatus(args)
|
||||
case "version", "--version", "-v":
|
||||
fmt.Printf("tinyforge %s\n", version)
|
||||
case "help", "-h", "--help":
|
||||
usage(os.Stdout)
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "tinyforge: unknown command %q\n\n", cmd)
|
||||
usage(os.Stderr)
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// Authenticated commands that hit a 401 get a re-login hint; the login
|
||||
// command itself surfaces the server message ("invalid credentials").
|
||||
if cmd != "login" && isAuthError(err) {
|
||||
err = fmt.Errorf("%w — run 'tinyforge login'", err)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "tinyforge: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func usage(w *os.File) {
|
||||
fmt.Fprint(w, `tinyforge — terminal client for a Tinyforge server
|
||||
|
||||
Usage:
|
||||
tinyforge <command> [flags]
|
||||
|
||||
Commands:
|
||||
login Authenticate and cache a token
|
||||
logout Revoke the cached token and clear it
|
||||
apps [list] List your apps (workloads with a source)
|
||||
deploy <app> Trigger a deploy (waits for completion)
|
||||
logs <app> Print container logs (use -f to follow)
|
||||
status [<app>] Show server health, or one app's containers
|
||||
version Print the CLI version
|
||||
|
||||
Global flags (accepted by any command):
|
||||
--base-url URL Server URL (default $TINYFORGE_URL or http://localhost:8080)
|
||||
--token TOKEN Auth token (default $TINYFORGE_TOKEN or cached config)
|
||||
--config PATH Config file (default $TINYFORGE_CONFIG or ~/.tinyforge/config.json)
|
||||
|
||||
Run "tinyforge <command> -h" for command-specific flags.
|
||||
`)
|
||||
}
|
||||
Reference in New Issue
Block a user