@gobi-ai/cli 1.3.8 → 2.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.
@@ -3,29 +3,28 @@
3
3
  ```
4
4
  Usage: gobi space [options] [command]
5
5
 
6
- Space commands (threads, replies). Space and member admin is web-UI only.
6
+ Space commands (posts, replies). Space and member admin is web-UI only.
7
7
 
8
8
  Options:
9
- --space-slug <slug> Space slug (overrides .gobi/settings.yaml)
10
- -h, --help display help for command
9
+ --space-slug <slug> Space slug (overrides .gobi/settings.yaml)
10
+ -h, --help display help for command
11
11
 
12
12
  Commands:
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
- warp [spaceSlug] Select the active space. Pass a slug to warp directly, or omit for interactive selection.
16
- list-topics [options] List topics in a space, ordered by most recent content linkage.
17
- 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
- get-thread [options] <threadId> Get a thread and its replies (paginated).
21
- list-threads [options] List threads in a space (paginated).
22
- create-thread [options] Create a thread in a space.
23
- edit-thread [options] <threadId> Edit a thread. You must be the author.
24
- delete-thread <threadId> Delete a thread. You must be the author.
25
- create-reply [options] <threadId> Create a reply to a thread in a space.
26
- edit-reply [options] <replyId> Edit a reply. You must be the author.
27
- delete-reply <replyId> Delete a reply. You must be the author.
28
- help [command] display help for command
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
+ warp [spaceSlug] Select the active space. Pass a slug to warp directly, or omit for interactive selection.
16
+ list-topics [options] List topics in a space, ordered by most recent content linkage.
17
+ list-topic-posts [options] <topicSlug> List posts tagged with a topic in a space (cursor-paginated).
18
+ feed [options] List the unified feed (posts and replies, newest first) in a space.
19
+ get-post [options] <postId> Get a post with its ancestors and replies (paginated).
20
+ list-posts [options] List posts in a space (paginated).
21
+ create-post [options] Create a post in a space.
22
+ edit-post [options] <postId> Edit a post. You must be the author.
23
+ delete-post <postId> Delete a post. You must be the author.
24
+ create-reply [options] <postId> Create a reply to a post in a space.
25
+ edit-reply [options] <replyId> Edit a reply. You must be the author.
26
+ delete-reply <replyId> Delete a reply. You must be the author.
27
+ help [command] display help for command
29
28
  ```
30
29
 
31
30
  ## get
@@ -51,12 +50,12 @@ Options:
51
50
  -h, --help display help for command
52
51
  ```
53
52
 
54
- ## list-topic-threads
53
+ ## list-topic-posts
55
54
 
56
55
  ```
57
- Usage: gobi space list-topic-threads [options] <topicSlug>
56
+ Usage: gobi space list-topic-posts [options] <topicSlug>
58
57
 
59
- List threads tagged with a topic in a space (cursor-paginated).
58
+ List posts tagged with a topic in a space (cursor-paginated).
60
59
 
61
60
  Options:
62
61
  --limit <number> Items per page (default: "20")
@@ -64,12 +63,12 @@ Options:
64
63
  -h, --help display help for command
65
64
  ```
66
65
 
67
- ## messages
66
+ ## feed
68
67
 
69
68
  ```
70
- Usage: gobi space messages [options]
69
+ Usage: gobi space feed [options]
71
70
 
72
- List the unified message feed (threads and replies, newest first) in a space.
71
+ List the unified feed (posts and replies, newest first) in a space.
73
72
 
74
73
  Options:
75
74
  --limit <number> Items per page (default: "20")
@@ -77,23 +76,12 @@ Options:
77
76
  -h, --help display help for command
78
77
  ```
79
78
 
80
- ## ancestors
79
+ ## get-post
81
80
 
82
81
  ```
83
- Usage: gobi space ancestors [options] <threadId>
82
+ Usage: gobi space get-post [options] <postId>
84
83
 
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
- ## get-thread
92
-
93
- ```
94
- Usage: gobi space get-thread [options] <threadId>
95
-
96
- Get a thread and its replies (paginated).
84
+ Get a post with its ancestors and replies (paginated).
97
85
 
98
86
  Options:
99
87
  --limit <number> Replies per page (default: "20")
@@ -101,12 +89,12 @@ Options:
101
89
  -h, --help display help for command
102
90
  ```
103
91
 
104
- ## list-threads
92
+ ## list-posts
105
93
 
