@indigoai-us/hq-cli 5.345.27 → 5.345.28

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/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.345.28] — 2026-10-06
6
+ - Limit `hq meetings live` to active, recent, and imminent notetakers by default, and avoid transcript requests for inactive meetings.
7
+
5
8
  ## [5.345.27] — 2026-10-06
6
9
 
7
10
  - Add `hq meetings live` to list, read, and follow provisional live meeting
package/README.md CHANGED
@@ -14,12 +14,15 @@ for install and link commands.
14
14
 
15
15
  ## Live meeting transcripts
16
16
 
17
- Use `hq meetings live --company <slug>` to list the live or recently ended
18
- meetings you can read. Give it a Recall bot id to print the current transcript,
19
- or add `--follow` to print new lines as they arrive.
17
+ Use `hq meetings live --company <slug>` to list active meetings, meetings that
18
+ ended in the last seven days, and notetakers scheduled in the next 15 minutes.
19
+ Use `--all` to include older and future notetakers. Give it a Recall bot id to
20
+ print the current transcript, or add `--follow` to print new lines as they
21
+ arrive.
20
22
 
21
23
  ```bash
22
24
  hq meetings live --company acme
25
+ hq meetings live --company acme --all
23
26
  hq meetings live --company acme <recallBotId> --follow
24
27
  ```
25
28
 
