@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.
@@ -3,8 +3,6 @@ import { join } from "path";
3
3
  import inquirer from "inquirer";
4
4
  import yaml from "js-yaml";
5
5
  import { apiGet, apiPost } from "../client.js";
6
- import { isAuthenticated } from "../auth/manager.js";
7
- import { runLoginFlow } from "./auth.js";
8
6
  const SETTINGS_DIR = ".gobi";
9
7
  const SETTINGS_FILE = "settings.yaml";
10
8
  function settingsPath() {
@@ -21,7 +19,7 @@ export function getSpaceSlug() {
21
19
  const settings = readSettings();
22
20
  const slug = settings?.selectedSpaceSlug;
23
21
  if (!slug) {
24
- throw new Error("Space not set. Run 'gobi space warp' first.");
22
+ throw new Error("Space not set. Run 'gobi space warp' to select a space, or pass --space-slug.");
25
23
  }
26
24
  return slug;
27
25
  }
@@ -29,27 +27,44 @@ export function getVaultSlug() {
29
27
  const settings = readSettings();
30
28
  const vault = settings?.vaultSlug;
31
29
  if (!vault) {
32
- throw new Error("Not initialized. Run 'gobi init' first.");
30
+ throw new Error("Vault not set. Run 'gobi vault init' first.");
33
31
  }
34
32
  return vault;
35
33
  }
36
- export function printContext() {
37
- const settings = readSettings();
38
- const vault = settings?.vaultSlug;
39
- const space = settings?.selectedSpaceSlug;
40
- if (!vault && !space) {
41
- console.log("Run 'gobi init' to set up, then 'gobi space warp' to select a space.");
42
- return;
43
- }
44
- if (!vault) {
45
- console.log("Vault not set. Run 'gobi init' to set up.");
46
- return;
47
- }
48
- if (!space) {
49
- console.log(`Vault: ${vault} | Space not set. Run 'gobi space warp' to select a space.`);
50
- return;
34
+ // Per-command requirement markers. Tri-state: true / false override / inherit
35
+ // from parent. The pre-action warning uses these to decide whether to remind
36
+ // the user to run `gobi vault init` / `gobi space warp`.
37
+ const REQUIRES_VAULT = Symbol.for("gobi.requiresVault");
38
+ const REQUIRES_SPACE = Symbol.for("gobi.requiresSpace");
39
+ export function setVaultRequirement(cmd, value) {
40
+ cmd[REQUIRES_VAULT] = value;
41
+ return cmd;
42
+ }
43
+ export function setSpaceRequirement(cmd, value) {
44
+ cmd[REQUIRES_SPACE] = value;
45
+ return cmd;
46
+ }
47
+ export function requireVault(cmd) {
48
+ return setVaultRequirement(cmd, true);
49
+ }
50
+ export function requireSpace(cmd) {
51
+ return setSpaceRequirement(cmd, true);
52
+ }
53
+ function resolveRequirement(cmd, key) {
54
+ let cur = cmd;
55
+ while (cur) {
56
+ const v = cur[key];
57
+ if (v !== undefined)
58
+ return v;
59
+ cur = cur.parent;
51
60
  }
52
- console.log(`Space: ${space} | Vault: ${vault}`);
61
+ return false;
62
+ }
63
+ export function commandRequiresVault(cmd) {
64
+ return resolveRequirement(cmd, REQUIRES_VAULT);
65
+ }
66
+ export function commandRequiresSpace(cmd) {
67
+ return resolveRequirement(cmd, REQUIRES_SPACE);
53
68
  }
54
69
  function ensureSettingsDir() {
55
70
  const dir = join(process.cwd(), SETTINGS_DIR);
@@ -171,13 +186,7 @@ async function createNewVault() {
171
186
  console.log(`Created vault "${vault.name}" (${vault.vaultId})`);
172
187
  return { vaultId: vault.vaultId, name: vault.name };
173
188
  }
174
- export async function runInitFlow() {
175
- if (!isAuthenticated()) {
176
- console.log("Not logged in. Starting login flow...\n");
177
- await runLoginFlow();
178
- console.log("");
179
- }
180
- // Select or create vault
189
+ export async function runVaultInitFlow() {
181
190
  let vaultId;
182
191
  let vaultName;
183
192
  while (true) {
@@ -216,11 +225,3 @@ export async function runInitFlow() {
216
225
  console.log("Created PUBLISH.md");
217
226
  }
218
227
  }
219
- export function registerInitCommand(program) {
220
- program
221
- .command("init")
222
- .description("Log in (if needed) and select or create the vault for the current directory.")
223
- .action(async () => {
224
- await runInitFlow();
225
- });
226
- }
@@ -127,7 +127,7 @@ export function registerMediaCommand(program) {
127
127
  // Avatars & Voices
128
128
  // ════════════════════════════════════════════════════════════════════
129
129
  media
130
- .command("avatars")
130
+ .command("list-avatars")
131
131
  .description("List available avatars.")
132
132
  .action(async () => {
133
133
  const resp = (await apiGet("/media-gen/avatars"));
@@ -146,7 +146,7 @@ export function registerMediaCommand(program) {
146
146
  }
147
147
  });
148
148
  media
149
- .command("voices")
149
+ .command("list-voices")
150
150
  .description("List available voices.")
151
151
  .action(async () => {
152
152
  const resp = (await apiGet("/media-gen/voices"));
@@ -168,7 +168,7 @@ export function registerMediaCommand(program) {
168
168
  // Videos
169
169
  // ════════════════════════════════════════════════════════════════════
170
170
  media
171
- .command("video-create")
171
+ .command("create-video")
172
172
  .description("Create an avatar video generation job.")
173
173
  .option("--name <name>", "Name for the video (auto-generated if omitted)")
174
174
  .requiredOption("--avatar-id <avatarId>", "Avatar to use")
@@ -220,7 +220,7 @@ export function registerMediaCommand(program) {
220
220
  }
221
221
  });
222
222
  media
223
- .command("video-list")
223
+ .command("list-videos")
224
224
  .description("List all videos.")
225
225
  .action(async () => {
226
226
  const resp = (await apiGet("/media-gen/videos"));
@@ -239,65 +239,65 @@ export function registerMediaCommand(program) {
239
239
  }
240
240
  });
241
241
  media
242
- .command("video-get <id>")
242
+ .command("get-video <videoId>")
243
243
  .description("Get video metadata.")
244
- .action(async (id) => {
245
- const resp = (await apiGet(`/media-gen/videos/${id}`));
244
+ .action(async (videoId) => {
245
+ const resp = (await apiGet(`/media-gen/videos/${videoId}`));
246
246
  const data = unwrapResp(resp);
247
247
  if (isJsonMode(media)) {
248
248
  jsonOut(data);
249
249
  return;
250
250
  }
251
- console.log(`Video ${id}:`);
251
+ console.log(`Video ${videoId}:`);
252
252
  for (const [k, v] of Object.entries(data)) {
253
253
  console.log(` ${k}: ${typeof v === "object" ? JSON.stringify(v) : v}`);
254
254
  }
255
255
  });
256
256
  media
257
- .command("video-status <id>")
258
- .description("Poll video generation status.")
257
+ .command("get-video-status <videoId>")
258
+ .description("Get video generation status.")
259
259
  .option("--wait", "Poll until a terminal state is reached")
260
260
  .option("-o, --output <path>", "Download video to this path when complete (implies --wait)")
261
- .action(async (id, opts) => {
261
+ .action(async (videoId, opts) => {
262
262
  const shouldWait = opts.wait || !!opts.output;
263
263
  if (shouldWait) {
264
- const data = await pollStatus(`/media-gen/videos/${id}/status`, ["inference_complete", "inference_failed"]);
264
+ const data = await pollStatus(`/media-gen/videos/${videoId}/status`, ["inference_complete", "inference_failed"]);
265
265
  // Download if -o specified and completed
266
266
  if (opts.output && data.status === "inference_complete") {
267
- const dlId = (data.videoId || data.id || id);
267
+ const dlId = (data.videoId || data.id || videoId);
268
268
  const { contentType, size } = await downloadVideoToFile(dlId, opts.output);
269
269
  if (isJsonMode(media)) {
270
270
  jsonOut({ ...data, filename: opts.output, contentType, size });
271
271
  return;
272
272
  }
273
- console.log(`Video ${id} — ${data.status}\nSaved to ${opts.output} (${size} bytes)`);
273
+ console.log(`Video ${videoId} — ${data.status}\nSaved to ${opts.output} (${size} bytes)`);
274
274
  return;
275
275
  }
276
276
  if (isJsonMode(media)) {
277
277
  jsonOut(data);
278
278
  return;
279
279
  }
280
- console.log(`Video ${id} — status: ${data.status}`);
280
+ console.log(`Video ${videoId} — status: ${data.status}`);
281
281
  return;
282
282
  }
283
- const resp = (await apiGet(`/media-gen/videos/${id}/status`));
283
+ const resp = (await apiGet(`/media-gen/videos/${videoId}/status`));
284
284
  const data = unwrapResp(resp);
285
285
  if (isJsonMode(media)) {
286
286
  jsonOut(data);
287
287
  return;
288
288
  }
289
- console.log(`Video ${id} — status: ${data.status || "unknown"}`);
289
+ console.log(`Video ${videoId} — status: ${data.status || "unknown"}`);
290
290
  });
291
291
  media
292
- .command("video-download <id>")
292
+ .command("download-video <videoId>")
293
293
  .description("Download a completed video (or get its URL).")
294
294
  .option("-o, --output <path>", "Save video to this file path")
295
- .action(async (id, opts) => {
295
+ .action(async (videoId, opts) => {
296
296
  const token = await getValidToken();
297
- const url = `${BASE_URL}/media-gen/videos/${id}/download`;
297
+ const url = `${BASE_URL}/media-gen/videos/${videoId}/download`;
298
298
  // If -o specified, download directly to file
299
299
  if (opts.output) {
300
- const { contentType, size } = await downloadVideoToFile(id, opts.output);
300
+ const { contentType, size } = await downloadVideoToFile(videoId, opts.output);
301
301
  if (isJsonMode(media)) {
302
302
  jsonOut({ filename: opts.output, contentType, size });
303
303
  return;
@@ -317,12 +317,12 @@ export function registerMediaCommand(program) {
317
317
  jsonOut({ downloadUrl: location });
318
318
  return;
319
319
  }
320
- console.log(`Download URL for video ${id}:\n ${location}`);
320
+ console.log(`Download URL for video ${videoId}:\n ${location}`);
321
321
  return;
322
322
  }
323
323
  if (!res.ok) {
324
324
  const text = (await res.text()) || "(no body)";
325
- throw new ApiError(res.status, `/media-gen/videos/${id}/download`, text);
325
+ throw new ApiError(res.status, `/media-gen/videos/${videoId}/download`, text);
326
326
  }
327
327
  // If it returns JSON instead of a redirect
328
328
  const resp = (await res.json());
@@ -331,13 +331,13 @@ export function registerMediaCommand(program) {
331
331
  jsonOut(data);
332
332
  return;
333
333
  }
334
- console.log(`Download URL for video ${id}:\n ${data.url || data.downloadUrl || JSON.stringify(data)}`);
334
+ console.log(`Download URL for video ${videoId}:\n ${data.url || data.downloadUrl || JSON.stringify(data)}`);
335
335
  });
336
336
  // ════════════════════════════════════════════════════════════════════
337
337
  // Cinematic Video
338
338
  // ════════════════════════════════════════════════════════════════════
339
339
  media
340
- .command("cinematic-create")
340
+ .command("create-cinematic")
341
341
  .description("Create a cinematic video from a text prompt.")
342
342
  .requiredOption("--prompt <prompt>", "Text prompt describing the video")
343
343
  .option("--name <name>", "Name for the video (auto-generated if omitted)")
@@ -425,7 +425,7 @@ export function registerMediaCommand(program) {
425
425
  // Custom Avatars
426
426
  // ════════════════════════════════════════════════════════════════════
427
427
  media
428
- .command("avatar-design")
428
+ .command("design-avatar")
429
429
  .description("Start a design-your-avatar job.")
430
430
  .option("--name <name>", "Name for the avatar (auto-generated if omitted)")
431
431
  .requiredOption("--gender <gender>", "Gender for the avatar design")
@@ -464,13 +464,13 @@ export function registerMediaCommand(program) {
464
464
  ` Job ID: ${jobId}\n` +
465
465
  ` Status: ${status}`);
466
466
  if (status === "variants_ready") {
467
- console.log(` Confirm: gobi media avatar-confirm --job-id ${jobId}`);
467
+ console.log(` Confirm: gobi media confirm-avatar --job-id ${jobId}`);
468
468
  }
469
469
  });
470
470
  media
471
- .command("avatar-confirm")
471
+ .command("confirm-avatar")
472
472
  .description("Confirm avatar variant(s) after design.")
473
- .requiredOption("--job-id <jobId>", "Job ID from avatar-design")
473
+ .requiredOption("--job-id <jobId>", "Job ID from design-avatar")
474
474
  .option("--variant <variant>", "Variant to confirm (1 or 2); omit to confirm both")
475
475
  .action(async (opts) => {
476
476
  const body = { jobId: opts.jobId };
@@ -491,8 +491,8 @@ export function registerMediaCommand(program) {
491
491
  ` Avatar ID: ${avatarId || JSON.stringify(data)}`);
492
492
  });
493
493
  media
494
- .command("avatar-from-selfie")
495
- .description("Create an avatar from a selfie (instant or enhanced with prompt).")
494
+ .command("design-avatar-from-selfie")
495
+ .description("Design an avatar from a selfie (instant or enhanced with prompt).")
496
496
  .option("--name <name>", "Name for the avatar (auto-generated if omitted)")
497
497
  .requiredOption("--photo <file>", "Selfie photo file (auto-uploaded)")
498
498
  .option("--prompt <prompt>", "Enhancement prompt (triggers async enhance flow)")
@@ -532,8 +532,8 @@ export function registerMediaCommand(program) {
532
532
  }
533
533
  });
534
534
  media
535
- .command("avatar-job-status <jobId>")
536
- .description("Check avatar job status.")
535
+ .command("get-avatar-job-status <jobId>")
536
+ .description("Get avatar job status.")
537
537
  .option("--wait", "Poll until a terminal state is reached")
538
538
  .action(async (jobId, opts) => {
539
539
  let data;
@@ -554,7 +554,7 @@ export function registerMediaCommand(program) {
554
554
  // Images
555
555
  // ════════════════════════════════════════════════════════════════════
556
556
  media
557
- .command("image-generate")
557
+ .command("generate-image")
558
558
  .description("Generate an image from a text prompt. Types: image (default), thumbnail (YouTube-optimized), asset (logo/product). Aspect ratios: 1:1, 16:9, 9:16, 4:3, 3:4")
559
559
  .requiredOption("--prompt <prompt>", "Text prompt for image generation")
560
560
  .option("--name <name>", "Name for the generated image (auto-generated from prompt if omitted)")
@@ -636,7 +636,7 @@ export function registerMediaCommand(program) {
636
636
  }
637
637
  });
638
638
  media
639
- .command("image-edit")
639
+ .command("edit-image")
640
640
  .description("Edit an existing image with a prompt (image-to-image).")
641
641
  .requiredOption("--image <file>", "Source image file (auto-uploaded)")
642
642
  .requiredOption("--prompt <prompt>", "Edit instruction")
@@ -699,7 +699,7 @@ export function registerMediaCommand(program) {
699
699
  }
700
700
  });
701
701
  media
702
- .command("image-inpaint")
702
+ .command("inpaint-image")
703
703
  .description("Inpaint an image region using a mask.")
704
704
  .requiredOption("--image <file>", "Source image file (auto-uploaded)")
705
705
  .requiredOption("--mask <file>", "Mask image file (auto-uploaded)")
@@ -766,8 +766,8 @@ export function registerMediaCommand(program) {
766
766
  }
767
767
  });
768
768
  media
769
- .command("image-status <jobId>")
770
- .description("Check image generation job status.")
769
+ .command("get-image-status <jobId>")
770
+ .description("Get image generation job status.")
771
771
  .option("--wait", "Poll until a terminal state is reached")
772
772
  .action(async (jobId, opts) => {
773
773
  let data;
@@ -794,7 +794,7 @@ export function registerMediaCommand(program) {
794
794
  }
795
795
  });
796
796
  media
797
- .command("image-download <jobId>")
797
+ .command("download-image <jobId>")
798
798
  .description("Download a generated image.")
799
799
  .option("--wait", "Poll until generation completes before downloading")
800
800
  .option("--type <type>", "Image type (image, thumbnail, asset)")
@@ -24,16 +24,17 @@ function formatSavedPostLine(item) {
24
24
  const space = item.spaceSlug ? `, space: ${item.spaceSlug}` : "";
25
25
  return `- [${item.postId}] "${snippet.replace(/\n/g, " ")}" by ${author ?? "?"}${space} (saved ${item.savedAt})`;
26
26
  }
27
- function registerNoteCommands(saved) {
28
- const note = saved
29
- .command("note")
30
- .description("Personal saved notes (create, list, get, edit, delete).");
31
- note
32
- .command("list")
27
+ export function registerSavedCommand(program) {
28
+ const saved = program
29
+ .command("saved")
30
+ .description("Saved-knowledge commands (notes and bookmarked posts).");
31
+ // ── Notes ──
32
+ saved
33
+ .command("list-notes")
33
34
  .description("List your notes. Without --date, returns recent notes via cursor pagination. With --date, returns all notes for that day.")
34
35
  .option("--date <date>", "Filter to a single day (YYYY-MM-DD)")
35
36
  .option("--timezone <tz>", "IANA timezone name (default: system timezone)")
36
- .option("--limit <number>", "Items per page (1-100)", "50")
37
+ .option("--limit <number>", "Items per page", "20")
37
38
  .option("--cursor <string>", "Pagination cursor from previous response")
38
39
  .action(async (opts) => {
39
40
  const params = {
@@ -61,8 +62,8 @@ function registerNoteCommands(saved) {
61
62
  : "";
62
63
  console.log(`Notes (${items.length} items):\n` + lines.join("\n") + footer);
63
64
  });
64
- note
65
- .command("get <noteId>")
65
+ saved
66
+ .command("get-note <noteId>")
66
67
  .description("Get a single note by id.")
67
68
  .action(async (noteId) => {
68
69
  const resp = (await apiGet(`/app/notes/${noteId}`));
@@ -90,8 +91,8 @@ function registerNoteCommands(saved) {
90
91
  .trimEnd();
91
92
  console.log(output);
92
93
  });
93
- note
94
- .command("create")
94
+ saved
95
+ .command("create-note")
95
96
  .description("Create a note. Provide --content (use '-' for stdin) and/or attachments.")
96
97
  .option("--content <content>", 'Note content (markdown supported, use "-" for stdin)')
97
98
  .option("--timezone <tz>", "IANA timezone name (default: system timezone)")
@@ -115,8 +116,8 @@ function registerNoteCommands(saved) {
115
116
  }
116
117
  console.log(`Note created!\n ID: ${note.id}\n Date: ${note.eventDate}\n Created: ${note.createdAt}`);
117
118
  });
118
- note
119
- .command("edit <noteId>")
119
+ saved
120
+ .command("edit-note <noteId>")
120
121
  .description("Edit a note. Provide --content and/or --agent-id.")
121
122
  .option("--content <content>", 'New note content (markdown supported, use "-" for stdin)')
122
123
  .option("--agent-id <number>", 'New agent id, or "null" to clear the association')
@@ -140,8 +141,8 @@ function registerNoteCommands(saved) {
140
141
  }
141
142
  console.log(`Note edited!\n ID: ${note.id}\n Updated: ${note.updatedAt}`);
142
143
  });
143
- note
144
- .command("delete <noteId>")
144
+ saved
145
+ .command("delete-note <noteId>")
145
146
  .description("Delete a note you authored.")
146
147
  .action(async (noteId) => {
147
148
  await apiDelete(`/app/notes/${noteId}`);
@@ -151,16 +152,12 @@ function registerNoteCommands(saved) {
151
152
  }
152
153
  console.log(`Note ${noteId} deleted.`);
153
154
  });
154
- }
155
- function registerPostCommands(saved) {
156
- const post = saved
157
- .command("post")
158
- .description("Saved posts (snapshots of posts and replies you bookmark).");
159
- post
160
- .command("list")
161
- .description("List posts you have saved.")
155
+ // ── Saved posts (bookmarks) ──
156
+ saved
157
+ .command("list-posts")
158
+ .description("List posts you have bookmarked (paginated).")
162
159
  .option("--type <type>", "Filter by type: all|article|space-post", "all")
163
- .option("--limit <number>", "Items per page (1-50)", "20")
160
+ .option("--limit <number>", "Items per page", "20")
164
161
  .option("--cursor <string>", "Pagination cursor from previous response")
165
162
  .action(async (opts) => {
166
163
  const params = {
@@ -184,8 +181,8 @@ function registerPostCommands(saved) {
184
181
  const footer = pagination.hasMore ? `\n Next cursor: ${pagination.nextCursor}` : "";
185
182
  console.log(`Saved posts (${items.length} items):\n` + lines.join("\n") + footer);
186
183
  });
187
- post
188
- .command("get <postId>")
184
+ saved
185
+ .command("get-post <postId>")
189
186
  .description("Get a saved post snapshot by post id.")
190
187
  .action(async (postId) => {
191
188
  const resp = (await apiGet(`/feed/${postId}`));
@@ -205,10 +202,10 @@ function registerPostCommands(saved) {
205
202
  post.content || "",
206
203
  ].join("\n"));
207
204
  });
208
- post
209
- .command("create")
210
- .description("Save a post or reply. Records a snapshot in your saved-posts collection.")
211
- .requiredOption("--source <id>", "Source post or reply id to save (numeric)")
205
+ saved
206
+ .command("create-post")
207
+ .description("Bookmark a post or reply by id. Records a snapshot in your saved-posts collection.")
208
+ .requiredOption("--source <id>", "Source post or reply id to bookmark (numeric)")
212
209
  .action(async (opts) => {
213
210
  const sourceId = parseInt(opts.source, 10);
214
211
  if (!Number.isFinite(sourceId)) {
@@ -219,27 +216,20 @@ function registerPostCommands(saved) {
219
216
  }));
220
217
  const data = unwrapResp(resp);
221
218
  if (isJsonMode(saved)) {
222
- jsonOut({ postId: sourceId, ...data });
219
+ jsonOut({ id: sourceId, ...data });
223
220
  return;
224
221
  }
225
222
  console.log(`Saved post ${sourceId}.`);
226
223
  });
227
- post
228
- .command("delete <postId>")
224
+ saved
225
+ .command("delete-post <postId>")
229
226
  .description("Remove a post from your saved-posts collection.")
230
227
  .action(async (postId) => {
231
228
  await apiDelete(`/reactions/posts/${postId}/save`);
232
229
  if (isJsonMode(saved)) {
233
- jsonOut({ postId });
230
+ jsonOut({ id: postId });
234
231
  return;
235
232
  }
236
233
  console.log(`Removed post ${postId} from saved.`);
237
234
  });
238
235
  }
239
- export function registerSavedCommand(program) {
240
- const saved = program
241
- .command("saved")
242
- .description("Saved-knowledge commands (notes and posts).");
243
- registerNoteCommands(saved);
244
- registerPostCommands(saved);
245
- }
@@ -6,8 +6,8 @@ export function registerSenseCommand(program) {
6
6
  .description("Sense commands (activities, transcriptions).");
7
7
  // ── Activities ──
8
8
  sense
9
- .command("activities")
10
- .description("Fetch activity records within a time range.")
9
+ .command("list-activities")
10
+ .description("List activity records within a time range.")
11
11
  .requiredOption("--start-time <iso>", "Start of time range (ISO 8601 UTC, e.g. 2026-03-20T00:00:00Z)")
12
12
  .requiredOption("--end-time <iso>", "End of time range (ISO 8601 UTC, e.g. 2026-03-20T23:59:59Z)")
13
13
  .action(async (opts) => {
@@ -55,8 +55,8 @@ export function registerSenseCommand(program) {
55
55
  });
56
56
  // ── Transcriptions ──
57
57
  sense
58
- .command("transcriptions")
59
- .description("Fetch transcription records within a time range.")
58
+ .command("list-transcriptions")
59
+ .description("List transcription records within a time range.")
60
60
  .requiredOption("--start-time <iso>", "Start of time range (ISO 8601 UTC, e.g. 2026-03-20T00:00:00Z)")
61
61
  .requiredOption("--end-time <iso>", "End of time range (ISO 8601 UTC, e.g. 2026-03-20T23:59:59Z)")
62
62
  .action(async (opts) => {
@@ -1,14 +1,14 @@
1
1
  import { apiGet, apiPost } from "../client.js";
2
- import { isJsonMode, jsonOut, unwrapResp } from "./utils.js";
2
+ import { isJsonMode, jsonOut, readStdin, unwrapResp } from "./utils.js";
3
3
  export function registerSessionsCommand(program) {
4
4
  const sessions = program
5
5
  .command("session")
6
- .description("Session commands (get, list, reply).");
6
+ .description("Session commands (get, list, create-reply).");
7
7
  // ── Get ──
8
8
  sessions
9
9
  .command("get <sessionId>")
10
10
  .description("Get a session and its messages (paginated).")
11
- .option("--limit <number>", "Messages per page", "20")
11
+ .option("--limit <number>", "Items per page", "20")
12
12
  .option("--cursor <string>", "Pagination cursor from previous response")
13
13
  .action(async (sessionId, opts) => {
14
14
  const params = {
@@ -85,9 +85,9 @@ export function registerSessionsCommand(program) {
85
85
  });
86
86
  // ── Reply ──
87
87
  sessions
88
- .command("reply <sessionId>")
88
+ .command("create-reply <sessionId>")
89
89
  .description("Send a human reply to a session you are a member of.")
90
- .option("--content <content>", "Reply content (markdown supported)")
90
+ .option("--content <content>", "Reply content (markdown supported, use \"-\" for stdin)")
91
91
  .option("--rich-text <richText>", "Rich-text JSON array (e.g. [{\"type\":\"text\",\"text\":\"hello\"}])")
92
92
  .action(async (sessionId, opts) => {
93
93
  if (!opts.content && !opts.richText) {
@@ -108,7 +108,7 @@ export function registerSessionsCommand(program) {
108
108
  body.richText = parsed;
109
109
  }
110
110
  else {
111
- body.content = opts.content;
111
+ body.content = opts.content === "-" ? readStdin() : opts.content;
112
112
  }
113
113
  const resp = (await apiPost(`/chat/${sessionId}/reply`, body));
114
114
  const msg = unwrapResp(resp);