106
94
  ```
107
- Usage: gobi space list-threads [options]
95
+ Usage: gobi space list-posts [options]
108
96
 
109
- List threads in a space (paginated).
97
+ List posts in a space (paginated).
110
98
 
111
99
  Options:
112
100
  --limit <number> Items per page (default: "20")
@@ -114,42 +102,42 @@ Options:
114
102
  -h, --help display help for command
115
103
  ```
116
104
 
117
- ## create-thread
105
+ ## create-post
118
106
 
119
107
  ```
120
- Usage: gobi space create-thread [options]
108
+ Usage: gobi space create-post [options]
121
109
 
122
- Create a thread in a space.
110
+ Create a post in a space.
123
111
 
124
112
  Options:
125
- --title <title> Title of the thread
126
- --content <content> Thread content (markdown supported)
113
+ --title <title> Title of the post
114
+ --content <content> Post content (markdown supported)
127
115
  --auto-attachments Upload wiki-linked [[files]] to webdrive before posting
128
116
  --vault-slug <vaultSlug> Vault slug for attachment uploads (overrides .gobi/settings.yaml)
129
117
  -h, --help display help for command
130
118
  ```
131
119
 
132
- ## edit-thread
120
+ ## edit-post
133
121
 
134
122
  ```
135
- Usage: gobi space edit-thread [options] <threadId>
123
+ Usage: gobi space edit-post [options] <postId>
136
124
 
137
- Edit a thread. You must be the author.
125
+ Edit a post. You must be the author.
138
126
 
139
127
  Options:
140
- --title <title> New title for the thread
141
- --content <content> New content for the thread (markdown supported)
128
+ --title <title> New title for the post
129
+ --content <content> New content for the post (markdown supported)
142
130
  --auto-attachments Upload wiki-linked [[files]] to webdrive before editing
143
131
  --vault-slug <vaultSlug> Vault slug for attachment uploads (overrides .gobi/settings.yaml)
144
132
  -h, --help display help for command
145
133
  ```
146
134
 
147
- ## delete-thread
135
+ ## delete-post
148
136
 
149
137
  ```
150
- Usage: gobi space delete-thread [options] <threadId>
138
+ Usage: gobi space delete-post [options] <postId>
151
139
 
152
- Delete a thread. You must be the author.
140
+ Delete a post. You must be the author.
153
141
 
154
142
  Options:
155
143
  -h, --help display help for command
@@ -158,9 +146,9 @@ Options:
158
146
  ## create-reply
159
147
 
160
148
  ```
161
- Usage: gobi space create-reply [options] <threadId>
149
+ Usage: gobi space create-reply [options] <postId>
162
150
 
163
- Create a reply to a thread in a space.
151
+ Create a reply to a post in a space.
164
152
 
165
153
  Options:
166
154
  --content <content> Reply content (markdown supported)
