@gobi-ai/cli 2.0.4 → 2.0.6
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.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +56 -42
- package/commands/space-share.md +1 -1
- package/dist/commands/auth.js +38 -9
- package/dist/commands/draft.js +9 -13
- package/dist/commands/global.js +90 -24
- package/dist/commands/init.js +36 -35
- package/dist/commands/media.js +39 -39
- package/dist/commands/saved.js +31 -41
- package/dist/commands/sense.js +4 -4
- package/dist/commands/sessions.js +6 -6
- package/dist/commands/space.js +188 -68
- package/dist/commands/utils.js +7 -2
- package/dist/commands/vault.js +40 -4
- package/dist/main.js +37 -13
- package/package.json +1 -1
- package/skills/gobi-core/SKILL.md +38 -42
- package/skills/gobi-core/references/session.md +10 -10
- package/skills/gobi-core/references/space.md +6 -6
- package/skills/gobi-draft/SKILL.md +4 -4
- package/skills/gobi-draft/references/draft.md +9 -8
- package/skills/gobi-media/SKILL.md +37 -38
- package/skills/gobi-media/references/media.md +59 -59
- package/skills/gobi-saved/SKILL.md +27 -26
- package/skills/gobi-saved/references/saved.md +108 -26
- package/skills/gobi-sense/SKILL.md +8 -8
- package/skills/gobi-sense/references/sense.md +10 -10
- package/skills/gobi-space/SKILL.md +28 -10
- package/skills/gobi-space/references/global.md +21 -15
- package/skills/gobi-space/references/space.md +54 -35
- package/skills/gobi-vault/SKILL.md +12 -5
- package/skills/gobi-vault/references/vault.md +25 -1
- package/skills/gobi-core/references/init.md +0 -10
package/dist/commands/space.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
|
|
2
|
-
import { selectSpace, writeSpaceSetting } from "./init.js";
|
|
2
|
+
import { requireSpace, selectSpace, setSpaceRequirement, writeSpaceSetting, } from "./init.js";
|
|
3
3
|
import { isJsonMode, jsonOut, readStdin, resolveSpaceSlug, resolveVaultSlug, unwrapResp, } from "./utils.js";
|
|
4
4
|
import { extractWikiLinks, uploadAttachments } from "../attachments.js";
|
|
5
5
|
import { getValidToken } from "../auth/manager.js";
|
|
@@ -29,9 +29,12 @@ export function registerSpaceCommand(program) {
|
|
|
29
29
|
const space = program
|
|
30
30
|
.command("space")
|
|
31
31
|
.description("Space commands (posts, replies). Space and member admin is web-UI only.")
|
|
32
|
-
.option("--space-slug <
|
|
32
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)");
|
|
33
|
+
// Default: every space subcommand needs a configured space (or --space-slug).
|
|
34
|
+
// `list`, `warp`, and `get` opt out below.
|
|
35
|
+
requireSpace(space);
|
|
33
36
|
// ── List spaces ──
|
|
34
|
-
space
|
|
37
|
+
const listCmd = space
|
|
35
38
|
.command("list")
|
|
36
39
|
.description("List spaces you are a member of.")
|
|
37
40
|
.action(async () => {
|
|
@@ -47,17 +50,22 @@ export function registerSpaceCommand(program) {
|
|
|
47
50
|
}
|
|
48
51
|
const lines = [];
|
|
49
52
|
for (const s of items) {
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
lines.push(`- [${s.slug}] ${s.name}`);
|
|
54
|
+
if (s.description)
|
|
55
|
+
lines.push(` Description: ${s.description}`);
|
|
56
|
+
if (s.rules)
|
|
57
|
+
lines.push(` Rules: ${s.rules}`);
|
|
52
58
|
}
|
|
53
59
|
console.log(`Spaces (${items.length}):\n` + lines.join("\n"));
|
|
54
60
|
});
|
|
61
|
+
setSpaceRequirement(listCmd, false);
|
|
55
62
|
// ── Get space ──
|
|
56
|
-
space
|
|
63
|
+
const getCmd = space
|
|
57
64
|
.command("get [spaceSlug]")
|
|
58
65
|
.description("Get details for a space. Pass a slug or omit to use the current space (from .gobi/settings.yaml or --space-slug).")
|
|
59
|
-
.
|
|
60
|
-
|
|
66
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
67
|
+
.action(async (spaceSlug, opts) => {
|
|
68
|
+
const slug = spaceSlug || resolveSpaceSlug(space, opts);
|
|
61
69
|
const resp = (await apiGet(`/spaces/${slug}`));
|
|
62
70
|
const s = unwrapResp(resp);
|
|
63
71
|
if (isJsonMode(space)) {
|
|
@@ -69,15 +77,18 @@ export function registerSpaceCommand(program) {
|
|
|
69
77
|
` ID: ${s.id}\n` +
|
|
70
78
|
` Created: ${s.createdAt}`);
|
|
71
79
|
});
|
|
80
|
+
// `get [spaceSlug]` accepts a positional arg; treat as not requiring space
|
|
81
|
+
// config so the no-arg case falls through to the action's own error.
|
|
82
|
+
setSpaceRequirement(getCmd, false);
|
|
72
83
|
// ── Warp (space selection) ──
|
|
73
|
-
space
|
|
84
|
+
const warpCmd = space
|
|
74
85
|
.command("warp [spaceSlug]")
|
|
75
86
|
.description("Select the active space. Pass a slug to warp directly, or omit for interactive selection.")
|
|
76
87
|
.action(async (spaceSlug) => {
|
|
77
88
|
if (spaceSlug) {
|
|
78
89
|
writeSpaceSetting(spaceSlug);
|
|
79
90
|
if (isJsonMode(space)) {
|
|
80
|
-
jsonOut({ spaceSlug });
|
|
91
|
+
jsonOut({ spaceSlug, spaceName: null });
|
|
81
92
|
return;
|
|
82
93
|
}
|
|
83
94
|
console.log(`Warped to space "${spaceSlug}"`);
|
|
@@ -95,13 +106,15 @@ export function registerSpaceCommand(program) {
|
|
|
95
106
|
}
|
|
96
107
|
console.log(`Warped to space "${result.name}" (${result.slug})`);
|
|
97
108
|
});
|
|
109
|
+
setSpaceRequirement(warpCmd, false);
|
|
98
110
|
// ── Topics ──
|
|
99
111
|
space
|
|
100
112
|
.command("list-topics")
|
|
101
113
|
.description("List topics in a space, ordered by most recent content linkage.")
|
|
102
|
-
.option("--limit <number>", "
|
|
114
|
+
.option("--limit <number>", "Items per page", "20")
|
|
115
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
103
116
|
.action(async (opts) => {
|
|
104
|
-
const spaceSlug = resolveSpaceSlug(space);
|
|
117
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
105
118
|
const params = {
|
|
106
119
|
limit: parseInt(opts.limit, 10),
|
|
107
120
|
};
|
|
@@ -127,8 +140,9 @@ export function registerSpaceCommand(program) {
|
|
|
127
140
|
.description("List posts tagged with a topic in a space (cursor-paginated).")
|
|
128
141
|
.option("--limit <number>", "Items per page", "20")
|
|
129
142
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
143
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
130
144
|
.action(async (topicSlug, opts) => {
|
|
131
|
-
const spaceSlug = resolveSpaceSlug(space);
|
|
145
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
132
146
|
const params = {
|
|
133
147
|
limit: parseInt(opts.limit, 10),
|
|
134
148
|
};
|
|
@@ -165,8 +179,9 @@ export function registerSpaceCommand(program) {
|
|
|
165
179
|
.description("List the unified feed (posts and replies, newest first) in a space.")
|
|
166
180
|
.option("--limit <number>", "Items per page", "20")
|
|
167
181
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
182
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
168
183
|
.action(async (opts) => {
|
|
169
|
-
const spaceSlug = resolveSpaceSlug(space);
|
|
184
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
170
185
|
const params = {
|
|
171
186
|
limit: parseInt(opts.limit, 10),
|
|
172
187
|
};
|
|
@@ -194,10 +209,12 @@ export function registerSpaceCommand(program) {
|
|
|
194
209
|
space
|
|
195
210
|
.command("get-post <postId>")
|
|
196
211
|
.description("Get a post with its ancestors and replies (paginated).")
|
|
197
|
-
.option("--limit <number>", "
|
|
212
|
+
.option("--limit <number>", "Items per page", "20")
|
|
198
213
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
214
|
+
.option("--full", "Show full reply content without truncation")
|
|
215
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
199
216
|
.action(async (postId, opts) => {
|
|
200
|
-
const spaceSlug = resolveSpaceSlug(space);
|
|
217
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
201
218
|
const params = {
|
|
202
219
|
limit: parseInt(opts.limit, 10),
|
|
203
220
|
};
|
|
@@ -231,8 +248,10 @@ export function registerSpaceCommand(program) {
|
|
|
231
248
|
const rAuthor = r.author?.name ||
|
|
232
249
|
`User ${r.authorId}`;
|
|
233
250
|
const text = r.content;
|
|
234
|
-
const
|
|
235
|
-
|
|
251
|
+
const body = opts.full || !text || text.length <= 200
|
|
252
|
+
? text
|
|
253
|
+
: text.slice(0, 200) + "…";
|
|
254
|
+
replyLines.push(` - ${rAuthor}: ${body} (${r.createdAt})`);
|
|
236
255
|
}
|
|
237
256
|
const isReplyPost = post.parentPostId != null;
|
|
238
257
|
const heading = isReplyPost
|
|
@@ -258,8 +277,9 @@ export function registerSpaceCommand(program) {
|
|
|
258
277
|
.description("List posts in a space (paginated).")
|
|
259
278
|
.option("--limit <number>", "Items per page", "20")
|
|
260
279
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
280
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
261
281
|
.action(async (opts) => {
|
|
262
|
-
const spaceSlug = resolveSpaceSlug(space);
|
|
282
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
263
283
|
const params = {
|
|
264
284
|
limit: parseInt(opts.limit, 10),
|
|
265
285
|
};
|
|
@@ -292,28 +312,48 @@ export function registerSpaceCommand(program) {
|
|
|
292
312
|
space
|
|
293
313
|
.command("create-post")
|
|
294
314
|
.description("Create a post in a space.")
|
|
295
|
-
.
|
|
296
|
-
.
|
|
315
|
+
.option("--title <title>", "Title of the post")
|
|
316
|
+
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
317
|
+
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
297
318
|
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting (also attributes the post to that vault)")
|
|
298
319
|
.option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultId). Also used as upload destination for --auto-attachments.")
|
|
320
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
299
321
|
.action(async (opts) => {
|
|
300
|
-
|
|
322
|
+
if (!opts.content && !opts.richText) {
|
|
323
|
+
throw new Error("Provide either --content or --rich-text.");
|
|
324
|
+
}
|
|
325
|
+
if (opts.content && opts.richText) {
|
|
326
|
+
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
327
|
+
}
|
|
301
328
|
let authorVaultSlug;
|
|
302
329
|
if (opts.vaultSlug || opts.autoAttachments) {
|
|
303
330
|
authorVaultSlug = resolveVaultSlug(opts);
|
|
304
331
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
332
|
+
const body = {};
|
|
333
|
+
if (opts.title != null)
|
|
334
|
+
body.title = opts.title;
|
|
335
|
+
if (opts.content != null) {
|
|
336
|
+
const content = readContent(opts.content);
|
|
337
|
+
if (opts.autoAttachments) {
|
|
338
|
+
const token = await getValidToken();
|
|
339
|
+
const links = extractWikiLinks(content);
|
|
340
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
341
|
+
}
|
|
342
|
+
body.content = content;
|
|
343
|
+
}
|
|
344
|
+
if (opts.richText != null) {
|
|
345
|
+
let parsed;
|
|
346
|
+
try {
|
|
347
|
+
parsed = JSON.parse(opts.richText);
|
|
348
|
+
}
|
|
349
|
+
catch {
|
|
350
|
+
throw new Error("Invalid --rich-text JSON.");
|
|
351
|
+
}
|
|
352
|
+
body.richText = parsed;
|
|
309
353
|
}
|
|
310
|
-
const spaceSlug = resolveSpaceSlug(space);
|
|
311
|
-
const body = {
|
|
312
|
-
title: opts.title,
|
|
313
|
-
content,
|
|
314
|
-
};
|
|
315
354
|
if (authorVaultSlug)
|
|
316
355
|
body.authorVaultSlug = authorVaultSlug;
|
|
356
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
317
357
|
const resp = (await apiPost(`/spaces/${spaceSlug}/posts`, body));
|
|
318
358
|
const post = unwrapResp(resp);
|
|
319
359
|
if (isJsonMode(space)) {
|
|
@@ -322,28 +362,32 @@ export function registerSpaceCommand(program) {
|
|
|
322
362
|
}
|
|
323
363
|
console.log(`Post created!\n` +
|
|
324
364
|
` ID: ${post.id}\n` +
|
|
325
|
-
` Title: ${post.title}\n` +
|
|
365
|
+
(post.title ? ` Title: ${post.title}\n` : "") +
|
|
326
366
|
` Created: ${post.createdAt}`);
|
|
327
367
|
});
|
|
328
368
|
space
|
|
329
369
|
.command("edit-post <postId>")
|
|
330
|
-
.description("Edit a post
|
|
370
|
+
.description("Edit a post you authored in a space.")
|
|
331
371
|
.option("--title <title>", "New title for the post")
|
|
332
|
-
.option("--content <content>", "New content for the post (markdown supported)")
|
|
372
|
+
.option("--content <content>", "New content for the post (markdown supported, use \"-\" for stdin)")
|
|
373
|
+
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
333
374
|
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before editing (also attributes the post to that vault)")
|
|
334
|
-
.option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultId). Also used as upload destination for --auto-attachments.
|
|
375
|
+
.option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultId). Also used as upload destination for --auto-attachments.")
|
|
376
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
335
377
|
.action(async (postId, opts) => {
|
|
336
|
-
const wantsVaultChange = opts.vaultSlug
|
|
337
|
-
if (
|
|
338
|
-
|
|
378
|
+
const wantsVaultChange = !!(opts.vaultSlug || opts.autoAttachments);
|
|
379
|
+
if (opts.title == null &&
|
|
380
|
+
opts.content == null &&
|
|
381
|
+
opts.richText == null &&
|
|
382
|
+
!wantsVaultChange) {
|
|
383
|
+
throw new Error("Provide at least --title, --content, --rich-text, or --vault-slug to update.");
|
|
339
384
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
if (opts.vaultSlug !== undefined) {
|
|
343
|
-
// Empty string detaches; non-empty resolves through settings fallback.
|
|
344
|
-
authorVaultSlug = opts.vaultSlug === "" ? "" : resolveVaultSlug(opts);
|
|
385
|
+
if (opts.content && opts.richText) {
|
|
386
|
+
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
345
387
|
}
|
|
346
|
-
|
|
388
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
389
|
+
let authorVaultSlug;
|
|
390
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
347
391
|
authorVaultSlug = resolveVaultSlug(opts);
|
|
348
392
|
}
|
|
349
393
|
const body = {};
|
|
@@ -358,6 +402,16 @@ export function registerSpaceCommand(program) {
|
|
|
358
402
|
}
|
|
359
403
|
body.content = content;
|
|
360
404
|
}
|
|
405
|
+
if (opts.richText != null) {
|
|
406
|
+
let parsed;
|
|
407
|
+
try {
|
|
408
|
+
parsed = JSON.parse(opts.richText);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
throw new Error("Invalid --rich-text JSON.");
|
|
412
|
+
}
|
|
413
|
+
body.richText = parsed;
|
|
414
|
+
}
|
|
361
415
|
if (authorVaultSlug !== undefined)
|
|
362
416
|
body.authorVaultSlug = authorVaultSlug;
|
|
363
417
|
const resp = (await apiPatch(`/spaces/${spaceSlug}/posts/${postId}`, body));
|
|
@@ -368,14 +422,15 @@ export function registerSpaceCommand(program) {
|
|
|
368
422
|
}
|
|
369
423
|
console.log(`Post edited!\n` +
|
|
370
424
|
` ID: ${post.id}\n` +
|
|
371
|
-
` Title: ${post.title}\n` +
|
|
425
|
+
(post.title ? ` Title: ${post.title}\n` : "") +
|
|
372
426
|
` Edited: ${post.editedAt}`);
|
|
373
427
|
});
|
|
374
428
|
space
|
|
375
429
|
.command("delete-post <postId>")
|
|
376
|
-
.description("Delete a post
|
|
377
|
-
.
|
|
378
|
-
|
|
430
|
+
.description("Delete a post you authored in a space.")
|
|
431
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
432
|
+
.action(async (postId, opts) => {
|
|
433
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
379
434
|
await apiDelete(`/spaces/${spaceSlug}/posts/${postId}`);
|
|
380
435
|
if (isJsonMode(space)) {
|
|
381
436
|
jsonOut({ id: postId });
|
|
@@ -387,10 +442,46 @@ export function registerSpaceCommand(program) {
|
|
|
387
442
|
space
|
|
388
443
|
.command("create-reply <postId>")
|
|
389
444
|
.description("Create a reply to a post in a space.")
|
|
390
|
-
.
|
|
445
|
+
.option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
|
|
446
|
+
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
447
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting (also attributes the reply to that vault)")
|
|
448
|
+
.option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug). Also used as upload destination for --auto-attachments.")
|
|
449
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
391
450
|
.action(async (postId, opts) => {
|
|
392
|
-
|
|
393
|
-
|
|
451
|
+
if (!opts.content && !opts.richText) {
|
|
452
|
+
throw new Error("Provide either --content or --rich-text.");
|
|
453
|
+
}
|
|
454
|
+
if (opts.content && opts.richText) {
|
|
455
|
+
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
456
|
+
}
|
|
457
|
+
let authorVaultSlug;
|
|
458
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
459
|
+
authorVaultSlug = resolveVaultSlug(opts);
|
|
460
|
+
}
|
|
461
|
+
const body = {};
|
|
462
|
+
if (opts.content != null) {
|
|
463
|
+
const content = readContent(opts.content);
|
|
464
|
+
if (opts.autoAttachments) {
|
|
465
|
+
const token = await getValidToken();
|
|
466
|
+
const links = extractWikiLinks(content);
|
|
467
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
468
|
+
}
|
|
469
|
+
body.content = content;
|
|
470
|
+
}
|
|
471
|
+
if (opts.richText != null) {
|
|
472
|
+
let parsed;
|
|
473
|
+
try {
|
|
474
|
+
parsed = JSON.parse(opts.richText);
|
|
475
|
+
}
|
|
476
|
+
catch {
|
|
477
|
+
throw new Error("Invalid --rich-text JSON.");
|
|
478
|
+
}
|
|
479
|
+
body.richText = parsed;
|
|
480
|
+
}
|
|
481
|
+
if (authorVaultSlug)
|
|
482
|
+
body.authorVaultSlug = authorVaultSlug;
|
|
483
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
484
|
+
const resp = (await apiPost(`/spaces/${spaceSlug}/posts/${postId}/replies`, body));
|
|
394
485
|
const msg = unwrapResp(resp);
|
|
395
486
|
const mentions = (resp.mentions || {});
|
|
396
487
|
if (isJsonMode(space)) {
|
|
@@ -401,20 +492,48 @@ export function registerSpaceCommand(program) {
|
|
|
401
492
|
});
|
|
402
493
|
space
|
|
403
494
|
.command("edit-reply <replyId>")
|
|
404
|
-
.description("Edit a reply
|
|
405
|
-
.
|
|
406
|
-
.option("--
|
|
407
|
-
.option("--
|
|
495
|
+
.description("Edit a reply you authored in a space.")
|
|
496
|
+
.option("--content <content>", "New content for the reply (markdown supported, use \"-\" for stdin)")
|
|
497
|
+
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
498
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before editing (also attributes the reply to that vault)")
|
|
499
|
+
.option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug). Also used as upload destination for --auto-attachments.")
|
|
500
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
408
501
|
.action(async (replyId, opts) => {
|
|
409
|
-
const
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
502
|
+
const wantsVaultChange = !!(opts.vaultSlug || opts.autoAttachments);
|
|
503
|
+
if (opts.content == null && opts.richText == null && !wantsVaultChange) {
|
|
504
|
+
throw new Error("Provide at least --content, --rich-text, or --vault-slug to update.");
|
|
505
|
+
}
|
|
506
|
+
if (opts.content && opts.richText) {
|
|
507
|
+
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
508
|
+
}
|
|
509
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
510
|
+
let authorVaultSlug;
|
|
511
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
512
|
+
authorVaultSlug = resolveVaultSlug(opts);
|
|
513
|
+
}
|
|
514
|
+
const body = {};
|
|
515
|
+
if (opts.content != null) {
|
|
516
|
+
const content = readContent(opts.content);
|
|
517
|
+
if (opts.autoAttachments) {
|
|
518
|
+
const token = await getValidToken();
|
|
519
|
+
const links = extractWikiLinks(content);
|
|
520
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
521
|
+
}
|
|
522
|
+
body.content = content;
|
|
523
|
+
}
|
|
524
|
+
if (opts.richText != null) {
|
|
525
|
+
let parsed;
|
|
526
|
+
try {
|
|
527
|
+
parsed = JSON.parse(opts.richText);
|
|
528
|
+
}
|
|
529
|
+
catch {
|
|
530
|
+
throw new Error("Invalid --rich-text JSON.");
|
|
531
|
+
}
|
|
532
|
+
body.richText = parsed;
|
|
533
|
+
}
|
|
534
|
+
if (authorVaultSlug !== undefined)
|
|
535
|
+
body.authorVaultSlug = authorVaultSlug;
|
|
536
|
+
const resp = (await apiPatch(`/spaces/${spaceSlug}/replies/${replyId}`, body));
|
|
418
537
|
const msg = unwrapResp(resp);
|
|
419
538
|
if (isJsonMode(space)) {
|
|
420
539
|
jsonOut(msg);
|
|
@@ -424,12 +543,13 @@ export function registerSpaceCommand(program) {
|
|
|
424
543
|
});
|
|
425
544
|
space
|
|
426
545
|
.command("delete-reply <replyId>")
|
|
427
|
-
.description("Delete a reply
|
|
428
|
-
.
|
|
429
|
-
|
|
546
|
+
.description("Delete a reply you authored in a space.")
|
|
547
|
+
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
548
|
+
.action(async (replyId, opts) => {
|
|
549
|
+
const spaceSlug = resolveSpaceSlug(space, opts);
|
|
430
550
|
await apiDelete(`/spaces/${spaceSlug}/replies/${replyId}`);
|
|
431
551
|
if (isJsonMode(space)) {
|
|
432
|
-
jsonOut({ replyId });
|
|
552
|
+
jsonOut({ id: replyId });
|
|
433
553
|
return;
|
|
434
554
|
}
|
|
435
555
|
console.log(`Reply ${replyId} deleted.`);
|
package/dist/commands/utils.js
CHANGED
|
@@ -11,8 +11,13 @@ export function isJsonMode(cmd) {
|
|
|
11
11
|
export function jsonOut(data) {
|
|
12
12
|
console.log(JSON.stringify({ success: true, data }));
|
|
13
13
|
}
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Resolves the space slug from (in order): the leaf subcommand's --space-slug
|
|
15
|
+
// option, the parent `gobi space` command's --space-slug option, then
|
|
16
|
+
// `.gobi/settings.yaml`. Either side of the subcommand works:
|
|
17
|
+
// gobi space --space-slug foo list-posts (parent-level)
|
|
18
|
+
// gobi space list-posts --space-slug foo (leaf-level)
|
|
19
|
+
export function resolveSpaceSlug(parent, leafOpts) {
|
|
20
|
+
return leafOpts?.spaceSlug || parent.opts().spaceSlug || getSpaceSlug();
|
|
16
21
|
}
|
|
17
22
|
export function resolveVaultSlug(opts) {
|
|
18
23
|
return opts.vaultSlug || getVaultSlug();
|
package/dist/commands/vault.js
CHANGED
|
@@ -3,15 +3,48 @@ import { join, resolve as pathResolve } from "path";
|
|
|
3
3
|
import { WEBDRIVE_BASE_URL } from "../constants.js";
|
|
4
4
|
import { getValidToken } from "../auth/manager.js";
|
|
5
5
|
import { GobiError } from "../errors.js";
|
|
6
|
-
import {
|
|
6
|
+
import { apiGet } from "../client.js";
|
|
7
|
+
import { getVaultSlug, requireVault, runVaultInitFlow } from "./init.js";
|
|
7
8
|
import { isJsonMode, jsonOut } from "./utils.js";
|
|
8
9
|
import { runSync } from "./sync.js";
|
|
9
10
|
export const PUBLISH_FILENAME = "PUBLISH.md";
|
|
10
11
|
export function registerVaultCommand(program) {
|
|
11
12
|
const vault = program
|
|
12
13
|
.command("vault")
|
|
13
|
-
.description("Vault commands (publish/unpublish profile, sync files).");
|
|
14
|
+
.description("Vault commands (init, list, publish/unpublish profile, sync files).");
|
|
14
15
|
vault
|
|
16
|
+
.command("init")
|
|
17
|
+
.description("Select or create the vault for the current directory. Writes .gobi/settings.yaml and seeds PUBLISH.md.")
|
|
18
|
+
.action(async () => {
|
|
19
|
+
await runVaultInitFlow();
|
|
20
|
+
});
|
|
21
|
+
vault
|
|
22
|
+
.command("list")
|
|
23
|
+
.description("List vaults you own.")
|
|
24
|
+
.action(async () => {
|
|
25
|
+
const resp = (await apiGet("/vaults"));
|
|
26
|
+
const items = (Array.isArray(resp)
|
|
27
|
+
? resp
|
|
28
|
+
: Array.isArray(resp?.data)
|
|
29
|
+
? resp.data
|
|
30
|
+
: []);
|
|
31
|
+
if (isJsonMode(vault)) {
|
|
32
|
+
jsonOut(items);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!items.length) {
|
|
36
|
+
console.log("No vaults found.");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const lines = [];
|
|
40
|
+
for (const v of items) {
|
|
41
|
+
const slug = (v.vaultId || v.slug);
|
|
42
|
+
const isPrimary = v.isPrimary ? " (primary)" : "";
|
|
43
|
+
lines.push(`- [${slug}] ${v.name}${isPrimary}`);
|
|
44
|
+
}
|
|
45
|
+
console.log(`Vaults (${items.length}):\n` + lines.join("\n"));
|
|
46
|
+
});
|
|
47
|
+
const publishCmd = vault
|
|
15
48
|
.command("publish")
|
|
16
49
|
.description(`Upload ${PUBLISH_FILENAME} to the vault root on webdrive. Triggers post-processing (vault sync, metadata update, Discord notification).`)
|
|
17
50
|
.action(async () => {
|
|
@@ -40,7 +73,8 @@ export function registerVaultCommand(program) {
|
|
|
40
73
|
}
|
|
41
74
|
console.log(`Published ${PUBLISH_FILENAME} to vault "${vaultId}"`);
|
|
42
75
|
});
|
|
43
|
-
|
|
76
|
+
requireVault(publishCmd);
|
|
77
|
+
const unpublishCmd = vault
|
|
44
78
|
.command("unpublish")
|
|
45
79
|
.description(`Delete ${PUBLISH_FILENAME} from the vault on webdrive.`)
|
|
46
80
|
.action(async () => {
|
|
@@ -60,7 +94,8 @@ export function registerVaultCommand(program) {
|
|
|
60
94
|
}
|
|
61
95
|
console.log(`Deleted ${PUBLISH_FILENAME} from vault "${vaultId}"`);
|
|
62
96
|
});
|
|
63
|
-
|
|
97
|
+
requireVault(unpublishCmd);
|
|
98
|
+
const syncCmd = vault
|
|
64
99
|
.command("sync")
|
|
65
100
|
.description("Sync local vault files with Gobi Webdrive.")
|
|
66
101
|
.option("--upload-only", "Only upload local changes to server")
|
|
@@ -110,4 +145,5 @@ export function registerVaultCommand(program) {
|
|
|
110
145
|
jsonMode: isJsonMode(this),
|
|
111
146
|
});
|
|
112
147
|
});
|
|
148
|
+
requireVault(syncCmd);
|
|
113
149
|
}
|
package/dist/main.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from "commander";
|
|
|
3
3
|
import { initCredentials } from "./auth/manager.js";
|
|
4
4
|
import { ApiError, GobiError } from "./errors.js";
|
|
5
5
|
import { registerAuthCommand } from "./commands/auth.js";
|
|
6
|
-
import {
|
|
6
|
+
import { commandRequiresSpace, commandRequiresVault, readSettings, } from "./commands/init.js";
|
|
7
7
|
import { registerSpaceCommand } from "./commands/space.js";
|
|
8
8
|
import { registerGlobalCommand } from "./commands/global.js";
|
|
9
9
|
import { registerVaultCommand } from "./commands/vault.js";
|
|
@@ -15,12 +15,38 @@ import { registerMediaCommand } from "./commands/media.js";
|
|
|
15
15
|
import { registerDraftCommand } from "./commands/draft.js";
|
|
16
16
|
const require = createRequire(import.meta.url);
|
|
17
17
|
const { version } = require("../package.json");
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
18
|
+
function hasParentOption(cmd, key) {
|
|
19
|
+
let cur = cmd;
|
|
20
|
+
while (cur) {
|
|
21
|
+
const v = cur.opts()[key];
|
|
22
|
+
if (v !== undefined && v !== "" && v !== false)
|
|
23
|
+
return true;
|
|
24
|
+
cur = cur.parent;
|
|
25
|
+
}
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
function maybePrintRequirementWarnings(actionCommand) {
|
|
29
|
+
const needsVault = commandRequiresVault(actionCommand);
|
|
30
|
+
const needsSpace = commandRequiresSpace(actionCommand);
|
|
31
|
+
if (!needsVault && !needsSpace)
|
|
32
|
+
return;
|
|
33
|
+
const settings = readSettings();
|
|
34
|
+
const hasVault = !!settings?.vaultSlug;
|
|
35
|
+
const hasSpace = !!settings?.selectedSpaceSlug;
|
|
36
|
+
const vaultOverride = hasParentOption(actionCommand, "vaultSlug");
|
|
37
|
+
const spaceOverride = hasParentOption(actionCommand, "spaceSlug");
|
|
38
|
+
const warnings = [];
|
|
39
|
+
if (needsVault && !hasVault && !vaultOverride) {
|
|
40
|
+
warnings.push("Vault not set. Run 'gobi vault init' first, or pass --vault-slug.");
|
|
41
|
+
}
|
|
42
|
+
if (needsSpace && !hasSpace && !spaceOverride) {
|
|
43
|
+
warnings.push("Space not set. Run 'gobi space warp' first, or pass --space-slug.");
|
|
44
|
+
}
|
|
45
|
+
if (warnings.length) {
|
|
46
|
+
for (const w of warnings)
|
|
47
|
+
console.log(w);
|
|
48
|
+
console.log("");
|
|
49
|
+
}
|
|
24
50
|
}
|
|
25
51
|
export async function cli() {
|
|
26
52
|
const program = new Command();
|
|
@@ -32,7 +58,6 @@ export async function cli() {
|
|
|
32
58
|
.configureHelp({ helpWidth: process.stdout.columns || 200 });
|
|
33
59
|
// Register all command groups
|
|
34
60
|
registerAuthCommand(program);
|
|
35
|
-
registerInitCommand(program);
|
|
36
61
|
registerSpaceCommand(program);
|
|
37
62
|
registerGlobalCommand(program);
|
|
38
63
|
registerVaultCommand(program);
|
|
@@ -50,12 +75,11 @@ export async function cli() {
|
|
|
50
75
|
sub.configureHelp({ helpWidth });
|
|
51
76
|
}
|
|
52
77
|
}
|
|
53
|
-
// Hook into the pre-action to init credentials and show
|
|
54
|
-
program.hook("preAction", async () => {
|
|
78
|
+
// Hook into the pre-action to init credentials and show requirement warnings
|
|
79
|
+
program.hook("preAction", async (_thisCommand, actionCommand) => {
|
|
55
80
|
await initCredentials();
|
|
56
|
-
if (!program.opts().json
|
|
57
|
-
|
|
58
|
-
console.log("");
|
|
81
|
+
if (!program.opts().json) {
|
|
82
|
+
maybePrintRequirementWarnings(actionCommand);
|
|
59
83
|
}
|
|
60
84
|
});
|
|
61
85
|
try {
|