code.oscarkilo.com/okg

Hash:
6b990f7f1491ae353d606b28e73abf7788cd781b
Author:
Igor Naverniouk <[email protected]>
Date:
Thu Jun 4 21:02:17 2026 -0400
Message:
okg: add `okg group members` CLI shape: `okg group members GROUP [--json]`. Resolves GROUP to its owid via /groups/list, then GET-equivalent POST /groups/members and prints direct members (Down[0]) one username per line. --json dumps the full GroupMembersResponse (Up + Down + Usernames) for callers that want the DAG context (e.g. nested groups, transitive memberships). No new //okg/who methods — GroupMembers + resolveGroupOwid arrived with the remove-member commit; this is purely the CLI surface.
diff --git a/README.md b/README.md
index 6a76993..1aa15a3 100644
--- a/README.md
+++ b/README.md
@@ -60,6 +60,7 @@ okg group list [--json]
okg group create NAME [--full-name TEXT] [--owner USER]
okg group add-member GROUP USER [USER ...]
okg group remove-member GROUP USER [USER ...]
+okg group members GROUP [--json]

okg embed [--model NAME] [--dims N] [--full-path]
(reads stdin → vectors on stdout)
diff --git a/group.go b/group.go
index 7c03219..12a8fd5 100644
--- a/group.go
+++ b/group.go
@@ -23,6 +23,8 @@ func runGroup(args []string) error {
return runGroupAddMember(args[1:])
case "remove-member":
return runGroupRemoveMember(args[1:])
+ case "members":
+ return runGroupMembers(args[1:])
default:
return fmt.Errorf(
"unknown group subcommand: %s", args[0])
@@ -196,6 +198,54 @@ func runGroupRemoveMember(args []string) error {
return nil
}

+func runGroupMembers(args []string) error {
+ fs := flag.NewFlagSet(
+ "group members", flag.ContinueOnError)
+ asJSON := fs.Bool("json", false,
+ "output full DAG (Up/Down/Usernames) as raw JSON")
+ if err := fs.Parse(args); err != nil {
+ return err
+ }
+ positional := fs.Args()
+ if len(positional) != 1 {
+ return fmt.Errorf("usage: okg group members GROUP")
+ }
+ groupName := positional[0]
+
+ c, err := newWhoClient()
+ if err != nil {
+ return err
+ }
+ groupOwid, err := resolveGroupOwid(c, groupName)
+ if err != nil {
+ return err
+ }
+ ms, err := c.GroupMembers(groupOwid)
+ if err != nil {
+ return err
+ }
+
+ if *asJSON {
+ buf, err := json.MarshalIndent(ms, "", " ")
+ if err != nil {
+ return err
+ }
+ fmt.Println(string(buf))
+ return nil
+ }
+
+ // Direct members are the first level of Down. Print one
+ // username per line; deeper nesting (group-of-groups) is
+ // available via --json.
+ if len(ms.Down) == 0 {
+ return nil
+ }
+ for _, owid := range ms.Down[0] {
+ fmt.Println(ms.Usernames[owid])
+ }
+ return nil
+}
+
// resolveGroupOwid translates a group username to its owid via
// /groups/list. Returns an error if the group isn't visible to
// the caller.
diff --git a/main.go b/main.go
index 028166c..5936fb6 100644
--- a/main.go
+++ b/main.go
@@ -67,6 +67,9 @@ GROUPS

okg group remove-member GROUP USER [USER ...]

+ okg group members GROUP
+ --json full DAG (Up/Down/Usernames) as raw JSON
+
PULL REQUESTS
okg pr list
--state STATE open or closed (default: open)
@@ -134,6 +137,7 @@ EXAMPLES
okg group create chat-bots
okg group add-member chat-bots claude openclaw
okg group remove-member chat-bots openclaw
+ okg group members chat-bots

Pull requests
okg pr list --state open
a/README.md
b/README.md
1
# okg — Oscar Kilo Goodness
1
# okg — Oscar Kilo Goodness
2
2
3
A command-line tool that bundles Oscar Kilo's externally usable
3
A command-line tool that bundles Oscar Kilo's externally usable
4
services into one binary. Today: git ops against
4
services into one binary. Today: git ops against
5
[klee](https://code.oscarkilo.com), and LLM embeddings against
5
[klee](https://code.oscarkilo.com), and LLM embeddings against
6
[klex](https://oscarkilo.com/klex). More LLM utilities (`one`,
6
[klex](https://oscarkilo.com/klex). More LLM utilities (`one`,
7
`exemplary`) coming as the legacy `klex-git` binaries fold in.
7
`exemplary`) coming as the legacy `klex-git` binaries fold in.
8
8
9
Designed for both humans and AI agents (Claude, OpenClaw) to
9
Designed for both humans and AI agents (Claude, OpenClaw) to
10
use directly from the command line.
10
use directly from the command line.
11
11
12
## Install
12
## Install
13
13
14
```bash
14
```bash
15
go install oscarkilo.com/okg@latest
15
go install oscarkilo.com/okg@latest
16
```
16
```
17
17
18
Or build from source:
18
Or build from source:
19
19
20
```bash
20
```bash
21
git clone https://code.oscarkilo.com/okg
21
git clone https://code.oscarkilo.com/okg
22
cd okg && go build .
22
cd okg && go build .
23
```
23
```
24
24
25
## Setup
25
## Setup
26
26
27
By default okg talks to the production klee host
27
By default okg talks to the production klee host
28
(`https://code.oscarkilo.com`); the only setup you need is your
28
(`https://code.oscarkilo.com`); the only setup you need is your
29
API key. `okg auth login` saves it to
29
API key. `okg auth login` saves it to
30
`~/.config/okg/config.json`.
30
`~/.config/okg/config.json`.
31
31
32
```bash
32
```bash
33
# Non-interactive (for agents and scripts).
33
# Non-interactive (for agents and scripts).
34
okg auth login --key sk-...
34
okg auth login --key sk-...
35
cat ~/.klex.key | okg auth login # equivalent, ps-safe
35
cat ~/.klex.key | okg auth login # equivalent, ps-safe
36
36
37
# Interactive (prompts for host then key).
37
# Interactive (prompts for host then key).
38
okg auth login
38
okg auth login
39
```
39
```
40
40
41
Override the host for dev/local work with `--host` on `auth login`.
41
Override the host for dev/local work with `--host` on `auth login`.
42
42
43
## Commands
43
## Commands
44
44
45
```
45
```
46
okg repo list
46
okg repo list
47
47
48
okg pr list [--state open|closed]
48
okg pr list [--state open|closed]
49
okg pr create --head BRANCH [--base master] --title TITLE [--body BODY]
49
okg pr create --head BRANCH [--base master] --title TITLE [--body BODY]
50
okg pr view NUMBER
50
okg pr view NUMBER
51
okg pr diff NUMBER
51
okg pr diff NUMBER
52
okg pr comment NUMBER --body BODY [--approve | --request-changes]
52
okg pr comment NUMBER --body BODY [--approve | --request-changes]
53
okg pr merge NUMBER
53
okg pr merge NUMBER
54
okg pr close NUMBER
54
okg pr close NUMBER
55
okg pr reopen NUMBER
55
okg pr reopen NUMBER
56
56
57
okg auth login [--key KEY] [--host HOST]
57
okg auth login [--key KEY] [--host HOST]
58
58
59
okg group list [--json]
59
okg group list [--json]
60
okg group create NAME [--full-name TEXT] [--owner USER]
60
okg group create NAME [--full-name TEXT] [--owner USER]
61
okg group add-member GROUP USER [USER ...]
61
okg group add-member GROUP USER [USER ...]
62
okg group remove-member GROUP USER [USER ...]
62
okg group remove-member GROUP USER [USER ...]
63
okg group members GROUP [--json]
63
64
64
okg embed [--model NAME] [--dims N] [--full-path]
65
okg embed [--model NAME] [--dims N] [--full-path]
65
(reads stdin → vectors on stdout)
66
(reads stdin → vectors on stdout)
66
okg one [--model NAME] [--system-file FILE] \
67
okg one [--model NAME] [--system-file FILE] \
67
[--prompt-file FILE] [--attach FILE] \
68
[--prompt-file FILE] [--attach FILE] \
68
[--format text|json|jsonindent] [--fast-fail]
69
[--format text|json|jsonindent] [--fast-fail]
69
(reads stdin as a JSON MessagesRequest)
70
(reads stdin as a JSON MessagesRequest)
70
```
71
```
71
72
72
### Coming next
73
### Coming next
73
74
74
- `okg exemplary` — few-shot batch runner (from
75
- `okg exemplary` — few-shot batch runner (from
75
`klex-git/exemplary`).
76
`klex-git/exemplary`).
76
77
77
Once that lands, the standalone `klex-git` binaries are
78
Once that lands, the standalone `klex-git` binaries are
78
deprecated.
79
deprecated.
79
80
80
### Flags
81
### Flags
81
82
82
- `--repo REPO` overrides auto-detected repo name
83
- `--repo REPO` overrides auto-detected repo name
83
(normally parsed from `git remote get-url origin`)
84
(normally parsed from `git remote get-url origin`)
84
- `--json` outputs raw JSON for any command
85
- `--json` outputs raw JSON for any command
85
- `OKG_REPO` env var also overrides repo detection
86
- `OKG_REPO` env var also overrides repo detection
86
87
87
## Repo Detection
88
## Repo Detection
88
89
89
Like `gh`, okg detects the repo from the current directory's
90
Like `gh`, okg detects the repo from the current directory's
90
git remote:
91
git remote:
91
92
92
```
93
```
93
git remote get-url origin
94
git remote get-url origin
94
→ https://code.oscarkilo.com/widget.git
95
→ https://code.oscarkilo.com/widget.git
95
→ repo = "widget"
96
→ repo = "widget"
96
```
97
```
97
98
98
## Dependencies
99
## Dependencies
99
100
100
- `oscarkilo.com/klex-git` for the LLM API client (transitional;
101
- `oscarkilo.com/klex-git` for the LLM API client (transitional;
101
to be folded in once all `klex-git` binaries have moved here).
102
to be folded in once all `klex-git` binaries have moved here).
102
- Otherwise Go standard library only.
103
- Otherwise Go standard library only.
a/group.go
b/group.go
1
package main
1
package main
2
2
3
import "encoding/json"
3
import "encoding/json"
4
import "flag"
4
import "flag"
5
import "fmt"
5
import "fmt"
6
import "os"
6
import "os"
7
import "text/tabwriter"
7
import "text/tabwriter"
8
8
9
import "oscarkilo.com/okg/who"
9
import "oscarkilo.com/okg/who"
10
10
11
func runGroup(args []string) error {
11
func runGroup(args []string) error {
12
if len(args) == 0 {
12
if len(args) == 0 {
13
return fmt.Errorf(
13
return fmt.Errorf(
14
"usage: okg group SUBCOMMAND ... " +
14
"usage: okg group SUBCOMMAND ... " +
15
"(try `okg --help`)")
15
"(try `okg --help`)")
16
}
16
}
17
switch args[0] {
17
switch args[0] {
18
case "list":
18
case "list":
19
return runGroupList(args[1:])
19
return runGroupList(args[1:])
20
case "create":
20
case "create":
21
return runGroupCreate(args[1:])
21
return runGroupCreate(args[1:])
22
case "add-member":
22
case "add-member":
23
return runGroupAddMember(args[1:])
23
return runGroupAddMember(args[1:])
24
case "remove-member":
24
case "remove-member":
25
return runGroupRemoveMember(args[1:])
25
return runGroupRemoveMember(args[1:])
26
case "members":
27
return runGroupMembers(args[1:])
26
default:
28
default:
27
return fmt.Errorf(
29
return fmt.Errorf(
28
"unknown group subcommand: %s", args[0])
30
"unknown group subcommand: %s", args[0])
29
}
31
}
30
}
32
}
31
33
32
func runGroupList(args []string) error {
34
func runGroupList(args []string) error {
33
fs := flag.NewFlagSet("group list", flag.ContinueOnError)
35
fs := flag.NewFlagSet("group list", flag.ContinueOnError)
34
asJSON := fs.Bool("json", false, "output raw JSON")
36
asJSON := fs.Bool("json", false, "output raw JSON")
35
if err := fs.Parse(args); err != nil {
37
if err := fs.Parse(args); err != nil {
36
return err
38
return err
37
}
39
}
38
40
39
c, err := newWhoClient()
41
c, err := newWhoClient()
40
if err != nil {
42
if err != nil {
41
return err
43
return err
42
}
44
}
43
45
44
groups, err := c.ListGroups()
46
groups, err := c.ListGroups()
45
if err != nil {
47
if err != nil {
46
return err
48
return err
47
}
49
}
48
50
49
if *asJSON {
51
if *asJSON {
50
buf, err := json.MarshalIndent(groups, "", " ")
52
buf, err := json.MarshalIndent(groups, "", " ")
51
if err != nil {
53
if err != nil {
52
return err
54
return err
53
}
55
}
54
fmt.Println(string(buf))
56
fmt.Println(string(buf))
55
return nil
57
return nil
56
}
58
}
57
59
58
tw := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
60
tw := tabwriter.NewWriter(os.Stdout, 0, 2, 2, ' ', 0)
59
fmt.Fprintln(tw, "USERNAME\tFULL NAME\tOWNER")
61
fmt.Fprintln(tw, "USERNAME\tFULL NAME\tOWNER")
60
for _, g := range groups {
62
for _, g := range groups {
61
fmt.Fprintf(tw, "%s\t%s\t%s\n",
63
fmt.Fprintf(tw, "%s\t%s\t%s\n",
62
g.Username, g.Name, g.OwnerUsername)
64
g.Username, g.Name, g.OwnerUsername)
63
}
65
}
64
return tw.Flush()
66
return tw.Flush()
65
}
67
}
66
68
67
func runGroupCreate(args []string) error {
69
func runGroupCreate(args []string) error {
68
fs := flag.NewFlagSet(
70
fs := flag.NewFlagSet(
69
"group create", flag.ContinueOnError)
71
"group create", flag.ContinueOnError)
70
fullName := fs.String("full-name", "",
72
fullName := fs.String("full-name", "",
71
"display name (default: NAME)")
73
"display name (default: NAME)")
72
owner := fs.String("owner", "",
74
owner := fs.String("owner", "",
73
"owner username (default: caller)")
75
"owner username (default: caller)")
74
if err := fs.Parse(args); err != nil {
76
if err := fs.Parse(args); err != nil {
75
return err
77
return err
76
}
78
}
77
positional := fs.Args()
79
positional := fs.Args()
78
if len(positional) != 1 {
80
if len(positional) != 1 {
79
return fmt.Errorf("usage: okg group create NAME")
81
return fmt.Errorf("usage: okg group create NAME")
80
}
82
}
81
name := positional[0]
83
name := positional[0]
82
84
83
c, err := newWhoClient()
85
c, err := newWhoClient()
84
if err != nil {
86
if err != nil {
85
return err
87
return err
86
}
88
}
87
89
88
if *owner == "" {
90
if *owner == "" {
89
me, err := c.GetProfile()
91
me, err := c.GetProfile()
90
if err != nil {
92
if err != nil {
91
return fmt.Errorf("resolve caller: %v", err)
93
return fmt.Errorf("resolve caller: %v", err)
92
}
94
}
93
*owner = me.Username
95
*owner = me.Username
94
}
96
}
95
97
96
displayName := *fullName
98
displayName := *fullName
97
if displayName == "" {
99
if displayName == "" {
98
displayName = name
100
displayName = name
99
}
101
}
100
102
101
if err := c.CreateGroup(who.CreateGroupRequest{
103
if err := c.CreateGroup(who.CreateGroupRequest{
102
Username: name,
104
Username: name,
103
Name: displayName,
105
Name: displayName,
104
OwnerUsername: *owner,
106
OwnerUsername: *owner,
105
}); err != nil {
107
}); err != nil {
106
return err
108
return err
107
}
109
}
108
fmt.Printf(
110
fmt.Printf(
109
"Created group %s (owner: %s)\n", name, *owner)
111
"Created group %s (owner: %s)\n", name, *owner)
110
return nil
112
return nil
111
}
113
}
112
114
113
func runGroupAddMember(args []string) error {
115
func runGroupAddMember(args []string) error {
114
fs := flag.NewFlagSet(
116
fs := flag.NewFlagSet(
115
"group add-member", flag.ContinueOnError)
117
"group add-member", flag.ContinueOnError)
116
if err := fs.Parse(args); err != nil {
118
if err := fs.Parse(args); err != nil {
117
return err
119
return err
118
}
120
}
119
positional := fs.Args()
121
positional := fs.Args()
120
if len(positional) < 2 {
122
if len(positional) < 2 {
121
return fmt.Errorf(
123
return fmt.Errorf(
122
"usage: okg group add-member GROUP USER [USER ...]")
124
"usage: okg group add-member GROUP USER [USER ...]")
123
}
125
}
124
group := positional[0]
126
group := positional[0]
125
members := positional[1:]
127
members := positional[1:]
126
128
127
c, err := newWhoClient()
129
c, err := newWhoClient()
128
if err != nil {
130
if err != nil {
129
return err
131
return err
130
}
132
}
131
if err := c.JoinGroups(who.JoinGroupsRequest{
133
if err := c.JoinGroups(who.JoinGroupsRequest{
132
GroupUsernames: []string{group},
134
GroupUsernames: []string{group},
133
MemberUsernames: members,
135
MemberUsernames: members,
134
}); err != nil {
136
}); err != nil {
135
return err
137
return err
136
}
138
}
137
fmt.Printf(
139
fmt.Printf(
138
"Added %d member(s) to %s\n", len(members), group)
140
"Added %d member(s) to %s\n", len(members), group)
139
return nil
141
return nil
140
}
142
}
141
143
142
func runGroupRemoveMember(args []string) error {
144
func runGroupRemoveMember(args []string) error {
143
fs := flag.NewFlagSet(
145
fs := flag.NewFlagSet(
144
"group remove-member", flag.ContinueOnError)
146
"group remove-member", flag.ContinueOnError)
145
if err := fs.Parse(args); err != nil {
147
if err := fs.Parse(args); err != nil {
146
return err
148
return err
147
}
149
}
148
positional := fs.Args()
150
positional := fs.Args()
149
if len(positional) < 2 {
151
if len(positional) < 2 {
150
return fmt.Errorf(
152
return fmt.Errorf(
151
"usage: okg group remove-member " +
153
"usage: okg group remove-member " +
152
"GROUP USER [USER ...]")
154
"GROUP USER [USER ...]")
153
}
155
}
154
groupName := positional[0]
156
groupName := positional[0]
155
members := positional[1:]
157
members := positional[1:]
156
158
157
c, err := newWhoClient()
159
c, err := newWhoClient()
158
if err != nil {
160
if err != nil {
159
return err
161
return err
160
}
162
}
161
163
162
// /groups/leave wants owids, not usernames. Resolve the
164
// /groups/leave wants owids, not usernames. Resolve the
163
// group's owid via ListGroups, then the members' owids via
165
// group's owid via ListGroups, then the members' owids via
164
// GroupMembers. Two round-trips on top of the actual leave
166
// GroupMembers. Two round-trips on top of the actual leave
165
// calls; shared across all members in this invocation.
167
// calls; shared across all members in this invocation.
166
groupOwid, err := resolveGroupOwid(c, groupName)
168
groupOwid, err := resolveGroupOwid(c, groupName)
167
if err != nil {
169
if err != nil {
168
return err
170
return err
169
}
171
}
170
ms, err := c.GroupMembers(groupOwid)
172
ms, err := c.GroupMembers(groupOwid)
171
if err != nil {
173
if err != nil {
172
return err
174
return err
173
}
175
}
174
username2owid := make(map[string]string)
176
username2owid := make(map[string]string)
175
for owid, name := range ms.Usernames {
177
for owid, name := range ms.Usernames {
176
username2owid[name] = owid
178
username2owid[name] = owid
177
}
179
}
178
180
179
for _, m := range members {
181
for _, m := range members {
180
memberOwid, ok := username2owid[m]
182
memberOwid, ok := username2owid[m]
181
if !ok {
183
if !ok {
182
return fmt.Errorf(
184
return fmt.Errorf(
183
"user %q is not a member of %s", m, groupName)
185
"user %q is not a member of %s", m, groupName)
184
}
186
}
185
if err := c.LeaveGroup(who.LeaveGroupRequest{
187
if err := c.LeaveGroup(who.LeaveGroupRequest{
186
GroupOwid: groupOwid,
188
GroupOwid: groupOwid,
187
MemberOwid: memberOwid,
189
MemberOwid: memberOwid,
188
}); err != nil {
190
}); err != nil {
189
return fmt.Errorf(
191
return fmt.Errorf(
190
"remove %s from %s: %v", m, groupName, err)
192
"remove %s from %s: %v", m, groupName, err)
191
}
193
}
192
}
194
}
193
fmt.Printf(
195
fmt.Printf(
194
"Removed %d member(s) from %s\n",
196
"Removed %d member(s) from %s\n",
195
len(members), groupName)
197
len(members), groupName)
196
return nil
198
return nil
197
}
199
}
198
200
201
func runGroupMembers(args []string) error {
202
fs := flag.NewFlagSet(
203
"group members", flag.ContinueOnError)
204
asJSON := fs.Bool("json", false,
205
"output full DAG (Up/Down/Usernames) as raw JSON")
206
if err := fs.Parse(args); err != nil {
207
return err
208
}
209
positional := fs.Args()
210
if len(positional) != 1 {
211
return fmt.Errorf("usage: okg group members GROUP")
212
}
213
groupName := positional[0]
214
215
c, err := newWhoClient()
216
if err != nil {
217
return err
218
}
219
groupOwid, err := resolveGroupOwid(c, groupName)
220
if err != nil {
221
return err
222
}
223
ms, err := c.GroupMembers(groupOwid)
224
if err != nil {
225
return err
226
}
227
228
if *asJSON {
229
buf, err := json.MarshalIndent(ms, "", " ")
230
if err != nil {
231
return err
232
}
233
fmt.Println(string(buf))
234
return nil
235
}
236
237
// Direct members are the first level of Down. Print one
238
// username per line; deeper nesting (group-of-groups) is
239
// available via --json.
240
if len(ms.Down) == 0 {
241
return nil
242
}
243
for _, owid := range ms.Down[0] {
244
fmt.Println(ms.Usernames[owid])
245
}
246
return nil
247
}
248
199
// resolveGroupOwid translates a group username to its owid via
249
// resolveGroupOwid translates a group username to its owid via
200
// /groups/list. Returns an error if the group isn't visible to
250
// /groups/list. Returns an error if the group isn't visible to
201
// the caller.
251
// the caller.
202
func resolveGroupOwid(
252
func resolveGroupOwid(
203
c *who.HTTPClient, name string,
253
c *who.HTTPClient, name string,
204
) (string, error) {
254
) (string, error) {
205
groups, err := c.ListGroups()
255
groups, err := c.ListGroups()
206
if err != nil {
256
if err != nil {
207
return "", err
257
return "", err
208
}
258
}
209
for _, g := range groups {
259
for _, g := range groups {
210
if g.Username == name {
260
if g.Username == name {
211
return g.Owid, nil
261
return g.Owid, nil
212
}
262
}
213
}
263
}
214
return "", fmt.Errorf(
264
return "", fmt.Errorf(
215
"group %q not found (or not visible to caller)", name)
265
"group %q not found (or not visible to caller)", name)
216
}
266
}
217
267
218
// newWhoClient builds a //who client from saved config. Shared
268
// newWhoClient builds a //who client from saved config. Shared
219
// by every `okg group` subcommand.
269
// by every `okg group` subcommand.
220
func newWhoClient() (*who.HTTPClient, error) {
270
func newWhoClient() (*who.HTTPClient, error) {
221
cfg, err := loadConfig()
271
cfg, err := loadConfig()
222
if err != nil {
272
if err != nil {
223
return nil, err
273
return nil, err
224
}
274
}
225
if cfg.ApiKey == "" {
275
if cfg.ApiKey == "" {
226
return nil, fmt.Errorf(
276
return nil, fmt.Errorf(
227
"no API key — run `okg auth login --key sk-...`")
277
"no API key — run `okg auth login --key sk-...`")
228
}
278
}
229
return who.NewHTTPClient(cfg.Host, cfg.ApiKey), nil
279
return who.NewHTTPClient(cfg.Host, cfg.ApiKey), nil
230
}
280
}
a/main.go
b/main.go
1
package main
1
package main
2
2
3
import "fmt"
3
import "fmt"
4
import "os"
4
import "os"
5
5
6
func main() {
6
func main() {
7
args := os.Args[1:]
7
args := os.Args[1:]
8
if len(args) == 0 {
8
if len(args) == 0 {
9
printUsage()
9
printUsage()
10
os.Exit(1)
10
os.Exit(1)
11
}
11
}
12
12
13
var err error
13
var err error
14
switch args[0] {
14
switch args[0] {
15
case "pr":
15
case "pr":
16
err = runPR(args[1:])
16
err = runPR(args[1:])
17
case "repo":
17
case "repo":
18
err = runRepo(args[1:])
18
err = runRepo(args[1:])
19
case "auth":
19
case "auth":
20
err = runAuth(args[1:])
20
err = runAuth(args[1:])
21
case "embed":
21
case "embed":
22
err = runEmbed(args[1:])
22
err = runEmbed(args[1:])
23
case "one":
23
case "one":
24
err = runOne(args[1:])
24
err = runOne(args[1:])
25
case "group":
25
case "group":
26
err = runGroup(args[1:])
26
err = runGroup(args[1:])
27
case "help", "--help", "-h":
27
case "help", "--help", "-h":
28
printUsage()
28
printUsage()
29
return
29
return
30
default:
30
default:
31
fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0])
31
fmt.Fprintf(os.Stderr, "unknown command: %s\n", args[0])
32
printUsage()
32
printUsage()
33
os.Exit(1)
33
os.Exit(1)
34
}
34
}
35
35
36
if err != nil {
36
if err != nil {
37
fmt.Fprintf(os.Stderr, "error: %v\n", err)
37
fmt.Fprintf(os.Stderr, "error: %v\n", err)
38
os.Exit(1)
38
os.Exit(1)
39
}
39
}
40
}
40
}
41
41
42
func printUsage() {
42
func printUsage() {
43
fmt.Fprintf(os.Stderr, `NAME
43
fmt.Fprintf(os.Stderr, `NAME
44
okg — Oscar Kilo Goodness
44
okg — Oscar Kilo Goodness
45
45
46
SETUP
46
SETUP
47
okg auth login
47
okg auth login
48
--key KEY API key (also accepted via stdin)
48
--key KEY API key (also accepted via stdin)
49
--host HOST klee host (default: production)
49
--host HOST klee host (default: production)
50
50
51
GIT REPOS
51
GIT REPOS
52
okg repo list
52
okg repo list
53
--json output raw JSON
53
--json output raw JSON
54
54
55
okg repo create NAME
55
okg repo create NAME
56
--reader USER grant read to USER (default: anyone)
56
--reader USER grant read to USER (default: anyone)
57
57
58
GROUPS
58
GROUPS
59
okg group list
59
okg group list
60
--json output raw JSON
60
--json output raw JSON
61
61
62
okg group create NAME
62
okg group create NAME
63
--full-name TEXT display name (default: NAME)
63
--full-name TEXT display name (default: NAME)
64
--owner USER owner username (default: caller)
64
--owner USER owner username (default: caller)
65
65
66
okg group add-member GROUP USER [USER ...]
66
okg group add-member GROUP USER [USER ...]
67
67
68
okg group remove-member GROUP USER [USER ...]
68
okg group remove-member GROUP USER [USER ...]
69
69
70
okg group members GROUP
71
--json full DAG (Up/Down/Usernames) as raw JSON
72
70
PULL REQUESTS
73
PULL REQUESTS
71
okg pr list
74
okg pr list
72
--state STATE open or closed (default: open)
75
--state STATE open or closed (default: open)
73
--json output raw JSON
76
--json output raw JSON
74
77
75
okg pr create
78
okg pr create
76
--head BRANCH source branch
79
--head BRANCH source branch
77
--base BRANCH target branch (default: master)
80
--base BRANCH target branch (default: master)
78
--title TITLE PR title
81
--title TITLE PR title
79
--body BODY PR body (optional)
82
--body BODY PR body (optional)
80
--json output raw JSON
83
--json output raw JSON
81
84
82
okg pr view NUMBER
85
okg pr view NUMBER
83
--json output raw JSON
86
--json output raw JSON
84
87
85
okg pr diff NUMBER
88
okg pr diff NUMBER
86
89
87
okg pr comment NUMBER
90
okg pr comment NUMBER
88
--body BODY comment body
91
--body BODY comment body
89
--approve also approve the PR
92
--approve also approve the PR
90
--request-changes also request changes
93
--request-changes also request changes
91
94
92
okg pr merge NUMBER
95
okg pr merge NUMBER
93
--json output raw JSON
96
--json output raw JSON
94
97
95
okg pr close NUMBER
98
okg pr close NUMBER
96
--json output raw JSON
99
--json output raw JSON
97
100
98
okg pr reopen NUMBER
101
okg pr reopen NUMBER
99
--json output raw JSON
102
--json output raw JSON
100
103
101
ARTIFICIAL INTELLIGENCE
104
ARTIFICIAL INTELLIGENCE
102
okg embed
105
okg embed
103
--model NAME embedding model
106
--model NAME embedding model
104
(default: openai:text-embedding-3-small)
107
(default: openai:text-embedding-3-small)
105
--dims N number of dimensions (default: 1536)
108
--dims N number of dimensions (default: 1536)
106
--full-path one vector per prefix of input
109
--full-path one vector per prefix of input
107
(reads stdin; writes vectors to stdout, one per line)
110
(reads stdin; writes vectors to stdout, one per line)
108
111
109
okg one
112
okg one
110
--model NAME override .Model in the request
113
--model NAME override .Model in the request
111
--system-file FILE override .System with contents of FILE
114
--system-file FILE override .System with contents of FILE
112
--prompt-file FILE append FILE as a user prompt
115
--prompt-file FILE append FILE as a user prompt
113
--attach FILE attach an image or PDF to the prompt
116
--attach FILE attach an image or PDF to the prompt
114
--format FORMAT text | json | jsonindent (default: text)
117
--format FORMAT text | json | jsonindent (default: text)
115
--fast-fail preflight attachment MIME (default: on)
118
--fast-fail preflight attachment MIME (default: on)
116
(reads stdin as a JSON MessagesRequest; flags override
119
(reads stdin as a JSON MessagesRequest; flags override
117
its fields)
120
its fields)
118
121
119
GLOBAL FLAGS
122
GLOBAL FLAGS
120
--repo REPO override auto-detected repo name
123
--repo REPO override auto-detected repo name
121
--json output raw JSON (where applicable)
124
--json output raw JSON (where applicable)
122
125
123
EXAMPLES
126
EXAMPLES
124
Setup
127
Setup
125
okg auth login --key sk-...
128
okg auth login --key sk-...
126
cat ~/.klex.key | okg auth login
129
cat ~/.klex.key | okg auth login
127
130
128
Git repos
131
Git repos
129
okg repo list
132
okg repo list
130
okg repo create my-new-repo
133
okg repo create my-new-repo
131
134
132
Groups
135
Groups
133
okg group list
136
okg group list
134
okg group create chat-bots
137
okg group create chat-bots
135
okg group add-member chat-bots claude openclaw
138
okg group add-member chat-bots claude openclaw
136
okg group remove-member chat-bots openclaw
139
okg group remove-member chat-bots openclaw
140
okg group members chat-bots
137
141
138
Pull requests
142
Pull requests
139
okg pr list --state open
143
okg pr list --state open
140
okg pr view 42
144
okg pr view 42
141
okg pr comment 42 --body 'LGTM' --approve
145
okg pr comment 42 --body 'LGTM' --approve
142
146
143
Artificial intelligence
147
Artificial intelligence
144
echo 'hello world' | okg embed --dims 384
148
echo 'hello world' | okg embed --dims 384
145
echo Hello? > /tmp/q.txt && \
149
echo Hello? > /tmp/q.txt && \
146
okg one --model openai:gpt-4o-mini --prompt-file /tmp/q.txt
150
okg one --model openai:gpt-4o-mini --prompt-file /tmp/q.txt
147
`)
151
`)
148
}
152
}