feat: implement core CLI packages and Phase 3 commands

- config: resolve binary dir, load/save .hf-config.json
- mode: detect padded-cell vs manual mode via pass_mgr
- client: HTTP client wrapper with auth header support
- passmgr: pass_mgr integration (get-secret, set, generate)
- output: human-readable + JSON output formatting with tables
- help: help and help-brief renderer for groups/commands
- commands: version, health, config (--url, --acc-mgr-token, show)
- auth: token resolution helper (padded-cell auto / manual explicit)
- main: command dispatcher with --json global flag support
- README: updated with current package layout and status
This commit is contained in:
zhi
2026-03-21 13:50:29 +00:00
parent cb0b7669b3
commit 7d3cff7d95
24 changed files with 810 additions and 52 deletions

36
internal/commands/auth.go Normal file
View File

@@ -0,0 +1,36 @@
package commands
import (
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/mode"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/passmgr"
)
// ResolveToken resolves the auth token based on runtime mode.
// In padded-cell mode, tokenFlag must be empty (enforced).
// In manual mode, tokenFlag is required.
func ResolveToken(tokenFlag string) string {
if mode.IsPaddedCell() {
if tokenFlag != "" {
output.Error("padded-cell installed, --token flag disabled, use command directly")
}
tok, err := passmgr.GetToken()
if err != nil {
output.Errorf("cannot resolve token: %v", err)
}
return tok
}
// manual mode
if tokenFlag == "" {
output.Error("--token <token> required or execute this with pcexec")
}
return tokenFlag
}
// RejectTokenInPaddedCell checks if --token was passed in padded-cell mode
// and terminates with the standard error message.
func RejectTokenInPaddedCell(tokenFlag string) {
if mode.IsPaddedCell() && tokenFlag != "" {
output.Error("padded-cell installed, --token flag disabled, use command directly")
}
}

View File

@@ -0,0 +1,53 @@
package commands
import (
"fmt"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/config"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/mode"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/passmgr"
)
// RunConfigURL sets the base URL in the config file.
func RunConfigURL(url string) {
if url == "" {
output.Error("usage: hf config --url <hf-url>")
}
if err := config.UpdateURL(url); err != nil {
output.Errorf("failed to update config: %v", err)
}
fmt.Printf("base-url set to %s\n", url)
}
// RunConfigAccMgrToken stores the account-manager token via pass_mgr.
func RunConfigAccMgrToken(token string) {
if token == "" {
output.Error("usage: hf config --acc-mgr-token <token>")
}
if !mode.IsPaddedCell() {
output.Error("--acc-mgr-token can only be set with padded-cell plugin")
}
if err := passmgr.SetAccountManagerToken(token); err != nil {
output.Errorf("failed to store acc-mgr-token: %v", err)
}
fmt.Println("account-manager token stored successfully")
}
// RunConfigShow displays the current config.
func RunConfigShow() {
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
if output.JSONMode {
output.PrintJSON(cfg)
} else {
p, _ := config.ConfigPath()
output.PrintKeyValue(
"base-url", cfg.BaseURL,
"config-file", p,
"mode", mode.Detect().String(),
)
}
}

View File

@@ -1,3 +0,0 @@
package commands
// Package commands will define the hf command tree.

View File

@@ -0,0 +1,32 @@
package commands
import (
"fmt"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/client"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/config"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
)
// RunHealth checks the HarborForge API health endpoint.
func RunHealth() {
cfg, err := config.Load()
if err != nil {
output.Errorf("config error: %v", err)
}
c := client.New(cfg.BaseURL, "")
result, err := c.Health()
if err != nil {
output.Errorf("health check failed: %v", err)
}
if output.JSONMode {
output.PrintJSON(result)
} else {
status, _ := result["status"].(string)
if status == "" {
status = "unknown"
}
fmt.Printf("HarborForge API: %s\n", status)
fmt.Printf("URL: %s\n", cfg.BaseURL)
}
}

View File

@@ -0,0 +1,19 @@
package commands
import (
"fmt"
"git.hangman-lab.top/zhi/HarborForge.Cli/internal/output"
)
// Version is the CLI version string, set at build time via ldflags.
var Version = "dev"
// RunVersion prints the CLI version.
func RunVersion() {
if output.JSONMode {
output.PrintJSON(map[string]string{"version": Version})
} else {
fmt.Printf("hf %s\n", Version)
}
}