@gobi-ai/cli 1.0.0 → 1.2.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.
@@ -3,7 +3,7 @@
3
3
  ```
4
4
  Usage: gobi space [options] [command]
5
5
 
6
- Space commands. A Space is a shared room of members where they post threads and replies, organized by topics.
6
+ Space commands (threads, replies).
7
7
 
8
8
  Options:
9
9
  --space-slug <slug> Space slug (overrides .gobi/settings.yaml)
@@ -11,12 +11,9 @@ Options:
11
11
 
12
12
  Commands:
13
13
  list List spaces you are a member of.
14
- get [spaceSlug] Get details for a space. Pass a slug or omit to use the current space (from .gobi/settings.yaml or --space-slug).
15
14
  warp [spaceSlug] Select the active space. Pass a slug to warp directly, or omit for interactive selection.
16
15
  list-topics [options] List topics in a space, ordered by most recent content linkage.
17
16
  list-topic-threads [options] <topicSlug> List threads tagged with a topic in a space (cursor-paginated).
18
- messages [options] List the unified message feed (threads and replies, newest first) in a space.
19
- ancestors <threadId> Show the ancestor lineage of a thread or reply (root → immediate parent).
20
17
  get-thread [options] <threadId> Get a thread and its replies (paginated).
21
18
  list-threads [options] List threads in a space (paginated).
22
19
  create-thread [options] Create a thread in a space.
@@ -28,17 +25,6 @@ Commands:
28
25
  help [command] display help for command
29
26
  ```
30
27
 
31
- ## get
32
-
33
- ```
34
- Usage: gobi space get [options] [spaceSlug]
35
-
36
- Get details for a space. Pass a slug or omit to use the current space (from .gobi/settings.yaml or --space-slug).
37
-
38
- Options:
39
- -h, --help display help for command
40
- ```
41
-
42
28
  ## list-topics
43
29
 
44
30
  ```
@@ -64,30 +50,6 @@ Options:
64
50
  -h, --help display help for command
65
51
  ```
66
52
 
67
- ## messages
68
-
69
- ```
70
- Usage: gobi space messages [options]
71
-
72
- List the unified message feed (threads and replies, newest first) in a space.
73
-
74
- Options:
75
- --limit <number> Items per page (default: "20")
76
- --cursor <string> Pagination cursor from previous response
77
- -h, --help display help for command
78
- ```
79
-
80
- ## ancestors
81
-
82
- ```
83
- Usage: gobi space ancestors [options] <threadId>
84
-
85
- Show the ancestor lineage of a thread or reply (root → immediate parent).
86
-
87
- Options:
88
- -h, --help display help for command
89
- ```
90
-
91
53
  ## get-thread
92
54
 
