@gobi-ai/cli 0.3.0 → 0.3.2

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 CHANGED
@@ -38,13 +38,13 @@ npm link
38
38
  gobi init
39
39
 
40
40
  # Select a space
41
- gobi astra warp
41
+ gobi space warp
42
42
 
43
- # Search brains in your space
44
- gobi astra search-brain --query "machine learning"
43
+ # Search brains across your spaces
44
+ gobi brain search --query "machine learning"
45
45
 
46
46
  # Ask a brain a question
47
- gobi astra ask-brain --vault-slug my-vault --question "What is RAG?"
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 astra warp` | Select the active space |
65
+ | `gobi space warp` | Select the active space |
66
66
 
67
67
  ### Brains
68
68
 
69
69
  | Command | Description |
70
70
  |---------|-------------|
71
- | `gobi astra search-brain --query <q>` | Search brains in a space |
72
- | `gobi astra ask-brain --vault-slug <slug> --question <q>` | Ask a brain a question (creates a 1:1 session) |
73
- | `gobi astra publish-brain` | Upload `BRAIN.md` to your vault |
74
- | `gobi astra unpublish-brain` | Remove `BRAIN.md` from your vault |
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
- ### Posts
76
+ ### Brain Updates
77
77
 
78
78
  | Command | Description |
79
79
  |---------|-------------|
80
- | `gobi astra list-posts` | List posts in the current space |
81
- | `gobi astra get-post <id>` | Get a post and its replies |
82
- | `gobi astra create-post --title <t> --content <c>` | Create a post |
83
- | `gobi astra edit-post <id> --title <t>` | Edit a post |
84
- | `gobi astra delete-post <id>` | Delete a post |
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
- ### Replies
86
+ ### Threads
87
87
 
88
88
  | Command | Description |
89
89
  |---------|-------------|
90
- | `gobi astra create-reply <postId> --content <c>` | Reply to a post |
91
- | `gobi astra edit-reply <replyId> --content <c>` | Edit a reply |
92
- | `gobi astra delete-reply <replyId>` | Delete a reply |
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
- ### Sessions
96
+ ### Replies
95
97
 
96
98
  | Command | Description |
97
99
  |---------|-------------|
98
- | `gobi astra list-sessions` | List your sessions |
99
- | `gobi astra get-session <id>` | Get a session and its messages |
100
- | `gobi astra reply-session <id> --content <c>` | Send a message in a session |
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
- ### Brain updates
104
+ ### Sessions
104
105
 
105
106
  | Command | Description |
106
107
  |---------|-------------|
107
- | `gobi astra list-brain-updates` | List brain updates in the space |
108
- | `gobi astra create-brain-update --title <t> --content <c>` | Create a brain update |
109
- | `gobi astra edit-brain-update <id> --title <t>` | Edit a brain update |
110
- | `gobi astra delete-brain-update <id>` | Delete a brain update |
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 (astra commands) |
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
 
@@ -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 { WEBDRIVE_BASE_URL } from "../constants.js";
5
- import { getValidToken } from "../auth/manager.js";
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 astra = program
27
- .command("astra")
28
- .description("Astra commands (posts, sessions, brains, brain updates).")
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
- astra
10
+ space
32
11
  .command("warp")
33
- .description("Select the active space for astra commands.")
12
+ .description("Select the active space.")
34
13
  .action(async () => {
35
14
  const result = await selectSpace();
36
15
  if (result === null) {
@@ -38,146 +17,37 @@ export function registerAstraCommand(program) {
38
17
  return;
39
18
  }
40
19
  writeSpaceSetting(result.slug);
41
- if (isJsonMode(astra)) {
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
- // ── Brains ──
48
- astra
49
- .command("search-brain")
50
- .description("Search brains (second brains/vaults) in a space using text and semantic search.")
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
31
  .option("--offset <number>", "Offset for reply pagination", "0")
162
- .action(async (postId, opts) => {
163
- const spaceSlug = resolveSpaceSlug(astra);
164
- const resp = (await apiGet(`/spaces/${spaceSlug}/posts/${postId}`, {
32
+ .action(async (threadId, opts) => {
33
+ const spaceSlug = resolveSpaceSlug(space);
34
+ const resp = (await apiGet(`/spaces/${spaceSlug}/threads/${threadId}`, {
165
35
  limit: parseInt(opts.limit, 10),
166
36
  offset: parseInt(opts.offset, 10),
167
37
  }));
168
38
  const data = unwrapResp(resp);
169
39
  const pagination = (resp.pagination || {});
170
- if (isJsonMode(astra)) {
40
+ if (isJsonMode(space)) {
171
41
  jsonOut({ ...data, pagination });
172
42
  return;
173
43
  }
174
- const msg = (data.post || data);
44
+ const thread = (data.thread || data);
175
45
  const replies = (data.items || []);
176
46
  const totalReplies = pagination.total ||
177
- msg.replyCount ||
47
+ thread.replyCount ||
178
48
  0;
179
- const author = msg.author?.name ||
180
- `User ${msg.authorId}`;
49
+ const author = thread.author?.name ||
50
+ `User ${thread.authorId}`;
181
51
  const replyLines = [];
182
52
  for (const r of replies) {
183
53
  const rAuthor = r.author?.name ||
@@ -187,28 +57,28 @@ export function registerAstraCommand(program) {
187
57
  replyLines.push(` - ${rAuthor}: ${truncated} (${r.createdAt})`);
188
58
  }
189
59
  const output = [
190
- `Post: ${msg.title}`,
191
- `By: ${author} on ${msg.createdAt}`,
60
+ `Thread: ${thread.title}`,
61
+ `By: ${author} on ${thread.createdAt}`,
192
62
  "",
193
- msg.content,
63
+ thread.content,
194
64
  "",
195
65
  `Replies (${replies.length} of ${totalReplies}):`,
196
66
  ...replyLines,
197
67
  ].join("\n");
198
68
  console.log(output);
199
69
  });
200
- astra
201
- .command("list-posts")
202
- .description("List posts in a space (paginated).")
70
+ space
71
+ .command("list-threads")
72
+ .description("List threads in a space (paginated).")
203
73
  .option("--limit <number>", "Items per page", "20")
204
74
  .option("--offset <number>", "Offset for pagination", "0")
205
75
  .action(async (opts) => {
206
- const spaceSlug = resolveSpaceSlug(astra);
207
- const resp = (await apiGet(`/spaces/${spaceSlug}/posts`, {
76
+ const spaceSlug = resolveSpaceSlug(space);
77
+ const resp = (await apiGet(`/spaces/${spaceSlug}/threads`, {
208
78
  limit: parseInt(opts.limit, 10),
209
79
  offset: parseInt(opts.offset, 10),
210
80
  }));
211
- if (isJsonMode(astra)) {
81
+ if (isJsonMode(space)) {
212
82
  jsonOut({
213
83
  items: resp.data || [],
214
84
  pagination: resp.pagination || {},
@@ -218,344 +88,116 @@ export function registerAstraCommand(program) {
218
88
  const items = (resp.data || []);
219
89
  const pagination = (resp.pagination || {});
220
90
  if (!items.length) {
221
- console.log("No posts found.");
91
+ console.log("No threads found.");
222
92
  return;
223
93
  }
224
94
  const lines = [];
225
- for (const msg of items) {
226
- const author = msg.author?.name ||
227
- `User ${msg.authorId}`;
228
- lines.push(`- [${msg.id}] "${msg.title}" by ${author} (${msg.replyCount} replies, ${msg.createdAt})`);
95
+ for (const t of items) {
96
+ const author = t.author?.name ||
97
+ `User ${t.authorId}`;
98
+ lines.push(`- [${t.id}] "${t.title}" by ${author} (${t.replyCount} replies, ${t.createdAt})`);
229
99
  }
230
100
  const total = pagination.total || items.length;
231
- console.log(`Posts (${items.length} of ${total}):\n` + lines.join("\n"));
101
+ console.log(`Threads (${items.length} of ${total}):\n` + lines.join("\n"));
232
102
  });
233
- astra
234
- .command("create-post")
235
- .description("Create a post in a space.")
236
- .requiredOption("--title <title>", "Title of the post")
237
- .requiredOption("--content <content>", "Post content (markdown supported)")
103
+ space
104
+ .command("create-thread")
105
+ .description("Create a thread in a space.")
106
+ .requiredOption("--title <title>", "Title of the thread")
107
+ .requiredOption("--content <content>", "Thread content (markdown supported)")
238
108
  .action(async (opts) => {
239
- const spaceSlug = resolveSpaceSlug(astra);
240
- const resp = (await apiPost(`/spaces/${spaceSlug}/posts`, {
109
+ const spaceSlug = resolveSpaceSlug(space);
110
+ const resp = (await apiPost(`/spaces/${spaceSlug}/threads`, {
241
111
  title: opts.title,
242
112
  content: opts.content,
243
113
  }));
244
- const msg = unwrapResp(resp);
245
- if (isJsonMode(astra)) {
246
- jsonOut(msg);
247
- return;
248
- }
249
- console.log(`Post created!\n` +
250
- ` ID: ${msg.id}\n` +
251
- ` Title: ${msg.title}\n` +
252
- ` Created: ${msg.createdAt}`);
253
- });
254
- astra
255
- .command("edit-post <postId>")
256
- .description("Edit a post. You must be the author.")
257
- .option("--title <title>", "New title for the post")
258
- .option("--content <content>", "New content for the post (markdown supported)")
259
- .action(async (postId, opts) => {
114
+ const thread = unwrapResp(resp);
115
+ if (isJsonMode(space)) {
116
+ jsonOut(thread);
117
+ return;
118
+ }
119
+ console.log(`Thread created!\n` +
120
+ ` ID: ${thread.id}\n` +
121
+ ` Title: ${thread.title}\n` +
122
+ ` Created: ${thread.createdAt}`);
123
+ });
124
+ space
125
+ .command("edit-thread <threadId>")
126
+ .description("Edit a thread. You must be the author.")
127
+ .option("--title <title>", "New title for the thread")
128
+ .option("--content <content>", "New content for the thread (markdown supported)")
129
+ .action(async (threadId, opts) => {
260
130
  if (!opts.title && !opts.content) {
261
131
  throw new Error("Provide at least --title or --content to update.");
262
132
  }
263
- const spaceSlug = resolveSpaceSlug(astra);
133
+ const spaceSlug = resolveSpaceSlug(space);
264
134
  const body = {};
265
135
  if (opts.title != null)
266
136
  body.title = opts.title;
267
137
  if (opts.content != null)
268
138
  body.content = opts.content;
269
- const resp = (await apiPatch(`/spaces/${spaceSlug}/posts/${postId}`, body));
270
- const msg = unwrapResp(resp);
271
- if (isJsonMode(astra)) {
272
- jsonOut(msg);
139
+ const resp = (await apiPatch(`/spaces/${spaceSlug}/threads/${threadId}`, body));
140
+ const thread = unwrapResp(resp);
141
+ if (isJsonMode(space)) {
142
+ jsonOut(thread);
273
143
  return;
274
144
  }
275
- console.log(`Post edited!\n` +
276
- ` ID: ${msg.id}\n` +
277
- ` Title: ${msg.title}\n` +
278
- ` Edited: ${msg.editedAt}`);
145
+ console.log(`Thread edited!\n` +
146
+ ` ID: ${thread.id}\n` +
147
+ ` Title: ${thread.title}\n` +
148
+ ` Edited: ${thread.editedAt}`);
279
149
  });
280
- astra
281
- .command("delete-post <postId>")
282
- .description("Delete a post. You must be the author.")
283
- .action(async (postId) => {
284
- const spaceSlug = resolveSpaceSlug(astra);
285
- await apiDelete(`/spaces/${spaceSlug}/posts/${postId}`);
286
- if (isJsonMode(astra)) {
287
- jsonOut({ id: postId });
150
+ space
151
+ .command("delete-thread <threadId>")
152
+ .description("Delete a thread. You must be the author.")
153
+ .action(async (threadId) => {
154
+ const spaceSlug = resolveSpaceSlug(space);
155
+ await apiDelete(`/spaces/${spaceSlug}/threads/${threadId}`);
156
+ if (isJsonMode(space)) {
157
+ jsonOut({ id: threadId });
288
158
  return;
289
159
  }
290
- console.log(`Post ${postId} deleted.`);
160
+ console.log(`Thread ${threadId} deleted.`);
291
161
  });
292
162
  // ── Replies (create, edit, delete) ──
293
- astra
294
- .command("create-reply <postId>")
295
- .description("Create a reply to a post in a space.")
163
+ space
164
+ .command("create-reply <threadId>")
165
+ .description("Create a reply to a thread in a space.")
296
166
  .requiredOption("--content <content>", "Reply content (markdown supported)")
297
- .action(async (postId, opts) => {
298
- const spaceSlug = resolveSpaceSlug(astra);
299
- const resp = (await apiPost(`/spaces/${spaceSlug}/posts/${postId}/replies`, { content: opts.content }));
167
+ .action(async (threadId, opts) => {
168
+ const spaceSlug = resolveSpaceSlug(space);
169
+ const resp = (await apiPost(`/spaces/${spaceSlug}/threads/${threadId}/replies`, { content: opts.content }));
300
170
  const msg = unwrapResp(resp);
301
- if (isJsonMode(astra)) {
171
+ if (isJsonMode(space)) {
302
172
  jsonOut(msg);
303
173
  return;
304
174
  }
305
175
  console.log(`Reply created!\n ID: ${msg.id}\n Created: ${msg.createdAt}`);
306
176
  });
307
- astra
177
+ space
308
178
  .command("edit-reply <replyId>")
309
179
  .description("Edit a reply. You must be the author.")
310
180
  .requiredOption("--content <content>", "New content for the reply (markdown supported)")
311
181
  .action(async (replyId, opts) => {
312
- const spaceSlug = resolveSpaceSlug(astra);
182
+ const spaceSlug = resolveSpaceSlug(space);
313
183
  const resp = (await apiPatch(`/spaces/${spaceSlug}/replies/${replyId}`, { content: opts.content }));
314
184
  const msg = unwrapResp(resp);
315
- if (isJsonMode(astra)) {
185
+ if (isJsonMode(space)) {
316
186
  jsonOut(msg);
317
187
  return;
318
188
  }
319
189
  console.log(`Reply edited!\n ID: ${msg.id}\n Edited: ${msg.editedAt}`);
320
190
  });
321
- astra
191
+ space
322
192
  .command("delete-reply <replyId>")
323
193
  .description("Delete a reply. You must be the author.")
324
194
  .action(async (replyId) => {
325
- const spaceSlug = resolveSpaceSlug(astra);
195
+ const spaceSlug = resolveSpaceSlug(space);
326
196
  await apiDelete(`/spaces/${spaceSlug}/replies/${replyId}`);
327
- if (isJsonMode(astra)) {
197
+ if (isJsonMode(space)) {
328
198
  jsonOut({ replyId });
329
199
  return;
330
200
  }
331
201
  console.log(`Reply ${replyId} deleted.`);
332
202
  });
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
203
  }
@@ -0,0 +1,227 @@
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 for a vault (paginated).")
130
+ .option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
131
+ .option("--mine", "List only my own brain updates")
132
+ .option("--limit <number>", "Items per page", "20")
133
+ .option("--offset <number>", "Offset for pagination", "0")
134
+ .action(async (opts) => {
135
+ const params = {
136
+ limit: parseInt(opts.limit, 10),
137
+ offset: parseInt(opts.offset, 10),
138
+ };
139
+ if (opts.mine)
140
+ params.mine = true;
141
+ if (opts.vaultSlug)
142
+ params.vaultSlug = opts.vaultSlug;
143
+ const resp = (await apiGet(`/brain-updates`, params));
144
+ if (isJsonMode(brain)) {
145
+ jsonOut({
146
+ items: resp.data || [],
147
+ pagination: resp.pagination || {},
148
+ });
149
+ return;
150
+ }
151
+ const items = (resp.data || []);
152
+ const pagination = (resp.pagination || {});
153
+ if (!items.length) {
154
+ console.log("No brain updates found.");
155
+ return;
156
+ }
157
+ const lines = [];
158
+ for (const u of items) {
159
+ const author = u.author?.name ||
160
+ `User ${u.authorId}`;
161
+ const vaultSlug = u.vault?.vaultSlug ||
162
+ "?";
163
+ lines.push(`- [${u.id}] "${u.title}" by ${author} (vault: ${vaultSlug}, ${u.createdAt})`);
164
+ }
165
+ const total = pagination.total || items.length;
166
+ console.log(`Brain updates (${items.length} of ${total}):\n` + lines.join("\n"));
167
+ });
168
+ brain
169
+ .command("post-update")
170
+ .description("Post a brain update for a vault.")
171
+ .option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
172
+ .requiredOption("--title <title>", "Title of the update")
173
+ .requiredOption("--content <content>", "Update content (markdown supported)")
174
+ .action(async (opts) => {
175
+ const vaultSlug = resolveVaultSlug(opts);
176
+ const resp = (await apiPost(`/brain-updates/vault/${vaultSlug}`, {
177
+ title: opts.title,
178
+ content: opts.content,
179
+ }));
180
+ const u = unwrapResp(resp);
181
+ if (isJsonMode(brain)) {
182
+ jsonOut(u);
183
+ return;
184
+ }
185
+ console.log(`Brain update posted!\n` +
186
+ ` ID: ${u.id}\n` +
187
+ ` Title: ${u.title}\n` +
188
+ ` Vault: ${u.vaultSlug || vaultSlug}\n` +
189
+ ` Created: ${u.createdAt}`);
190
+ });
191
+ brain
192
+ .command("edit-update <updateId>")
193
+ .description("Edit a published brain update. You must be the author.")
194
+ .option("--title <title>", "New title for the update")
195
+ .option("--content <content>", "New content for the update (markdown supported)")
196
+ .action(async (updateId, opts) => {
197
+ if (!opts.title && !opts.content) {
198
+ throw new Error("Provide at least --title or --content to update.");
199
+ }
200
+ const body = {};
201
+ if (opts.title != null)
202
+ body.title = opts.title;
203
+ if (opts.content != null)
204
+ body.content = opts.content;
205
+ const resp = (await apiPatch(`/brain-updates/${updateId}`, body));
206
+ const u = unwrapResp(resp);
207
+ if (isJsonMode(brain)) {
208
+ jsonOut(u);
209
+ return;
210
+ }
211
+ console.log(`Brain update edited!\n` +
212
+ ` ID: ${u.id}\n` +
213
+ ` Title: ${u.title}\n` +
214
+ ` Updated: ${u.updatedAt}`);
215
+ });
216
+ brain
217
+ .command("delete-update <updateId>")
218
+ .description("Delete a published brain update. You must be the author.")
219
+ .action(async (updateId) => {
220
+ await apiDelete(`/brain-updates/${updateId}`);
221
+ if (isJsonMode(brain)) {
222
+ jsonOut({ id: updateId });
223
+ return;
224
+ }
225
+ console.log(`Brain update ${updateId} deleted.`);
226
+ });
227
+ }
@@ -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 astra warp' first.");
24
+ throw new Error("Space not set. Run 'gobi space warp' first.");
25
25
  }
26
26
  return slug;
27
27
  }
@@ -35,13 +35,21 @@ export function getVaultSlug() {
35
35
  }
36
36
  export function printContext() {
37
37
  const settings = readSettings();
38
- const slug = settings?.selectedSpaceSlug;
39
- if (!slug) {
40
- console.log("Not initialized. Run 'gobi init' to set up.");
38
+ const vault = settings?.vaultSlug;
39
+ const space = settings?.selectedSpaceSlug;
40
+ if (!vault && !space) {
41
+ console.log("Run 'gobi init' to set up, then 'gobi space warp' to select a space.");
42
+ return;
43
+ }
44
+ if (!vault) {
45
+ console.log("Vault not set. Run 'gobi init' to set up.");
46
+ return;
47
+ }
48
+ if (!space) {
49
+ console.log(`Vault: ${vault} | Space not set. Run 'gobi space warp' to select a space.`);
41
50
  return;
42
51
  }
43
- const vaultId = settings?.vaultSlug || "?";
44
- console.log(`Space: ${slug} | Vault: ${vaultId}`);
52
+ console.log(`Space: ${space} | Vault: ${vault}`);
45
53
  }
46
54
  function ensureSettingsDir() {
47
55
  const dir = join(process.cwd(), SETTINGS_DIR);
@@ -0,0 +1,138 @@
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("--offset <number>", "Offset for message pagination", "0")
13
+ .action(async (sessionId, opts) => {
14
+ const resp = (await apiGet(`/session/${sessionId}`, {
15
+ limit: parseInt(opts.limit, 10),
16
+ offset: parseInt(opts.offset, 10),
17
+ }));
18
+ const data = unwrapResp(resp);
19
+ if (isJsonMode(sessions)) {
20
+ jsonOut(data);
21
+ return;
22
+ }
23
+ const session = (data.session || data);
24
+ const messages = (data.messages || []);
25
+ const pagination = (data.pagination || {});
26
+ const totalMessages = pagination.total || messages.length;
27
+ const msgLines = [];
28
+ for (const m of messages) {
29
+ const author = m.author?.name ||
30
+ m.source ||
31
+ `User ${m.authorId}`;
32
+ const text = m.content;
33
+ const truncated = text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
34
+ msgLines.push(` - ${author}: ${truncated} (${m.createdAt})`);
35
+ }
36
+ const output = [
37
+ `Session: ${session.title}`,
38
+ ` ID: ${session.id}`,
39
+ ` Mode: ${session.mode}`,
40
+ ` Last activity: ${session.lastMessageAt}`,
41
+ "",
42
+ `Messages (${messages.length} of ${totalMessages}):`,
43
+ ...msgLines,
44
+ ].join("\n");
45
+ console.log(output);
46
+ });
47
+ // ── List ──
48
+ sessions
49
+ .command("list")
50
+ .description("List all sessions you are part of, sorted by most recent activity.")
51
+ .option("--space-slug <spaceSlug>", "Filter by space slug")
52
+ .option("--limit <number>", "Items per page", "20")
53
+ .option("--offset <number>", "Offset for pagination", "0")
54
+ .action(async (opts) => {
55
+ const limit = parseInt(opts.limit, 10);
56
+ const offset = parseInt(opts.offset, 10);
57
+ const query = { limit, offset };
58
+ if (opts.spaceSlug)
59
+ query.spaceSlug = opts.spaceSlug;
60
+ const resp = (await apiGet(`/session/my-sessions`, query));
61
+ const items = (resp.data || []);
62
+ const pagination = (resp.pagination || {});
63
+ const total = pagination.total ?? items.length;
64
+ if (isJsonMode(sessions)) {
65
+ jsonOut({
66
+ items,
67
+ pagination: resp.pagination || {},
68
+ });
69
+ return;
70
+ }
71
+ if (!items.length) {
72
+ console.log("No sessions found.");
73
+ return;
74
+ }
75
+ const lines = [];
76
+ for (const s of items) {
77
+ const title = s.title || "(no title)";
78
+ const members = s.members || [];
79
+ const memberCount = s.memberCount ?? 0;
80
+ let memberInfo = "";
81
+ if (members.length > 0) {
82
+ const names = members.map((m) => m.vaultName || m.name || "Unknown");
83
+ const overflow = memberCount - members.length - 1; // -1 for "me"
84
+ memberInfo = ` | with: ${names.join(", ")}`;
85
+ if (overflow > 0)
86
+ memberInfo += ` +${overflow} more`;
87
+ }
88
+ lines.push(`- [${s.id}] "${title}" (mode: ${s.mode}, last activity: ${s.lastMessageAt})${memberInfo}`);
89
+ }
90
+ console.log(`Sessions (${items.length} of ${total}):\n` + lines.join("\n"));
91
+ });
92
+ // ── Reply ──
93
+ sessions
94
+ .command("reply <sessionId>")
95
+ .description("Send a human reply to a session you are a member of.")
96
+ .requiredOption("--content <content>", "Reply content (markdown supported)")
97
+ .action(async (sessionId, opts) => {
98
+ const resp = (await apiPost(`/session/${sessionId}/reply`, {
99
+ content: opts.content,
100
+ }));
101
+ const msg = unwrapResp(resp);
102
+ if (isJsonMode(sessions)) {
103
+ jsonOut(msg);
104
+ return;
105
+ }
106
+ console.log(`Reply sent!\n` +
107
+ ` Message ID: ${msg.id}\n` +
108
+ ` Source: ${msg.source}\n` +
109
+ ` Created: ${msg.createdAt}`);
110
+ });
111
+ // ── Update ──
112
+ sessions
113
+ .command("update <sessionId>")
114
+ .description('Update a session. "auto" lets the AI respond automatically; "manual" requires human replies.')
115
+ .option("--mode <mode>", 'Session mode: "auto" or "manual"')
116
+ .action(async (sessionId, opts) => {
117
+ if (!opts.mode) {
118
+ throw new Error("Provide at least one option to update (e.g. --mode).");
119
+ }
120
+ const body = {};
121
+ if (opts.mode != null) {
122
+ if (opts.mode !== "auto" && opts.mode !== "manual") {
123
+ throw new Error('Invalid mode. Must be "auto" or "manual".');
124
+ }
125
+ body.mode = opts.mode;
126
+ }
127
+ const resp = (await apiPatch(`/session/${sessionId}`, body));
128
+ const data = unwrapResp(resp);
129
+ if (isJsonMode(sessions)) {
130
+ jsonOut(data);
131
+ return;
132
+ }
133
+ const session = (data.session || data);
134
+ console.log(`Session updated!\n` +
135
+ ` ID: ${session.id}\n` +
136
+ ` Mode: ${session.mode}`);
137
+ });
138
+ }
@@ -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) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",