Skip to content

Commit b0e192c

Browse files
authored
Merge pull request #1 from nextlevelbuilder/claude/agitated-shirley
feat(cli): multi-tenant support, 12 new command groups, modularization
2 parents 5512bdc + d51196d commit b0e192c

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+5280
-1676
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ dist/
1414
# Go
1515
vendor/
1616
*.test
17+
coverage.out
18+
19+
# Planning artifacts
20+
plans/
1721

1822
# IDE
1923
.idea/

README.md

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,31 +53,37 @@ echo "Analyze this log" | goclaw chat myagent
5353
| Command | Description |
5454
|---------|-------------|
5555
| `auth` | Login, logout, device pairing, profile management |
56-
| `agents` | CRUD, shares, delegation links, per-user instances |
57-
| `chat` | Interactive or single-shot messaging with streaming |
56+
| `agents` | CRUD, shares, delegation links, per-user instances, wait |
57+
| `chat` | Interactive/single-shot messaging, inject, status, abort |
5858
| `sessions` | List, preview, delete, reset, label |
59-
| `skills` | Upload, manage, grant/revoke access |
59+
| `skills` | Upload, manage, grant/revoke, versions, files, tenant-config, deps, runtimes |
6060
| `mcp` | MCP server management, grants, access requests |
61-
| `providers` | LLM provider CRUD, model listing, verification |
62-
| `tools` | Custom + built-in tool management, invocation |
61+
| `providers` | LLM provider CRUD, model listing, verification, embedding status |
62+
| `tools` | Builtin tool management, tenant-config |
6363
| `cron` | Scheduled jobs CRUD, trigger, run history |
64-
| `teams` | Team management, task board, workspace |
65-
| `channels` | Channel instances, contacts, pending messages |
64+
| `teams` | Team management, task board, task approval, workspace, events |
65+
| `channels` | Channel instances, contacts, pending messages, writers |
6666
| `traces` | LLM trace viewer, export |
6767
| `memory` | Memory documents, semantic search |
68-
| `knowledge-graph` | Entity extraction, linking, querying |
69-
| `usage` | Usage analytics and cost breakdown |
70-
| `config` | Server configuration get/apply/patch |
68+
| `knowledge-graph` | Entity extraction, linking, querying, traversal |
69+
| `usage` | Usage analytics, cost breakdown, timeseries |
70+
| `config` | Server configuration get/apply/patch, permissions |
7171
| `logs` | Real-time log streaming |
72-
| `storage` | Workspace file browser |
72+
| `storage` | Workspace file browser, download, move |
7373
| `approvals` | Execution approval management |
7474
| `delegations` | Delegation history |
75-
| `credentials` | CLI credential store |
76-
| `tts` | Text-to-speech operations |
75+
| `credentials` | CLI credential store, presets, testing |
76+
| `tts` | Text-to-speech operations, convert |
7777
| `media` | Media upload/download |
7878
| `activity` | Audit log |
7979
| `api-keys` | API key management (create, list, revoke) |
8080
| `api-docs` | API documentation (Swagger UI, OpenAPI spec) |
81+
| `tenants` | Tenant CRUD, user management (admin) |
82+
| `system-config` | Per-tenant key-value configuration |
83+
| `packages` | Package management, runtimes |
84+
| `contacts` | Contact resolution, merge/unmerge |
85+
| `pending-messages` | Pending message management |
86+
| `heartbeat` | Health monitoring, checklist, targets |
8187

8288
## API Keys
8389

@@ -126,6 +132,24 @@ export GOCLAW_TOKEN=your-token
126132
goclaw agents list
127133
```
128134

135+
## Multi-Tenant
136+
137+
All commands support tenant context via the `--tenant-id` flag:
138+
139+
```bash
140+
# Set tenant context for all operations
141+
goclaw agents list --tenant-id my-tenant
142+
143+
# Or via environment variable
144+
export GOCLAW_TENANT_ID=my-tenant
145+
goclaw agents list
146+
147+
# Manage tenants (admin only)
148+
goclaw tenants list
149+
goclaw tenants create --name "My Tenant"
150+
goclaw tenants users list <tenant-id>
151+
```
152+
129153
## Configuration
130154

131155
Config stored in `~/.goclaw/config.yaml`:
@@ -141,6 +165,14 @@ profiles:
141165
token: staging-token
142166
```
143167
168+
Environment variables:
169+
170+
| Variable | Description |
171+
|----------|-------------|
172+
| `GOCLAW_SERVER` | Server URL |
173+
| `GOCLAW_TOKEN` | Auth token or API key |
174+
| `GOCLAW_TENANT_ID` | Tenant ID for multi-tenant operations |
175+
144176
Switch profiles:
145177

