@gobi-ai/cli 0.3.1 → 0.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -30
- package/dist/commands/astra.js +103 -459
- package/dist/commands/brain.js +232 -0
- package/dist/commands/init.js +3 -3
- package/dist/commands/sessions.js +140 -0
- package/dist/commands/utils.js +19 -0
- package/dist/main.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,13 +38,13 @@ npm link
|
|
|
38
38
|
gobi init
|
|
39
39
|
|
|
40
40
|
# Select a space
|
|
41
|
-
gobi
|
|
41
|
+
gobi space warp
|
|
42
42
|
|
|
43
|
-
# Search brains
|
|
44
|
-
gobi
|
|
43
|
+
# Search brains across your spaces
|
|
44
|
+
gobi brain search --query "machine learning"
|
|
45
45
|
|
|
46
46
|
# Ask a brain a question
|
|
47
|
-
gobi
|
|
47
|
+
gobi brain ask --vault-slug my-vault --space-slug my-space --question "What is RAG?"
|
|
48
48
|
```
|
|
49
49
|
|
|
50
50
|
## Commands
|
|
@@ -62,59 +62,61 @@ gobi astra ask-brain --vault-slug my-vault --question "What is RAG?"
|
|
|
62
62
|
| Command | Description |
|
|
63
63
|
|---------|-------------|
|
|
64
64
|
| `gobi init` | Log in (if needed) and select or create a vault |
|
|
65
|
-
| `gobi
|
|
65
|
+
| `gobi space warp` | Select the active space |
|
|
66
66
|
|
|
67
67
|
### Brains
|
|
68
68
|
|
|
69
69
|
| Command | Description |
|
|
70
70
|
|---------|-------------|
|
|
71
|
-
| `gobi
|
|
72
|
-
| `gobi
|
|
73
|
-
| `gobi
|
|
74
|
-
| `gobi
|
|
71
|
+
| `gobi brain search --query <q>` | Search brains across all your spaces |
|
|
72
|
+
| `gobi brain ask --vault-slug <slug> --space-slug <slug> --question <q>` | Ask a brain a question (creates a 1:1 session) |
|
|
73
|
+
| `gobi brain publish` | Upload `BRAIN.md` to your vault |
|
|
74
|
+
| `gobi brain unpublish` | Remove `BRAIN.md` from your vault |
|
|
75
75
|
|
|
76
|
-
###
|
|
76
|
+
### Brain Updates
|
|
77
77
|
|
|
78
78
|
| Command | Description |
|
|
79
79
|
|---------|-------------|
|
|
80
|
-
| `gobi
|
|
81
|
-
| `gobi
|
|
82
|
-
| `gobi
|
|
83
|
-
| `gobi
|
|
84
|
-
| `gobi
|
|
80
|
+
| `gobi brain list-updates` | List brain updates for your vault |
|
|
81
|
+
| `gobi brain list-updates --mine` | List only your own brain updates |
|
|
82
|
+
| `gobi brain post-update --title <t> --content <c>` | Post a brain update |
|
|
83
|
+
| `gobi brain edit-update <id> --title <t>` | Edit a brain update |
|
|
84
|
+
| `gobi brain delete-update <id>` | Delete a brain update |
|
|
85
85
|
|
|
86
|
-
###
|
|
86
|
+
### Threads
|
|
87
87
|
|
|
88
88
|
| Command | Description |
|
|
89
89
|
|---------|-------------|
|
|
90
|
-
| `gobi
|
|
91
|
-
| `gobi
|
|
92
|
-
| `gobi
|
|
90
|
+
| `gobi space list-threads` | List threads in the current space |
|
|
91
|
+
| `gobi space get-thread <id>` | Get a thread and its replies |
|
|
92
|
+
| `gobi space create-thread --title <t> --content <c>` | Create a thread |
|
|
93
|
+
| `gobi space edit-thread <id> --title <t>` | Edit a thread |
|
|
94
|
+
| `gobi space delete-thread <id>` | Delete a thread |
|
|
93
95
|
|
|
94
|
-
###
|
|
96
|
+
### Replies
|
|
95
97
|
|
|
96
98
|
| Command | Description |
|
|
97
99
|
|---------|-------------|
|
|
98
|
-
| `gobi
|
|
99
|
-
| `gobi
|
|
100
|
-
| `gobi
|
|
101
|
-
| `gobi astra update-session <id> --mode <mode>` | Set session mode (auto/manual) |
|
|
100
|
+
| `gobi space create-reply <threadId> --content <c>` | Reply to a thread |
|
|
101
|
+
| `gobi space edit-reply <replyId> --content <c>` | Edit a reply |
|
|
102
|
+
| `gobi space delete-reply <replyId>` | Delete a reply |
|
|
102
103
|
|
|
103
|
-
###
|
|
104
|
+
### Sessions
|
|
104
105
|
|
|
105
106
|
| Command | Description |
|
|
106
107
|
|---------|-------------|
|
|
107
|
-
| `gobi
|
|
108
|
-
| `gobi
|
|
109
|
-
| `gobi
|
|
110
|
-
| `gobi
|
|
108
|
+
| `gobi session list` | List your sessions |
|
|
109
|
+
| `gobi session get <id>` | Get a session and its messages |
|
|
110
|
+
| `gobi session reply <id> --content <c>` | Send a message in a session |
|
|
111
|
+
| `gobi session update <id> --mode <mode>` | Set session mode (auto/manual) |
|
|
111
112
|
|
|
112
113
|
### Global options
|
|
113
114
|
|
|
114
115
|
| Option | Description |
|
|
115
116
|
|--------|-------------|
|
|
116
117
|
| `--json` | Output results as JSON |
|
|
117
|
-
| `--space-slug <slug>` | Override the default space (
|
|
118
|
+
| `--space-slug <slug>` | Override the default space (on `space` commands); required on `brain ask`, optional filter on `session list` |
|
|
119
|
+
| `--vault-slug <slug>` | Override the default vault (on `brain list-updates` and `brain post-update`) |
|
|
118
120
|
|
|
119
121
|
## Configuration
|
|
120
122
|
|
package/dist/commands/astra.js
CHANGED
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
import { existsSync, readFileSync } from "fs";
|
|
2
|
-
import { join } from "path";
|
|
3
1
|
import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { getSpaceSlug, getVaultSlug, selectSpace, writeSpaceSetting } from "./init.js";
|
|
7
|
-
function isJsonMode(cmd) {
|
|
8
|
-
return !!cmd.parent?.opts().json;
|
|
9
|
-
}
|
|
10
|
-
function jsonOut(data) {
|
|
11
|
-
console.log(JSON.stringify({ success: true, data }));
|
|
12
|
-
}
|
|
13
|
-
function resolveSpaceSlug(cmd) {
|
|
14
|
-
return cmd.opts().spaceSlug || getSpaceSlug();
|
|
15
|
-
}
|
|
16
|
-
function resolveVaultSlug(opts) {
|
|
17
|
-
return opts.vaultSlug || getVaultSlug();
|
|
18
|
-
}
|
|
19
|
-
function unwrapResp(resp) {
|
|
20
|
-
if (typeof resp === "object" && resp !== null && "data" in resp) {
|
|
21
|
-
return resp.data;
|
|
22
|
-
}
|
|
23
|
-
return resp;
|
|
24
|
-
}
|
|
2
|
+
import { selectSpace, writeSpaceSetting } from "./init.js";
|
|
3
|
+
import { isJsonMode, jsonOut, resolveSpaceSlug, unwrapResp } from "./utils.js";
|
|
25
4
|
export function registerAstraCommand(program) {
|
|
26
|
-
const
|
|
27
|
-
.command("
|
|
28
|
-
.description("
|
|
5
|
+
const space = program
|
|
6
|
+
.command("space")
|
|
7
|
+
.description("Space commands (threads, replies).")
|
|
29
8
|
.option("--space-slug <slug>", "Space slug (overrides .gobi/settings.yaml)");
|
|
30
9
|
// ── Warp (space selection) ──
|
|
31
|
-
|
|
10
|
+
space
|
|
32
11
|
.command("warp")
|
|
33
|
-
.description("Select the active space
|
|
12
|
+
.description("Select the active space.")
|
|
34
13
|
.action(async () => {
|
|
35
14
|
const result = await selectSpace();
|
|
36
15
|
if (result === null) {
|
|
@@ -38,146 +17,36 @@ export function registerAstraCommand(program) {
|
|
|
38
17
|
return;
|
|
39
18
|
}
|
|
40
19
|
writeSpaceSetting(result.slug);
|
|
41
|
-
if (isJsonMode(
|
|
20
|
+
if (isJsonMode(space)) {
|
|
42
21
|
jsonOut({ spaceSlug: result.slug, spaceName: result.name });
|
|
43
22
|
return;
|
|
44
23
|
}
|
|
45
24
|
console.log(`Warped to space "${result.name}" (${result.slug})`);
|
|
46
25
|
});
|
|
47
|
-
// ──
|
|
48
|
-
|
|
49
|
-
.command("
|
|
50
|
-
.description("
|
|
51
|
-
.requiredOption("--query <query>", "Search query")
|
|
52
|
-
.action(async (opts) => {
|
|
53
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
54
|
-
const resp = (await apiGet(`/spaces/${spaceSlug}/brains`, {
|
|
55
|
-
query: opts.query,
|
|
56
|
-
}));
|
|
57
|
-
const results = (Array.isArray(resp) ? resp : resp.data || resp);
|
|
58
|
-
if (isJsonMode(astra)) {
|
|
59
|
-
jsonOut(results || []);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
if (!results || results.length === 0) {
|
|
63
|
-
console.log(`No brains found matching "${opts.query}".`);
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
const lines = [];
|
|
67
|
-
for (const entry of results) {
|
|
68
|
-
const vault = (entry.vault || entry);
|
|
69
|
-
const owner = (entry.owner || {});
|
|
70
|
-
const ownerName = owner.name ? ` by ${owner.name}` : "";
|
|
71
|
-
const sim = entry.similarity != null
|
|
72
|
-
? ` [similarity: ${entry.similarity.toFixed(3)}]`
|
|
73
|
-
: "";
|
|
74
|
-
lines.push(`- ${vault.name || vault.title || "N/A"} (ID: ${vault.vaultId || vault.id || "N/A"})${ownerName}${sim}`);
|
|
75
|
-
}
|
|
76
|
-
console.log(`Brains matching "${opts.query}":\n` + lines.join("\n"));
|
|
77
|
-
});
|
|
78
|
-
astra
|
|
79
|
-
.command("ask-brain")
|
|
80
|
-
.description("Ask a brain a question. Creates a targeted session (1:1 conversation).")
|
|
81
|
-
.requiredOption("--vault-slug <vaultSlug>", "Slug of the brain/vault to ask")
|
|
82
|
-
.requiredOption("--question <question>", "The question to ask (markdown supported)")
|
|
83
|
-
.option("--mode <mode>", 'Session mode: "auto" or "manual"')
|
|
84
|
-
.action(async (opts) => {
|
|
85
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
86
|
-
const body = {
|
|
87
|
-
vaultSlug: opts.vaultSlug,
|
|
88
|
-
spaceSlug,
|
|
89
|
-
question: opts.question,
|
|
90
|
-
};
|
|
91
|
-
if (opts.mode != null)
|
|
92
|
-
body.mode = opts.mode;
|
|
93
|
-
const resp = (await apiPost(`/session/targeted`, body));
|
|
94
|
-
const data = unwrapResp(resp);
|
|
95
|
-
if (isJsonMode(astra)) {
|
|
96
|
-
jsonOut(data);
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const session = (data.session || {});
|
|
100
|
-
const members = (data.members || []);
|
|
101
|
-
console.log(`Session created!\n` +
|
|
102
|
-
` Session ID: ${session.id}\n` +
|
|
103
|
-
` Mode: ${session.mode}\n` +
|
|
104
|
-
` Members: ${members.length}\n` +
|
|
105
|
-
` Question sent.`);
|
|
106
|
-
});
|
|
107
|
-
astra
|
|
108
|
-
.command("publish-brain")
|
|
109
|
-
.description("Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (brain sync, metadata update, Discord notification).")
|
|
110
|
-
.action(async () => {
|
|
111
|
-
const vaultId = getVaultSlug();
|
|
112
|
-
const filePath = join(process.cwd(), "BRAIN.md");
|
|
113
|
-
if (!existsSync(filePath)) {
|
|
114
|
-
throw new Error(`BRAIN.md not found in ${process.cwd()}`);
|
|
115
|
-
}
|
|
116
|
-
const content = readFileSync(filePath, "utf-8");
|
|
117
|
-
const token = await getValidToken();
|
|
118
|
-
const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/files/BRAIN.md`;
|
|
119
|
-
const res = await fetch(url, {
|
|
120
|
-
method: "PUT",
|
|
121
|
-
headers: {
|
|
122
|
-
Authorization: `Bearer ${token}`,
|
|
123
|
-
"Content-Type": "text/markdown",
|
|
124
|
-
},
|
|
125
|
-
body: content,
|
|
126
|
-
});
|
|
127
|
-
if (!res.ok) {
|
|
128
|
-
throw new Error(`Upload failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
|
|
129
|
-
}
|
|
130
|
-
if (isJsonMode(astra)) {
|
|
131
|
-
jsonOut({ vaultId });
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
console.log(`Published BRAIN.md to vault "${vaultId}"`);
|
|
135
|
-
});
|
|
136
|
-
astra
|
|
137
|
-
.command("unpublish-brain")
|
|
138
|
-
.description("Delete BRAIN.md from the vault on webdrive.")
|
|
139
|
-
.action(async () => {
|
|
140
|
-
const vaultId = getVaultSlug();
|
|
141
|
-
const token = await getValidToken();
|
|
142
|
-
const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/files/BRAIN.md`;
|
|
143
|
-
const res = await fetch(url, {
|
|
144
|
-
method: "DELETE",
|
|
145
|
-
headers: { Authorization: `Bearer ${token}` },
|
|
146
|
-
});
|
|
147
|
-
if (!res.ok) {
|
|
148
|
-
throw new Error(`Delete failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
|
|
149
|
-
}
|
|
150
|
-
if (isJsonMode(astra)) {
|
|
151
|
-
jsonOut({ vaultId });
|
|
152
|
-
return;
|
|
153
|
-
}
|
|
154
|
-
console.log(`Deleted BRAIN.md from vault "${vaultId}"`);
|
|
155
|
-
});
|
|
156
|
-
// ── Posts (get, list, create, edit, delete) ──
|
|
157
|
-
astra
|
|
158
|
-
.command("get-post <postId>")
|
|
159
|
-
.description("Get a post and its replies (paginated).")
|
|
26
|
+
// ── Threads (get, list, create, edit, delete) ──
|
|
27
|
+
space
|
|
28
|
+
.command("get-thread <threadId>")
|
|
29
|
+
.description("Get a thread and its replies (paginated).")
|
|
160
30
|
.option("--limit <number>", "Replies per page", "20")
|
|
161
|
-
.option("--
|
|
162
|
-
.action(async (
|
|
163
|
-
const spaceSlug = resolveSpaceSlug(
|
|
164
|
-
const
|
|
31
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
32
|
+
.action(async (threadId, opts) => {
|
|
33
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
34
|
+
const params = {
|
|
165
35
|
limit: parseInt(opts.limit, 10),
|
|
166
|
-
|
|
167
|
-
|
|
36
|
+
};
|
|
37
|
+
if (opts.cursor)
|
|
38
|
+
params.cursor = opts.cursor;
|
|
39
|
+
const resp = (await apiGet(`/spaces/${spaceSlug}/threads/${threadId}`, params));
|
|
168
40
|
const data = unwrapResp(resp);
|
|
169
41
|
const pagination = (resp.pagination || {});
|
|
170
|
-
if (isJsonMode(
|
|
42
|
+
if (isJsonMode(space)) {
|
|
171
43
|
jsonOut({ ...data, pagination });
|
|
172
44
|
return;
|
|
173
45
|
}
|
|
174
|
-
const
|
|
46
|
+
const thread = (data.thread || data);
|
|
175
47
|
const replies = (data.items || []);
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
0;
|
|
179
|
-
const author = msg.author?.name ||
|
|
180
|
-
`User ${msg.authorId}`;
|
|
48
|
+
const author = thread.author?.name ||
|
|
49
|
+
`User ${thread.authorId}`;
|
|
181
50
|
const replyLines = [];
|
|
182
51
|
for (const r of replies) {
|
|
183
52
|
const rAuthor = r.author?.name ||
|
|
@@ -187,28 +56,31 @@ export function registerAstraCommand(program) {
|
|
|
187
56
|
replyLines.push(` - ${rAuthor}: ${truncated} (${r.createdAt})`);
|
|
188
57
|
}
|
|
189
58
|
const output = [
|
|
190
|
-
`
|
|
191
|
-
`By: ${author} on ${
|
|
59
|
+
`Thread: ${thread.title}`,
|
|
60
|
+
`By: ${author} on ${thread.createdAt}`,
|
|
192
61
|
"",
|
|
193
|
-
|
|
62
|
+
thread.content,
|
|
194
63
|
"",
|
|
195
|
-
`Replies (${replies.length}
|
|
64
|
+
`Replies (${replies.length} items):`,
|
|
196
65
|
...replyLines,
|
|
66
|
+
...(pagination.hasMore ? [` Next cursor: ${pagination.nextCursor}`] : []),
|
|
197
67
|
].join("\n");
|
|
198
68
|
console.log(output);
|
|
199
69
|
});
|
|
200
|
-
|
|
201
|
-
.command("list-
|
|
202
|
-
.description("List
|
|
70
|
+
space
|
|
71
|
+
.command("list-threads")
|
|
72
|
+
.description("List threads in a space (paginated).")
|
|
203
73
|
.option("--limit <number>", "Items per page", "20")
|
|
204
|
-
.option("--
|
|
74
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
205
75
|
.action(async (opts) => {
|
|
206
|
-
const spaceSlug = resolveSpaceSlug(
|
|
207
|
-
const
|
|
76
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
77
|
+
const params = {
|
|
208
78
|
limit: parseInt(opts.limit, 10),
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
79
|
+
};
|
|
80
|
+
if (opts.cursor)
|
|
81
|
+
params.cursor = opts.cursor;
|
|
82
|
+
const resp = (await apiGet(`/spaces/${spaceSlug}/threads`, params));
|
|
83
|
+
if (isJsonMode(space)) {
|
|
212
84
|
jsonOut({
|
|
213
85
|
items: resp.data || [],
|
|
214
86
|
pagination: resp.pagination || {},
|
|
@@ -218,344 +90,116 @@ export function registerAstraCommand(program) {
|
|
|
218
90
|
const items = (resp.data || []);
|
|
219
91
|
const pagination = (resp.pagination || {});
|
|
220
92
|
if (!items.length) {
|
|
221
|
-
console.log("No
|
|
93
|
+
console.log("No threads found.");
|
|
222
94
|
return;
|
|
223
95
|
}
|
|
224
96
|
const lines = [];
|
|
225
|
-
for (const
|
|
226
|
-
const author =
|
|
227
|
-
`User ${
|
|
228
|
-
lines.push(`- [${
|
|
229
|
-
}
|
|
230
|
-
const
|
|
231
|
-
console.log(`
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
.command("create-
|
|
235
|
-
.description("Create a
|
|
236
|
-
.requiredOption("--title <title>", "Title of the
|
|
237
|
-
.requiredOption("--content <content>", "
|
|
97
|
+
for (const t of items) {
|
|
98
|
+
const author = t.author?.name ||
|
|
99
|
+
`User ${t.authorId}`;
|
|
100
|
+
lines.push(`- [${t.id}] "${t.title}" by ${author} (${t.replyCount} replies, ${t.createdAt})`);
|
|
101
|
+
}
|
|
102
|
+
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
103
|
+
console.log(`Threads (${items.length} items):\n` + lines.join("\n") + footer);
|
|
104
|
+
});
|
|
105
|
+
space
|
|
106
|
+
.command("create-thread")
|
|
107
|
+
.description("Create a thread in a space.")
|
|
108
|
+
.requiredOption("--title <title>", "Title of the thread")
|
|
109
|
+
.requiredOption("--content <content>", "Thread content (markdown supported)")
|
|
238
110
|
.action(async (opts) => {
|
|
239
|
-
const spaceSlug = resolveSpaceSlug(
|
|
240
|
-
const resp = (await apiPost(`/spaces/${spaceSlug}/
|
|
111
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
112
|
+
const resp = (await apiPost(`/spaces/${spaceSlug}/threads`, {
|
|
241
113
|
title: opts.title,
|
|
242
114
|
content: opts.content,
|
|
243
115
|
}));
|
|
244
|
-
const
|
|
245
|
-
if (isJsonMode(
|
|
246
|
-
jsonOut(
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
console.log(`
|
|
250
|
-
` ID: ${
|
|
251
|
-
` Title: ${
|
|
252
|
-
` Created: ${
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
.command("edit-
|
|
256
|
-
.description("Edit a
|
|
257
|
-
.option("--title <title>", "New title for the
|
|
258
|
-
.option("--content <content>", "New content for the
|
|
259
|
-
.action(async (
|
|
116
|
+
const thread = unwrapResp(resp);
|
|
117
|
+
if (isJsonMode(space)) {
|
|
118
|
+
jsonOut(thread);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
console.log(`Thread created!\n` +
|
|
122
|
+
` ID: ${thread.id}\n` +
|
|
123
|
+
` Title: ${thread.title}\n` +
|
|
124
|
+
` Created: ${thread.createdAt}`);
|
|
125
|
+
});
|
|
126
|
+
space
|
|
127
|
+
.command("edit-thread <threadId>")
|
|
128
|
+
.description("Edit a thread. You must be the author.")
|
|
129
|
+
.option("--title <title>", "New title for the thread")
|
|
130
|
+
.option("--content <content>", "New content for the thread (markdown supported)")
|
|
131
|
+
.action(async (threadId, opts) => {
|
|
260
132
|
if (!opts.title && !opts.content) {
|
|
261
133
|
throw new Error("Provide at least --title or --content to update.");
|
|
262
134
|
}
|
|
263
|
-
const spaceSlug = resolveSpaceSlug(
|
|
135
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
264
136
|
const body = {};
|
|
265
137
|
if (opts.title != null)
|
|
266
138
|
body.title = opts.title;
|
|
267
139
|
if (opts.content != null)
|
|
268
140
|
body.content = opts.content;
|
|
269
|
-
const resp = (await apiPatch(`/spaces/${spaceSlug}/
|
|
270
|
-
const
|
|
271
|
-
if (isJsonMode(
|
|
272
|
-
jsonOut(
|
|
141
|
+
const resp = (await apiPatch(`/spaces/${spaceSlug}/threads/${threadId}`, body));
|
|
142
|
+
const thread = unwrapResp(resp);
|
|
143
|
+
if (isJsonMode(space)) {
|
|
144
|
+
jsonOut(thread);
|
|
273
145
|
return;
|
|
274
146
|
}
|
|
275
|
-
console.log(`
|
|
276
|
-
` ID: ${
|
|
277
|
-
` Title: ${
|
|
278
|
-
` Edited: ${
|
|
147
|
+
console.log(`Thread edited!\n` +
|
|
148
|
+
` ID: ${thread.id}\n` +
|
|
149
|
+
` Title: ${thread.title}\n` +
|
|
150
|
+
` Edited: ${thread.editedAt}`);
|
|
279
151
|
});
|
|
280
|
-
|
|
281
|
-
.command("delete-
|
|
282
|
-
.description("Delete a
|
|
283
|
-
.action(async (
|
|
284
|
-
const spaceSlug = resolveSpaceSlug(
|
|
285
|
-
await apiDelete(`/spaces/${spaceSlug}/
|
|
286
|
-
if (isJsonMode(
|
|
287
|
-
jsonOut({ id:
|
|
152
|
+
space
|
|
153
|
+
.command("delete-thread <threadId>")
|
|
154
|
+
.description("Delete a thread. You must be the author.")
|
|
155
|
+
.action(async (threadId) => {
|
|
156
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
157
|
+
await apiDelete(`/spaces/${spaceSlug}/threads/${threadId}`);
|
|
158
|
+
if (isJsonMode(space)) {
|
|
159
|
+
jsonOut({ id: threadId });
|
|
288
160
|
return;
|
|
289
161
|
}
|
|
290
|
-
console.log(`
|
|
162
|
+
console.log(`Thread ${threadId} deleted.`);
|
|
291
163
|
});
|
|
292
164
|
// ── Replies (create, edit, delete) ──
|
|
293
|
-
|
|
294
|
-
.command("create-reply <
|
|
295
|
-
.description("Create a reply to a
|
|
165
|
+
space
|
|
166
|
+
.command("create-reply <threadId>")
|
|
167
|
+
.description("Create a reply to a thread in a space.")
|
|
296
168
|
.requiredOption("--content <content>", "Reply content (markdown supported)")
|
|
297
|
-
.action(async (
|
|
298
|
-
const spaceSlug = resolveSpaceSlug(
|
|
299
|
-
const resp = (await apiPost(`/spaces/${spaceSlug}/
|
|
169
|
+
.action(async (threadId, opts) => {
|
|
170
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
171
|
+
const resp = (await apiPost(`/spaces/${spaceSlug}/threads/${threadId}/replies`, { content: opts.content }));
|
|
300
172
|
const msg = unwrapResp(resp);
|
|
301
|
-
if (isJsonMode(
|
|
173
|
+
if (isJsonMode(space)) {
|
|
302
174
|
jsonOut(msg);
|
|
303
175
|
return;
|
|
304
176
|
}
|
|
305
177
|
console.log(`Reply created!\n ID: ${msg.id}\n Created: ${msg.createdAt}`);
|
|
306
178
|
});
|
|
307
|
-
|
|
179
|
+
space
|
|
308
180
|
.command("edit-reply <replyId>")
|
|
309
181
|
.description("Edit a reply. You must be the author.")
|
|
310
182
|
.requiredOption("--content <content>", "New content for the reply (markdown supported)")
|
|
311
183
|
.action(async (replyId, opts) => {
|
|
312
|
-
const spaceSlug = resolveSpaceSlug(
|
|
184
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
313
185
|
const resp = (await apiPatch(`/spaces/${spaceSlug}/replies/${replyId}`, { content: opts.content }));
|
|
314
186
|
const msg = unwrapResp(resp);
|
|
315
|
-
if (isJsonMode(
|
|
187
|
+
if (isJsonMode(space)) {
|
|
316
188
|
jsonOut(msg);
|
|
317
189
|
return;
|
|
318
190
|
}
|
|
319
191
|
console.log(`Reply edited!\n ID: ${msg.id}\n Edited: ${msg.editedAt}`);
|
|
320
192
|
});
|
|
321
|
-
|
|
193
|
+
space
|
|
322
194
|
.command("delete-reply <replyId>")
|
|
323
195
|
.description("Delete a reply. You must be the author.")
|
|
324
196
|
.action(async (replyId) => {
|
|
325
|
-
const spaceSlug = resolveSpaceSlug(
|
|
197
|
+
const spaceSlug = resolveSpaceSlug(space);
|
|
326
198
|
await apiDelete(`/spaces/${spaceSlug}/replies/${replyId}`);
|
|
327
|
-
if (isJsonMode(
|
|
199
|
+
if (isJsonMode(space)) {
|
|
328
200
|
jsonOut({ replyId });
|
|
329
201
|
return;
|
|
330
202
|
}
|
|
331
203
|
console.log(`Reply ${replyId} deleted.`);
|
|
332
204
|
});
|
|
333
|
-
// ── Sessions (get, list, reply) ──
|
|
334
|
-
astra
|
|
335
|
-
.command("get-session <sessionId>")
|
|
336
|
-
.description("Get a session and its messages (paginated).")
|
|
337
|
-
.option("--limit <number>", "Messages per page", "20")
|
|
338
|
-
.option("--offset <number>", "Offset for message pagination", "0")
|
|
339
|
-
.action(async (sessionId, opts) => {
|
|
340
|
-
const resp = (await apiGet(`/session/${sessionId}`, {
|
|
341
|
-
limit: parseInt(opts.limit, 10),
|
|
342
|
-
offset: parseInt(opts.offset, 10),
|
|
343
|
-
}));
|
|
344
|
-
const data = unwrapResp(resp);
|
|
345
|
-
if (isJsonMode(astra)) {
|
|
346
|
-
jsonOut(data);
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
|
-
const session = (data.session || data);
|
|
350
|
-
const messages = (data.messages || []);
|
|
351
|
-
const pagination = (data.pagination || {});
|
|
352
|
-
const totalMessages = pagination.total || messages.length;
|
|
353
|
-
const msgLines = [];
|
|
354
|
-
for (const m of messages) {
|
|
355
|
-
const author = m.author?.name ||
|
|
356
|
-
m.source ||
|
|
357
|
-
`User ${m.authorId}`;
|
|
358
|
-
const text = m.content;
|
|
359
|
-
const truncated = text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
|
|
360
|
-
msgLines.push(` - ${author}: ${truncated} (${m.createdAt})`);
|
|
361
|
-
}
|
|
362
|
-
const output = [
|
|
363
|
-
`Session: ${session.title}`,
|
|
364
|
-
` ID: ${session.id}`,
|
|
365
|
-
` Mode: ${session.mode}`,
|
|
366
|
-
` Last activity: ${session.lastMessageAt}`,
|
|
367
|
-
"",
|
|
368
|
-
`Messages (${messages.length} of ${totalMessages}):`,
|
|
369
|
-
...msgLines,
|
|
370
|
-
].join("\n");
|
|
371
|
-
console.log(output);
|
|
372
|
-
});
|
|
373
|
-
astra
|
|
374
|
-
.command("list-sessions")
|
|
375
|
-
.description("List all sessions you are part of, sorted by most recent activity.")
|
|
376
|
-
.option("--limit <number>", "Items per page", "20")
|
|
377
|
-
.option("--offset <number>", "Offset for pagination", "0")
|
|
378
|
-
.action(async (opts) => {
|
|
379
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
380
|
-
const limit = parseInt(opts.limit, 10);
|
|
381
|
-
const offset = parseInt(opts.offset, 10);
|
|
382
|
-
const resp = (await apiGet(`/session/my-sessions`, {
|
|
383
|
-
spaceSlug,
|
|
384
|
-
limit,
|
|
385
|
-
offset,
|
|
386
|
-
}));
|
|
387
|
-
const items = (resp.data || []);
|
|
388
|
-
const pagination = (resp.pagination || {});
|
|
389
|
-
const total = pagination.total ?? items.length;
|
|
390
|
-
if (isJsonMode(astra)) {
|
|
391
|
-
jsonOut({
|
|
392
|
-
items,
|
|
393
|
-
pagination: resp.pagination || {},
|
|
394
|
-
});
|
|
395
|
-
return;
|
|
396
|
-
}
|
|
397
|
-
if (!items.length) {
|
|
398
|
-
console.log("No sessions found.");
|
|
399
|
-
return;
|
|
400
|
-
}
|
|
401
|
-
const lines = [];
|
|
402
|
-
for (const s of items) {
|
|
403
|
-
const title = s.title || "(no title)";
|
|
404
|
-
const members = s.members || [];
|
|
405
|
-
const memberCount = s.memberCount ?? 0;
|
|
406
|
-
let memberInfo = "";
|
|
407
|
-
if (members.length > 0) {
|
|
408
|
-
const names = members.map((m) => m.vaultName || m.name || "Unknown");
|
|
409
|
-
const overflow = memberCount - members.length - 1; // -1 for "me"
|
|
410
|
-
memberInfo = ` | with: ${names.join(", ")}`;
|
|
411
|
-
if (overflow > 0)
|
|
412
|
-
memberInfo += ` +${overflow} more`;
|
|
413
|
-
}
|
|
414
|
-
lines.push(`- [${s.id}] "${title}" (mode: ${s.mode}, last activity: ${s.lastMessageAt})${memberInfo}`);
|
|
415
|
-
}
|
|
416
|
-
console.log(`Sessions (${items.length} of ${total}):\n` + lines.join("\n"));
|
|
417
|
-
});
|
|
418
|
-
astra
|
|
419
|
-
.command("reply-session <sessionId>")
|
|
420
|
-
.description("Send a human reply to a session you are a member of.")
|
|
421
|
-
.requiredOption("--content <content>", "Reply content (markdown supported)")
|
|
422
|
-
.action(async (sessionId, opts) => {
|
|
423
|
-
const resp = (await apiPost(`/session/${sessionId}/reply`, {
|
|
424
|
-
content: opts.content,
|
|
425
|
-
}));
|
|
426
|
-
const msg = unwrapResp(resp);
|
|
427
|
-
if (isJsonMode(astra)) {
|
|
428
|
-
jsonOut(msg);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
console.log(`Reply sent!\n` +
|
|
432
|
-
` Message ID: ${msg.id}\n` +
|
|
433
|
-
` Source: ${msg.source}\n` +
|
|
434
|
-
` Created: ${msg.createdAt}`);
|
|
435
|
-
});
|
|
436
|
-
astra
|
|
437
|
-
.command("update-session <sessionId>")
|
|
438
|
-
.description('Update a session. "auto" lets the AI respond automatically; "manual" requires human replies.')
|
|
439
|
-
.option("--mode <mode>", 'Session mode: "auto" or "manual"')
|
|
440
|
-
.action(async (sessionId, opts) => {
|
|
441
|
-
if (!opts.mode) {
|
|
442
|
-
throw new Error("Provide at least one option to update (e.g. --mode).");
|
|
443
|
-
}
|
|
444
|
-
const body = {};
|
|
445
|
-
if (opts.mode != null) {
|
|
446
|
-
if (opts.mode !== "auto" && opts.mode !== "manual") {
|
|
447
|
-
throw new Error('Invalid mode. Must be "auto" or "manual".');
|
|
448
|
-
}
|
|
449
|
-
body.mode = opts.mode;
|
|
450
|
-
}
|
|
451
|
-
const resp = (await apiPatch(`/session/${sessionId}`, body));
|
|
452
|
-
const data = unwrapResp(resp);
|
|
453
|
-
if (isJsonMode(astra)) {
|
|
454
|
-
jsonOut(data);
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
const session = (data.session || data);
|
|
458
|
-
console.log(`Session updated!\n` +
|
|
459
|
-
` ID: ${session.id}\n` +
|
|
460
|
-
` Mode: ${session.mode}`);
|
|
461
|
-
});
|
|
462
|
-
// ── Brain Updates (list, create, edit, delete) ──
|
|
463
|
-
astra
|
|
464
|
-
.command("list-brain-updates")
|
|
465
|
-
.description("List recent brain updates in a space (paginated).")
|
|
466
|
-
.option("--limit <number>", "Items per page", "20")
|
|
467
|
-
.option("--offset <number>", "Offset for pagination", "0")
|
|
468
|
-
.action(async (opts) => {
|
|
469
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
470
|
-
const resp = (await apiGet(`/spaces/${spaceSlug}/brain-updates`, {
|
|
471
|
-
limit: parseInt(opts.limit, 10),
|
|
472
|
-
offset: parseInt(opts.offset, 10),
|
|
473
|
-
}));
|
|
474
|
-
if (isJsonMode(astra)) {
|
|
475
|
-
jsonOut({
|
|
476
|
-
items: resp.data || [],
|
|
477
|
-
pagination: resp.pagination || {},
|
|
478
|
-
});
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
const items = (resp.data || []);
|
|
482
|
-
const pagination = (resp.pagination || {});
|
|
483
|
-
if (!items.length) {
|
|
484
|
-
console.log("No brain updates found.");
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const lines = [];
|
|
488
|
-
for (const u of items) {
|
|
489
|
-
const author = u.author?.name ||
|
|
490
|
-
`User ${u.authorId}`;
|
|
491
|
-
const vaultSlug = u.vault?.vaultSlug ||
|
|
492
|
-
"?";
|
|
493
|
-
lines.push(`- [${u.id}] "${u.title}" by ${author} (vault: ${vaultSlug}, ${u.createdAt})`);
|
|
494
|
-
}
|
|
495
|
-
const total = pagination.total || items.length;
|
|
496
|
-
console.log(`Brain updates (${items.length} of ${total}):\n` + lines.join("\n"));
|
|
497
|
-
});
|
|
498
|
-
astra
|
|
499
|
-
.command("create-brain-update")
|
|
500
|
-
.description("Create a brain update in a space. Uses the vault from settings.")
|
|
501
|
-
.option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
|
|
502
|
-
.requiredOption("--title <title>", "Title of the update")
|
|
503
|
-
.requiredOption("--content <content>", "Update content (markdown supported)")
|
|
504
|
-
.action(async (opts) => {
|
|
505
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
506
|
-
const vaultSlug = resolveVaultSlug(opts);
|
|
507
|
-
const resp = (await apiPost(`/spaces/${spaceSlug}/brain-updates`, {
|
|
508
|
-
vaultSlug,
|
|
509
|
-
title: opts.title,
|
|
510
|
-
content: opts.content,
|
|
511
|
-
}));
|
|
512
|
-
const u = unwrapResp(resp);
|
|
513
|
-
if (isJsonMode(astra)) {
|
|
514
|
-
jsonOut(u);
|
|
515
|
-
return;
|
|
516
|
-
}
|
|
517
|
-
console.log(`Brain update created!\n` +
|
|
518
|
-
` ID: ${u.id}\n` +
|
|
519
|
-
` Title: ${u.title}\n` +
|
|
520
|
-
` Vault: ${u.vaultSlug || vaultSlug}\n` +
|
|
521
|
-
` Created: ${u.createdAt}`);
|
|
522
|
-
});
|
|
523
|
-
astra
|
|
524
|
-
.command("edit-brain-update <updateId>")
|
|
525
|
-
.description("Edit a published brain update. You must be the author.")
|
|
526
|
-
.option("--title <title>", "New title for the update")
|
|
527
|
-
.option("--content <content>", "New content for the update (markdown supported)")
|
|
528
|
-
.action(async (updateId, opts) => {
|
|
529
|
-
if (!opts.title && !opts.content) {
|
|
530
|
-
throw new Error("Provide at least --title or --content to update.");
|
|
531
|
-
}
|
|
532
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
533
|
-
const body = {};
|
|
534
|
-
if (opts.title != null)
|
|
535
|
-
body.title = opts.title;
|
|
536
|
-
if (opts.content != null)
|
|
537
|
-
body.content = opts.content;
|
|
538
|
-
const resp = (await apiPatch(`/spaces/${spaceSlug}/brain-updates/${updateId}`, body));
|
|
539
|
-
const u = unwrapResp(resp);
|
|
540
|
-
if (isJsonMode(astra)) {
|
|
541
|
-
jsonOut(u);
|
|
542
|
-
return;
|
|
543
|
-
}
|
|
544
|
-
console.log(`Brain update edited!\n` +
|
|
545
|
-
` ID: ${u.id}\n` +
|
|
546
|
-
` Title: ${u.title}\n` +
|
|
547
|
-
` Updated: ${u.updatedAt}`);
|
|
548
|
-
});
|
|
549
|
-
astra
|
|
550
|
-
.command("delete-brain-update <updateId>")
|
|
551
|
-
.description("Delete a published brain update. You must be the author.")
|
|
552
|
-
.action(async (updateId) => {
|
|
553
|
-
const spaceSlug = resolveSpaceSlug(astra);
|
|
554
|
-
await apiDelete(`/spaces/${spaceSlug}/brain-updates/${updateId}`);
|
|
555
|
-
if (isJsonMode(astra)) {
|
|
556
|
-
jsonOut({ id: updateId });
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
console.log(`Brain update ${updateId} deleted.`);
|
|
560
|
-
});
|
|
561
205
|
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
|
|
4
|
+
import { WEBDRIVE_BASE_URL } from "../constants.js";
|
|
5
|
+
import { getValidToken } from "../auth/manager.js";
|
|
6
|
+
import { getVaultSlug } from "./init.js";
|
|
7
|
+
import { isJsonMode, jsonOut, resolveVaultSlug, unwrapResp } from "./utils.js";
|
|
8
|
+
export function registerBrainCommand(program) {
|
|
9
|
+
const brain = program
|
|
10
|
+
.command("brain")
|
|
11
|
+
.description("Brain commands (search, ask, publish, unpublish, updates).");
|
|
12
|
+
// ── Search ──
|
|
13
|
+
brain
|
|
14
|
+
.command("search")
|
|
15
|
+
.description("Search public brains by text and semantic similarity.")
|
|
16
|
+
.requiredOption("--query <query>", "Search query")
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const resp = (await apiGet(`/vault/public/search`, {
|
|
19
|
+
query: opts.query,
|
|
20
|
+
}));
|
|
21
|
+
const results = (Array.isArray(resp) ? resp : resp.data || resp);
|
|
22
|
+
if (isJsonMode(brain)) {
|
|
23
|
+
jsonOut(results || []);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
if (!results || results.length === 0) {
|
|
27
|
+
console.log(`No brains found matching "${opts.query}".`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const lines = [];
|
|
31
|
+
for (const entry of results) {
|
|
32
|
+
const vault = (entry.vault || entry);
|
|
33
|
+
const owner = (entry.owner || {});
|
|
34
|
+
const ownerName = owner.name ? ` by ${owner.name}` : "";
|
|
35
|
+
const sim = entry.similarity != null
|
|
36
|
+
? ` [similarity: ${entry.similarity.toFixed(3)}]`
|
|
37
|
+
: "";
|
|
38
|
+
const spaceSlug = (entry.spaceSlug || vault.spaceSlug || "");
|
|
39
|
+
const vaultSlug = (vault.slug || vault.vaultSlug || vault.id || "N/A");
|
|
40
|
+
lines.push(`- ${vault.name || vault.title || "N/A"} (vault: ${vaultSlug}, space: ${spaceSlug || "N/A"})${ownerName}${sim}`);
|
|
41
|
+
}
|
|
42
|
+
console.log(`Brains matching "${opts.query}":\n` + lines.join("\n"));
|
|
43
|
+
});
|
|
44
|
+
// ── Ask ──
|
|
45
|
+
brain
|
|
46
|
+
.command("ask")
|
|
47
|
+
.description("Ask a brain a question. Creates a targeted session (1:1 conversation).")
|
|
48
|
+
.requiredOption("--vault-slug <vaultSlug>", "Slug of the brain/vault to ask")
|
|
49
|
+
.requiredOption("--space-slug <spaceSlug>", "Space slug where the brain belongs")
|
|
50
|
+
.requiredOption("--question <question>", "The question to ask (markdown supported)")
|
|
51
|
+
.option("--mode <mode>", 'Session mode: "auto" or "manual"')
|
|
52
|
+
.action(async (opts) => {
|
|
53
|
+
const spaceSlug = opts.spaceSlug;
|
|
54
|
+
const body = {
|
|
55
|
+
vaultSlug: opts.vaultSlug,
|
|
56
|
+
spaceSlug,
|
|
57
|
+
question: opts.question,
|
|
58
|
+
};
|
|
59
|
+
if (opts.mode != null)
|
|
60
|
+
body.mode = opts.mode;
|
|
61
|
+
const resp = (await apiPost(`/session/targeted`, body));
|
|
62
|
+
const data = unwrapResp(resp);
|
|
63
|
+
if (isJsonMode(brain)) {
|
|
64
|
+
jsonOut(data);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const session = (data.session || {});
|
|
68
|
+
const members = (data.members || []);
|
|
69
|
+
console.log(`Session created!\n` +
|
|
70
|
+
` Session ID: ${session.id}\n` +
|
|
71
|
+
` Mode: ${session.mode}\n` +
|
|
72
|
+
` Members: ${members.length}\n` +
|
|
73
|
+
` Question sent.`);
|
|
74
|
+
});
|
|
75
|
+
// ── Publish ──
|
|
76
|
+
brain
|
|
77
|
+
.command("publish")
|
|
78
|
+
.description("Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (brain sync, metadata update, Discord notification).")
|
|
79
|
+
.action(async () => {
|
|
80
|
+
const vaultId = getVaultSlug();
|
|
81
|
+
const filePath = join(process.cwd(), "BRAIN.md");
|
|
82
|
+
if (!existsSync(filePath)) {
|
|
83
|
+
throw new Error(`BRAIN.md not found in ${process.cwd()}`);
|
|
84
|
+
}
|
|
85
|
+
const content = readFileSync(filePath, "utf-8");
|
|
86
|
+
const token = await getValidToken();
|
|
87
|
+
const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/files/BRAIN.md`;
|
|
88
|
+
const res = await fetch(url, {
|
|
89
|
+
method: "PUT",
|
|
90
|
+
headers: {
|
|
91
|
+
Authorization: `Bearer ${token}`,
|
|
92
|
+
"Content-Type": "text/markdown",
|
|
93
|
+
},
|
|
94
|
+
body: content,
|
|
95
|
+
});
|
|
96
|
+
if (!res.ok) {
|
|
97
|
+
throw new Error(`Upload failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
|
|
98
|
+
}
|
|
99
|
+
if (isJsonMode(brain)) {
|
|
100
|
+
jsonOut({ vaultId });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
console.log(`Published BRAIN.md to vault "${vaultId}"`);
|
|
104
|
+
});
|
|
105
|
+
// ── Unpublish ──
|
|
106
|
+
brain
|
|
107
|
+
.command("unpublish")
|
|
108
|
+
.description("Delete BRAIN.md from the vault on webdrive.")
|
|
109
|
+
.action(async () => {
|
|
110
|
+
const vaultId = getVaultSlug();
|
|
111
|
+
const token = await getValidToken();
|
|
112
|
+
const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/files/BRAIN.md`;
|
|
113
|
+
const res = await fetch(url, {
|
|
114
|
+
method: "DELETE",
|
|
115
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
116
|
+
});
|
|
117
|
+
if (!res.ok) {
|
|
118
|
+
throw new Error(`Delete failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
|
|
119
|
+
}
|
|
120
|
+
if (isJsonMode(brain)) {
|
|
121
|
+
jsonOut({ vaultId });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
console.log(`Deleted BRAIN.md from vault "${vaultId}"`);
|
|
125
|
+
});
|
|
126
|
+
// ── Updates (list, post, edit, delete) ──
|
|
127
|
+
brain
|
|
128
|
+
.command("list-updates")
|
|
129
|
+
.description("List recent brain updates. Without --space-slug, lists all updates for you. With --space-slug, lists updates for that space. Use --mine to show only updates by you.")
|
|
130
|
+
.option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
|
|
131
|
+
.option("--space-slug <spaceSlug>", "List updates for a space")
|
|
132
|
+
.option("--mine", "List only my own brain updates")
|
|
133
|
+
.option("--limit <number>", "Items per page", "20")
|
|
134
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
135
|
+
.action(async (opts) => {
|
|
136
|
+
const params = {
|
|
137
|
+
limit: parseInt(opts.limit, 10),
|
|
138
|
+
};
|
|
139
|
+
if (opts.cursor)
|
|
140
|
+
params.cursor = opts.cursor;
|
|
141
|
+
if (opts.mine)
|
|
142
|
+
params.mine = true;
|
|
143
|
+
if (opts.vaultSlug)
|
|
144
|
+
params.vaultSlug = opts.vaultSlug;
|
|
145
|
+
const path = opts.spaceSlug
|
|
146
|
+
? `/spaces/${opts.spaceSlug}/brain-updates`
|
|
147
|
+
: `/brain-updates`;
|
|
148
|
+
const resp = (await apiGet(path, params));
|
|
149
|
+
if (isJsonMode(brain)) {
|
|
150
|
+
jsonOut({
|
|
151
|
+
items: resp.data || [],
|
|
152
|
+
pagination: resp.pagination || {},
|
|
153
|
+
});
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const items = (resp.data || []);
|
|
157
|
+
const pagination = (resp.pagination || {});
|
|
158
|
+
if (!items.length) {
|
|
159
|
+
console.log("No brain updates found.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const lines = [];
|
|
163
|
+
for (const u of items) {
|
|
164
|
+
const author = u.author?.name ||
|
|
165
|
+
`User ${u.authorId}`;
|
|
166
|
+
const vaultSlug = u.vault?.vaultSlug ||
|
|
167
|
+
"?";
|
|
168
|
+
lines.push(`- [${u.id}] "${u.title}" by ${author} (vault: ${vaultSlug}, ${u.createdAt})`);
|
|
169
|
+
}
|
|
170
|
+
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
171
|
+
console.log(`Brain updates (${items.length} items):\n` + lines.join("\n") + footer);
|
|
172
|
+
});
|
|
173
|
+
brain
|
|
174
|
+
.command("post-update")
|
|
175
|
+
.description("Post a brain update for a vault.")
|
|
176
|
+
.option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
|
|
177
|
+
.requiredOption("--title <title>", "Title of the update")
|
|
178
|
+
.requiredOption("--content <content>", "Update content (markdown supported)")
|
|
179
|
+
.action(async (opts) => {
|
|
180
|
+
const vaultSlug = resolveVaultSlug(opts);
|
|
181
|
+
const resp = (await apiPost(`/brain-updates/vault/${vaultSlug}`, {
|
|
182
|
+
title: opts.title,
|
|
183
|
+
content: opts.content,
|
|
184
|
+
}));
|
|
185
|
+
const u = unwrapResp(resp);
|
|
186
|
+
if (isJsonMode(brain)) {
|
|
187
|
+
jsonOut(u);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
console.log(`Brain update posted!\n` +
|
|
191
|
+
` ID: ${u.id}\n` +
|
|
192
|
+
` Title: ${u.title}\n` +
|
|
193
|
+
` Vault: ${u.vaultSlug || vaultSlug}\n` +
|
|
194
|
+
` Created: ${u.createdAt}`);
|
|
195
|
+
});
|
|
196
|
+
brain
|
|
197
|
+
.command("edit-update <updateId>")
|
|
198
|
+
.description("Edit a published brain update. You must be the author.")
|
|
199
|
+
.option("--title <title>", "New title for the update")
|
|
200
|
+
.option("--content <content>", "New content for the update (markdown supported)")
|
|
201
|
+
.action(async (updateId, opts) => {
|
|
202
|
+
if (!opts.title && !opts.content) {
|
|
203
|
+
throw new Error("Provide at least --title or --content to update.");
|
|
204
|
+
}
|
|
205
|
+
const body = {};
|
|
206
|
+
if (opts.title != null)
|
|
207
|
+
body.title = opts.title;
|
|
208
|
+
if (opts.content != null)
|
|
209
|
+
body.content = opts.content;
|
|
210
|
+
const resp = (await apiPatch(`/brain-updates/${updateId}`, body));
|
|
211
|
+
const u = unwrapResp(resp);
|
|
212
|
+
if (isJsonMode(brain)) {
|
|
213
|
+
jsonOut(u);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
console.log(`Brain update edited!\n` +
|
|
217
|
+
` ID: ${u.id}\n` +
|
|
218
|
+
` Title: ${u.title}\n` +
|
|
219
|
+
` Updated: ${u.updatedAt}`);
|
|
220
|
+
});
|
|
221
|
+
brain
|
|
222
|
+
.command("delete-update <updateId>")
|
|
223
|
+
.description("Delete a published brain update. You must be the author.")
|
|
224
|
+
.action(async (updateId) => {
|
|
225
|
+
await apiDelete(`/brain-updates/${updateId}`);
|
|
226
|
+
if (isJsonMode(brain)) {
|
|
227
|
+
jsonOut({ id: updateId });
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
console.log(`Brain update ${updateId} deleted.`);
|
|
231
|
+
});
|
|
232
|
+
}
|
package/dist/commands/init.js
CHANGED
|
@@ -21,7 +21,7 @@ export function getSpaceSlug() {
|
|
|
21
21
|
const settings = readSettings();
|
|
22
22
|
const slug = settings?.selectedSpaceSlug;
|
|
23
23
|
if (!slug) {
|
|
24
|
-
throw new Error("Space not set. Run 'gobi
|
|
24
|
+
throw new Error("Space not set. Run 'gobi space warp' first.");
|
|
25
25
|
}
|
|
26
26
|
return slug;
|
|
27
27
|
}
|
|
@@ -38,7 +38,7 @@ export function printContext() {
|
|
|
38
38
|
const vault = settings?.vaultSlug;
|
|
39
39
|
const space = settings?.selectedSpaceSlug;
|
|
40
40
|
if (!vault && !space) {
|
|
41
|
-
console.log("Run 'gobi init' to set up, then 'gobi
|
|
41
|
+
console.log("Run 'gobi init' to set up, then 'gobi space warp' to select a space.");
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
44
|
if (!vault) {
|
|
@@ -46,7 +46,7 @@ export function printContext() {
|
|
|
46
46
|
return;
|
|
47
47
|
}
|
|
48
48
|
if (!space) {
|
|
49
|
-
console.log(`Vault: ${vault} | Space not set. Run 'gobi
|
|
49
|
+
console.log(`Vault: ${vault} | Space not set. Run 'gobi space warp' to select a space.`);
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
console.log(`Space: ${space} | Vault: ${vault}`);
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { apiGet, apiPost, apiPatch } from "../client.js";
|
|
2
|
+
import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
|
|
3
|
+
export function registerSessionsCommand(program) {
|
|
4
|
+
const sessions = program
|
|
5
|
+
.command("session")
|
|
6
|
+
.description("Session commands (get, list, reply, update).");
|
|
7
|
+
// ── Get ──
|
|
8
|
+
sessions
|
|
9
|
+
.command("get <sessionId>")
|
|
10
|
+
.description("Get a session and its messages (paginated).")
|
|
11
|
+
.option("--limit <number>", "Messages per page", "20")
|
|
12
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
13
|
+
.action(async (sessionId, opts) => {
|
|
14
|
+
const params = {
|
|
15
|
+
limit: parseInt(opts.limit, 10),
|
|
16
|
+
};
|
|
17
|
+
if (opts.cursor)
|
|
18
|
+
params.cursor = opts.cursor;
|
|
19
|
+
const resp = (await apiGet(`/session/${sessionId}`, params));
|
|
20
|
+
const data = unwrapResp(resp);
|
|
21
|
+
if (isJsonMode(sessions)) {
|
|
22
|
+
jsonOut(data);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const session = (data.session || data);
|
|
26
|
+
const messages = (data.messages || []);
|
|
27
|
+
const pagination = (data.pagination || {});
|
|
28
|
+
const msgLines = [];
|
|
29
|
+
for (const m of messages) {
|
|
30
|
+
const author = m.author?.name ||
|
|
31
|
+
m.source ||
|
|
32
|
+
`User ${m.authorId}`;
|
|
33
|
+
const text = m.content;
|
|
34
|
+
const truncated = text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
|
|
35
|
+
msgLines.push(` - ${author}: ${truncated} (${m.createdAt})`);
|
|
36
|
+
}
|
|
37
|
+
const output = [
|
|
38
|
+
`Session: ${session.title}`,
|
|
39
|
+
` ID: ${session.id}`,
|
|
40
|
+
` Mode: ${session.mode}`,
|
|
41
|
+
` Last activity: ${session.lastMessageAt}`,
|
|
42
|
+
"",
|
|
43
|
+
`Messages (${messages.length} items):`,
|
|
44
|
+
...msgLines,
|
|
45
|
+
...(pagination.hasMore ? [` Next cursor: ${pagination.nextCursor}`] : []),
|
|
46
|
+
].join("\n");
|
|
47
|
+
console.log(output);
|
|
48
|
+
});
|
|
49
|
+
// ── List ──
|
|
50
|
+
sessions
|
|
51
|
+
.command("list")
|
|
52
|
+
.description("List all sessions you are part of, sorted by most recent activity.")
|
|
53
|
+
.option("--space-slug <spaceSlug>", "Filter by space slug")
|
|
54
|
+
.option("--limit <number>", "Items per page", "20")
|
|
55
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
56
|
+
.action(async (opts) => {
|
|
57
|
+
const query = { limit: parseInt(opts.limit, 10) };
|
|
58
|
+
if (opts.cursor)
|
|
59
|
+
query.cursor = opts.cursor;
|
|
60
|
+
if (opts.spaceSlug)
|
|
61
|
+
query.spaceSlug = opts.spaceSlug;
|
|
62
|
+
const resp = (await apiGet(`/session/my-sessions`, query));
|
|
63
|
+
const items = (resp.data || []);
|
|
64
|
+
const pagination = (resp.pagination || {});
|
|
65
|
+
if (isJsonMode(sessions)) {
|
|
66
|
+
jsonOut({
|
|
67
|
+
items,
|
|
68
|
+
pagination: resp.pagination || {},
|
|
69
|
+
});
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
if (!items.length) {
|
|
73
|
+
console.log("No sessions found.");
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const lines = [];
|
|
77
|
+
for (const s of items) {
|
|
78
|
+
const title = s.title || "(no title)";
|
|
79
|
+
const members = s.members || [];
|
|
80
|
+
const memberCount = s.memberCount ?? 0;
|
|
81
|
+
let memberInfo = "";
|
|
82
|
+
if (members.length > 0) {
|
|
83
|
+
const names = members.map((m) => m.vaultName || m.name || "Unknown");
|
|
84
|
+
const overflow = memberCount - members.length - 1; // -1 for "me"
|
|
85
|
+
memberInfo = ` | with: ${names.join(", ")}`;
|
|
86
|
+
if (overflow > 0)
|
|
87
|
+
memberInfo += ` +${overflow} more`;
|
|
88
|
+
}
|
|
89
|
+
lines.push(`- [${s.id}] "${title}" (mode: ${s.mode}, last activity: ${s.lastMessageAt})${memberInfo}`);
|
|
90
|
+
}
|
|
91
|
+
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
92
|
+
console.log(`Sessions (${items.length} items):\n` + lines.join("\n") + footer);
|
|
93
|
+
});
|
|
94
|
+
// ── Reply ──
|
|
95
|
+
sessions
|
|
96
|
+
.command("reply <sessionId>")
|
|
97
|
+
.description("Send a human reply to a session you are a member of.")
|
|
98
|
+
.requiredOption("--content <content>", "Reply content (markdown supported)")
|
|
99
|
+
.action(async (sessionId, opts) => {
|
|
100
|
+
const resp = (await apiPost(`/session/${sessionId}/reply`, {
|
|
101
|
+
content: opts.content,
|
|
102
|
+
}));
|
|
103
|
+
const msg = unwrapResp(resp);
|
|
104
|
+
if (isJsonMode(sessions)) {
|
|
105
|
+
jsonOut(msg);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
console.log(`Reply sent!\n` +
|
|
109
|
+
` Message ID: ${msg.id}\n` +
|
|
110
|
+
` Source: ${msg.source}\n` +
|
|
111
|
+
` Created: ${msg.createdAt}`);
|
|
112
|
+
});
|
|
113
|
+
// ── Update ──
|
|
114
|
+
sessions
|
|
115
|
+
.command("update <sessionId>")
|
|
116
|
+
.description('Update a session. "auto" lets the AI respond automatically; "manual" requires human replies.')
|
|
117
|
+
.option("--mode <mode>", 'Session mode: "auto" or "manual"')
|
|
118
|
+
.action(async (sessionId, opts) => {
|
|
119
|
+
if (!opts.mode) {
|
|
120
|
+
throw new Error("Provide at least one option to update (e.g. --mode).");
|
|
121
|
+
}
|
|
122
|
+
const body = {};
|
|
123
|
+
if (opts.mode != null) {
|
|
124
|
+
if (opts.mode !== "auto" && opts.mode !== "manual") {
|
|
125
|
+
throw new Error('Invalid mode. Must be "auto" or "manual".');
|
|
126
|
+
}
|
|
127
|
+
body.mode = opts.mode;
|
|
128
|
+
}
|
|
129
|
+
const resp = (await apiPatch(`/session/${sessionId}`, body));
|
|
130
|
+
const data = unwrapResp(resp);
|
|
131
|
+
if (isJsonMode(sessions)) {
|
|
132
|
+
jsonOut(data);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const session = (data.session || data);
|
|
136
|
+
console.log(`Session updated!\n` +
|
|
137
|
+
` ID: ${session.id}\n` +
|
|
138
|
+
` Mode: ${session.mode}`);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { getSpaceSlug, getVaultSlug } from "./init.js";
|
|
2
|
+
export function isJsonMode(cmd) {
|
|
3
|
+
return !!cmd.parent?.opts().json;
|
|
4
|
+
}
|
|
5
|
+
export function jsonOut(data) {
|
|
6
|
+
console.log(JSON.stringify({ success: true, data }));
|
|
7
|
+
}
|
|
8
|
+
export function resolveSpaceSlug(cmd) {
|
|
9
|
+
return cmd.opts().spaceSlug || getSpaceSlug();
|
|
10
|
+
}
|
|
11
|
+
export function resolveVaultSlug(opts) {
|
|
12
|
+
return opts.vaultSlug || getVaultSlug();
|
|
13
|
+
}
|
|
14
|
+
export function unwrapResp(resp) {
|
|
15
|
+
if (typeof resp === "object" && resp !== null && "data" in resp) {
|
|
16
|
+
return resp.data;
|
|
17
|
+
}
|
|
18
|
+
return resp;
|
|
19
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -5,6 +5,8 @@ import { ApiError, AstraError } from "./errors.js";
|
|
|
5
5
|
import { registerAuthCommand } from "./commands/auth.js";
|
|
6
6
|
import { registerInitCommand, printContext } from "./commands/init.js";
|
|
7
7
|
import { registerAstraCommand } from "./commands/astra.js";
|
|
8
|
+
import { registerBrainCommand } from "./commands/brain.js";
|
|
9
|
+
import { registerSessionsCommand } from "./commands/sessions.js";
|
|
8
10
|
const require = createRequire(import.meta.url);
|
|
9
11
|
const { version } = require("../package.json");
|
|
10
12
|
const SKIP_BANNER_COMMANDS = new Set(["auth", "init"]);
|
|
@@ -26,6 +28,8 @@ export async function cli() {
|
|
|
26
28
|
registerAuthCommand(program);
|
|
27
29
|
registerInitCommand(program);
|
|
28
30
|
registerAstraCommand(program);
|
|
31
|
+
registerBrainCommand(program);
|
|
32
|
+
registerSessionsCommand(program);
|
|
29
33
|
// Propagate helpWidth to all subcommands
|
|
30
34
|
const helpWidth = process.stdout.columns || 200;
|
|
31
35
|
for (const cmd of program.commands) {
|