@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
|
|
18
|
-
|
|
19
|
-
|
|
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: "
|
|
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": "
|
|
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
|
-
|
|
7
|
-
|
|
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
|
|
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("
|
|
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
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
|
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 } :
|
|
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
|
|
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(
|
|
416
|
+
console.log(JSON.stringify(readable, null, 2));
|
|
377
417
|
else
|
|
378
|
-
printLiveBots(
|
|
418
|
+
printLiveBots(readable);
|
|
379
419
|
return;
|
|
380
420
|
}
|
|
381
421
|
const intervalSeconds = Number(opts.interval ?? "3");
|