@@ -0,0 +1,92 @@
1
+ ---
2
+ name: gobi-vault
3
+ description: >-
4
+ Gobi vault commands for publishing your vault profile and syncing files:
5
+ publish/unpublish PUBLISH.md and run the local-to-webdrive sync. Use when
6
+ the user wants to publish their vault, unpublish it, or push/pull files.
7
+ allowed-tools: Bash(gobi:*)
8
+ metadata:
9
+ author: gobi-ai
10
+ version: "0.9.13"
11
+ ---
12
+
13
+ # gobi-vault
14
+
15
+ Gobi vault commands for publishing your vault profile and syncing files (v0.9.13).
16
+
17
+ Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
18
+
19
+ ## Gobi Vault
20
+
21
+ A "vault" is your file-backed knowledge home. Public vaults are accessible at `https://gobispace.com/@{vaultSlug}`. Each vault has a profile written to `PUBLISH.md` at its root; publishing pushes that file to webdrive, which then updates vault metadata and the public profile.
22
+
23
+ ## Important: JSON Mode
24
+
25
+ For programmatic/agent usage, always pass `--json` as a **global** option (before the subcommand):
26
+
27
+ ```bash
28
+ gobi --json vault publish
29
+ ```
30
+
31
+ ## Available Commands
32
+
33
+ - `gobi vault publish` — Upload `PUBLISH.md` to the vault root on webdrive. Triggers post-processing (vault profile sync, metadata update, Discord notification).
34
+ - `gobi vault unpublish` — Delete `PUBLISH.md` from the vault on webdrive.
35
+ - `gobi vault sync` — Sync local vault files with Gobi Webdrive. Supports `--upload-only`, `--download-only`, `--conflict <ask|server|client|skip>`, `--dry-run`, `--full`, `--path <p>`, `--plan-file`, `--execute`.
36
+
37
+ ## PUBLISH.md Frontmatter Reference
38
+
39
+ `PUBLISH.md` is the metadata file at the root of every vault. Its YAML frontmatter controls the vault's public profile, homepage, and AI agent behavior. Example:
40
+
41
+ ```yaml
42
+ ---
43
+ title: My Vault
44
+ tags:
45
+ - topic1
46
+ - topic2
47
+ description: A short description of what this vault is about.
48
+ thumbnail: "[[PROFILE.png]]"
49
+ homepage: "[[app/home.html?nav=false]]"
50
+ prompt: "[[system-prompt.md]]"
51
+ ---
52
+ ```
53
+
54
+ ### Fields
55
+
56
+ - **`title`** (required) — Display name of the vault.
57
+ - **`description`** (required for public listing) — Short description shown on the vault card and public profile. Without both `title` and `description`, the vault won't appear in the public catalog.
58
+ - **`tags`** — Tags for categorization and discovery. Supports YAML block list or inline array format:
59
+ ```yaml
60
+ # Block list
61
+ tags:
62
+ - ambient ai
63
+ - wearables
64
+
65
+ # Inline array
66
+ tags: [ambient ai, wearables]
67
+ ```
68
+ - **`thumbnail`** — Profile image for the vault card. Uses wiki-link syntax pointing to an image file in the vault (e.g. `"[[PROFILE.png]]"`).
69
+ - **`homepage`** — Custom HTML page to serve as the vault's public homepage at `gobispace.com/@{vaultSlug}`. Uses wiki-link syntax pointing to an HTML file in the vault. Supports a `nav` query parameter to control Gobi's sidebar navigation:
70
+ - `"[[app/home.html]]"` — Shows the Gobi sidebar alongside the homepage (default)
71
+ - `"[[app/home.html?nav=false]]"` — Full-screen, no Gobi sidebar/chrome
72
+ - **`prompt`** — Wiki-link to a custom system prompt file for the vault's AI agent (e.g. `"[[system-prompt.md]]"`).
73
+
74
+ > For details on building custom HTML homepages and using the `window.gobi` API, see the **gobi-homepage** skill.
75
+
76
+ ## Publishing Workflow
77
+
78
+ After editing `PUBLISH.md` frontmatter, follow these steps to make your changes live:
79
+
80
+ 1. **Edit `PUBLISH.md`** in the vault root with the desired frontmatter fields.
81
+ 2. **Sync referenced files** — if the homepage HTML, thumbnail image, or prompt file is new or updated, upload them first:
82
+ ```bash
83
+ gobi vault sync
84
+ ```
85
+ 3. **Publish the vault**:
86
+ ```bash
87
+ gobi vault publish
88
+ ```
89
+ This uploads `PUBLISH.md` to webdrive, triggers post-processing that extracts metadata (title, description, tags, thumbnail, homepage path), updates the vault's public profile, and sends a Discord notification.
90
+ 4. The vault is now live at `https://gobispace.com/@{vaultSlug}`.
91
+
92
+ > **Important:** Any time you change `PUBLISH.md` frontmatter (e.g. adding or updating `homepage`), you must re-run `gobi vault publish` for the changes to take effect.
@@ -1,7 +1,46 @@
1
- # gobi sync
1
+ # gobi vault
2
2
 
