@gobi-ai/cli 2.0.34 → 2.0.36
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 +15 -13
- package/dist/commands/artifact.js +14 -6
- package/dist/commands/global.js +23 -12
- package/dist/commands/personal.js +43 -13
- package/dist/commands/space.js +31 -16
- package/dist/commands/utils.js +101 -6
- package/dist/main.js +2 -2
- package/package.json +1 -1
- package/skills/gobi-artifact/SKILL.md +52 -30
- package/skills/gobi-artifact/references/personal.md +42 -0
- package/skills/gobi-artifact/references/space.md +42 -0
- package/skills/gobi-core/SKILL.md +3 -3
- package/skills/gobi-homepage/SKILL.md +1 -1
- package/skills/gobi-media/SKILL.md +2 -2
- package/skills/gobi-sense/SKILL.md +2 -2
- package/skills/gobi-space/SKILL.md +5 -5
- package/skills/gobi-space/references/global.md +2 -2
- package/skills/gobi-space/references/personal.md +30 -2
- package/skills/gobi-space/references/space.md +2 -2
- package/skills/gobi-vault/SKILL.md +2 -2
- package/skills/gobi-artifact/references/artifact.md +0 -142
|
@@ -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.
|
|
7
|
+
"version": "2.0.36",
|
|
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.
|
|
12
|
+
"version": "2.0.36",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "gobi-ai"
|
|
15
15
|
},
|
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Each setup step unlocks a different family of commands — run only the ones the
|
|
|
63
63
|
| Step | Unlocks |
|
|
64
64
|
|------|---------|
|
|
65
65
|
| `gobi auth login` | All authenticated commands |
|
|
66
|
-
| `gobi vault init` | Every `gobi vault …` command (`publish`, `unpublish`, `sync`); also lets
|
|
66
|
+
| `gobi vault init` | Every `gobi vault …` command (`publish`, `unpublish`, `sync`); also lets `<scope> artifact create --auto-attachments` resolve that vault automatically |
|
|
67
67
|
| `gobi space warp` | Every `gobi space …` command without needing `--space-slug` |
|
|
68
68
|
|
|
69
69
|
---
|
|
@@ -233,19 +233,21 @@ Times are ISO 8601 UTC (e.g. `2026-03-20T00:00:00Z`).
|
|
|
233
233
|
|
|
234
234
|
An *artifact* is a versioned, human-owned creation attached to posts. Kinds: `image | video | gif | markdown | meeting_summary`. Markdown kinds carry a body; media kinds carry an uploaded file. Revisions form a draft/published tree (at most one published per artifact). Markdown kinds store `metadata.vaultSlug` for `[[wikilink]]` resolution. See the `gobi-artifact` skill for full workflows.
|
|
235
235
|
|
|
236
|
+
Artifacts are **scoped to a space**: the subcommands live under `gobi personal artifact …` (your personal space) and `gobi space artifact …` (the active team space — `gobi space warp <slug>` or `gobi space --space-slug <slug> artifact …`). `<scope>` below is `personal` or `space`.
|
|
237
|
+
|
|
236
238
|
| Command | Description |
|
|
237
239
|
|---------|-------------|
|
|
238
|
-
| `gobi artifact list [--kind <k>] [--limit N]` | List
|
|
239
|
-
| `gobi artifact get <artifactId>` | Get one artifact with its current revision |
|
|
240
|
-
| `gobi artifact create --kind <k> [--file <path> \| --content <md>] [--title <t>] [--vault-slug <slug>] [--post-id <id>] [--auto-attachments] [--change-note <note>]` | Create an artifact. markdown/meeting_summary take a body via `--file`, `--content`, or stdin (`-`); image/gif/video upload `--file`. `--post-id` attaches it to a post (appends, doesn't clobber). `--auto-attachments` (markdown) uploads `[[wikilinks]]` to `--vault-slug`. |
|
|
241
|
-
| `gobi artifact revise <artifactId> [--file <path> \| --content <md>] [--change-note <note>] [--from <revisionId>] [--auto-attachments]` | Add a draft revision. `--from` branches off a specific revision. `--auto-attachments` reuses the artifact's stored `metadata.vaultSlug`. |
|
|
242
|
-
| `gobi artifact publish <artifactId> --revision <revisionId>` | Publish a revision (the artifact's single published revision) |
|
|
243
|
-
| `gobi artifact revert <artifactId> --to <revisionId>` | Move the published pointer to an earlier revision |
|
|
244
|
-
| `gobi artifact history <artifactId>` | List the full revision tree (owner only) |
|
|
245
|
-
| `gobi artifact download <artifactId> [--revision <revisionId>] [--out <path>]` | Download a revision's content (markdown body to file/stdout; media bytes to file). Defaults to the current revision. |
|
|
246
|
-
| `gobi artifact delete <artifactId>` | Delete an artifact and its revision tree |
|
|
247
|
-
|
|
248
|
-
Attach an artifact to a post at creation time with `gobi artifact create --post-id <postId>` (it merges into the post's existing artifacts without clobbering them).
|
|
240
|
+
| `gobi <scope> artifact list [--kind <k>] [--limit N]` | List this scope's artifacts (newest first) |
|
|
241
|
+
| `gobi <scope> artifact get <artifactId>` | Get one artifact with its current revision |
|
|
242
|
+
| `gobi <scope> artifact create --kind <k> [--file <path> \| --content <md>] [--title <t>] [--vault-slug <slug>] [--post-id <id>] [--auto-attachments] [--change-note <note>]` | Create an artifact in this scope. markdown/meeting_summary take a body via `--file`, `--content`, or stdin (`-`); image/gif/video upload `--file`. `--post-id` attaches it to a post (appends, doesn't clobber). `--auto-attachments` (markdown) uploads `[[wikilinks]]` to `--vault-slug`. |
|
|
243
|
+
| `gobi <scope> artifact revise <artifactId> [--file <path> \| --content <md>] [--change-note <note>] [--from <revisionId>] [--auto-attachments]` | Add a draft revision. `--from` branches off a specific revision. `--auto-attachments` reuses the artifact's stored `metadata.vaultSlug`. |
|
|
244
|
+
| `gobi <scope> artifact publish <artifactId> --revision <revisionId>` | Publish a revision (the artifact's single published revision) |
|
|
245
|
+
| `gobi <scope> artifact revert <artifactId> --to <revisionId>` | Move the published pointer to an earlier revision |
|
|
246
|
+
| `gobi <scope> artifact history <artifactId>` | List the full revision tree (owner only) |
|
|
247
|
+
| `gobi <scope> artifact download <artifactId> [--revision <revisionId>] [--out <path>]` | Download a revision's content (markdown body to file/stdout; media bytes to file). Defaults to the current revision. |
|
|
248
|
+
| `gobi <scope> artifact delete <artifactId>` | Delete an artifact and its revision tree |
|
|
249
|
+
|
|
250
|
+
Attach an artifact to a post at creation time with `gobi <scope> artifact create --post-id <postId>` (it merges into the post's existing artifacts without clobbering them).
|
|
249
251
|
|
|
250
252
|
### Media generation
|
|
251
253
|
|
|
@@ -303,7 +305,7 @@ The CLI ships a `.claude-plugin/` manifest with skills that wrap the command gro
|
|
|
303
305
|
| `gobi-core` | Auth, update, space list/warp |
|
|
304
306
|
| `gobi-vault` | `gobi vault init/list/publish/unpublish/sync` |
|
|
305
307
|
| `gobi-space` | `gobi space …`, `gobi global …`, and `gobi personal …` |
|
|
306
|
-
| `gobi-artifact` | `gobi artifact …` |
|
|
308
|
+
| `gobi-artifact` | `gobi personal artifact …` and `gobi space artifact …` |
|
|
307
309
|
| `gobi-media` | `gobi media …` |
|
|
308
310
|
| `gobi-sense` | `gobi sense list-activities/list-transcriptions` |
|
|
309
311
|
| `gobi-homepage` | Building custom HTML homepages with `window.gobi` |
|
|
@@ -135,11 +135,11 @@ function printArtifact(a) {
|
|
|
135
135
|
console.log(` mediaUrl: ${rev.mediaUrl}`);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
// Registers the full `artifact` subcommand tree under `parent` (a `gobi space`
|
|
139
|
+
// or `gobi personal` group). Moved here from a top-level `gobi artifact` group
|
|
140
|
+
// so artifacts are scoped to a space (team or personal), matching posts.
|
|
141
|
+
export function registerArtifactSubcommands(parent, scope, description) {
|
|
142
|
+
const artifact = parent.command("artifact").description(description);
|
|
143
143
|
// ── Create ──
|
|
144
144
|
artifact
|
|
145
145
|
.command("create")
|
|
@@ -158,6 +158,10 @@ export function registerArtifactCommand(program) {
|
|
|
158
158
|
throw new Error(`--kind must be one of: ${ALL_KINDS.join(", ")}`);
|
|
159
159
|
}
|
|
160
160
|
const body = { kind };
|
|
161
|
+
// Scope the new artifact to this group's space (team) or personal space.
|
|
162
|
+
const { spaceSlug } = scope.resolve();
|
|
163
|
+
if (spaceSlug)
|
|
164
|
+
body.spaceSlug = spaceSlug;
|
|
161
165
|
if (opts.title != null)
|
|
162
166
|
body.title = opts.title;
|
|
163
167
|
if (opts.vaultSlug)
|
|
@@ -421,11 +425,15 @@ export function registerArtifactCommand(program) {
|
|
|
421
425
|
// ── List ──
|
|
422
426
|
artifact
|
|
423
427
|
.command("list")
|
|
424
|
-
.description("List
|
|
428
|
+
.description("List this scope's artifacts (newest first).")
|
|
425
429
|
.option("--kind <kind>", `Filter by kind: ${ALL_KINDS.join(" | ")}`)
|
|
426
430
|
.option("--limit <n>", "Max items to return")
|
|
427
431
|
.action(async (opts) => {
|
|
428
432
|
const params = {};
|
|
433
|
+
// Scope the listing to this group's space (team) or personal space.
|
|
434
|
+
const { spaceSlug } = scope.resolve();
|
|
435
|
+
if (spaceSlug)
|
|
436
|
+
params.spaceSlug = spaceSlug;
|
|
429
437
|
if (opts.kind) {
|
|
430
438
|
if (!ALL_KINDS.includes(opts.kind)) {
|
|
431
439
|
throw new Error(`--kind must be one of: ${ALL_KINDS.join(", ")}`);
|
package/dist/commands/global.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { WEB_BASE_URL } from "../constants.js";
|
|
2
2
|
import { apiGet, apiPost, apiPatch, apiPut, apiDelete } from "../client.js";
|
|
3
|
-
import { formatAttachmentLines, formatAttachmentSummary, formatReactionChips, isJsonMode, jsonOut, readStdin, unwrapResp, } from "./utils.js";
|
|
3
|
+
import { buildMentionMap, formatAttachmentLines, formatAttachmentSummary, formatPostLabel, formatReactionChips, formatReplyLine, isJsonMode, jsonOut, postBodyText, readStdin, unwrapResp, } from "./utils.js";
|
|
4
4
|
import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
|
|
5
5
|
function readContent(value) {
|
|
6
6
|
if (value === "-")
|
|
@@ -10,7 +10,7 @@ function readContent(value) {
|
|
|
10
10
|
function buildPersonalPostUrl(post) {
|
|
11
11
|
return `${WEB_BASE_URL}/posts/${post.id}`;
|
|
12
12
|
}
|
|
13
|
-
function formatFeedLine(m) {
|
|
13
|
+
function formatFeedLine(m, mentions) {
|
|
14
14
|
const isReply = m.parentPostId != null ||
|
|
15
15
|
m.type === "post-reply";
|
|
16
16
|
const id = `[${isReply ? "r" : "p"}:${m.id}]`;
|
|
@@ -19,12 +19,11 @@ function formatFeedLine(m) {
|
|
|
19
19
|
`User ${m.authorId ?? "?"}`;
|
|
20
20
|
let label;
|
|
21
21
|
if (isReply) {
|
|
22
|
-
const text = m.
|
|
22
|
+
const text = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
|
|
23
23
|
label = text.length > 80 ? text.slice(0, 80) + "…" : text;
|
|
24
|
-
label = label.replace(/\s+/g, " ").trim();
|
|
25
24
|
}
|
|
26
25
|
else {
|
|
27
|
-
label = m
|
|
26
|
+
label = formatPostLabel(m, mentions);
|
|
28
27
|
}
|
|
29
28
|
const chips = formatReactionChips(m);
|
|
30
29
|
const attachSummary = formatAttachmentSummary(m);
|
|
@@ -56,6 +55,7 @@ export function registerGlobalCommand(program) {
|
|
|
56
55
|
jsonOut({
|
|
57
56
|
items: resp.data || [],
|
|
58
57
|
pagination: resp.pagination || {},
|
|
58
|
+
mentions: resp.mentions || {},
|
|
59
59
|
});
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
@@ -65,7 +65,8 @@ export function registerGlobalCommand(program) {
|
|
|
65
65
|
console.log("No items found.");
|
|
66
66
|
return;
|
|
67
67
|
}
|
|
68
|
-
const
|
|
68
|
+
const mentions = buildMentionMap(resp);
|
|
69
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
69
70
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
70
71
|
console.log(`Global feed (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
71
72
|
});
|
|
@@ -89,6 +90,7 @@ export function registerGlobalCommand(program) {
|
|
|
89
90
|
jsonOut({
|
|
90
91
|
items: resp.data || [],
|
|
91
92
|
pagination: resp.pagination || {},
|
|
93
|
+
mentions: resp.mentions || {},
|
|
92
94
|
});
|
|
93
95
|
return;
|
|
94
96
|
}
|
|
@@ -98,11 +100,19 @@ export function registerGlobalCommand(program) {
|
|
|
98
100
|
console.log("No posts found.");
|
|
99
101
|
return;
|
|
100
102
|
}
|
|
103
|
+
const mentions = buildMentionMap(resp);
|
|
101
104
|
const lines = [];
|
|
102
105
|
for (const t of items) {
|
|
103
106
|
const author = t.author?.name ||
|
|
104
107
|
`User ${t.authorId}`;
|
|
105
|
-
lines.push(`- [${t.id}] "${t
|
|
108
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" by ${author} (${t.replyCount ?? 0} replies, ${t.createdAt})`);
|
|
109
|
+
for (const line of formatAttachmentLines(t, " ", "📎")) {
|
|
110
|
+
lines.push(line);
|
|
111
|
+
}
|
|
112
|
+
const replies = t.replies || [];
|
|
113
|
+
for (const r of replies) {
|
|
114
|
+
lines.push(formatReplyLine(r, mentions));
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
108
118
|
console.log(`Posts (${items.length} items):\n` + lines.join("\n") + footer);
|
|
@@ -135,19 +145,20 @@ export function registerGlobalCommand(program) {
|
|
|
135
145
|
}
|
|
136
146
|
const post = (data.update || data.post || data);
|
|
137
147
|
const replies = (data.replies || []);
|
|
148
|
+
const mentionMap = buildMentionMap(postResp);
|
|
138
149
|
const author = post.author?.name ||
|
|
139
150
|
`User ${post.authorId}`;
|
|
140
151
|
const ancestorLines = [];
|
|
141
152
|
if (ancestors.length) {
|
|
142
153
|
ancestors.forEach((a, i) => {
|
|
143
|
-
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a)}`);
|
|
154
|
+
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a, mentionMap)}`);
|
|
144
155
|
});
|
|
145
156
|
}
|
|
146
157
|
const replyLines = [];
|
|
147
158
|
for (const r of replies) {
|
|
148
159
|
const rAuthor = r.author?.name ||
|
|
149
160
|
`User ${r.authorId}`;
|
|
150
|
-
const text = r
|
|
161
|
+
const text = postBodyText(r, mentionMap);
|
|
151
162
|
const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "…";
|
|
152
163
|
const rChips = formatReactionChips(r);
|
|
153
164
|
const rAttach = formatAttachmentSummary(r);
|
|
@@ -167,7 +178,7 @@ export function registerGlobalCommand(program) {
|
|
|
167
178
|
? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
|
|
168
179
|
: []),
|
|
169
180
|
"",
|
|
170
|
-
post
|
|
181
|
+
postBodyText(post, mentionMap),
|
|
171
182
|
...(attachmentLines.length
|
|
172
183
|
? ["", `Attachments (${attachmentLines.length}):`, ...attachmentLines]
|
|
173
184
|
: []),
|
|
@@ -187,7 +198,7 @@ export function registerGlobalCommand(program) {
|
|
|
187
198
|
.option("--title <title>", "Title of the post")
|
|
188
199
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
189
200
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
190
|
-
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
201
|
+
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
191
202
|
.option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
|
|
192
203
|
.option("--repost-post-id <postId>", "Wrap an existing top-level post as the embedded card on this new post. Composes with --content / --rich-text / --attach (the wrapping author's text + media render above the embedded card). Reposts-of-reposts are collapsed to the transitive root server-side. The referenced post must exist, not be deleted, and not itself be a reply.")
|
|
193
204
|
.action(async (opts) => {
|
|
@@ -253,7 +264,7 @@ export function registerGlobalCommand(program) {
|
|
|
253
264
|
.option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
|
|
254
265
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
255
266
|
.option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
|
|
256
|
-
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
267
|
+
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
257
268
|
.action(async (postId, opts) => {
|
|
258
269
|
const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
|
|
259
270
|
const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { apiGet, apiPost, apiPatch, apiPut, apiDelete } from "../client.js";
|
|
2
|
-
import { formatAttachmentLines, formatAttachmentSummary, formatReactionChips, isJsonMode, jsonOut, readStdin, unwrapResp, } from "./utils.js";
|
|
2
|
+
import { buildMentionMap, formatAttachmentLines, formatAttachmentSummary, formatPostLabel, formatReactionChips, formatReplyLine, isJsonMode, jsonOut, postBodyText, readStdin, unwrapResp, } from "./utils.js";
|
|
3
3
|
import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
|
|
4
|
+
import { registerArtifactSubcommands } from "./artifact.js";
|
|
4
5
|
function readContent(value) {
|
|
5
6
|
if (value === "-")
|
|
6
7
|
return readStdin();
|
|
7
8
|
return value;
|
|
8
9
|
}
|
|
9
|
-
function formatFeedLine(m) {
|
|
10
|
+
function formatFeedLine(m, mentions) {
|
|
10
11
|
const isReply = m.parentPostId != null ||
|
|
11
12
|
m.type === "post-reply";
|
|
12
13
|
const id = `[${isReply ? "r" : "p"}:${m.id}]`;
|
|
@@ -15,12 +16,11 @@ function formatFeedLine(m) {
|
|
|
15
16
|
`User ${m.authorId ?? "?"}`;
|
|
16
17
|
let label;
|
|
17
18
|
if (isReply) {
|
|
18
|
-
const text = m.
|
|
19
|
+
const text = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
|
|
19
20
|
label = text.length > 80 ? text.slice(0, 80) + "…" : text;
|
|
20
|
-
label = label.replace(/\s+/g, " ").trim();
|
|
21
21
|
}
|
|
22
22
|
else {
|
|
23
|
-
label = m
|
|
23
|
+
label = formatPostLabel(m, mentions);
|
|
24
24
|
}
|
|
25
25
|
const chips = formatReactionChips(m);
|
|
26
26
|
const attachSummary = formatAttachmentSummary(m);
|
|
@@ -51,6 +51,7 @@ export function registerPersonalCommand(program) {
|
|
|
51
51
|
jsonOut({
|
|
52
52
|
items: resp.data || [],
|
|
53
53
|
pagination: resp.pagination || {},
|
|
54
|
+
mentions: resp.mentions || {},
|
|
54
55
|
});
|
|
55
56
|
return;
|
|
56
57
|
}
|
|
@@ -60,7 +61,8 @@ export function registerPersonalCommand(program) {
|
|
|
60
61
|
console.log("No items in your personal space yet.");
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
|
-
const
|
|
64
|
+
const mentions = buildMentionMap(resp);
|
|
65
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
64
66
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
65
67
|
console.log(`Personal-space feed (${items.length} items, newest first):\n` +
|
|
66
68
|
lines.join("\n") +
|
|
@@ -96,7 +98,8 @@ export function registerPersonalCommand(program) {
|
|
|
96
98
|
console.log("No results found.");
|
|
97
99
|
return;
|
|
98
100
|
}
|
|
99
|
-
const
|
|
101
|
+
const mentions = buildMentionMap(resp);
|
|
102
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
100
103
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
101
104
|
console.log(`Search results (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
102
105
|
});
|
|
@@ -129,9 +132,31 @@ export function registerPersonalCommand(program) {
|
|
|
129
132
|
console.log("No posts found in your personal space.");
|
|
130
133
|
return;
|
|
131
134
|
}
|
|
135
|
+
const mentions = buildMentionMap(resp);
|
|
136
|
+
// The personal feed returns posts and replies as a flat list; group the
|
|
137
|
+
// replies under their root post so they can be nested. Falls back to an
|
|
138
|
+
// embedded `replies` array if the endpoint provides one.
|
|
139
|
+
const repliesByRoot = new Map();
|
|
140
|
+
for (const it of allItems) {
|
|
141
|
+
if (it.type === "post-reply" || it.parentPostId != null) {
|
|
142
|
+
const root = it.rootPostId ?? it.parentPostId;
|
|
143
|
+
const arr = repliesByRoot.get(root) || [];
|
|
144
|
+
arr.push(it);
|
|
145
|
+
repliesByRoot.set(root, arr);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
132
148
|
const lines = [];
|
|
133
149
|
for (const t of items) {
|
|
134
|
-
lines.push(`- [${t.id}] "${t
|
|
150
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" (${t.replyCount ?? 0} replies, ${t.createdAt})`);
|
|
151
|
+
for (const line of formatAttachmentLines(t, " ", "📎")) {
|
|
152
|
+
lines.push(line);
|
|
153
|
+
}
|
|
154
|
+
const replies = t.replies ||
|
|
155
|
+
repliesByRoot.get(t.id) ||
|
|
156
|
+
[];
|
|
157
|
+
for (const r of replies) {
|
|
158
|
+
lines.push(formatReplyLine(r, mentions));
|
|
159
|
+
}
|
|
135
160
|
}
|
|
136
161
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
137
162
|
console.log(`Personal-space posts (${items.length} of ${allItems.length} feed items):\n` +
|
|
@@ -171,19 +196,20 @@ export function registerPersonalCommand(program) {
|
|
|
171
196
|
}
|
|
172
197
|
const post = (data.update || data.post || data);
|
|
173
198
|
const replies = (data.replies || []);
|
|
199
|
+
const mentionMap = buildMentionMap(postResp);
|
|
174
200
|
const author = post.author?.name ||
|
|
175
201
|
`User ${post.authorId}`;
|
|
176
202
|
const ancestorLines = [];
|
|
177
203
|
if (ancestors.length) {
|
|
178
204
|
ancestors.forEach((a, i) => {
|
|
179
|
-
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a)}`);
|
|
205
|
+
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a, mentionMap)}`);
|
|
180
206
|
});
|
|
181
207
|
}
|
|
182
208
|
const replyLines = [];
|
|
183
209
|
for (const r of replies) {
|
|
184
210
|
const rAuthor = r.author?.name ||
|
|
185
211
|
`User ${r.authorId}`;
|
|
186
|
-
const text = r
|
|
212
|
+
const text = postBodyText(r, mentionMap);
|
|
187
213
|
const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "…";
|
|
188
214
|
const rChips = formatReactionChips(r);
|
|
189
215
|
const rAttach = formatAttachmentSummary(r);
|
|
@@ -203,7 +229,7 @@ export function registerPersonalCommand(program) {
|
|
|
203
229
|
? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
|
|
204
230
|
: []),
|
|
205
231
|
"",
|
|
206
|
-
post
|
|
232
|
+
postBodyText(post, mentionMap),
|
|
207
233
|
...(attachmentLines.length
|
|
208
234
|
? ["", `Attachments (${attachmentLines.length}):`, ...attachmentLines]
|
|
209
235
|
: []),
|
|
@@ -229,7 +255,7 @@ export function registerPersonalCommand(program) {
|
|
|
229
255
|
.option("--title <title>", "Title of the post")
|
|
230
256
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
231
257
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
232
|
-
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
258
|
+
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
233
259
|
.option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
|
|
234
260
|
.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.")
|
|
235
261
|
.action(async (opts) => {
|
|
@@ -299,7 +325,7 @@ export function registerPersonalCommand(program) {
|
|
|
299
325
|
.option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
|
|
300
326
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
301
327
|
.option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
|
|
302
|
-
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
328
|
+
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
303
329
|
.action(async (postId, opts) => {
|
|
304
330
|
const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
|
|
305
331
|
const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
|
|
@@ -475,4 +501,8 @@ export function registerPersonalCommand(program) {
|
|
|
475
501
|
console.log(`Removed ${emoji} reaction from ${postId}.` +
|
|
476
502
|
(chips ? `\n Now: ${chips}` : ""));
|
|
477
503
|
});
|
|
504
|
+
// ── Artifacts (scoped to your personal space) ──
|
|
505
|
+
registerArtifactSubcommands(personal, { resolve: () => ({}) }, "Versioned creations attached to posts, scoped to your personal space (visible " +
|
|
506
|
+
"only to you). Kinds: image | video | gif | markdown | meeting_summary. Always " +
|
|
507
|
+
"human-owned; revisions form a draft/published tree (one published per artifact).");
|
|
478
508
|
}
|
package/dist/commands/space.js
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { WEB_BASE_URL } from "../constants.js";
|
|
2
2
|
import { apiGet, apiPost, apiPatch, apiPut, apiDelete } from "../client.js";
|
|
3
3
|
import { requireSpace, selectSpace, setSpaceRequirement, writeSpaceSetting, } from "./init.js";
|
|
4
|
-
import { formatAttachmentLines, formatAttachmentSummary, formatReactionChips, isJsonMode, jsonOut, readStdin, resolveSpaceSlug, unwrapResp, } from "./utils.js";
|
|
4
|
+
import { buildMentionMap, formatAttachmentLines, formatAttachmentSummary, formatPostLabel, formatReactionChips, formatReplyLine, isJsonMode, jsonOut, postBodyText, readStdin, resolveSpaceSlug, unwrapResp, } from "./utils.js";
|
|
5
5
|
import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
|
|
6
|
+
import { registerArtifactSubcommands } from "./artifact.js";
|
|
6
7
|
function readContent(value) {
|
|
7
8
|
if (value === "-")
|
|
8
9
|
return readStdin();
|
|
9
10
|
return value;
|
|
10
11
|
}
|
|
11
|
-
function formatFeedLine(m) {
|
|
12
|
+
function formatFeedLine(m, mentions) {
|
|
12
13
|
const isReply = m.parentPostId != null;
|
|
13
14
|
const id = `[${isReply ? "r" : "p"}:${m.id}]`;
|
|
14
15
|
const kind = isReply ? "reply" : "post ";
|
|
@@ -16,12 +17,11 @@ function formatFeedLine(m) {
|
|
|
16
17
|
`User ${m.authorId ?? "?"}`;
|
|
17
18
|
let label;
|
|
18
19
|
if (isReply) {
|
|
19
|
-
const text = m.
|
|
20
|
+
const text = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
|
|
20
21
|
label = text.length > 80 ? text.slice(0, 80) + "…" : text;
|
|
21
|
-
label = label.replace(/\s+/g, " ").trim();
|
|
22
22
|
}
|
|
23
23
|
else {
|
|
24
|
-
label = m
|
|
24
|
+
label = formatPostLabel(m, mentions);
|
|
25
25
|
}
|
|
26
26
|
const chips = formatReactionChips(m);
|
|
27
27
|
const attachSummary = formatAttachmentSummary(m);
|
|
@@ -174,11 +174,12 @@ export function registerSpaceCommand(program) {
|
|
|
174
174
|
console.log(`No posts found for topic "${topic.name || topicSlug}".`);
|
|
175
175
|
return;
|
|
176
176
|
}
|
|
177
|
+
const mentions = buildMentionMap(resp);
|
|
177
178
|
const lines = [];
|
|
178
179
|
for (const t of posts) {
|
|
179
180
|
const author = t.author?.name || "Unknown";
|
|
180
181
|
const spaceName = t.space?.name || "";
|
|
181
|
-
lines.push(`- [${t.id}] "${t
|
|
182
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" by ${author} in ${spaceName} (${t.replyCount} replies, ${t.createdAt})`);
|
|
182
183
|
}
|
|
183
184
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
184
185
|
console.log(`Topic: ${topic.name || topicSlug}\n` +
|
|
@@ -216,7 +217,8 @@ export function registerSpaceCommand(program) {
|
|
|
216
217
|
console.log("No items found.");
|
|
217
218
|
return;
|
|
218
219
|
}
|
|
219
|
-
const
|
|
220
|
+
const mentions = buildMentionMap(resp);
|
|
221
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
220
222
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
221
223
|
console.log(`Feed (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
222
224
|
});
|
|
@@ -254,7 +256,8 @@ export function registerSpaceCommand(program) {
|
|
|
254
256
|
console.log("No results found.");
|
|
255
257
|
return;
|
|
256
258
|
}
|
|
257
|
-
const
|
|
259
|
+
const mentions = buildMentionMap(resp);
|
|
260
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
258
261
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
259
262
|
console.log(`Search results (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
260
263
|
});
|
|
@@ -286,21 +289,22 @@ export function registerSpaceCommand(program) {
|
|
|
286
289
|
jsonOut({ ...data, ancestors, pagination, mentions });
|
|
287
290
|
return;
|
|
288
291
|
}
|
|
289
|
-
const post = (data.thread || data);
|
|
292
|
+
const post = (data.post || data.thread || data);
|
|
290
293
|
const replies = (data.items || []);
|
|
294
|
+
const mentionMap = buildMentionMap(postResp);
|
|
291
295
|
const author = post.author?.name ||
|
|
292
296
|
`User ${post.authorId}`;
|
|
293
297
|
const ancestorLines = [];
|
|
294
298
|
if (ancestors.length) {
|
|
295
299
|
ancestors.forEach((a, i) => {
|
|
296
|
-
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a)}`);
|
|
300
|
+
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a, mentionMap)}`);
|
|
297
301
|
});
|
|
298
302
|
}
|
|
299
303
|
const replyLines = [];
|
|
300
304
|
for (const r of replies) {
|
|
301
305
|
const rAuthor = r.author?.name ||
|
|
302
306
|
`User ${r.authorId}`;
|
|
303
|
-
const text = r
|
|
307
|
+
const text = postBodyText(r, mentionMap);
|
|
304
308
|
const body = opts.full || !text || text.length <= 200
|
|
305
309
|
? text
|
|
306
310
|
: text.slice(0, 200) + "…";
|
|
@@ -322,7 +326,7 @@ export function registerSpaceCommand(program) {
|
|
|
322
326
|
? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
|
|
323
327
|
: []),
|
|
324
328
|
"",
|
|
325
|
-
post
|
|
329
|
+
postBodyText(post, mentionMap),
|
|
326
330
|
...(attachmentLines.length
|
|
327
331
|
? ["", `Attachments (${attachmentLines.length}):`, ...attachmentLines]
|
|
328
332
|
: []),
|
|
@@ -363,12 +367,19 @@ export function registerSpaceCommand(program) {
|
|
|
363
367
|
console.log("No posts found.");
|
|
364
368
|
return;
|
|
365
369
|
}
|
|
370
|
+
const mentions = buildMentionMap(resp);
|
|
366
371
|
const lines = [];
|
|
367
372
|
for (const t of items) {
|
|
368
373
|
const author = t.author?.name ||
|
|
369
374
|
`User ${t.authorId}`;
|
|
370
|
-
|
|
371
|
-
|
|
375
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" by ${author} (${t.replyCount} replies, ${t.createdAt})`);
|
|
376
|
+
for (const line of formatAttachmentLines(t, " ", "📎")) {
|
|
377
|
+
lines.push(line);
|
|
378
|
+
}
|
|
379
|
+
const replies = t.replies || [];
|
|
380
|
+
for (const r of replies) {
|
|
381
|
+
lines.push(formatReplyLine(r, mentions));
|
|
382
|
+
}
|
|
372
383
|
}
|
|
373
384
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
374
385
|
console.log(`Posts (${items.length} items):\n` + lines.join("\n") + footer);
|
|
@@ -379,7 +390,7 @@ export function registerSpaceCommand(program) {
|
|
|
379
390
|
.option("--title <title>", "Title of the post")
|
|
380
391
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
381
392
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
382
|
-
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
393
|
+
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi space artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
383
394
|
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
384
395
|
.option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
|
|
385
396
|
.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.")
|
|
@@ -452,7 +463,7 @@ export function registerSpaceCommand(program) {
|
|
|
452
463
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
453
464
|
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
454
465
|
.option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
|
|
455
|
-
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
466
|
+
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi space artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
456
467
|
.action(async (postId, opts) => {
|
|
457
468
|
const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
|
|
458
469
|
const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
|
|
@@ -714,4 +725,8 @@ export function registerSpaceCommand(program) {
|
|
|
714
725
|
}
|
|
715
726
|
console.log(`Channel members (${items.length}):\n` + lines.join("\n"));
|
|
716
727
|
});
|
|
728
|
+
// ── Artifacts (scoped to this space) ──
|
|
729
|
+
registerArtifactSubcommands(space, { resolve: () => ({ spaceSlug: resolveSpaceSlug(space) }) }, "Versioned creations attached to posts, scoped to this space (visible to its " +
|
|
730
|
+
"members). Kinds: image | video | gif | markdown | meeting_summary. Always " +
|
|
731
|
+
"human-owned; revisions form a draft/published tree (one published per artifact).");
|
|
717
732
|
}
|