Skip to content

Commit 04ffc17

Browse files
authored
Merge pull request #25 from nextlevelbuilder/codex/runtime-packages-cli-parity
feat(cli): align runtime packages contracts
2 parents ed46934 + 3ce0deb commit 04ffc17

22 files changed

Lines changed: 1208 additions & 76 deletions

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,16 @@ Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5656
- `goclaw activity aggregate --group-by {action|actor_type|entity_type|actor_id} [--from --to --limit --actor-type --actor-id --action --entity-type --entity-id]` — group audit-log activity by dimension with bucket counts (`GET /v1/activity/aggregate`). Attached as subcommand of existing `activity` parent.
5757
- `goclaw logs aggregate [--group-by {level|source}] [--level --source --from]` — summarize the runtime log ring buffer (`GET /v1/logs/runtime/aggregate`, admin-only). Distinct from `logs tail`. Epoch-millis `last_seen` rendered as RFC3339, never scientific notation.
5858

59+
**Runtime & Packages parity**
60+
- `goclaw credentials agent-credentials` — list/get/set/delete per-agent credential material for secure CLI credentials.
61+
- `goclaw packages updates apply-all [packages...]` — accepts positional package specs in addition to `--packages`.
62+
5963
### Fixed
6064

65+
- `goclaw packages list` now decodes current server grouped payloads `{system,pip,npm,github}` in table mode while preserving raw object payloads for JSON/YAML.
66+
- `goclaw packages install` and `goclaw packages uninstall` now send the server-compatible `package` key; legacy `--runtime python|node` translates to `pip:`/`npm:` specs.
67+
- `goclaw packages runtimes`, `packages deny-groups`, and `packages github-releases --repo --limit` now match current server envelopes and required query parameters.
68+
- `goclaw credentials list`, `credentials presets`, `credentials agent-grants list`, and `credentials user-credentials list` now decode current server envelope payloads.
6169
- `goclaw traces list` now decodes the current server payload `{traces,total,limit,offset}`. JSON/YAML mode preserves that envelope; table mode renders rows from `traces` using `id`, `total_input_tokens`, `total_output_tokens`, and `total_cost`.
6270
- `goclaw traces get <id>` — TTY mode now renders a human-readable summary (header card + span tree) instead of dumping raw JSON. JSON-mode payload unchanged. Decode failures surface as wrapped errors instead of an empty `{}`. Trace ids are validated against `^[A-Za-z0-9._-]+$` and reserved tokens (`.`, `..`) are rejected before any HTTP call. Distinct exit codes per failure: not-found → 3, permission-denied → 2, malformed-id → 4, server-failure → 5. Latent retry-body bug in `internal/client/http.go` fixed: the final 5xx/429 response body is now preserved so the typed `APIError` reaches the caller (previously collapsed to exit 1). Closes #17.
6371
- `goclaw traces get <id>` now handles the current server detail payload `{trace,spans}` while preserving the server envelope in JSON/YAML mode.