3
3
  ```
4
- Usage: gobi sync [options]
4
+ Usage: gobi vault [options] [command]
5
+
6
+ Vault commands (publish/unpublish profile, sync files).
7
+
8
+ Options:
9
+ -h, --help display help for command
10
+
11
+ Commands:
12
+ publish Upload PUBLISH.md to the vault root on webdrive. Triggers post-processing (vault sync, metadata update, Discord notification).
13
+ unpublish Delete PUBLISH.md from the vault on webdrive.
14
+ sync [options] Sync local vault files with Gobi Webdrive.
15
+ help [command] display help for command
16
+ ```
17
+
18
+ ## publish
19
+
20
+ ```
21
+ Usage: gobi vault publish [options]
22
+
23
+ Upload PUBLISH.md to the vault root on webdrive. Triggers post-processing (vault sync, metadata update, Discord notification).
24
+
25
+ Options:
26
+ -h, --help display help for command
27
+ ```
28
+
29
+ ## unpublish
30
+
31
+ ```
32
+ Usage: gobi vault unpublish [options]
33
+
34
+ Delete PUBLISH.md from the vault on webdrive.
35
+
36
+ Options:
37
+ -h, --help display help for command
38
+ ```
39
+
40
+ ## sync
41
+
42
+ ```
43
+ Usage: gobi vault sync [options]
5
44
 
6
45
  Sync local vault files with Gobi Webdrive.
7
46
 
@@ -1,141 +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 registerBrainCommand(program) {
9
- const brain = program
10
- .command("brain")
11
- .description("Brain commands (search, ask, publish, unpublish).");
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
- .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(brain)) {
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
- brain
92
- .command("publish")
93
- .description("Upload BRAIN.md to the vault root on webdrive. Triggers post-processing (brain sync, metadata update, Discord notification).")
94
- .action(async () => {
95
- const vaultId = getVaultSlug();
96
- const filePath = join(process.cwd(), "BRAIN.md");
97
- if (!existsSync(filePath)) {
98
- throw new Error(`BRAIN.md not found in ${process.cwd()}`);
99
- }
100
- const content = readFileSync(filePath, "utf-8");
101
- const token = await getValidToken();
102
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
103
- const res = await fetch(url, {
104
- method: "PUT",
105
- headers: {
106
- Authorization: `Bearer ${token}`,
107
- "Content-Type": "text/markdown",
108
- },
109
- body: content,
110
- });
111
- if (!res.ok) {
112
- throw new Error(`Upload failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
113
- }
114
- if (isJsonMode(brain)) {
115
- jsonOut({ vaultId });
116
- return;
117
- }
118
- console.log(`Published BRAIN.md to vault "${vaultId}"`);
119
- });
120
- // ── Unpublish ──
121
- brain
122
- .command("unpublish")
123
- .description("Delete BRAIN.md from the vault on webdrive.")
124
- .action(async () => {
125
- const vaultId = getVaultSlug();
126
- const token = await getValidToken();
127
- const url = `${WEBDRIVE_BASE_URL}/api/v1/vaults/${vaultId}/file/BRAIN.md`;
128
- const res = await fetch(url, {
129
- method: "DELETE",
130
- headers: { Authorization: `Bearer ${token}` },
131
- });
132
- if (!res.ok) {
133
- throw new Error(`Delete failed: HTTP ${res.status}: ${(await res.text()) || "(no body)"}`);
134
- }
135
- if (isJsonMode(brain)) {
136
- jsonOut({ vaultId });
137
- return;
138
- }
139
- console.log(`Deleted BRAIN.md from vault "${vaultId}"`);
140
- });
141
- }
@@ -1,148 +0,0 @@
1
- import { readFileSync } from "fs";
2
- import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
3
- import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
4
- export function registerFeedCommand(program) {
5
- const feed = program
6
- .command("feed")
7
- .description("Feed of brain updates from people across the platform.");
8
- // ── List ──
9
- feed
10
- .command("list")
11
- .description("List recent brain updates from the global public feed.")
12
- .option("--limit <number>", "Items per page", "20")
13
- .option("--cursor <string>", "Pagination cursor from previous response")
14
- .action(async (opts) => {
15
- const params = {
16
- limit: parseInt(opts.limit, 10),
17
- };
18
- if (opts.cursor)
19
- params.cursor = opts.cursor;
20
- const resp = (await apiGet(`/feed`, params));
21
- if (isJsonMode(feed)) {
22
- jsonOut({
23
- items: resp.data || [],
24
- pagination: resp.pagination || {},
25
- mentions: resp.mentions || {},
26
- });
27
- return;
28
- }
29
- const items = (resp.data || []);
30
- const pagination = (resp.pagination || {});
31
- if (!items.length) {
32
- console.log("No feed items found.");
33
- return;
34
- }
35
- const lines = [];
36
- for (const u of items) {
37
- const author = u.author?.name ||
38
- `User ${u.authorId}`;
39
- const vaultSlug = u.vault?.vaultSlug ||
40
- u.authorVault?.vaultSlug ||
41
- "?";
42
- const replyCount = u.replyCount ?? 0;
43
- const replies = replyCount ? `, ${replyCount} ${replyCount === 1 ? "reply" : "replies"}` : "";
44
- lines.push(`- [${u.id}] "${u.title}" by ${author} (vault: ${vaultSlug}, ${u.createdAt}${replies})`);
45
- }
46
- const footer = pagination.hasMore
47
- ? `\n Next cursor: ${pagination.nextCursor}`
48
- : "";
49
- console.log(`Feed (${items.length} items):\n` + lines.join("\n") + footer);
50
- });
51
- // ── Get ──
52
- feed
53
- .command("get <updateId>")
54
- .description("Get a feed brain update and its replies (paginated).")
55
- .option("--limit <number>", "Replies per page", "20")
56
- .option("--cursor <string>", "Pagination cursor from previous response")
57
- .option("--full", "Show full reply content without truncation")
58
- .action(async (updateId, opts) => {
59
- const params = {
60
- limit: parseInt(opts.limit, 10),
61
- };
62
- if (opts.cursor)
63
- params.cursor = opts.cursor;
64
- const resp = (await apiGet(`/feed/${updateId}`, params));
65
- const data = unwrapResp(resp);
66
- const pagination = (resp.pagination || {});
67
- const mentions = (resp.mentions || {});
68
- if (isJsonMode(feed)) {
69
- jsonOut({ ...data, pagination, mentions });
70
- return;
71
- }
72
- const update = (data.update || data);
73
- const replies = (data.replies || []);
74
- const author = update.author?.name ||
75
- `User ${update.authorId}`;
76
- const vault = update.vault?.vaultSlug ||
77
- update.authorVault?.vaultSlug ||
78
- "?";
79
- const replyLines = [];
80
- for (const r of replies) {
81
- const rAuthor = r.author?.name ||
82
- `User ${r.authorId}`;
83
- const text = r.content || "";
84
- const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "…";
85
- replyLines.push(` - ${rAuthor}: ${truncated} (${r.createdAt})`);
86
- }
87
- const output = [
88
- `Feed Update: ${update.title || "(no title)"}`,
89
- `By: ${author} (vault: ${vault}) on ${update.createdAt}`,
90
- "",
91
- update.content || "",
92
- "",
93
- `Replies (${replies.length} items):`,
94
- ...replyLines,
95
- ...(pagination.hasMore
96
- ? [` Next cursor: ${pagination.nextCursor}`]
97
- : []),
98
- ].join("\n");
99
- console.log(output);
100
- });
101
- // ── Reply ──
102
- feed
103
- .command("post-reply <updateId>")
104
- .description("Post a reply to a brain update in the feed.")
105
- .requiredOption("--content <content>", 'Reply content (markdown supported, use "-" for stdin)')
106
- .action(async (updateId, opts) => {
107
- const content = opts.content === "-" ? readFileSync("/dev/stdin", "utf8") : opts.content;
108
- const resp = (await apiPost(`/feed/${updateId}/replies`, {
109
- content,
110
- }));
111
- const reply = unwrapResp(resp);
112
- const mentions = (resp.mentions || {});
113
- if (isJsonMode(feed)) {
114
- jsonOut({ ...reply, mentions });
115
- return;
116
- }
117
- console.log(`Reply created!\n ID: ${reply.id}\n Created: ${reply.createdAt}`);
118
- });
119
- // ── Edit reply ──
120
- feed
121
- .command("edit-reply <replyId>")
122
- .description("Edit a reply you authored in the feed.")
123
- .requiredOption("--content <content>", 'New reply content (markdown supported, use "-" for stdin)')
124
- .action(async (replyId, opts) => {
125
- const content = opts.content === "-" ? readFileSync("/dev/stdin", "utf8") : opts.content;
126
- const resp = (await apiPatch(`/brain-updates/replies/${replyId}`, {
127
- content,
128
- }));
129
- const reply = unwrapResp(resp);
130
- if (isJsonMode(feed)) {
131
- jsonOut(reply);
132
- return;
133
- }
134
- console.log(`Reply edited!\n ID: ${reply.id}\n Edited: ${reply.editedAt}`);
135
- });
136
- // ── Delete reply ──
137
- feed
138
- .command("delete-reply <replyId>")
139
- .description("Delete a reply you authored in the feed.")
140
- .action(async (replyId) => {
141
- await apiDelete(`/brain-updates/replies/${replyId}`);
142
- if (isJsonMode(feed)) {
143
- jsonOut({ replyId });
144
- return;
145
- }
146
- console.log(`Reply ${replyId} deleted.`);
147
- });
148
- }