@indigoai-us/hq-cli 5.345.159 → 5.345.161
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 +48 -0
- package/assets/scaffold/core/scripts/session-journal.sh +116 -24
- package/dist/command-catalog.generated.d.ts +222 -9
- package/dist/command-catalog.generated.js +273 -9
- package/dist/commands/access.d.ts +6 -0
- package/dist/commands/access.js +18 -1
- package/dist/commands/acl-lock.d.ts +41 -0
- package/dist/commands/acl-lock.js +28 -0
- package/dist/commands/agent-enroll.d.ts +2 -0
- package/dist/commands/agent-enroll.js +9 -4
- package/dist/commands/agents.js +5 -11
- package/dist/commands/auth.js +6 -5
- package/dist/commands/billing-sponsor.d.ts +4 -4
- package/dist/commands/billing-sponsor.js +11 -11
- package/dist/commands/billing-usage.d.ts +22 -8
- package/dist/commands/billing-usage.js +57 -22
- package/dist/commands/billing.d.ts +7 -7
- package/dist/commands/billing.js +13 -13
- package/dist/commands/cloud-provision.js +2 -2
- package/dist/commands/cloud.js +7 -7
- package/dist/commands/core-session-helper-runtime.d.ts +1 -0
- package/dist/commands/core-session-journal-native.d.ts +1 -0
- package/dist/commands/core-session-journal-native.js +158 -34
- package/dist/commands/db-remote-deps.js +12 -1
- package/dist/commands/feedback.d.ts +12 -0
- package/dist/commands/feedback.js +52 -0
- package/dist/commands/files-browse.d.ts +8 -0
- package/dist/commands/files-browse.js +83 -1
- package/dist/commands/files.js +19 -7
- package/dist/commands/install-global.d.ts +2 -0
- package/dist/commands/install-global.js +24 -1
- package/dist/commands/integrations-connect.js +1 -0
- package/dist/commands/integrations-manage.d.ts +2 -0
- package/dist/commands/integrations-manage.js +6 -4
- package/dist/commands/lanes-link.js +5 -3
- package/dist/commands/lanes-session-start.d.ts +12 -0
- package/dist/commands/lanes-session-start.js +93 -12
- package/dist/commands/meetings.d.ts +15 -4
- package/dist/commands/meetings.js +58 -19
- package/dist/commands/skill.js +6 -6
- package/dist/commands/whoami.js +1 -1
- package/dist/hook-engine-path.js +13 -2
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +3236 -1988
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +21 -0
- package/dist/lanes-session-start-argv.d.ts +7 -0
- package/dist/lanes-session-start-argv.js +9 -0
- package/dist/lib/billing/plan-limit-refusals.d.ts +13 -3
- package/dist/lib/billing/plan-limit-refusals.js +26 -3
- package/dist/lib/billing/plan-limit-upgrade-url.d.ts +8 -0
- package/dist/lib/billing/plan-limit-upgrade-url.js +18 -0
- package/dist/lib/billing/plan-lock.d.ts +22 -6
- package/dist/lib/billing/plan-lock.js +58 -30
- package/dist/lib/billing/usage-cost.d.ts +24 -2
- package/dist/lib/billing/usage-cost.js +103 -3
- package/dist/lib/billing/usage-only.d.ts +37 -1
- package/dist/lib/billing/usage-only.js +89 -4
- package/dist/lib/billing/usage-pause.d.ts +27 -0
- package/dist/lib/billing/usage-pause.js +50 -0
- package/dist/lib/bot/log.d.ts +3 -2
- package/dist/lib/bot/progress.d.ts +23 -1
- package/dist/lib/bot/progress.js +43 -5
- package/dist/lib/bot/run.js +2 -0
- package/dist/lib/bot/runtime/claude.d.ts +12 -0
- package/dist/lib/bot/runtime/claude.js +20 -0
- package/dist/lib/bot/runtime/index.d.ts +10 -1
- package/dist/lib/bot/runtime/index.js +14 -2
- package/dist/lib/bot/runtime/types.d.ts +8 -0
- package/dist/lib/db/control-plane.d.ts +5 -0
- package/dist/lib/db/control-plane.js +10 -0
- package/dist/lib/doctor/checks/ps1-shims.d.ts +21 -0
- package/dist/lib/doctor/checks/ps1-shims.js +82 -0
- package/dist/lib/doctor/fix/apply.d.ts +13 -1
- package/dist/lib/doctor/fix/apply.js +38 -3
- package/dist/lib/doctor/fix/remediation.d.ts +9 -1
- package/dist/lib/doctor/fix/remediation.js +22 -0
- package/dist/lib/doctor/registry.js +2 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/hook-engine/resolve.d.ts +14 -0
- package/dist/lib/hook-engine/resolve.js +55 -1
- package/dist/lib/lanes/links.d.ts +2 -1
- package/dist/lib/lanes/links.js +235 -17
- package/dist/lib/lanes/registry-flag.d.ts +24 -0
- package/dist/lib/lanes/registry-flag.js +72 -6
- package/dist/lib/lanes/store.d.ts +4 -2
- package/dist/lib/lanes/store.js +8 -8
- package/dist/lib/plan-limit-nag.d.ts +0 -16
- package/dist/lib/plan-limit-nag.js +17 -18
- package/dist/lib/windows/ps1-shims.d.ts +60 -0
- package/dist/lib/windows/ps1-shims.js +152 -0
- package/dist/main.d.ts +0 -2
- package/dist/main.js +19 -57
- package/dist/utils/plan-gate-error.d.ts +30 -14
- package/dist/utils/plan-gate-error.js +60 -46
- package/dist/utils/self-update.d.ts +2 -0
- package/dist/utils/self-update.js +8 -0
- package/dist/utils/staged-npm-install.js +13 -2
- package/dist/utils/team-upgrade.d.ts +19 -6
- package/dist/utils/team-upgrade.js +37 -39
- package/dist/utils/token-keychain.d.ts +11 -8
- package/dist/utils/token-keychain.js +36 -15
- package/package.json +8 -8
- package/scripts/install-hook-engine.mjs +12 -0
|
@@ -76,6 +76,8 @@ export function parsePrincipal(raw) {
|
|
|
76
76
|
}
|
|
77
77
|
throw new IntegrationsCliError(`Could not tell what '${raw}' refers to. Use an email address, a uid (prs_… / agt_… / grp_…), \`everyone\`, or an explicit \`person:<id>\` / \`group:<id>\` form.`, { expected: true });
|
|
78
78
|
}
|
|
79
|
+
/** `--with` / `--to` / `--from` help. Groups resolve membership at call time. */
|
|
80
|
+
export const PRINCIPAL_HELP = "Email, uid, vault group (grp_…), or `everyone`";
|
|
79
81
|
/** How a principal reads back in output, without leaking a raw uid as a name. */
|
|
80
82
|
function describePrincipal(entry) {
|
|
81
83
|
if (entry.granteeType === "company-wide")
|
|
@@ -379,7 +381,7 @@ export function registerManageCommands(integrations) {
|
|
|
379
381
|
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
380
382
|
.option("--connection <id>", "Connection id (acct_…)")
|
|
381
383
|
.requiredOption("--tool <name>", "The tool to authorize")
|
|
382
|
-
.requiredOption("--to <principal>",
|
|
384
|
+
.requiredOption("--to <principal>", PRINCIPAL_HELP)
|
|
383
385
|
.option("--permission <level>", "read, write, or admin", "write")
|
|
384
386
|
.option("--json", "Machine-readable output")
|
|
385
387
|
.action(async (app, opts) => {
|
|
@@ -418,7 +420,7 @@ export function registerManageCommands(integrations) {
|
|
|
418
420
|
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
419
421
|
.option("--connection <id>", "Connection id (acct_…)")
|
|
420
422
|
.requiredOption("--tool <name>", "The tool to stop authorizing")
|
|
421
|
-
.requiredOption("--from <principal>",
|
|
423
|
+
.requiredOption("--from <principal>", PRINCIPAL_HELP)
|
|
422
424
|
.option("--json", "Machine-readable output")
|
|
423
425
|
.action(async (app, opts) => {
|
|
424
426
|
const principal = parsePrincipal(opts.from);
|
|
@@ -465,7 +467,7 @@ export function registerManageCommands(integrations) {
|
|
|
465
467
|
.option("--company <slug>", "Company slug, e.g. indigo")
|
|
466
468
|
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
467
469
|
.option("--connection <id>", "Connection id (acct_…)")
|
|
468
|
-
.requiredOption("--with <principal>",
|
|
470
|
+
.requiredOption("--with <principal>", PRINCIPAL_HELP)
|
|
469
471
|
.option("--permission <level>", "read, write, or admin", "write")
|
|
470
472
|
.option("--json", "Machine-readable output")
|
|
471
473
|
.action(async (app, opts) => {
|
|
@@ -495,7 +497,7 @@ export function registerManageCommands(integrations) {
|
|
|
495
497
|
.option("--company <slug>", "Company slug, e.g. indigo")
|
|
496
498
|
.option("--provider <slug>", "Connected app (e.g. linear)")
|
|
497
499
|
.option("--connection <id>", "Connection id (acct_…)")
|
|
498
|
-
.requiredOption("--from <principal>",
|
|
500
|
+
.requiredOption("--from <principal>", PRINCIPAL_HELP)
|
|
499
501
|
.option("--json", "Machine-readable output")
|
|
500
502
|
.action(async (app, opts) => {
|
|
501
503
|
const principal = parsePrincipal(opts.from);
|
|
@@ -14,6 +14,7 @@ function addLinkOptions(command) {
|
|
|
14
14
|
.option("--engine <engine>", "Session engine")
|
|
15
15
|
.option("--title <title>", "Session title")
|
|
16
16
|
.option("--host-session <id>", "Host session id")
|
|
17
|
+
.option("--claims <ids>", "Exact comma-separated recipient receipt IDs")
|
|
17
18
|
.option("--text <message>", "Message text")
|
|
18
19
|
.option("--text-file <path>", "Read message text from a file")
|
|
19
20
|
.option("--state <state>", "Child state")
|
|
@@ -35,7 +36,7 @@ function commandError(error) {
|
|
|
35
36
|
}
|
|
36
37
|
export function registerLanesLinkCommand(lanes) {
|
|
37
38
|
const link = lanes.command("link").description("Link live sessions to a conductor through session inboxes");
|
|
38
|
-
const commands = ["open", "join", "send", "report", "status", "inbox", "read", "wait", "list", "leave", "close"];
|
|
39
|
+
const commands = ["open", "join", "send", "ack", "capabilities", "report", "status", "inbox", "read", "wait", "list", "leave", "close"];
|
|
39
40
|
for (const subcommand of commands) {
|
|
40
41
|
addLinkOptions(link.command(subcommand)).action(async (options) => {
|
|
41
42
|
try {
|
|
@@ -56,7 +57,8 @@ export function registerLanesLinkCommand(lanes) {
|
|
|
56
57
|
}
|
|
57
58
|
const deliver = link.command("_deliver", { hidden: true })
|
|
58
59
|
.description("Internal linked-session hook delivery")
|
|
59
|
-
.requiredOption("--event <event>", "Hook event")
|
|
60
|
+
.requiredOption("--event <event>", "Hook event")
|
|
61
|
+
.option("--ack-mode <mode>", "Internal explicit recipient acknowledgement");
|
|
60
62
|
deliver.action(async (options) => {
|
|
61
63
|
// Hook commands must not update/re-exec the CLI inside the provider's time budget.
|
|
62
64
|
process.env.HQ_NO_UPDATE_CHECK = "1";
|
|
@@ -70,7 +72,7 @@ export function registerLanesLinkCommand(lanes) {
|
|
|
70
72
|
env: process.env,
|
|
71
73
|
defaultValue: true,
|
|
72
74
|
});
|
|
73
|
-
const result = await runLaneLinkWithFlag("_deliver", { hqRoot: root, env: process.env, event: options.event, input }, enabled);
|
|
75
|
+
const result = await runLaneLinkWithFlag("_deliver", { hqRoot: root, env: process.env, event: options.event, ackMode: options.ackMode, input }, enabled);
|
|
74
76
|
if (result.output)
|
|
75
77
|
writeJson(result.output);
|
|
76
78
|
}
|
|
@@ -8,9 +8,21 @@ export interface SessionStartOptions {
|
|
|
8
8
|
stderr?: Pick<NodeJS.WriteStream, "write">;
|
|
9
9
|
resolveRoot?: (env: NodeJS.ProcessEnv, cwd?: string) => string;
|
|
10
10
|
startSweep?: (root: string, env: NodeJS.ProcessEnv) => void;
|
|
11
|
+
/** Starts the off-hot-path registry read when an offline flag value is missing or old. */
|
|
12
|
+
refreshFlags?: (env: NodeJS.ProcessEnv) => void;
|
|
11
13
|
}
|
|
14
|
+
/** Offline flag values older than this trigger one background registry read. */
|
|
15
|
+
export declare const SESSION_START_FLAG_REFRESH_AFTER_MS: number;
|
|
12
16
|
/** Starts the CL-4 read-only orphan report once per host, without holding SessionStart open. */
|
|
13
17
|
export declare function startSessionStartOrphanSweep(root: string, env?: NodeJS.ProcessEnv, spawnDetached?: typeof spawn, rootScoped?: boolean): void;
|
|
18
|
+
/**
|
|
19
|
+
* Start a detached `hq lanes session-start --refresh-flags` so the registry
|
|
20
|
+
* read never runs inside the SessionStart hook. A short lease keeps a burst of
|
|
21
|
+
* session starts to one refresh. An env without a state dir never spawns.
|
|
22
|
+
*/
|
|
23
|
+
export declare function startSessionStartFlagRefresh(env: NodeJS.ProcessEnv, spawnDetached?: typeof spawn, now?: number): boolean;
|
|
24
|
+
/** Read the session-start flags from the registry, refreshing the offline cache. */
|
|
25
|
+
export declare function refreshSessionStartFlags(env?: NodeJS.ProcessEnv): Promise<void>;
|
|
14
26
|
/** SessionStart-compatible, fail-open conduct bootstrap. */
|
|
15
27
|
export declare function runLaneSessionStart(options?: SessionStartOptions): Promise<void>;
|
|
16
28
|
export declare function registerLaneSessionStartCommand(lanes: Command): void;
|
|
@@ -4,8 +4,17 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { createHash, randomUUID } from "node:crypto";
|
|
6
6
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
7
|
-
import {
|
|
7
|
+
import { readLanesRegistryFlagOffline, readLanesRegistryFlagResult } from "../lib/lanes/registry-flag.js";
|
|
8
8
|
import { LANE_CONDUCT_SESSION_START_FLAG, LANE_ORPHAN_SWEEP_CONTEXT_FLAG, LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG } from "../lib/lanes/flag-keys.js";
|
|
9
|
+
const SESSION_START_FLAGS = [
|
|
10
|
+
LANE_CONDUCT_SESSION_START_FLAG,
|
|
11
|
+
LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG,
|
|
12
|
+
LANE_ORPHAN_SWEEP_CONTEXT_FLAG,
|
|
13
|
+
];
|
|
14
|
+
/** Offline flag values older than this trigger one background registry read. */
|
|
15
|
+
export const SESSION_START_FLAG_REFRESH_AFTER_MS = 10 * 60 * 1000;
|
|
16
|
+
const SESSION_START_FLAG_REFRESH_LEASE_MS = 60 * 1000;
|
|
17
|
+
const SESSION_START_FLAG_REFRESH_LEASE = "lanes-session-start-flag-refresh.lock";
|
|
9
18
|
const CODEX_DEFAULT_KEYS = ["conduct_engine", "conduct_child_model", "conduct_child_effort", "conduct_default_source"];
|
|
10
19
|
function readSessionMetaValue(text, key) {
|
|
11
20
|
const line = text.split(/\r?\n/).find((entry) => new RegExp(`^\\s*${key}:`).test(entry));
|
|
@@ -247,6 +256,58 @@ export function startSessionStartOrphanSweep(root, env = process.env, spawnDetac
|
|
|
247
256
|
// SessionStart's orphan report is advisory and must never block startup.
|
|
248
257
|
}
|
|
249
258
|
}
|
|
259
|
+
function flagRefreshLeaseDir(env) {
|
|
260
|
+
const stateDir = env.HQ_STATE_DIR?.trim();
|
|
261
|
+
if (stateDir)
|
|
262
|
+
return stateDir;
|
|
263
|
+
const home = env.HOME?.trim() || env.USERPROFILE?.trim();
|
|
264
|
+
return home ? path.join(home, ".hq") : null;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Start a detached `hq lanes session-start --refresh-flags` so the registry
|
|
268
|
+
* read never runs inside the SessionStart hook. A short lease keeps a burst of
|
|
269
|
+
* session starts to one refresh. An env without a state dir never spawns.
|
|
270
|
+
*/
|
|
271
|
+
export function startSessionStartFlagRefresh(env, spawnDetached = spawn, now = Date.now()) {
|
|
272
|
+
const dir = flagRefreshLeaseDir(env);
|
|
273
|
+
const cli = process.argv[1];
|
|
274
|
+
if (!dir || !cli)
|
|
275
|
+
return false;
|
|
276
|
+
try {
|
|
277
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
278
|
+
const lease = path.join(dir, SESSION_START_FLAG_REFRESH_LEASE);
|
|
279
|
+
try {
|
|
280
|
+
if (now - fs.statSync(lease).mtimeMs < SESSION_START_FLAG_REFRESH_LEASE_MS)
|
|
281
|
+
return false;
|
|
282
|
+
fs.rmSync(lease, { force: true });
|
|
283
|
+
}
|
|
284
|
+
catch (error) {
|
|
285
|
+
if (error.code !== "ENOENT")
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
fs.closeSync(fs.openSync(lease, "wx", 0o600));
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
const child = spawnDetached(process.execPath, [cli, "lanes", "session-start", "--refresh-flags"], {
|
|
295
|
+
detached: true,
|
|
296
|
+
stdio: "ignore",
|
|
297
|
+
env: { ...env, HQ_NO_UPDATE_CHECK: "1" },
|
|
298
|
+
});
|
|
299
|
+
child.on("error", () => { });
|
|
300
|
+
child.unref();
|
|
301
|
+
return true;
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return false;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/** Read the session-start flags from the registry, refreshing the offline cache. */
|
|
308
|
+
export async function refreshSessionStartFlags(env = process.env) {
|
|
309
|
+
await Promise.all(SESSION_START_FLAGS.map((flag) => readLanesRegistryFlagResult(flag, { env, defaultValue: true, reportFailure: () => undefined }).catch(() => undefined)));
|
|
310
|
+
}
|
|
250
311
|
/** SessionStart-compatible, fail-open conduct bootstrap. */
|
|
251
312
|
export async function runLaneSessionStart(options = {}) {
|
|
252
313
|
const env = options.env ?? process.env;
|
|
@@ -278,19 +339,33 @@ export async function runLaneSessionStart(options = {}) {
|
|
|
278
339
|
if (/(^|,)auto-conduct(,|$)/.test(disabledHooks))
|
|
279
340
|
return;
|
|
280
341
|
const root = (options.resolveRoot ?? ((e, cwd) => resolveLanesHqRoot({ env: e, cwd })))(env, options.cwd);
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
342
|
+
// Flags come from offline state only. A missing or old value fails open and
|
|
343
|
+
// schedules one background registry read for the next session start.
|
|
344
|
+
let refreshNeeded = false;
|
|
345
|
+
const readFlag = (flag) => {
|
|
346
|
+
let read = null;
|
|
347
|
+
try {
|
|
348
|
+
read = readLanesRegistryFlagOffline(flag, { env, defaultValue: true });
|
|
349
|
+
}
|
|
350
|
+
catch {
|
|
351
|
+
read = null;
|
|
352
|
+
}
|
|
353
|
+
if (!read || (read.source !== "local-override" && read.ageMs > SESSION_START_FLAG_REFRESH_AFTER_MS))
|
|
354
|
+
refreshNeeded = true;
|
|
355
|
+
return read?.value ?? true;
|
|
356
|
+
};
|
|
357
|
+
const flagEnabled = readFlag(LANE_CONDUCT_SESSION_START_FLAG);
|
|
358
|
+
const rootScoped = readFlag(LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG);
|
|
359
|
+
const contextEnabled = readFlag(LANE_ORPHAN_SWEEP_CONTEXT_FLAG);
|
|
360
|
+
if (refreshNeeded) {
|
|
361
|
+
try {
|
|
362
|
+
(options.refreshFlags ?? ((e) => { startSessionStartFlagRefresh(e); }))(env);
|
|
363
|
+
}
|
|
364
|
+
catch { /* advisory */ }
|
|
365
|
+
}
|
|
289
366
|
if (!flagEnabled)
|
|
290
367
|
return;
|
|
291
|
-
const rootScoped = await readLanesRegistryFlagFailOpen(LANE_ORPHAN_SWEEP_ROOT_KEYING_FLAG, { env, defaultValue: true });
|
|
292
368
|
(options.startSweep ?? ((r, e) => startSessionStartOrphanSweep(r, e, spawn, rootScoped)))(root, env);
|
|
293
|
-
const contextEnabled = await readLanesRegistryFlagFailOpen(LANE_ORPHAN_SWEEP_CONTEXT_FLAG, { env, defaultValue: true });
|
|
294
369
|
if (contextEnabled) {
|
|
295
370
|
const count = readOrphanCount(root, env, rootScoped, (error) => debug(`hq lanes session-start: orphan report read failed (${error instanceof Error ? error.name : "Error"})`));
|
|
296
371
|
if (count > 0)
|
|
@@ -324,6 +399,12 @@ export async function runLaneSessionStart(options = {}) {
|
|
|
324
399
|
export function registerLaneSessionStartCommand(lanes) {
|
|
325
400
|
lanes.command("session-start", { hidden: true })
|
|
326
401
|
.description("Internal SessionStart bootstrap")
|
|
327
|
-
.
|
|
402
|
+
.option("--refresh-flags", "Refresh the offline session-start flag values from the registry")
|
|
403
|
+
.action(async (opts) => {
|
|
404
|
+
if (opts.refreshFlags)
|
|
405
|
+
await refreshSessionStartFlags();
|
|
406
|
+
else
|
|
407
|
+
await runLaneSessionStart();
|
|
408
|
+
});
|
|
328
409
|
}
|
|
329
410
|
//# sourceMappingURL=lanes-session-start.js.map
|
|
@@ -3,11 +3,22 @@ import { type PricingPlan } from "../lib/billing/usage-only.js";
|
|
|
3
3
|
/** Upper bound on list pages one short-id lookup reads (per cursor chain). */
|
|
4
4
|
export declare const SHORT_ID_MAX_PAGES = 50;
|
|
5
5
|
/**
|
|
6
|
-
* The 402 copy for meeting bots. A usage
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* The 402 copy for meeting bots. A usage pause (US-024) prints the server's
|
|
7
|
+
* message and Billing link. A usage-only company (US-012) is told to add a
|
|
8
|
+
* card. Every other company gets the server's sentence under a line that
|
|
9
|
+
* names no plan: the flat plans are closed.
|
|
10
10
|
*/
|
|
11
11
|
export declare function meetingPaymentRequiredLines(body: Record<string, unknown>, resolvePlan?: () => Promise<PricingPlan | null>): Promise<string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* The company a `hq meetings` call is about: the explicit `--company` value,
|
|
14
|
+
* otherwise the company an explicitly named HQ session is bound to (the session's company
|
|
15
|
+
* lock, from `scope-capability.json` or `meta.yaml`). A session bound to
|
|
16
|
+
* Personal, an unbound session, or an unreadable lock gives undefined so the
|
|
17
|
+
* server's own default applies. Before this, every subcommand sent no
|
|
18
|
+
* company at all without `--company`, so the list, get, transcript and
|
|
19
|
+
* search views silently showed only the caller's personal recordings.
|
|
20
|
+
* Feedback 04771691-fcbc-4847-899a-6c966c561b52.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sessionCompanySlug(env?: NodeJS.ProcessEnv): string | undefined;
|
|
12
23
|
export declare function registerMeetingsCommand(program: Command): void;
|
|
13
24
|
//# sourceMappingURL=meetings.d.ts.map
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import { readFile } from "node:fs/promises";
|
|
3
3
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
4
|
+
import { readSessionCompanyLock } from "../lib/session-company-lock.js";
|
|
5
|
+
import { sessionIdFromEnv } from "../lib/core-utils/session-id.js";
|
|
4
6
|
import { vaultApiFetch, getCompanyUid, resolveCallerPersonUid, } from "../utils/vault-api.js";
|
|
5
7
|
import { isPlanGateError, planGateErrorFromPayload, } from "../utils/plan-gate-error.js";
|
|
6
8
|
import { mapWithConcurrency } from "./sync-manifest.js";
|
|
7
|
-
import { isUsageOnly, pricingPlanFromBody, resolveCompanyPricingPlan, usageOnlyCardRequiredLines, } from "../lib/billing/usage-only.js";
|
|
9
|
+
import { isUsageOnly, pricingPlanFromBody, resolveCompanyPricingPlan, usageOnlyCardRequiredLines, usagePauseFromBody, } from "../lib/billing/usage-only.js";
|
|
8
10
|
const LIVE_SNAPSHOT_STATUSES = new Set([
|
|
9
11
|
"joining",
|
|
10
12
|
"recording",
|
|
@@ -318,23 +320,32 @@ async function resolveShortId(token, prefix, query) {
|
|
|
318
320
|
process.exit(1);
|
|
319
321
|
}
|
|
320
322
|
/**
|
|
321
|
-
* The 402 copy for meeting bots. A usage
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
323
|
+
* The 402 copy for meeting bots. A usage pause (US-024) prints the server's
|
|
324
|
+
* message and Billing link. A usage-only company (US-012) is told to add a
|
|
325
|
+
* card. Every other company gets the server's sentence under a line that
|
|
326
|
+
* names no plan: the flat plans are closed.
|
|
325
327
|
*/
|
|
326
328
|
export async function meetingPaymentRequiredLines(body, resolvePlan = () => resolveCompanyPricingPlan()) {
|
|
327
329
|
const plan = pricingPlanFromBody(body) ?? (await resolvePlan().catch(() => null));
|
|
328
330
|
if (isUsageOnly(plan)) {
|
|
329
331
|
return usageOnlyCardRequiredLines("A meeting notetaker bot").map((line, i) => i === 0 ? chalk.red(line) : chalk.dim(line));
|
|
330
332
|
}
|
|
333
|
+
// US-024: a usage pause is the server's own answer: its message and link.
|
|
334
|
+
const pause = usagePauseFromBody(body);
|
|
335
|
+
if (pause) {
|
|
336
|
+
return [
|
|
337
|
+
chalk.red(pause.message),
|
|
338
|
+
...(pause.actionUrl ? [chalk.dim(`Billing: ${pause.actionUrl}`)] : []),
|
|
339
|
+
];
|
|
340
|
+
}
|
|
341
|
+
// The server's sentence when it sent one; otherwise copy that names no plan.
|
|
331
342
|
const detail = typeof body.message === "string"
|
|
332
343
|
? body.message
|
|
333
344
|
: typeof body.error === "string"
|
|
334
345
|
? body.error
|
|
335
|
-
: "
|
|
346
|
+
: "Run `hq billing status` or open the company's Billing page to check it.";
|
|
336
347
|
return [
|
|
337
|
-
chalk.red("Meeting notetaker bots
|
|
348
|
+
chalk.red("Meeting notetaker bots are not available on this company's current billing."),
|
|
338
349
|
chalk.dim(detail),
|
|
339
350
|
];
|
|
340
351
|
}
|
|
@@ -425,11 +436,39 @@ function printMeetingTable(meetings) {
|
|
|
425
436
|
].join(" "));
|
|
426
437
|
}
|
|
427
438
|
}
|
|
439
|
+
/**
|
|
440
|
+
* The company a `hq meetings` call is about: the explicit `--company` value,
|
|
441
|
+
* otherwise the company an explicitly named HQ session is bound to (the session's company
|
|
442
|
+
* lock, from `scope-capability.json` or `meta.yaml`). A session bound to
|
|
443
|
+
* Personal, an unbound session, or an unreadable lock gives undefined so the
|
|
444
|
+
* server's own default applies. Before this, every subcommand sent no
|
|
445
|
+
* company at all without `--company`, so the list, get, transcript and
|
|
446
|
+
* search views silently showed only the caller's personal recordings.
|
|
447
|
+
* Feedback 04771691-fcbc-4847-899a-6c966c561b52.
|
|
448
|
+
*/
|
|
449
|
+
export function sessionCompanySlug(env = process.env) {
|
|
450
|
+
// The shared reader also supports the last-writer-wins .current pointer.
|
|
451
|
+
// It cannot identify a plain terminal or cron caller's session.
|
|
452
|
+
if (!sessionIdFromEnv(env))
|
|
453
|
+
return undefined;
|
|
454
|
+
let lock;
|
|
455
|
+
try {
|
|
456
|
+
lock = readSessionCompanyLock(env);
|
|
457
|
+
}
|
|
458
|
+
catch (err) {
|
|
459
|
+
// Fall through to the server default, but say why so an unbound call is
|
|
460
|
+
// never mistaken for a working company scope.
|
|
461
|
+
console.error(chalk.dim(` (session company not applied: ${err instanceof Error ? err.message : String(err)})`));
|
|
462
|
+
return undefined;
|
|
463
|
+
}
|
|
464
|
+
const primary = lock?.companies[0];
|
|
465
|
+
return primary && primary !== "personal" ? primary : undefined;
|
|
466
|
+
}
|
|
428
467
|
export function registerMeetingsCommand(program) {
|
|
429
468
|
const meetings = program
|
|
430
469
|
.command("meetings")
|
|
431
470
|
.description("View and search meeting recordings, transcripts, and notes")
|
|
432
|
-
.option("--company <slug>", "Company slug (
|
|
471
|
+
.option("--company <slug>", "Company slug (defaults to the company this HQ session is bound to)")
|
|
433
472
|
.option("--json", "Output raw JSON instead of formatted text");
|
|
434
473
|
// ── hq meetings list ──────────────────────────────────────────────
|
|
435
474
|
meetings
|
|
@@ -441,7 +480,7 @@ export function registerMeetingsCommand(program) {
|
|
|
441
480
|
try {
|
|
442
481
|
const token = await ensureCognitoToken();
|
|
443
482
|
const query = {};
|
|
444
|
-
const companySlug = meetings.opts().company;
|
|
483
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
445
484
|
if (opts.limit)
|
|
446
485
|
query.limit = opts.limit;
|
|
447
486
|
if (opts.next)
|
|
@@ -479,9 +518,9 @@ export function registerMeetingsCommand(program) {
|
|
|
479
518
|
.action(async (recallBotId, opts) => {
|
|
480
519
|
try {
|
|
481
520
|
const token = await ensureCognitoToken();
|
|
482
|
-
const companySlug = meetings.opts().company;
|
|
521
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
483
522
|
if (!companySlug)
|
|
484
|
-
throw new Error("--company <slug|uid> is required for live transcripts");
|
|
523
|
+
throw new Error("--company <slug|uid> is required for live transcripts (this session is not bound to a company)");
|
|
485
524
|
const companyId = companySlug.startsWith("cmp_") ? companySlug : await getCompanyUid(token, companySlug);
|
|
486
525
|
if (!recallBotId) {
|
|
487
526
|
if (opts.follow || opts.since)
|
|
@@ -601,9 +640,9 @@ export function registerMeetingsCommand(program) {
|
|
|
601
640
|
.description("Import a normalized historical transcript JSON file")
|
|
602
641
|
.action(async (file) => {
|
|
603
642
|
try {
|
|
604
|
-
const companySlug = meetings.opts().company;
|
|
643
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
605
644
|
if (!companySlug) {
|
|
606
|
-
throw new Error("--company <slug> is required for historical imports");
|
|
645
|
+
throw new Error("--company <slug> is required for historical imports (this session is not bound to a company)");
|
|
607
646
|
}
|
|
608
647
|
const bytes = await readFile(file);
|
|
609
648
|
if (bytes.byteLength > 5 * 1024 * 1024) {
|
|
@@ -657,10 +696,10 @@ export function registerMeetingsCommand(program) {
|
|
|
657
696
|
.action(async (meetingUrl) => {
|
|
658
697
|
try {
|
|
659
698
|
const token = await ensureCognitoToken();
|
|
660
|
-
const companySlug = meetings.opts().company;
|
|
699
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
661
700
|
// Always name a company. With no companyId the backend files the bot
|
|
662
701
|
// under Personal, which can never record, so an entitled user would
|
|
663
|
-
// get the
|
|
702
|
+
// get the payment-required refusal. Without --company this uses the
|
|
664
703
|
// caller's only active company, or asks them to pick one.
|
|
665
704
|
const query = {
|
|
666
705
|
companyId: await getCompanyUid(token, companySlug),
|
|
@@ -698,7 +737,7 @@ export function registerMeetingsCommand(program) {
|
|
|
698
737
|
try {
|
|
699
738
|
const token = await ensureCognitoToken();
|
|
700
739
|
const query = {};
|
|
701
|
-
const companySlug = meetings.opts().company;
|
|
740
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
702
741
|
if (companySlug)
|
|
703
742
|
query.companyId = await getCompanyUid(token, companySlug);
|
|
704
743
|
const meetingId = await resolveShortId(token, rawId, query);
|
|
@@ -823,7 +862,7 @@ export function registerMeetingsCommand(program) {
|
|
|
823
862
|
try {
|
|
824
863
|
const token = await ensureCognitoToken();
|
|
825
864
|
const params = { q: query };
|
|
826
|
-
const companySlug = meetings.opts().company;
|
|
865
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
827
866
|
if (companySlug)
|
|
828
867
|
params.companyId = await getCompanyUid(token, companySlug);
|
|
829
868
|
const res = await vaultApiFetch({
|
|
@@ -854,7 +893,7 @@ export function registerMeetingsCommand(program) {
|
|
|
854
893
|
try {
|
|
855
894
|
const token = await ensureCognitoToken();
|
|
856
895
|
const query = {};
|
|
857
|
-
const companySlug = meetings.opts().company;
|
|
896
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
858
897
|
if (companySlug)
|
|
859
898
|
query.companyId = await getCompanyUid(token, companySlug);
|
|
860
899
|
const meetingId = await resolveShortId(token, rawId, query);
|
|
@@ -923,7 +962,7 @@ export function registerMeetingsCommand(program) {
|
|
|
923
962
|
try {
|
|
924
963
|
const token = await ensureCognitoToken();
|
|
925
964
|
const query = {};
|
|
926
|
-
const companySlug = meetings.opts().company;
|
|
965
|
+
const companySlug = meetings.opts().company ?? sessionCompanySlug();
|
|
927
966
|
if (companySlug)
|
|
928
967
|
query.companyId = await getCompanyUid(token, companySlug);
|
|
929
968
|
const meetingId = await resolveShortId(token, rawId, query);
|
package/dist/commands/skill.js
CHANGED
|
@@ -28,7 +28,7 @@ import { AuthError } from "../utils/auth-error.js";
|
|
|
28
28
|
import { redactErrorText } from "../utils/redact-error-text.js";
|
|
29
29
|
import { stampVaultAccessDenied, vaultAccessDeniedDiagnostics, } from "../utils/vault-access-denied-error.js";
|
|
30
30
|
import { PlanGateError } from "../utils/plan-gate-error.js";
|
|
31
|
-
import { createPlanLimitRefusalTracker, trackingProgressRecorder, } from "../lib/billing/plan-limit-refusals.js";
|
|
31
|
+
import { createPlanLimitRefusalTracker, REFUSAL_FALLBACK_SENTENCE, trackingProgressRecorder, } from "../lib/billing/plan-limit-refusals.js";
|
|
32
32
|
export const SKILL_UID_PATTERN = /^skl_[A-Za-z0-9]+$/;
|
|
33
33
|
export const SKILL_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
34
34
|
const COMPANY_SLUG_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
@@ -54,16 +54,16 @@ async function defaultSyncFile(input) {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
* The upload of a stamped skill file was refused
|
|
57
|
+
* The upload of a stamped skill file was refused (new files paused). Registration
|
|
58
58
|
* and the local stamp already succeeded, so say that on stderr, then return a
|
|
59
|
-
* typed plan gate for main.ts to render (reason and
|
|
59
|
+
* typed plan gate for main.ts to render (reason and billing link) and exit
|
|
60
60
|
* non-zero. Never "Skill ready … Sync: complete".
|
|
61
61
|
*/
|
|
62
62
|
function skillUploadRefused(skillUid, filePath, companySlug, refused) {
|
|
63
|
-
console.warn(chalk.yellow(`⚠ Skill ${skillUid} is stamped locally at '${filePath}', but the upload was refused
|
|
63
|
+
console.warn(chalk.yellow(`⚠ Skill ${skillUid} is stamped locally at '${filePath}', but the upload was refused: new files are paused for this account.`));
|
|
64
64
|
return new PlanGateError("PLAN_LIMIT_EXCEEDED", {
|
|
65
|
-
reason: `${refused.message ??
|
|
66
|
-
"The skill file was not uploaded. Once
|
|
65
|
+
reason: `${refused.message ?? REFUSAL_FALLBACK_SENTENCE} ` +
|
|
66
|
+
"The skill file was not uploaded. Once uploads resume, upload it with: " +
|
|
67
67
|
`hq sync push --company ${companySlug} '${filePath}'`,
|
|
68
68
|
...(refused.upgradeUrl ? { upgradeUrl: refused.upgradeUrl } : {}),
|
|
69
69
|
});
|
package/dist/commands/whoami.js
CHANGED
|
@@ -123,7 +123,7 @@ export function registerWhoamiCommand(program) {
|
|
|
123
123
|
.command('whoami')
|
|
124
124
|
.description('Show the currently authenticated user')
|
|
125
125
|
.option('--json', 'Output identity metadata as JSON (never tokens)')
|
|
126
|
-
.option('--company <slug>', 'Also report this company\'s
|
|
126
|
+
.option('--company <slug>', 'Also report this company\'s billing and usage notice')
|
|
127
127
|
.action(async (opts) => {
|
|
128
128
|
try {
|
|
129
129
|
// Effective source (same as auth status / ensureCognitoToken): env
|
package/dist/hook-engine-path.js
CHANGED
|
@@ -18,7 +18,7 @@ export async function runHookEnginePath(args) {
|
|
|
18
18
|
console.log('Usage: hq core hook-engine-path [--repair] [--for-registration]\nResolve and pin the installed native hook engine offline.\n--for-registration prints a path only when the launcher flag is enabled.');
|
|
19
19
|
return;
|
|
20
20
|
}
|
|
21
|
-
const { installHookEngine, hookEngineRequired, HOOK_ENGINE_REPAIR } = await import('./lib/hook-engine/resolve.js');
|
|
21
|
+
const { installHookEngine, hookEngineRequired, removeHookEnginePointer, verifyHookEngine, HOOK_ENGINE_REPAIR } = await import('./lib/hook-engine/resolve.js');
|
|
22
22
|
if (args.includes('--for-registration')) {
|
|
23
23
|
const { launcherEnabled } = await import('./lib/hook-engine/registration.js');
|
|
24
24
|
if (!launcherEnabled()) {
|
|
@@ -27,8 +27,19 @@ export async function runHookEnginePath(args) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
const binary = installHookEngine({ repair: args.includes('--repair') });
|
|
30
|
-
if (binary)
|
|
30
|
+
if (binary) {
|
|
31
|
+
// The printed path is one that runs and reports as an hq-hook engine.
|
|
32
|
+
// A binary that cannot answer its self-check must not be bridged, so the
|
|
33
|
+
// shell pointer is removed and the command exits non-zero.
|
|
34
|
+
const problem = verifyHookEngine(binary);
|
|
35
|
+
if (problem) {
|
|
36
|
+
removeHookEnginePointer();
|
|
37
|
+
console.error(`${problem}. Existing bash hooks remain active. Repair: ${HOOK_ENGINE_REPAIR}`);
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
31
41
|
console.log(binary);
|
|
42
|
+
}
|
|
32
43
|
else {
|
|
33
44
|
console.error(`Native hook engine missing; ${hookEngineRequired() ? 'repair before running hooks' : 'existing bash hooks remain active'}. Repair: ${HOOK_ENGINE_REPAIR}`);
|
|
34
45
|
process.exitCode = 1;
|