Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ dist/
# Go
vendor/
*.test
coverage.out

# Planning artifacts
plans/

# IDE
.idea/
Expand Down
58 changes: 45 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,31 +53,37 @@ echo "Analyze this log" | goclaw chat myagent
| Command | Description |
|---------|-------------|
| `auth` | Login, logout, device pairing, profile management |
| `agents` | CRUD, shares, delegation links, per-user instances |
| `chat` | Interactive or single-shot messaging with streaming |
| `agents` | CRUD, shares, delegation links, per-user instances, wait |
| `chat` | Interactive/single-shot messaging, inject, status, abort |
| `sessions` | List, preview, delete, reset, label |
| `skills` | Upload, manage, grant/revoke access |
| `skills` | Upload, manage, grant/revoke, versions, files, tenant-config, deps, runtimes |
| `mcp` | MCP server management, grants, access requests |
| `providers` | LLM provider CRUD, model listing, verification |
| `tools` | Custom + built-in tool management, invocation |
| `providers` | LLM provider CRUD, model listing, verification, embedding status |
| `tools` | Builtin tool management, tenant-config |
| `cron` | Scheduled jobs CRUD, trigger, run history |
| `teams` | Team management, task board, workspace |
| `channels` | Channel instances, contacts, pending messages |
| `teams` | Team management, task board, task approval, workspace, events |
| `channels` | Channel instances, contacts, pending messages, writers |
| `traces` | LLM trace viewer, export |
| `memory` | Memory documents, semantic search |
| `knowledge-graph` | Entity extraction, linking, querying |
| `usage` | Usage analytics and cost breakdown |
| `config` | Server configuration get/apply/patch |
| `knowledge-graph` | Entity extraction, linking, querying, traversal |
| `usage` | Usage analytics, cost breakdown, timeseries |
| `config` | Server configuration get/apply/patch, permissions |
| `logs` | Real-time log streaming |
| `storage` | Workspace file browser |
| `storage` | Workspace file browser, download, move |
| `approvals` | Execution approval management |
| `delegations` | Delegation history |
| `credentials` | CLI credential store |
| `tts` | Text-to-speech operations |
| `credentials` | CLI credential store, presets, testing |
| `tts` | Text-to-speech operations, convert |
| `media` | Media upload/download |
| `activity` | Audit log |
| `api-keys` | API key management (create, list, revoke) |
| `api-docs` | API documentation (Swagger UI, OpenAPI spec) |
| `tenants` | Tenant CRUD, user management (admin) |
| `system-config` | Per-tenant key-value configuration |
| `packages` | Package management, runtimes |
| `contacts` | Contact resolution, merge/unmerge |
| `pending-messages` | Pending message management |
| `heartbeat` | Health monitoring, checklist, targets |

## API Keys

Expand Down Expand Up @@ -126,6 +132,24 @@ export GOCLAW_TOKEN=your-token
goclaw agents list
```

## Multi-Tenant

All commands support tenant context via the `--tenant-id` flag:

```bash
# Set tenant context for all operations
goclaw agents list --tenant-id my-tenant

# Or via environment variable
export GOCLAW_TENANT_ID=my-tenant
goclaw agents list

# Manage tenants (admin only)
goclaw tenants list
goclaw tenants create --name "My Tenant"
goclaw tenants users list <tenant-id>
```

## Configuration

Config stored in `~/.goclaw/config.yaml`:
Expand All @@ -141,6 +165,14 @@ profiles:
token: staging-token
```

Environment variables:

| Variable | Description |
|----------|-------------|
| `GOCLAW_SERVER` | Server URL |
| `GOCLAW_TOKEN` | Auth token or API key |
| `GOCLAW_TENANT_ID` | Tenant ID for multi-tenant operations |

Switch profiles:

