Skip to content

Commit 07bcbf6

Browse files
committed
feat(cli): add complete command coverage for all GoClaw backend APIs
New modules: - tenants: multi-tenant CRUD + user management - heartbeat: agent heartbeat monitoring, checklist, targets - devices: paired device management + pairing status - system-configs: system configuration key-value CRUD - packages: package install/uninstall/runtimes - export/import: agents, teams, skills, MCP export/import - contacts: contact merge/unmerge/search Updated modules: - agents: add wake, agent-level files list/get/set - teams: add tasks delete/events, team events, scopes - memory: add index/index-all/chunks, KG entities CRUD, graph, stats, traverse, dedup, merge - admin: add credentials get/update/test/presets, proper media upload - providers: add verify-embedding, embedding-status - skills: add install-dep, tenant-config set/delete - mcp: add server reconnect - traces: add usage timeseries/breakdown - config: add permissions list/grant/revoke
1 parent 392122a commit 07bcbf6

18 files changed

Lines changed: 2001 additions & 21 deletions

CLAUDE.md

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,42 @@ make install # Install to GOPATH/bin
2323
## Project Structure
2424

2525
```
26-
cmd/ # Cobra command files (1 per resource group)
26+
cmd/ # Cobra command files (1 per resource group)
27+
├── admin.go # Approvals, delegations, credentials, activity, TTS, media
28+
├── agents.go # Agent CRUD, links, instances, files, wake
29+
├── api_docs.go # API documentation viewer
30+
├── api_keys.go # Scoped API key management
31+
├── auth.go # Login, logout, profiles, device pairing
32+
├── channels.go # Messaging channels, writers, pending
33+
├── chat.go # Interactive/single-shot chat with agents
34+
├── config_cmd.go # Server config get/apply/patch/schema/permissions
35+
├── contacts.go # Contact merge/unmerge/search
36+
├── cron.go # Scheduled jobs CRUD
37+
├── devices.go # Device pairing management
38+
├── export_import.go # Export/import agents, teams, skills, MCP
39+
├── heartbeat.go # Agent heartbeat monitoring/checklist
40+
├── helpers.go # Shared helpers (newHTTP, buildBody, etc.)
41+
├── logs.go # Server log streaming
42+
├── mcp.go # MCP servers, grants, access requests
43+
├── memory.go # Memory docs + knowledge graph operations
44+
├── packages.go # Package install/uninstall/runtimes
45+
├── providers.go # LLM provider CRUD, verify, embedding
46+
├── root.go # Root command, global flags
47+
├── sessions.go # Chat session management
48+
├── skills.go # Skill CRUD, upload, grants, deps
49+
├── status.go # Server health/status
50+
├── storage.go # Workspace file browser
51+
├── system_configs.go # System config key-value store
52+
├── teams.go # Teams CRUD, members, tasks, workspace, events
53+
├── tenants.go # Multi-tenant management
54+
├── tools.go # Custom + built-in tool management
55+
├── traces.go # LLM traces + usage analytics
56+
└── version.go # CLI version
2757
internal/
28-
├── client/ # HTTP + WebSocket + auth clients
29-
├── config/ # Config loader (~/.goclaw/)
30-
├── output/ # Table/JSON/YAML formatters
31-
└── tui/ # Interactive prompts
58+
├── client/ # HTTP + WebSocket + auth clients
59+
├── config/ # Config loader (~/.goclaw/)
60+
├── output/ # Table/JSON/YAML formatters
61+
└── tui/ # Interactive prompts
3262
```
3363

3464
## Conventions

cmd/admin.go

Lines changed: 102 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package cmd
22

33
import (
4+
"bytes"
45
"fmt"
56
"io"
7+
"mime/multipart"
68
"net/url"
79
"os"
10+
"path/filepath"
811

912
"github.com/nextlevelbuilder/goclaw-cli/internal/output"
1013
"github.com/nextlevelbuilder/goclaw-cli/internal/tui"
@@ -176,6 +179,78 @@ var credentialsCreateCmd = &cobra.Command{
176179
},
177180
}
178181

