@gobi-ai/cli 1.3.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +3 -4
- package/.claude-plugin/plugin.json +2 -3
- package/README.md +71 -80
- package/commands/space-explore.md +10 -10
- package/commands/space-share.md +13 -7
- package/dist/commands/global.js +205 -70
- package/dist/commands/init.js +5 -5
- package/dist/commands/{notes.js → saved.js} +112 -19
- package/dist/commands/space.js +92 -97
- package/dist/commands/sync.js +2 -56
- package/dist/commands/vault.js +113 -0
- package/dist/main.js +4 -8
- package/package.json +2 -2
- package/skills/gobi-core/SKILL.md +5 -7
- package/skills/gobi-core/references/space.md +18 -19
- package/skills/gobi-draft/SKILL.md +1 -1
- package/skills/gobi-homepage/SKILL.md +16 -16
- package/skills/gobi-saved/SKILL.md +59 -0
- package/skills/gobi-saved/references/saved.md +52 -0
- package/skills/gobi-space/SKILL.md +34 -31
- package/skills/gobi-space/references/global.md +84 -24
- package/skills/gobi-space/references/space.md +45 -57
- package/skills/gobi-vault/SKILL.md +92 -0
- package/skills/{gobi-core/references/sync.md → gobi-vault/references/vault.md} +41 -2
- package/dist/commands/brain.js +0 -141
- package/dist/commands/feed.js +0 -148
- package/skills/gobi-brain/SKILL.md +0 -100
- package/skills/gobi-brain/references/brain.md +0 -66
- package/skills/gobi-feed/SKILL.md +0 -43
- package/skills/gobi-feed/references/feed.md +0 -80
- package/skills/gobi-notes/SKILL.md +0 -52
- package/skills/gobi-notes/references/notes.md +0 -82
package/dist/commands/global.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { readFileSync } from "fs";
|
|
2
|
-
import { apiGet, apiPost } from "../client.js";
|
|
3
|
-
import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
|
|
2
|
+
import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
|
|
3
|
+
import { isJsonMode, jsonOut, resolveVaultSlug, unwrapResp } from "./utils.js";
|
|
4
|
+
import { extractWikiLinks, uploadAttachments } from "../attachments.js";
|
|
5
|
+
import { getValidToken } from "../auth/manager.js";
|
|
4
6
|
function readContent(value) {
|
|
5
7
|
if (value === "-")
|
|
6
8
|
return readFileSync("/dev/stdin", "utf8");
|
|
7
9
|
return value;
|
|
8
10
|
}
|
|
9
|
-
function
|
|
10
|
-
const isReply = m.
|
|
11
|
-
|
|
12
|
-
const
|
|
11
|
+
function formatFeedLine(m) {
|
|
12
|
+
const isReply = m.parentPostId != null ||
|
|
13
|
+
m.type === "post-reply";
|
|
14
|
+
const id = `[${isReply ? "r" : "p"}:${m.id}]`;
|
|
15
|
+
const kind = isReply ? "reply" : "post ";
|
|
13
16
|
const author = m.author?.name ||
|
|
14
17
|
`User ${m.authorId ?? "?"}`;
|
|
15
18
|
let label;
|
|
@@ -26,20 +29,23 @@ function formatMessageLine(m) {
|
|
|
26
29
|
export function registerGlobalCommand(program) {
|
|
27
30
|
const global = program
|
|
28
31
|
.command("global")
|
|
29
|
-
.description("Global
|
|
30
|
-
// ──
|
|
32
|
+
.description("Global commands (posts and replies in the public feed across all vaults).");
|
|
33
|
+
// ── Feed (unified) ──
|
|
31
34
|
global
|
|
32
|
-
.command("
|
|
33
|
-
.description("List the global
|
|
35
|
+
.command("feed")
|
|
36
|
+
.description("List the global public feed (posts and replies, newest first).")
|
|
34
37
|
.option("--limit <number>", "Items per page", "20")
|
|
35
38
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
39
|
+
.option("--following", "Only include posts from authors you follow")
|
|
36
40
|
.action(async (opts) => {
|
|
37
41
|
const params = {
|
|
38
42
|
limit: parseInt(opts.limit, 10),
|
|
39
43
|
};
|
|
40
44
|
if (opts.cursor)
|
|
41
45
|
params.cursor = opts.cursor;
|
|
42
|
-
|
|
46
|
+
if (opts.following)
|
|
47
|
+
params.following = "true";
|
|
48
|
+
const resp = (await apiGet(`/feed`, params));
|
|
43
49
|
if (isJsonMode(global)) {
|
|
44
50
|
jsonOut({
|
|
45
51
|
items: resp.data || [],
|
|
@@ -50,92 +56,185 @@ export function registerGlobalCommand(program) {
|
|
|
50
56
|
const items = (resp.data || []);
|
|
51
57
|
const pagination = (resp.pagination || {});
|
|
52
58
|
if (!items.length) {
|
|
53
|
-
console.log("No
|
|
59
|
+
console.log("No items found.");
|
|
54
60
|
return;
|
|
55
61
|
}
|
|
56
|
-
const lines = items.map(
|
|
62
|
+
const lines = items.map(formatFeedLine);
|
|
57
63
|
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
58
|
-
console.log(`Global
|
|
64
|
+
console.log(`Global feed (${items.length} items, newest first):\n` + lines.join("\n") + footer);
|
|
59
65
|
});
|
|
60
|
-
// ──
|
|
66
|
+
// ── List posts ──
|
|
61
67
|
global
|
|
62
|
-
.command("
|
|
63
|
-
.description("
|
|
64
|
-
.option("--limit <number>", "
|
|
68
|
+
.command("list-posts")
|
|
69
|
+
.description("List posts in the global feed (paginated). Pass --mine to limit to your own posts.")
|
|
70
|
+
.option("--limit <number>", "Items per page", "20")
|
|
65
71
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
66
|
-
.
|
|
72
|
+
.option("--mine", "Only include posts authored by you")
|
|
73
|
+
.option("--vault-slug <vaultSlug>", "Filter by author vault slug")
|
|
74
|
+
.action(async (opts) => {
|
|
67
75
|
const params = {
|
|
68
76
|
limit: parseInt(opts.limit, 10),
|
|
69
77
|
};
|
|
70
78
|
if (opts.cursor)
|
|
71
79
|
params.cursor = opts.cursor;
|
|
72
|
-
|
|
73
|
-
|
|
80
|
+
if (opts.mine)
|
|
81
|
+
params.mine = "true";
|
|
82
|
+
if (opts.vaultSlug)
|
|
83
|
+
params.vaultSlug = opts.vaultSlug;
|
|
84
|
+
const resp = (await apiGet(`/posts`, params));
|
|
85
|
+
if (isJsonMode(global)) {
|
|
86
|
+
jsonOut({
|
|
87
|
+
items: resp.data || [],
|
|
88
|
+
pagination: resp.pagination || {},
|
|
89
|
+
});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const items = (resp.data || []);
|
|
74
93
|
const pagination = (resp.pagination || {});
|
|
94
|
+
if (!items.length) {
|
|
95
|
+
console.log("No posts found.");
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const lines = [];
|
|
99
|
+
for (const t of items) {
|
|
100
|
+
const author = t.author?.name ||
|
|
101
|
+
`User ${t.authorId}`;
|
|
102
|
+
const vaultSlug = t.vault?.vaultSlug ||
|
|
103
|
+
t.authorVault?.vaultSlug ||
|
|
104
|
+
"?";
|
|
105
|
+
lines.push(`- [${t.id}] "${t.title}" by ${author} (vault: ${vaultSlug}, ${t.replyCount ?? 0} replies, ${t.createdAt})`);
|
|
106
|
+
}
|
|
107
|
+
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
108
|
+
console.log(`Posts (${items.length} items):\n` + lines.join("\n") + footer);
|
|
109
|
+
});
|
|
110
|
+
// ── Get post (with ancestors and replies) ──
|
|
111
|
+
global
|
|
112
|
+
.command("get-post <postId>")
|
|
113
|
+
.description("Get a global post with its ancestors and replies (paginated).")
|
|
114
|
+
.option("--limit <number>", "Replies per page", "20")
|
|
115
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
116
|
+
.option("--full", "Show full reply content without truncation")
|
|
117
|
+
.action(async (postId, opts) => {
|
|
118
|
+
const params = {
|
|
119
|
+
limit: parseInt(opts.limit, 10),
|
|
120
|
+
};
|
|
121
|
+
if (opts.cursor)
|
|
122
|
+
params.cursor = opts.cursor;
|
|
123
|
+
const [postResp, ancestorsResp] = await Promise.all([
|
|
124
|
+
apiGet(`/feed/${postId}`, params),
|
|
125
|
+
apiGet(`/feed/${postId}/ancestors`),
|
|
126
|
+
]);
|
|
127
|
+
const data = unwrapResp(postResp);
|
|
128
|
+
const pagination = (postResp.pagination || {});
|
|
129
|
+
const mentions = (postResp.mentions || {});
|
|
130
|
+
const ancestorsData = unwrapResp(ancestorsResp);
|
|
131
|
+
const ancestors = (ancestorsData.ancestors || []);
|
|
75
132
|
if (isJsonMode(global)) {
|
|
76
|
-
jsonOut({ ...data, pagination });
|
|
133
|
+
jsonOut({ ...data, ancestors, pagination, mentions });
|
|
77
134
|
return;
|
|
78
135
|
}
|
|
79
|
-
const
|
|
80
|
-
const replies = (data.
|
|
81
|
-
const author =
|
|
82
|
-
`User ${
|
|
136
|
+
const post = (data.update || data.post || data);
|
|
137
|
+
const replies = (data.replies || []);
|
|
138
|
+
const author = post.author?.name ||
|
|
139
|
+
`User ${post.authorId}`;
|
|
140
|
+
const vault = post.vault?.vaultSlug ||
|
|
141
|
+
post.authorVault?.vaultSlug ||
|
|
142
|
+
"?";
|
|
143
|
+
const ancestorLines = [];
|
|
144
|
+
if (ancestors.length) {
|
|
145
|
+
ancestors.forEach((a, i) => {
|
|
146
|
+
ancestorLines.push(` ${i + 1}. ${formatFeedLine(a)}`);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
83
149
|
const replyLines = [];
|
|
84
150
|
for (const r of replies) {
|
|
85
151
|
const rAuthor = r.author?.name ||
|
|
86
152
|
`User ${r.authorId}`;
|
|
87
153
|
const text = r.content || "";
|
|
88
|
-
const truncated = text.length
|
|
154
|
+
const truncated = opts.full || text.length <= 200 ? text : text.slice(0, 200) + "…";
|
|
89
155
|
replyLines.push(` - ${rAuthor}: ${truncated} (${r.createdAt})`);
|
|
90
156
|
}
|
|
91
|
-
const
|
|
92
|
-
const heading =
|
|
93
|
-
? `Reply [r:${
|
|
94
|
-
: `
|
|
157
|
+
const isReplyPost = post.parentPostId != null;
|
|
158
|
+
const heading = isReplyPost
|
|
159
|
+
? `Reply [r:${post.id}]`
|
|
160
|
+
: `Post: ${post.title || "(no title)"}`;
|
|
95
161
|
const output = [
|
|
96
162
|
heading,
|
|
97
|
-
`By: ${author} on ${
|
|
163
|
+
`By: ${author} (vault: ${vault}) on ${post.createdAt}`,
|
|
164
|
+
...(ancestorLines.length
|
|
165
|
+
? ["", `Ancestors (${ancestors.length} items, root first):`, ...ancestorLines]
|
|
166
|
+
: []),
|
|
98
167
|
"",
|
|
99
|
-
|
|
168
|
+
post.content || "",
|
|
100
169
|
"",
|
|
101
170
|
`Replies (${replies.length} items):`,
|
|
102
171
|
...replyLines,
|
|
103
|
-
...(pagination.hasMore
|
|
172
|
+
...(pagination.hasMore
|
|
173
|
+
? [` Next cursor: ${pagination.nextCursor}`]
|
|
174
|
+
: []),
|
|
104
175
|
].join("\n");
|
|
105
176
|
console.log(output);
|
|
106
177
|
});
|
|
107
|
-
// ──
|
|
178
|
+
// ── Create post ──
|
|
108
179
|
global
|
|
109
|
-
.command("
|
|
110
|
-
.description("
|
|
111
|
-
.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
180
|
+
.command("create-post")
|
|
181
|
+
.description("Create a post in the global feed (publishes from your vault).")
|
|
182
|
+
.option("--title <title>", "Title of the post")
|
|
183
|
+
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
184
|
+
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
185
|
+
.option("--vault-slug <vaultSlug>", "Author vault slug (overrides .gobi/settings.yaml)")
|
|
186
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting")
|
|
187
|
+
.action(async (opts) => {
|
|
188
|
+
if (!opts.content && !opts.richText) {
|
|
189
|
+
throw new Error("Provide either --content or --rich-text.");
|
|
190
|
+
}
|
|
191
|
+
if (opts.content && opts.richText) {
|
|
192
|
+
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
118
193
|
}
|
|
119
|
-
|
|
120
|
-
|
|
194
|
+
const vaultSlug = resolveVaultSlug(opts);
|
|
195
|
+
const body = {};
|
|
196
|
+
if (opts.title != null)
|
|
197
|
+
body.title = opts.title;
|
|
198
|
+
if (opts.content != null) {
|
|
199
|
+
const content = readContent(opts.content);
|
|
200
|
+
if (opts.autoAttachments) {
|
|
201
|
+
const token = await getValidToken();
|
|
202
|
+
const links = extractWikiLinks(content);
|
|
203
|
+
await uploadAttachments(vaultSlug, links, token, { addToSyncfiles: true });
|
|
204
|
+
}
|
|
205
|
+
body.content = content;
|
|
206
|
+
}
|
|
207
|
+
if (opts.richText != null) {
|
|
208
|
+
let parsed;
|
|
209
|
+
try {
|
|
210
|
+
parsed = JSON.parse(opts.richText);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
throw new Error("Invalid --rich-text JSON.");
|
|
214
|
+
}
|
|
215
|
+
body.richText = parsed;
|
|
216
|
+
}
|
|
217
|
+
const resp = (await apiPost(`/posts/vault/${vaultSlug}`, body));
|
|
218
|
+
const post = unwrapResp(resp);
|
|
219
|
+
if (isJsonMode(global)) {
|
|
220
|
+
jsonOut(post);
|
|
121
221
|
return;
|
|
122
222
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
console.log(`Ancestors (${ancestors.length} items, root first):\n` + lines.join("\n"));
|
|
223
|
+
console.log(`Post created!\n` +
|
|
224
|
+
` ID: ${post.id}\n` +
|
|
225
|
+
(post.title ? ` Title: ${post.title}\n` : "") +
|
|
226
|
+
` Created: ${post.createdAt}`);
|
|
128
227
|
});
|
|
129
|
-
// ──
|
|
228
|
+
// ── Edit post ──
|
|
130
229
|
global
|
|
131
|
-
.command("
|
|
132
|
-
.description("
|
|
133
|
-
.option("--title <title>", "
|
|
134
|
-
.option("--content <content>", "
|
|
230
|
+
.command("edit-post <postId>")
|
|
231
|
+
.description("Edit a post you authored in the global feed.")
|
|
232
|
+
.option("--title <title>", "New title")
|
|
233
|
+
.option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
|
|
135
234
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
136
|
-
.action(async (opts) => {
|
|
137
|
-
if (
|
|
138
|
-
throw new Error("Provide
|
|
235
|
+
.action(async (postId, opts) => {
|
|
236
|
+
if (opts.title == null && opts.content == null && opts.richText == null) {
|
|
237
|
+
throw new Error("Provide at least --title, --content, or --rich-text to update.");
|
|
139
238
|
}
|
|
140
239
|
if (opts.content && opts.richText) {
|
|
141
240
|
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
@@ -155,24 +254,33 @@ export function registerGlobalCommand(program) {
|
|
|
155
254
|
}
|
|
156
255
|
body.richText = parsed;
|
|
157
256
|
}
|
|
158
|
-
const resp = (await
|
|
159
|
-
const
|
|
257
|
+
const resp = (await apiPatch(`/posts/${postId}`, body));
|
|
258
|
+
const post = unwrapResp(resp);
|
|
160
259
|
if (isJsonMode(global)) {
|
|
161
|
-
jsonOut(
|
|
260
|
+
jsonOut(post);
|
|
162
261
|
return;
|
|
163
262
|
}
|
|
164
|
-
console.log(`
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
263
|
+
console.log(`Post edited!\n ID: ${post.id}\n Edited: ${post.editedAt ?? post.updatedAt}`);
|
|
264
|
+
});
|
|
265
|
+
// ── Delete post ──
|
|
266
|
+
global
|
|
267
|
+
.command("delete-post <postId>")
|
|
268
|
+
.description("Delete a post you authored in the global feed.")
|
|
269
|
+
.action(async (postId) => {
|
|
270
|
+
await apiDelete(`/posts/${postId}`);
|
|
271
|
+
if (isJsonMode(global)) {
|
|
272
|
+
jsonOut({ id: postId });
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
console.log(`Post ${postId} deleted.`);
|
|
168
276
|
});
|
|
169
277
|
// ── Reply ──
|
|
170
278
|
global
|
|
171
|
-
.command("reply <
|
|
172
|
-
.description("Reply to a
|
|
279
|
+
.command("create-reply <postId>")
|
|
280
|
+
.description("Reply to a post in the global feed.")
|
|
173
281
|
.option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
|
|
174
282
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
175
|
-
.action(async (
|
|
283
|
+
.action(async (postId, opts) => {
|
|
176
284
|
if (!opts.content && !opts.richText) {
|
|
177
285
|
throw new Error("Provide either --content or --rich-text.");
|
|
178
286
|
}
|
|
@@ -192,7 +300,7 @@ export function registerGlobalCommand(program) {
|
|
|
192
300
|
}
|
|
193
301
|
body.richText = parsed;
|
|
194
302
|
}
|
|
195
|
-
const resp = (await apiPost(`/
|
|
303
|
+
const resp = (await apiPost(`/posts/${postId}/replies`, body));
|
|
196
304
|
const reply = unwrapResp(resp);
|
|
197
305
|
if (isJsonMode(global)) {
|
|
198
306
|
jsonOut(reply);
|
|
@@ -200,4 +308,31 @@ export function registerGlobalCommand(program) {
|
|
|
200
308
|
}
|
|
201
309
|
console.log(`Reply created!\n ID: ${reply.id}\n Created: ${reply.createdAt}`);
|
|
202
310
|
});
|
|
311
|
+
global
|
|
312
|
+
.command("edit-reply <replyId>")
|
|
313
|
+
.description("Edit a reply you authored in the global feed.")
|
|
314
|
+
.requiredOption("--content <content>", "New reply content (markdown supported, use \"-\" for stdin)")
|
|
315
|
+
.action(async (replyId, opts) => {
|
|
316
|
+
const content = readContent(opts.content);
|
|
317
|
+
const resp = (await apiPatch(`/posts/replies/${replyId}`, {
|
|
318
|
+
content,
|
|
319
|
+
}));
|
|
320
|
+
const reply = unwrapResp(resp);
|
|
321
|
+
if (isJsonMode(global)) {
|
|
322
|
+
jsonOut(reply);
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
console.log(`Reply edited!\n ID: ${reply.id}\n Edited: ${reply.editedAt ?? reply.updatedAt}`);
|
|
326
|
+
});
|
|
327
|
+
global
|
|
328
|
+
.command("delete-reply <replyId>")
|
|
329
|
+
.description("Delete a reply you authored in the global feed.")
|
|
330
|
+
.action(async (replyId) => {
|
|
331
|
+
await apiDelete(`/posts/replies/${replyId}`);
|
|
332
|
+
if (isJsonMode(global)) {
|
|
333
|
+
jsonOut({ replyId });
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
console.log(`Reply ${replyId} deleted.`);
|
|
337
|
+
});
|
|
203
338
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -205,11 +205,11 @@ export async function runInitFlow() {
|
|
|
205
205
|
writeVaultSetting(vaultId);
|
|
206
206
|
console.log(`Vault set to "${vaultName}" (${vaultId})`);
|
|
207
207
|
console.log(`Updated ${SETTINGS_DIR}/${SETTINGS_FILE}`);
|
|
208
|
-
// Create default
|
|
209
|
-
const
|
|
210
|
-
if (!existsSync(
|
|
211
|
-
writeFileSync(
|
|
212
|
-
console.log("Created
|
|
208
|
+
// Create default PUBLISH.md if it doesn't exist
|
|
209
|
+
const publishPath = join(process.cwd(), "PUBLISH.md");
|
|
210
|
+
if (!existsSync(publishPath)) {
|
|
211
|
+
writeFileSync(publishPath, `---\ntitle: ${vaultName}\ntags: []\ndescription:\nthumbnail:\nprompt:\n---\n`, "utf-8");
|
|
212
|
+
console.log("Created PUBLISH.md");
|
|
213
213
|
}
|
|
214
214
|
}
|
|
215
215
|
export function registerInitCommand(program) {
|
|
@@ -18,12 +18,18 @@ function formatNoteLine(note) {
|
|
|
18
18
|
const agent = note.agentId != null ? `, agent: ${note.agentId}` : "";
|
|
19
19
|
return `- [${note.id}] "${snippet.replace(/\n/g, " ")}" (${note.eventDate}${agent}${attachStr}, updated ${note.updatedAt})`;
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
21
|
+
function formatSavedPostLine(item) {
|
|
22
|
+
const author = item.author?.name;
|
|
23
|
+
const title = item.title || item.content || "(no title)";
|
|
24
|
+
const snippet = title.length > 80 ? title.slice(0, 80) + "…" : title;
|
|
25
|
+
const space = item.spaceSlug ? `, space: ${item.spaceSlug}` : "";
|
|
26
|
+
return `- [${item.postId}] "${snippet.replace(/\n/g, " ")}" by ${author ?? "?"}${space} (saved ${item.savedAt})`;
|
|
27
|
+
}
|
|
28
|
+
function registerNoteCommands(saved) {
|
|
29
|
+
const note = saved
|
|
30
|
+
.command("note")
|
|
31
|
+
.description("Personal saved notes (create, list, get, edit, delete).");
|
|
32
|
+
note
|
|
27
33
|
.command("list")
|
|
28
34
|
.description("List your notes. Without --date, returns recent notes via cursor pagination. With --date, returns all notes for that day.")
|
|
29
35
|
.option("--date <date>", "Filter to a single day (YYYY-MM-DD)")
|
|
@@ -42,7 +48,7 @@ export function registerNotesCommand(program) {
|
|
|
42
48
|
const resp = (await apiGet(`/app/notes`, params));
|
|
43
49
|
const items = (resp.data || []);
|
|
44
50
|
const pagination = (resp.pagination || {});
|
|
45
|
-
if (isJsonMode(
|
|
51
|
+
if (isJsonMode(saved)) {
|
|
46
52
|
jsonOut({ items, pagination });
|
|
47
53
|
return;
|
|
48
54
|
}
|
|
@@ -56,14 +62,13 @@ export function registerNotesCommand(program) {
|
|
|
56
62
|
: "";
|
|
57
63
|
console.log(`Notes (${items.length} items):\n` + lines.join("\n") + footer);
|
|
58
64
|
});
|
|
59
|
-
|
|
60
|
-
notes
|
|
65
|
+
note
|
|
61
66
|
.command("get <noteId>")
|
|
62
67
|
.description("Get a single note by id.")
|
|
63
68
|
.action(async (noteId) => {
|
|
64
69
|
const resp = (await apiGet(`/app/notes/${noteId}`));
|
|
65
70
|
const note = unwrapResp(resp);
|
|
66
|
-
if (isJsonMode(
|
|
71
|
+
if (isJsonMode(saved)) {
|
|
67
72
|
jsonOut(note);
|
|
68
73
|
return;
|
|
69
74
|
}
|
|
@@ -86,8 +91,7 @@ export function registerNotesCommand(program) {
|
|
|
86
91
|
.trimEnd();
|
|
87
92
|
console.log(output);
|
|
88
93
|
});
|
|
89
|
-
|
|
90
|
-
notes
|
|
94
|
+
note
|
|
91
95
|
.command("create")
|
|
92
96
|
.description("Create a note. Provide --content (use '-' for stdin) and/or attachments.")
|
|
93
97
|
.option("--content <content>", 'Note content (markdown supported, use "-" for stdin)')
|
|
@@ -106,14 +110,13 @@ export function registerNotesCommand(program) {
|
|
|
106
110
|
body.agentId = parseInt(opts.agentId, 10);
|
|
107
111
|
const resp = (await apiPost(`/app/notes`, body));
|
|
108
112
|
const note = unwrapResp(resp);
|
|
109
|
-
if (isJsonMode(
|
|
113
|
+
if (isJsonMode(saved)) {
|
|
110
114
|
jsonOut(note);
|
|
111
115
|
return;
|
|
112
116
|
}
|
|
113
117
|
console.log(`Note created!\n ID: ${note.id}\n Date: ${note.eventDate}\n Created: ${note.createdAt}`);
|
|
114
118
|
});
|
|
115
|
-
|
|
116
|
-
notes
|
|
119
|
+
note
|
|
117
120
|
.command("edit <noteId>")
|
|
118
121
|
.description("Edit a note. Provide --content and/or --agent-id.")
|
|
119
122
|
.option("--content <content>", 'New note content (markdown supported, use "-" for stdin)')
|
|
@@ -132,22 +135,112 @@ export function registerNotesCommand(program) {
|
|
|
132
135
|
}
|
|
133
136
|
const resp = (await apiPatch(`/app/notes/${noteId}`, body));
|
|
134
137
|
const note = unwrapResp(resp);
|
|
135
|
-
if (isJsonMode(
|
|
138
|
+
if (isJsonMode(saved)) {
|
|
136
139
|
jsonOut(note);
|
|
137
140
|
return;
|
|
138
141
|
}
|
|
139
142
|
console.log(`Note edited!\n ID: ${note.id}\n Updated: ${note.updatedAt}`);
|
|
140
143
|
});
|
|
141
|
-
|
|
142
|
-
notes
|
|
144
|
+
note
|
|
143
145
|
.command("delete <noteId>")
|
|
144
146
|
.description("Delete a note you authored.")
|
|
145
147
|
.action(async (noteId) => {
|
|
146
148
|
await apiDelete(`/app/notes/${noteId}`);
|
|
147
|
-
if (isJsonMode(
|
|
149
|
+
if (isJsonMode(saved)) {
|
|
148
150
|
jsonOut({ id: noteId });
|
|
149
151
|
return;
|
|
150
152
|
}
|
|
151
153
|
console.log(`Note ${noteId} deleted.`);
|
|
152
154
|
});
|
|
153
155
|
}
|
|
156
|
+
function registerPostCommands(saved) {
|
|
157
|
+
const post = saved
|
|
158
|
+
.command("post")
|
|
159
|
+
.description("Saved posts (snapshots of posts and replies you bookmark).");
|
|
160
|
+
post
|
|
161
|
+
.command("list")
|
|
162
|
+
.description("List posts you have saved.")
|
|
163
|
+
.option("--type <type>", "Filter by type: all|article|space-post", "all")
|
|
164
|
+
.option("--limit <number>", "Items per page (1-50)", "20")
|
|
165
|
+
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
166
|
+
.action(async (opts) => {
|
|
167
|
+
const params = {
|
|
168
|
+
type: opts.type,
|
|
169
|
+
limit: parseInt(opts.limit, 10),
|
|
170
|
+
};
|
|
171
|
+
if (opts.cursor)
|
|
172
|
+
params.cursor = opts.cursor;
|
|
173
|
+
const resp = (await apiGet(`/reactions/me/saved`, params));
|
|
174
|
+
const items = (resp.data || []);
|
|
175
|
+
const pagination = (resp.pagination || {});
|
|
176
|
+
if (isJsonMode(saved)) {
|
|
177
|
+
jsonOut({ items, pagination });
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!items.length) {
|
|
181
|
+
console.log("No saved posts found.");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const lines = items.map(formatSavedPostLine);
|
|
185
|
+
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
186
|
+
console.log(`Saved posts (${items.length} items):\n` + lines.join("\n") + footer);
|
|
187
|
+
});
|
|
188
|
+
post
|
|
189
|
+
.command("get <postId>")
|
|
190
|
+
.description("Get a saved post snapshot by post id.")
|
|
191
|
+
.action(async (postId) => {
|
|
192
|
+
const resp = (await apiGet(`/feed/${postId}`));
|
|
193
|
+
const data = unwrapResp(resp);
|
|
194
|
+
if (isJsonMode(saved)) {
|
|
195
|
+
jsonOut(data);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
const post = (data.update || data.post || data);
|
|
199
|
+
const author = post.author?.name ||
|
|
200
|
+
`User ${post.authorId}`;
|
|
201
|
+
const title = post.title || "(no title)";
|
|
202
|
+
console.log([
|
|
203
|
+
`Saved post [${post.id}]: ${title}`,
|
|
204
|
+
`By: ${author} on ${post.createdAt}`,
|
|
205
|
+
"",
|
|
206
|
+
post.content || "",
|
|
207
|
+
].join("\n"));
|
|
208
|
+
});
|
|
209
|
+
post
|
|
210
|
+
.command("create")
|
|
211
|
+
.description("Save a post or reply. Records a snapshot in your saved-posts collection.")
|
|
212
|
+
.requiredOption("--source <id>", "Source post or reply id to save (numeric)")
|
|
213
|
+
.action(async (opts) => {
|
|
214
|
+
const sourceId = parseInt(opts.source, 10);
|
|
215
|
+
if (!Number.isFinite(sourceId)) {
|
|
216
|
+
throw new Error("--source must be a numeric post or reply id.");
|
|
217
|
+
}
|
|
218
|
+
const resp = (await apiPost(`/reactions/posts/${sourceId}/save`, {
|
|
219
|
+
vaultIds: [],
|
|
220
|
+
}));
|
|
221
|
+
const data = unwrapResp(resp);
|
|
222
|
+
if (isJsonMode(saved)) {
|
|
223
|
+
jsonOut({ postId: sourceId, ...data });
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
console.log(`Saved post ${sourceId}.`);
|
|
227
|
+
});
|
|
228
|
+
post
|
|
229
|
+
.command("delete <postId>")
|
|
230
|
+
.description("Remove a post from your saved-posts collection.")
|
|
231
|
+
.action(async (postId) => {
|
|
232
|
+
await apiDelete(`/reactions/posts/${postId}/save`);
|
|
233
|
+
if (isJsonMode(saved)) {
|
|
234
|
+
jsonOut({ postId });
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
console.log(`Removed post ${postId} from saved.`);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
export function registerSavedCommand(program) {
|
|
241
|
+
const saved = program
|
|
242
|
+
.command("saved")
|
|
243
|
+
.description("Saved-knowledge commands (notes and posts).");
|
|
244
|
+
registerNoteCommands(saved);
|
|
245
|
+
registerPostCommands(saved);
|
|
246
|
+
}
|