@gobi-ai/cli 2.0.25 → 2.0.27

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.
@@ -4,12 +4,12 @@
4
4
  "name": "gobi-ai"
5
5
  },
6
6
  "description": "Claude Code plugin for the Gobi collaborative knowledge platform CLI",
7
- "version": "2.0.25",
7
+ "version": "2.0.27",
8
8
  "plugins": [
9
9
  {
10
10
  "name": "gobi",
11
11
  "description": "Manage the Gobi collaborative knowledge platform from the command line. Publish vault profiles, create posts and replies, generate images and videos.",
12
- "version": "2.0.25",
12
+ "version": "2.0.27",
13
13
  "author": {
14
14
  "name": "gobi-ai"
15
15
  },
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "gobi",
3
3
  "description": "Manage the Gobi collaborative knowledge platform from the command line",
4
- "version": "2.0.25",
4
+ "version": "2.0.27",
5
5
  "author": {
6
6
  "name": "gobi-ai"
7
7
  },
package/README.md CHANGED
@@ -178,31 +178,29 @@ A *Space* is a community knowledge area. A *Space Post* lives in one space. The
178
178
  | `gobi space list-topic-posts <topicSlug>` | List posts tagged with a topic |
179
179
  | `gobi space list-posts` | List posts in the space |
180
180
  | `gobi space get-post <postId> [--full]` | Get a post with its ancestors and replies. `--full` shows reply content without truncation. |
181
- | `gobi space create-post [--title <t>] (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--repost-post-id <id>] [--attach <file>]…` | Create a space post. Must provide content via `--content` or `--rich-text`. `--vault-slug` attributes it to a vault you own. `--repost-post-id` reposts an existing post (sets `repostPostId` on the new post). `--attach` uploads local media to render inline in-feed (repeatable; X-style mix rule — up to 4 photos OR 1 GIF OR 1 video). |
182
- | `gobi space edit-post <postId> [--title <t>] [--content <c>] [--vault-slug <slug>]` | Edit a space post. `--vault-slug ""` detaches the vault. |
181
+ | `gobi space create-post [--title <t>] (--content <c> \| --rich-text <json>) [--artifact <artifactId>] [--repost-post-id <id>] [--attach <file>]…` | Create a space post. Must provide content via `--content` or `--rich-text`. `--artifact` attaches an existing artifact to the post (repeatable). `--repost-post-id` reposts an existing post (sets `repostPostId` on the new post). `--attach` uploads local media to render inline in-feed (repeatable; X-style mix rule — up to 4 photos OR 1 GIF OR 1 video). |
182
+ | `gobi space edit-post <postId> [--title <t>] [--content <c>]` | Edit a space post. |
183
183
  | `gobi space delete-post <postId>` | Delete a space post |
184
- | `gobi space create-reply <postId> (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--attach <file>]…` | Create a reply to a space post. `--attach` works the same as on `create-post`. |
185
- | `gobi space edit-reply <replyId> [--content <c>] [--rich-text <json>] [--vault-slug <slug>]` | Edit a reply you authored. `--vault-slug ""` detaches attribution. |
184
+ | `gobi space create-reply <postId> (--content <c> \| --rich-text <json>) [--attach <file>]…` | Create a reply to a space post. `--attach` works the same as on `create-post`. |
185
+ | `gobi space edit-reply <replyId> [--content <c>] [--rich-text <json>]` | Edit a reply you authored. |
186
186
  | `gobi space delete-reply <replyId>` | Delete a reply you authored |
187
187
 
188
188
  ### Global feed (personal posts)
189
189
 
190
- A *Personal Post* surfaces in the public global feed. Pass `--vault-slug` to attribute it to a vault you own (it'll then surface on that vault's profile too); with no `--vault-slug` the post has no `authorVaultSlug` and lives only on the global feed. Same `Post` model as a Space Post, scoped to the user instead of a space.
190
+ A *Personal Post* surfaces in the public global feed. Same `Post` model as a Space Post, scoped to the user instead of a space.
191
191
 
192
192
  | Command | Description |
193
193
  |---------|-------------|
194
194
  | `gobi global feed [--following]` | List the global public feed (posts + replies, newest first). `--following` limits to authors you follow. |
195
- | `gobi global list-posts [--mine] [--vault-slug <slug>]` | List personal posts; filter to your own or by author vault |
195
+ | `gobi global list-posts [--mine]` | List personal posts; filter to your own |
196
196
  | `gobi global get-post <postId> [--full]` | Get a personal post with its ancestors and replies. `--full` shows reply content without truncation. |
197
- | `gobi global create-post [--title <t>] (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--repost-post-id <id>] [--attach <file>]…` | Create a personal post. `--repost-post-id` reposts an existing post. `--attach` uploads local media for inline rendering (see `gobi space create-post` above for the mix rule). |
198
- | `gobi global edit-post <postId> [--title <t>] [--content <c>] [--vault-slug <slug>]` | Edit a personal post you authored. `--vault-slug ""` detaches the vault. |
197
+ | `gobi global create-post [--title <t>] (--content <c> \| --rich-text <json>) [--artifact <artifactId>] [--repost-post-id <id>] [--attach <file>]…` | Create a personal post. `--artifact` attaches an existing artifact to the post (repeatable). `--repost-post-id` reposts an existing post. `--attach` uploads local media for inline rendering (see `gobi space create-post` above for the mix rule). |
198
+ | `gobi global edit-post <postId> [--title <t>] [--content <c>]` | Edit a personal post you authored. |
199
199
  | `gobi global delete-post <postId>` | Delete a personal post you authored |
200
- | `gobi global create-reply <postId> (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--attach <file>]…` | Create a reply to a personal post |
201
- | `gobi global edit-reply <replyId> [--content <c>] [--rich-text <json>] [--vault-slug <slug>]` | Edit a reply you authored. `--vault-slug ""` detaches attribution. |
200
+ | `gobi global create-reply <postId> (--content <c> \| --rich-text <json>) [--attach <file>]…` | Create a reply to a personal post |
201
+ | `gobi global edit-reply <replyId> [--content <c>] [--rich-text <json>]` | Edit a reply you authored. |
202
202
  | `gobi global delete-reply <replyId>` | Delete a reply you authored |
203
203
 
204
- `--vault-slug` requires that the caller hold `role: 'owner'` on the target vault. When set, it becomes the post's `authorVaultSlug`.
205
-
206
204
  ### Personal space (private posts)
207
205
 
208
206
  > Naming note: a **Personal Post** (under `gobi global`, above) is the public-feed kind — it lives on your vault profile and surfaces on the global feed. A **personal-space post** (this section, under `gobi personal`) is the private kind — same `Post` data model, but scoped via `personalSpaceUserId` so only you can see it.
@@ -214,11 +212,11 @@ Private posts and replies visible only to you. Same `Post` data model and subcom
214
212
  | `gobi personal feed` | Your personal-space feed (posts + replies, newest first) |
215
213
  | `gobi personal list-posts` | List personal-space posts |
216
214
  | `gobi personal get-post <postId> [--full]` | Get a personal-space post with its ancestors and replies |
217
- | `gobi personal create-post [--title <t>] (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--repost-post-id <id>] [--attach <file>]…` | Create a private post in your personal space. `--vault-slug` attributes it to a vault you own. `--attach` works the same as on `gobi global create-post`. |
218
- | `gobi personal edit-post <postId> [--title <t>] [--content <c>] [--vault-slug <slug>]` | Edit a personal-space post you authored |
215
+ | `gobi personal create-post [--title <t>] (--content <c> \| --rich-text <json>) [--artifact <artifactId>] [--repost-post-id <id>] [--attach <file>]…` | Create a private post in your personal space. `--artifact` attaches an existing artifact to the post (repeatable). `--attach` works the same as on `gobi global create-post`. |
216
+ | `gobi personal edit-post <postId> [--title <t>] [--content <c>]` | Edit a personal-space post you authored |
219
217
  | `gobi personal delete-post <postId>` | Delete a personal-space post you authored |
220
- | `gobi personal create-reply <postId> (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--attach <file>]…` | Reply to a personal-space post (inherits the parent's private scope) |
221
- | `gobi personal edit-reply <replyId> [--content <c>] [--rich-text <json>] [--vault-slug <slug>]` | Edit a reply you authored |
218
+ | `gobi personal create-reply <postId> (--content <c> \| --rich-text <json>) [--attach <file>]…` | Reply to a personal-space post (inherits the parent's private scope) |
219
+ | `gobi personal edit-reply <replyId> [--content <c>] [--rich-text <json>]` | Edit a reply you authored |
222
220
  | `gobi personal delete-reply <replyId>` | Delete a reply you authored |
223
221
 
224
222
  ### Sense
@@ -14,7 +14,7 @@ First, verify the user is set up:
14
14
  gobi --json auth status
15
15
  ```
16
16
 
17
- Check that `.gobi/settings.yaml` exists. If you plan to publish to a Space (`gobi space create-post`), it must contain `selectedSpaceSlug` — otherwise stop and ask the user to run `gobi space warp` first. `vaultSlug` is optional for `gobi global create-post`; if missing the post is created without an `authorVaultSlug` (vault-less). Run `gobi vault init` if the user wants the post attributed to a vault they own.
17
+ Check that `.gobi/settings.yaml` exists. If you plan to publish to a Space (`gobi space create-post`), it must contain `selectedSpaceSlug` — otherwise stop and ask the user to run `gobi space warp` first.
18
18
 
19
19
  ## Draft a personal post
20
20
 
@@ -1,6 +1,6 @@
1
1
  import { WEB_BASE_URL } from "../constants.js";
2
2
  import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
3
- import { isJsonMode, jsonOut, readStdin, resolveVaultSlug, unwrapResp, } from "./utils.js";
3
+ import { isJsonMode, jsonOut, readStdin, unwrapResp, } from "./utils.js";
4
4
  import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
5
5
  function readContent(value) {
6
6
  if (value === "-")
@@ -8,14 +8,7 @@ function readContent(value) {
8
8
  return value;
9
9
  }
10
10
  function buildPersonalPostUrl(post) {
11
- const id = post.id;
12
- const vaultSlug = post.vault?.vaultSlug ||
13
- post.authorVault?.vaultSlug ||
14
- post.authorVaultSlug ||
15
- undefined;
16
- return vaultSlug
17
- ? `${WEB_BASE_URL}/@${vaultSlug}?postId=${id}`
18
- : `${WEB_BASE_URL}/posts/${id}`;
11
+ return `${WEB_BASE_URL}/posts/${post.id}`;
19
12
  }
20
13
  function formatFeedLine(m) {
21
14
  const isReply = m.parentPostId != null ||
@@ -79,7 +72,6 @@ export function registerGlobalCommand(program) {
79
72
  .option("--limit <number>", "Items per page", "20")
80
73
  .option("--cursor <string>", "Pagination cursor from previous response")
81
74
  .option("--mine", "Only include posts authored by you")
82
- .option("--vault-slug <vaultSlug>", "Filter by author vault slug")
83
75
  .action(async (opts) => {
84
76
  const params = {
85
77
  limit: parseInt(opts.limit, 10),
@@ -88,8 +80,6 @@ export function registerGlobalCommand(program) {
88
80
  params.cursor = opts.cursor;
89
81
  if (opts.mine)
90
82
  params.mine = "true";
91
- if (opts.vaultSlug)
92
- params.vaultSlug = opts.vaultSlug;
93
83
  const resp = (await apiGet(`/posts`, params));
94
84
  if (isJsonMode(global)) {
95
85
  jsonOut({
@@ -108,10 +98,7 @@ export function registerGlobalCommand(program) {
108
98
  for (const t of items) {
109
99
  const author = t.author?.name ||
110
100
  `User ${t.authorId}`;
111
- const vaultSlug = t.vault?.vaultSlug ||
112
- t.authorVault?.vaultSlug ||
113
- "?";
114
- lines.push(`- [${t.id}] "${t.title}" by ${author} (vault: ${vaultSlug}, ${t.replyCount ?? 0} replies, ${t.createdAt})`);
101
+ lines.push(`- [${t.id}] "${t.title}" by ${author} (${t.replyCount ?? 0} replies, ${t.createdAt})`);
115
102
  }
116
103
  const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
117
104
  console.log(`Posts (${items.length} items):\n` + lines.join("\n") + footer);
@@ -146,9 +133,6 @@ export function registerGlobalCommand(program) {
146
133
  const replies = (data.replies || []);
147
134
  const author = post.author?.name ||
148
135
  `User ${post.authorId}`;
149
- const vault = post.vault?.vaultSlug ||
150
- post.authorVault?.vaultSlug ||
151
- "?";
152
136
  const ancestorLines = [];
153
137
  if (ancestors.length) {
154
138
  ancestors.forEach((a, i) => {
@@ -169,7 +153,7 @@ export function registerGlobalCommand(program) {
169
153
  : `Post: ${post.title || "(no title)"}`;
170
154
  const output = [
171
155
  heading,
172
- `By: ${author} (vault: ${vault}) on ${post.createdAt}`,
156
+ `By: ${author} on ${post.createdAt}`,
173
157
  ...(ancestorLines.length
174
158
  ? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
175
159
  : []),
@@ -187,11 +171,11 @@ export function registerGlobalCommand(program) {
187
171
  // ── Create post ──
188
172
  global
189
173
  .command("create-post")
190
- .description("Create a post in the global feed. --vault-slug attributes it to a vault you own. With no --vault-slug, the post is created without an authorVaultSlug (vault-less personal post).")
174
+ .description("Create a post in the global feed.")
191
175
  .option("--title <title>", "Title of the post")
192
176
  .option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
193
177
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
194
- .option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.")
178
+ .option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
195
179
  .option("--attach <file>", "Local media file to attach. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video.", (value, prev = []) => [...prev, value], [])
196
180
  .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.")
197
181
  .action(async (opts) => {
@@ -201,10 +185,6 @@ export function registerGlobalCommand(program) {
201
185
  if (opts.content && opts.richText) {
202
186
  throw new Error("--content and --rich-text are mutually exclusive.");
203
187
  }
204
- let authorVaultSlug;
205
- if (opts.vaultSlug) {
206
- authorVaultSlug = resolveVaultSlug({ vaultSlug: opts.vaultSlug });
207
- }
208
188
  const body = {};
209
189
  if (opts.title != null)
210
190
  body.title = opts.title;
@@ -221,8 +201,8 @@ export function registerGlobalCommand(program) {
221
201
  }
222
202
  body.richText = parsed;
223
203
  }
224
- if (authorVaultSlug)
225
- body.authorVaultSlug = authorVaultSlug;
204
+ if (opts.artifact && opts.artifact.length > 0)
205
+ body.artifactIds = opts.artifact;
226
206
  if (opts.attach && opts.attach.length > 0) {
227
207
  assertPostAttachmentMix(opts.attach);
228
208
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -254,25 +234,18 @@ export function registerGlobalCommand(program) {
254
234
  .option("--title <title>", "New title")
255
235
  .option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
256
236
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
257
- .option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug).")
258
237
  .option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
259
238
  .action(async (postId, opts) => {
260
- const wantsVaultChange = !!opts.vaultSlug;
261
239
  const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
262
240
  if (opts.title == null &&
263
241
  opts.content == null &&
264
242
  opts.richText == null &&
265
- !wantsVaultChange &&
266
243
  !wantsAttachChange) {
267
- throw new Error("Provide at least --title, --content, --rich-text, --vault-slug, or --attach to update.");
244
+ throw new Error("Provide at least --title, --content, --rich-text, or --attach to update.");
268
245
  }
269
246
  if (opts.content && opts.richText) {
270
247
  throw new Error("--content and --rich-text are mutually exclusive.");
271
248
  }
272
- let authorVaultSlug;
273
- if (opts.vaultSlug) {
274
- authorVaultSlug = resolveVaultSlug(opts);
275
- }
276
249
  const body = {};
277
250
  if (opts.title != null)
278
251
  body.title = opts.title;
@@ -289,8 +262,6 @@ export function registerGlobalCommand(program) {
289
262
  }
290
263
  body.richText = parsed;
291
264
  }
292
- if (authorVaultSlug !== undefined)
293
- body.authorVaultSlug = authorVaultSlug;
294
265
  if (opts.attach && opts.attach.length > 0) {
295
266
  assertPostAttachmentMix(opts.attach);
296
267
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -321,7 +292,6 @@ export function registerGlobalCommand(program) {
321
292
  .description("Create a reply to a post in the global feed.")
322
293
  .option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
323
294
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
324
- .option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug).")
325
295
  .option("--attach <file>", "Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video.", (value, prev = []) => [...prev, value], [])
326
296
  .action(async (postId, opts) => {
327
297
  if (!opts.content && !opts.richText) {
@@ -330,10 +300,6 @@ export function registerGlobalCommand(program) {
330
300
  if (opts.content && opts.richText) {
331
301
  throw new Error("--content and --rich-text are mutually exclusive.");
332
302
  }
333
- let authorVaultSlug;
334
- if (opts.vaultSlug) {
335
- authorVaultSlug = resolveVaultSlug(opts);
336
- }
337
303
  const body = {};
338
304
  if (opts.content != null) {
339
305
  body.content = readContent(opts.content);
@@ -348,8 +314,6 @@ export function registerGlobalCommand(program) {
348
314
  }
349
315
  body.richText = parsed;
350
316
  }
351
- if (authorVaultSlug)
352
- body.authorVaultSlug = authorVaultSlug;
353
317
  if (opts.attach && opts.attach.length > 0) {
354
318
  assertPostAttachmentMix(opts.attach);
355
319
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -367,19 +331,13 @@ export function registerGlobalCommand(program) {
367
331
  .description("Edit a reply you authored in the global feed.")
368
332
  .option("--content <content>", "New reply content (markdown supported, use \"-\" for stdin)")
369
333
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
370
- .option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug).")
371
334
  .action(async (replyId, opts) => {
372
- const wantsVaultChange = !!opts.vaultSlug;
373
- if (opts.content == null && opts.richText == null && !wantsVaultChange) {
374
- throw new Error("Provide at least --content, --rich-text, or --vault-slug to update.");
335
+ if (opts.content == null && opts.richText == null) {
336
+ throw new Error("Provide at least --content or --rich-text to update.");
375
337
  }
376
338
  if (opts.content && opts.richText) {
377
339
  throw new Error("--content and --rich-text are mutually exclusive.");
378
340
  }
379
- let authorVaultSlug;
380
- if (opts.vaultSlug) {
381
- authorVaultSlug = resolveVaultSlug(opts);
382
- }
383
341
  const body = {};
384
342
  if (opts.content != null) {
385
343
  body.content = readContent(opts.content);
@@ -394,8 +352,6 @@ export function registerGlobalCommand(program) {
394
352
  }
395
353
  body.richText = parsed;
396
354
  }
397
- if (authorVaultSlug !== undefined)
398
- body.authorVaultSlug = authorVaultSlug;
399
355
  const resp = (await apiPatch(`/posts/replies/${replyId}`, body));
400
356
  const reply = unwrapResp(resp);
401
357
  if (isJsonMode(global)) {
@@ -1,5 +1,5 @@
1
1
  import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
2
- import { isJsonMode, jsonOut, readStdin, resolveVaultSlug, unwrapResp, } from "./utils.js";
2
+ import { isJsonMode, jsonOut, readStdin, unwrapResp, } from "./utils.js";
3
3
  import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
4
4
  function readContent(value) {
5
5
  if (value === "-")
@@ -93,10 +93,7 @@ export function registerPersonalCommand(program) {
93
93
  }
94
94
  const lines = [];
95
95
  for (const t of items) {
96
- const vaultSlug = t.vault?.vaultSlug ||
97
- t.authorVault?.vaultSlug ||
98
- "—";
99
- lines.push(`- [${t.id}] "${t.title ?? "(no title)"}" (vault: ${vaultSlug}, ${t.replyCount ?? 0} replies, ${t.createdAt})`);
96
+ lines.push(`- [${t.id}] "${t.title ?? "(no title)"}" (${t.replyCount ?? 0} replies, ${t.createdAt})`);
100
97
  }
101
98
  const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
102
99
  console.log(`Personal-space posts (${items.length} of ${allItems.length} feed items):\n` +
@@ -138,9 +135,6 @@ export function registerPersonalCommand(program) {
138
135
  const replies = (data.replies || []);
139
136
  const author = post.author?.name ||
140
137
  `User ${post.authorId}`;
141
- const vault = post.vault?.vaultSlug ||
142
- post.authorVault?.vaultSlug ||
143
- "—";
144
138
  const ancestorLines = [];
145
139
  if (ancestors.length) {
146
140
  ancestors.forEach((a, i) => {
@@ -161,7 +155,7 @@ export function registerPersonalCommand(program) {
161
155
  : `Post: ${post.title || "(no title)"} (private)`;
162
156
  const output = [
163
157
  heading,
164
- `By: ${author} (vault: ${vault}) on ${post.createdAt}`,
158
+ `By: ${author} on ${post.createdAt}`,
165
159
  ...(ancestorLines.length
166
160
  ? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
167
161
  : []),
@@ -185,11 +179,11 @@ export function registerPersonalCommand(program) {
185
179
  // private posts have no audience.
186
180
  personal
187
181
  .command("create-post")
188
- .description("Create a private post in your personal space. --vault-slug attributes it to a vault you own. Visible only to you.")
182
+ .description("Create a private post in your personal space. Visible only to you.")
189
183
  .option("--title <title>", "Title of the post")
190
184
  .option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
191
185
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
192
- .option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.")
186
+ .option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
193
187
  .option("--attach <file>", "Local media file to attach. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video.", (value, prev = []) => [...prev, value], [])
194
188
  .option("--repost-post-id <postId>", "Wrap an existing top-level post as the embedded card on this new private post. The referenced post must be visible to you (your own personal-space post, a global-feed post, or a post in a space you're a member of). Reposting someone else's personal-space post returns 404.")
195
189
  .action(async (opts) => {
@@ -199,10 +193,6 @@ export function registerPersonalCommand(program) {
199
193
  if (opts.content && opts.richText) {
200
194
  throw new Error("--content and --rich-text are mutually exclusive.");
201
195
  }
202
- let authorVaultSlug;
203
- if (opts.vaultSlug) {
204
- authorVaultSlug = resolveVaultSlug({ vaultSlug: opts.vaultSlug });
205
- }
206
196
  const body = {};
207
197
  if (opts.title != null)
208
198
  body.title = opts.title;
@@ -219,8 +209,8 @@ export function registerPersonalCommand(program) {
219
209
  }
220
210
  body.richText = parsed;
221
211
  }
222
- if (authorVaultSlug)
223
- body.authorVaultSlug = authorVaultSlug;
212
+ if (opts.artifact && opts.artifact.length > 0)
213
+ body.artifactIds = opts.artifact;
224
214
  if (opts.attach && opts.attach.length > 0) {
225
215
  assertPostAttachmentMix(opts.attach);
226
216
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -255,25 +245,18 @@ export function registerPersonalCommand(program) {
255
245
  .option("--title <title>", "New title")
256
246
  .option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
257
247
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
258
- .option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug).")
259
248
  .option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
260
249
  .action(async (postId, opts) => {
261
- const wantsVaultChange = !!opts.vaultSlug;
262
250
  const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
263
251
  if (opts.title == null &&
264
252
  opts.content == null &&
265
253
  opts.richText == null &&
266
- !wantsVaultChange &&
267
254
  !wantsAttachChange) {
268
- throw new Error("Provide at least --title, --content, --rich-text, --vault-slug, or --attach to update.");
255
+ throw new Error("Provide at least --title, --content, --rich-text, or --attach to update.");
269
256
  }
270
257
  if (opts.content && opts.richText) {
271
258
  throw new Error("--content and --rich-text are mutually exclusive.");
272
259
  }
273
- let authorVaultSlug;
274
- if (opts.vaultSlug) {
275
- authorVaultSlug = resolveVaultSlug(opts);
276
- }
277
260
  const body = {};
278
261
  if (opts.title != null)
279
262
  body.title = opts.title;
@@ -290,8 +273,6 @@ export function registerPersonalCommand(program) {
290
273
  }
291
274
  body.richText = parsed;
292
275
  }
293
- if (authorVaultSlug !== undefined)
294
- body.authorVaultSlug = authorVaultSlug;
295
276
  if (opts.attach && opts.attach.length > 0) {
296
277
  assertPostAttachmentMix(opts.attach);
297
278
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -327,7 +308,6 @@ export function registerPersonalCommand(program) {
327
308
  .description("Reply to a personal-space post. The reply inherits the parent's private scope automatically.")
328
309
  .option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
329
310
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
330
- .option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug).")
331
311
  .option("--attach <file>", "Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video.", (value, prev = []) => [...prev, value], [])
332
312
  .action(async (postId, opts) => {
333
313
  if (!opts.content && !opts.richText) {
@@ -336,10 +316,6 @@ export function registerPersonalCommand(program) {
336
316
  if (opts.content && opts.richText) {
337
317
  throw new Error("--content and --rich-text are mutually exclusive.");
338
318
  }
339
- let authorVaultSlug;
340
- if (opts.vaultSlug) {
341
- authorVaultSlug = resolveVaultSlug(opts);
342
- }
343
319
  const body = {};
344
320
  if (opts.content != null) {
345
321
  body.content = readContent(opts.content);
@@ -354,8 +330,6 @@ export function registerPersonalCommand(program) {
354
330
  }
355
331
  body.richText = parsed;
356
332
  }
357
- if (authorVaultSlug)
358
- body.authorVaultSlug = authorVaultSlug;
359
333
  if (opts.attach && opts.attach.length > 0) {
360
334
  assertPostAttachmentMix(opts.attach);
361
335
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -373,19 +347,13 @@ export function registerPersonalCommand(program) {
373
347
  .description("Edit a reply you authored in your personal space.")
374
348
  .option("--content <content>", "New reply content (markdown supported, use \"-\" for stdin)")
375
349
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
376
- .option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug).")
377
350
  .action(async (replyId, opts) => {
378
- const wantsVaultChange = !!opts.vaultSlug;
379
- if (opts.content == null && opts.richText == null && !wantsVaultChange) {
380
- throw new Error("Provide at least --content, --rich-text, or --vault-slug to update.");
351
+ if (opts.content == null && opts.richText == null) {
352
+ throw new Error("Provide at least --content or --rich-text to update.");
381
353
  }
382
354
  if (opts.content && opts.richText) {
383
355
  throw new Error("--content and --rich-text are mutually exclusive.");
384
356
  }
385
- let authorVaultSlug;
386
- if (opts.vaultSlug) {
387
- authorVaultSlug = resolveVaultSlug(opts);
388
- }
389
357
  const body = {};
390
358
  if (opts.content != null) {
391
359
  body.content = readContent(opts.content);
@@ -400,8 +368,6 @@ export function registerPersonalCommand(program) {
400
368
  }
401
369
  body.richText = parsed;
402
370
  }
403
- if (authorVaultSlug !== undefined)
404
- body.authorVaultSlug = authorVaultSlug;
405
371
  const resp = (await apiPatch(`/posts/replies/${replyId}`, body));
406
372
  const reply = unwrapResp(resp);
407
373
  if (isJsonMode(personal)) {
@@ -1,7 +1,7 @@
1
1
  import { WEB_BASE_URL } from "../constants.js";
2
2
  import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
3
3
  import { requireSpace, selectSpace, setSpaceRequirement, writeSpaceSetting, } from "./init.js";
4
- import { isJsonMode, jsonOut, readStdin, resolveSpaceSlug, resolveVaultSlug, unwrapResp, } from "./utils.js";
4
+ import { isJsonMode, jsonOut, readStdin, resolveSpaceSlug, unwrapResp, } from "./utils.js";
5
5
  import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
6
6
  function readContent(value) {
7
7
  if (value === "-")
@@ -315,7 +315,7 @@ export function registerSpaceCommand(program) {
315
315
  .option("--title <title>", "Title of the post")
316
316
  .option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
317
317
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
318
- .option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.")
318
+ .option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
319
319
  .option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
320
320
  .option("--attach <file>", "Local media file to attach. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video.", (value, prev = []) => [...prev, value], [])
321
321
  .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.")
@@ -326,10 +326,6 @@ export function registerSpaceCommand(program) {
326
326
  if (opts.content && opts.richText) {
327
327
  throw new Error("--content and --rich-text are mutually exclusive.");
328
328
  }
329
- let authorVaultSlug;
330
- if (opts.vaultSlug) {
331
- authorVaultSlug = resolveVaultSlug({ vaultSlug: opts.vaultSlug });
332
- }
333
329
  const body = {};
334
330
  if (opts.title != null)
335
331
  body.title = opts.title;
@@ -346,8 +342,8 @@ export function registerSpaceCommand(program) {
346
342
  }
347
343
  body.richText = parsed;
348
344
  }
349
- if (authorVaultSlug)
350
- body.authorVaultSlug = authorVaultSlug;
345
+ if (opts.artifact && opts.artifact.length > 0)
346
+ body.artifactIds = opts.artifact;
351
347
  if (opts.attach && opts.attach.length > 0) {
352
348
  assertPostAttachmentMix(opts.attach);
353
349
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -379,27 +375,20 @@ export function registerSpaceCommand(program) {
379
375
  .option("--title <title>", "New title for the post")
380
376
  .option("--content <content>", "New content for the post (markdown supported, use \"-\" for stdin)")
381
377
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
382
- .option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.")
383
378
  .option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
384
379
  .option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
385
380
  .action(async (postId, opts) => {
386
- const wantsVaultChange = !!opts.vaultSlug;
387
381
  const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
388
382
  if (opts.title == null &&
389
383
  opts.content == null &&
390
384
  opts.richText == null &&
391
- !wantsVaultChange &&
392
385
  !wantsAttachChange) {
393
- throw new Error("Provide at least --title, --content, --rich-text, --vault-slug, or --attach to update.");
386
+ throw new Error("Provide at least --title, --content, --rich-text, or --attach to update.");
394
387
  }
395
388
  if (opts.content && opts.richText) {
396
389
  throw new Error("--content and --rich-text are mutually exclusive.");
397
390
  }
398
391
  const spaceSlug = resolveSpaceSlug(space, opts);
399
- let authorVaultSlug;
400
- if (opts.vaultSlug) {
401
- authorVaultSlug = resolveVaultSlug(opts);
402
- }
403
392
  const body = {};
404
393
  if (opts.title != null)
405
394
  body.title = opts.title;
@@ -416,8 +405,6 @@ export function registerSpaceCommand(program) {
416
405
  }
417
406
  body.richText = parsed;
418
407
  }
419
- if (authorVaultSlug !== undefined)
420
- body.authorVaultSlug = authorVaultSlug;
421
408
  if (opts.attach && opts.attach.length > 0) {
422
409
  assertPostAttachmentMix(opts.attach);
423
410
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -452,7 +439,6 @@ export function registerSpaceCommand(program) {
452
439
  .description("Create a reply to a post in a space.")
453
440
  .option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
454
441
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
455
- .option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug). Caller must own the vault.")
456
442
  .option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
457
443
  .option("--attach <file>", "Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video.", (value, prev = []) => [...prev, value], [])
458
444
  .action(async (postId, opts) => {
@@ -462,10 +448,6 @@ export function registerSpaceCommand(program) {
462
448
  if (opts.content && opts.richText) {
463
449
  throw new Error("--content and --rich-text are mutually exclusive.");
464
450
  }
465
- let authorVaultSlug;
466
- if (opts.vaultSlug) {
467
- authorVaultSlug = resolveVaultSlug(opts);
468
- }
469
451
  const body = {};
470
452
  if (opts.content != null) {
471
453
  body.content = readContent(opts.content);
@@ -480,8 +462,6 @@ export function registerSpaceCommand(program) {
480
462
  }
481
463
  body.richText = parsed;
482
464
  }
483
- if (authorVaultSlug)
484
- body.authorVaultSlug = authorVaultSlug;
485
465
  if (opts.attach && opts.attach.length > 0) {
486
466
  assertPostAttachmentMix(opts.attach);
487
467
  body.attachments = await uploadPostAttachments(opts.attach);
@@ -501,21 +481,15 @@ export function registerSpaceCommand(program) {
501
481
  .description("Edit a reply you authored in a space.")
502
482
  .option("--content <content>", "New content for the reply (markdown supported, use \"-\" for stdin)")
503
483
  .option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
504
- .option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug). Caller must own the vault.")
505
484
  .option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
506
485
  .action(async (replyId, opts) => {
507
- const wantsVaultChange = !!opts.vaultSlug;
508
- if (opts.content == null && opts.richText == null && !wantsVaultChange) {
509
- throw new Error("Provide at least --content, --rich-text, or --vault-slug to update.");
486
+ if (opts.content == null && opts.richText == null) {
487
+ throw new Error("Provide at least --content or --rich-text to update.");
510
488
  }
511
489
  if (opts.content && opts.richText) {
512
490
  throw new Error("--content and --rich-text are mutually exclusive.");
513
491
  }
514
492
  const spaceSlug = resolveSpaceSlug(space, opts);
515
- let authorVaultSlug;
516
- if (opts.vaultSlug) {
517
- authorVaultSlug = resolveVaultSlug(opts);
518
- }
519
493
  const body = {};
520
494
  if (opts.content != null) {
521
495
  body.content = readContent(opts.content);
@@ -530,8 +504,6 @@ export function registerSpaceCommand(program) {
530
504
  }
531
505
  body.richText = parsed;
532
506
  }
533
- if (authorVaultSlug !== undefined)
534
- body.authorVaultSlug = authorVaultSlug;
535
507
  const resp = (await apiPatch(`/spaces/${spaceSlug}/replies/${replyId}`, body));
536
508
  const msg = unwrapResp(resp);
537
509
  if (isJsonMode(space)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "2.0.25",
3
+ "version": "2.0.27",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -9,12 +9,12 @@ description: >-
9
9
  allowed-tools: Bash(gobi:*)
10
10
  metadata:
11
11
  author: gobi-ai
12
- version: "2.0.25"
12
+ version: "2.0.27"
13
13
  ---
14
14
 
15
15
  # gobi-artifact
16
16
 
17
- Gobi artifact commands for versioned, post-attachable creations (v2.0.25).
17
+ Gobi artifact commands for versioned, post-attachable creations (v2.0.27).
18
18
 
19
19
  Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
20
20
 
@@ -8,12 +8,12 @@ description: >-
8
8
  allowed-tools: Bash(gobi:*)
9
9
  metadata:
10
10
  author: gobi-ai
11
- version: "2.0.25"
11
+ version: "2.0.27"
12
12
  ---
13
13
 
14
14
  # gobi-core
15
15
 
16
- Core CLI commands for the Gobi collaborative knowledge platform (v2.0.25).
16
+ Core CLI commands for the Gobi collaborative knowledge platform (v2.0.27).
17
17
 
18
18
  ## Prerequisites
19
19
 
@@ -77,16 +77,8 @@ gobi auth status
77
77
  | `vault init` | no (it sets it up) | no | – |
78
78
  | `space list` / `warp [slug]` / `get [slug]` | no | no | – |
79
79
  | `space list-topics` / `feed` / `list-posts` / `get-post` / `create-post` / `edit-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` / `list-topic-posts` | no | **yes** | parent `--space-slug <slug>` |
80
- | `global feed` / `list-posts` / `get-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` | no | no | – |
81
- | `global create-post` | optional¹ | no | command-level `--vault-slug <slug>` |
82
- | `global edit-post` | optional² | no | command-level `--vault-slug <slug>` |
83
- | `personal feed` / `list-posts` / `get-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` | no | no | – |
84
- | `personal create-post` | optional¹ | no | command-level `--vault-slug <slug>` |
85
- | `personal edit-post` | optional² | no | command-level `--vault-slug <slug>` |
86
-
87
- ¹ `global create-post` accepts `--vault-slug`, optional. With no flag and no `vaultSlug` in `.gobi`, the post is created with no `authorVaultSlug` (vault-less personal post) — same as a Space post that isn't attributed to any vault. Set `--vault-slug` to attribute it.
88
-
89
- ² `global edit-post` only consults `--vault-slug` when you pass it explicitly. Use `--vault-slug ""` to detach an existing attribution; non-empty re-attaches.
80
+ | `global feed` / `list-posts` / `get-post` / `create-post` / `edit-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` | no | no | – |
81
+ | `personal feed` / `list-posts` / `get-post` / `create-post` / `edit-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` | no | no | |
90
82
 
91
83
  When a command needs vault or space and neither `.gobi` nor an override flag provides it, the CLI prints a one-line warning before the command runs (e.g. `Vault not set. Run 'gobi vault init' first, or pass --vault-slug.`). The warning is suppressed under `--json`.
92
84
 
@@ -7,7 +7,7 @@ description: >-
7
7
  allowed-tools: Bash(gobi:*)
8
8
  metadata:
9
9
  author: gobi-ai
10
- version: "2.0.25"
10
+ version: "2.0.27"
11
11
  ---
12
12
 
13
13
  # Gobi Homepage Developer Guide
@@ -10,12 +10,12 @@ description: >-
10
10
  allowed-tools: Bash(gobi:*)
11
11
  metadata:
12
12
  author: gobi-ai
13
- version: "2.0.25"
13
+ version: "2.0.27"
14
14
  ---
15
15
 
16
16
  # gobi-media
17
17
 
18
- Gobi media generation commands (v2.0.25).
18
+ Gobi media generation commands (v2.0.27).
19
19
 
20
20
  Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
21
21
 
@@ -7,12 +7,12 @@ description: >-
7
7
  allowed-tools: Bash(gobi:*)
8
8
  metadata:
9
9
  author: gobi-ai
10
- version: "2.0.25"
10
+ version: "2.0.27"
11
11
  ---
12
12
 
13
13
  # gobi-sense
14
14
 
15
- Gobi sense commands for activity and transcription data (v2.0.25).
15
+ Gobi sense commands for activity and transcription data (v2.0.27).
16
16
 
17
17
  Requires gobi-cli installed and authenticated. See the **gobi-core** skill for setup.
18
18
 
@@ -11,12 +11,12 @@ description: >-
11
11
  allowed-tools: Bash(gobi:*)
12
12
  metadata:
13
13
  author: gobi-ai
14
- version: "2.0.25"
14
+ version: "2.0.27"
15
15
  ---
16
16
 
17
17
  # gobi-space
18
18
 
19
- Gobi space, global, and personal-space posts (v2.0.25).
19
+ Gobi space, global, and personal-space posts (v2.0.27).
20
20
 
21
21
  Requires gobi-cli installed and authenticated. See the **gobi-core** skill for setup.
22
22
 
@@ -25,10 +25,10 @@ Requires gobi-cli installed and authenticated. See the **gobi-core** skill for s
25
25
  The same `Post` data type drives all three surfaces — the difference is **scope**:
26
26
 
27
27
  - **Space Post** — `gobi space …` — lives in a community space's feed.
28
- - **Personal Post** — `gobi global …` — surfaces in the public global feed. When attributed via `--vault-slug` it also surfaces on that vault's profile; with no vault attribution it lives only on the global feed.
28
+ - **Personal Post** — `gobi global …` — lives on the public global feed.
29
29
  - **Personal-space Post** — `gobi personal …` — private posts and replies visible only to the author. Same shape as `gobi global`, scoped via `personalSpaceUserId` so they never surface on the public feed.
30
30
 
31
- Anything you can do to a Space Post (reply, edit, delete, attribute to a vault) you can do to a Personal Post or a Personal-space Post.
31
+ Anything you can do to a Space Post (reply, edit, delete) you can do to a Personal Post or a Personal-space Post.
32
32
 
33
33
  - When the user wants to explore or catch up on what's happening in their space, invoke `/gobi:space-explore`.
34
34
  - When the user wants to share or post learnings from the current session, invoke `/gobi:space-share`.
@@ -45,9 +45,9 @@ Anything you can do to a Space Post (reply, edit, delete, attribute to a vault)
45
45
 
46
46
  The same applies to replies: a reply has only `--content` (no title), so do not synthesize a title-like heading at the top of a reply either.
47
47
 
48
- ## Author vault attribution (`--vault-slug`)
48
+ ## Attaching artifacts (`--artifact`)
49
49
 
50
- `create-post` / `edit-post` across all three scopes (`gobi space`, `gobi global`, `gobi personal`) accept `--vault-slug <slug>`. When set, the slug becomes the post's `authorVaultSlug` the vault the user is posting on behalf of. The caller must hold `role: 'owner'` on that vault. Pass `--vault-slug ""` on edit to detach.
50
+ Posts have no vault attribution. `create-post` across all three scopes (`gobi space`, `gobi global`, `gobi personal`) accepts `--artifact <artifactId>` (repeatable) to attach existing artifacts to the post. To author a vault-anchored document, create a markdown artifact (`gobi artifact create --kind markdown --vault-slug <slug>`) and attach it via `--artifact`. See the **gobi-artifact** skill.
51
51
 
52
52
  > **Wiki-link uploads moved to artifacts.** The `--auto-attachments` flag that used to upload `[[wiki-linked files]]` from a post body now lives on `gobi artifact create` / `gobi artifact revise` (markdown kinds). To publish a markdown creation with resolvable wikilinks, create a markdown artifact with `--vault-slug` + `--auto-attachments` and attach it to the post (`--post-id`). See the **gobi-artifact** skill. Before relying on wikilink resolution, confirm the anchor vault is published: `gobi --json vault status --vault-slug <slug>` should report `isPublished: true`.
53
53
 
@@ -63,13 +63,12 @@ Use `--attach` for media you want shown in the post itself; use a markdown **art
63
63
 
64
64
  Once a post is created, you can build a shareable URL from the response:
65
65
 
66
- - **Personal post on a vault** — `https://gobispace.com/@{authorVaultSlug}?postId={id}` (e.g. `https://gobispace.com/@jyk?postId=144869`). This is the canonical share link for `gobi global` posts attributed to a vault.
67
- - **Personal post without a vault** (created with no `--vault-slug`) — use `https://gobispace.com/posts/{id}` as a direct fallback.
66
+ - **Personal post** — `https://gobispace.com/posts/{id}` (e.g. `https://gobispace.com/posts/144869`). This is the canonical share link for `gobi global` posts.
68
67
  - **Space post** — `https://gobispace.com/spaces/{spaceSlug}?postId={id}` (overlay on the space feed) or `https://gobispace.com/spaces/{spaceSlug}/posts/{id}` (dedicated page).
69
68
  - **Vault profile** — `https://gobispace.com/@{vaultSlug}`.
70
69
  - **Vault file** — `https://gobispace.com/file/{vaultSlug}?path={path}` (e.g. `https://gobispace.com/file/jyk?path=notes/intro.md`). First-class URL for linking to a single file from a published vault — renders in the main feed chrome (not the vault homepage). Use this when a post body or reply needs to point readers at a specific vault file. URL-encode each path segment. See **gobi-vault** skill for full semantics.
71
70
 
72
- When you echo a "Post created!" line (or the JSON response is consumed by another agent), include the assembled URL using the fields actually returned (`id`, `authorVaultSlug`, `spaceSlug`) don't fabricate slugs.
71
+ When you echo a "Post created!" line (or the JSON response is consumed by another agent), include the assembled URL using the fields actually returned `id` for global posts, `spaceSlug` + `id` for space posts. Don't fabricate slugs.
73
72
 
74
73
  ## Prerequisites & space slug
75
74
 
@@ -84,7 +83,7 @@ When you echo a "Post created!" line (or the JSON response is consumed by anothe
84
83
 
85
84
  If `.gobi/settings.yaml` has no `selectedSpaceSlug` and `--space-slug` isn't passed, the command will error.
86
85
 
87
- `gobi global` commands target the public global feed and have no space requirement and no `.gobi` requirement. `gobi global create-post` is symmetric with `gobi space create-post`: a vault is **optional** — pass `--vault-slug <slug>` to attribute the post; with no flag the post is created without an `authorVaultSlug`.
86
+ `gobi global` commands target the public global feed and have no space requirement and no `.gobi` requirement. `gobi global create-post` is symmetric with `gobi space create-post`: attach existing artifacts with `--artifact <artifactId>` (repeatable).
88
87
 
89
88
  ## Important: JSON Mode
90
89
 
@@ -112,8 +111,8 @@ gobi --json space list-posts
112
111
  ### Space posts
113
112
  - `gobi space list-posts` — List posts in a space (paginated).
114
113
  - `gobi space get-post <postId>` — Get a post with its ancestors and replies (paginated). Ancestors and replies are returned together; there is no separate `ancestors` or `list-replies` command.
115
- - `gobi space create-post` — Create a space post. `--vault-slug` attributes it to a vault you own.
116
- - `gobi space edit-post <postId>` — Edit a space post. You must be the author. `--vault-slug ""` detaches the vault.
114
+ - `gobi space create-post` — Create a space post. `--artifact <artifactId>` (repeatable) attaches existing artifacts.
115
+ - `gobi space edit-post <postId>` — Edit a space post. You must be the author.
117
116
  - `gobi space delete-post <postId>` — Delete a space post. You must be the author.
118
117
 
119
118
  ### Space replies
@@ -126,25 +125,25 @@ gobi --json space list-posts
126
125
  `gobi global` is the same surface for Personal Posts — posts that live on the author's profile and surface in the public global feed.
127
126
 
128
127
  - `gobi global feed` — List the public global feed (posts and replies, newest first).
129
- - `gobi global list-posts` — List personal posts. `--mine` for your own; `--vault-slug <slug>` to filter by author vault.
128
+ - `gobi global list-posts` — List personal posts. `--mine` for your own.
130
129
  - `gobi global get-post <postId>` — Get a personal post with its ancestors and replies.
131
- - `gobi global create-post` — Create a personal post. `--vault-slug` works the same as on `space create-post`. It is optional: with no flag, the post is created without an `authorVaultSlug` (vault-less personal post).
132
- - `gobi global edit-post <postId>` — Edit a personal post you authored. `--vault-slug ""` detaches the vault.
130
+ - `gobi global create-post` — Create a personal post. `--artifact <artifactId>` (repeatable) attaches existing artifacts.
131
+ - `gobi global edit-post <postId>` — Edit a personal post you authored.
133
132
  - `gobi global delete-post <postId>` — Delete a personal post you authored.
134
133
  - `gobi global create-reply <postId>` — Reply to a personal post.
135
- - `gobi global edit-reply <replyId>` — Edit a reply you authored. Accepts `--vault-slug` for author attribution (mirrors `space edit-reply`).
134
+ - `gobi global edit-reply <replyId>` — Edit a reply you authored.
136
135
  - `gobi global delete-reply <replyId>` — Delete a reply you authored.
137
136
 
138
137
  ### Personal-space posts (private)
139
138
 
140
139
  `gobi personal` mirrors `gobi global`'s subcommand and write-flag shape, but every row is scoped to a private personal space (`personalSpaceUserId`). Nothing here surfaces on the public global feed — these posts are visible only to you. Use for private notes-as-posts, scratch drafts, or any post you want to author against your vault without making it public.
141
140
 
142
- A couple of read-side flags don't mirror — `personal feed` has no `--following` (there's no follow graph in a private space), and `personal list-posts` has no `--mine` / `--vault-slug` (everything in the personal space is already yours).
141
+ A couple of read-side flags don't mirror — `personal feed` has no `--following` (there's no follow graph in a private space), and `personal list-posts` has no `--mine` (everything in the personal space is already yours).
143
142
 
144
143
  - `gobi personal feed` — Your personal-space feed (posts and replies, newest first).
145
144
  - `gobi personal list-posts` — List your personal-space posts.
146
145
  - `gobi personal get-post <postId>` — Get a personal-space post with its ancestors and replies.
147
- - `gobi personal create-post` — Create a private personal-space post. Same flags as `gobi global create-post` (`--vault-slug`, `--repost-post-id`, `--attach`).
146
+ - `gobi personal create-post` — Create a private personal-space post. Same flags as `gobi global create-post` (`--artifact`, `--repost-post-id`, `--attach`).
148
147
  - `gobi personal edit-post <postId>` — Edit a personal-space post you authored.
149
148
  - `gobi personal delete-post <postId>` — Delete a personal-space post you authored.
150
149
  - `gobi personal create-reply <postId>` — Reply to a personal-space post (inherits the parent's private scope).
@@ -153,7 +152,7 @@ A couple of read-side flags don't mirror — `personal feed` has no `--following
153
152
 
154
153
  ## Confirm before mutating
155
154
 
156
- Most posts and replies are publicly visible — in a community space (`gobi space …`) or in the global feed (`gobi global …`). `gobi personal …` is the exception: those rows are private to the author. Either way, before running any write, confirm with the user — show the exact command and the resolved title, content (or a short preview), and `authorVaultSlug` if `--vault-slug` is set. This applies even when running autonomously.
155
+ Most posts and replies are publicly visible — in a community space (`gobi space …`) or in the global feed (`gobi global …`). `gobi personal …` is the exception: those rows are private to the author. Either way, before running any write, confirm with the user — show the exact command and the resolved title, content (or a short preview), and any attached artifact ids. This applies even when running autonomously.
157
156
 
158
157
  - `create-post` / `create-reply` — content goes live on submission.
159
158
  - `edit-post` / `edit-reply` — confirm the *new* content; people who already saw the original may re-see it.
@@ -12,8 +12,7 @@ Commands:
12
12
  feed [options] List the unified feed (posts and replies, newest first) in the global public feed.
13
13
  list-posts [options] List posts in the global feed (paginated). Pass --mine to limit to your own posts.
14
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. --vault-slug attributes it to a vault you own. With no --vault-slug, the post is created without an authorVaultSlug (vault-less
16
- personal post).
15
+ create-post [options] Create a post in the global feed.
17
16
  edit-post [options] <postId> Edit a post you authored in the global feed.
18
17
  delete-post <postId> Delete a post you authored in the global feed.
19
18
  create-reply [options] <postId> Create a reply to a post in the global feed.
@@ -44,11 +43,10 @@ Usage: gobi global list-posts [options]
44
43
  List posts in the global feed (paginated). Pass --mine to limit to your own posts.
45
44
 
46
45
  Options:
47
- --limit <number> Items per page (default: "20")
48
- --cursor <string> Pagination cursor from previous response
49
- --mine Only include posts authored by you
50
- --vault-slug <vaultSlug> Filter by author vault slug
51
- -h, --help display help for command
46
+ --limit <number> Items per page (default: "20")
47
+ --cursor <string> Pagination cursor from previous response
48
+ --mine Only include posts authored by you
49
+ -h, --help display help for command
52
50
  ```
53
51
 
54
52
  ## get-post
@@ -70,13 +68,13 @@ Options:
70
68
  ```
71
69
  Usage: gobi global create-post [options]
72
70
 
73
- Create a post in the global feed. --vault-slug attributes it to a vault you own. With no --vault-slug, the post is created without an authorVaultSlug (vault-less personal post).
71
+ Create a post in the global feed.
74
72
 
75
73
  Options:
76
74
  --title <title> Title of the post
77
75
  --content <content> Post content (markdown supported, use "-" for stdin)
78
76
  --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
79
- --vault-slug <vaultSlug> Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.
77
+ --artifact <artifactId> Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`. (default: [])
80
78
  --attach <file> Local media file to attach. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default: [])
81
79
  --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
82
80
  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.
@@ -91,13 +89,12 @@ Usage: gobi global edit-post [options] <postId>
91
89
  Edit a post you authored in the global feed.
92
90
 
93
91
  Options:
94
- --title <title> New title
95
- --content <content> New content (markdown supported, use "-" for stdin)
96
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
97
- --vault-slug <vaultSlug> Attribute the post to this vault (sets authorVaultSlug).
98
- --attach <file> Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size
99
- ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged. (default: [])
100
- -h, --help display help for command
92
+ --title <title> New title
93
+ --content <content> New content (markdown supported, use "-" for stdin)
94
+ --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
95
+ --attach <file> Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size
96
+ ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged. (default: [])
97
+ -h, --help display help for command
101
98
  ```
102
99
 
103
100
  ## delete-post
@@ -119,12 +116,11 @@ Usage: gobi global create-reply [options] <postId>
119
116
  Create a reply to a post in the global feed.
120
117
 
121
118
  Options:
122
- --content <content> Reply content (markdown supported, use "-" for stdin)
123
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
124
- --vault-slug <vaultSlug> Attribute the reply to this vault (sets authorVaultSlug).
125
- --attach <file> Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default:
126
- [])
127
- -h, --help display help for command
119
+ --content <content> Reply content (markdown supported, use "-" for stdin)
120
+ --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
121
+ --attach <file> Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default:
122
+ [])
123
+ -h, --help display help for command
128
124
  ```
129
125
 
130
126
  ## edit-reply
@@ -135,10 +131,9 @@ Usage: gobi global edit-reply [options] <replyId>
135
131
  Edit a reply you authored in the global feed.
136
132
 
137
133
  Options:
138
- --content <content> New reply content (markdown supported, use "-" for stdin)
139
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
140
- --vault-slug <vaultSlug> Attribute the reply to this vault (sets authorVaultSlug).
141
- -h, --help display help for command
134
+ --content <content> New reply content (markdown supported, use "-" for stdin)
135
+ --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
136
+ -h, --help display help for command
142
137
  ```
143
138
 
144
139
  ## delete-reply
@@ -13,7 +13,7 @@ Commands:
13
13
  feed [options] List your personal-space feed (posts and replies, newest first). Only you can see these rows.
14
14
  list-posts [options] List root posts (no replies) in your personal space. Filters the personal feed client-side; pagination cursor advances through the underlying feed page.
15
15
  get-post [options] <postId> Get a personal-space post with its ancestors and replies (paginated). Same endpoint as `gobi global get-post`; only the owner can resolve a private id.
16
- create-post [options] Create a private post in your personal space. --vault-slug attributes it to a vault you own. Visible only to you.
16
+ create-post [options] Create a private post in your personal space. Visible only to you.
17
17
  edit-post [options] <postId> Edit a post you authored in your personal space.
18
18
  delete-post <postId> Delete a post you authored in your personal space.
19
19
  create-reply [options] <postId> Reply to a personal-space post. The reply inherits the parent's private scope automatically.
@@ -67,13 +67,13 @@ Options:
67
67
  ```
68
68
  Usage: gobi personal create-post [options]
69
69
 
70
- Create a private post in your personal space. --vault-slug attributes it to a vault you own. Visible only to you.
70
+ Create a private post in your personal space. Visible only to you.
71
71
 
72
72
  Options:
73
73
  --title <title> Title of the post
74
74
  --content <content> Post content (markdown supported, use "-" for stdin)
75
75
  --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
76
- --vault-slug <vaultSlug> Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.
76
+ --artifact <artifactId> Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`. (default: [])
77
77
  --attach <file> Local media file to attach. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default: [])
78
78
  --repost-post-id <postId> Wrap an existing top-level post as the embedded card on this new private post. The referenced post must be visible to you (your own personal-space post, a global-feed
79
79
  post, or a post in a space you're a member of). Reposting someone else's personal-space post returns 404.
@@ -88,13 +88,12 @@ Usage: gobi personal edit-post [options] <postId>
88
88
  Edit a post you authored in your personal space.
89
89
 
90
90
  Options:
91
- --title <title> New title
92
- --content <content> New content (markdown supported, use "-" for stdin)
93
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
94
- --vault-slug <vaultSlug> Attribute the post to this vault (sets authorVaultSlug).
95
- --attach <file> Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size
96
- ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged. (default: [])
97
- -h, --help display help for command
91
+ --title <title> New title
92
+ --content <content> New content (markdown supported, use "-" for stdin)
93
+ --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
94
+ --attach <file> Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size
95
+ ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged. (default: [])
96
+ -h, --help display help for command
98
97
  ```
99
98
 
100
99
  ## delete-post
@@ -116,12 +115,11 @@ Usage: gobi personal create-reply [options] <postId>
116
115
  Reply to a personal-space post. The reply inherits the parent's private scope automatically.
117
116
 
118
117
  Options:
119
- --content <content> Reply content (markdown supported, use "-" for stdin)
120
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
121
- --vault-slug <vaultSlug> Attribute the reply to this vault (sets authorVaultSlug).
122
- --attach <file> Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default:
123
- [])
124
- -h, --help display help for command
118
+ --content <content> Reply content (markdown supported, use "-" for stdin)
119
+ --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
120
+ --attach <file> Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default:
121
+ [])
122
+ -h, --help display help for command
125
123
  ```
126
124
 
127
125
  ## edit-reply
@@ -132,10 +130,9 @@ Usage: gobi personal edit-reply [options] <replyId>
132
130
  Edit a reply you authored in your personal space.
133
131
 
134
132
  Options:
135
- --content <content> New reply content (markdown supported, use "-" for stdin)
136
- --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
137
- --vault-slug <vaultSlug> Attribute the reply to this vault (sets authorVaultSlug).
138
- -h, --help display help for command
133
+ --content <content> New reply content (markdown supported, use "-" for stdin)
134
+ --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
135
+ -h, --help display help for command
139
136
  ```
140
137
 
141
138
  ## delete-reply
@@ -118,7 +118,7 @@ Options:
118
118
  --title <title> Title of the post
119
119
  --content <content> Post content (markdown supported, use "-" for stdin)
120
120
  --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
121
- --vault-slug <vaultSlug> Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.
121
+ --artifact <artifactId> Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`. (default: [])
122
122
  --space-slug <spaceSlug> Space slug (overrides .gobi/settings.yaml)
123
123
  --attach <file> Local media file to attach. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default: [])
124
124
  --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
@@ -137,7 +137,6 @@ Options:
137
137
  --title <title> New title for the post
138
138
  --content <content> New content for the post (markdown supported, use "-" for stdin)
139
139
  --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
140
- --vault-slug <vaultSlug> Attribute the post to this vault (sets authorVaultSlug). Caller must own the vault.
141
140
  --space-slug <spaceSlug> Space slug (overrides .gobi/settings.yaml)
142
141
  --attach <file> Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size
143
142
  ceilings: 5MB photos / 15MB GIFs / 512MB video. Omit to leave attachments unchanged. (default: [])
@@ -166,7 +165,6 @@ Create a reply to a post in a space.
166
165
  Options:
167
166
  --content <content> Reply content (markdown supported, use "-" for stdin)
168
167
  --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
169
- --vault-slug <vaultSlug> Attribute the reply to this vault (sets authorVaultSlug). Caller must own the vault.
170
168
  --space-slug <spaceSlug> Space slug (overrides .gobi/settings.yaml)
171
169
  --attach <file> Local media file to attach to this reply. Repeatable. X-style mix rule: up to 4 photos OR 1 GIF OR 1 video. Size ceilings: 5MB photos / 15MB GIFs / 512MB video. (default:
172
170
  [])
@@ -183,7 +181,6 @@ Edit a reply you authored in a space.
183
181
  Options:
184
182
  --content <content> New content for the reply (markdown supported, use "-" for stdin)
185
183
  --rich-text <richText> Rich-text JSON array (mutually exclusive with --content)
186
- --vault-slug <vaultSlug> Attribute the reply to this vault (sets authorVaultSlug). Caller must own the vault.
187
184
  --space-slug <spaceSlug> Space slug (overrides .gobi/settings.yaml)
188
185
  -h, --help display help for command
189
186
  ```
@@ -8,12 +8,12 @@ description: >-
8
8
  allowed-tools: Bash(gobi:*)
9
9
  metadata:
10
10
  author: gobi-ai
11
- version: "2.0.25"
11
+ version: "2.0.27"
12
12
  ---
13
13
 
14
14
  # gobi-vault
15
15
 
16
- Gobi vault commands for publishing your vault profile and syncing files (v2.0.25).
16
+ Gobi vault commands for publishing your vault profile and syncing files (v2.0.27).
17
17
 
18
18
  Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
19
19
 
@@ -56,11 +56,10 @@ gobi --json vault publish
56
56
  Once a vault is published (i.e. `gobi vault status` reports `isPublished: yes`), it is reachable at predictable URLs:
57
57
 
58
58
  - **Vault profile** — `https://gobispace.com/@{vaultSlug}` (e.g. `https://gobispace.com/@jyk`).
59
- - **Direct link to a personal post on the vault** — `https://gobispace.com/@{vaultSlug}?postId={postId}` (e.g. `https://gobispace.com/@jyk?postId=144869`). Open in the vault profile with that post focused.
60
59
  - **Direct link to a vault file** — `https://gobispace.com/file/{vaultSlug}?path={path}` (e.g. `https://gobispace.com/file/jyk?path=notes/intro.md`). This is the first-class URL for sharing a single file from a vault — use it whenever you want a reader to land on one specific file. The page renders inside the main feed chrome (sidebar + header), so readers stay in `gobispace.com` instead of pivoting to the vault homepage. Paths without an extension are treated as markdown (the same wikilink-stem resolution webdrive uses), so `?path=intro` and `?path=intro.md` both resolve. URL-encode each path segment when assembling.
61
60
  - **Custom homepage** — when `homepage` is set in `PUBLISH.md` frontmatter, the vault profile URL renders that HTML file. See **gobi-homepage** skill.
62
61
 
63
- When linking back to your own posts or files, assemble the URL from concrete fields (the post's `authorVaultSlug` + `id`, or the vault's `vaultSlug` + the file's path) rather than guessing.
62
+ When linking to a vault file, assemble the URL from concrete fields (the vault's `vaultSlug` + the file's path) rather than guessing.
64
63
 
65
64
  ## Confirm before mutating
66
65