@gobi-ai/cli 2.0.4 → 2.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +56 -42
- package/commands/space-share.md +1 -1
- package/dist/commands/auth.js +38 -9
- package/dist/commands/draft.js +9 -13
- package/dist/commands/global.js +90 -24
- package/dist/commands/init.js +36 -35
- package/dist/commands/media.js +39 -39
- package/dist/commands/saved.js +31 -41
- package/dist/commands/sense.js +4 -4
- package/dist/commands/sessions.js +6 -6
- package/dist/commands/space.js +188 -68
- package/dist/commands/utils.js +7 -2
- package/dist/commands/vault.js +40 -4
- package/dist/main.js +37 -13
- package/package.json +1 -1
- package/skills/gobi-core/SKILL.md +38 -42
- package/skills/gobi-core/references/session.md +10 -10
- package/skills/gobi-core/references/space.md +6 -6
- package/skills/gobi-draft/SKILL.md +4 -4
- package/skills/gobi-draft/references/draft.md +9 -8
- package/skills/gobi-media/SKILL.md +37 -38
- package/skills/gobi-media/references/media.md +59 -59
- package/skills/gobi-saved/SKILL.md +27 -26
- package/skills/gobi-saved/references/saved.md +108 -26
- package/skills/gobi-sense/SKILL.md +8 -8
- package/skills/gobi-sense/references/sense.md +10 -10
- package/skills/gobi-space/SKILL.md +28 -10
- package/skills/gobi-space/references/global.md +21 -15
- package/skills/gobi-space/references/space.md +54 -35
- package/skills/gobi-vault/SKILL.md +12 -5
- package/skills/gobi-vault/references/vault.md +25 -1
- package/skills/gobi-core/references/init.md +0 -10
|
@@ -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.6",
|
|
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, manage saved notes and posts, manage sessions, generate images and videos.",
|
|
12
|
-
"version": "2.0.
|
|
12
|
+
"version": "2.0.6",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "gobi-ai"
|
|
15
15
|
},
|
package/README.md
CHANGED
|
@@ -38,10 +38,13 @@ npm link
|
|
|
38
38
|
## Quick start
|
|
39
39
|
|
|
40
40
|
```sh
|
|
41
|
-
#
|
|
42
|
-
gobi
|
|
41
|
+
# Sign in (device-code flow — opens a URL, you authorize, the CLI polls)
|
|
42
|
+
gobi auth login
|
|
43
43
|
|
|
44
|
-
#
|
|
44
|
+
# Set up the vault for the current directory (creates PUBLISH.md if missing)
|
|
45
|
+
gobi vault init
|
|
46
|
+
|
|
47
|
+
# Select a community space for the current directory
|
|
45
48
|
gobi space warp
|
|
46
49
|
|
|
47
50
|
# Publish your vault profile (after editing PUBLISH.md frontmatter)
|
|
@@ -52,9 +55,17 @@ gobi vault sync
|
|
|
52
55
|
|
|
53
56
|
# Browse the global feed and create a personal post
|
|
54
57
|
gobi global feed
|
|
55
|
-
gobi global create-post --title "Hello" --content "Trying gobi"
|
|
58
|
+
gobi global create-post --title "Hello" --content "Trying gobi"
|
|
56
59
|
```
|
|
57
60
|
|
|
61
|
+
Each setup step unlocks a different family of commands — run only the ones the workflow needs:
|
|
62
|
+
|
|
63
|
+
| Step | Unlocks |
|
|
64
|
+
|------|---------|
|
|
65
|
+
| `gobi auth login` | All authenticated commands |
|
|
66
|
+
| `gobi vault init` | Every `gobi vault …` command (`publish`, `unpublish`, `sync`) and lets `global create-post` default to that vault |
|
|
67
|
+
| `gobi space warp` | Every `gobi space …` command without needing `--space-slug` |
|
|
68
|
+
|
|
58
69
|
---
|
|
59
70
|
|
|
60
71
|
## Using gobi from an agent
|
|
@@ -82,7 +93,7 @@ The CLI looks up two pieces of state:
|
|
|
82
93
|
| Path | What | Who manages |
|
|
83
94
|
|------|------|-------------|
|
|
84
95
|
| `~/.gobi/credentials.json` | Auth tokens (`accessToken`, `refreshToken`) | `gobi auth login` writes; `gobi auth logout` clears |
|
|
85
|
-
| `.gobi/settings.yaml` | Per-project `vaultSlug` and `selectedSpaceSlug` | `gobi init` and `gobi space warp` write |
|
|
96
|
+
| `.gobi/settings.yaml` | Per-project `vaultSlug` and `selectedSpaceSlug` | `gobi vault init` and `gobi space warp` write |
|
|
86
97
|
|
|
87
98
|
An agent should check these before calling commands that need a vault or space:
|
|
88
99
|
|
|
@@ -94,9 +105,9 @@ gobi --json auth status
|
|
|
94
105
|
cat .gobi/settings.yaml 2>/dev/null
|
|
95
106
|
```
|
|
96
107
|
|
|
97
|
-
If `.gobi/settings.yaml` is missing, `gobi init` and `gobi space warp` are the interactive entry points — they require user input, so an agent should hand off to the user rather than trying to drive them silently.
|
|
108
|
+
If `.gobi/settings.yaml` is missing, `gobi vault init` and `gobi space warp` are the interactive entry points — they require user input, so an agent should hand off to the user rather than trying to drive them silently.
|
|
98
109
|
|
|
99
|
-
|
|
110
|
+
`gobi space …` commands accept `--space-slug <slug>` (on the parent group or any subcommand) to override the default space. Per-command `--vault-slug` overrides are documented inline.
|
|
100
111
|
|
|
101
112
|
### Headless auth
|
|
102
113
|
|
|
@@ -122,7 +133,8 @@ When the runtime exports `GOBI_SESSION_ID`, `gobi draft add` picks it up automat
|
|
|
122
133
|
|
|
123
134
|
| Command | Description |
|
|
124
135
|
|---------|-------------|
|
|
125
|
-
| `gobi init` |
|
|
136
|
+
| `gobi vault init` | Select or create the vault for this directory. Writes `vaultSlug` to `.gobi/settings.yaml` and seeds `PUBLISH.md`. |
|
|
137
|
+
| `gobi vault list` | List vaults you own |
|
|
126
138
|
| `gobi space list` | List spaces you are a member of |
|
|
127
139
|
| `gobi space warp [spaceSlug]` | Select the active space (interactive if slug omitted) |
|
|
128
140
|
|
|
@@ -164,13 +176,13 @@ A *Space* is a community knowledge area. A *Space Post* lives in one space. The
|
|
|
164
176
|
| `gobi space list-topics` | List topics in the space, ordered by most recent linkage |
|
|
165
177
|
| `gobi space list-topic-posts <topicSlug>` | List posts tagged with a topic |
|
|
166
178
|
| `gobi space list-posts` | List posts in the space |
|
|
167
|
-
| `gobi space get-post <postId
|
|
179
|
+
| `gobi space get-post <postId> [--full]` | Get a post with its ancestors and replies. `--full` shows reply content without truncation. |
|
|
168
180
|
| `gobi space create-post --title <t> --content <c> [--vault-slug <slug>] [--auto-attachments]` | Create a space post. `--vault-slug` attributes it to a vault you own; `--auto-attachments` uploads `[[wikilinks]]` to that vault and uses it as `authorVaultSlug`. |
|
|
169
181
|
| `gobi space edit-post <postId> [--title <t>] [--content <c>] [--vault-slug <slug>] [--auto-attachments]` | Edit a space post. `--vault-slug ""` detaches the vault. |
|
|
170
182
|
| `gobi space delete-post <postId>` | Delete a space post |
|
|
171
|
-
| `gobi space create-reply <postId> --content <c
|
|
172
|
-
| `gobi space edit-reply <replyId> --content <c
|
|
173
|
-
| `gobi space delete-reply <replyId>` | Delete a reply |
|
|
183
|
+
| `gobi space create-reply <postId> (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--auto-attachments]` | Create a reply to a space post |
|
|
184
|
+
| `gobi space edit-reply <replyId> [--content <c>] [--rich-text <json>] [--vault-slug <slug>] [--auto-attachments]` | Edit a reply you authored. `--vault-slug ""` detaches attribution. |
|
|
185
|
+
| `gobi space delete-reply <replyId>` | Delete a reply you authored |
|
|
174
186
|
|
|
175
187
|
### Global feed (personal posts)
|
|
176
188
|
|
|
@@ -178,14 +190,14 @@ A *Personal Post* lives on the author's profile (their primary vault) and surfac
|
|
|
178
190
|
|
|
179
191
|
| Command | Description |
|
|
180
192
|
|---------|-------------|
|
|
181
|
-
| `gobi global feed` | List the global public feed (posts + replies, newest first) |
|
|
193
|
+
| `gobi global feed [--following]` | List the global public feed (posts + replies, newest first). `--following` limits to authors you follow. |
|
|
182
194
|
| `gobi global list-posts [--mine] [--vault-slug <slug>]` | List personal posts; filter to your own or by author vault |
|
|
183
|
-
| `gobi global get-post <postId
|
|
195
|
+
| `gobi global get-post <postId> [--full]` | Get a personal post with its ancestors and replies. `--full` shows reply content without truncation. |
|
|
184
196
|
| `gobi global create-post [--title <t>] (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--auto-attachments]` | Create a personal post |
|
|
185
197
|
| `gobi global edit-post <postId> [--title <t>] [--content <c>] [--vault-slug <slug>]` | Edit a personal post you authored. `--vault-slug ""` detaches the vault. |
|
|
186
198
|
| `gobi global delete-post <postId>` | Delete a personal post you authored |
|
|
187
|
-
| `gobi global create-reply <postId> (--content <c> \| --rich-text <json>)` |
|
|
188
|
-
| `gobi global edit-reply <replyId> --content <c
|
|
199
|
+
| `gobi global create-reply <postId> (--content <c> \| --rich-text <json>) [--vault-slug <slug>] [--auto-attachments]` | Create a reply to a personal post |
|
|
200
|
+
| `gobi global edit-reply <replyId> [--content <c>] [--rich-text <json>] [--vault-slug <slug>] [--auto-attachments]` | Edit a reply you authored. `--vault-slug ""` detaches attribution. |
|
|
189
201
|
| `gobi global delete-reply <replyId>` | Delete a reply you authored |
|
|
190
202
|
|
|
191
203
|
`--vault-slug` requires that the caller hold `role: 'owner'` on the target vault. When set, it becomes the post's `authorVaultSlug`. When `--auto-attachments` is set, the same vault is used both as the upload destination for `[[wikilinks]]` and as `authorVaultSlug`.
|
|
@@ -196,9 +208,9 @@ A *Personal Post* lives on the author's profile (their primary vault) and surfac
|
|
|
196
208
|
|---------|-------------|
|
|
197
209
|
| `gobi session list` | List your sessions |
|
|
198
210
|
| `gobi session get <id>` | Get a session and its messages |
|
|
199
|
-
| `gobi session reply <id> --content <c>` | Send a message in a session |
|
|
211
|
+
| `gobi session create-reply <id> --content <c>` | Send a message in a session |
|
|
200
212
|
|
|
201
|
-
`session reply` also accepts `--rich-text <json>` (mutually exclusive with `--content`).
|
|
213
|
+
`session create-reply` also accepts `--rich-text <json>` (mutually exclusive with `--content`).
|
|
202
214
|
|
|
203
215
|
### Sense
|
|
204
216
|
|
|
@@ -206,8 +218,8 @@ Activity and transcription data captured by Gobi Sense (or the mobile app).
|
|
|
206
218
|
|
|
207
219
|
| Command | Description |
|
|
208
220
|
|---------|-------------|
|
|
209
|
-
| `gobi sense activities --start-time <iso> --end-time <iso>` |
|
|
210
|
-
| `gobi sense transcriptions --start-time <iso> --end-time <iso>` |
|
|
221
|
+
| `gobi sense list-activities --start-time <iso> --end-time <iso>` | List activity records in a time range |
|
|
222
|
+
| `gobi sense list-transcriptions --start-time <iso> --end-time <iso>` | List transcription records in a time range |
|
|
211
223
|
|
|
212
224
|
Times are ISO 8601 UTC (e.g. `2026-03-20T00:00:00Z`).
|
|
213
225
|
|
|
@@ -219,22 +231,22 @@ Times are ISO 8601 UTC (e.g. `2026-03-20T00:00:00Z`).
|
|
|
219
231
|
|
|
220
232
|
| Command | Description |
|
|
221
233
|
|---------|-------------|
|
|
222
|
-
| `gobi saved
|
|
223
|
-
| `gobi saved note
|
|
224
|
-
| `gobi saved note
|
|
225
|
-
| `gobi saved note
|
|
226
|
-
| `gobi saved note
|
|
234
|
+
| `gobi saved list-notes [--date YYYY-MM-DD]` | List your notes (recent via cursor, or all for a day) |
|
|
235
|
+
| `gobi saved get-note <id>` | Get a single note |
|
|
236
|
+
| `gobi saved create-note --content <c>` | Create a note (use `-` to read content from stdin) |
|
|
237
|
+
| `gobi saved edit-note <id> [--content <c>] [--agent-id <id>]` | Edit a note (`--agent-id null` clears the link) |
|
|
238
|
+
| `gobi saved delete-note <id>` | Delete a note you authored |
|
|
227
239
|
|
|
228
|
-
`saved
|
|
240
|
+
`saved list-notes` and `saved create-note` accept `--timezone <iana>` (default: system timezone).
|
|
229
241
|
|
|
230
|
-
#### Saved posts
|
|
242
|
+
#### Saved posts (bookmarks)
|
|
231
243
|
|
|
232
244
|
| Command | Description |
|
|
233
245
|
|---------|-------------|
|
|
234
|
-
| `gobi saved
|
|
235
|
-
| `gobi saved post
|
|
236
|
-
| `gobi saved post
|
|
237
|
-
| `gobi saved post
|
|
246
|
+
| `gobi saved list-posts [--type all\|article\|space-post]` | List posts you've bookmarked |
|
|
247
|
+
| `gobi saved get-post <postId>` | Get a saved post snapshot |
|
|
248
|
+
| `gobi saved create-post --source <id>` | Bookmark a post or reply by id (records a snapshot) |
|
|
249
|
+
| `gobi saved delete-post <postId>` | Remove a post from your saved collection |
|
|
238
250
|
|
|
239
251
|
### Drafts
|
|
240
252
|
|
|
@@ -247,7 +259,7 @@ Each action is `{ label, message? }`: `label` is the short button text (1–80 c
|
|
|
247
259
|
| Command | Description |
|
|
248
260
|
|---------|-------------|
|
|
249
261
|
| `gobi draft list [--limit N]` | List drafts (priority ASC, then newest first) |
|
|
250
|
-
| `gobi draft get <id>` |
|
|
262
|
+
| `gobi draft get <id>` | Get one draft with its history and suggested actions |
|
|
251
263
|
| `gobi draft add <title> <content> [--session <id>] [--priority N] [--action <label[::message]>]…` | Add a draft. Pass `--action` up to 3 times; each action is `Label` or `Label::Message`. `--session` falls back to `$GOBI_SESSION_ID`. Use `-` for content to read from stdin. |
|
|
252
264
|
| `gobi draft delete <id>` | Delete a draft |
|
|
253
265
|
| `gobi draft prioritize <id> <priority>` | Set priority (lower = higher) |
|
|
@@ -260,12 +272,14 @@ Image, video, and avatar generation. See the `gobi-media` skill for full workflo
|
|
|
260
272
|
|
|
261
273
|
| Command | Description |
|
|
262
274
|
|---------|-------------|
|
|
263
|
-
| `gobi media image
|
|
264
|
-
| `gobi media image
|
|
265
|
-
| `gobi media
|
|
266
|
-
| `gobi media
|
|
267
|
-
| `gobi media
|
|
268
|
-
| `gobi media
|
|
275
|
+
| `gobi media generate-image --prompt <p> [--aspect-ratio <r>] [-o <file>]` | Generate an image (use `-o` to wait + download) |
|
|
276
|
+
| `gobi media edit-image --image <f> --prompt <p>` | Edit an image with a prompt |
|
|
277
|
+
| `gobi media inpaint-image --image <f> --mask <m> --prompt <p>` | Inpaint a masked region |
|
|
278
|
+
| `gobi media create-video --avatar-id <a> --voice-id <v> --script <s>` | Avatar video with voice narration |
|
|
279
|
+
| `gobi media create-cinematic --prompt <p>` | Cinematic video from a text prompt |
|
|
280
|
+
| `gobi media design-avatar / design-avatar-from-selfie` | Custom avatars from prompts or selfies |
|
|
281
|
+
| `gobi media list-avatars` / `gobi media list-voices` | List available avatars and voices |
|
|
282
|
+
| `gobi media list-videos` / `gobi media get-video <id>` | List or get videos |
|
|
269
283
|
| `gobi media upload <file>` | Upload a local file and get a media id |
|
|
270
284
|
|
|
271
285
|
### Global options
|
|
@@ -302,13 +316,13 @@ The CLI ships a `.claude-plugin/` manifest with eight skills that wrap the comma
|
|
|
302
316
|
|
|
303
317
|
| Skill | Covers |
|
|
304
318
|
|-------|--------|
|
|
305
|
-
| `gobi-core` | Auth,
|
|
306
|
-
| `gobi-vault` | `gobi vault publish/unpublish/sync` |
|
|
319
|
+
| `gobi-core` | Auth, session, update, space list/warp |
|
|
320
|
+
| `gobi-vault` | `gobi vault init/list/publish/unpublish/sync` |
|
|
307
321
|
| `gobi-space` | `gobi space …` and `gobi global …` |
|
|
308
|
-
| `gobi-saved` | `gobi saved note
|
|
322
|
+
| `gobi-saved` | `gobi saved list-notes/create-note/list-posts/create-post/…` |
|
|
309
323
|
| `gobi-draft` | `gobi draft …` |
|
|
310
324
|
| `gobi-media` | `gobi media …` |
|
|
311
|
-
| `gobi-sense` | `gobi sense activities/transcriptions` |
|
|
325
|
+
| `gobi-sense` | `gobi sense list-activities/list-transcriptions` |
|
|
312
326
|
| `gobi-homepage` | Building custom HTML homepages with `window.gobi` |
|
|
313
327
|
|
|
314
328
|
Each skill's `SKILL.md` is hand-written orientation; `references/` is regenerated from `--help` output by `npm run generate-skill-docs`.
|
package/commands/space-share.md
CHANGED
|
@@ -14,7 +14,7 @@ First, verify the user is set up:
|
|
|
14
14
|
gobi --json auth status
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
Check that `.gobi/settings.yaml` exists
|
|
17
|
+
Check that `.gobi/settings.yaml` exists. If you plan to publish to a Space (`gobi space create-post`), it must contain `selectedSpaceSlug` — otherwise stop and ask the user to run `gobi space warp` first. `vaultSlug` is optional for `gobi global create-post`; if missing the post is created without an `authorVaultSlug` (vault-less). Run `gobi vault init` if the user wants the post attributed to a vault they own.
|
|
18
18
|
|
|
19
19
|
## Draft a personal post
|
|
20
20
|
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BASE_URL, POLL_MAX_DURATION_MS } from "../constants.js";
|
|
2
2
|
import { DeviceCodeError } from "../errors.js";
|
|
3
3
|
import { storeTokens, logout, isAuthenticated, getCurrentUser, } from "../auth/manager.js";
|
|
4
|
-
import {
|
|
4
|
+
import { readSettings } from "./init.js";
|
|
5
|
+
import { isJsonMode, jsonOut } from "./utils.js";
|
|
5
6
|
function sleep(ms) {
|
|
6
7
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
7
8
|
}
|
|
@@ -69,22 +70,50 @@ export function registerAuthCommand(program) {
|
|
|
69
70
|
.command("status")
|
|
70
71
|
.description("Check whether you are currently authenticated with Gobi.")
|
|
71
72
|
.action(() => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
const settings = readSettings();
|
|
74
|
+
const vaultSlug = settings?.vaultSlug ?? null;
|
|
75
|
+
const spaceSlug = settings?.selectedSpaceSlug ?? null;
|
|
76
|
+
if (!isAuthenticated()) {
|
|
77
|
+
if (isJsonMode(auth)) {
|
|
78
|
+
jsonOut({
|
|
79
|
+
authenticated: false,
|
|
80
|
+
user: null,
|
|
81
|
+
vaultSlug,
|
|
82
|
+
spaceSlug,
|
|
83
|
+
});
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
80
86
|
console.log("You are not authenticated. Use 'gobi auth login' to log in.");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const user = getCurrentUser();
|
|
90
|
+
if (isJsonMode(auth)) {
|
|
91
|
+
jsonOut({
|
|
92
|
+
authenticated: true,
|
|
93
|
+
user: {
|
|
94
|
+
name: user?.name ?? null,
|
|
95
|
+
email: user?.email ?? null,
|
|
96
|
+
},
|
|
97
|
+
vaultSlug,
|
|
98
|
+
spaceSlug,
|
|
99
|
+
});
|
|
100
|
+
return;
|
|
81
101
|
}
|
|
102
|
+
const name = user?.name || "Unknown";
|
|
103
|
+
const email = user?.email || "Unknown";
|
|
104
|
+
console.log(`Authenticated as ${name} (${email})`);
|
|
105
|
+
console.log(` Vault: ${vaultSlug ?? "(not set)"}`);
|
|
106
|
+
console.log(` Space: ${spaceSlug ?? "(not set)"}`);
|
|
82
107
|
});
|
|
83
108
|
auth
|
|
84
109
|
.command("logout")
|
|
85
110
|
.description("Log out of Gobi and remove stored credentials.")
|
|
86
111
|
.action(async () => {
|
|
87
112
|
await logout();
|
|
113
|
+
if (isJsonMode(auth)) {
|
|
114
|
+
jsonOut({ loggedOut: true });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
88
117
|
console.log("Logged out. Credentials removed.");
|
|
89
118
|
});
|
|
90
119
|
}
|
package/dist/commands/draft.js
CHANGED
|
@@ -43,7 +43,7 @@ export function registerDraftCommand(program) {
|
|
|
43
43
|
draft
|
|
44
44
|
.command("list")
|
|
45
45
|
.description("List drafts (priority ASC, then newest first).")
|
|
46
|
-
.option("--limit <number>", "
|
|
46
|
+
.option("--limit <number>", "Items per page", "20")
|
|
47
47
|
.action(async (opts) => {
|
|
48
48
|
const params = { limit: parseInt(opts.limit, 10) };
|
|
49
49
|
const resp = (await apiGet("/app/drafts", params));
|
|
@@ -63,7 +63,7 @@ export function registerDraftCommand(program) {
|
|
|
63
63
|
// ── Get ──
|
|
64
64
|
draft
|
|
65
65
|
.command("get <draftId>")
|
|
66
|
-
.description("
|
|
66
|
+
.description("Get one draft with its history and suggested actions.")
|
|
67
67
|
.action(async (draftId) => {
|
|
68
68
|
const resp = (await apiGet(`/app/drafts/${draftId}`));
|
|
69
69
|
const d = unwrapResp(resp);
|
|
@@ -121,21 +121,18 @@ export function registerDraftCommand(program) {
|
|
|
121
121
|
// ── Add ──
|
|
122
122
|
draft
|
|
123
123
|
.command("add <title> <content>")
|
|
124
|
-
.description("Add a draft. Pass '-' for content to read from stdin. Pass --action up to 3 times to attach AI-suggested actions.
|
|
125
|
-
.option("--session <sessionId>", "Originating chat session UUID. Falls back to $GOBI_SESSION_ID
|
|
124
|
+
.description("Add a draft. Pass '-' for content to read from stdin. Pass --action up to 3 times to attach AI-suggested actions. Session id is optional: the Gobi agent runtime exports GOBI_SESSION_ID automatically and `--session` takes precedence; if neither is set, the server mints a new chat session anchored to your primary vault and seeds it with the draft so clicking an action later has somewhere to land.")
|
|
125
|
+
.option("--session <sessionId>", "Originating chat session UUID. Falls back to $GOBI_SESSION_ID; if unset, the server creates a new session.")
|
|
126
126
|
.option("--priority <number>", "Priority (lower = higher), default 100")
|
|
127
127
|
.option("--action <label[::message]>", "Suggested action (repeatable, max 3). `label` is the button text; an optional `::message` suffix is what the user is taken to be saying to the agent on click. Without the suffix, the message falls back to the label.", (value, prev = []) => [...prev, value], [])
|
|
128
128
|
.action(async (title, content, opts) => {
|
|
129
|
-
const sessionId = opts.session || process.env.GOBI_SESSION_ID
|
|
130
|
-
if (!sessionId) {
|
|
131
|
-
console.error("Error: missing session id. Pass --session <uuid> or set GOBI_SESSION_ID in the environment.");
|
|
132
|
-
process.exit(1);
|
|
133
|
-
}
|
|
129
|
+
const sessionId = opts.session || process.env.GOBI_SESSION_ID;
|
|
134
130
|
const body = {
|
|
135
131
|
title,
|
|
136
132
|
content: readContent(content),
|
|
137
|
-
sessionId,
|
|
138
133
|
};
|
|
134
|
+
if (sessionId)
|
|
135
|
+
body.sessionId = sessionId;
|
|
139
136
|
if (opts.priority)
|
|
140
137
|
body.priority = parseInt(opts.priority, 10);
|
|
141
138
|
const actions = parseActionFlags(opts.action);
|
|
@@ -156,7 +153,7 @@ export function registerDraftCommand(program) {
|
|
|
156
153
|
.action(async (draftId) => {
|
|
157
154
|
await apiDelete(`/app/drafts/${draftId}`);
|
|
158
155
|
if (isJsonMode(draft)) {
|
|
159
|
-
jsonOut({
|
|
156
|
+
jsonOut({ id: draftId });
|
|
160
157
|
return;
|
|
161
158
|
}
|
|
162
159
|
console.log(`Deleted ${draftId}.`);
|
|
@@ -183,8 +180,7 @@ export function registerDraftCommand(program) {
|
|
|
183
180
|
.action(async (draftId, actionIndex) => {
|
|
184
181
|
const idx = parseInt(actionIndex, 10);
|
|
185
182
|
if (Number.isNaN(idx) || idx < 0 || idx > 2) {
|
|
186
|
-
|
|
187
|
-
process.exit(1);
|
|
183
|
+
throw new Error("actionIndex must be 0, 1, or 2.");
|
|
188
184
|
}
|
|
189
185
|
const resp = (await apiPost(`/app/drafts/${draftId}/action`, {
|
|
190
186
|
actionIndex: idx,
|
package/dist/commands/global.js
CHANGED
|
@@ -32,7 +32,7 @@ export function registerGlobalCommand(program) {
|
|
|
32
32
|
// ── Feed (unified) ──
|
|
33
33
|
global
|
|
34
34
|
.command("feed")
|
|
35
|
-
.description("List the
|
|
35
|
+
.description("List the unified feed (posts and replies, newest first) in the global public feed.")
|
|
36
36
|
.option("--limit <number>", "Items per page", "20")
|
|
37
37
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
38
38
|
.option("--following", "Only include posts from authors you follow")
|
|
@@ -110,7 +110,7 @@ export function registerGlobalCommand(program) {
|
|
|
110
110
|
global
|
|
111
111
|
.command("get-post <postId>")
|
|
112
112
|
.description("Get a global post with its ancestors and replies (paginated).")
|
|
113
|
-
.option("--limit <number>", "
|
|
113
|
+
.option("--limit <number>", "Items per page", "20")
|
|
114
114
|
.option("--cursor <string>", "Pagination cursor from previous response")
|
|
115
115
|
.option("--full", "Show full reply content without truncation")
|
|
116
116
|
.action(async (postId, opts) => {
|
|
@@ -177,12 +177,12 @@ export function registerGlobalCommand(program) {
|
|
|
177
177
|
// ── Create post ──
|
|
178
178
|
global
|
|
179
179
|
.command("create-post")
|
|
180
|
-
.description("Create a post in the global feed
|
|
180
|
+
.description("Create a post in the global feed. --vault-slug attributes it to a vault you own; defaults to your primary vault.")
|
|
181
181
|
.option("--title <title>", "Title of the post")
|
|
182
182
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
183
183
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
184
|
-
.option("--vault-slug <vaultSlug>", "
|
|
185
|
-
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting")
|
|
184
|
+
.option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug). Defaults to your primary vault.")
|
|
185
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting (also sets authorVaultSlug to that vault)")
|
|
186
186
|
.action(async (opts) => {
|
|
187
187
|
if (!opts.content && !opts.richText) {
|
|
188
188
|
throw new Error("Provide either --content or --rich-text.");
|
|
@@ -190,16 +190,19 @@ export function registerGlobalCommand(program) {
|
|
|
190
190
|
if (opts.content && opts.richText) {
|
|
191
191
|
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
192
192
|
}
|
|
193
|
-
|
|
193
|
+
let authorVaultSlug;
|
|
194
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
195
|
+
authorVaultSlug = resolveVaultSlug(opts);
|
|
196
|
+
}
|
|
194
197
|
const body = {};
|
|
195
198
|
if (opts.title != null)
|
|
196
199
|
body.title = opts.title;
|
|
197
200
|
if (opts.content != null) {
|
|
198
201
|
const content = readContent(opts.content);
|
|
199
|
-
if (opts.autoAttachments) {
|
|
202
|
+
if (opts.autoAttachments && authorVaultSlug) {
|
|
200
203
|
const token = await getValidToken();
|
|
201
204
|
const links = extractWikiLinks(content);
|
|
202
|
-
await uploadAttachments(
|
|
205
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
203
206
|
}
|
|
204
207
|
body.content = content;
|
|
205
208
|
}
|
|
@@ -213,7 +216,9 @@ export function registerGlobalCommand(program) {
|
|
|
213
216
|
}
|
|
214
217
|
body.richText = parsed;
|
|
215
218
|
}
|
|
216
|
-
|
|
219
|
+
if (authorVaultSlug)
|
|
220
|
+
body.authorVaultSlug = authorVaultSlug;
|
|
221
|
+
const resp = (await apiPost(`/posts`, body));
|
|
217
222
|
const post = unwrapResp(resp);
|
|
218
223
|
if (isJsonMode(global)) {
|
|
219
224
|
jsonOut(post);
|
|
@@ -231,22 +236,35 @@ export function registerGlobalCommand(program) {
|
|
|
231
236
|
.option("--title <title>", "New title")
|
|
232
237
|
.option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
|
|
233
238
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
234
|
-
.option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets
|
|
239
|
+
.option("--vault-slug <vaultSlug>", "Attribute the post to this vault (sets authorVaultSlug).")
|
|
240
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before editing (uses --vault-slug or .gobi vault)")
|
|
235
241
|
.action(async (postId, opts) => {
|
|
242
|
+
const wantsVaultChange = !!(opts.vaultSlug || opts.autoAttachments);
|
|
236
243
|
if (opts.title == null &&
|
|
237
244
|
opts.content == null &&
|
|
238
245
|
opts.richText == null &&
|
|
239
|
-
|
|
246
|
+
!wantsVaultChange) {
|
|
240
247
|
throw new Error("Provide at least --title, --content, --rich-text, or --vault-slug to update.");
|
|
241
248
|
}
|
|
242
249
|
if (opts.content && opts.richText) {
|
|
243
250
|
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
244
251
|
}
|
|
252
|
+
let authorVaultSlug;
|
|
253
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
254
|
+
authorVaultSlug = resolveVaultSlug(opts);
|
|
255
|
+
}
|
|
245
256
|
const body = {};
|
|
246
257
|
if (opts.title != null)
|
|
247
258
|
body.title = opts.title;
|
|
248
|
-
if (opts.content != null)
|
|
249
|
-
|
|
259
|
+
if (opts.content != null) {
|
|
260
|
+
const content = readContent(opts.content);
|
|
261
|
+
if (opts.autoAttachments && authorVaultSlug) {
|
|
262
|
+
const token = await getValidToken();
|
|
263
|
+
const links = extractWikiLinks(content);
|
|
264
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
265
|
+
}
|
|
266
|
+
body.content = content;
|
|
267
|
+
}
|
|
250
268
|
if (opts.richText != null) {
|
|
251
269
|
let parsed;
|
|
252
270
|
try {
|
|
@@ -257,8 +275,8 @@ export function registerGlobalCommand(program) {
|
|
|
257
275
|
}
|
|
258
276
|
body.richText = parsed;
|
|
259
277
|
}
|
|
260
|
-
if (
|
|
261
|
-
body.authorVaultSlug =
|
|
278
|
+
if (authorVaultSlug !== undefined)
|
|
279
|
+
body.authorVaultSlug = authorVaultSlug;
|
|
262
280
|
const resp = (await apiPatch(`/posts/${postId}`, body));
|
|
263
281
|
const post = unwrapResp(resp);
|
|
264
282
|
if (isJsonMode(global)) {
|
|
@@ -282,9 +300,11 @@ export function registerGlobalCommand(program) {
|
|
|
282
300
|
// ── Reply ──
|
|
283
301
|
global
|
|
284
302
|
.command("create-reply <postId>")
|
|
285
|
-
.description("
|
|
303
|
+
.description("Create a reply to a post in the global feed.")
|
|
286
304
|
.option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
|
|
287
305
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
306
|
+
.option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug). Also used as upload destination for --auto-attachments.")
|
|
307
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before posting (also attributes the reply to that vault)")
|
|
288
308
|
.action(async (postId, opts) => {
|
|
289
309
|
if (!opts.content && !opts.richText) {
|
|
290
310
|
throw new Error("Provide either --content or --rich-text.");
|
|
@@ -292,9 +312,20 @@ export function registerGlobalCommand(program) {
|
|
|
292
312
|
if (opts.content && opts.richText) {
|
|
293
313
|
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
294
314
|
}
|
|
315
|
+
let authorVaultSlug;
|
|
316
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
317
|
+
authorVaultSlug = resolveVaultSlug(opts);
|
|
318
|
+
}
|
|
295
319
|
const body = {};
|
|
296
|
-
if (opts.content != null)
|
|
297
|
-
|
|
320
|
+
if (opts.content != null) {
|
|
321
|
+
const content = readContent(opts.content);
|
|
322
|
+
if (opts.autoAttachments && authorVaultSlug) {
|
|
323
|
+
const token = await getValidToken();
|
|
324
|
+
const links = extractWikiLinks(content);
|
|
325
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
326
|
+
}
|
|
327
|
+
body.content = content;
|
|
328
|
+
}
|
|
298
329
|
if (opts.richText != null) {
|
|
299
330
|
let parsed;
|
|
300
331
|
try {
|
|
@@ -305,6 +336,8 @@ export function registerGlobalCommand(program) {
|
|
|
305
336
|
}
|
|
306
337
|
body.richText = parsed;
|
|
307
338
|
}
|
|
339
|
+
if (authorVaultSlug)
|
|
340
|
+
body.authorVaultSlug = authorVaultSlug;
|
|
308
341
|
const resp = (await apiPost(`/posts/${postId}/replies`, body));
|
|
309
342
|
const reply = unwrapResp(resp);
|
|
310
343
|
if (isJsonMode(global)) {
|
|
@@ -316,12 +349,45 @@ export function registerGlobalCommand(program) {
|
|
|
316
349
|
global
|
|
317
350
|
.command("edit-reply <replyId>")
|
|
318
351
|
.description("Edit a reply you authored in the global feed.")
|
|
319
|
-
.
|
|
352
|
+
.option("--content <content>", "New reply content (markdown supported, use \"-\" for stdin)")
|
|
353
|
+
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
354
|
+
.option("--vault-slug <vaultSlug>", "Attribute the reply to this vault (sets authorVaultSlug). Also used as upload destination for --auto-attachments.")
|
|
355
|
+
.option("--auto-attachments", "Upload wiki-linked [[files]] to webdrive before editing (also attributes the reply to that vault)")
|
|
320
356
|
.action(async (replyId, opts) => {
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
content,
|
|
324
|
-
}
|
|
357
|
+
const wantsVaultChange = !!(opts.vaultSlug || opts.autoAttachments);
|
|
358
|
+
if (opts.content == null && opts.richText == null && !wantsVaultChange) {
|
|
359
|
+
throw new Error("Provide at least --content, --rich-text, or --vault-slug to update.");
|
|
360
|
+
}
|
|
361
|
+
if (opts.content && opts.richText) {
|
|
362
|
+
throw new Error("--content and --rich-text are mutually exclusive.");
|
|
363
|
+
}
|
|
364
|
+
let authorVaultSlug;
|
|
365
|
+
if (opts.vaultSlug || opts.autoAttachments) {
|
|
366
|
+
authorVaultSlug = resolveVaultSlug(opts);
|
|
367
|
+
}
|
|
368
|
+
const body = {};
|
|
369
|
+
if (opts.content != null) {
|
|
370
|
+
const content = readContent(opts.content);
|
|
371
|
+
if (opts.autoAttachments && authorVaultSlug) {
|
|
372
|
+
const token = await getValidToken();
|
|
373
|
+
const links = extractWikiLinks(content);
|
|
374
|
+
await uploadAttachments(authorVaultSlug, links, token, { addToSyncfiles: true });
|
|
375
|
+
}
|
|
376
|
+
body.content = content;
|
|
377
|
+
}
|
|
378
|
+
if (opts.richText != null) {
|
|
379
|
+
let parsed;
|
|
380
|
+
try {
|
|
381
|
+
parsed = JSON.parse(opts.richText);
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
throw new Error("Invalid --rich-text JSON.");
|
|
385
|
+
}
|
|
386
|
+
body.richText = parsed;
|
|
387
|
+
}
|
|
388
|
+
if (authorVaultSlug !== undefined)
|
|
389
|
+
body.authorVaultSlug = authorVaultSlug;
|
|
390
|
+
const resp = (await apiPatch(`/posts/replies/${replyId}`, body));
|
|
325
391
|
const reply = unwrapResp(resp);
|
|
326
392
|
if (isJsonMode(global)) {
|
|
327
393
|
jsonOut(reply);
|
|
@@ -335,7 +401,7 @@ export function registerGlobalCommand(program) {
|
|
|
335
401
|
.action(async (replyId) => {
|
|
336
402
|
await apiDelete(`/posts/replies/${replyId}`);
|
|
337
403
|
if (isJsonMode(global)) {
|
|
338
|
-
jsonOut({ replyId });
|
|
404
|
+
jsonOut({ id: replyId });
|
|
339
405
|
return;
|
|
340
406
|
}
|
|
341
407
|
console.log(`Reply ${replyId} deleted.`);
|