```bash
Expand Down
265 changes: 2 additions & 263 deletions cmd/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ package cmd

import (
"fmt"
"io"
"net/url"
"os"

"github.com/nextlevelbuilder/goclaw-cli/internal/output"
"github.com/nextlevelbuilder/goclaw-cli/internal/tui"
"github.com/spf13/cobra"
)

Expand Down Expand Up @@ -122,7 +119,7 @@ var delegationsGetCmd = &cobra.Command{
if err != nil {
return err
}
data, err := c.Get("/v1/delegations/" + args[0])
data, err := c.Get("/v1/delegations/" + url.PathEscape(args[0]))
if err != nil {
return err
}
Expand All @@ -131,247 +128,6 @@ var delegationsGetCmd = &cobra.Command{
},
}

// --- CLI Credentials ---

var credentialsCmd = &cobra.Command{Use: "credentials", Short: "Manage CLI credentials store"}

var credentialsListCmd = &cobra.Command{
Use: "list", Short: "List stored credentials",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newHTTP()
if err != nil {
return err
}
data, err := c.Get("/v1/cli-credentials")
if err != nil {
return err
}
if cfg.OutputFormat != "table" {
printer.Print(unmarshalList(data))
return nil
}
tbl := output.NewTable("ID", "NAME", "CREATED")
for _, cr := range unmarshalList(data) {
tbl.AddRow(str(cr, "id"), str(cr, "name"), str(cr, "created_at"))
}
printer.Print(tbl)
return nil
},
}

var credentialsCreateCmd = &cobra.Command{
Use: "create", Short: "Create CLI credential",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newHTTP()
if err != nil {
return err
}
name, _ := cmd.Flags().GetString("name")
data, err := c.Post("/v1/cli-credentials", map[string]any{"name": name})
if err != nil {
return err
}
printer.Print(unmarshalMap(data))
return nil
},
}

var credentialsDeleteCmd = &cobra.Command{
Use: "delete <id>", Short: "Delete CLI credential", Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if !tui.Confirm("Delete this credential?", cfg.Yes) {
return nil
}
c, err := newHTTP()
if err != nil {
return err
}
_, err = c.Delete("/v1/cli-credentials/" + args[0])
if err != nil {
return err
}
printer.Success("Credential deleted")
return nil
},
}

// --- Activity ---

var activityCmd = &cobra.Command{
Use: "activity", Short: "View audit log",
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newHTTP()
if err != nil {
return err
}
q := url.Values{}
if v, _ := cmd.Flags().GetInt("limit"); v > 0 {
q.Set("limit", fmt.Sprintf("%d", v))
}
path := "/v1/activity"
if len(q) > 0 {
path += "?" + q.Encode()
}
data, err := c.Get(path)
if err != nil {
return err
}
printer.Print(unmarshalList(data))
return nil
},
}

// --- TTS ---

var ttsCmd = &cobra.Command{Use: "tts", Short: "Text-to-speech operations"}

var ttsStatusCmd = &cobra.Command{
Use: "status", Short: "TTS status",
RunE: func(cmd *cobra.Command, args []string) error {
ws, err := newWS("cli")
if err != nil {
return err
}
if _, err := ws.Connect(); err != nil {
return err
}
defer ws.Close()
data, err := ws.Call("tts.status", nil)
if err != nil {
return err
}
printer.Print(unmarshalMap(data))
return nil
},
}

var ttsEnableCmd = &cobra.Command{
Use: "enable", Short: "Enable TTS",
RunE: func(cmd *cobra.Command, args []string) error {
ws, err := newWS("cli")
if err != nil {
return err
}
if _, err := ws.Connect(); err != nil {
return err
}
defer ws.Close()
_, err = ws.Call("tts.enable", nil)
if err != nil {
return err
}
printer.Success("TTS enabled")
return nil
},
}

var ttsDisableCmd = &cobra.Command{
Use: "disable", Short: "Disable TTS",
RunE: func(cmd *cobra.Command, args []string) error {
ws, err := newWS("cli")
if err != nil {
return err
}
if _, err := ws.Connect(); err != nil {
return err
}
defer ws.Close()
_, err = ws.Call("tts.disable", nil)
if err != nil {
return err
}
printer.Success("TTS disabled")
return nil
},
}

var ttsProvidersCmd = &cobra.Command{
Use: "providers", Short: "List TTS providers",
RunE: func(cmd *cobra.Command, args []string) error {
ws, err := newWS("cli")
if err != nil {
return err
}
if _, err := ws.Connect(); err != nil {
return err
}
defer ws.Close()
data, err := ws.Call("tts.providers", nil)
if err != nil {
return err
}
printer.Print(unmarshalList(data))
return nil
},
}

var ttsSetProviderCmd = &cobra.Command{
Use: "set-provider", Short: "Set TTS provider",
RunE: func(cmd *cobra.Command, args []string) error {
ws, err := newWS("cli")
if err != nil {
return err
}
if _, err := ws.Connect(); err != nil {
return err
}
defer ws.Close()
name, _ := cmd.Flags().GetString("name")
_, err = ws.Call("tts.setProvider", map[string]any{"provider": name})
if err != nil {
return err
}
printer.Success("TTS provider set")
return nil
},
}

// --- Media ---

var mediaCmd = &cobra.Command{Use: "media", Short: "Upload and download media"}

var mediaUploadCmd = &cobra.Command{
Use: "upload <file>", Short: "Upload media file", Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newHTTP()
if err != nil {
return err
}
// Use PostRaw with multipart
// Simplified: read file and POST
printer.Success(fmt.Sprintf("Upload %s — use HTTP API directly for multipart uploads", args[0]))
_ = c
return nil
},
}

var mediaGetCmd = &cobra.Command{
Use: "get <mediaID>", Short: "Download media", Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
c, err := newHTTP()
if err != nil {
return err
}
outFile, _ := cmd.Flags().GetString("output")
if outFile == "" {
outFile = args[0]
}
resp, err := c.GetRaw("/v1/media/" + args[0])
if err != nil {
return err
}
defer resp.Body.Close()
f, err := os.Create(outFile)
if err != nil {
return err
}
defer f.Close()
n, _ := io.Copy(f, resp.Body)
printer.Success(fmt.Sprintf("Downloaded %d bytes to %s", n, outFile))
return nil
},
}

func init() {
// Approvals
approvalsDenyCmd.Flags().String("reason", "", "Denial reason")
Expand All @@ -382,22 +138,5 @@ func init() {
delegationsListCmd.Flags().Int("limit", 20, "Max results")
delegationsCmd.AddCommand(delegationsListCmd, delegationsGetCmd)

// Credentials
credentialsCreateCmd.Flags().String("name", "", "Credential name")
_ = credentialsCreateCmd.MarkFlagRequired("name")
credentialsCmd.AddCommand(credentialsListCmd, credentialsCreateCmd, credentialsDeleteCmd)

// Activity
activityCmd.Flags().Int("limit", 50, "Max results")

// TTS
ttsSetProviderCmd.Flags().String("name", "", "Provider name")
_ = ttsSetProviderCmd.MarkFlagRequired("name")
ttsCmd.AddCommand(ttsStatusCmd, ttsEnableCmd, ttsDisableCmd, ttsProvidersCmd, ttsSetProviderCmd)

// Media
mediaGetCmd.Flags().StringP("output", "f", "", "Output file")
mediaCmd.AddCommand(mediaUploadCmd, mediaGetCmd)

rootCmd.AddCommand(approvalsCmd, delegationsCmd, credentialsCmd, activityCmd, ttsCmd, mediaCmd)
rootCmd.AddCommand(approvalsCmd, delegationsCmd)
}
Loading
Loading