@gobi-ai/cli 2.0.20 → 2.0.22
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 +1 -2
- package/README.md +3 -39
- package/dist/commands/personal.js +470 -0
- package/dist/main.js +2 -4
- package/package.json +1 -1
- package/skills/gobi-core/SKILL.md +7 -15
- package/skills/gobi-draft/SKILL.md +0 -2
- package/dist/commands/saved.js +0 -261
- package/dist/commands/sessions.js +0 -124
- package/skills/gobi-core/references/session.md +0 -55
- package/skills/gobi-saved/SKILL.md +0 -70
- package/skills/gobi-saved/references/saved.md +0 -136
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
name: gobi-core
|
|
3
3
|
description: >-
|
|
4
4
|
Core Gobi CLI: authentication (login/logout/status), space selection (gobi
|
|
5
|
-
space warp/list), CLI updates (gobi update)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
file sync is also in gobi-vault.
|
|
5
|
+
space warp/list), and CLI updates (gobi update). Use when the user needs to
|
|
6
|
+
authenticate or update the CLI. Vault setup is in the gobi-vault skill; file
|
|
7
|
+
sync is also in gobi-vault.
|
|
9
8
|
allowed-tools: Bash(gobi:*)
|
|
10
9
|
metadata:
|
|
11
10
|
author: gobi-ai
|
|
@@ -41,7 +40,7 @@ brew tap gobi-ai/tap && brew install gobi
|
|
|
41
40
|
- **Vault**: A filetree-backed knowledge home. A local directory becomes a vault when it contains `.gobi/settings.yaml` with a `vaultSlug`. Each vault has a slug (e.g. `brave-path-zr962w`); public profile is configured by a `PUBLISH.md` document at the vault root and pushed via `gobi vault publish`.
|
|
42
41
|
- **Personal Post**: A post on the author's profile that surfaces in the public global feed. Same `Post` data model as a Space Post — only the scope differs.
|
|
43
42
|
- **Space Post**: A post inside a community space.
|
|
44
|
-
- **Space**: A shared community knowledge area. A user can be a member of one or more spaces; each space contains posts, replies,
|
|
43
|
+
- **Space**: A shared community knowledge area. A user can be a member of one or more spaces; each space contains posts, replies, and connected vaults.
|
|
45
44
|
- **Draft**: A unit of standing guidance authored by an agent during chat. Each draft carries 0–3 AI-suggested actions the user picks from. The top 5 pending drafts feed the agent's system prompt every turn.
|
|
46
45
|
|
|
47
46
|
## Setup steps (run only what you need)
|
|
@@ -73,7 +72,7 @@ gobi auth status
|
|
|
73
72
|
|
|
74
73
|
| Command family | Needs vault in `.gobi`? | Needs space in `.gobi`? | Per-call override |
|
|
75
74
|
|----------------|------------------------|------------------------|-------------------|
|
|
76
|
-
| `auth …`, `update`, `
|
|
75
|
+
| `auth …`, `update`, `draft …`, `media …`, `sense …` | no | no | – |
|
|
77
76
|
| `vault publish` / `unpublish` / `sync` | **yes** | no | none — must run `gobi vault init` first |
|
|
78
77
|
| `vault init` | no (it sets it up) | no | – |
|
|
79
78
|
| `space list` / `warp [slug]` / `get [slug]` | no | no | – |
|
|
@@ -93,7 +92,7 @@ When a command needs vault or space and neither `.gobi` nor an override flag pro
|
|
|
93
92
|
For programmatic/agent usage, always pass `--json` as a **global** option (before the subcommand) to get structured JSON output:
|
|
94
93
|
|
|
95
94
|
```bash
|
|
96
|
-
gobi --json
|
|
95
|
+
gobi --json space list
|
|
97
96
|
```
|
|
98
97
|
|
|
99
98
|
JSON responses have the shape `{ "success": true, "data": ... }` on success or `{ "success": false, "error": "..." }` on failure. Pagination metadata (`pagination: { hasMore, nextCursor }`) ships alongside `data` on list endpoints.
|
|
@@ -106,26 +105,19 @@ JSON responses have the shape `{ "success": true, "data": ... }` on success or `
|
|
|
106
105
|
- `gobi auth logout` — Log out of Gobi and remove stored credentials.
|
|
107
106
|
- `gobi space list` — List spaces you are a member of.
|
|
108
107
|
- `gobi space warp` — Select the active space. Pass a slug to warp directly, or omit for interactive selection.
|
|
109
|
-
- `gobi session` — Session commands (get, list, reply).
|
|
110
|
-
- `gobi session get` — Get a session and its messages (paginated).
|
|
111
|
-
- `gobi session list` — List all sessions you are part of, sorted by most recent activity.
|
|
112
|
-
- `gobi session create-reply` — Send a human reply to a session you are a member of.
|
|
113
108
|
- `gobi update` — Update gobi-cli to the latest version.
|
|
114
109
|
|
|
115
110
|
> Vault setup (`gobi vault init`) and file sync (`gobi vault sync`) live in the **gobi-vault** skill.
|
|
116
111
|
|
|
117
112
|
## Confirm before mutating
|
|
118
113
|
|
|
119
|
-
`gobi session create-reply` posts a human-attributed message into a chat session — the message becomes part of the user's permanent chat history and triggers the agent to respond. Before running it, confirm with the user — show the exact session id and the message text. This applies even when running autonomously.
|
|
120
|
-
|
|
121
114
|
`auth login` / `auth logout` are explicit user-driven commands; they prompt the user themselves and don't need an extra confirmation layer. `update` upgrades the CLI binary — fine to run without extra confirmation.
|
|
122
115
|
|
|
123
|
-
Read-only commands (`auth status`, `
|
|
116
|
+
Read-only commands (`auth status`, `space list`) run without confirmation.
|
|
124
117
|
|
|
125
118
|
## Reference Documentation
|
|
126
119
|
|
|
127
120
|
- [gobi auth](references/auth.md)
|
|
128
|
-
- [gobi session](references/session.md)
|
|
129
121
|
- [gobi update](references/update.md)
|
|
130
122
|
- [gobi space (list/warp)](references/space.md)
|
|
131
123
|
|
|
@@ -80,13 +80,11 @@ When the user picks an action via `gobi draft action <id> <index>`, the response
|
|
|
80
80
|
When the user picks an action like "Post to Global Feed" / "Post to <space>" / "Save as note" and your next turn creates the post or note, pass `--draft-id <draftId>` to the relevant create command. `--draft-id` is the **sole** source of `title` and `content` — the CLI fetches the draft and uses its title and content directly, so `--title` / `--content` / `--rich-text` are not allowed alongside it on `create-post`, and `--content` is not allowed alongside it on `create-note`. If you want to change the wording, `gobi draft revise` first, then create.
|
|
81
81
|
|
|
82
82
|
- `gobi space create-post --draft-id` / `gobi global create-post --draft-id` — uses draft's title and content as the post's `title` and `content`. The draft's `vaultSlug` (when set) seeds `--vault-slug` if not given explicitly. `--auto-attachments`, `--space-slug`, and an explicit `--vault-slug` override remain allowed. Records `{ postId, spaceSlug? }` on `draft.metadata` for an "Open post" button.
|
|
83
|
-
- `gobi saved create-note --draft-id` — fetches the draft and folds it into the note as `# <title>\n\n<content>` (or just content when title is empty). Records `{ noteId }` on `draft.metadata` for an "Open note" button.
|
|
84
83
|
|
|
85
84
|
```bash
|
|
86
85
|
gobi --json global create-post --draft-id <draftId>
|
|
87
86
|
gobi --json space create-post --space-slug <slug> --draft-id <draftId>
|
|
88
87
|
gobi --json global create-post --draft-id <draftId> --auto-attachments
|
|
89
|
-
gobi --json saved create-note --draft-id <draftId>
|
|
90
88
|
```
|
|
91
89
|
|
|
92
90
|
## Available Commands
|
package/dist/commands/saved.js
DELETED
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { apiGet, apiPost, apiPatch, apiDelete } from "../client.js";
|
|
2
|
-
import { fetchDraftSummary, isJsonMode, jsonOut, readStdin, unwrapResp, } from "./utils.js";
|
|
3
|
-
function defaultTimezone() {
|
|
4
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
5
|
-
}
|
|
6
|
-
function formatNoteLine(note) {
|
|
7
|
-
const content = note.content ?? "";
|
|
8
|
-
const snippet = content
|
|
9
|
-
? content.length > 80
|
|
10
|
-
? content.slice(0, 80) + "…"
|
|
11
|
-
: content
|
|
12
|
-
: "(no content)";
|
|
13
|
-
const attachments = (note.attachments || []);
|
|
14
|
-
const attachStr = attachments.length
|
|
15
|
-
? `, ${attachments.length} ${attachments.length === 1 ? "attachment" : "attachments"}`
|
|
16
|
-
: "";
|
|
17
|
-
const agent = note.agentId != null ? `, agent: ${note.agentId}` : "";
|
|
18
|
-
return `- [${note.id}] "${snippet.replace(/\n/g, " ")}" (${note.eventDate}${agent}${attachStr}, updated ${note.updatedAt})`;
|
|
19
|
-
}
|
|
20
|
-
function formatSavedPostLine(item) {
|
|
21
|
-
const author = item.author?.name;
|
|
22
|
-
const title = item.title || item.content || "(no title)";
|
|
23
|
-
const snippet = title.length > 80 ? title.slice(0, 80) + "…" : title;
|
|
24
|
-
const space = item.spaceSlug ? `, space: ${item.spaceSlug}` : "";
|
|
25
|
-
return `- [${item.postId}] "${snippet.replace(/\n/g, " ")}" by ${author ?? "?"}${space} (saved ${item.savedAt})`;
|
|
26
|
-
}
|
|
27
|
-
export function registerSavedCommand(program) {
|
|
28
|
-
const saved = program
|
|
29
|
-
.command("saved")
|
|
30
|
-
.description("Saved-knowledge commands (notes and bookmarked posts).");
|
|
31
|
-
// ── Notes ──
|
|
32
|
-
saved
|
|
33
|
-
.command("list-notes")
|
|
34
|
-
.description("List your notes. Without --date, returns recent notes via cursor pagination. With --date, returns all notes for that day.")
|
|
35
|
-
.option("--date <date>", "Filter to a single day (YYYY-MM-DD)")
|
|
36
|
-
.option("--timezone <tz>", "IANA timezone name (default: system timezone)")
|
|
37
|
-
.option("--limit <number>", "Items per page", "20")
|
|
38
|
-
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
39
|
-
.action(async (opts) => {
|
|
40
|
-
const params = {
|
|
41
|
-
timezone: opts.timezone || defaultTimezone(),
|
|
42
|
-
limit: parseInt(opts.limit, 10),
|
|
43
|
-
};
|
|
44
|
-
if (opts.date)
|
|
45
|
-
params.date = opts.date;
|
|
46
|
-
if (opts.cursor)
|
|
47
|
-
params.cursor = opts.cursor;
|
|
48
|
-
const resp = (await apiGet(`/app/notes`, params));
|
|
49
|
-
const items = (resp.data || []);
|
|
50
|
-
const pagination = (resp.pagination || {});
|
|
51
|
-
if (isJsonMode(saved)) {
|
|
52
|
-
jsonOut({ items, pagination });
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
if (!items.length) {
|
|
56
|
-
console.log("No notes found.");
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
const lines = items.map(formatNoteLine);
|
|
60
|
-
const footer = pagination.hasMore
|
|
61
|
-
? `\n Next cursor: ${pagination.nextCursor}`
|
|
62
|
-
: "";
|
|
63
|
-
console.log(`Notes (${items.length} items):\n` + lines.join("\n") + footer);
|
|
64
|
-
});
|
|
65
|
-
saved
|
|
66
|
-
.command("get-note <noteId>")
|
|
67
|
-
.description("Get a single note by id.")
|
|
68
|
-
.action(async (noteId) => {
|
|
69
|
-
const resp = (await apiGet(`/app/notes/${noteId}`));
|
|
70
|
-
const note = unwrapResp(resp);
|
|
71
|
-
if (isJsonMode(saved)) {
|
|
72
|
-
jsonOut(note);
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const attachments = (note.attachments || []);
|
|
76
|
-
const attachLines = attachments.map((a) => ` - [${a.id}] ${a.mediaUrl} (position ${a.position})`);
|
|
77
|
-
const agentLine = note.agentId != null ? `Agent: ${note.agentId}\n` : "";
|
|
78
|
-
const output = [
|
|
79
|
-
`Note: ${note.id}`,
|
|
80
|
-
`Date: ${note.eventDate}`,
|
|
81
|
-
`Created: ${note.createdAt}`,
|
|
82
|
-
`Updated: ${note.updatedAt}`,
|
|
83
|
-
agentLine.trimEnd(),
|
|
84
|
-
"",
|
|
85
|
-
note.content || "(no content)",
|
|
86
|
-
...(attachLines.length ? ["", `Attachments (${attachLines.length}):`, ...attachLines] : []),
|
|
87
|
-
]
|
|
88
|
-
.filter((line) => line !== "")
|
|
89
|
-
.concat([""])
|
|
90
|
-
.join("\n")
|
|
91
|
-
.trimEnd();
|
|
92
|
-
console.log(output);
|
|
93
|
-
});
|
|
94
|
-
saved
|
|
95
|
-
.command("create-note")
|
|
96
|
-
.description("Create a note. Provide --content (use '-' for stdin), or --draft-id to source content from a draft.")
|
|
97
|
-
.option("--content <content>", 'Note content (markdown supported, use "-" for stdin)')
|
|
98
|
-
.option("--timezone <tz>", "IANA timezone name (default: system timezone)")
|
|
99
|
-
.option("--agent-id <number>", "Optional agent id to associate with the note")
|
|
100
|
-
.option("--draft-id <draftId>", "Use this draft as the source of content (mutually exclusive with --content). The draft's title is prepended as an H1 heading. On success, links the note back by recording noteId on draft.metadata so the client can render an 'Open note' button.")
|
|
101
|
-
.action(async (opts) => {
|
|
102
|
-
if (opts.draftId) {
|
|
103
|
-
if (opts.content) {
|
|
104
|
-
throw new Error("--draft-id sources content from the draft; --content is not allowed alongside it.");
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
else if (!opts.content) {
|
|
108
|
-
throw new Error("--content is required (use '-' to read from stdin), or pass --draft-id.");
|
|
109
|
-
}
|
|
110
|
-
let content;
|
|
111
|
-
if (opts.draftId) {
|
|
112
|
-
const draft = await fetchDraftSummary(opts.draftId);
|
|
113
|
-
content = draft.title
|
|
114
|
-
? `# ${draft.title}\n\n${draft.content}`
|
|
115
|
-
: draft.content;
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
content = opts.content === "-" ? readStdin() : opts.content;
|
|
119
|
-
}
|
|
120
|
-
const body = {
|
|
121
|
-
content,
|
|
122
|
-
timezone: opts.timezone || defaultTimezone(),
|
|
123
|
-
};
|
|
124
|
-
if (opts.agentId)
|
|
125
|
-
body.agentId = parseInt(opts.agentId, 10);
|
|
126
|
-
const resp = (await apiPost(`/app/notes`, body));
|
|
127
|
-
const note = unwrapResp(resp);
|
|
128
|
-
if (opts.draftId && note.id != null) {
|
|
129
|
-
try {
|
|
130
|
-
await apiPatch(`/app/drafts/${opts.draftId}/metadata`, {
|
|
131
|
-
noteId: typeof note.id === "number" ? note.id : Number(note.id),
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
catch (e) {
|
|
135
|
-
console.error(`Warning: failed to link note to draft ${opts.draftId}: ${e.message}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
if (isJsonMode(saved)) {
|
|
139
|
-
jsonOut(note);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
console.log(`Note created!\n ID: ${note.id}\n Date: ${note.eventDate}\n Created: ${note.createdAt}` +
|
|
143
|
-
(opts.draftId ? `\n Linked to draft: ${opts.draftId}` : ""));
|
|
144
|
-
});
|
|
145
|
-
saved
|
|
146
|
-
.command("edit-note <noteId>")
|
|
147
|
-
.description("Edit a note. Provide --content and/or --agent-id.")
|
|
148
|
-
.option("--content <content>", 'New note content (markdown supported, use "-" for stdin)')
|
|
149
|
-
.option("--agent-id <number>", 'New agent id, or "null" to clear the association')
|
|
150
|
-
.action(async (noteId, opts) => {
|
|
151
|
-
if (opts.content == null && opts.agentId == null) {
|
|
152
|
-
throw new Error("Provide at least --content or --agent-id to update.");
|
|
153
|
-
}
|
|
154
|
-
const body = {};
|
|
155
|
-
if (opts.content != null) {
|
|
156
|
-
body.content =
|
|
157
|
-
opts.content === "-" ? readStdin() : opts.content;
|
|
158
|
-
}
|
|
159
|
-
if (opts.agentId != null) {
|
|
160
|
-
body.agentId = opts.agentId === "null" ? null : parseInt(opts.agentId, 10);
|
|
161
|
-
}
|
|
162
|
-
const resp = (await apiPatch(`/app/notes/${noteId}`, body));
|
|
163
|
-
const note = unwrapResp(resp);
|
|
164
|
-
if (isJsonMode(saved)) {
|
|
165
|
-
jsonOut(note);
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
console.log(`Note edited!\n ID: ${note.id}\n Updated: ${note.updatedAt}`);
|
|
169
|
-
});
|
|
170
|
-
saved
|
|
171
|
-
.command("delete-note <noteId>")
|
|
172
|
-
.description("Delete a note you authored.")
|
|
173
|
-
.action(async (noteId) => {
|
|
174
|
-
await apiDelete(`/app/notes/${noteId}`);
|
|
175
|
-
if (isJsonMode(saved)) {
|
|
176
|
-
jsonOut({ id: noteId });
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
console.log(`Note ${noteId} deleted.`);
|
|
180
|
-
});
|
|
181
|
-
// ── Saved posts (bookmarks) ──
|
|
182
|
-
saved
|
|
183
|
-
.command("list-posts")
|
|
184
|
-
.description("List posts you have bookmarked (paginated).")
|
|
185
|
-
.option("--type <type>", "Filter by type: all|article|space-post", "all")
|
|
186
|
-
.option("--limit <number>", "Items per page", "20")
|
|
187
|
-
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
188
|
-
.action(async (opts) => {
|
|
189
|
-
const params = {
|
|
190
|
-
type: opts.type,
|
|
191
|
-
limit: parseInt(opts.limit, 10),
|
|
192
|
-
};
|
|
193
|
-
if (opts.cursor)
|
|
194
|
-
params.cursor = opts.cursor;
|
|
195
|
-
const resp = (await apiGet(`/reactions/me/saved`, params));
|
|
196
|
-
const items = (resp.data || []);
|
|
197
|
-
const pagination = (resp.pagination || {});
|
|
198
|
-
if (isJsonMode(saved)) {
|
|
199
|
-
jsonOut({ items, pagination });
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
if (!items.length) {
|
|
203
|
-
console.log("No saved posts found.");
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
const lines = items.map(formatSavedPostLine);
|
|
207
|
-
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
208
|
-
console.log(`Saved posts (${items.length} items):\n` + lines.join("\n") + footer);
|
|
209
|
-
});
|
|
210
|
-
saved
|
|
211
|
-
.command("get-post <postId>")
|
|
212
|
-
.description("Get a saved post snapshot by post id.")
|
|
213
|
-
.action(async (postId) => {
|
|
214
|
-
const resp = (await apiGet(`/posts/${postId}`));
|
|
215
|
-
const data = unwrapResp(resp);
|
|
216
|
-
if (isJsonMode(saved)) {
|
|
217
|
-
jsonOut(data);
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
const post = (data.update || data.post || data);
|
|
221
|
-
const author = post.author?.name ||
|
|
222
|
-
`User ${post.authorId}`;
|
|
223
|
-
const title = post.title || "(no title)";
|
|
224
|
-
console.log([
|
|
225
|
-
`Saved post [${post.id}]: ${title}`,
|
|
226
|
-
`By: ${author} on ${post.createdAt}`,
|
|
227
|
-
"",
|
|
228
|
-
post.content || "",
|
|
229
|
-
].join("\n"));
|
|
230
|
-
});
|
|
231
|
-
saved
|
|
232
|
-
.command("create-post")
|
|
233
|
-
.description("Bookmark a post or reply by id. Records a snapshot in your saved-posts collection.")
|
|
234
|
-
.requiredOption("--source <id>", "Source post or reply id to bookmark (numeric)")
|
|
235
|
-
.action(async (opts) => {
|
|
236
|
-
const sourceId = parseInt(opts.source, 10);
|
|
237
|
-
if (!Number.isFinite(sourceId)) {
|
|
238
|
-
throw new Error("--source must be a numeric post or reply id.");
|
|
239
|
-
}
|
|
240
|
-
const resp = (await apiPost(`/reactions/posts/${sourceId}/save`, {
|
|
241
|
-
vaultIds: [],
|
|
242
|
-
}));
|
|
243
|
-
const data = unwrapResp(resp);
|
|
244
|
-
if (isJsonMode(saved)) {
|
|
245
|
-
jsonOut({ id: sourceId, ...data });
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
console.log(`Saved post ${sourceId}.`);
|
|
249
|
-
});
|
|
250
|
-
saved
|
|
251
|
-
.command("delete-post <postId>")
|
|
252
|
-
.description("Remove a post from your saved-posts collection.")
|
|
253
|
-
.action(async (postId) => {
|
|
254
|
-
await apiDelete(`/reactions/posts/${postId}/save`);
|
|
255
|
-
if (isJsonMode(saved)) {
|
|
256
|
-
jsonOut({ id: postId });
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
console.log(`Removed post ${postId} from saved.`);
|
|
260
|
-
});
|
|
261
|
-
}
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
import { apiGet, apiPost } from "../client.js";
|
|
2
|
-
import { isJsonMode, jsonOut, readStdin, unwrapResp } from "./utils.js";
|
|
3
|
-
export function registerSessionsCommand(program) {
|
|
4
|
-
const sessions = program
|
|
5
|
-
.command("session")
|
|
6
|
-
.description("Session commands (get, list, create-reply).");
|
|
7
|
-
// ── Get ──
|
|
8
|
-
sessions
|
|
9
|
-
.command("get <sessionId>")
|
|
10
|
-
.description("Get a session and its messages (paginated).")
|
|
11
|
-
.option("--limit <number>", "Items per page", "20")
|
|
12
|
-
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
13
|
-
.action(async (sessionId, opts) => {
|
|
14
|
-
const params = {
|
|
15
|
-
limit: parseInt(opts.limit, 10),
|
|
16
|
-
};
|
|
17
|
-
if (opts.cursor)
|
|
18
|
-
params.cursor = opts.cursor;
|
|
19
|
-
const resp = (await apiGet(`/chat/${sessionId}/messages`, params));
|
|
20
|
-
const messages = (resp.data || []);
|
|
21
|
-
const pagination = (resp.pagination || {});
|
|
22
|
-
if (isJsonMode(sessions)) {
|
|
23
|
-
jsonOut({ messages, pagination });
|
|
24
|
-
return;
|
|
25
|
-
}
|
|
26
|
-
const msgLines = [];
|
|
27
|
-
for (const m of messages) {
|
|
28
|
-
const author = m.author?.name ||
|
|
29
|
-
m.source ||
|
|
30
|
-
`User ${m.authorId}`;
|
|
31
|
-
const text = m.content;
|
|
32
|
-
const truncated = text.length > 200 ? text.slice(0, 200) + "\u2026" : text;
|
|
33
|
-
msgLines.push(` - ${author}: ${truncated} (${m.createdAt})`);
|
|
34
|
-
}
|
|
35
|
-
const output = [
|
|
36
|
-
`Session: ${sessionId}`,
|
|
37
|
-
"",
|
|
38
|
-
`Messages (${messages.length} items):`,
|
|
39
|
-
...msgLines,
|
|
40
|
-
...(pagination.hasMore ? [` Next cursor: ${pagination.nextCursor}`] : []),
|
|
41
|
-
].join("\n");
|
|
42
|
-
console.log(output);
|
|
43
|
-
});
|
|
44
|
-
// ── List ──
|
|
45
|
-
sessions
|
|
46
|
-
.command("list")
|
|
47
|
-
.description("List all sessions you are part of, sorted by most recent activity.")
|
|
48
|
-
.option("--limit <number>", "Items per page", "20")
|
|
49
|
-
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
50
|
-
.action(async (opts) => {
|
|
51
|
-
const query = { limit: parseInt(opts.limit, 10) };
|
|
52
|
-
if (opts.cursor)
|
|
53
|
-
query.cursor = opts.cursor;
|
|
54
|
-
const resp = (await apiGet(`/chat/my-sessions`, query));
|
|
55
|
-
const items = (resp.data || []);
|
|
56
|
-
const pagination = (resp.pagination || {});
|
|
57
|
-
if (isJsonMode(sessions)) {
|
|
58
|
-
jsonOut({
|
|
59
|
-
items,
|
|
60
|
-
pagination: resp.pagination || {},
|
|
61
|
-
});
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (!items.length) {
|
|
65
|
-
console.log("No sessions found.");
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const lines = [];
|
|
69
|
-
for (const s of items) {
|
|
70
|
-
const title = s.title || "(no title)";
|
|
71
|
-
const members = s.members || [];
|
|
72
|
-
const memberCount = s.memberCount ?? 0;
|
|
73
|
-
let memberInfo = "";
|
|
74
|
-
if (members.length > 0) {
|
|
75
|
-
const names = members.map((m) => m.vaultName || m.name || "Unknown");
|
|
76
|
-
const overflow = memberCount - members.length - 1; // -1 for "me"
|
|
77
|
-
memberInfo = ` | with: ${names.join(", ")}`;
|
|
78
|
-
if (overflow > 0)
|
|
79
|
-
memberInfo += ` +${overflow} more`;
|
|
80
|
-
}
|
|
81
|
-
lines.push(`- [${s.sessionId}] "${title}" (mode: ${s.mode}, last activity: ${s.lastMessageAt})${memberInfo}`);
|
|
82
|
-
}
|
|
83
|
-
const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
84
|
-
console.log(`Sessions (${items.length} items):\n` + lines.join("\n") + footer);
|
|
85
|
-
});
|
|
86
|
-
// ── Reply ──
|
|
87
|
-
sessions
|
|
88
|
-
.command("create-reply <sessionId>")
|
|
89
|
-
.description("Send a human reply to a session you are a member of.")
|
|
90
|
-
.option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
|
|
91
|
-
.option("--rich-text <richText>", "Rich-text JSON array (e.g. [{\"type\":\"text\",\"text\":\"hello\"}])")
|
|
92
|
-
.action(async (sessionId, opts) => {
|
|
93
|
-
if (!opts.content && !opts.richText) {
|
|
94
|
-
throw new Error("Provide either --content or --rich-text.");
|
|
95
|
-
}
|
|
96
|
-
if (opts.content && opts.richText) {
|
|
97
|
-
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
98
|
-
}
|
|
99
|
-
const body = {};
|
|
100
|
-
if (opts.richText != null) {
|
|
101
|
-
let parsed;
|
|
102
|
-
try {
|
|
103
|
-
parsed = JSON.parse(opts.richText);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
throw new Error("Invalid --rich-text JSON.");
|
|
107
|
-
}
|
|
108
|
-
body.richText = parsed;
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
111
|
-
body.content = opts.content === "-" ? readStdin() : opts.content;
|
|
112
|
-
}
|
|
113
|
-
const resp = (await apiPost(`/chat/${sessionId}/reply`, body));
|
|
114
|
-
const msg = unwrapResp(resp);
|
|
115
|
-
if (isJsonMode(sessions)) {
|
|
116
|
-
jsonOut(msg);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
console.log(`Reply sent!\n` +
|
|
120
|
-
` Message ID: ${msg.id}\n` +
|
|
121
|
-
` Source: ${msg.source}\n` +
|
|
122
|
-
` Created: ${msg.createdAt}`);
|
|
123
|
-
});
|
|
124
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
# gobi session
|
|
2
|
-
|
|
3
|
-
```
|
|
4
|
-
Usage: gobi session [options] [command]
|
|
5
|
-
|
|
6
|
-
Session commands (get, list, create-reply).
|
|
7
|
-
|
|
8
|
-
Options:
|
|
9
|
-
-h, --help display help for command
|
|
10
|
-
|
|
11
|
-
Commands:
|
|
12
|
-
get [options] <sessionId> Get a session and its messages (paginated).
|
|
13
|
-
list [options] List all sessions you are part of, sorted by most recent activity.
|
|
14
|
-
create-reply [options] <sessionId> Send a human reply to a session you are a member of.
|
|
15
|
-
help [command] display help for command
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
## get
|
|
19
|
-
|
|
20
|
-
```
|
|
21
|
-
Usage: gobi session get [options] <sessionId>
|
|
22
|
-
|
|
23
|
-
Get a session and its messages (paginated).
|
|
24
|
-
|
|
25
|
-
Options:
|
|
26
|
-
--limit <number> Items per page (default: "20")
|
|
27
|
-
--cursor <string> Pagination cursor from previous response
|
|
28
|
-
-h, --help display help for command
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## list
|
|
32
|
-
|
|
33
|
-
```
|
|
34
|
-
Usage: gobi session list [options]
|
|
35
|
-
|
|
36
|
-
List all sessions you are part of, sorted by most recent activity.
|
|
37
|
-
|
|
38
|
-
Options:
|
|
39
|
-
--limit <number> Items per page (default: "20")
|
|
40
|
-
--cursor <string> Pagination cursor from previous response
|
|
41
|
-
-h, --help display help for command
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## create-reply
|
|
45
|
-
|
|
46
|
-
```
|
|
47
|
-
Usage: gobi session create-reply [options] <sessionId>
|
|
48
|
-
|
|
49
|
-
Send a human reply to a session you are a member of.
|
|
50
|
-
|
|
51
|
-
Options:
|
|
52
|
-
--content <content> Reply content (markdown supported, use "-" for stdin)
|
|
53
|
-
--rich-text <richText> Rich-text JSON array (e.g. [{"type":"text","text":"hello"}])
|
|
54
|
-
-h, --help display help for command
|
|
55
|
-
```
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: gobi-saved
|
|
3
|
-
description: >-
|
|
4
|
-
Gobi saved commands for the user's personal saved-knowledge collection:
|
|
5
|
-
notes (list/get/create/edit/delete) and bookmarked posts (snapshot a
|
|
6
|
-
post/reply from feed/space; list/get/delete). Use when the user wants to
|
|
7
|
-
capture their own notes or bookmark/manage posts they've saved.
|
|
8
|
-
allowed-tools: Bash(gobi:*)
|
|
9
|
-
metadata:
|
|
10
|
-
author: gobi-ai
|
|
11
|
-
version: "2.0.9"
|
|
12
|
-
---
|
|
13
|
-
|
|
14
|
-
# gobi-saved
|
|
15
|
-
|
|
16
|
-
Gobi saved-knowledge commands (v2.0.9).
|
|
17
|
-
|
|
18
|
-
Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
|
|
19
|
-
|
|
20
|
-
## What is "saved"?
|
|
21
|
-
|
|
22
|
-
`gobi saved` is the user's personal saved-knowledge collection. It covers two kinds of items:
|
|
23
|
-
|
|
24
|
-
- **Notes** — user-authored notes (private, dated entries). Verbs: `list-notes`, `get-note`, `create-note`, `edit-note`, `delete-note`.
|
|
25
|
-
- **Posts** — snapshots of posts (or replies) bookmarked from a space or the global feed. Verbs: `list-posts`, `get-post`, `create-post`, `delete-post`.
|
|
26
|
-
|
|
27
|
-
Both are user-private — only the author can see/edit/delete their own items.
|
|
28
|
-
|
|
29
|
-
> Naming note: `gobi saved create-post --source <id>` is a **bookmark** operation — it saves an existing post into your collection. It does *not* author a new post (use `gobi global create-post` or `gobi space create-post` for that).
|
|
30
|
-
|
|
31
|
-
## Timezone
|
|
32
|
-
|
|
33
|
-
`gobi saved list-notes` and `gobi saved create-note` need a timezone to compute the calendar day. The CLI auto-detects your system timezone via `Intl.DateTimeFormat().resolvedOptions().timeZone`. Override with `--timezone <iana-name>` (e.g. `America/Los_Angeles`).
|
|
34
|
-
|
|
35
|
-
## Important: JSON Mode
|
|
36
|
-
|
|
37
|
-
For programmatic/agent usage, always pass `--json` as a **global** option (before the subcommand):
|
|
38
|
-
|
|
39
|
-
```bash
|
|
40
|
-
gobi --json saved list-notes --date 2026-04-27
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Available Commands
|
|
44
|
-
|
|
45
|
-
### Notes
|
|
46
|
-
- `gobi saved list-notes` — List your notes. Without `--date`, returns recent notes via cursor pagination. With `--date YYYY-MM-DD`, returns all notes for that day.
|
|
47
|
-
- `gobi saved get-note <noteId>` — Get a single note by id.
|
|
48
|
-
- `gobi saved create-note --content <md>` — Create a note. Use `'-'` for stdin. Or pass `--draft-id <draftId>` to source content from a draft (mutually exclusive with `--content`); the draft's title is prepended as an H1, and the resulting `noteId` is recorded on `draft.metadata` so the client can render an "Open note" button.
|
|
49
|
-
- `gobi saved edit-note <noteId>` — Edit a note. Provide `--content` and/or `--agent-id`.
|
|
50
|
-
- `gobi saved delete-note <noteId>` — Delete a note you authored.
|
|
51
|
-
|
|
52
|
-
### Posts (bookmarks)
|
|
53
|
-
- `gobi saved list-posts` — List posts you have bookmarked (paginated). Filter with `--type all|article|space-post`.
|
|
54
|
-
- `gobi saved get-post <postId>` — Get a saved post snapshot by post id.
|
|
55
|
-
- `gobi saved create-post --source <id>` — Bookmark a post or reply by id. Records a snapshot in your saved-posts collection.
|
|
56
|
-
- `gobi saved delete-post <postId>` — Remove a post from your saved-posts collection.
|
|
57
|
-
|
|
58
|
-
## Confirm before mutating
|
|
59
|
-
|
|
60
|
-
Saved items are the user's private collection but they still persist server-side and deletes are irreversible. Before running any write, confirm with the user — show the command and the note content / target id. This applies even when running autonomously.
|
|
61
|
-
|
|
62
|
-
- `create-note`, `edit-note` — confirm the content (or content delta on edit).
|
|
63
|
-
- `create-post` (bookmarking a feed post) — confirm the source id you're about to bookmark.
|
|
64
|
-
- `delete-note`, `delete-post` — irreversible. Flag that explicitly and confirm the target id before running.
|
|
65
|
-
|
|
66
|
-
Read-only commands (`list-notes`, `get-note`, `list-posts`, `get-post`) run without confirmation.
|
|
67
|
-
|
|
68
|
-
## Reference Documentation
|
|
69
|
-
|
|
70
|
-
- [gobi saved](references/saved.md)
|