93
55
  ```
@@ -1,203 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import { apiGet, apiPost } from "../client.js";
3
- import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
4
- function readContent(value) {
5
- if (value === "-")
6
- return readFileSync("/dev/stdin", "utf8");
7
- return value;
8
- }
9
- function formatMessageLine(m) {
10
- const isReply = m.parentThreadId != null;
11
- const id = `[${isReply ? "r" : "t"}:${m.id}]`;
12
- const kind = isReply ? "reply " : "thread";
13
- const author = m.author?.name ||
14
- `User ${m.authorId ?? "?"}`;
15
- let label;
16
- if (isReply) {
17
- const text = m.content || "";
18
- label = text.length > 80 ? text.slice(0, 80) + "…" : text;
19
- label = label.replace(/\s+/g, " ").trim();
20
- }
21
- else {
22
- label = m.title || m.content || "";
23
- }
24
- return `${id} ${kind} ${author} "${label}" ${m.createdAt}`;
25
- }
26
- export function registerGlobalCommand(program) {
27
- const global = program
28
- .command("global")
29
- .description("Global thread commands. Global is the platform-wide thread feed visible to everyone on Gobi.");
30
- // ── Messages (unified feed) ──
31
- global
32
- .command("messages")
33
- .description("List the global unified message feed (threads and replies, newest first).")
34
- .option("--limit <number>", "Items per page", "20")
35
- .option("--cursor <string>", "Pagination cursor from previous response")
36
- .action(async (opts) => {
37
- const params = {
38
- limit: parseInt(opts.limit, 10),
39
- };
40
- if (opts.cursor)
41
- params.cursor = opts.cursor;
42
- const resp = (await apiGet(`/global/messages`, params));
43
- if (isJsonMode(global)) {
44
- jsonOut({
45
- items: resp.data || [],
46
- pagination: resp.pagination || {},
47
- });
48
- return;
49
- }
50
- const items = (resp.data || []);
51
- const pagination = (resp.pagination || {});
52
- if (!items.length) {
53
- console.log("No messages found.");
54
- return;
55
- }
56
- const lines = items.map(formatMessageLine);
57
- const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
58
- console.log(`Global messages (${items.length} items, newest first):\n` + lines.join("\n") + footer);
59
- });
60
- // ── Get thread ──
61
- global
62
- .command("get-thread <threadId>")
63
- .description("Get a global thread and its direct replies (paginated).")
64
- .option("--limit <number>", "Replies per page", "20")
65
- .option("--cursor <string>", "Pagination cursor from previous response")
66
- .action(async (threadId, opts) => {
67
- const params = {
68
- limit: parseInt(opts.limit, 10),
69
- };
70
- if (opts.cursor)
71
- params.cursor = opts.cursor;
72
- const resp = (await apiGet(`/global/threads/${threadId}`, params));
73
- const data = unwrapResp(resp);
74
- const pagination = (resp.pagination || {});
75
- if (isJsonMode(global)) {
76
- jsonOut({ ...data, pagination });
77
- return;
78
- }
79
- const thread = (data.thread || data);
80
- const replies = (data.items || []);
81
- const author = thread.author?.name ||
82
- `User ${thread.authorId}`;
83
- const replyLines = [];
84
- for (const r of replies) {
85
- const rAuthor = r.author?.name ||
86
- `User ${r.authorId}`;
87
- const text = r.content || "";
88
- const truncated = text.length > 200 ? text.slice(0, 200) + "…" : text;
89
- replyLines.push(` - ${rAuthor}: ${truncated} (${r.createdAt})`);
90
- }
91
- const isReply = thread.parentThreadId != null;
92
- const heading = isReply
93
- ? `Reply [r:${thread.id}]`
94
- : `Thread: ${thread.title || "(no title)"}`;
95
- const output = [
96
- heading,
97
- `By: ${author} on ${thread.createdAt}`,
98
- "",
99
- thread.content,
100
- "",
101
- `Replies (${replies.length} items):`,
102
- ...replyLines,
103
- ...(pagination.hasMore ? [` Next cursor: ${pagination.nextCursor}`] : []),
104
- ].join("\n");
105
- console.log(output);
106
- });
107
- // ── Ancestors ──
108
- global
109
- .command("ancestors <threadId>")
110
- .description("Show the ancestor lineage of a global thread or reply (root → immediate parent).")
111
- .action(async (threadId) => {
112
- const resp = (await apiGet(`/global/threads/${threadId}/ancestors`));
113
- const data = unwrapResp(resp);
114
- const ancestors = (data.ancestors || []);
115
- if (isJsonMode(global)) {
116
- jsonOut({ ancestors });
117
- return;
118
- }
119
- if (!ancestors.length) {
120
- console.log("No ancestors (this is a root thread).");
121
- return;
122
- }
123
- const lines = [];
124
- ancestors.forEach((a, i) => {
125
- lines.push(`${i + 1}. ${formatMessageLine(a)}`);
126
- });
127
- console.log(`Ancestors (${ancestors.length} items, root first):\n` + lines.join("\n"));
128
- });
129
- // ── Create thread ──
130
- global
131
- .command("create-thread")
132
- .description("Create a global thread (visible platform-wide).")
133
- .option("--title <title>", "Title of the thread")
134
- .option("--content <content>", "Thread content (markdown supported, use \"-\" for stdin)")
135
- .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
136
- .action(async (opts) => {
137
- if (!opts.content && !opts.richText) {
138
- throw new Error("Provide either --content or --rich-text.");
139
- }
140
- if (opts.content && opts.richText) {
141
- throw new Error("--content and --rich-text are mutually exclusive.");
142
- }
143
- const body = {};
144
- if (opts.title != null)
145
- body.title = opts.title;
146
- if (opts.content != null)
147
- body.content = readContent(opts.content);
148
- if (opts.richText != null) {
149
- let parsed;
150
- try {
151
- parsed = JSON.parse(opts.richText);
152
- }
153
- catch {
154
- throw new Error("Invalid --rich-text JSON.");
155
- }
156
- body.richText = parsed;
157
- }
158
- const resp = (await apiPost(`/global/threads`, body));
159
- const thread = unwrapResp(resp);
160
- if (isJsonMode(global)) {
161
- jsonOut(thread);
162
- return;
163
- }
164
- console.log(`Global thread created!\n` +
165
- ` ID: ${thread.id}\n` +
166
- (thread.title ? ` Title: ${thread.title}\n` : "") +
167
- ` Created: ${thread.createdAt}`);
168
- });
169
- // ── Reply ──
170
- global
171
- .command("reply <threadId>")
172
- .description("Reply to a global thread.")
173
- .option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
174
- .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
175
- .action(async (threadId, opts) => {
176
- if (!opts.content && !opts.richText) {
177
- throw new Error("Provide either --content or --rich-text.");
178
- }
179
- if (opts.content && opts.richText) {
180
- throw new Error("--content and --rich-text are mutually exclusive.");
181
- }
182
- const body = {};
183
- if (opts.content != null)
184
- body.content = readContent(opts.content);
185
- if (opts.richText != null) {
186
- let parsed;
187
- try {
188
- parsed = JSON.parse(opts.richText);
189
- }
190
- catch {
191
- throw new Error("Invalid --rich-text JSON.");
192
- }
193
- body.richText = parsed;
194
- }
195
- const resp = (await apiPost(`/global/threads/${threadId}/replies`, body));
196
- const reply = unwrapResp(resp);
197
- if (isJsonMode(global)) {
198
- jsonOut(reply);
199
- return;
200
- }
201
- console.log(`Reply created!\n ID: ${reply.id}\n Created: ${reply.createdAt}`);
202
- });
203
- }
@@ -1,143 +0,0 @@
1
- import { existsSync, readFileSync } from "fs";
2
- import { join } from "path";
3
- import { apiGet, apiPost } 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, unwrapResp } from "./utils.js";
8
- export function registerVaultCommand(program) {
9
- const vaultCmd = program
10
- .command("vault")
11
- .description("Vault commands. A Vault is your personal knowledge container — search public vaults, ask them questions, and publish your BRAIN.md profile.");
12
- // ── Search ──
13
- vaultCmd
14
- .command("search")
15
- .description("Search public vaults 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(vaultCmd)) {
23
- jsonOut(results || []);
24
- return;
25
- }
26
- if (!results || results.length === 0) {
27
- console.log(`No vaults 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(`Vaults matching "${opts.query}":\n` + lines.join("\n"));
43
- });
44
- // ── Ask ──
45
- vaultCmd
46
- .command("ask")
47
- .description("Ask a vault a question. Creates a targeted session (1:1 conversation).")
48
- .requiredOption("--vault-slug <vaultSlug>", "Slug of the vault to ask")
49
- .option("--question <question>", "The question to ask (markdown supported)")
50
- .option("--rich-text <richText>", "Rich-text JSON array (e.g. [{\"type\":\"text\",\"text\":\"hello\"}])")
51
- .option("--mode <mode>", 'Session mode: "auto" or "manual"')
52
- .action(async (opts) => {
53
- if (!opts.question && !opts.richText) {
54
- throw new Error("Provide either --question or --rich-text.");
55
- }
56
- if (opts.question && opts.richText) {
57
- throw new Error("--question and --rich-text are mutually exclusive.");
58
- }
59
- const body = {
60
- vaultSlug: opts.vaultSlug,
61
- };
62
- if (opts.question != null)
63
- body.question = opts.question;
64
- if (opts.richText != null) {
65
- let parsed;
66
- try {
67
- parsed = JSON.parse(opts.richText);
68
- }
69
- catch {
70
- throw new Error("Invalid --rich-text JSON.");
71
- }
72
- body.richText = parsed;
73
- }
74
- if (opts.mode != null)
75
- body.mode = opts.mode;
76
- const resp = (await apiPost(`/chat/targeted`, body));
77
- const data = unwrapResp(resp);
78
- if (isJsonMode(vaultCmd)) {
79
- jsonOut(data);
80
- return;
81
- }
82
- const session = (data.session || {});
83
- const members = (data.members || []);
84
- console.log(`Session created!\n` +
85
- ` Session ID: ${session.id}\n` +
86
- ` Mode: ${session.mode}\n` +
87
- ` Members: ${members.length}\n` +
88
- ` Question sent.`);
89
- });
90
- // ── Publish ──
91
- // Note: file is still named BRAIN.md on webdrive — the post-processing pipeline
92
- // (gobi-webdrive) hardcodes the filename. Rename the file here once that ships.
93
- vaultCmd
94
- .command("publish")
95
- .description("Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (vault sync, metadata update, Discord notification).")
96
- .action(async () => {
97
- const vaultId = getVaultSlug();
98
- const filePath = join(process.cwd(), "BRAIN.md");
99
- if (!existsSync(filePath)) {
100
- throw new Error(`BRAIN.md not found in ${process.cwd()}`);
101
- }
102
- const content = readFileSync(filePath, "utf-8");
103
- const token = await getValidToken();
104
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
105
- const res = await fetch(url, {
106
- method: "PUT",
107
- headers: {
108
- Authorization: `Bearer ${token}`,
109
- "Content-Type": "text/markdown",
110
- },
111
- body: content,
112
- });
113
- if (!res.ok) {
114
- throw new Error(`Upload failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
115
- }
116
- if (isJsonMode(vaultCmd)) {
117
- jsonOut({ vaultId });
118
- return;
119
- }
120
- console.log(`Published BRAIN.md to vault "${vaultId}"`);
121
- });
122
- // ── Unpublish ──
123
- vaultCmd
124
- .command("unpublish")
125
- .description("Delete BRAIN.md from the vault on webdrive.")
126
- .action(async () => {
127
- const vaultId = getVaultSlug();
128
- const token = await getValidToken();
129
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
130
- const res = await fetch(url, {
131
- method: "DELETE",
132
- headers: { Authorization: `Bearer ${token}` },
133
- });
134
- if (!res.ok) {
135
- throw new Error(`Delete failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
136
- }
137
- if (isJsonMode(vaultCmd)) {
138
- jsonOut({ vaultId });
139
- return;
140
- }
141
- console.log(`Deleted BRAIN.md from vault "${vaultId}"`);
142
- });
143
- }
@@ -1,82 +0,0 @@
1
- # gobi global
2
-
3
- ```
4
- Usage: gobi global [options] [command]
5
-
6
- Global thread commands. Global is the platform-wide thread feed visible to everyone on Gobi.
7
-
8
- Options:
9
- -h, --help display help for command
10
-
11
- Commands:
12
- messages [options] List the global unified message feed (threads and replies, newest first).
13
- get-thread [options] <threadId> Get a global thread and its direct replies (paginated).
14
- ancestors <threadId> Show the ancestor lineage of a global thread or reply (root → immediate parent).
15
- create-thread [options] Create a global thread (visible platform-wide).
16
- reply [options] <threadId> Reply to a global thread.
17
- help [command] display help for command
18
- ```
19
-
20
- ## messages
21
-
22
- ```
23
- Usage: gobi global messages [options]
24
-
25
- List the global unified message feed (threads and replies, newest first).
26
-
27
- Options:
28
- --limit <number> Items per page (default: "20")
29
- --cursor <string> Pagination cursor from previous response
30
- -h, --help display help for command
31
- ```
32
-
33
- ## get-thread
34
-
35
- ```
36
- Usage: gobi global get-thread [options] <threadId>
37
-
38
- Get a global thread and its direct replies (paginated).
39
-
40
- Options:
41
- --limit <number> Replies per page (default: "20")
42
- --cursor <string> Pagination cursor from previous response
43
- -h, --help display help for command
44
- ```
45
-
46
- ## ancestors
47
-
48
- ```
49
- Usage: gobi global ancestors [options] <threadId>
50
-
51
- Show the ancestor lineage of a global thread or reply (root → immediate parent).
52
-
53
- Options:
54
- -h, --help display help for command
55
- ```
56
-
57
- ## create-thread
58
-
59
- ```
60
- Usage: gobi global create-thread [options]
61
-
62
- Create a global thread (visible platform-wide).
63
-
64
- Options:
65
- --title <title> Title of the thread
66
- --content <content> Thread content (markdown supported, use "-" for stdin)
67
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
68
- -h, --help display help for command
69
- ```
70
-
71
- ## reply
72
-
73
- ```
74
- Usage: gobi global reply [options] <threadId>
75
-
76
- Reply to a global thread.
77
-
78
- Options:
79
- --content <content> Reply content (markdown supported, use "-" for stdin)
80
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
81
- -h, --help display help for command
82
- ```
@@ -1,66 +0,0 @@
1
- # gobi vault
2
-
3
- ```
4
- Usage: gobi vault [options] [command]
5
-
6
- Vault commands. A Vault is your personal knowledge container — search public vaults, ask them questions, and publish your BRAIN.md profile.
7
-
8
- Options:
9
- -h, --help display help for command
10
-
11
- Commands:
12
- search [options] Search public vaults by text and semantic similarity.
13
- ask [options] Ask a vault a question. Creates a targeted session (1:1 conversation).
14
- publish Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (vault sync, metadata update, Discord notification).
15
- unpublish Delete BRAIN.md from the vault on webdrive.
16
- help [command] display help for command
17
- ```
18
-
19
- ## search
20
-
21
- ```
22
- Usage: gobi vault search [options]
23
-
24
- Search public vaults by text and semantic similarity.
25
-
26
- Options:
27
- --query <query> Search query
28
- -h, --help display help for command
29
- ```
30
-
31
- ## ask
32
-
33
- ```
34
- Usage: gobi vault ask [options]
35
-
36
- Ask a vault a question. Creates a targeted session (1:1 conversation).
37
-
38
- Options:
39
- --vault-slug <vaultSlug> Slug of the vault to ask
40
- --question <question> The question to ask (markdown supported)
41
- --rich-text <richText> Rich-text JSON array (e.g. [{"type":"text","text":"hello"}])
42
- --mode <mode> Session mode: "auto" or "manual"
43
- -h, --help display help for command
44
- ```
45
-
46
- ## publish
47
-
48
- ```
49
- Usage: gobi vault publish [options]
50
-
51
- Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (vault sync, metadata update, Discord notification).
52
-
53
- Options:
54
- -h, --help display help for command
55
- ```
56
-
57
- ## unpublish
58
-
59
- ```
60
- Usage: gobi vault unpublish [options]
61
-
62
- Delete BRAIN.md from the vault on webdrive.
63
-
64
- Options:
65
- -h, --help display help for command
66
- ```