@gobi-ai/cli 2.0.36 → 2.0.40

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.
@@ -1,129 +1,180 @@
1
1
  import { apiGet } from "../client.js";
2
2
  import { isJsonMode, jsonOut } from "./utils.js";
3
- export function registerSenseCommand(program) {
4
- const sense = program
5
- .command("sense")
6
- .description("Sense commands (activities, transcriptions).");
7
- // ── Activities ──
8
- sense
9
- .command("list-activities")
10
- .description("List activity records within a time range.")
11
- .requiredOption("--start-time <iso>", "Start of time range (ISO 8601 UTC, e.g. 2026-03-20T00:00:00Z)")
12
- .requiredOption("--end-time <iso>", "End of time range (ISO 8601 UTC, e.g. 2026-03-20T23:59:59Z)")
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 params = {
15
- startTime: opts.startTime,
16
- endTime: opts.endTime,
17
- };
18
- const resp = (await apiGet("/app/activities", params));
19
- const strip = ({ id, device_id, created_at, updated_at, ...rest }) => rest;
20
- const allActivities = (resp.activities || []).map((a) => strip(a));
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 (!activities.length && !last_activity) {
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
- if (last_activity) {
45
- console.log(`Last activity: [${last_activity.device_id}] ${last_activity.category}: ${last_activity.details} (${last_activity.start_time} → ongoing)`);
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
- const lines = activities.map((a) => {
48
- const endStr = a.end_time ? ` ${a.end_time}` : "";
49
- return `- [${a.device_id}] ${a.category}: ${a.details} (${a.start_time}${endStr})`;
50
- });
51
- if (lines.length)
52
- console.log(`Activities (${activities.length} items):\n` + lines.join("\n"));
53
- if (lastSeenTime)
54
- console.log(`Latest data available: ${lastSeenTime}`);
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
- // ── Transcriptions ──
57
- sense
58
- .command("list-transcriptions")
59
- .description("List transcription records within a time range.")
60
- .requiredOption("--start-time <iso>", "Start of time range (ISO 8601 UTC, e.g. 2026-03-20T00:00:00Z)")
61
- .requiredOption("--end-time <iso>", "End of time range (ISO 8601 UTC, e.g. 2026-03-20T23:59:59Z)")
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 cross-scope `/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 params = {
64
- startTime: opts.startTime,
65
- endTime: opts.endTime,
66
- };
67
- const resp = (await apiGet("/app/transcriptions", params));
68
- const transcriptions = (resp.transcriptions || []);
69
- const lastSeenTime = resp.latestTimestamp;
70
- const allTurns = [];
71
- for (const t of transcriptions) {
72
- const rawTurns = (t.turns || []);
73
- for (const turn of rawTurns) {
74
- const text = String(turn.text ?? "").trim();
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
- // Filter to requested time range and sort by time.
85
- const startMs = new Date(opts.startTime).getTime();
86
- const endMs = new Date(opts.endTime).getTime();
87
- const filtered = allTurns
88
- .filter((t) => {
89
- const ts = new Date(t.timestamp).getTime();
90
- return ts >= startMs && ts <= endMs;
91
- })
92
- .sort((a, b) => a.timestamp.localeCompare(b.timestamp));
93
- // Friendly speaker labels: named voices → their name, the user → "Me",
94
- // anonymous voices (bare index, Person:/uv:) → a stable "Speaker N"
95
- // numbered by first appearance across the sorted transcript.
96
- const anonOrdinals = new Map();
97
- let nextOrdinal = 1;
98
- const labelFor = (token) => {
99
- const named = token.match(/^(.+):(\d+)$/);
100
- if (named) {
101
- const name = named[1].trim();
102
- if (name === "Me")
103
- return "Me";
104
- if (name && name !== "Person" && name !== "uv")
105
- return name;
106
- }
107
- if (!anonOrdinals.has(token))
108
- anonOrdinals.set(token, nextOrdinal++);
109
- return `Speaker ${anonOrdinals.get(token)}`;
110
- };
111
- if (isJsonMode(sense)) {
112
- jsonOut({
113
- transcriptions: filtered.map((t) => ({ ...t, speaker: labelFor(t.speaker) })),
114
- lastSeenTime,
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 (!filtered.length) {
119
- console.log("No transcriptions found.");
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
- const lines = filtered.map((t) => `- ${labelFor(t.speaker)} (${t.timestamp}): ${t.text}`);
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
  }
@@ -4,6 +4,7 @@ import { requireSpace, selectSpace, setSpaceRequirement, writeSpaceSetting, } fr
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
6
  import { registerArtifactSubcommands } from "./artifact.js";
7
+ import { registerActivitiesSubcommands, registerConversationsSubcommands, } from "./sense.js";
7
8
  function readContent(value) {
8
9
  if (value === "-")
9
10
  return readStdin();
@@ -729,4 +730,38 @@ export function registerSpaceCommand(program) {
729
730
  registerArtifactSubcommands(space, { resolve: () => ({ spaceSlug: resolveSpaceSlug(space) }) }, "Versioned creations attached to posts, scoped to this space (visible to its " +
730
731
  "members). Kinds: image | video | gif | markdown | meeting_summary. Always " +
731
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 spans all the user's scopes (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`.");
732
767
  }
@@ -28,8 +28,8 @@ function detectInstallMethod() {
28
28
  return "brew";
29
29
  }
30
30
  try {
31
- const npmGlobalDir = execSync("npm root -g", { encoding: "utf-8" }).trim();
32
- if (gobiBin && gobiBin.includes(npmGlobalDir.replace("/lib/node_modules", ""))) {
31
+ const npmRootDir = execSync("npm root -g", { encoding: "utf-8" }).trim();
32
+ if (gobiBin && gobiBin.includes(npmRootDir.replace("/lib/node_modules", ""))) {
33
33
  return "npm";
34
34
  }
35
35
  }
@@ -5,7 +5,7 @@ import { getSpaceSlug, getVaultSlug } from "./init.js";
5
5
  export function readStdin() {
6
6
  return readFileSync(0, "utf8");
7
7
  }
8
- // The global `--json` flag lives on the root program. Walk up the ancestry so
8
+ // The top-level `--json` flag lives on the root program. Walk up the ancestry so
9
9
  // this works regardless of how deeply the calling command is nested (e.g. a
10
10
  // leaf under `gobi space artifact …`, not just a direct child of the program).
11
11
  export function isJsonMode(cmd) {
package/dist/main.js CHANGED
@@ -5,10 +5,8 @@ import { ApiError, GobiError } from "./errors.js";
5
5
  import { registerAuthCommand } from "./commands/auth.js";
6
6
  import { commandRequiresSpace, commandRequiresVault, readSettings, } from "./commands/init.js";
7
7
  import { registerSpaceCommand } from "./commands/space.js";
8
- import { registerGlobalCommand } from "./commands/global.js";
9
8
  import { registerPersonalCommand } from "./commands/personal.js";
10
9
  import { registerVaultCommand } from "./commands/vault.js";
11
- import { registerSenseCommand } from "./commands/sense.js";
12
10
  import { registerUpdateCommand } from "./commands/update.js";
13
11
  import { registerMediaCommand } from "./commands/media.js";
14
12
  const require = createRequire(import.meta.url);
@@ -57,14 +55,12 @@ export async function cli() {
57
55
  // Register all command groups
58
56
  registerAuthCommand(program);
59
57
  registerSpaceCommand(program);
60
- registerGlobalCommand(program);
61
58
  registerPersonalCommand(program);
62
59
  registerVaultCommand(program);
63
- registerSenseCommand(program);
64
60
  registerUpdateCommand(program);
65
61
  registerMediaCommand(program);
66
- // Artifact subcommands live under `gobi space` and `gobi personal` (registered
67
- // by those groups), not as a top-level `gobi artifact` group.
62
+ // Artifact, activities, and conversations subcommands live under `gobi space`
63
+ // and `gobi personal` (registered by those groups), not as top-level groups.
68
64
  // Propagate helpWidth to all subcommands
69
65
  const helpWidth = process.stdout.columns || 200;
70
66
  for (const cmd of program.commands) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gobi-ai/cli",
3
- "version": "2.0.36",
3
+ "version": "2.0.40",
4
4
  "description": "CLI client for the Gobi collaborative knowledge platform",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -11,12 +11,12 @@ description: >-
11
11
  allowed-tools: Bash(gobi:*)
12
12
  metadata:
13
13
  author: gobi-ai
14
- version: "2.0.36"
14
+ version: "2.0.40"
15
15
  ---
16
16
 
17
17
  # gobi-artifact
18
18
 
19
- Gobi artifact commands for versioned, post-attachable creations (v2.0.36).
19
+ Gobi artifact commands for versioned, post-attachable creations (v2.0.40).
20
20
 
21
21
  Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
22
22
 
@@ -51,7 +51,7 @@ Markdown bodies can reference vault notes with `[[wikilinks]]`. Resolution again
51
51
 
52
52
  ## Important: JSON Mode
53
53
 
54
- For programmatic/agent usage, always pass `--json` as a **global** option (before everything else):
54
+ For programmatic/agent usage, always pass `--json` as a **top-level** option (before everything else):
55
55
 
56
56
  ```bash
57
57
  gobi --json personal artifact list --limit 20
@@ -3,8 +3,8 @@
3
3
  ```
4
4
  Usage: gobi personal [options] [command]
5
5
 
6
- Personal-space commands (private posts and replies visible only to you). Mirrors the `global` subcommand shape — posts/replies live in the same data model, scoped via personalSpaceUserId so they
7
- never surface on the public feed.
6
+ Personal-space commands (private posts and replies visible only to you). Posts/replies live in the same data model as space posts, scoped via personalSpaceUserId so they never surface on the public
7
+ feed.
8
8
 
9
9
  Options:
10
10
  -h, --help display help for command
@@ -8,12 +8,12 @@ description: >-
8
8
  allowed-tools: Bash(gobi:*)
9
9
  metadata:
10
10
  author: gobi-ai
11
- version: "2.0.36"
11
+ version: "2.0.40"
12
12
  ---
13
13
 
14
14
  # gobi-core
15
15
 
16
- Core CLI commands for the Gobi collaborative knowledge platform (v2.0.36).
16
+ Core CLI commands for the Gobi collaborative knowledge platform (v2.0.40).
17
17
 
18
18
  ## Prerequisites
19
19
 
@@ -38,7 +38,6 @@ brew tap gobi-ai/tap && brew install gobi
38
38
  ## Key Concepts
39
39
 
40
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`.
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.
42
41
  - **Space Post**: A post inside a community space.
43
42
  - **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.
44
43
  - **Artifact**: A versioned, human-owned creation (image, video, gif, markdown, or meeting_summary) attached to posts. Its revisions form a draft/published tree (at most one published). See the **gobi-artifact** skill.
@@ -72,19 +71,19 @@ gobi auth status
72
71
 
73
72
  | Command family | Needs vault in `.gobi`? | Needs space in `.gobi`? | Per-call override |
74
73
  |----------------|------------------------|------------------------|-------------------|
75
- | `auth …`, `update`, `artifact …`, `media …`, `sense …` | no | no | – |
74
+ | `auth …`, `update`, `media …`, `personal artifact/activities/conversations …` | no | no | – |
75
+ | `space artifact …` / `space activities …` / `space conversations …` | no | **yes** | parent `--space-slug <slug>` |
76
76
  | `vault publish` / `unpublish` / `sync` | **yes** | no | none — must run `gobi vault init` first |
77
77
  | `vault init` | no (it sets it up) | no | – |
78
78
  | `space list` / `warp [slug]` / `get [slug]` | no | no | – |
79
79
  | `space list-topics` / `feed` / `list-posts` / `get-post` / `create-post` / `edit-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` / `list-topic-posts` | no | **yes** | parent `--space-slug <slug>` |
80
- | `global feed` / `list-posts` / `get-post` / `create-post` / `edit-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` | no | no | – |
81
80
  | `personal feed` / `list-posts` / `get-post` / `create-post` / `edit-post` / `delete-post` / `create-reply` / `edit-reply` / `delete-reply` | no | no | – |
82
81
 
83
82
  When a command needs vault or space and neither `.gobi` nor an override flag provides it, the CLI prints a one-line warning before the command runs (e.g. `Vault not set. Run 'gobi vault init' first, or pass --vault-slug.`). The warning is suppressed under `--json`.
84
83
 
85
84
  ## Important: JSON Mode
86
85
 
87
- For programmatic/agent usage, always pass `--json` as a **global** option (before the subcommand) to get structured JSON output:
86
+ For programmatic/agent usage, always pass `--json` as a **top-level** option (before the subcommand) to get structured JSON output:
88
87
 
89
88
  ```bash
90
89
  gobi --json space list
@@ -7,7 +7,7 @@ description: >-
7
7
  allowed-tools: Bash(gobi:*)
8
8
  metadata:
9
9
  author: gobi-ai
10
- version: "2.0.36"
10
+ version: "2.0.40"
11
11
  ---
12
12
 
13
13
  # Gobi Homepage Developer Guide
@@ -10,18 +10,18 @@ description: >-
10
10
  allowed-tools: Bash(gobi:*)
11
11
  metadata:
12
12
  author: gobi-ai
13
- version: "2.0.36"
13
+ version: "2.0.40"
14
14
  ---
15
15
 
16
16
  # gobi-media
17
17
 
18
- Gobi media generation commands (v2.0.36).
18
+ Gobi media generation commands (v2.0.40).
19
19
 
20
20
  Requires gobi-cli installed and authenticated. See gobi-core skill for setup.
21
21
 
22
22
  ## Important: JSON Mode
23
23
 
24
- For programmatic/agent usage, always pass `--json` as a **global** option (before the subcommand):
24
+ For programmatic/agent usage, always pass `--json` as a **top-level** option (before the subcommand):
25
25
 
26
26
  ```bash
27
27
  gobi --json media generate-image --prompt "a sunset over mountains"
@@ -1,38 +1,88 @@
1
1
  ---
2
2
  name: gobi-sense
3
3
  description: >-
4
- Gobi sense commands for activity and transcription data: list activity
5
- records and list transcription records within a time range. Use when the
6
- user wants to review their activities or transcriptions from Gobi Sense.
4
+ Gobi Sense commands for browsing activities and conversations captured by the
5
+ wearable and mobile app. Activities (what you were doing) and conversations
6
+ (phone-mic Audio Logs + detected conversations, with transcripts) are scoped
7
+ to a space: `gobi personal activities/conversations …` (your personal space)
8
+ or `gobi space activities/conversations …` (the active team space). Read-only.
9
+ Use when the user wants to review their Sense activities or conversations.
7
10
  allowed-tools: Bash(gobi:*)
8
11
  metadata:
9
12
  author: gobi-ai
10
- version: "2.0.36"
13
+ version: "2.0.40"
11
14
  ---
12
15
 
13
16
  # gobi-sense
14
17
 
15
- Gobi sense commands for activity and transcription data (v2.0.36).
18
+ Gobi Sense commands for browsing activities and conversations (v2.0.40).
16
19
 
17
20
  Requires gobi-cli installed and authenticated. See the **gobi-core** skill for setup.
18
21
 
19
- Activities and transcriptions are captured by Gobi Sense (the wearable) and the mobile app, then ingested via the cloud pipeline. The CLI surface is read-only — fetch records in a time range and feed them to whatever analysis you want to run.
22
+ Sense data is captured by Gobi Sense (the wearable) and the mobile app, then ingested by the cloud pipeline. The CLI surface is **read-only**list records and fetch transcripts, then feed them to whatever analysis you want to run.
23
+
24
+ ## Scope: personal vs space
25
+
26
+ Sense data is tagged with the space it was captured in — either your **personal space** or a **team space**. The commands are the same under each scope; pick the group that matches where the data lives:
27
+
28
+ - `gobi personal activities …` / `gobi personal conversations …` — your personal space (visible only to you).
29
+ - `gobi space activities …` / `gobi space conversations …` — the active team space. The space comes from `.gobi/settings.yaml` (set with `gobi space warp <slug>`) or `gobi space --space-slug <slug> activities …`.
30
+
31
+ The examples below use `personal`; swap in `space` to browse a team space.
32
+
33
+ ## Activities vs conversations
34
+
35
+ - **activities** — a running log of what you were doing (category + details), each with a start/end time. In a team space, every member's activities show up, attributed to their recorder. Transcripts are owner-only.
36
+ - **conversations** — phone-mic Audio Log recordings plus Sense-detected conversations, each with a transcript and an auto-generated summary. In a team space, every member's conversations show up, attributed to their recorder (the transcript and `audio` signed URL stay owner-only). In your personal space, you see your own.
37
+
38
+ The old `gobi sense list-activities` / `gobi sense list-transcriptions` commands are gone — transcriptions were unified into **conversations**, and both concepts are now space-scoped.
20
39
 
21
40
  ## Important: JSON Mode
22
41
 
23
- For programmatic/agent usage, always pass `--json` as a **global** option (before the subcommand):
42
+ For programmatic/agent usage, always pass `--json` as a **top-level** option (before the subcommand):
24
43
 
25
44
  ```bash
26
- gobi --json sense list-activities --start-time 2026-04-01T00:00:00Z --end-time 2026-04-08T00:00:00Z
45
+ gobi --json personal activities list --limit 30
46
+ gobi --json personal conversations list
47
+ gobi --json space --space-slug my-team activities transcript 978
27
48
  ```
28
49
 
29
- Times are ISO 8601 UTC.
50
+ JSON mode wraps the response as `{"success": true, "data": <…>}` (or `{"success": false, "error": "…"}`).
51
+
52
+ ## Typical workflow
53
+
54
+ List recent activities (newest first, paged with `--limit` / `--before`), then pull one's transcript:
55
+
56
+ ```bash
57
+ gobi --json personal activities list --limit 30
58
+ gobi --json personal activities get 978
59
+ gobi --json personal activities transcript 978
60
+ ```
61
+
62
+ List recent conversations in a space, then read a transcript (with its summary) or grab the recording:
63
+
64
+ ```bash
65
+ gobi --json space --space-slug my-team conversations list
66
+ gobi --json space --space-slug my-team conversations transcript 12345
67
+ gobi --json space --space-slug my-team conversations audio 12345
68
+ ```
69
+
70
+ Both list commands are newest-first and page with `--limit` / `--before` (pass a previous response's `nextCursor` to `--before`). Scope difference: **`gobi space … activities/conversations list`** is a complete, fully-paginated per-space history (every member's records). **`gobi personal … conversations list`** is filtered from the cross-scope conversations feed, so it shows your recent personal conversations rather than a fully paginated history (`gobi personal activities list` is fully paginated).
30
71
 
31
72
  ## Available Commands
32
73
 
33
- - `gobi sense list-activities` List activity records within a time range.
34
- - `gobi sense list-transcriptions` — List transcription records within a time range.
74
+ Under `gobi personal …` (personal space) or `gobi space …` (active team space):
75
+
76
+ - `activities list` — List Sense activities in this scope (`--limit`, `--before`, `--mine`).
77
+ - `activities get <activityId>` — Get one activity's details.
78
+ - `activities transcript <activityId>` — Get an activity's transcript (owner-only).
79
+ - `conversations list` — List conversations captured in this scope, newest first (`--limit`, `--before`, `--mine`). In a space, every member's (attributed to each recorder).
80
+ - `conversations transcript <conversationId>` — Get a conversation's transcript and summary.
81
+ - `conversations audio <conversationId>` — Get a signed URL for the recording (owner-only).
82
+
83
+ All commands are read-only. In a space, `--mine` on either `list` restricts it to records **you** recorded (`user_id = you`); it's a no-op in the personal lane, which is already all yours.
35
84
 
36
85
  ## Reference Documentation
37
86
 
38
- - [gobi sense](references/sense.md)
87
+ - [gobi personal](references/personal.md)
88
+ - [gobi space](references/space.md)