146178
```bash

cmd/admin.go

Lines changed: 2 additions & 263 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,9 @@ package cmd
22

33
import (
44
"fmt"
5-
"io"
65
"net/url"
7-
"os"
86

97
"github.com/nextlevelbuilder/goclaw-cli/internal/output"
10-
"github.com/nextlevelbuilder/goclaw-cli/internal/tui"
118
"github.com/spf13/cobra"
129
)
1310

@@ -122,7 +119,7 @@ var delegationsGetCmd = &cobra.Command{
122119
if err != nil {
123120
return err
124121
}
125-
data, err := c.Get("/v1/delegations/" + args[0])
122+
data, err := c.Get("/v1/delegations/" + url.PathEscape(args[0]))
126123
if err != nil {
127124
return err
128125
}
@@ -131,247 +128,6 @@ var delegationsGetCmd = &cobra.Command{
131128
},
132129
}
133130

134-
// --- CLI Credentials ---
135-
136-
var credentialsCmd = &cobra.Command{Use: "credentials", Short: "Manage CLI credentials store"}
137-
138-
var credentialsListCmd = &cobra.Command{
139-
Use: "list", Short: "List stored credentials",
140-
RunE: func(cmd *cobra.Command, args []string) error {
141-
c, err := newHTTP()
142-
if err != nil {
143-
return err
144-
}
145-
data, err := c.Get("/v1/cli-credentials")
146-
if err != nil {
147-
return err
148-
}
149-
if cfg.OutputFormat != "table" {
150-
printer.Print(unmarshalList(data))
151-
return nil
152-
}
153-
tbl := output.NewTable("ID", "NAME", "CREATED")
154-
for _, cr := range unmarshalList(data) {
155-
tbl.AddRow(str(cr, "id"), str(cr, "name"), str(cr, "created_at"))
156-
}
157-
printer.Print(tbl)
158-
return nil
159-
},
160-
}
161-
162-
var credentialsCreateCmd = &cobra.Command{
163-
Use: "create", Short: "Create CLI credential",
164-
RunE: func(cmd *cobra.Command, args []string) error {
165-
c, err := newHTTP()
166-
if err != nil {
167-
return err
168-
}
169-
name, _ := cmd.Flags().GetString("name")
170-
data, err := c.Post("/v1/cli-credentials", map[string]any{"name": name})
171-
if err != nil {
172-
return err
173-
}
174-
printer.Print(unmarshalMap(data))
175-
return nil
176-
},
177-
}
178-
179-
var credentialsDeleteCmd = &cobra.Command{
180-
Use: "delete <id>", Short: "Delete CLI credential", Args: cobra.ExactArgs(1),
181-
RunE: func(cmd *cobra.Command, args []string) error {
182-
if !tui.Confirm("Delete this credential?", cfg.Yes) {
183-
return nil
184-
}
185-
c, err := newHTTP()
186-
if err != nil {
187-
return err
188-
}
189-
_, err = c.Delete("/v1/cli-credentials/" + args[0])
190-
if err != nil {
191-
return err
192-
}
193-
printer.Success("Credential deleted")
194-
return nil
195-
},
196-
}
197-
198-
// --- Activity ---
199-
200-
var activityCmd = &cobra.Command{
201-
Use: "activity", Short: "View audit log",
202-
RunE: func(cmd *cobra.Command, args []string) error {
203-
c, err := newHTTP()
204-
if err != nil {
205-
return err
206-
}
207-
q := url.Values{}
208-
if v, _ := cmd.Flags().GetInt("limit"); v > 0 {
209-
q.Set("limit", fmt.Sprintf("%d", v))
210-
}
211-
path := "/v1/activity"
212-
if len(q) > 0 {
213-
path += "?" + q.Encode()
214-
}
215-
data, err := c.Get(path)
216-
if err != nil {
217-
return err
218-
}
219-
printer.Print(unmarshalList(data))
220-
return nil
221-
},
222-
}
223-
224-
// --- TTS ---
225-
226-
var ttsCmd = &cobra.Command{Use: "tts", Short: "Text-to-speech operations"}
227-
228-
var ttsStatusCmd = &cobra.Command{
229-
Use: "status", Short: "TTS status",
230-
RunE: func(cmd *cobra.Command, args []string) error {
231-
ws, err := newWS("cli")
232-
if err != nil {
233-
return err
234-
}
235-
if _, err := ws.Connect(); err != nil {
236-
return err
237-
}
238-
defer ws.Close()
239-
data, err := ws.Call("tts.status", nil)
240-
if err != nil {
241-
return err
242-
}
243-
printer.Print(unmarshalMap(data))
244-
return nil
245-
},
246-
}
247-
248-
var ttsEnableCmd = &cobra.Command{
249-
Use: "enable", Short: "Enable TTS",
250-
RunE: func(cmd *cobra.Command, args []string) error {
251-
ws, err := newWS("cli")
252-
if err != nil {
253-
return err
254-
}
255-
if _, err := ws.Connect(); err != nil {
256-
return err
257-
}
258-
defer ws.Close()
259-
_, err = ws.Call("tts.enable", nil)
260-
if err != nil {
261-
return err
262-
}
263-
printer.Success("TTS enabled")
264-
return nil
265-
},
266-
}
267-
268-
var ttsDisableCmd = &cobra.Command{
269-
Use: "disable", Short: "Disable TTS",
270-
RunE: func(cmd *cobra.Command, args []string) error {
271-
ws, err := newWS("cli")
272-
if err != nil {
273-
return err
274-
}
275-
if _, err := ws.Connect(); err != nil {
276-
return err
277-
}
278-
defer ws.Close()
279-
_, err = ws.Call("tts.disable", nil)
280-
if err != nil {
281-
return err
282-
}
283-
printer.Success("TTS disabled")
284-
return nil
285-
},
286-
}
287-
288-
var ttsProvidersCmd = &cobra.Command{
289-
Use: "providers", Short: "List TTS providers",
290-
RunE: func(cmd *cobra.Command, args []string) error {
291-
ws, err := newWS("cli")
292-
if err != nil {
293-
return err
294-
}
295-
if _, err := ws.Connect(); err != nil {
296-
return err
297-
}
298-
defer ws.Close()
299-
data, err := ws.Call("tts.providers", nil)
300-
if err != nil {
301-
return err
302-
}
303-
printer.Print(unmarshalList(data))
304-
return nil
305-
},
306-
}
307-
308-
var ttsSetProviderCmd = &cobra.Command{
309-
Use: "set-provider", Short: "Set TTS provider",
310-
RunE: func(cmd *cobra.Command, args []string) error {
311-
ws, err := newWS("cli")
312-
if err != nil {
313-
return err
314-
}
315-
if _, err := ws.Connect(); err != nil {
316-
return err
317-
}
318-
defer ws.Close()
319-
name, _ := cmd.Flags().GetString("name")
320-
_, err = ws.Call("tts.setProvider", map[string]any{"provider": name})
321-
if err != nil {
322-
return err
323-
}
324-
printer.Success("TTS provider set")
325-
return nil
326-
},
327-
}
328-
329-
// --- Media ---
330-
331-
var mediaCmd = &cobra.Command{Use: "media", Short: "Upload and download media"}
332-
333-
var mediaUploadCmd = &cobra.Command{
334-
Use: "upload <file>", Short: "Upload media file", Args: cobra.ExactArgs(1),
335-
RunE: func(cmd *cobra.Command, args []string) error {
336-
c, err := newHTTP()
337-
if err != nil {
338-
return err
339-
}
340-
// Use PostRaw with multipart
341-
// Simplified: read file and POST
342-
printer.Success(fmt.Sprintf("Upload %s — use HTTP API directly for multipart uploads", args[0]))
343-
_ = c
344-
return nil
345-
},
346-
}
347-
348-
var mediaGetCmd = &cobra.Command{
349-
Use: "get <mediaID>", Short: "Download media", Args: cobra.ExactArgs(1),
350-
RunE: func(cmd *cobra.Command, args []string) error {
351-
c, err := newHTTP()
352-
if err != nil {
353-
return err
354-
}
355-
outFile, _ := cmd.Flags().GetString("output")
356-
if outFile == "" {
357-
outFile = args[0]
358-
}
359-
resp, err := c.GetRaw("/v1/media/" + args[0])
360-
if err != nil {
361-
return err
362-
}
363-
defer resp.Body.Close()
364-
f, err := os.Create(outFile)
365-
if err != nil {
366-
return err
367-
}
368-
defer f.Close()
369-
n, _ := io.Copy(f, resp.Body)
370-
printer.Success(fmt.Sprintf("Downloaded %d bytes to %s", n, outFile))
371-
return nil
372-
},
373-
}
374-
375131
func init() {
376132
// Approvals
377133
approvalsDenyCmd.Flags().String("reason", "", "Denial reason")
@@ -382,22 +138,5 @@ func init() {
382138
delegationsListCmd.Flags().Int("limit", 20, "Max results")
383139
delegationsCmd.AddCommand(delegationsListCmd, delegationsGetCmd)
384140

385-
// Credentials
386-
credentialsCreateCmd.Flags().String("name", "", "Credential name")
387-
_ = credentialsCreateCmd.MarkFlagRequired("name")
388-
credentialsCmd.AddCommand(credentialsListCmd, credentialsCreateCmd, credentialsDeleteCmd)
389-
390-
// Activity
391-
activityCmd.Flags().Int("limit", 50, "Max results")
392-
393-
// TTS
394-
ttsSetProviderCmd.Flags().String("name", "", "Provider name")
395-
_ = ttsSetProviderCmd.MarkFlagRequired("name")
396-
ttsCmd.AddCommand(ttsStatusCmd, ttsEnableCmd, ttsDisableCmd, ttsProvidersCmd, ttsSetProviderCmd)
397-
398-
// Media
399-
mediaGetCmd.Flags().StringP("output", "f", "", "Output file")
400-
mediaCmd.AddCommand(mediaUploadCmd, mediaGetCmd)
401-
402-
rootCmd.AddCommand(approvalsCmd, delegationsCmd, credentialsCmd, activityCmd, ttsCmd, mediaCmd)
141+
rootCmd.AddCommand(approvalsCmd, delegationsCmd)
403142
}

0 commit comments

Comments
 (0)