README.md

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,8 @@ echo "Analyze this log" | goclaw chat myagent
7878
| `storage` | Workspace file browser |
7979
| `approvals` | Execution approval management |
8080
| `delegations` | Delegation history |
81-
| `credentials` | CLI credential store |
81+
| `packages` | Runtime package inventory, installs, updates, runtimes, deny groups, GitHub releases |
82+
| `credentials` | CLI credential store, grants, user credentials, agent credentials |
8283
| `tts` | Text-to-speech operations |
8384
| `media` | Media upload/download |
8485
| `activity` | Audit log |
@@ -132,6 +133,40 @@ goclaw logs aggregate [--group-by <level|source>] [--level <l>] [--source <s>] [
132133

133134
All are one-shot HTTP — no watch loops or WS streams. `logs aggregate` is admin-only on the server; `activity aggregate --group-by actor_id` is also admin-only (server-enforced).
134135

136+
### Runtime & Packages
137+
138+
```bash
139+
# Runtime inventory grouped by system, pip, npm, and GitHub package sources
140+
goclaw packages list
141+
142+
# Install or uninstall with legacy runtime flags translated to server package specs
143+
goclaw packages install pandas --runtime python
144+
goclaw packages uninstall typescript --runtime node --yes
145+
146+
# Runtime readiness, deny groups, GitHub release lookup, and update lifecycle
147+
goclaw packages runtimes
148+
goclaw packages deny-groups
149+
goclaw packages github-releases --repo cli/cli --limit 10
150+
goclaw packages updates list
151+
goclaw packages updates apply pip:pandas
152+
goclaw packages updates apply-all pip:pandas npm:typescript
153+
```
154+
155+
### CLI Credentials
156+
157+
```bash
158+
# Server-side secure CLI credential store
159+
goclaw credentials list
160+
goclaw credentials presets
161+
goclaw credentials create --body '{"preset":"git"}'
162+
163+
# Access grants and per-principal credential material
164+
goclaw credentials agent-grants list <credential-id>
165+
goclaw credentials user-credentials list <credential-id>
166+
goclaw credentials agent-credentials list <credential-id>
167+
goclaw credentials agent-credentials set <credential-id> <agent-id> --body '{"credential_type":"pat","env":{"GITHUB_TOKEN":"..."}}'
168+
```
169+
135170
### Reading a Trace by ID
136171

137172
```bash

cmd/admin_credentials.go

Lines changed: 21 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
package cmd
22

33
import (
4-
"encoding/json"
5-
"fmt"
6-
7-
"github.com/nextlevelbuilder/goclaw-cli/internal/output"
84
"github.com/nextlevelbuilder/goclaw-cli/internal/tui"
95
"github.com/spf13/cobra"
106
)
@@ -33,14 +29,10 @@ var adminCredentialsListCmd = &cobra.Command{
3329
return err
3430
}
3531
if cfg.OutputFormat != "table" {
36-
printer.Print(unmarshalList(data))
32+
printer.Print(rawPayload(data))
3733
return nil
3834
}
39-
tbl := output.NewTable("ID", "NAME", "CREATED")
40-
for _, cr := range unmarshalList(data) {
41-
tbl.AddRow(str(cr, "id"), str(cr, "name"), str(cr, "created_at"))
42-
}
43-
printer.Print(tbl)
35+
printer.Print(cliCredentialsTable(data))
4436
return nil
4537
},
4638
}
@@ -53,8 +45,11 @@ var adminCredentialsCreateCmd = &cobra.Command{
5345
if err != nil {
5446
return err
5547
}
56-
name, _ := cmd.Flags().GetString("name")
57-
data, err := c.Post("/v1/cli-credentials", map[string]any{"name": name})
48+
body, err := credentialCreateBody(cmd)
49+
if err != nil {
50+
return err
51+
}
52+
data, err := c.Post("/v1/cli-credentials", body)
5853
if err != nil {
5954
return err
6055
}
@@ -68,13 +63,9 @@ var adminCredentialsUpdateCmd = &cobra.Command{
6863
Short: "Update a CLI credential",
6964
Args: cobra.ExactArgs(1),
7065
RunE: func(cmd *cobra.Command, args []string) error {
71-
bodyJSON, _ := cmd.Flags().GetString("body")
72-
if bodyJSON == "" {
73-
return fmt.Errorf("--body is required (JSON object)")
74-
}
75-
var body map[string]any
76-
if err := json.Unmarshal([]byte(bodyJSON), &body); err != nil {
77-
return fmt.Errorf("invalid --body JSON: %w", err)
66+
body, err := jsonObjectFlag(cmd, "body", true)
67+
if err != nil {
68+
return err
7869
}
7970
c, err := newHTTP()
8071
if err != nil {
@@ -140,7 +131,11 @@ var adminCredentialsPresetsCmd = &cobra.Command{
140131
if err != nil {
141132
return err
142133
}
143-
printer.Print(unmarshalList(data))
134+
if cfg.OutputFormat != "table" {
135+
printer.Print(rawPayload(data))
136+
return nil
137+
}
138+
printer.Print(credentialPresetsTable(data))
144139
return nil
145140
},
146141
}
@@ -149,12 +144,9 @@ var adminCredentialsCheckBinaryCmd = &cobra.Command{
149144
Use: "check-binary",
150145
Short: "Verify a CLI binary is accessible on the server",
151146
RunE: func(cmd *cobra.Command, args []string) error {
152-
bodyJSON, _ := cmd.Flags().GetString("body")
153-
var body map[string]any
154-
if bodyJSON != "" {
155-
if err := json.Unmarshal([]byte(bodyJSON), &body); err != nil {
156-
return fmt.Errorf("invalid --body JSON: %w", err)
157-
}
147+
body, err := jsonObjectFlag(cmd, "body", false)
148+
if err != nil {
149+
return err
158150
}
159151
c, err := newHTTP()
160152
if err != nil {
@@ -170,8 +162,9 @@ var adminCredentialsCheckBinaryCmd = &cobra.Command{
170162
}
171163

172164
func init() {
173-
adminCredentialsCreateCmd.Flags().String("name", "", "Credential name (required)")
174-
_ = adminCredentialsCreateCmd.MarkFlagRequired("name")
165+
adminCredentialsCreateCmd.Flags().String("name", "", "Credential binary name")
166+
adminCredentialsCreateCmd.Flags().String("preset", "", "Credential preset name")
167+
adminCredentialsCreateCmd.Flags().String("body", "", "Create payload as JSON object")
175168

176169
adminCredentialsUpdateCmd.Flags().String("body", "", "Update payload as JSON object (required)")
177170
adminCredentialsCheckBinaryCmd.Flags().String("body", "", "Check payload as JSON object")

cmd/admin_credentials_agents.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package cmd
2+
3+
import (
4+
"github.com/nextlevelbuilder/goclaw-cli/internal/tui"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
// admin_credentials_agents.go adds per-agent credential material management.
9+
// Routes: GET/PUT/DELETE /v1/cli-credentials/{id}/agent-credentials[/{agentId}]
10+
11+
var adminCredAgentCredentialsCmd = &cobra.Command{
12+
Use: "agent-credentials",
13+
Short: "Manage per-agent credentials for a CLI credential",
14+
}
15+
16+
var adminCredAgentCredentialsListCmd = &cobra.Command{
17+
Use: "list <credID>",
18+
Short: "List agent credentials for a CLI credential",
19+
Args: cobra.ExactArgs(1),
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
c, err := newHTTP()
22+
if err != nil {
23+
return err
24+
}
25+
data, err := c.Get("/v1/cli-credentials/" + args[0] + "/agent-credentials")
26+
if err != nil {
27+
return err
28+
}
29+
if cfg.OutputFormat != "table" {
30+
printer.Print(rawPayload(data))
31+
return nil
32+
}
33+
printer.Print(agentCredentialsTable(data))
34+
return nil
35+
},
36+
}
37+
38+
var adminCredAgentCredentialsGetCmd = &cobra.Command{
39+
Use: "get <credID> <agentID>",
40+
Short: "Get an agent credential entry",
41+
Args: cobra.ExactArgs(2),
42+
RunE: func(cmd *cobra.Command, args []string) error {
43+
c, err := newHTTP()
44+
if err != nil {
45+
return err
46+
}
47+
data, err := c.Get("/v1/cli-credentials/" + args[0] + "/agent-credentials/" + args[1])
48+
if err != nil {
49+
return err
50+
}
51+
printer.Print(unmarshalMap(data))
52+
return nil
53+
},
54+
}
55+
56+
var adminCredAgentCredentialsSetCmd = &cobra.Command{
57+
Use: "set <credID> <agentID>",
58+
Short: "Create or update an agent credential entry",
59+
Args: cobra.ExactArgs(2),
60+
RunE: func(cmd *cobra.Command, args []string) error {
61+
body, err := jsonObjectFlag(cmd, "body", true)
62+
if err != nil {
63+
return err
64+
}
65+
c, err := newHTTP()
66+
if err != nil {
67+
return err
68+
}
69+
_, err = c.Put("/v1/cli-credentials/"+args[0]+"/agent-credentials/"+args[1], body)
70+
if err != nil {
71+
return err
72+
}
73+
printer.Success("Agent credential set")
74+
return nil
75+
},
76+
}
77+
78+
var adminCredAgentCredentialsDeleteCmd = &cobra.Command{
79+
Use: "delete <credID> <agentID>",
80+
Short: "Delete an agent credential entry (requires --yes)",
81+
Args: cobra.ExactArgs(2),
82+
RunE: func(cmd *cobra.Command, args []string) error {
83+
if !tui.Confirm("Delete agent credential?", cfg.Yes) {
84+
return nil
85+
}
86+
c, err := newHTTP()
87+
if err != nil {
88+
return err
89+
}
90+
_, err = c.Delete("/v1/cli-credentials/" + args[0] + "/agent-credentials/" + args[1])
91+
if err != nil {
92+
return err
93+
}
94+
printer.Success("Agent credential deleted")
95+
return nil
96+
},
97+
}
98+
99+
func init() {
100+
adminCredAgentCredentialsSetCmd.Flags().String("body", "", "Credential payload as JSON object (required)")
101+
_ = adminCredAgentCredentialsSetCmd.MarkFlagRequired("body")
102+
103+
adminCredAgentCredentialsCmd.AddCommand(
104+
adminCredAgentCredentialsListCmd,
105+
adminCredAgentCredentialsGetCmd,
106+
adminCredAgentCredentialsSetCmd,
107+
adminCredAgentCredentialsDeleteCmd,
108+
)
109+
adminCredentialsCmd.AddCommand(adminCredAgentCredentialsCmd)
110+
}

cmd/admin_credentials_grants.go

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cmd
22

33
import (
4-
"encoding/json"
54
"fmt"
65

76
"github.com/nextlevelbuilder/goclaw-cli/internal/tui"
@@ -29,7 +28,11 @@ var adminCredGrantsListCmd = &cobra.Command{
2928
if err != nil {
3029
return err
3130
}
32-
printer.Print(unmarshalList(data))
31+
if cfg.OutputFormat != "table" {
32+
printer.Print(rawPayload(data))
33+
return nil
34+
}
35+
printer.Print(credentialGrantsTable(data))
3336
return nil
3437
},
3538
}
@@ -39,13 +42,9 @@ var adminCredGrantsCreateCmd = &cobra.Command{
3942
Short: "Create an agent grant for a CLI credential",
4043
Args: cobra.ExactArgs(1),
4144
RunE: func(cmd *cobra.Command, args []string) error {
42-
bodyJSON, _ := cmd.Flags().GetString("body")
43-
if bodyJSON == "" {
44-
return fmt.Errorf("--body is required (JSON object)")
45-
}
46-
var body map[string]any
47-
if err := json.Unmarshal([]byte(bodyJSON), &body); err != nil {
48-
return fmt.Errorf("invalid --body JSON: %w", err)
45+
body, err := jsonObjectFlag(cmd, "body", true)
46+
if err != nil {
47+
return err
4948
}
5049
c, err := newHTTP()
5150
if err != nil {
@@ -84,13 +83,9 @@ var adminCredGrantsUpdateCmd = &cobra.Command{
8483
Short: "Update an agent grant",
8584
Args: cobra.ExactArgs(2),
8685
RunE: func(cmd *cobra.Command, args []string) error {
87-
bodyJSON, _ := cmd.Flags().GetString("body")
88-
if bodyJSON == "" {
89-
return fmt.Errorf("--body is required (JSON object)")
90-
}
91-
var body map[string]any
92-
if err := json.Unmarshal([]byte(bodyJSON), &body); err != nil {
93-
return fmt.Errorf("invalid --body JSON: %w", err)
86+
body, err := jsonObjectFlag(cmd, "body", true)
87+
if err != nil {
88+
return err
9489
}
9590
c, err := newHTTP()
9691
if err != nil {

0 commit comments

Comments
 (0)