@gobi-ai/cli 2.0.35 → 2.0.39
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 +30 -19
- package/dist/commands/artifact.js +14 -6
- package/dist/commands/global.js +2 -2
- package/dist/commands/personal.js +31 -2
- package/dist/commands/sense.js +162 -111
- package/dist/commands/space.js +42 -2
- package/dist/commands/utils.js +10 -1
- package/dist/main.js +2 -4
- package/package.json +1 -1
- package/skills/gobi-artifact/SKILL.md +52 -30
- package/skills/gobi-artifact/references/personal.md +42 -0
- package/skills/gobi-artifact/references/space.md +42 -0
- package/skills/gobi-core/SKILL.md +5 -4
- package/skills/gobi-homepage/SKILL.md +1 -1
- package/skills/gobi-media/SKILL.md +2 -2
- package/skills/gobi-sense/SKILL.md +61 -11
- package/skills/gobi-sense/references/personal.md +50 -0
- package/skills/gobi-sense/references/space.md +53 -0
- package/skills/gobi-space/SKILL.md +5 -5
- package/skills/gobi-space/references/global.md +2 -2
- package/skills/gobi-space/references/personal.md +66 -2
- package/skills/gobi-space/references/space.md +2 -2
- package/skills/gobi-vault/SKILL.md +2 -2
- package/skills/gobi-artifact/references/artifact.md +0 -142
- package/skills/gobi-sense/references/sense.md +0 -41
|
@@ -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.39",
|
|
8
8
|
"plugins": [
|
|
9
9
|
{
|
|
10
10
|
"name": "gobi",
|
|
11
11
|
"description": "Manage the Gobi collaborative knowledge platform from the command line. Publish vault profiles, create posts and replies, generate images and videos.",
|
|
12
|
-
"version": "2.0.
|
|
12
|
+
"version": "2.0.39",
|
|
13
13
|
"author": {
|
|
14
14
|
"name": "gobi-ai"
|
|
15
15
|
},
|
package/README.md
CHANGED
|
@@ -63,7 +63,7 @@ Each setup step unlocks a different family of commands — run only the ones the
|
|
|
63
63
|
| Step | Unlocks |
|
|
64
64
|
|------|---------|
|
|
65
65
|
| `gobi auth login` | All authenticated commands |
|
|
66
|
-
| `gobi vault init` | Every `gobi vault …` command (`publish`, `unpublish`, `sync`); also lets
|
|
66
|
+
| `gobi vault init` | Every `gobi vault …` command (`publish`, `unpublish`, `sync`); also lets `<scope> artifact create --auto-attachments` resolve that vault automatically |
|
|
67
67
|
| `gobi space warp` | Every `gobi space …` command without needing `--space-slug` |
|
|
68
68
|
|
|
69
69
|
---
|
|
@@ -218,34 +218,45 @@ Private posts and replies visible only to you. Same `Post` data model and subcom
|
|
|
218
218
|
| `gobi personal edit-reply <replyId> [--content <c>] [--rich-text <json>]` | Edit a reply you authored |
|
|
219
219
|
| `gobi personal delete-reply <replyId>` | Delete a reply you authored |
|
|
220
220
|
|
|
221
|
-
### Sense
|
|
221
|
+
### Sense (activities & conversations)
|
|
222
222
|
|
|
223
|
-
Activity and
|
|
223
|
+
Activity and conversation data captured by Gobi Sense (the wearable) and the mobile app, then ingested by the cloud pipeline. Read-only. See the `gobi-sense` skill for full workflows.
|
|
224
|
+
|
|
225
|
+
Like posts and artifacts, Sense data is **scoped to a space**: the subcommands live under `gobi personal …` (your personal space) and `gobi space …` (the active team space — `gobi space warp <slug>` or `gobi space --space-slug <slug> …`). `<scope>` below is `personal` or `space`.
|
|
226
|
+
|
|
227
|
+
- **activities** — what you were doing (category + details, start/end times). In a team space, every member's activities show up, attributed to their recorder.
|
|
228
|
+
- **conversations** — phone-mic Audio Logs plus Sense-detected conversations, each with a transcript and auto-generated summary. In a team space, every member's conversations show up, attributed to their recorder (transcript/audio stay owner-only). (This replaces the old `list-transcriptions` — transcriptions were unified into conversations.)
|
|
224
229
|
|
|
225
230
|
| Command | Description |
|
|
226
231
|
|---------|-------------|
|
|
227
|
-
| `gobi
|
|
228
|
-
| `gobi
|
|
232
|
+
| `gobi <scope> activities list [--limit N] [--before <cursor>] [--mine]` | List Sense activities in this scope (newest first) |
|
|
233
|
+
| `gobi <scope> activities get <activityId>` | Get one activity's details |
|
|
234
|
+
| `gobi <scope> activities transcript <activityId>` | Get an activity's transcript (owner-only) |
|
|
235
|
+
| `gobi <scope> conversations list [--limit N] [--before <cursor>] [--mine]` | List conversations captured in this scope (newest first) |
|
|
236
|
+
| `gobi <scope> conversations transcript <conversationId>` | Get a conversation's transcript and summary |
|
|
237
|
+
| `gobi <scope> conversations audio <conversationId>` | Get a signed URL for the recording (owner-only) |
|
|
229
238
|
|
|
230
|
-
|
|
239
|
+
`gobi space …` lists are a complete, fully-paginated per-space history (every member's records); add `--mine` to restrict either `list` to records you recorded. `gobi personal conversations list` is filtered from the user-global conversations feed, so it shows your recent personal conversations rather than a fully paginated history (`gobi personal activities list` is fully paginated).
|
|
231
240
|
|
|
232
241
|
### Artifacts
|
|
233
242
|
|
|
234
243
|
An *artifact* is a versioned, human-owned creation attached to posts. Kinds: `image | video | gif | markdown | meeting_summary`. Markdown kinds carry a body; media kinds carry an uploaded file. Revisions form a draft/published tree (at most one published per artifact). Markdown kinds store `metadata.vaultSlug` for `[[wikilink]]` resolution. See the `gobi-artifact` skill for full workflows.
|
|
235
244
|
|
|
245
|
+
Artifacts are **scoped to a space**: the subcommands live under `gobi personal artifact …` (your personal space) and `gobi space artifact …` (the active team space — `gobi space warp <slug>` or `gobi space --space-slug <slug> artifact …`). `<scope>` below is `personal` or `space`.
|
|
246
|
+
|
|
236
247
|
| Command | Description |
|
|
237
248
|
|---------|-------------|
|
|
238
|
-
| `gobi artifact list [--kind <k>] [--limit N]` | List
|
|
239
|
-
| `gobi artifact get <artifactId>` | Get one artifact with its current revision |
|
|
240
|
-
| `gobi artifact create --kind <k> [--file <path> \| --content <md>] [--title <t>] [--vault-slug <slug>] [--post-id <id>] [--auto-attachments] [--change-note <note>]` | Create an artifact. markdown/meeting_summary take a body via `--file`, `--content`, or stdin (`-`); image/gif/video upload `--file`. `--post-id` attaches it to a post (appends, doesn't clobber). `--auto-attachments` (markdown) uploads `[[wikilinks]]` to `--vault-slug`. |
|
|
241
|
-
| `gobi artifact revise <artifactId> [--file <path> \| --content <md>] [--change-note <note>] [--from <revisionId>] [--auto-attachments]` | Add a draft revision. `--from` branches off a specific revision. `--auto-attachments` reuses the artifact's stored `metadata.vaultSlug`. |
|
|
242
|
-
| `gobi artifact publish <artifactId> --revision <revisionId>` | Publish a revision (the artifact's single published revision) |
|
|
243
|
-
| `gobi artifact revert <artifactId> --to <revisionId>` | Move the published pointer to an earlier revision |
|
|
244
|
-
| `gobi artifact history <artifactId>` | List the full revision tree (owner only) |
|
|
245
|
-
| `gobi artifact download <artifactId> [--revision <revisionId>] [--out <path>]` | Download a revision's content (markdown body to file/stdout; media bytes to file). Defaults to the current revision. |
|
|
246
|
-
| `gobi artifact delete <artifactId>` | Delete an artifact and its revision tree |
|
|
247
|
-
|
|
248
|
-
Attach an artifact to a post at creation time with `gobi artifact create --post-id <postId>` (it merges into the post's existing artifacts without clobbering them).
|
|
249
|
+
| `gobi <scope> artifact list [--kind <k>] [--limit N]` | List this scope's artifacts (newest first) |
|
|
250
|
+
| `gobi <scope> artifact get <artifactId>` | Get one artifact with its current revision |
|
|
251
|
+
| `gobi <scope> artifact create --kind <k> [--file <path> \| --content <md>] [--title <t>] [--vault-slug <slug>] [--post-id <id>] [--auto-attachments] [--change-note <note>]` | Create an artifact in this scope. markdown/meeting_summary take a body via `--file`, `--content`, or stdin (`-`); image/gif/video upload `--file`. `--post-id` attaches it to a post (appends, doesn't clobber). `--auto-attachments` (markdown) uploads `[[wikilinks]]` to `--vault-slug`. |
|
|
252
|
+
| `gobi <scope> artifact revise <artifactId> [--file <path> \| --content <md>] [--change-note <note>] [--from <revisionId>] [--auto-attachments]` | Add a draft revision. `--from` branches off a specific revision. `--auto-attachments` reuses the artifact's stored `metadata.vaultSlug`. |
|
|
253
|
+
| `gobi <scope> artifact publish <artifactId> --revision <revisionId>` | Publish a revision (the artifact's single published revision) |
|
|
254
|
+
| `gobi <scope> artifact revert <artifactId> --to <revisionId>` | Move the published pointer to an earlier revision |
|
|
255
|
+
| `gobi <scope> artifact history <artifactId>` | List the full revision tree (owner only) |
|
|
256
|
+
| `gobi <scope> artifact download <artifactId> [--revision <revisionId>] [--out <path>]` | Download a revision's content (markdown body to file/stdout; media bytes to file). Defaults to the current revision. |
|
|
257
|
+
| `gobi <scope> artifact delete <artifactId>` | Delete an artifact and its revision tree |
|
|
258
|
+
|
|
259
|
+
Attach an artifact to a post at creation time with `gobi <scope> artifact create --post-id <postId>` (it merges into the post's existing artifacts without clobbering them).
|
|
249
260
|
|
|
250
261
|
### Media generation
|
|
251
262
|
|
|
@@ -303,9 +314,9 @@ The CLI ships a `.claude-plugin/` manifest with skills that wrap the command gro
|
|
|
303
314
|
| `gobi-core` | Auth, update, space list/warp |
|
|
304
315
|
| `gobi-vault` | `gobi vault init/list/publish/unpublish/sync` |
|
|
305
316
|
| `gobi-space` | `gobi space …`, `gobi global …`, and `gobi personal …` |
|
|
306
|
-
| `gobi-artifact` | `gobi artifact …` |
|
|
317
|
+
| `gobi-artifact` | `gobi personal artifact …` and `gobi space artifact …` |
|
|
307
318
|
| `gobi-media` | `gobi media …` |
|
|
308
|
-
| `gobi-sense` | `gobi
|
|
319
|
+
| `gobi-sense` | `gobi personal activities/conversations …` and `gobi space activities/conversations …` |
|
|
309
320
|
| `gobi-homepage` | Building custom HTML homepages with `window.gobi` |
|
|
310
321
|
|
|
311
322
|
Each skill's `SKILL.md` is hand-written orientation; `references/` is regenerated from `--help` output by `npm run generate-skill-docs`.
|
|
@@ -135,11 +135,11 @@ function printArtifact(a) {
|
|
|
135
135
|
console.log(` mediaUrl: ${rev.mediaUrl}`);
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
138
|
+
// Registers the full `artifact` subcommand tree under `parent` (a `gobi space`
|
|
139
|
+
// or `gobi personal` group). Moved here from a top-level `gobi artifact` group
|
|
140
|
+
// so artifacts are scoped to a space (team or personal), matching posts.
|
|
141
|
+
export function registerArtifactSubcommands(parent, scope, description) {
|
|
142
|
+
const artifact = parent.command("artifact").description(description);
|
|
143
143
|
// ── Create ──
|
|
144
144
|
artifact
|
|
145
145
|
.command("create")
|
|
@@ -158,6 +158,10 @@ export function registerArtifactCommand(program) {
|
|
|
158
158
|
throw new Error(`--kind must be one of: ${ALL_KINDS.join(", ")}`);
|
|
159
159
|
}
|
|
160
160
|
const body = { kind };
|
|
161
|
+
// Scope the new artifact to this group's space (team) or personal space.
|
|
162
|
+
const { spaceSlug } = scope.resolve();
|
|
163
|
+
if (spaceSlug)
|
|
164
|
+
body.spaceSlug = spaceSlug;
|
|
161
165
|
if (opts.title != null)
|
|
162
166
|
body.title = opts.title;
|
|
163
167
|
if (opts.vaultSlug)
|
|
@@ -421,11 +425,15 @@ export function registerArtifactCommand(program) {
|
|
|
421
425
|
// ── List ──
|
|
422
426
|
artifact
|
|
423
427
|
.command("list")
|
|
424
|
-
.description("List
|
|
428
|
+
.description("List this scope's artifacts (newest first).")
|
|
425
429
|
.option("--kind <kind>", `Filter by kind: ${ALL_KINDS.join(" | ")}`)
|
|
426
430
|
.option("--limit <n>", "Max items to return")
|
|
427
431
|
.action(async (opts) => {
|
|
428
432
|
const params = {};
|
|
433
|
+
// Scope the listing to this group's space (team) or personal space.
|
|
434
|
+
const { spaceSlug } = scope.resolve();
|
|
435
|
+
if (spaceSlug)
|
|
436
|
+
params.spaceSlug = spaceSlug;
|
|
429
437
|
if (opts.kind) {
|
|
430
438
|
if (!ALL_KINDS.includes(opts.kind)) {
|
|
431
439
|
throw new Error(`--kind must be one of: ${ALL_KINDS.join(", ")}`);
|
package/dist/commands/global.js
CHANGED
|
@@ -198,7 +198,7 @@ export function registerGlobalCommand(program) {
|
|
|
198
198
|
.option("--title <title>", "Title of the post")
|
|
199
199
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
200
200
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
201
|
-
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
201
|
+
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
202
202
|
.option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
|
|
203
203
|
.option("--repost-post-id <postId>", "Wrap an existing top-level post as the embedded card on this new post. Composes with --content / --rich-text / --attach (the wrapping author's text + media render above the embedded card). Reposts-of-reposts are collapsed to the transitive root server-side. The referenced post must exist, not be deleted, and not itself be a reply.")
|
|
204
204
|
.action(async (opts) => {
|
|
@@ -264,7 +264,7 @@ export function registerGlobalCommand(program) {
|
|
|
264
264
|
.option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
|
|
265
265
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
266
266
|
.option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
|
|
267
|
-
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
267
|
+
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
268
268
|
.action(async (postId, opts) => {
|
|
269
269
|
const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
|
|
270
270
|
const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { apiGet, apiPost, apiPatch, apiPut, apiDelete } from "../client.js";
|
|
2
2
|
import { buildMentionMap, formatAttachmentLines, formatAttachmentSummary, formatPostLabel, formatReactionChips, formatReplyLine, isJsonMode, jsonOut, postBodyText, readStdin, unwrapResp, } from "./utils.js";
|
|
3
3
|
import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
|
|
4
|
+
import { registerArtifactSubcommands } from "./artifact.js";
|
|
5
|
+
import { registerActivitiesSubcommands, registerConversationsSubcommands, } from "./sense.js";
|
|
4
6
|
function readContent(value) {
|
|
5
7
|
if (value === "-")
|
|
6
8
|
return readStdin();
|
|
@@ -254,7 +256,7 @@ export function registerPersonalCommand(program) {
|
|
|
254
256
|
.option("--title <title>", "Title of the post")
|
|
255
257
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
256
258
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
257
|
-
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
259
|
+
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
258
260
|
.option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
|
|
259
261
|
.option("--repost-post-id <postId>", "Wrap an existing top-level post as the embedded card on this new private post. The referenced post must be visible to you (your own personal-space post, a global-feed post, or a post in a space you're a member of). Reposting someone else's personal-space post returns 404.")
|
|
260
262
|
.action(async (opts) => {
|
|
@@ -324,7 +326,7 @@ export function registerPersonalCommand(program) {
|
|
|
324
326
|
.option("--content <content>", "New content (markdown supported, use \"-\" for stdin)")
|
|
325
327
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
326
328
|
.option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
|
|
327
|
-
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
329
|
+
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi personal artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
328
330
|
.action(async (postId, opts) => {
|
|
329
331
|
const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
|
|
330
332
|
const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
|
|
@@ -500,4 +502,31 @@ export function registerPersonalCommand(program) {
|
|
|
500
502
|
console.log(`Removed ${emoji} reaction from ${postId}.` +
|
|
501
503
|
(chips ? `\n Now: ${chips}` : ""));
|
|
502
504
|
});
|
|
505
|
+
// ── Artifacts (scoped to your personal space) ──
|
|
506
|
+
registerArtifactSubcommands(personal, { resolve: () => ({}) }, "Versioned creations attached to posts, scoped to your personal space (visible " +
|
|
507
|
+
"only to you). Kinds: image | video | gif | markdown | meeting_summary. Always " +
|
|
508
|
+
"human-owned; revisions form a draft/published tree (one published per artifact).");
|
|
509
|
+
// ── Sense: activities + conversations (scoped to your personal space) ──
|
|
510
|
+
const senseScope = {
|
|
511
|
+
label: "personal",
|
|
512
|
+
listActivities: async (params) => {
|
|
513
|
+
const resp = (await apiGet("/app/activities", params));
|
|
514
|
+
return {
|
|
515
|
+
items: (resp.activities || []),
|
|
516
|
+
pagination: resp.pagination,
|
|
517
|
+
};
|
|
518
|
+
},
|
|
519
|
+
// `/app/conversations` is user-global (all scopes, newest ~50, no paging);
|
|
520
|
+
// filter to the personal scope (spaceId 0). Params are ignored — the endpoint
|
|
521
|
+
// takes none.
|
|
522
|
+
listConversations: async () => {
|
|
523
|
+
const resp = (await apiGet("/app/conversations"));
|
|
524
|
+
const all = (resp.conversations || []);
|
|
525
|
+
return { items: all.filter((c) => Number(c.spaceId ?? 0) === 0) };
|
|
526
|
+
},
|
|
527
|
+
};
|
|
528
|
+
registerActivitiesSubcommands(personal, senseScope, "Your personal Sense activities (what you were doing, from the wearable/app), " +
|
|
529
|
+
"browse-only. Recorded in your personal space (visible only to you).");
|
|
530
|
+
registerConversationsSubcommands(personal, senseScope, "Your personal Sense conversations (phone-mic Audio Logs + detected conversations), " +
|
|
531
|
+
"browse-only. Recorded in your personal space (visible only to you).");
|
|
503
532
|
}
|
package/dist/commands/sense.js
CHANGED
|
@@ -1,129 +1,180 @@
|
|
|
1
1
|
import { apiGet } from "../client.js";
|
|
2
2
|
import { isJsonMode, jsonOut } from "./utils.js";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
3
|
+
// One-liner for a transcript turn. The backend resolves speaker identities at
|
|
4
|
+
// read time into `speakerLabel` (a managed-voice name, "Me", a session-stable
|
|
5
|
+
// "Speaker N", or "Unknown"); fall back to the raw diarization token only if
|
|
6
|
+
// it's somehow absent.
|
|
7
|
+
function formatTurnLine(t) {
|
|
8
|
+
const who = t.speakerLabel || t.speaker || "Unknown";
|
|
9
|
+
return `- ${who} (${t.timestamp}): ${t.text}`;
|
|
10
|
+
}
|
|
11
|
+
function printTranscriptTurns(turns) {
|
|
12
|
+
const cleaned = (turns || [])
|
|
13
|
+
.map((t) => ({ ...t, text: String(t.text ?? "").trim() }))
|
|
14
|
+
.filter((t) => t.text);
|
|
15
|
+
if (!cleaned.length) {
|
|
16
|
+
console.log("No transcript available.");
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
console.log(`Transcript (${cleaned.length} turns):\n` + cleaned.map(formatTurnLine).join("\n"));
|
|
20
|
+
}
|
|
21
|
+
function formatActivityLine(a) {
|
|
22
|
+
const end = a.end_time ? ` → ${a.end_time}` : " → ongoing";
|
|
23
|
+
const details = a.details ? `: ${a.details}` : "";
|
|
24
|
+
// Space activities carry the recorder(s); personal ones don't.
|
|
25
|
+
const recorders = Array.isArray(a.recorders) && a.recorders.length
|
|
26
|
+
? ` by ${a.recorders
|
|
27
|
+
.map((r) => r.name || `user ${r.id}`)
|
|
28
|
+
.join(", ")}`
|
|
29
|
+
: "";
|
|
30
|
+
return `- [${a.id}] ${a.category ?? "activity"}${details} (${a.start_time}${end})${recorders}`;
|
|
31
|
+
}
|
|
32
|
+
function formatConversationLine(c) {
|
|
33
|
+
const durMs = typeof c.durationMs === "number" ? c.durationMs : null;
|
|
34
|
+
const dur = durMs != null ? ` (${Math.max(1, Math.round(durMs / 60000))}m)` : "";
|
|
35
|
+
const cat = c.category ? ` — ${c.category}` : "";
|
|
36
|
+
const status = c.status ? ` [${c.status}]` : "";
|
|
37
|
+
// Space conversations carry the recorder (whose conversation it is); personal
|
|
38
|
+
// ones don't.
|
|
39
|
+
const rec = c.recorder && typeof c.recorder === "object"
|
|
40
|
+
? ` by ${c.recorder.name ?? "someone"}`
|
|
41
|
+
: "";
|
|
42
|
+
return `- [${c.id}] ${c.source ?? "conversation"}${status}${cat} (${c.startTime}${dur})${rec}`;
|
|
43
|
+
}
|
|
44
|
+
function paginationFooter(pagination) {
|
|
45
|
+
return pagination?.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
|
|
46
|
+
}
|
|
47
|
+
// ── Activities ──
|
|
48
|
+
//
|
|
49
|
+
// Registers the `activities` subcommand tree under `parent` (a `gobi space` or
|
|
50
|
+
// `gobi personal` group). Replaces the old top-level `gobi sense list-activities`.
|
|
51
|
+
export function registerActivitiesSubcommands(parent, scope, description) {
|
|
52
|
+
const activities = parent.command("activities").description(description);
|
|
53
|
+
// ── List ──
|
|
54
|
+
activities
|
|
55
|
+
.command("list")
|
|
56
|
+
.description("List Sense activities in this scope (newest first).")
|
|
57
|
+
.option("--limit <n>", "Max items to return (default 30, max 100)")
|
|
58
|
+
.option("--before <cursor>", "Pagination cursor from a previous response (nextCursor)")
|
|
59
|
+
.option("--mine", "Only activities you recorded (space scope; no-op for personal, already yours)")
|
|
13
60
|
.action(async (opts) => {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const lastSeenTime = resp.latestTimestamp;
|
|
22
|
-
// Pull out the last activity with null end_time as "last_activity"
|
|
23
|
-
let lastActivityIdx = -1;
|
|
24
|
-
for (let i = allActivities.length - 1; i >= 0; i--) {
|
|
25
|
-
if (allActivities[i].end_time == null) {
|
|
26
|
-
lastActivityIdx = i;
|
|
27
|
-
break;
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
const last_activity = lastActivityIdx !== -1 ? allActivities[lastActivityIdx] : undefined;
|
|
31
|
-
const activities = lastActivityIdx !== -1
|
|
32
|
-
? allActivities.filter((_, i) => i !== lastActivityIdx)
|
|
33
|
-
: allActivities;
|
|
34
|
-
if (isJsonMode(sense)) {
|
|
35
|
-
jsonOut({ activities, last_activity, lastSeenTime });
|
|
61
|
+
const { items, pagination } = await scope.listActivities({
|
|
62
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
63
|
+
before: opts.before,
|
|
64
|
+
mine: opts.mine,
|
|
65
|
+
});
|
|
66
|
+
if (isJsonMode(activities)) {
|
|
67
|
+
jsonOut({ activities: items, pagination: pagination ?? {} });
|
|
36
68
|
return;
|
|
37
69
|
}
|
|
38
|
-
if (!
|
|
70
|
+
if (!items.length) {
|
|
39
71
|
console.log("No activities found.");
|
|
40
|
-
if (lastSeenTime)
|
|
41
|
-
console.log(`Latest data available: ${lastSeenTime}`);
|
|
42
72
|
return;
|
|
43
73
|
}
|
|
44
|
-
|
|
45
|
-
|
|
74
|
+
console.log(`Activities (${items.length} items, newest first):\n` +
|
|
75
|
+
items.map(formatActivityLine).join("\n") +
|
|
76
|
+
paginationFooter(pagination));
|
|
77
|
+
});
|
|
78
|
+
// ── Get (by id; scope-independent) ──
|
|
79
|
+
activities
|
|
80
|
+
.command("get <activityId>")
|
|
81
|
+
.description("Get one activity's details (visible to you if you recorded it or are a member of its space).")
|
|
82
|
+
.action(async (activityId) => {
|
|
83
|
+
const a = (await apiGet(`/app/activity/${activityId}`));
|
|
84
|
+
if (isJsonMode(activities)) {
|
|
85
|
+
jsonOut(a);
|
|
86
|
+
return;
|
|
46
87
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
88
|
+
console.log(`Activity ${a.id}\n` +
|
|
89
|
+
` category: ${a.category ?? "(none)"}\n` +
|
|
90
|
+
(a.details ? ` details: ${a.details}\n` : "") +
|
|
91
|
+
` start: ${a.start_time}\n` +
|
|
92
|
+
` end: ${a.end_time ?? "ongoing"}`);
|
|
93
|
+
});
|
|
94
|
+
// ── Transcript (by id; owner-only) ──
|
|
95
|
+
activities
|
|
96
|
+
.command("transcript <activityId>")
|
|
97
|
+
.description("Get an activity's transcript (owner-only; 403 for other space members).")
|
|
98
|
+
.action(async (activityId) => {
|
|
99
|
+
const resp = (await apiGet(`/app/activity/${activityId}/transcript`));
|
|
100
|
+
const turns = (resp.turns || []);
|
|
101
|
+
if (isJsonMode(activities)) {
|
|
102
|
+
jsonOut({ turns });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
printTranscriptTurns(turns);
|
|
55
106
|
});
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
107
|
+
}
|
|
108
|
+
// ── Conversations ──
|
|
109
|
+
//
|
|
110
|
+
// Registers the `conversations` subcommand tree under `parent`. The `space`
|
|
111
|
+
// group lists via `/spaces/:slug/conversations` (every member's, keyset-paged);
|
|
112
|
+
// the `personal` group lists via the user-global `/app/conversations` filtered
|
|
113
|
+
// to the personal scope. Replaces the old top-level `gobi sense list-transcriptions`.
|
|
114
|
+
export function registerConversationsSubcommands(parent, scope, description) {
|
|
115
|
+
const conversations = parent.command("conversations").description(description);
|
|
116
|
+
// ── List ──
|
|
117
|
+
conversations
|
|
118
|
+
.command("list")
|
|
119
|
+
.description("List conversations captured in this scope (newest first).")
|
|
120
|
+
.option("--limit <n>", "Max items to return (default 30, max 100). Ignored for personal.")
|
|
121
|
+
.option("--before <cursor>", "Pagination cursor from a previous response (nextCursor). Space scope only.")
|
|
122
|
+
.option("--mine", "Only conversations you recorded (space scope; no-op for personal, already yours)")
|
|
62
123
|
.action(async (opts) => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!text)
|
|
76
|
-
continue;
|
|
77
|
-
allTurns.push({
|
|
78
|
-
speaker: String(turn.speaker ?? ""),
|
|
79
|
-
timestamp: String(turn.timestamp ?? ""),
|
|
80
|
-
text,
|
|
81
|
-
});
|
|
82
|
-
}
|
|
124
|
+
const { items, pagination } = await scope.listConversations({
|
|
125
|
+
limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
|
|
126
|
+
before: opts.before,
|
|
127
|
+
mine: opts.mine,
|
|
128
|
+
});
|
|
129
|
+
if (isJsonMode(conversations)) {
|
|
130
|
+
jsonOut({ conversations: items, pagination: pagination ?? {} });
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!items.length) {
|
|
134
|
+
console.log("No conversations found.");
|
|
135
|
+
return;
|
|
83
136
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
console.log(`Conversations (${items.length} items, newest first):\n` +
|
|
138
|
+
items.map(formatConversationLine).join("\n") +
|
|
139
|
+
paginationFooter(pagination));
|
|
140
|
+
});
|
|
141
|
+
// ── Transcript (by id) ──
|
|
142
|
+
conversations
|
|
143
|
+
.command("transcript <conversationId>")
|
|
144
|
+
.description("Get a conversation's transcript and summary (owner-only).")
|
|
145
|
+
.action(async (conversationId) => {
|
|
146
|
+
const resp = (await apiGet(`/app/conversations/${conversationId}/transcript`));
|
|
147
|
+
const turns = (resp.turns || []);
|
|
148
|
+
const summary = resp.summary || null;
|
|
149
|
+
if (isJsonMode(conversations)) {
|
|
150
|
+
jsonOut(resp);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (summary && (summary.category || summary.details)) {
|
|
154
|
+
console.log(`Summary: ${summary.category ?? "(uncategorized)"}` +
|
|
155
|
+
(summary.details ? `\n ${summary.details}` : ""));
|
|
156
|
+
console.log("");
|
|
157
|
+
}
|
|
158
|
+
if (resp.status && resp.status !== "ready") {
|
|
159
|
+
console.log(`Status: ${resp.status}`);
|
|
160
|
+
}
|
|
161
|
+
printTranscriptTurns(turns);
|
|
162
|
+
});
|
|
163
|
+
// ── Audio (by id; signed URL, owner-only) ──
|
|
164
|
+
conversations
|
|
165
|
+
.command("audio <conversationId>")
|
|
166
|
+
.description("Get a signed URL for a conversation's combined recording (owner-only; null for analyzer conversations).")
|
|
167
|
+
.action(async (conversationId) => {
|
|
168
|
+
const resp = (await apiGet(`/app/conversations/${conversationId}/audio`));
|
|
169
|
+
const url = resp.url ?? null;
|
|
170
|
+
if (isJsonMode(conversations)) {
|
|
171
|
+
jsonOut({ url });
|
|
116
172
|
return;
|
|
117
173
|
}
|
|
118
|
-
if (!
|
|
119
|
-
console.log("No
|
|
120
|
-
if (lastSeenTime)
|
|
121
|
-
console.log(`Latest data available: ${lastSeenTime}`);
|
|
174
|
+
if (!url) {
|
|
175
|
+
console.log("No audio available for this conversation.");
|
|
122
176
|
return;
|
|
123
177
|
}
|
|
124
|
-
|
|
125
|
-
console.log(`Transcriptions (${filtered.length} turns):\n` + lines.join("\n"));
|
|
126
|
-
if (lastSeenTime)
|
|
127
|
-
console.log(`Latest data available: ${lastSeenTime}`);
|
|
178
|
+
console.log(url);
|
|
128
179
|
});
|
|
129
180
|
}
|
package/dist/commands/space.js
CHANGED
|
@@ -3,6 +3,8 @@ import { apiGet, apiPost, apiPatch, apiPut, apiDelete } from "../client.js";
|
|
|
3
3
|
import { requireSpace, selectSpace, setSpaceRequirement, writeSpaceSetting, } from "./init.js";
|
|
4
4
|
import { buildMentionMap, formatAttachmentLines, formatAttachmentSummary, formatPostLabel, formatReactionChips, formatReplyLine, isJsonMode, jsonOut, postBodyText, readStdin, resolveSpaceSlug, unwrapResp, } from "./utils.js";
|
|
5
5
|
import { uploadPostAttachments, assertPostAttachmentMix, } from "../attachments.js";
|
|
6
|
+
import { registerArtifactSubcommands } from "./artifact.js";
|
|
7
|
+
import { registerActivitiesSubcommands, registerConversationsSubcommands, } from "./sense.js";
|
|
6
8
|
function readContent(value) {
|
|
7
9
|
if (value === "-")
|
|
8
10
|
return readStdin();
|
|
@@ -389,7 +391,7 @@ export function registerSpaceCommand(program) {
|
|
|
389
391
|
.option("--title <title>", "Title of the post")
|
|
390
392
|
.option("--content <content>", "Post content (markdown supported, use \"-\" for stdin)")
|
|
391
393
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
392
|
-
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
394
|
+
.option("--artifact <artifactId>", "Attach an existing artifact to the post (repeatable). Create artifacts with `gobi space artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
393
395
|
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
394
396
|
.option("--attach <file>", "Local media or document file to attach. Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files.", (value, prev = []) => [...prev, value], [])
|
|
395
397
|
.option("--repost-post-id <postId>", "Wrap an existing top-level post as the embedded card on this new post. Composes with --content / --rich-text / --attach (the wrapping author's text + media render above the embedded card). Reposts-of-reposts are collapsed to the transitive root server-side. The referenced post must exist, not be deleted, and not itself be a reply.")
|
|
@@ -462,7 +464,7 @@ export function registerSpaceCommand(program) {
|
|
|
462
464
|
.option("--rich-text <richText>", "Rich-text JSON array (mutually exclusive with --content)")
|
|
463
465
|
.option("--space-slug <spaceSlug>", "Space slug (overrides .gobi/settings.yaml)")
|
|
464
466
|
.option("--attach <file>", "Replace the post's media attachments with the given files (existing attachments are removed). Repeatable. Mix rule: up to 4 photos + up to 4 document files (pdf/md/txt/csv) OR 1 GIF OR 1 video. Size ceilings: 10MB photos / 15MB GIFs / 512MB video / 250MB files. Omit to leave attachments unchanged.", (value, prev = []) => [...prev, value], [])
|
|
465
|
-
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
467
|
+
.option("--artifact <artifactId>", "Replace the post's artifact attachments with the given artifact(s) (existing artifact attachments are removed). Repeatable. Omit to leave them unchanged. Create artifacts with `gobi space artifact create`.", (value, prev = []) => [...prev, value], [])
|
|
466
468
|
.action(async (postId, opts) => {
|
|
467
469
|
const wantsAttachChange = !!(opts.attach && opts.attach.length > 0);
|
|
468
470
|
const wantsArtifactChange = !!(opts.artifact && opts.artifact.length > 0);
|
|
@@ -724,4 +726,42 @@ export function registerSpaceCommand(program) {
|
|
|
724
726
|
}
|
|
725
727
|
console.log(`Channel members (${items.length}):\n` + lines.join("\n"));
|
|
726
728
|
});
|
|
729
|
+
// ── Artifacts (scoped to this space) ──
|
|
730
|
+
registerArtifactSubcommands(space, { resolve: () => ({ spaceSlug: resolveSpaceSlug(space) }) }, "Versioned creations attached to posts, scoped to this space (visible to its " +
|
|
731
|
+
"members). Kinds: image | video | gif | markdown | meeting_summary. Always " +
|
|
732
|
+
"human-owned; revisions form a draft/published tree (one published per artifact).");
|
|
733
|
+
// ── Sense: activities + conversations (scoped to this space) ──
|
|
734
|
+
//
|
|
735
|
+
// The conversations list endpoint is user-global (returns the caller's recent
|
|
736
|
+
// conversations across all scopes, each tagged with spaceId), so the scope
|
|
737
|
+
// resolves this space's numeric id — via GET /spaces/:slug — to filter it.
|
|
738
|
+
const senseScope = {
|
|
739
|
+
label: "space",
|
|
740
|
+
listActivities: async (params) => {
|
|
741
|
+
const q = { limit: params.limit, before: params.before };
|
|
742
|
+
if (params.mine)
|
|
743
|
+
q.mine = true;
|
|
744
|
+
const resp = (await apiGet(`/spaces/${resolveSpaceSlug(space)}/activities`, q));
|
|
745
|
+
return {
|
|
746
|
+
items: (resp.activities || []),
|
|
747
|
+
pagination: resp.pagination,
|
|
748
|
+
};
|
|
749
|
+
},
|
|
750
|
+
listConversations: async (params) => {
|
|
751
|
+
const q = { limit: params.limit, before: params.before };
|
|
752
|
+
if (params.mine)
|
|
753
|
+
q.mine = true;
|
|
754
|
+
const resp = (await apiGet(`/spaces/${resolveSpaceSlug(space)}/conversations`, q));
|
|
755
|
+
return {
|
|
756
|
+
items: (resp.conversations || []),
|
|
757
|
+
pagination: resp.pagination,
|
|
758
|
+
};
|
|
759
|
+
},
|
|
760
|
+
};
|
|
761
|
+
registerActivitiesSubcommands(space, senseScope, "This space's Sense activities — every member's, attributed to each recorder " +
|
|
762
|
+
"(browse-only). Use `gobi space --space-slug <slug> activities …` or set the " +
|
|
763
|
+
"active space with `gobi space warp`.");
|
|
764
|
+
registerConversationsSubcommands(space, senseScope, "This space's Sense conversations — every member's, attributed to each recorder " +
|
|
765
|
+
"(browse-only; transcript/audio stay owner-only). Use `gobi space --space-slug " +
|
|
766
|
+
"<slug> conversations …` or set the active space with `gobi space warp`.");
|
|
727
767
|
}
|