chore: Detect interactive mod and View only TUI for basic onboard

This commit is contained in:
Yoftahe 2026-02-23 17:30:54 +03:00
parent c72e810157
commit e2c5c4aa1d
3 changed files with 180 additions and 23 deletions

View file

@ -18,23 +18,37 @@ import (
//go:embed workspace
var embeddedFiles embed.FS
func onboard() {
func onboard(args []string) {
configPath := getConfigPath()
// Use the new setup package to run interactive setup and ask for missing values.
interactive := false
for _, arg := range args {
if arg == "--interactive" || arg == "-i" {
interactive = true
break
}
}
s, err := setup.NewSetup(configPath)
if err != nil {
fmt.Printf("Error initializing setup: %v\n", err)
os.Exit(1)
}
if err := s.Run(); err != nil {
fmt.Printf("TUI error: %v\n", err)
}
if interactive {
if err := s.Run(); err != nil {
fmt.Printf("TUI error: %v\n", err)
}
if err := s.AskMissing(); err != nil {
fmt.Printf("Error updating config: %v\n", err)
os.Exit(1)
if err := s.AskMissing(); err != nil {
fmt.Printf("Error updating config: %v\n", err)
os.Exit(1)
}
} else {
if err := s.RunNonInteractive(); err != nil {
fmt.Printf("Error running non-interactive setup: %v\n", err)
os.Exit(1)
}
}
cfg := s.Cfg
@ -47,44 +61,37 @@ func onboard() {
}
func copyEmbeddedToTarget(targetDir string) error {
// Ensure target directory exists
if err := os.MkdirAll(targetDir, 0o755); err != nil {
return fmt.Errorf("Failed to create target directory: %w", err)
return fmt.Errorf("failed to create target directory: %w", err)
}
// Walk through all files in embed.FS
err := fs.WalkDir(embeddedFiles, "workspace", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
// Skip directories
if d.IsDir() {
return nil
}
// Read embedded file
data, err := embeddedFiles.ReadFile(path)
if err != nil {
return fmt.Errorf("Failed to read embedded file %s: %w", path, err)
return fmt.Errorf("failed to read embedded file %s: %w", path, err)
}
new_path, err := filepath.Rel("workspace", path)
newPath, err := filepath.Rel("workspace", path)
if err != nil {
return fmt.Errorf("Failed to get relative path for %s: %v\n", path, err)
return fmt.Errorf("failed to get relative path for %s: %v\n", path, err)
}
// Build target file path
targetPath := filepath.Join(targetDir, new_path)
targetPath := filepath.Join(targetDir, newPath)
// Ensure target file's directory exists
if err := os.MkdirAll(filepath.Dir(targetPath), 0o755); err != nil {
return fmt.Errorf("Failed to create directory %s: %w", filepath.Dir(targetPath), err)
return fmt.Errorf("failed to create directory %s: %w", filepath.Dir(targetPath), err)
}
// Write file
if err := os.WriteFile(targetPath, data, 0o644); err != nil {
return fmt.Errorf("Failed to write file %s: %w", targetPath, err)
return fmt.Errorf("failed to write file %s: %w", targetPath, err)
}
return nil

View file

@ -102,7 +102,7 @@ func main() {
switch command {
case "onboard":
onboard()
onboard(os.Args[2:])
case "agent":
agentCmd()
case "gateway":

View file

@ -100,6 +100,156 @@ func (s *Setup) AskMissing() error {
return nil
}
func (s *Setup) RunNonInteractive() error {
cfg := s.Cfg
if cfg.Agents.Defaults.Workspace == "" {
cfg.Agents.Defaults.Workspace = "~/.picoclaw/workspace"
}
workspace := cfg.WorkspacePath()
titleStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")).Bold(true)
successStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("82")).Bold(true)
dimStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("244"))
highlightStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("86")).Bold(true)
sectionStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("214")).Bold(true)
var b strings.Builder
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(titleStyle.Render("╔═══════════════════════════════════════════════════════╗")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(titleStyle.Render("║ PicoClaw Setup Complete ║")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(titleStyle.Render("╚═══════════════════════════════════════════════════════╝")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(successStyle.Render("✓ Workspace config created and is working!")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("📁 Workspace:")))
b.WriteString(" ")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(workspace)))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("⚙ Config:")))
b.WriteString(" ")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(s.ConfigPath)))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("📋 IMPORTANT COMMANDS")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(highlightStyle.Render(" picoclaw agent - Start chatting with the agent")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" picoclaw onboard --interactive - Full interactive setup")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" picoclaw auth login - Login to providers (openai, anthropic)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" picoclaw skills list - List available skills")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" picoclaw status - Show picoclaw status")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" picoclaw gateway - Start the gateway server")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("🔌 PROVIDERS (in config.json)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(highlightStyle.Render(" openai : OpenAI API key")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" anthropic : Anthropic/Claude API key")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" openrouter : OpenRouter (unified API)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" groq : Groq (fast inference)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" deepseek : DeepSeek API")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" ollama : Local models (http://localhost:11434)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" vllm : Local vLLM server")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" + more: gemini, nvidia, moonshot, qwen, etc.")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("💬 CHANNELS (in config.json)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(highlightStyle.Render(" telegram : Telegram bot (token from @BotFather)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" slack : Slack bot (bot_token, app_token)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" discord : Discord bot (token from dev portal)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" whatsapp : WhatsApp bridge (bridge_url)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" feishu : Feishu/Lark (app_id, app_secret)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" dingtalk : DingTalk (client_id, client_secret)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" line : LINE (channel_access_token)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" qq : QQ (app_id)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" onebot : OneBot (ws_url, access_token)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" wecom : WeCom (token, encoding_aes_key)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" wecom_app : WeCom App (corp_id, agent_id)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" maixcam : MaixCam device")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("🤖 MODELS (in model_list)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" Format: {\"model\": \"provider/model-id\", \"api_base\": \"url\"}")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" Examples:")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" {\"model\": \"openai/gpt-4o\", \"api_base\": \"https://api.openai.com/v1\"}")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" {\"model\": \"anthropic/claude-sonnet-4-20250514\", \"api_base\": \"https://api.anthropic.com/v1\"}")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" {\"model\": \"deepseek/deepseek-chat\", \"api_base\": \"https://api.deepseek.com/v1\"}")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" {\"model\": \"ollama/llama3\", \"api_base\": \"http://localhost:11434/v1\"}")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(sectionStyle.Render("🚀 NEXT STEPS")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(highlightStyle.Render(" 1. Edit config.json to add your provider API key")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" 2. Enable and configure your desired channel(s)")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" 3. Run 'picoclaw auth login' for OAuth providers")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(successStyle.Render(" 4. Run 'picoclaw agent' to start chatting!")))
b.WriteString("\n\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")))
b.WriteString("\n")
b.WriteString(lipgloss.NewStyle().PaddingLeft(4).Render(dimStyle.Render(" Run 'picoclaw onboard --interactive' for guided setup")))
b.WriteString("\n\n")
fmt.Println(b.String())
return nil
}
func saveConfigPath(path string, cfg *config.Config) error {
dir := filepath.Dir(path)
if err := os.MkdirAll(dir, 0o755); err != nil {