@gobi-ai/cli 2.0.34 → 2.0.35
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/dist/commands/global.js +21 -10
- package/dist/commands/personal.js +36 -11
- package/dist/commands/space.js +24 -14
- package/dist/commands/utils.js +91 -5
- package/package.json +1 -1
- package/skills/gobi-artifact/SKILL.md +2 -2
- package/skills/gobi-core/SKILL.md +2 -2
- 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 +2 -2
- package/skills/gobi-vault/SKILL.md +2 -2
|
@@ -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.35",
|
|
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.35",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "gobi-ai"
|
|
15
15
|
},
|
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
|
: []),
|
|
@@ -1,12 +1,12 @@
|
|
|
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
4
|
function readContent(value) {
|
|
5
5
|
if (value === "-")
|
|
6
6
|
return readStdin();
|
|
7
7
|
return value;
|
|
8
8
|
}
|
|
9
|
-
function formatFeedLine(m) {
|
|
9
|
+
function formatFeedLine(m, mentions) {
|
|
10
10
|
const isReply = m.parentPostId != null ||
|
|
11
11
|
m.type === "post-reply";
|
|
12
12
|
const id = `[${isReply ? "r" : "p"}:${m.id}]`;
|
|
@@ -15,12 +15,11 @@ function formatFeedLine(m) {
|
|
|
15
15
|
`User ${m.authorId ?? "?"}`;
|
|
16
16
|
let label;
|
|
17
17
|
if (isReply) {
|
|
18
|
-
const text = m.
|
|
18
|
+
const text = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
|
|
19
19
|
label = text.length > 80 ? text.slice(0, 80) + "…" : text;
|
|
20
|
-
label = label.replace(/\s+/g, " ").trim();
|
|
21
20
|
}
|
|
22
21
|
else {
|
|
23
|
-
label = m
|
|
22
|
+
label = formatPostLabel(m, mentions);
|
|
24
23
|
}
|
|
25
24
|
const chips = formatReactionChips(m);
|
|
26
25
|
const attachSummary = formatAttachmentSummary(m);
|
|
@@ -51,6 +50,7 @@ export function registerPersonalCommand(program) {
|
|
|
51
50
|
jsonOut({
|
|
52
51
|
items: resp.data || [],
|
|
53
52
|
pagination: resp.pagination || {},
|
|
53
|
+
mentions: resp.mentions || {},
|
|
54
54
|
});
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
@@ -60,7 +60,8 @@ export function registerPersonalCommand(program) {
|
|
|
60
60
|
console.log("No items in your personal space yet.");
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
-
const
|
|
63
|
+
const mentions = buildMentionMap(resp);
|
|
64
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
64
65
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
65
66
|
console.log(`Personal-space feed (${items.length} items, newest first):\n` +
|
|
66
67
|
lines.join("\n") +
|
|
@@ -96,7 +97,8 @@ export function registerPersonalCommand(program) {
|
|
|
96
97
|
console.log("No results found.");
|
|
97
98
|
return;
|
|
98
99
|
}
|
|
99
|
-
const
|
|
100
|
+
const mentions = buildMentionMap(resp);
|
|
101
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
100
102
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
101
103
|
console.log(`Search results (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
102
104
|
});
|
|
@@ -129,9 +131,31 @@ export function registerPersonalCommand(program) {
|
|
|
129
131
|
console.log("No posts found in your personal space.");
|
|
130
132
|
return;
|
|
131
133
|
}
|
|
134
|
+
const mentions = buildMentionMap(resp);
|
|
135
|
+
// The personal feed returns posts and replies as a flat list; group the
|
|
136
|
+
// replies under their root post so they can be nested. Falls back to an
|
|
137
|
+
// embedded `replies` array if the endpoint provides one.
|
|
138
|
+
const repliesByRoot = new Map();
|
|
139
|
+
for (const it of allItems) {
|
|
140
|
+
if (it.type === "post-reply" || it.parentPostId != null) {
|
|
141
|
+
const root = it.rootPostId ?? it.parentPostId;
|
|
142
|
+
const arr = repliesByRoot.get(root) || [];
|
|
143
|
+
arr.push(it);
|
|
144
|
+
repliesByRoot.set(root, arr);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
132
147
|
const lines = [];
|
|
133
148
|
for (const t of items) {
|
|
134
|
-
lines.push(`- [${t.id}] "${t
|
|
149
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" (${t.replyCount ?? 0} replies, ${t.createdAt})`);
|
|
150
|
+
for (const line of formatAttachmentLines(t, " ", "📎")) {
|
|
151
|
+
lines.push(line);
|
|
152
|
+
}
|
|
153
|
+
const replies = t.replies ||
|
|
154
|
+
repliesByRoot.get(t.id) ||
|
|
155
|
+
[];
|
|
156
|
+
for (const r of replies) {
|
|
157
|
+
lines.push(formatReplyLine(r, mentions));
|
|
158
|
+
}
|
|
135
159
|
}
|
|
136
160
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
137
161
|
console.log(`Personal-space posts (${items.length} of ${allItems.length} feed items):\n` +
|
|
@@ -171,19 +195,20 @@ export function registerPersonalCommand(program) {
|
|
|
171
195
|
}
|
|
172
196
|
const post = (data.update || data.post || data);
|
|
173
197
|
const replies = (data.replies || []);
|
|
198
|
+
const mentionMap = buildMentionMap(postResp);
|
|
174
199
|
const author = post.author?.name ||
|
|
175
200
|
`User ${post.authorId}`;
|
|
176
201
|
const ancestorLines = [];
|
|
177
202
|
if (ancestors.length) {
|
|
178
203
|
ancestors.forEach((a, i) => {
|
|
179
|
-
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a)}`);
|
|
204
|
+
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a, mentionMap)}`);
|
|
180
205
|
});
|
|
181
206
|
}
|
|
182
207
|
const replyLines = [];
|
|
183
208
|
for (const r of replies) {
|
|
184
209
|
const rAuthor = r.author?.name ||
|
|
185
210
|
`User ${r.authorId}`;
|
|
186
|
-
const text = r
|
|
211
|
+
const text = postBodyText(r, mentionMap);
|
|
187
212
|
const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "…";
|
|
188
213
|
const rChips = formatReactionChips(r);
|
|
189
214
|
const rAttach = formatAttachmentSummary(r);
|
|
@@ -203,7 +228,7 @@ export function registerPersonalCommand(program) {
|
|
|
203
228
|
? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
|
|
204
229
|
: []),
|
|
205
230
|
"",
|
|
206
|
-
post
|
|
231
|
+
postBodyText(post, mentionMap),
|
|
207
232
|
...(attachmentLines.length
|
|
208
233
|
? ["", `Attachments (${attachmentLines.length}):`, ...attachmentLines]
|
|
209
234
|
: []),
|
package/dist/commands/space.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
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
6
|
function readContent(value) {
|
|
7
7
|
if (value === "-")
|
|
8
8
|
return readStdin();
|
|
9
9
|
return value;
|
|
10
10
|
}
|
|
11
|
-
function formatFeedLine(m) {
|
|
11
|
+
function formatFeedLine(m, mentions) {
|
|
12
12
|
const isReply = m.parentPostId != null;
|
|
13
13
|
const id = `[${isReply ? "r" : "p"}:${m.id}]`;
|
|
14
14
|
const kind = isReply ? "reply" : "post ";
|
|
@@ -16,12 +16,11 @@ function formatFeedLine(m) {
|
|
|
16
16
|
`User ${m.authorId ?? "?"}`;
|
|
17
17
|
let label;
|
|
18
18
|
if (isReply) {
|
|
19
|
-
const text = m.
|
|
19
|
+
const text = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
|
|
20
20
|
label = text.length > 80 ? text.slice(0, 80) + "…" : text;
|
|
21
|
-
label = label.replace(/\s+/g, " ").trim();
|
|
22
21
|
}
|
|
23
22
|
else {
|
|
24
|
-
label = m
|
|
23
|
+
label = formatPostLabel(m, mentions);
|
|
25
24
|
}
|
|
26
25
|
const chips = formatReactionChips(m);
|
|
27
26
|
const attachSummary = formatAttachmentSummary(m);
|
|
@@ -174,11 +173,12 @@ export function registerSpaceCommand(program) {
|
|
|
174
173
|
console.log(`No posts found for topic "${topic.name || topicSlug}".`);
|
|
175
174
|
return;
|
|
176
175
|
}
|
|
176
|
+
const mentions = buildMentionMap(resp);
|
|
177
177
|
const lines = [];
|
|
178
178
|
for (const t of posts) {
|
|
179
179
|
const author = t.author?.name || "Unknown";
|
|
180
180
|
const spaceName = t.space?.name || "";
|
|
181
|
-
lines.push(`- [${t.id}] "${t
|
|
181
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" by ${author} in ${spaceName} (${t.replyCount} replies, ${t.createdAt})`);
|
|
182
182
|
}
|
|
183
183
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
184
184
|
console.log(`Topic: ${topic.name || topicSlug}\n` +
|
|
@@ -216,7 +216,8 @@ export function registerSpaceCommand(program) {
|
|
|
216
216
|
console.log("No items found.");
|
|
217
217
|
return;
|
|
218
218
|
}
|
|
219
|
-
const
|
|
219
|
+
const mentions = buildMentionMap(resp);
|
|
220
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
220
221
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
221
222
|
console.log(`Feed (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
222
223
|
});
|
|
@@ -254,7 +255,8 @@ export function registerSpaceCommand(program) {
|
|
|
254
255
|
console.log("No results found.");
|
|
255
256
|
return;
|
|
256
257
|
}
|
|
257
|
-
const
|
|
258
|
+
const mentions = buildMentionMap(resp);
|
|
259
|
+
const lines = items.map((m) => formatFeedLine(m, mentions));
|
|
258
260
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
259
261
|
console.log(`Search results (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
260
262
|
});
|
|
@@ -286,21 +288,22 @@ export function registerSpaceCommand(program) {
|
|
|
286
288
|
jsonOut({ ...data, ancestors, pagination, mentions });
|
|
287
289
|
return;
|
|
288
290
|
}
|
|
289
|
-
const post = (data.thread || data);
|
|
291
|
+
const post = (data.post || data.thread || data);
|
|
290
292
|
const replies = (data.items || []);
|
|
293
|
+
const mentionMap = buildMentionMap(postResp);
|
|
291
294
|
const author = post.author?.name ||
|
|
292
295
|
`User ${post.authorId}`;
|
|
293
296
|
const ancestorLines = [];
|
|
294
297
|
if (ancestors.length) {
|
|
295
298
|
ancestors.forEach((a, i) => {
|
|
296
|
-
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a)}`);
|
|
299
|
+
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a, mentionMap)}`);
|
|
297
300
|
});
|
|
298
301
|
}
|
|
299
302
|
const replyLines = [];
|
|
300
303
|
for (const r of replies) {
|
|
301
304
|
const rAuthor = r.author?.name ||
|
|
302
305
|
`User ${r.authorId}`;
|
|
303
|
-
const text = r
|
|
306
|
+
const text = postBodyText(r, mentionMap);
|
|
304
307
|
const body = opts.full || !text || text.length <= 200
|
|
305
308
|
? text
|
|
306
309
|
: text.slice(0, 200) + "…";
|
|
@@ -322,7 +325,7 @@ export function registerSpaceCommand(program) {
|
|
|
322
325
|
? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
|
|
323
326
|
: []),
|
|
324
327
|
"",
|
|
325
|
-
post
|
|
328
|
+
postBodyText(post, mentionMap),
|
|
326
329
|
...(attachmentLines.length
|
|
327
330
|
? ["", `Attachments (${attachmentLines.length}):`, ...attachmentLines]
|
|
328
331
|
: []),
|
|
@@ -363,12 +366,19 @@ export function registerSpaceCommand(program) {
|
|
|
363
366
|
console.log("No posts found.");
|
|
364
367
|
return;
|
|
365
368
|
}
|
|
369
|
+
const mentions = buildMentionMap(resp);
|
|
366
370
|
const lines = [];
|
|
367
371
|
for (const t of items) {
|
|
368
372
|
const author = t.author?.name ||
|
|
369
373
|
`User ${t.authorId}`;
|
|
370
|
-
|
|
371
|
-
|
|
374
|
+
lines.push(`- [${t.id}] "${formatPostLabel(t, mentions)}" by ${author} (${t.replyCount} replies, ${t.createdAt})`);
|
|
375
|
+
for (const line of formatAttachmentLines(t, " ", "📎")) {
|
|
376
|
+
lines.push(line);
|
|
377
|
+
}
|
|
378
|
+
const replies = t.replies || [];
|
|
379
|
+
for (const r of replies) {
|
|
380
|
+
lines.push(formatReplyLine(r, mentions));
|
|
381
|
+
}
|
|
372
382
|
}
|
|
373
383
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
374
384
|
console.log(`Posts (${items.length} items):\n` + lines.join("\n") + footer);
|
package/dist/commands/utils.js
CHANGED
|
@@ -38,6 +38,89 @@ export function formatReactionChips(m) {
|
|
|
38
38
|
.map((r) => `${r.emoji}${r.count}${r.reactedByMe ? "*" : ""}`)
|
|
39
39
|
.join(" ");
|
|
40
40
|
}
|
|
41
|
+
// Build a userId -> name lookup from a list/feed/thread response's `mentions`
|
|
42
|
+
// block. Returns an empty map when absent, so callers can pass it through
|
|
43
|
+
// unconditionally and mentions just fall back to `@<id>`.
|
|
44
|
+
export function buildMentionMap(resp) {
|
|
45
|
+
const map = new Map();
|
|
46
|
+
// `mentions` sits at the top level on raw list/feed responses but under
|
|
47
|
+
// `data` on unwrapped thread/topic payloads — accept either.
|
|
48
|
+
const data = resp?.data;
|
|
49
|
+
const mentions = (resp?.mentions || data?.mentions);
|
|
50
|
+
const users = mentions?.users;
|
|
51
|
+
if (Array.isArray(users)) {
|
|
52
|
+
for (const u of users) {
|
|
53
|
+
if (u && typeof u.id === "number" && typeof u.name === "string") {
|
|
54
|
+
map.set(u.id, u.name);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return map;
|
|
59
|
+
}
|
|
60
|
+
// Flatten a richText node array into a plain-text string for display. Posts
|
|
61
|
+
// commonly carry an empty `content` with the real body living only in
|
|
62
|
+
// `richText`, so list/feed labels go blank without this. Renders text nodes
|
|
63
|
+
// verbatim, user mentions as `@<name>` (resolved via `mentions`, else `@<id>`),
|
|
64
|
+
// and link nodes as their label or URL.
|
|
65
|
+
export function flattenRichText(richText, mentions) {
|
|
66
|
+
if (!Array.isArray(richText))
|
|
67
|
+
return "";
|
|
68
|
+
const parts = [];
|
|
69
|
+
for (const node of richText) {
|
|
70
|
+
if (!node || typeof node !== "object")
|
|
71
|
+
continue;
|
|
72
|
+
const n = node;
|
|
73
|
+
if (typeof n.text === "string" && n.text) {
|
|
74
|
+
parts.push(n.text);
|
|
75
|
+
}
|
|
76
|
+
else if (n.type === "user") {
|
|
77
|
+
const id = n.userId;
|
|
78
|
+
const resolved = n.name ||
|
|
79
|
+
(typeof id === "number" ? mentions?.get(id) : undefined) ||
|
|
80
|
+
"";
|
|
81
|
+
parts.push(resolved ? `@${resolved.replace(/^@/, "")}` : id != null ? `@${id}` : "");
|
|
82
|
+
}
|
|
83
|
+
else if (n.type === "link") {
|
|
84
|
+
parts.push(n.text || n.url || n.href || "");
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return parts.join("");
|
|
88
|
+
}
|
|
89
|
+
// Best available plain-text body for a post or reply: explicit `content` first,
|
|
90
|
+
// then flattened `richText`. Empty string if neither has text.
|
|
91
|
+
export function postBodyText(m, mentions) {
|
|
92
|
+
const content = m.content || "";
|
|
93
|
+
if (content.trim())
|
|
94
|
+
return content;
|
|
95
|
+
return flattenRichText(m.richText, mentions);
|
|
96
|
+
}
|
|
97
|
+
// Single-line display label for a post: its title, else a body snippet, else
|
|
98
|
+
// "(untitled)". Falls through title -> content -> richText so titleless posts
|
|
99
|
+
// (the common case) read sensibly instead of printing "null"/blank. Body
|
|
100
|
+
// snippets are collapsed to one line and truncated; titles are left intact.
|
|
101
|
+
export function formatPostLabel(m, mentions, maxLen = 100) {
|
|
102
|
+
const title = m.title || "";
|
|
103
|
+
if (title.trim())
|
|
104
|
+
return title;
|
|
105
|
+
const body = postBodyText(m, mentions).replace(/\s+/g, " ").trim();
|
|
106
|
+
if (!body)
|
|
107
|
+
return "(untitled)";
|
|
108
|
+
return body.length > maxLen ? body.slice(0, maxLen) + "…" : body;
|
|
109
|
+
}
|
|
110
|
+
// Compact one-line rendering of a reply for nested display under a post (used
|
|
111
|
+
// by `list-posts`). Indented two levels so it reads as a child of the post
|
|
112
|
+
// line; surfaces attachments/artifacts and reactions on the reply.
|
|
113
|
+
export function formatReplyLine(r, mentions, maxLen = 200) {
|
|
114
|
+
const author = r.author?.name ||
|
|
115
|
+
`User ${r.authorId ?? "?"}`;
|
|
116
|
+
const text = postBodyText(r, mentions).replace(/\s+/g, " ").trim();
|
|
117
|
+
const body = text.length > maxLen ? text.slice(0, maxLen) + "…" : text;
|
|
118
|
+
const attach = formatAttachmentSummary(r);
|
|
119
|
+
const chips = formatReactionChips(r);
|
|
120
|
+
return (` - [r:${r.id}] ${author}: ${body} (${r.createdAt})` +
|
|
121
|
+
(attach ? ` ${attach}` : "") +
|
|
122
|
+
(chips ? ` ${chips}` : ""));
|
|
123
|
+
}
|
|
41
124
|
// Classify a wire attachment the way the clients do: declared mimeType first,
|
|
42
125
|
// then the media-url extension (covers rows that predate the mimeType field).
|
|
43
126
|
function attachmentKind(a) {
|
|
@@ -78,9 +161,12 @@ export function formatAttachmentSummary(m) {
|
|
|
78
161
|
}
|
|
79
162
|
return `📎 ${parts.join(", ")}`;
|
|
80
163
|
}
|
|
81
|
-
// One line per attachment with
|
|
82
|
-
//
|
|
83
|
-
|
|
164
|
+
// One line per attachment with a fetchable reference — a `mediaUrl` for files
|
|
165
|
+
// and media, or the `artifactId` for artifacts (retrievable via
|
|
166
|
+
// `gobi artifact get <id>`). Used by `get-post` and nested under posts in
|
|
167
|
+
// `list-posts` so an agent can fetch/describe the data without re-querying.
|
|
168
|
+
// `marker` is the bullet prefix (e.g. "-" or "📎").
|
|
169
|
+
export function formatAttachmentLines(m, indent = " ", marker = "-") {
|
|
84
170
|
const attachments = m.attachments || [];
|
|
85
171
|
if (!Array.isArray(attachments) || !attachments.length)
|
|
86
172
|
return [];
|
|
@@ -90,11 +176,11 @@ export function formatAttachmentLines(m, indent = " ") {
|
|
|
90
176
|
const art = a.artifact;
|
|
91
177
|
const title = art.title ? ` "${art.title}"` : "";
|
|
92
178
|
const status = art.isPublished ? "published" : "unpublished";
|
|
93
|
-
return `${indent}
|
|
179
|
+
return `${indent}${marker} artifact [${art.kind}]${title} (${art.artifactId}, ${status})${art.mediaUrl ? ` — ${art.mediaUrl}` : ""}`;
|
|
94
180
|
}
|
|
95
181
|
const dims = a.width && a.height ? ` ${a.width}×${a.height}` : "";
|
|
96
182
|
const name = a.fileName ? ` "${a.fileName}"` : "";
|
|
97
183
|
const mime = a.mimeType ? ` (${a.mimeType})` : "";
|
|
98
|
-
return `${indent}
|
|
184
|
+
return `${indent}${marker} ${kind}${name}${dims}${mime} — ${a.mediaUrl}`;
|
|
99
185
|
});
|
|
100
186
|
}
|
package/package.json
CHANGED
|
@@ -9,12 +9,12 @@ description: >-
|
|
|
9
9
|
allowed-tools: Bash(gobi:*)
|
|
10
10
|
metadata:
|
|
11
11
|
author: gobi-ai
|
|
12
|
-
version: "2.0.
|
|
12
|
+
version: "2.0.35"
|
|
13
13
|
---
|
|
14
14
|
|
|
15
15
|
# gobi-artifact
|
|
16
16
|
|
|
17
|
-
Gobi artifact commands for versioned, post-attachable creations (v2.0.
|
|
17
|
+
Gobi artifact commands for versioned, post-attachable creations (v2.0.35).
|
|
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.
|
|
11
|
+
version: "2.0.35"
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# gobi-core
|
|
15
15
|
|
|
16
|
-
Core CLI commands for the Gobi collaborative knowledge platform (v2.0.
|
|
16
|
+
Core CLI commands for the Gobi collaborative knowledge platform (v2.0.35).
|
|
17
17
|
|
|
18
18
|
## Prerequisites
|
|
19
19
|
|
|
@@ -10,12 +10,12 @@ description: >-
|
|
|
10
10
|
allowed-tools: Bash(gobi:*)
|
|
11
11
|
metadata:
|
|
12
12
|
author: gobi-ai
|
|
13
|
-
version: "2.0.
|
|
13
|
+
version: "2.0.35"
|
|
14
14
|
---
|
|
15
15
|
|
|
16
16
|
# gobi-media
|
|
17
17
|
|
|
18
|
-
Gobi media generation commands (v2.0.
|
|
18
|
+
Gobi media generation commands (v2.0.35).
|
|
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.
|
|
10
|
+
version: "2.0.35"
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# gobi-sense
|
|
14
14
|
|
|
15
|
-
Gobi sense commands for activity and transcription data (v2.0.
|
|
15
|
+
Gobi sense commands for activity and transcription data (v2.0.35).
|
|
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.
|
|
14
|
+
version: "2.0.35"
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# gobi-space
|
|
18
18
|
|
|
19
|
-
Gobi space, global, and personal-space posts (v2.0.
|
|
19
|
+
Gobi space, global, and personal-space posts (v2.0.35).
|
|
20
20
|
|
|
21
21
|
Requires gobi-cli installed and authenticated. See the **gobi-core** skill for setup.
|
|
22
22
|
|
|
@@ -8,12 +8,12 @@ description: >-
|
|
|
8
8
|
allowed-tools: Bash(gobi:*)
|
|
9
9
|
metadata:
|
|
10
10
|
author: gobi-ai
|
|
11
|
-
version: "2.0.
|
|
11
|
+
version: "2.0.35"
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
# gobi-vault
|
|
15
15
|
|
|
16
|
-
Gobi vault commands for publishing your vault profile and syncing files (v2.0.
|
|
16
|
+
Gobi vault commands for publishing your vault profile and syncing files (v2.0.35).
|
|
17
17
|
|
|
18
18
|
Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
|
|
19
19
|
|