@gobi-ai/cli 2.0.39 → 2.0.41

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,438 +0,0 @@
1
- import { WEB_BASE_URL } from "../constants.js";
2
- import { apiGet, apiPost, apiPatch, apiPut, apiDelete } from "../client.js";
3
- import { buildMentionMap, formatAttachmentLines, formatAttachmentSummary, formatPostLabel, formatReactionChips, formatReplyLine, isJsonMode, jsonOut, postBodyText, readStdin, unwrapResp, } from "./utils.js";
4
- import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
5
- function readContent(value) {
6
- if (value === "-")
7
- return readStdin();
8
- return value;
9
- }
10
- function buildPersonalPostUrl(post) {
11
- return `${WEB_BASE_URL}/posts/${post.id}`;
12
- }
13
- function formatFeedLine(m, mentions) {
14
- const isReply = m.parentPostId != null ||
15
- m.type === "post-reply";
16
- const id = `[${isReply ? "r" : "p"}:${m.id}]`;
17
- const kind = isReply ? "reply" : "post ";
18
- const author = m.author?.name ||
19
- `User ${m.authorId ?? "?"}`;
20
- let label;
21
- if (isReply) {
22
- const text = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
23
- label = text.length > 80 ? text.slice(0, 80) + "…" : text;
24
- }
25
- else {
26
- label = formatPostLabel(m, mentions);
27
- }
28
- const chips = formatReactionChips(m);
29
- const attachSummary = formatAttachmentSummary(m);
30
- return (`${id} ${kind} ${author} "${label}" ${m.createdAt}` +
31
- (attachSummary ? ` ${attachSummary}` : "") +
32
- (chips ? ` ${chips}` : ""));
33
- }
34
- export function registerGlobalCommand(program) {
35
- const global = program
36
- .command("global")
37
- .description("Global commands (posts and replies in the public feed across all vaults).");
38
- // ── Feed (unified) ──
39
- global
40
- .command("feed")
41
- .description("List the unified feed (posts and replies, newest first) in the global public feed.")
42
- .option("--limit <number>", "Items per page", "20")
43
- .option("--cursor <string>", "Pagination cursor from previous response")
44
- .option("--following", "Only include posts from authors you follow")
45
- .action(async (opts) => {
46
- const params = {
47
- limit: parseInt(opts.limit, 10),
48
- };
49
- if (opts.cursor)
50
- params.cursor = opts.cursor;
51
- if (opts.following)
52
- params.following = "true";
53
- const resp = (await apiGet(`/posts/feed`, params));
54
- if (isJsonMode(global)) {
55
- jsonOut({
56
- items: resp.data || [],
57
- pagination: resp.pagination || {},
58
- mentions: resp.mentions || {},
59
- });
60
- return;
61
- }
62
- const items = (resp.data || []);
63
- const pagination = (resp.pagination || {});
64
- if (!items.length) {
65
- console.log("No items found.");
66
- return;
67
- }
68
- const mentions = buildMentionMap(resp);
69
- const lines = items.map((m) => formatFeedLine(m, mentions));
70
- const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
71
- console.log(`Global feed (${items.length} items, newest first):\n` + lines.join("\n") + footer);
72
- });
73
- // ── List posts ──
74
- global
75
- .command("list-posts")
76
- .description("List posts in the global feed (paginated). Pass --mine to limit to your own posts.")
77
- .option("--limit <number>", "Items per page", "20")
78
- .option("--cursor <string>", "Pagination cursor from previous response")
79
- .option("--mine", "Only include posts authored by you")
80
- .action(async (opts) => {
81
- const params = {
82
- limit: parseInt(opts.limit, 10),
83
- };
84
- if (opts.cursor)
85
- params.cursor = opts.cursor;
86
- if (opts.mine)
87
- params.mine = "true";
88
- const resp = (await apiGet(`/posts`, params));
89
- if (isJsonMode(global)) {
90
- jsonOut({
91
- items: resp.data || [],
92
- pagination: resp.pagination || {},
93
- mentions: resp.mentions || {},
94
- });
95
- return;
96
- }
97
- const items = (resp.data || []);
98
- const pagination = (resp.pagination || {});
99
- if (!items.length) {
100
- console.log("No posts found.");
101
- return;
102
- }
103
- const mentions = buildMentionMap(resp);
104
- const lines = [];
105
- for (const t of items) {
106
- const author = t.author?.name ||
107
- `User ${t.authorId}`;
108
- lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" by ${author} (${t.replyCount ?? 0} replies, ${t.createdAt})`);
109
- for (const line of formatAttachmentLines(t, " ", "📎")) {
110
- lines.push(line);
111
- }
112
- const replies = t.replies || [];
113
- for (const r of replies) {
114
- lines.push(formatReplyLine(r, mentions));
115
- }
116
- }
117
- const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
118
- console.log(`Posts (${items.length} items):\n` + lines.join("\n") + footer);
119
- });
120
- // ── Get post (with ancestors and replies) ──
121
- global
122
- .command("get-post <postId>")
123
- .description("Get a global post with its ancestors and replies (paginated).")
124
- .option("--limit <number>", "Items per page", "20")
125
- .option("--cursor <string>", "Pagination cursor from previous response")
126
- .option("--full", "Show full reply content without truncation")
127
- .action(async (postId, opts) => {
128
- const params = {
129
- limit: parseInt(opts.limit, 10),
130
- };
131
- if (opts.cursor)
132
- params.cursor = opts.cursor;
133
- const [postResp, ancestorsResp] = await Promise.all([
134
- apiGet(`/posts/${postId}`, params),
135
- apiGet(`/posts/${postId}/ancestors`),
136
- ]);
137
- const data = unwrapResp(postResp);
138
- const pagination = (postResp.pagination || {});
139
- const mentions = (postResp.mentions || {});
140
- const ancestorsData = unwrapResp(ancestorsResp);
141
- const ancestors = (ancestorsData.ancestors || []);
142
- if (isJsonMode(global)) {
143
- jsonOut({ ...data, ancestors, pagination, mentions });
144
- return;
145
- }
146
- const post = (data.update || data.post || data);
147
- const replies = (data.replies || []);
148
- const mentionMap = buildMentionMap(postResp);
149
- const author = post.author?.name ||
150
- `User ${post.authorId}`;
151
- const ancestorLines = [];
152
- if (ancestors.length) {
153
- ancestors.forEach((a, i) => {
154
- ancestorLines.push(` ${i + 1}. ${formatFeedLine(a, mentionMap)}`);
155
- });
156
- }
157
- const replyLines = [];
158
- for (const r of replies) {
159
- const rAuthor = r.author?.name ||
160
- `User ${r.authorId}`;
161
- const text = postBodyText(r, mentionMap);
162
- const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "…";
163
- const rChips = formatReactionChips(r);
164
- const rAttach = formatAttachmentSummary(r);
165
- replyLines.push(` - [r:${r.id}] ${rAuthor}: ${truncated} (${r.createdAt})${rAttach ? ` ${rAttach}` : ""}${rChips ? ` ${rChips}` : ""}`);
166
- }
167
- const isReplyPost = post.parentPostId != null;
168
- const heading = isReplyPost
169
- ? `Reply [r:${post.id}]`
170
- : `Post: ${post.title || "(no title)"}`;
171
- const postChips = formatReactionChips(post);
172
- const attachmentLines = formatAttachmentLines(post);
173
- const output = [
174
- heading,
175
- `By: ${author} on ${post.createdAt}`,
176
- ...(postChips ? [`Reactions: ${postChips}`] : []),
177
- ...(ancestorLines.length
178
- ? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
179
- : []),
180
- "",
181
- postBodyText(post, mentionMap),
182
- ...(attachmentLines.length
183
- ? ["", `Attachments (${attachmentLines.length}):`, ...attachmentLines]
184
- : []),
185
- "",
186
- `Replies (${replies.length} items):`,
187
- ...replyLines,
188
- ...(pagination.hasMore
189
- ? [` Next cursor: ${pagination.nextCursor}`]
190
- : []),
191
- ].join("\n");
192
- console.log(output);
193
- });
194
- // ── Create post ──
195
- global
196
- .command("create-post")
197
- .description("Create a post in the global feed.")
198
- .option("--title <title>", "Title of the post")
199
- .option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
200
- .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
201
- .option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
202
- .option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
203
- .option("--repost-post-id <postId>", "Wrap an existing top-level post as the embedded card on this new post. Composes with --content / --rich-text / --attach (the wrapping author's text + media render above the embedded card). Reposts-of-reposts are collapsed to the transitive root server-side. The referenced post must exist, not be deleted, and not itself be a reply.")
204
- .action(async (opts) => {
205
- // A post is substantive if it has a text body OR carries an attachment
206
- // (artifact card / media) OR embeds a repost. Only block the truly empty
207
- // case — this is what lets an artifact-only post be created with no content.
208
- const hasAttachmentPayload = (opts.artifact && opts.artifact.length > 0) ||
209
- (opts.attach && opts.attach.length > 0) ||
210
- opts.repostPostId != null;
211
- if (!opts.content && !opts.richText && !hasAttachmentPayload) {
212
- throw new Error("Provide --content, --rich-text, or an attachment (--artifact / --attach / --repost-post-id).");
213
- }
214
- if (opts.content && opts.richText) {
215
- throw new Error("--content and --rich-text are mutually exclusive.");
216
- }
217
- const body = {};
218
- if (opts.title != null)
219
- body.title = opts.title;
220
- if (opts.content != null) {
221
- body.content = readContent(opts.content);
222
- }
223
- if (opts.richText != null) {
224
- let parsed;
225
- try {
226
- parsed = JSON.parse(opts.richText);
227
- }
228
- catch {
229
- throw new Error("Invalid --rich-text JSON.");
230
- }
231
- body.richText = parsed;
232
- }
233
- if (opts.artifact && opts.artifact.length > 0)
234
- body.artifactIds = opts.artifact;
235
- if (opts.attach && opts.attach.length > 0) {
236
- assertPostAttachmentMix(opts.attach);
237
- body.attachments = await uploadPostAttachments(opts.attach);
238
- }
239
- if (opts.repostPostId != null) {
240
- const n = Number(opts.repostPostId);
241
- if (!Number.isFinite(n) || n <= 0 || !Number.isInteger(n)) {
242
- throw new Error("--repost-post-id must be a positive integer.");
243
- }
244
- body.repostPostId = n;
245
- }
246
- const resp = (await apiPost(`/posts`, body));
247
- const post = unwrapResp(resp);
248
- const shareUrl = buildPersonalPostUrl(post);
249
- if (isJsonMode(global)) {
250
- jsonOut({ ...post, shareUrl });
251
- return;
252
- }
253
- console.log(`Post created!\n` +
254
- ` ID: ${post.id}\n` +
255
- (post.title ? ` Title: ${post.title}\n` : "") +
256
- ` Created: ${post.createdAt}\n` +
257
- ` URL: ${shareUrl}`);
258
- });
259
- // ── Edit post ──
260
- global
261
- .command("edit-post <postId>")
262
- .description("Edit a post you authored in the global feed.")
263
- .option("--title <title>", "New title")
264
- .option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
265
- .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
266
- .option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
267
- .option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
268
- .action(async (postId, opts) => {
269
- const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
270
- const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
271
- if (opts.title == null &&
272
- opts.content == null &&
273
- opts.richText == null &&
274
- !wantsAttachChange &&
275
- !wantsArtifactChange) {
276
- throw new Error("Provide at least --title, --content, --rich-text, --attach, or --artifact to update.");
277
- }
278
- if (opts.content && opts.richText) {
279
- throw new Error("--content and --rich-text are mutually exclusive.");
280
- }
281
- const body = {};
282
- if (opts.title != null)
283
- body.title = opts.title;
284
- if (opts.content != null) {
285
- body.content = readContent(opts.content);
286
- }
287
- if (opts.richText != null) {
288
- let parsed;
289
- try {
290
- parsed = JSON.parse(opts.richText);
291
- }
292
- catch {
293
- throw new Error("Invalid --rich-text JSON.");
294
- }
295
- body.richText = parsed;
296
- }
297
- if (opts.attach && opts.attach.length > 0) {
298
- assertPostAttachmentMix(opts.attach);
299
- body.attachments = await uploadPostAttachments(opts.attach);
300
- }
301
- if (opts.artifact && opts.artifact.length > 0)
302
- body.artifactIds = opts.artifact;
303
- const resp = (await apiPatch(`/posts/${postId}`, body));
304
- const post = unwrapResp(resp);
305
- if (isJsonMode(global)) {
306
- jsonOut(post);
307
- return;
308
- }
309
- console.log(`Post edited!\n ID: ${post.id}\n Edited: ${post.editedAt ?? post.updatedAt}`);
310
- });
311
- // ── Delete post ──
312
- global
313
- .command("delete-post <postId>")
314
- .description("Delete a post you authored in the global feed.")
315
- .action(async (postId) => {
316
- await apiDelete(`/posts/${postId}`);
317
- if (isJsonMode(global)) {
318
- jsonOut({ id: postId });
319
- return;
320
- }
321
- console.log(`Post ${postId} deleted.`);
322
- });
323
- // ── Reply ──
324
- global
325
- .command("create-reply <postId>")
326
- .description("Create a reply to a post in the global feed.")
327
- .option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
328
- .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
329
- .option("--attach <file>", "Local media or document file to attach to this reply. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
330
- .action(async (postId, opts) => {
331
- if (!opts.content && !opts.richText) {
332
- throw new Error("Provide either --content or --rich-text.");
333
- }
334
- if (opts.content && opts.richText) {
335
- throw new Error("--content and --rich-text are mutually exclusive.");
336
- }
337
- const body = {};
338
- if (opts.content != null) {
339
- body.content = readContent(opts.content);
340
- }
341
- if (opts.richText != null) {
342
- let parsed;
343
- try {
344
- parsed = JSON.parse(opts.richText);
345
- }
346
- catch {
347
- throw new Error("Invalid --rich-text JSON.");
348
- }
349
- body.richText = parsed;
350
- }
351
- if (opts.attach && opts.attach.length > 0) {
352
- assertPostAttachmentMix(opts.attach);
353
- body.attachments = await uploadPostAttachments(opts.attach);
354
- }
355
- const resp = (await apiPost(`/posts/${postId}/replies`, body));
356
- const reply = unwrapResp(resp);
357
- if (isJsonMode(global)) {
358
- jsonOut(reply);
359
- return;
360
- }
361
- console.log(`Reply created!\n ID: ${reply.id}\n Created: ${reply.createdAt}`);
362
- });
363
- global
364
- .command("edit-reply <replyId>")
365
- .description("Edit a reply you authored in the global feed.")
366
- .option("--content <content>", "New reply content (markdown supported, use \"-\" for stdin)")
367
- .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
368
- .action(async (replyId, opts) => {
369
- if (opts.content == null && opts.richText == null) {
370
- throw new Error("Provide at least --content or --rich-text to update.");
371
- }
372
- if (opts.content && opts.richText) {
373
- throw new Error("--content and --rich-text are mutually exclusive.");
374
- }
375
- const body = {};
376
- if (opts.content != null) {
377
- body.content = readContent(opts.content);
378
- }
379
- if (opts.richText != null) {
380
- let parsed;
381
- try {
382
- parsed = JSON.parse(opts.richText);
383
- }
384
- catch {
385
- throw new Error("Invalid --rich-text JSON.");
386
- }
387
- body.richText = parsed;
388
- }
389
- const resp = (await apiPatch(`/posts/replies/${replyId}`, body));
390
- const reply = unwrapResp(resp);
391
- if (isJsonMode(global)) {
392
- jsonOut(reply);
393
- return;
394
- }
395
- console.log(`Reply edited!\n ID: ${reply.id}\n Edited: ${reply.editedAt ?? reply.updatedAt}`);
396
- });
397
- global
398
- .command("delete-reply <replyId>")
399
- .description("Delete a reply you authored in the global feed.")
400
- .action(async (replyId) => {
401
- await apiDelete(`/posts/replies/${replyId}`);
402
- if (isJsonMode(global)) {
403
- jsonOut({ id: replyId });
404
- return;
405
- }
406
- console.log(`Reply ${replyId} deleted.`);
407
- });
408
- // ── Reactions (react, unreact) ──
409
- global
410
- .command("react <postId> <emoji>")
411
- .description("Add an emoji reaction to a global-feed post or reply (idempotent). <postId> is the numeric id of a post OR a reply — the [p:N]/[r:N] ids shown in feed output.")
412
- .action(async (postId, emoji) => {
413
- const resp = (await apiPut(`/posts/${postId}/reactions`, {
414
- emoji,
415
- }));
416
- const data = unwrapResp(resp);
417
- if (isJsonMode(global)) {
418
- jsonOut(data);
419
- return;
420
- }
421
- const chips = formatReactionChips(data);
422
- console.log(`Reacted ${emoji} to ${postId}.` + (chips ? `\n Now: ${chips}` : ""));
423
- });
424
- global
425
- .command("unreact <postId> <emoji>")
426
- .description("Remove your emoji reaction from a global-feed post or reply. <postId> is the numeric id of a post OR a reply.")
427
- .action(async (postId, emoji) => {
428
- const resp = (await apiDelete(`/posts/${postId}/reactions/${encodeURIComponent(emoji)}`));
429
- const data = unwrapResp(resp);
430
- if (isJsonMode(global)) {
431
- jsonOut(data);
432
- return;
433
- }
434
- const chips = formatReactionChips(data);
435
- console.log(`Removed ${emoji} reaction from ${postId}.` +
436
- (chips ? `\n Now: ${chips}` : ""));
437
- });
438
- }
@@ -1,175 +0,0 @@
1
- # gobi global
2
-
3
- ```
4
- Usage: gobi global [options] [command]
5
-
6
- Global commands (posts and replies in the public feed across all vaults).
7
-
8
- Options:
9
- -h, --help display help for command
10
-
11
- Commands:
12
- feed [options] List the unified feed (posts and replies, newest first) in the global public feed.
13
- list-posts [options] List posts in the global feed (paginated). Pass --mine to limit to your own posts.
14
- get-post [options] <postId> Get a global post with its ancestors and replies (paginated).
15
- create-post [options] Create a post in the global feed.
16
- edit-post [options] <postId> Edit a post you authored in the global feed.
17
- delete-post <postId> Delete a post you authored in the global feed.
18
- create-reply [options] <postId> Create a reply to a post in the global feed.
19
- edit-reply [options] <replyId> Edit a reply you authored in the global feed.
20
- delete-reply <replyId> Delete a reply you authored in the global feed.
21
- react <postId> <emoji> Add an emoji reaction to a global-feed post or reply (idempotent). <postId> is the numeric id of a post OR a reply — the [p:N]/[r:N] ids shown in feed output.
22
- unreact <postId> <emoji> Remove your emoji reaction from a global-feed post or reply. <postId> is the numeric id of a post OR a reply.
23
- help [command] display help for command
24
- ```
25
-
26
- ## feed
27
-
28
- ```
29
- Usage: gobi global feed [options]
30
-
31
- List the unified feed (posts and replies, newest first) in the global public feed.
32
-
33
- Options:
34
- --limit <number> Items per page (default: "20")
35
- --cursor <string> Pagination cursor from previous response
36
- --following Only include posts from authors you follow
37
- -h, --help display help for command
38
- ```
39
-
40
- ## list-posts
41
-
42
- ```
43
- Usage: gobi global list-posts [options]
44
-
45
- List posts in the global feed (paginated). Pass --mine to limit to your own posts.
46
-
47
- Options:
48
- --limit <number> Items per page (default: "20")
49
- --cursor <string> Pagination cursor from previous response
50
- --mine Only include posts authored by you
51
- -h, --help display help for command
52
- ```
53
-
54
- ## get-post
55
-
56
- ```
57
- Usage: gobi global get-post [options] <postId>
58
-
59
- Get a global post with its ancestors and replies (paginated).
60
-
61
- Options:
62
- --limit <number> Items per page (default: "20")
63
- --cursor <string> Pagination cursor from previous response
64
- --full Show full reply content without truncation
65
- -h, --help display help for command
66
- ```
67
-
68
- ## create-post
69
-
70
- ```
71
- Usage: gobi global create-post [options]
72
-
73
- Create a post in the global feed.
74
-
75
- Options:
76
- --title <title> Title of the post
77
- --content <content> Post content (markdown supported, use "-" for stdin)
78
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
79
- --artifact <artifactId> Attach an existing artifact to the post (repeatable). Create artifacts with `gobi personal artifact create`. (default: [])
80
- --attach <file> Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos /
81
- 15MB GIFs / 512MB video / 250MB files. (default: [])
82
- --repost-post-id <postId> Wrap an existing top-level post as the embedded card on this new post. Composes with --content / --rich-text / --attach (the wrapping author's text + media render above
83
- the embedded card). Reposts-of-reposts are collapsed to the transitive root server-side. The referenced post must exist, not be deleted, and not itself be a reply.
84
- -h, --help display help for command
85
- ```
86
-
87
- ## edit-post
88
-
89
- ```
90
- Usage: gobi global edit-post [options] <postId>
91
-
92
- Edit a post you authored in the global feed.
93
-
94
- Options:
95
- --title <title> New title
96
- --content <content> New content (markdown supported, use "-" for stdin)
97
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
98
- --attach <file> Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv)
99
- OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged. (default: [])
100
- --artifact <artifactId> Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts
101
- with `gobi personal artifact create`. (default: [])
102
- -h, --help display help for command
103
- ```
104
-
105
- ## delete-post
106
-
107
- ```
108
- Usage: gobi global delete-post [options] <postId>
109
-
110
- Delete a post you authored in the global feed.
111
-
112
- Options:
113
- -h, --help display help for command
114
- ```
115
-
116
- ## create-reply
117
-
118
- ```
119
- Usage: gobi global create-reply [options] <postId>
120
-
121
- Create a reply to a post in the global feed.
122
-
123
- Options:
124
- --content <content> Reply content (markdown supported, use "-" for stdin)
125
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
126
- --attach <file> Local media or document file to attach to this reply. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB
127
- photos / 15MB GIFs / 512MB video / 250MB files. (default: [])
128
- -h, --help display help for command
129
- ```
130
-
131
- ## edit-reply
132
-
133
- ```
134
- Usage: gobi global edit-reply [options] <replyId>
135
-
136
- Edit a reply you authored in the global feed.
137
-
138
- Options:
139
- --content <content> New reply content (markdown supported, use "-" for stdin)
140
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
141
- -h, --help display help for command
142
- ```
143
-
144
- ## delete-reply
145
-
146
- ```
147
- Usage: gobi global delete-reply [options] <replyId>
148
-
149
- Delete a reply you authored in the global feed.
150
-
151
- Options:
152
- -h, --help display help for command
153
- ```
154
-
155
- ## react
156
-
157
- ```
158
- Usage: gobi global react [options] <postId> <emoji>
159
-
160
- Add an emoji reaction to a global-feed post or reply (idempotent). <postId> is the numeric id of a post OR a reply — the [p:N]/[r:N] ids shown in feed output.
161
-
162
- Options:
163
- -h, --help display help for command
164
- ```
165
-
166
- ## unreact
167
-
168
- ```
169
- Usage: gobi global unreact [options] <postId> <emoji>
170
-
171
- Remove your emoji reaction from a global-feed post or reply. <postId> is the numeric id of a post OR a reply.
172
-
173
- Options:
174
- -h, --help display help for command
175
- ```