182+
var credentialsGetCmd = &cobra.Command{
183+
Use: "get <id>", Short: "Get CLI credential details", Args: cobra.ExactArgs(1),
184+
RunE: func(cmd *cobra.Command, args []string) error {
185+
c, err := newHTTP()
186+
if err != nil {
187+
return err
188+
}
189+
data, err := c.Get("/v1/cli-credentials/" + args[0])
190+
if err != nil {
191+
return err
192+
}
193+
printer.Print(unmarshalMap(data))
194+
return nil
195+
},
196+
}
197+
198+
var credentialsUpdateCmd = &cobra.Command{
199+
Use: "update <id>", Short: "Update CLI credential", Args: cobra.ExactArgs(1),
200+
RunE: func(cmd *cobra.Command, args []string) error {
201+
c, err := newHTTP()
202+
if err != nil {
203+
return err
204+
}
205+
body := make(map[string]any)
206+
if cmd.Flags().Changed("name") {
207+
v, _ := cmd.Flags().GetString("name")
208+
body["name"] = v
209+
}
210+
if len(body) == 0 {
211+
return fmt.Errorf("no fields to update")
212+
}
213+
_, err = c.Put("/v1/cli-credentials/"+args[0], body)
214+
if err != nil {
215+
return err
216+
}
217+
printer.Success("Credential updated")
218+
return nil
219+
},
220+
}
221+
222+
var credentialsTestCmd = &cobra.Command{
223+
Use: "test <id>", Short: "Test CLI credential", Args: cobra.ExactArgs(1),
224+
RunE: func(cmd *cobra.Command, args []string) error {
225+
c, err := newHTTP()
226+
if err != nil {
227+
return err
228+
}
229+
data, err := c.Post("/v1/cli-credentials/"+args[0]+"/test", nil)
230+
if err != nil {
231+
return err
232+
}
233+
printer.Print(unmarshalMap(data))
234+
return nil
235+
},
236+
}
237+
238+
var credentialsPresetsCmd = &cobra.Command{
239+
Use: "presets", Short: "List credential presets",
240+
RunE: func(cmd *cobra.Command, args []string) error {
241+
c, err := newHTTP()
242+
if err != nil {
243+
return err
244+
}
245+
data, err := c.Get("/v1/cli-credentials/presets")
246+
if err != nil {
247+
return err
248+
}
249+
printer.Print(unmarshalList(data))
250+
return nil
251+
},
252+
}
253+
179254
var credentialsDeleteCmd = &cobra.Command{
180255
Use: "delete <id>", Short: "Delete CLI credential", Args: cobra.ExactArgs(1),
181256
RunE: func(cmd *cobra.Command, args []string) error {
@@ -337,10 +412,30 @@ var mediaUploadCmd = &cobra.Command{
337412
if err != nil {
338413
return err
339414
}
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
415+
f, err := os.Open(args[0])
416+
if err != nil {
417+
return fmt.Errorf("open file: %w", err)
418+
}
419+
defer f.Close()
420+
421+
var buf bytes.Buffer
422+
writer := multipart.NewWriter(&buf)
423+
part, err := writer.CreateFormFile("file", filepath.Base(args[0]))
424+
if err != nil {
425+
return err
426+
}
427+
if _, err := io.Copy(part, f); err != nil {
428+
return err
429+
}
430+
writer.Close()
431+
432+
resp, err := c.PostRaw("/v1/media/upload", writer.FormDataContentType(), &buf)
433+
if err != nil {
434+
return err
435+
}
436+
defer resp.Body.Close()
437+
respData, _ := io.ReadAll(resp.Body)
438+
printer.Print(unmarshalMap(respData))
344439
return nil
345440
},
346441
}
@@ -385,7 +480,9 @@ func init() {
385480
// Credentials
386481
credentialsCreateCmd.Flags().String("name", "", "Credential name")
387482
_ = credentialsCreateCmd.MarkFlagRequired("name")
388-
credentialsCmd.AddCommand(credentialsListCmd, credentialsCreateCmd, credentialsDeleteCmd)
483+
credentialsUpdateCmd.Flags().String("name", "", "Credential name")
484+
credentialsCmd.AddCommand(credentialsListCmd, credentialsGetCmd, credentialsCreateCmd,
485+
credentialsUpdateCmd, credentialsTestCmd, credentialsPresetsCmd, credentialsDeleteCmd)
389486

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

cmd/agents.go

Lines changed: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,111 @@ var agentsResummonCmd = &cobra.Command{
233233
},
234234
}
235235

236+
// --- Agent Wake ---
237+
238+
var agentsWakeCmd = &cobra.Command{
239+
Use: "wake <id>",
240+
Short: "Wake up a sleeping agent",
241+
Args: cobra.ExactArgs(1),
242+
RunE: func(cmd *cobra.Command, args []string) error {
243+
c, err := newHTTP()
244+
if err != nil {
245+
return err
246+
}
247+
_, err = c.Post("/v1/agents/"+args[0]+"/wake", nil)
248+
if err != nil {
249+
return err
250+
}
251+
printer.Success("Agent woken up")
252+
return nil
253+
},
254+
}
255+
256+
// --- Agent Files (agent-level, not instance-level) ---
257+
258+
var agentsFilesCmd = &cobra.Command{
259+
Use: "files",
260+
Short: "Manage agent context files",
261+
}
262+
263+
var agentsFilesListCmd = &cobra.Command{
264+
Use: "list <agentID>",
265+
Short: "List agent files",
266+
Args: cobra.ExactArgs(1),
267+
RunE: func(cmd *cobra.Command, args []string) error {
268+
ws, err := newWS("cli")
269+
if err != nil {
270+
return err
271+
}
272+
if _, err := ws.Connect(); err != nil {
273+
return err
274+
}
275+
defer ws.Close()
276+
data, err := ws.Call("agents.files.list", map[string]any{"agent_id": args[0]})
277+
if err != nil {
278+
return err
279+
}
280+
printer.Print(unmarshalList(data))
281+
return nil
282+
},
283+
}
284+
285+
var agentsFilesGetCmd = &cobra.Command{
286+
Use: "get <agentID> <fileName>",
287+
Short: "Get agent file content",
288+
Args: cobra.ExactArgs(2),
289+
RunE: func(cmd *cobra.Command, args []string) error {
290+
ws, err := newWS("cli")
291+
if err != nil {
292+
return err
293+
}
294+
if _, err := ws.Connect(); err != nil {
295+
return err
296+
}
297+
defer ws.Close()
298+
data, err := ws.Call("agents.files.get", map[string]any{"agent_id": args[0], "file_name": args[1]})
299+
if err != nil {
300+
return err
301+
}
302+
m := unmarshalMap(data)
303+
if content := str(m, "content"); content != "" {
304+
fmt.Println(content)
305+
} else {
306+
printer.Print(m)
307+
}
308+
return nil
309+
},
310+
}
311+
312+
var agentsFilesSetCmd = &cobra.Command{
313+
Use: "set <agentID> <fileName>",
314+
Short: "Set agent file content",
315+
Args: cobra.ExactArgs(2),
316+
RunE: func(cmd *cobra.Command, args []string) error {
317+
ws, err := newWS("cli")
318+
if err != nil {
319+
return err
320+
}
321+
if _, err := ws.Connect(); err != nil {
322+
return err
323+
}
324+
defer ws.Close()
325+
contentVal, _ := cmd.Flags().GetString("content")
326+
content, err := readContent(contentVal)
327+
if err != nil {
328+
return err
329+
}
330+
_, err = ws.Call("agents.files.set", map[string]any{
331+
"agent_id": args[0], "file_name": args[1], "content": content,
332+
})
333+
if err != nil {
334+
return err
335+
}
336+
printer.Success("File updated")
337+
return nil
338+
},
339+
}
340+
236341
// --- Agent Links ---
237342

238343
var agentsLinksCmd = &cobra.Command{
@@ -481,11 +586,16 @@ func init() {
481586
_ = agentsInstancesSetFileCmd.MarkFlagRequired("content")
482587
agentsInstancesMetadataCmd.Flags().String("patch", "", "JSON patch object")
483588

589+
// Agent files flags
590+
agentsFilesSetCmd.Flags().String("content", "", "Content (or @filepath)")
591+
_ = agentsFilesSetCmd.MarkFlagRequired("content")
592+
484593
// Wire up subcommands
594+
agentsFilesCmd.AddCommand(agentsFilesListCmd, agentsFilesGetCmd, agentsFilesSetCmd)
485595
agentsLinksCmd.AddCommand(agentsLinksListCmd, agentsLinksCreateCmd, agentsLinksUpdateCmd, agentsLinksDeleteCmd)
486596
agentsInstancesCmd.AddCommand(agentsInstancesListCmd, agentsInstancesGetFileCmd, agentsInstancesSetFileCmd, agentsInstancesMetadataCmd)
487597
agentsCmd.AddCommand(agentsListCmd, agentsGetCmd, agentsCreateCmd, agentsUpdateCmd, agentsDeleteCmd,
488-
agentsShareCmd, agentsUnshareCmd, agentsRegenerateCmd, agentsResummonCmd,
489-
agentsLinksCmd, agentsInstancesCmd)
598+
agentsShareCmd, agentsUnshareCmd, agentsRegenerateCmd, agentsResummonCmd, agentsWakeCmd,
599+
agentsFilesCmd, agentsLinksCmd, agentsInstancesCmd)
490600
rootCmd.AddCommand(agentsCmd)
491601
}

0 commit comments

Comments
 (0)