@gobi-ai/cli 0.9.13 → 1.0.0

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.
@@ -1,347 +0,0 @@
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
- import { extractWikiLinks, uploadAttachments } from "../attachments.js";
9
- export function registerBrainCommand(program) {
10
- const brain = program
11
- .command("brain")
12
- .description("Brain commands (search, ask, publish, unpublish, updates).");
13
- // ── Search ──
14
- brain
15
- .command("search")
16
- .description("Search public brains by text and semantic similarity.")
17
- .requiredOption("--query <query>", "Search query")
18
- .action(async (opts) => {
19
- const resp = (await apiGet(`/vault/public/search`, {
20
- query: opts.query,
21
- }));
22
- const results = (Array.isArray(resp) ? resp : resp.data || resp);
23
- if (isJsonMode(brain)) {
24
- jsonOut(results || []);
25
- return;
26
- }
27
- if (!results || results.length === 0) {
28
- console.log(`No brains found matching "${opts.query}".`);
29
- return;
30
- }
31
- const lines = [];
32
- for (const entry of results) {
33
- const vault = (entry.vault || entry);
34
- const owner = (entry.owner || {});
35
- const ownerName = owner.name ? ` by ${owner.name}` : "";
36
- const sim = entry.similarity != null
37
- ? ` [similarity: ${entry.similarity.toFixed(3)}]`
38
- : "";
39
- const spaceSlug = (entry.spaceSlug || vault.spaceSlug || "");
40
- const vaultSlug = (vault.slug || vault.vaultSlug || vault.id || "N/A");
41
- lines.push(`- ${vault.name || vault.title || "N/A"} (vault: ${vaultSlug}, space: ${spaceSlug || "N/A"})${ownerName}${sim}`);
42
- }
43
- console.log(`Brains matching "${opts.query}":\n` + lines.join("\n"));
44
- });
45
- // ── Ask ──
46
- brain
47
- .command("ask")
48
- .description("Ask a brain a question. Creates a targeted session (1:1 conversation).")
49
- .requiredOption("--vault-slug <vaultSlug>", "Slug of the brain/vault to ask")
50
- .option("--question <question>", "The question to ask (markdown supported)")
51
- .option("--rich-text <richText>", "Rich-text JSON array (e.g. [{\"type\":\"text\",\"text\":\"hello\"}])")
52
- .option("--mode <mode>", 'Session mode: "auto" or "manual"')
53
- .action(async (opts) => {
54
- if (!opts.question && !opts.richText) {
55
- throw new Error("Provide either --question or --rich-text.");
56
- }
57
- if (opts.question && opts.richText) {
58
- throw new Error("--question and --rich-text are mutually exclusive.");
59
- }
60
- const body = {
61
- vaultSlug: opts.vaultSlug,
62
- };
63
- if (opts.question != null)
64
- body.question = opts.question;
65
- if (opts.richText != null) {
66
- let parsed;
67
- try {
68
- parsed = JSON.parse(opts.richText);
69
- }
70
- catch {
71
- throw new Error("Invalid --rich-text JSON.");
72
- }
73
- body.richText = parsed;
74
- }
75
- if (opts.mode != null)
76
- body.mode = opts.mode;
77
- const resp = (await apiPost(`/chat/targeted`, body));
78
- const data = unwrapResp(resp);
79
- if (isJsonMode(brain)) {
80
- jsonOut(data);
81
- return;
82
- }
83
- const session = (data.session || {});
84
- const members = (data.members || []);
85
- console.log(`Session created!\n` +
86
- ` Session ID: ${session.id}\n` +
87
- ` Mode: ${session.mode}\n` +
88
- ` Members: ${members.length}\n` +
89
- ` Question sent.`);
90
- });
91
- // ── Publish ──
92
- brain
93
- .command("publish")
94
- .description("Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (brain sync, metadata update, Discord notification).")
95
- .action(async () => {
96
- const vaultId = getVaultSlug();
97
- const filePath = join(process.cwd(), "BRAIN.md");
98
- if (!existsSync(filePath)) {
99
- throw new Error(`BRAIN.md not found in ${process.cwd()}`);
100
- }
101
- const content = readFileSync(filePath, "utf-8");
102
- const token = await getValidToken();
103
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
104
- const res = await fetch(url, {
105
- method: "PUT",
106
- headers: {
107
- Authorization: `Bearer ${token}`,
108
- "Content-Type": "text/markdown",
109
- },
110
- body: content,
111
- });
112
- if (!res.ok) {
113
- throw new Error(`Upload failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
114
- }
115
- if (isJsonMode(brain)) {
116
- jsonOut({ vaultId });
117
- return;
118
- }
119
- console.log(`Published BRAIN.md to vault "${vaultId}"`);
120
- });
121
- // ── Unpublish ──
122
- brain
123
- .command("unpublish")
124
- .description("Delete BRAIN.md from the vault on webdrive.")
125
- .action(async () => {
126
- const vaultId = getVaultSlug();
127
- const token = await getValidToken();
128
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
129
- const res = await fetch(url, {
130
- method: "DELETE",
131
- headers: { Authorization: `Bearer ${token}` },
132
- });
133
- if (!res.ok) {
134
- throw new Error(`Delete failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
135
- }
136
- if (isJsonMode(brain)) {
137
- jsonOut({ vaultId });
138
- return;
139
- }
140
- console.log(`Deleted BRAIN.md from vault "${vaultId}"`);
141
- });
142
- // ── Updates (list, post, edit, delete) ──
143
- brain
144
- .command("list-updates")
145
- .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.")
146
- .option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
147
- .option("--space-slug <spaceSlug>", "List updates for a space")
148
- .option("--mine", "List only my own brain updates")
149
- .option("--limit <number>", "Items per page", "20")
150
- .option("--cursor <string>", "Pagination cursor from previous response")
151
- .action(async (opts) => {
152
- const params = {
153
- limit: parseInt(opts.limit, 10),
154
- };
155
- if (opts.cursor)
156
- params.cursor = opts.cursor;
157
- if (opts.mine)
158
- params.mine = true;
159
- if (opts.vaultSlug)
160
- params.vaultSlug = opts.vaultSlug;
161
- const path = opts.spaceSlug
162
- ? `/spaces/${opts.spaceSlug}/brain-updates`
163
- : `/brain-updates`;
164
- const resp = (await apiGet(path, params));
165
- if (isJsonMode(brain)) {
166
- jsonOut({
167
- items: resp.data || [],
168
- pagination: resp.pagination || {},
169
- });
170
- return;
171
- }
172
- const items = (resp.data || []);
173
- const pagination = (resp.pagination || {});
174
- if (!items.length) {
175
- console.log("No brain updates found.");
176
- return;
177
- }
178
- const lines = [];
179
- for (const u of items) {
180
- const author = u.author?.name ||
181
- `User ${u.authorId}`;
182
- const vaultSlug = u.vault?.vaultSlug ||
183
- "?";
184
- lines.push(`- [${u.id}] "${u.title}" by ${author} (vault: ${vaultSlug}, ${u.createdAt})`);
185
- }
186
- const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
187
- console.log(`Brain updates (${items.length} items):\n` + lines.join("\n") + footer);
188
- });
189
- brain
190
- .command("post-update")
191
- .description("Post a brain update for a vault.")
192
- .option("--vault-slug <vaultSlug>", "Vault slug (overrides .gobi/settings.yaml)")
193
- .requiredOption("--title <title>", "Title of the update")
194
- .requiredOption("--content <content>", "Update content (markdown supported)")
195
- .option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting")
196
- .action(async (opts) => {
197
- const vaultSlug = resolveVaultSlug(opts);
198
- if (opts.autoAttachments) {
199
- const token = await getValidToken();
200
- const links = extractWikiLinks(opts.content);
201
- await uploadAttachments(vaultSlug, links, token, { addToSyncfiles: true });
202
- }
203
- const resp = (await apiPost(`/brain-updates/vault/${vaultSlug}`, {
204
- title: opts.title,
205
- content: opts.content,
206
- }));
207
- const u = unwrapResp(resp);
208
- if (isJsonMode(brain)) {
209
- jsonOut(u);
210
- return;
211
- }
212
- console.log(`Brain update posted!\n` +
213
- ` ID: ${u.id}\n` +
214
- ` Title: ${u.title}\n` +
215
- ` Vault: ${u.vaultSlug || vaultSlug}\n` +
216
- ` Created: ${u.createdAt}`);
217
- });
218
- brain
219
- .command("edit-update <updateId>")
220
- .description("Edit a published brain update. You must be the author.")
221
- .option("--title <title>", "New title for the update")
222
- .option("--content <content>", "New content for the update (markdown supported)")
223
- .option("--vault-slug <vaultSlug>", "Vault slug for attachment uploads (overrides .gobi/settings.yaml)")
224
- .option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before editing")
225
- .action(async (updateId, opts) => {
226
- if (!opts.title && !opts.content) {
227
- throw new Error("Provide at least --title or --content to update.");
228
- }
229
- if (opts.autoAttachments && opts.content) {
230
- const vaultSlug = resolveVaultSlug(opts);
231
- const token = await getValidToken();
232
- const links = extractWikiLinks(opts.content);
233
- await uploadAttachments(vaultSlug, links, token, { addToSyncfiles: true });
234
- }
235
- const body = {};
236
- if (opts.title != null)
237
- body.title = opts.title;
238
- if (opts.content != null)
239
- body.content = opts.content;
240
- const resp = (await apiPatch(`/brain-updates/${updateId}`, body));
241
- const u = unwrapResp(resp);
242
- if (isJsonMode(brain)) {
243
- jsonOut(u);
244
- return;
245
- }
246
- console.log(`Brain update edited!\n` +
247
- ` ID: ${u.id}\n` +
248
- ` Title: ${u.title}\n` +
249
- ` Updated: ${u.updatedAt}`);
250
- });
251
- brain
252
- .command("delete-update <updateId>")
253
- .description("Delete a published brain update. You must be the author.")
254
- .action(async (updateId) => {
255
- await apiDelete(`/brain-updates/${updateId}`);
256
- if (isJsonMode(brain)) {
257
- jsonOut({ id: updateId });
258
- return;
259
- }
260
- console.log(`Brain update ${updateId} deleted.`);
261
- });
262
- // ── Update Replies (get-update, reply-to-update, edit-update-reply, delete-update-reply) ──
263
- brain
264
- .command("get-update <updateId>")
265
- .description("Get a brain update and its replies (paginated).")
266
- .option("--limit <number>", "Replies per page", "20")
267
- .option("--cursor <string>", "Pagination cursor from previous response")
268
- .option("--full", "Show full reply content without truncation")
269
- .action(async (updateId, opts) => {
270
- const params = {
271
- limit: parseInt(opts.limit, 10),
272
- };
273
- if (opts.cursor)
274
- params.cursor = opts.cursor;
275
- const resp = (await apiGet(`/brain-updates/${updateId}`, params));
276
- const data = unwrapResp(resp);
277
- const pagination = (resp.pagination || {});
278
- if (isJsonMode(brain)) {
279
- jsonOut({ ...data, pagination });
280
- return;
281
- }
282
- const update = (data.update || data);
283
- const replies = (data.replies || []);
284
- const author = update.author?.name ||
285
- `User ${update.authorId}`;
286
- const vault = update.vault?.vaultSlug || "?";
287
- const replyLines = [];
288
- for (const r of replies) {
289
- const rAuthor = r.author?.name ||
290
- `User ${r.authorId}`;
291
- const text = r.content;
292
- const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "\u2026";
293
- replyLines.push(` - ${rAuthor}: ${truncated} (${r.createdAt})`);
294
- }
295
- const output = [
296
- `Brain Update: ${update.title || "(no title)"}`,
297
- `By: ${author} (vault: ${vault}) on ${update.createdAt}`,
298
- "",
299
- update.content,
300
- "",
301
- `Replies (${replies.length} items):`,
302
- ...replyLines,
303
- ...(pagination.hasMore
304
- ? [` Next cursor: ${pagination.nextCursor}`]
305
- : []),
306
- ].join("\n");
307
- console.log(output);
308
- });
309
- brain
310
- .command("reply-to-update <updateId>")
311
- .description("Reply to a brain update.")
312
- .requiredOption("--content <content>", 'Reply content (markdown supported, use "-" for stdin)')
313
- .action(async (updateId, opts) => {
314
- const content = opts.content === "-" ? readFileSync("/dev/stdin", "utf8") : opts.content;
315
- const resp = (await apiPost(`/brain-updates/${updateId}/replies`, { content }));
316
- const reply = unwrapResp(resp);
317
- if (isJsonMode(brain)) {
318
- jsonOut(reply);
319
- return;
320
- }
321
- console.log(`Reply created!\n ID: ${reply.id}\n Created: ${reply.createdAt}`);
322
- });
323
- brain
324
- .command("edit-update-reply <replyId>")
325
- .description("Edit a brain update reply. You must be the author.")
326
- .requiredOption("--content <content>", "New content for the reply (markdown supported)")
327
- .action(async (replyId, opts) => {
328
- const resp = (await apiPatch(`/brain-updates/replies/${replyId}`, { content: opts.content }));
329
- const reply = unwrapResp(resp);
330
- if (isJsonMode(brain)) {
331
- jsonOut(reply);
332
- return;
333
- }
334
- console.log(`Reply edited!\n ID: ${reply.id}\n Edited: ${reply.editedAt}`);
335
- });
336
- brain
337
- .command("delete-update-reply <replyId>")
338
- .description("Delete a brain update reply. You must be the author.")
339
- .action(async (replyId) => {
340
- await apiDelete(`/brain-updates/replies/${replyId}`);
341
- if (isJsonMode(brain)) {
342
- jsonOut({ replyId });
343
- return;
344
- }
345
- console.log(`Brain update reply ${replyId} deleted.`);
346
- });
347
- }
@@ -1,181 +0,0 @@
1
- # gobi brain
2
-
3
- ```
4
- Usage: gobi brain [options] [command]
5
-
6
- Brain commands (search, ask, publish, unpublish, updates).
7
-
8
- Options:
9
- -h, --help display help for command
10
-
11
- Commands:
12
- search [options] Search public brains by text and semantic similarity.
13
- ask [options] Ask a brain a question. Creates a targeted session (1:1 conversation).
14
- publish Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (brain sync, metadata update, Discord notification).
15
- unpublish Delete BRAIN.md from the vault on webdrive.
16
- list-updates [options] 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
17
- by you.
18
- post-update [options] Post a brain update for a vault.
19
- edit-update [options] <updateId> Edit a published brain update. You must be the author.
20
- delete-update <updateId> Delete a published brain update. You must be the author.
21
- get-update [options] <updateId> Get a brain update and its replies (paginated).
22
- reply-to-update [options] <updateId> Reply to a brain update.
23
- edit-update-reply [options] <replyId> Edit a brain update reply. You must be the author.
24
- delete-update-reply <replyId> Delete a brain update reply. You must be the author.
25
- help [command] display help for command
26
- ```
27
-
28
- ## search
29
-
30
- ```
31
- Usage: gobi brain search [options]
32
-
33
- Search public brains by text and semantic similarity.
34
-
35
- Options:
36
- --query <query> Search query
37
- -h, --help display help for command
38
- ```
39
-
40
- ## ask
41
-
42
- ```
43
- Usage: gobi brain ask [options]
44
-
45
- Ask a brain a question. Creates a targeted session (1:1 conversation).
46
-
47
- Options:
48
- --vault-slug <vaultSlug> Slug of the brain/vault to ask
49
- --question <question> The question to ask (markdown supported)
50
- --rich-text <richText> Rich-text JSON array (e.g. [{"type":"text","text":"hello"}])
51
- --mode <mode> Session mode: "auto" or "manual"
52
- -h, --help display help for command
53
- ```
54
-
55
- ## publish
56
-
57
- ```
58
- Usage: gobi brain publish [options]
59
-
60
- Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (brain sync, metadata update, Discord notification).
61
-
62
- Options:
63
- -h, --help display help for command
64
- ```
65
-
66
- ## unpublish
67
-
68
- ```
69
- Usage: gobi brain unpublish [options]
70
-
71
- Delete BRAIN.md from the vault on webdrive.
72
-
73
- Options:
74
- -h, --help display help for command
75
- ```
76
-
77
- ## list-updates
78
-
79
- ```
80
- Usage: gobi brain list-updates [options]
81
-
82
- 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.
83
-
84
- Options:
85
- --vault-slug <vaultSlug> Vault slug (overrides .gobi/settings.yaml)
86
- --space-slug <spaceSlug> List updates for a space
87
- --mine List only my own brain updates
88
- --limit <number> Items per page (default: "20")
89
- --cursor <string> Pagination cursor from previous response
90
- -h, --help display help for command
91
- ```
92
-
93
- ## post-update
94
-
95
- ```
96
- Usage: gobi brain post-update [options]
97
-
98
- Post a brain update for a vault.
99
-
100
- Options:
101
- --vault-slug <vaultSlug> Vault slug (overrides .gobi/settings.yaml)
102
- --title <title> Title of the update
103
- --content <content> Update content (markdown supported)
104
- --auto-attachments Upload wiki-linked [[files]] to webdrive before posting
105
- -h, --help display help for command
106
- ```
107
-
108
- ## edit-update
109
-
110
- ```
111
- Usage: gobi brain edit-update [options] <updateId>
112
-
113
- Edit a published brain update. You must be the author.
114
-
115
- Options:
116
- --title <title> New title for the update
117
- --content <content> New content for the update (markdown supported)
118
- --vault-slug <vaultSlug> Vault slug for attachment uploads (overrides .gobi/settings.yaml)
119
- --auto-attachments Upload wiki-linked [[files]] to webdrive before editing
120
- -h, --help display help for command
121
- ```
122
-
123
- ## delete-update
124
-
125
- ```
126
- Usage: gobi brain delete-update [options] <updateId>
127
-
128
- Delete a published brain update. You must be the author.
129
-
130
- Options:
131
- -h, --help display help for command
132
- ```
133
-
134
- ## get-update
135
-
136
- ```
137
- Usage: gobi brain get-update [options] <updateId>
138
-
139
- Get a brain update and its replies (paginated).
140
-
141
- Options:
142
- --limit <number> Replies per page (default: "20")
143
- --cursor <string> Pagination cursor from previous response
144
- --full Show full reply content without truncation
145
- -h, --help display help for command
146
- ```
147
-
148
- ## reply-to-update
149
-
150
- ```
151
- Usage: gobi brain reply-to-update [options] <updateId>
152
-
153
- Reply to a brain update.
154
-
155
- Options:
156
- --content <content> Reply content (markdown supported, use "-" for stdin)
157
- -h, --help display help for command
158
- ```
159
-
160
- ## edit-update-reply
161
-
162
- ```
163
- Usage: gobi brain edit-update-reply [options] <replyId>
164
-
165
- Edit a brain update reply. You must be the author.
166
-
167
- Options:
168
- --content <content> New content for the reply (markdown supported)
169
- -h, --help display help for command
170
- ```
171
-
172
- ## delete-update-reply
173
-
174
- ```
175
- Usage: gobi brain delete-update-reply [options] <replyId>
176
-
177
- Delete a brain update reply. You must be the author.
178
-
179
- Options:
180
- -h, --help display help for command
181
- ```