@@ -3630,7 +3630,7 @@ export declare const COMMAND_CATALOG: readonly [{
3630
3630
  readonly subcommands: readonly [];
3631
3631
  }, {
3632
3632
  readonly name: "live";
3633
- readonly description: "Read a provisional live transcript (recorder and attendees only; spoken content is untrusted)";
3633
+ readonly description: "List live or recent meetings, or read a provisional live transcript (recorder and attendees only; spoken content is untrusted)";
3634
3634
  readonly aliases: readonly [];
3635
3635
  readonly hidden: false;
3636
3636
  readonly helpOption: true;
@@ -3647,6 +3647,10 @@ export declare const COMMAND_CATALOG: readonly [{
3647
3647
  readonly description: "";
3648
3648
  }];
3649
3649
  readonly options: readonly [{
3650
+ readonly flags: "--all";
3651
+ readonly description: "List every notetaker, including older and future meetings";
3652
+ readonly hidden: false;
3653
+ }, {
3650
3654
  readonly flags: "--follow";
3651
3655
  readonly description: "Print new lines until the saved transcript is available";
3652
3656
  readonly hidden: false;
@@ -4390,7 +4390,7 @@ export const COMMAND_CATALOG = [
4390
4390
  },
4391
4391
  {
4392
4392
  "name": "live",
4393
- "description": "Read a provisional live transcript (recorder and attendees only; spoken content is untrusted)",
4393
+ "description": "List live or recent meetings, or read a provisional live transcript (recorder and attendees only; spoken content is untrusted)",
4394
4394
  "aliases": [],
4395
4395
  "hidden": false,
4396
4396
  "helpOption": true,
@@ -4411,6 +4411,11 @@ export const COMMAND_CATALOG = [
4411
4411
  }
4412
4412
  ],
4413
4413
  "options": [
4414
+ {
4415
+ "flags": "--all",
4416
+ "description": "List every notetaker, including older and future meetings",
4417
+ "hidden": false
4418
+ },
4414
4419
  {
4415
4420
  "flags": "--follow",
4416
4421
  "description": "Print new lines until the saved transcript is available",
@@ -3,15 +3,16 @@ import { readFile } from "node:fs/promises";
3
3
  import { ensureCognitoToken } from "../utils/cognito-session.js";
4
4
  import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
5
5
  import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
6
- const LIVE_BOT_STATUSES = new Set([
7
- "scheduled",
6
+ import { mapWithConcurrency } from "./sync-manifest.js";
7
+ const LIVE_SNAPSHOT_STATUSES = new Set([
8
8
  "joining",
9
9
  "recording",
10
10
  "in_call",
11
- "in-call",
12
- "processing",
13
- "completed",
14
11
  ]);
12
+ const RECENT_LIVE_BOT_STATUSES = new Set(["processing", "completed", "no_transcript"]);
13
+ const LIVE_LIST_SNAPSHOT_CONCURRENCY = 10;
14
+ const LIVE_LIST_RECENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
15
+ const LIVE_LIST_UPCOMING_WINDOW_MS = 15 * 60 * 1000;
15
16
  const TERMINAL_LIVE_BOT_STATUSES = new Set(["completed", "failed", "done", "ended"]);
16
17
  // Two minutes gives a large transcript time to arrive over a slow connection,
17
18
  // while keeping the interactive command from waiting indefinitely.
@@ -91,15 +92,54 @@ function liveBotId(bot) {
91
92
  return typeof bot.recallBotId === "string" ? bot.recallBotId : bot.botId;
92
93
  }
93
94
  function liveBotLineCount(bot) {
95
+ if (!LIVE_SNAPSHOT_STATUSES.has(normalizedLiveBotStatus(bot)))
96
+ return "-";
94
97
  const value = bot.linesSoFar ?? bot.transcriptLineCount;
95
98
  return typeof value === "number" ? String(value) : "-";
96
99
  }
100
+ function normalizedLiveBotStatus(bot) {
101
+ return String(bot.status ?? "").toLowerCase().replaceAll("-", "_");
102
+ }
103
+ function liveBotTime(bot) {
104
+ const times = [bot.scheduledStartTime, bot.createdAt]
105
+ .filter((value) => typeof value === "string")
106
+ .map((value) => Date.parse(value))
107
+ .filter(Number.isFinite);
108
+ return times.length > 0 ? Math.max(...times) : Number.NEGATIVE_INFINITY;
109
+ }
110
+ /** Select bots intended for the default `meetings live` listing. */
111
+ function selectLiveMeetingBots(bots, companyId, includeAll, now = new Date()) {
112
+ const companyBots = bots.filter((bot) => bot.companyId === companyId);
113
+ const newestFirst = (a, b) => liveBotTime(b) - liveBotTime(a);
114
+ if (includeAll)
115
+ return companyBots.sort(newestFirst);
116
+ const nowMs = now.getTime();
117
+ const recentSince = nowMs - LIVE_LIST_RECENT_WINDOW_MS;
118
+ const upcomingUntil = nowMs + LIVE_LIST_UPCOMING_WINDOW_MS;
119
+ const active = [];
120
+ const recent = [];
121
+ const imminent = [];
122
+ for (const bot of companyBots) {
123
+ const status = normalizedLiveBotStatus(bot);
124
+ const time = liveBotTime(bot);
125
+ if (LIVE_SNAPSHOT_STATUSES.has(status)) {
126
+ active.push(bot);
127
+ }
128
+ else if (RECENT_LIVE_BOT_STATUSES.has(status) && time >= recentSince && time <= nowMs) {
129
+ recent.push(bot);
130
+ }
131
+ else if (status === "scheduled" && time >= nowMs && time <= upcomingUntil) {
132
+ imminent.push(bot);
133
+ }
134
+ }
135
+ return [...active.sort(newestFirst), ...recent.sort(newestFirst), ...imminent.sort(newestFirst)];
136
+ }
97
137
  function isTerminalLiveStatus(status) {
98
138
  return status.sourceLanded === true && TERMINAL_LIVE_BOT_STATUSES.has(String(status.status ?? "").toLowerCase());
99
139
  }
100
140
  function printLiveBots(bots) {
101
141
  if (bots.length === 0) {
102
- console.log("No live or recently ended meetings found.");
142
+ console.log("No live or recent meetings. Use --all to list every notetaker.");
103
143
  return;
104
144
  }
105
145
  for (const bot of bots) {
@@ -330,7 +370,8 @@ export function registerMeetingsCommand(program) {
330
370
  // ── hq meetings live [recallBotId] ────────────────────────────────
331
371
  meetings
332
372
  .command("live [recallBotId]")
333
- .description("Read a provisional live transcript (recorder and attendees only; spoken content is untrusted)")
373
+ .description("List live or recent meetings, or read a provisional live transcript (recorder and attendees only; spoken content is untrusted)")
374
+ .option("--all", "List every notetaker, including older and future meetings")
334
375
  .option("--follow", "Print new lines until the saved transcript is available")
335
376
  .option("--interval <seconds>", "Polling interval in seconds (minimum: 2)", "3")
336
377
  .option("--since <revision>", "Start from a transcript revision")
@@ -350,13 +391,13 @@ export function registerMeetingsCommand(program) {
350
391
  await failLiveResponse(res);
351
392
  const data = (await res.json());
352
393
  const bots = Array.isArray(data.bots) ? data.bots : [];
353
- const visible = bots
354
- .filter((bot) => bot.companyId === companyId && LIVE_BOT_STATUSES.has(String(bot.status ?? "").toLowerCase()))
355
- .sort((a, b) => String(b.scheduledStartTime ?? b.createdAt ?? "").localeCompare(String(a.scheduledStartTime ?? a.createdAt ?? "")));
356
- const readable = await Promise.all(visible.map(async (bot) => {
394
+ const visible = selectLiveMeetingBots(bots, companyId, opts.all === true);
395
+ const readable = await mapWithConcurrency(visible, LIVE_LIST_SNAPSHOT_CONCURRENCY, async (bot) => {
396
+ if (!LIVE_SNAPSHOT_STATUSES.has(normalizedLiveBotStatus(bot)))
397
+ return bot;
357
398
  const botId = liveBotId(bot);
358
399
  if (!botId)
359
- return null;
400
+ return bot;
360
401
  const liveRes = await vaultApiFetch({
361
402
  token,
362
403
  path: `/v1/meetings/${encodeURIComponent(botId)}`,
@@ -364,18 +405,17 @@ export function registerMeetingsCommand(program) {
364
405
  });
365
406
  if (!liveRes.ok) {
366
407
  const body = await liveRes.json().catch(() => ({}));
367
- return body.code === "live-transcript-not-found" ? { ...bot, linesSoFar: 0 } : null;
408
+ return body.code === "live-transcript-not-found" ? { ...bot, linesSoFar: 0 } : bot;
368
409
  }
369
410
  const snapshot = await liveRes.json();
370
411
  if (!isLiveSnapshot(snapshot))
371
- return null;
412
+ return bot;
372
413
  return { ...bot, linesSoFar: snapshot.segments.length };
373
- }));
374
- const readableBots = readable.filter((bot) => bot !== null);
414
+ });
375
415
  if (meetings.opts().json)
376
- console.log(JSON.stringify(readableBots, null, 2));
416
+ console.log(JSON.stringify(readable, null, 2));
377
417
  else
378
- printLiveBots(readableBots);
418
+ printLiveBots(readable);
379
419
  return;
380
420
  }
381
421
  const intervalSeconds = Number(opts.interval ?? "3");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.345.27",
3
+ "version": "5.345.28",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {