@indigoai-us/hq-cli 5.345.198 → 5.345.200
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 +71 -0
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +6 -4
- package/dist/command-catalog.generated.d.ts +150 -6
- package/dist/command-catalog.generated.js +182 -6
- package/dist/commands/bot-events.d.ts +19 -0
- package/dist/commands/bot-events.js +46 -0
- package/dist/commands/bot-jobs.d.ts +2 -0
- package/dist/commands/bot-jobs.js +43 -2
- package/dist/commands/bot-runtime.d.ts +35 -0
- package/dist/commands/bot-runtime.js +115 -0
- package/dist/commands/bot.d.ts +11 -0
- package/dist/commands/bot.js +72 -11
- package/dist/commands/creators.js +2 -1
- package/dist/commands/lanes-report.d.ts +2 -1
- package/dist/commands/lanes-report.js +44 -1
- package/dist/commands/lanes.d.ts +9 -0
- package/dist/commands/lanes.js +95 -9
- package/dist/commands/library-remove.js +2 -1
- package/dist/commands/library.js +5 -4
- package/dist/commands/meetings.js +1 -21
- package/dist/commands/mesh.d.ts +0 -7
- package/dist/commands/mesh.js +5 -1
- package/dist/commands/signals-run.d.ts +2 -0
- package/dist/commands/signals-run.js +6 -1
- package/dist/commands/sync-held.js +2 -2
- package/dist/lanes-drain-hook.js +9 -0
- package/dist/lib/bot/api.d.ts +5 -0
- package/dist/lib/bot/claude-login.d.ts +31 -0
- package/dist/lib/bot/claude-login.js +160 -0
- package/dist/lib/bot/events.d.ts +18 -0
- package/dist/lib/bot/events.js +160 -0
- package/dist/lib/bot/executive-settings.d.ts +10 -0
- package/dist/lib/bot/executive-settings.js +43 -0
- package/dist/lib/bot/fleet-cron.js +4 -0
- package/dist/lib/bot/fleet-download.d.ts +27 -0
- package/dist/lib/bot/fleet-download.js +162 -0
- package/dist/lib/bot/fleet-local.d.ts +51 -2
- package/dist/lib/bot/fleet-local.js +179 -76
- package/dist/lib/bot/fleet-python.d.ts +2 -0
- package/dist/lib/bot/fleet-python.js +29 -0
- package/dist/lib/bot/job-run.js +1 -1
- package/dist/lib/bot/jobs/hot-signal-sync.d.ts +8 -0
- package/dist/lib/bot/jobs/hot-signal-sync.js +2 -0
- package/dist/lib/bot/jobs/meeting-detect.d.ts +28 -0
- package/dist/lib/bot/jobs/meeting-detect.js +176 -0
- package/dist/lib/bot/jobs/meeting-rider.d.ts +53 -0
- package/dist/lib/bot/jobs/meeting-rider.js +284 -0
- package/dist/lib/bot/jobs/signal-to-lane.d.ts +32 -0
- package/dist/lib/bot/jobs/signal-to-lane.js +169 -0
- package/dist/lib/bot/local-jobs.d.ts +15 -2
- package/dist/lib/bot/local-jobs.js +24 -7
- package/dist/lib/bot/run.d.ts +7 -49
- package/dist/lib/bot/run.js +100 -4
- package/dist/lib/bot/status.d.ts +21 -0
- package/dist/lib/doctor/checks/bots.d.ts +6 -0
- package/dist/lib/doctor/checks/bots.js +47 -1
- package/dist/lib/hot-channels/adapters/meetings.d.ts +1 -0
- package/dist/lib/hot-channels/adapters/meetings.js +3 -1
- package/dist/lib/hot-channels/meeting-source-ref.d.ts +3 -0
- package/dist/lib/hot-channels/meeting-source-ref.js +7 -0
- package/dist/lib/hot-channels/schedule.js +2 -2
- package/dist/lib/hot-channels/signals-pass.d.ts +9 -32
- package/dist/lib/hot-channels/signals-pass.js +51 -17
- package/dist/lib/lanes/admission.d.ts +2 -0
- package/dist/lib/lanes/admission.js +4 -0
- package/dist/lib/lanes/approval-gate.js +4 -1
- package/dist/lib/lanes/links.d.ts +8 -1
- package/dist/lib/lanes/links.js +84 -3
- package/dist/lib/lanes/mesh-events.js +3 -1
- package/dist/lib/lanes/outcome.d.ts +154 -0
- package/dist/lib/lanes/outcome.js +151 -0
- package/dist/lib/lanes/reconcile.js +1 -0
- package/dist/lib/lanes/signal-brief-template.d.ts +7 -0
- package/dist/lib/lanes/signal-brief-template.js +40 -0
- package/dist/lib/lanes/signal-lane.d.ts +24 -0
- package/dist/lib/lanes/signal-lane.js +99 -0
- package/dist/lib/lanes/source-report.d.ts +37 -0
- package/dist/lib/lanes/source-report.js +66 -0
- package/dist/lib/lanes/spawn.d.ts +6 -0
- package/dist/lib/lanes/spawn.js +21 -0
- package/dist/lib/lanes/store.d.ts +3 -0
- package/dist/lib/lanes/store.js +64 -0
- package/dist/lib/lanes/story-ledger.d.ts +3 -0
- package/dist/lib/lanes/story-ledger.js +1 -0
- package/dist/lib/lanes/types.d.ts +137 -0
- package/dist/lib/lanes/types.js +17 -2
- package/dist/lib/lanes/watcher.js +4 -0
- package/dist/lib/live-meetings.d.ts +19 -0
- package/dist/lib/live-meetings.js +23 -0
- package/dist/lib/mesh/api.js +2 -1
- package/dist/lib/mesh/live/daemon/doctor.js +6 -7
- package/dist/lib/mesh/live/daemon/run.d.ts +0 -10
- package/dist/lib/mesh/live/daemon/run.js +3 -0
- package/dist/lib/mesh/live/emit.d.ts +2 -11
- package/dist/lib/mesh/live/emit.js +20 -2
- package/dist/lib/mesh/live/flush.d.ts +2 -0
- package/dist/lib/mesh/live/flush.js +34 -3
- package/dist/lib/mesh/live/reporting-privacy.d.ts +7 -0
- package/dist/lib/mesh/live/reporting-privacy.js +47 -0
- package/dist/lib/mesh/live/spool.d.ts +8 -0
- package/dist/lib/mesh/live/spool.js +59 -40
- package/dist/lib/plan-limit-nag.d.ts +30 -4
- package/dist/lib/plan-limit-nag.js +68 -17
- package/dist/lib/signals/engine.d.ts +4 -0
- package/dist/lib/signals/engine.js +25 -5
- package/dist/lib/signals/incremental.d.ts +42 -0
- package/dist/lib/signals/incremental.js +191 -0
- package/dist/lib/signals/prior-signals.d.ts +11 -0
- package/dist/lib/signals/prior-signals.js +44 -0
- package/dist/lib/signals/reply.js +6 -1
- package/dist/lib/signals/settings.d.ts +5 -0
- package/dist/lib/signals/settings.js +17 -0
- package/dist/lib/signals/steps/extract.d.ts +23 -2
- package/dist/lib/signals/steps/extract.js +142 -9
- package/dist/lib/signals/steps/types.d.ts +1 -0
- package/dist/lib/signals/write-guard.js +7 -0
- package/dist/lib/work-context/company.d.ts +2 -3
- package/dist/lib/work-context/company.js +4 -22
- package/dist/secrets-env-action.js +5 -2
- package/dist/utils/cognito-callback-port.d.ts +17 -0
- package/dist/utils/cognito-callback-port.js +56 -0
- package/dist/utils/cognito-config.js +7 -3
- package/dist/utils/cognito-session.d.ts +7 -6
- package/dist/utils/cognito-session.js +19 -14
- package/dist/utils/interactive-caller.d.ts +23 -0
- package/dist/utils/interactive-caller.js +80 -0
- package/dist/utils/login-browser.js +5 -2
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +7 -7
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
* 4b. persisted per-repo company map (gap 4 — person "always ask per repo",
|
|
13
13
|
* remembered by repoIdentityKey; above the git-remote heuristic + device
|
|
14
14
|
* default, below the identity file so an agent box always wins)
|
|
15
|
-
* 5.
|
|
16
|
-
* 6. exactly one deterministic mapping (cwd under companies/{slug}/ or repo remote)
|
|
15
|
+
* 5. exactly one deterministic mapping (cwd under companies/{slug}/ or repo remote)
|
|
17
16
|
*
|
|
18
17
|
* Prompt-text matching is never used.
|
|
19
|
-
* Device
|
|
18
|
+
* Device defaults never attribute a session; explicit evidence is required.
|
|
20
19
|
*/
|
|
21
20
|
import type { TrustedExplicitContext } from "./contract.js";
|
|
22
21
|
import { type SessionStateFile } from "./state.js";
|
|
@@ -12,15 +12,14 @@
|
|
|
12
12
|
* 4b. persisted per-repo company map (gap 4 — person "always ask per repo",
|
|
13
13
|
* remembered by repoIdentityKey; above the git-remote heuristic + device
|
|
14
14
|
* default, below the identity file so an agent box always wins)
|
|
15
|
-
* 5.
|
|
16
|
-
* 6. exactly one deterministic mapping (cwd under companies/{slug}/ or repo remote)
|
|
15
|
+
* 5. exactly one deterministic mapping (cwd under companies/{slug}/ or repo remote)
|
|
17
16
|
*
|
|
18
17
|
* Prompt-text matching is never used.
|
|
19
|
-
* Device
|
|
18
|
+
* Device defaults never attribute a session; explicit evidence is required.
|
|
20
19
|
*/
|
|
21
20
|
import * as fs from "node:fs";
|
|
22
21
|
import * as path from "node:path";
|
|
23
|
-
import {
|
|
22
|
+
import { getRepoCompany } from "./config.js";
|
|
24
23
|
import { authoritativeCompanyFromState, readSessionState, } from "./state.js";
|
|
25
24
|
/** Pointer to the US-017B correction client (never auto-switches). */
|
|
26
25
|
const COMPANY_CORRECTION_PATH_PREFIX = "hq mesh context correct --session";
|
|
@@ -333,8 +332,7 @@ export function resolveCompany(input) {
|
|
|
333
332
|
};
|
|
334
333
|
}
|
|
335
334
|
}
|
|
336
|
-
//
|
|
337
|
-
const deviceDefault = input.allowDeviceDefault === false ? null : getDefaultCompany({ root: input.root });
|
|
335
|
+
// Membership and device preferences are not session attribution evidence.
|
|
338
336
|
const deterministic = resolveDeterministicCompany({
|
|
339
337
|
cwd: input.cwd,
|
|
340
338
|
hqRoot: input.hqRoot,
|
|
@@ -354,22 +352,6 @@ export function resolveCompany(input) {
|
|
|
354
352
|
evidenceCompany: deterministicConflict.remote,
|
|
355
353
|
};
|
|
356
354
|
}
|
|
357
|
-
if (deviceDefault?.enabled && deviceDefault.slug) {
|
|
358
|
-
const def = {
|
|
359
|
-
slug: deviceDefault.slug,
|
|
360
|
-
uid: deviceDefault.uid,
|
|
361
|
-
source: "device_default",
|
|
362
|
-
};
|
|
363
|
-
if (deterministic &&
|
|
364
|
-
!sameCompany(def, deterministic)) {
|
|
365
|
-
return {
|
|
366
|
-
status: "company_conflict",
|
|
367
|
-
defaultCompany: def,
|
|
368
|
-
evidenceCompany: deterministic,
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
return { status: "resolved", company: def };
|
|
372
|
-
}
|
|
373
355
|
if (deterministic) {
|
|
374
356
|
return { status: "resolved", company: deterministic };
|
|
375
357
|
}
|
|
@@ -2,6 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { writeSync } from "node:fs";
|
|
3
3
|
import * as nodePath from "node:path";
|
|
4
4
|
import { cognitoCachePrincipalUid } from "./utils/cognito-session.js";
|
|
5
|
+
import { gatedInteractiveLogin } from "./utils/interactive-caller.js";
|
|
5
6
|
import { DEFAULT_SECRETS_CACHE_TTL_MS, hasCacheEntry, readCache, removeCacheEntry, writeCache, } from "./utils/secrets-cache.js";
|
|
6
7
|
import { computeSha256 } from "./utils/integrity.js";
|
|
7
8
|
import { SECRET_NAME_PATTERN } from "./commands/_patterns.js";
|
|
@@ -21,10 +22,12 @@ const SECRET_LOAD_TIMEOUT_MS = 30_000;
|
|
|
21
22
|
* stdin and stderr must be TTYs, and CI is always non-interactive.
|
|
22
23
|
*/
|
|
23
24
|
export function secretsCredentialOptions() {
|
|
25
|
+
// The shared gate also refuses lanes, daemons, bot runtimes, launchd
|
|
26
|
+
// tasks, and HQ_NO_BROWSER_LOGIN children that happen to hold a TTY.
|
|
24
27
|
return {
|
|
25
|
-
interactive: process.stdin.isTTY === true &&
|
|
28
|
+
interactive: gatedInteractiveLogin(process.stdin.isTTY === true &&
|
|
26
29
|
process.stderr.isTTY === true &&
|
|
27
|
-
!process.env.CI,
|
|
30
|
+
!process.env.CI),
|
|
28
31
|
};
|
|
29
32
|
}
|
|
30
33
|
export function scopeOpts(opts) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { CognitoAuthConfig } from "@indigoai-us/hq-cloud";
|
|
2
|
+
/** Fallback and default loopback callback port. */
|
|
3
|
+
export declare const DEFAULT_COGNITO_CALLBACK_PORT = 8765;
|
|
4
|
+
/** Ports registered as callback URLs on the HQ CLI Cognito app client. */
|
|
5
|
+
export declare const REGISTERED_COGNITO_CALLBACK_PORTS: readonly number[];
|
|
6
|
+
export type PortWarn = (message: string) => void;
|
|
7
|
+
/** Test hook: forget that the stray-port warning was printed. */
|
|
8
|
+
export declare function resetStrayPortWarningForTests(): void;
|
|
9
|
+
/**
|
|
10
|
+
* Resolve a callback port value, a string from the environment or a number,
|
|
11
|
+
* to a registered port. Unset resolves to 8765 silently; an unregistered value
|
|
12
|
+
* resolves to 8765 and logs a warning.
|
|
13
|
+
*/
|
|
14
|
+
export declare function resolveCognitoCallbackPort(raw: string | number | undefined, warn?: PortWarn): number;
|
|
15
|
+
/** Return `config` with its callback port forced into the registered set. */
|
|
16
|
+
export declare function withRegisteredCallbackPort<T extends CognitoAuthConfig>(config: T, warn?: PortWarn): T;
|
|
17
|
+
//# sourceMappingURL=cognito-callback-port.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// src/utils/cognito-callback-port.ts
|
|
2
|
+
//
|
|
3
|
+
// The HQ CLI Cognito app client accepts exactly three loopback redirect URIs:
|
|
4
|
+
// http://localhost:{8765,3000,53682}/callback (hq-pro infra/cognito.ts,
|
|
5
|
+
// callbackUrls). browserLogin builds its redirect_uri from config.port, so any
|
|
6
|
+
// other port opens a sign-in tab that Cognito rejects with redirect_mismatch.
|
|
7
|
+
// A stray HQ_COGNITO_CALLBACK_PORT (seen with random ports such as 57726 and
|
|
8
|
+
// 61604) produced exactly that tab. Every browser sign-in now goes through
|
|
9
|
+
// this guard: an unregistered port falls back to 8765 with a stderr warning.
|
|
10
|
+
/** Fallback and default loopback callback port. */
|
|
11
|
+
export const DEFAULT_COGNITO_CALLBACK_PORT = 8765;
|
|
12
|
+
/** Ports registered as callback URLs on the HQ CLI Cognito app client. */
|
|
13
|
+
export const REGISTERED_COGNITO_CALLBACK_PORTS = [8765, 3000, 53682];
|
|
14
|
+
/** One stray-port warning per process: config and session both resolve the env value. */
|
|
15
|
+
let warnedStrayPort = false;
|
|
16
|
+
function defaultWarn(message) {
|
|
17
|
+
console.error(message);
|
|
18
|
+
}
|
|
19
|
+
function warnOnce(message, warn) {
|
|
20
|
+
if (warn === defaultWarn) {
|
|
21
|
+
if (warnedStrayPort)
|
|
22
|
+
return;
|
|
23
|
+
warnedStrayPort = true;
|
|
24
|
+
}
|
|
25
|
+
warn(message);
|
|
26
|
+
}
|
|
27
|
+
/** Test hook: forget that the stray-port warning was printed. */
|
|
28
|
+
export function resetStrayPortWarningForTests() {
|
|
29
|
+
warnedStrayPort = false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a callback port value, a string from the environment or a number,
|
|
33
|
+
* to a registered port. Unset resolves to 8765 silently; an unregistered value
|
|
34
|
+
* resolves to 8765 and logs a warning.
|
|
35
|
+
*/
|
|
36
|
+
export function resolveCognitoCallbackPort(raw, warn = defaultWarn) {
|
|
37
|
+
if (raw === undefined || (typeof raw === "string" && !raw.trim())) {
|
|
38
|
+
return DEFAULT_COGNITO_CALLBACK_PORT;
|
|
39
|
+
}
|
|
40
|
+
const port = typeof raw === "number" ? raw : Number(raw.trim());
|
|
41
|
+
if (REGISTERED_COGNITO_CALLBACK_PORTS.includes(port))
|
|
42
|
+
return port;
|
|
43
|
+
warnOnce(`hq: ignoring callback port ${String(raw)}: it is not a registered Cognito callback ` +
|
|
44
|
+
`(${REGISTERED_COGNITO_CALLBACK_PORTS.join(", ")}). Using ${DEFAULT_COGNITO_CALLBACK_PORT}. ` +
|
|
45
|
+
"Unset HQ_COGNITO_CALLBACK_PORT or set it to a registered port.", warn);
|
|
46
|
+
return DEFAULT_COGNITO_CALLBACK_PORT;
|
|
47
|
+
}
|
|
48
|
+
/** Return `config` with its callback port forced into the registered set. */
|
|
49
|
+
export function withRegisteredCallbackPort(config, warn = defaultWarn) {
|
|
50
|
+
// Unset keeps hq-cloud's own default (3000), which is registered.
|
|
51
|
+
if (config.port === undefined)
|
|
52
|
+
return config;
|
|
53
|
+
const port = resolveCognitoCallbackPort(config.port, warn);
|
|
54
|
+
return port === config.port ? config : { ...config, port };
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=cognito-callback-port.js.map
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
+
import { resolveCognitoCallbackPort } from "./cognito-callback-port.js";
|
|
1
2
|
/** Lightweight Cognito and Vault defaults shared by startup-safe modules. */
|
|
2
3
|
export const DEFAULT_COGNITO = {
|
|
3
4
|
region: process.env.AWS_REGION ?? "us-east-1",
|
|
4
5
|
userPoolDomain: process.env.HQ_COGNITO_DOMAIN ?? "vault-indigo-hq-prod",
|
|
5
6
|
clientId: process.env.HQ_COGNITO_CLIENT_ID ?? "7acei2c8v870enheptb1j5foln",
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// Resolved on read, not at module load: main.ts imports this for every
|
|
8
|
+
// command, and a stray HQ_COGNITO_CALLBACK_PORT should warn only when a
|
|
9
|
+
// sign-in actually reads the port.
|
|
10
|
+
get port() {
|
|
11
|
+
return resolveCognitoCallbackPort(process.env.HQ_COGNITO_CALLBACK_PORT);
|
|
12
|
+
},
|
|
9
13
|
identityProvider: process.env.HQ_COGNITO_IDENTITY_PROVIDER !== undefined
|
|
10
14
|
? process.env.HQ_COGNITO_IDENTITY_PROVIDER || undefined
|
|
11
15
|
: "Google",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
*/
|
|
21
21
|
import { isExpiring, isMachineIdentity, type CognitoAuthConfig, type CognitoTokens, type ClientInfo, type VaultServiceConfig } from "@indigoai-us/hq-cloud";
|
|
22
22
|
import { hasPersonCachedSession, isMachineCredsFileEnvSet, wantsMachineCognitoTokens, type CognitoTokenSource } from "./machine-principal-state.js";
|
|
23
|
+
import { HQ_NO_BROWSER_LOGIN_ENV, type InteractiveCallerInput } from "./interactive-caller.js";
|
|
23
24
|
import { loadCachedTokens, personTokenCacheFile, readPersonTokenFile } from "./person-token-cache.js";
|
|
24
25
|
export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
25
26
|
export { isExpiring, isMachineIdentity };
|
|
@@ -115,14 +116,14 @@ export type EnsureCognitoTokenOptions = {
|
|
|
115
116
|
/** Cancels bounded, non-interactive person-session refreshes. */
|
|
116
117
|
signal?: AbortSignal;
|
|
117
118
|
};
|
|
119
|
+
export { HQ_NO_BROWSER_LOGIN_ENV };
|
|
118
120
|
/**
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
121
|
+
* An explicit `interactive` option wins. Otherwise the implicit browser sign-in
|
|
122
|
+
* is allowed only for an interactive caller (see isInteractiveCaller): a TTY on
|
|
123
|
+
* stdin and stdout, and not a daemon, lane, monitor, bot, `mcp serve`, sync
|
|
124
|
+
* runner, launchd task, or HQ_NO_BROWSER_LOGIN=1 child.
|
|
122
125
|
*/
|
|
123
|
-
export declare
|
|
124
|
-
/** An explicit `interactive` option wins; otherwise interactive unless the env switch is "1". */
|
|
125
|
-
export declare function resolveInteractiveLogin(options: Pick<EnsureCognitoTokenOptions, "interactive">, env?: NodeJS.ProcessEnv): boolean;
|
|
126
|
+
export declare function resolveInteractiveLogin(options: Pick<EnsureCognitoTokenOptions, "interactive">, env?: NodeJS.ProcessEnv, caller?: Omit<InteractiveCallerInput, "env">): boolean;
|
|
126
127
|
/**
|
|
127
128
|
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
128
129
|
* cache, and the default machine-creds file is readable. Distinct from
|
|
@@ -32,6 +32,8 @@ import { daemonDir } from "../lib/mesh/live/daemon/paths.js";
|
|
|
32
32
|
import { workMeshRoot } from "../lib/mesh/live/paths.js";
|
|
33
33
|
import { defaultHqRootFallback, hqDiscoveryCandidates, pathForPlatform, } from "./hq-root-resolver.js";
|
|
34
34
|
import { acquireLockFileExclusively } from "./exclusive-lock.js";
|
|
35
|
+
import { resolveCognitoCallbackPort, withRegisteredCallbackPort } from "./cognito-callback-port.js";
|
|
36
|
+
import { HQ_NO_BROWSER_LOGIN_ENV, NON_INTERACTIVE_LOGIN_MESSAGE, isInteractiveCaller, } from "./interactive-caller.js";
|
|
35
37
|
import { reportNoninteractivePersonRefreshRejection } from "./cognito-refresh-sentry.js";
|
|
36
38
|
import { decodeJwtPayload, loadCachedTokens, personTokenCacheFile, readPersonTokenFile, readTokenFile, } from "./person-token-cache.js";
|
|
37
39
|
export { loadCachedTokens, personTokenCacheFile, readPersonTokenFile };
|
|
@@ -49,9 +51,12 @@ export const DEFAULT_COGNITO = {
|
|
|
49
51
|
region: process.env.AWS_REGION ?? "us-east-1",
|
|
50
52
|
userPoolDomain: process.env.HQ_COGNITO_DOMAIN ?? "vault-indigo-hq-prod",
|
|
51
53
|
clientId: process.env.HQ_COGNITO_CLIENT_ID ?? "7acei2c8v870enheptb1j5foln",
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
// Resolved on read, not at module load: main.ts imports this for every
|
|
55
|
+
// command, and a stray HQ_COGNITO_CALLBACK_PORT should warn only when a
|
|
56
|
+
// sign-in actually reads the port.
|
|
57
|
+
get port() {
|
|
58
|
+
return resolveCognitoCallbackPort(process.env.HQ_COGNITO_CALLBACK_PORT);
|
|
59
|
+
},
|
|
55
60
|
// Skip Cognito's Hosted UI — go straight to Google OAuth. The Cognito
|
|
56
61
|
// /oauth2/authorize endpoint honors `identity_provider` and performs an
|
|
57
62
|
// internal redirect, so the user never sees the (un-styleable) Hosted UI.
|
|
@@ -244,17 +249,17 @@ export function machineTokenCacheFile(home = os.homedir(), env = process.env) {
|
|
|
244
249
|
export function defaultMachineCredsFile(home = os.homedir()) {
|
|
245
250
|
return path.join(home, ".hq-agent", "machine-creds.json");
|
|
246
251
|
}
|
|
252
|
+
export { HQ_NO_BROWSER_LOGIN_ENV };
|
|
247
253
|
/**
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
254
|
+
* An explicit `interactive` option wins. Otherwise the implicit browser sign-in
|
|
255
|
+
* is allowed only for an interactive caller (see isInteractiveCaller): a TTY on
|
|
256
|
+
* stdin and stdout, and not a daemon, lane, monitor, bot, `mcp serve`, sync
|
|
257
|
+
* runner, launchd task, or HQ_NO_BROWSER_LOGIN=1 child.
|
|
251
258
|
*/
|
|
252
|
-
export
|
|
253
|
-
/** An explicit `interactive` option wins; otherwise interactive unless the env switch is "1". */
|
|
254
|
-
export function resolveInteractiveLogin(options, env = process.env) {
|
|
259
|
+
export function resolveInteractiveLogin(options, env = process.env, caller = {}) {
|
|
255
260
|
if (options.interactive !== undefined)
|
|
256
261
|
return options.interactive;
|
|
257
|
-
return
|
|
262
|
+
return isInteractiveCaller({ ...caller, env });
|
|
258
263
|
}
|
|
259
264
|
/**
|
|
260
265
|
* Fresh-box / Outpost bootstrap: env unset, not forced to person, no person
|
|
@@ -939,7 +944,7 @@ async function ensureCognitoTokenUnserialized(options) {
|
|
|
939
944
|
return fallback.idToken;
|
|
940
945
|
}
|
|
941
946
|
if (!interactive) {
|
|
942
|
-
throw new Error(
|
|
947
|
+
throw new Error(NON_INTERACTIVE_LOGIN_MESSAGE);
|
|
943
948
|
}
|
|
944
949
|
if (isHeadlessHost() && hostRunsAnAgent()) {
|
|
945
950
|
// An agent host with no display has no one to complete a browser sign-in,
|
|
@@ -951,7 +956,7 @@ async function ensureCognitoTokenUnserialized(options) {
|
|
|
951
956
|
"(or HQ_MACHINE_CREDS_FILE to its machine-creds.json). Otherwise run `hq login` on a computer with a browser.");
|
|
952
957
|
}
|
|
953
958
|
console.error(chalk.cyan(" No cached HQ session — launching browser sign-in..."));
|
|
954
|
-
const tokens = await browserLogin(DEFAULT_COGNITO);
|
|
959
|
+
const tokens = await browserLogin(withRegisteredCallbackPort(DEFAULT_COGNITO));
|
|
955
960
|
return tokens.accessToken;
|
|
956
961
|
}
|
|
957
962
|
/**
|
|
@@ -1022,7 +1027,7 @@ async function ensureCognitoIdTokenUnserialized(options) {
|
|
|
1022
1027
|
return fallback.idToken;
|
|
1023
1028
|
}
|
|
1024
1029
|
if (!interactive) {
|
|
1025
|
-
throw new Error(
|
|
1030
|
+
throw new Error(NON_INTERACTIVE_LOGIN_MESSAGE);
|
|
1026
1031
|
}
|
|
1027
1032
|
if (isHeadlessHost() && hostRunsAnAgent()) {
|
|
1028
1033
|
// A browser sign-in on a server has no one to complete it and blocks
|
|
@@ -1032,7 +1037,7 @@ async function ensureCognitoIdTokenUnserialized(options) {
|
|
|
1032
1037
|
"(or HQ_MACHINE_CREDS_FILE to its machine-creds.json). Otherwise run `hq login` on a computer with a browser.");
|
|
1033
1038
|
}
|
|
1034
1039
|
console.error(chalk.cyan(" No cached HQ session — launching browser sign-in..."));
|
|
1035
|
-
const tokens = await browserLogin(DEFAULT_COGNITO);
|
|
1040
|
+
const tokens = await browserLogin(withRegisteredCallbackPort(DEFAULT_COGNITO));
|
|
1036
1041
|
return tokens.idToken;
|
|
1037
1042
|
}
|
|
1038
1043
|
/**
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** Parent-set switch: a child `hq` must never open a browser sign-in. */
|
|
2
|
+
export declare const HQ_NO_BROWSER_LOGIN_ENV = "HQ_NO_BROWSER_LOGIN";
|
|
3
|
+
/** Remedy printed when an implicit sign-in is refused. */
|
|
4
|
+
export declare const NON_INTERACTIVE_LOGIN_MESSAGE: string;
|
|
5
|
+
export type InteractiveCallerInput = {
|
|
6
|
+
env?: NodeJS.ProcessEnv;
|
|
7
|
+
argv?: readonly string[];
|
|
8
|
+
stdinIsTTY?: boolean;
|
|
9
|
+
stdoutIsTTY?: boolean;
|
|
10
|
+
};
|
|
11
|
+
/** The reason a caller is not interactive, or null when it is. */
|
|
12
|
+
export declare function nonInteractiveReason(input?: InteractiveCallerInput): string | null;
|
|
13
|
+
/** True only when a person is at a terminal and may complete a browser sign-in. */
|
|
14
|
+
export declare function isInteractiveCaller(input?: InteractiveCallerInput): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Gate a call site's request for an implicit browser sign-in. A command that
|
|
17
|
+
* asks for `interactive: true` gets it only when a person is at a terminal, so
|
|
18
|
+
* the same command run from a lane, daemon, bot, launchd task, or piped child
|
|
19
|
+
* fails with the `hq login` remedy instead of opening a sign-in tab. Explicit
|
|
20
|
+
* `false` stays false. `hq login` calls browserLogin directly and is not gated.
|
|
21
|
+
*/
|
|
22
|
+
export declare function gatedInteractiveLogin(requested?: boolean, input?: InteractiveCallerInput): boolean;
|
|
23
|
+
//# sourceMappingURL=interactive-caller.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/utils/interactive-caller.ts
|
|
2
|
+
//
|
|
3
|
+
// One predicate for "may this process open an implicit browser sign-in?".
|
|
4
|
+
//
|
|
5
|
+
// HQ-CLI-15 follow-up: ~100 ensureCognitoToken/ensureCognitoIdToken call sites
|
|
6
|
+
// fall back to browserLogin when a token refresh fails. Before this predicate
|
|
7
|
+
// the fallback ran for every caller unless HQ_NO_BROWSER_LOGIN=1 was set, so a
|
|
8
|
+
// daemon, lane, monitor, bot, `hq mcp serve`, sync runner, or launchd task
|
|
9
|
+
// opened a sign-in tab on its own that nobody had asked for. The implicit
|
|
10
|
+
// sign-in is now allowed only when a person is at a terminal: stdin AND stdout
|
|
11
|
+
// are TTYs and none of the background markers below is present. Explicit
|
|
12
|
+
// `hq login` calls browserLogin directly and is not gated here.
|
|
13
|
+
/** Parent-set switch: a child `hq` must never open a browser sign-in. */
|
|
14
|
+
export const HQ_NO_BROWSER_LOGIN_ENV = "HQ_NO_BROWSER_LOGIN";
|
|
15
|
+
/** Remedy printed when an implicit sign-in is refused. */
|
|
16
|
+
export const NON_INTERACTIVE_LOGIN_MESSAGE = "No valid HQ session and interactive login is disabled. Run `hq login` first. " +
|
|
17
|
+
"This process is not interactive (no terminal, or a background HQ runtime), so it did not open a browser sign-in.";
|
|
18
|
+
/** launchd job labels the HQ service manager installs (see main.ts runCli). */
|
|
19
|
+
const HQ_LAUNCHD_LABEL_PREFIXES = ["ai.indigo.", "ai.getindigo."];
|
|
20
|
+
/** Env markers set for background HQ runtimes. Any non-empty value counts. */
|
|
21
|
+
const BACKGROUND_ENV_MARKERS = [
|
|
22
|
+
"HQ_LANE_ID", // hq lanes worker sessions
|
|
23
|
+
"HQ_BOT_AGENT_UID", // hq bot runtimes
|
|
24
|
+
"HQ_DAEMON_UNIT", // daemon service units
|
|
25
|
+
];
|
|
26
|
+
/**
|
|
27
|
+
* argv prefixes (after `hq`) for long-running background entry points. The
|
|
28
|
+
* daemon's sync runner child has no TTY on stdio, so the TTY check covers it.
|
|
29
|
+
*/
|
|
30
|
+
const BACKGROUND_COMMANDS = [
|
|
31
|
+
["daemon"],
|
|
32
|
+
["mesh", "daemon"],
|
|
33
|
+
["bot", "run"],
|
|
34
|
+
["mcp", "serve"],
|
|
35
|
+
["monitor"],
|
|
36
|
+
];
|
|
37
|
+
function nonEmpty(value) {
|
|
38
|
+
return Boolean(value && value.trim());
|
|
39
|
+
}
|
|
40
|
+
/** The reason a caller is not interactive, or null when it is. */
|
|
41
|
+
export function nonInteractiveReason(input = {}) {
|
|
42
|
+
const env = input.env ?? process.env;
|
|
43
|
+
const argv = input.argv ?? process.argv.slice(2);
|
|
44
|
+
const stdinIsTTY = input.stdinIsTTY ?? process.stdin.isTTY === true;
|
|
45
|
+
const stdoutIsTTY = input.stdoutIsTTY ?? process.stdout.isTTY === true;
|
|
46
|
+
if (env[HQ_NO_BROWSER_LOGIN_ENV]?.trim() === "1")
|
|
47
|
+
return `${HQ_NO_BROWSER_LOGIN_ENV}=1`;
|
|
48
|
+
if (!stdinIsTTY)
|
|
49
|
+
return "stdin is not a terminal";
|
|
50
|
+
if (!stdoutIsTTY)
|
|
51
|
+
return "stdout is not a terminal";
|
|
52
|
+
for (const marker of BACKGROUND_ENV_MARKERS) {
|
|
53
|
+
if (nonEmpty(env[marker]))
|
|
54
|
+
return `${marker} is set`;
|
|
55
|
+
}
|
|
56
|
+
const xpc = env.XPC_SERVICE_NAME ?? "";
|
|
57
|
+
if (HQ_LAUNCHD_LABEL_PREFIXES.some((p) => xpc.startsWith(p)))
|
|
58
|
+
return "launchd task";
|
|
59
|
+
const args = argv.filter((a) => !a.startsWith("-"));
|
|
60
|
+
for (const cmd of BACKGROUND_COMMANDS) {
|
|
61
|
+
if (cmd.every((part, i) => args[i] === part))
|
|
62
|
+
return `\`hq ${cmd.join(" ")}\``;
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
/** True only when a person is at a terminal and may complete a browser sign-in. */
|
|
67
|
+
export function isInteractiveCaller(input = {}) {
|
|
68
|
+
return nonInteractiveReason(input) === null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Gate a call site's request for an implicit browser sign-in. A command that
|
|
72
|
+
* asks for `interactive: true` gets it only when a person is at a terminal, so
|
|
73
|
+
* the same command run from a lane, daemon, bot, launchd task, or piped child
|
|
74
|
+
* fails with the `hq login` remedy instead of opening a sign-in tab. Explicit
|
|
75
|
+
* `false` stays false. `hq login` calls browserLogin directly and is not gated.
|
|
76
|
+
*/
|
|
77
|
+
export function gatedInteractiveLogin(requested = true, input = {}) {
|
|
78
|
+
return requested && isInteractiveCaller(input);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=interactive-caller.js.map
|
|
@@ -2,6 +2,7 @@ import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
3
|
import { browserLogin, saveCachedTokens, CognitoAuthError } from "@indigoai-us/hq-cloud";
|
|
4
4
|
import { isHeadless } from "./headless.js";
|
|
5
|
+
import { withRegisteredCallbackPort } from "./cognito-callback-port.js";
|
|
5
6
|
function assertHumanLoginAllowed() {
|
|
6
7
|
// Preserve hq-cloud's company-machine boundary on the manual URL path.
|
|
7
8
|
if (["1", "true", "yes"].includes(process.env.HQ_MACHINE_AUTH_REQUIRED?.trim().toLowerCase() ?? "")) {
|
|
@@ -10,10 +11,12 @@ function assertHumanLoginAllowed() {
|
|
|
10
11
|
}
|
|
11
12
|
/** Keep desktop login unchanged; manual login uses the same PKCE loopback flow. */
|
|
12
13
|
export async function loginWithBrowserPreference(config, browser = true) {
|
|
14
|
+
// A callback port outside the registered set opens a redirect_mismatch tab.
|
|
13
15
|
if (browser && !isHeadless())
|
|
14
|
-
return browserLogin(config);
|
|
16
|
+
return browserLogin(withRegisteredCallbackPort(config));
|
|
15
17
|
assertHumanLoginAllowed();
|
|
16
|
-
|
|
18
|
+
// The manual URL path builds the same redirect_uri, so it needs the same guard.
|
|
19
|
+
const port = withRegisteredCallbackPort(config).port ?? 3000;
|
|
17
20
|
const redirectUri = `http://localhost:${port}/callback`;
|
|
18
21
|
const base = `https://${config.userPoolDomain}.auth.${config.region}.amazoncognito.com`;
|
|
19
22
|
const verifier = randomBytes(32).toString("base64url");
|
|
@@ -28,6 +28,8 @@ export const RESIDENT_PACKAGE_MODULES = {
|
|
|
28
28
|
],
|
|
29
29
|
"agents-terminal": ["../sentry.js", "../lib/doctor/client-health-reason-codes-flag.js", "@indigoai-us/hq-flags-client", "../lib/plan-limit-nag.js", "../utils/plan-gate-error.js", "open", "../utils/cognito-session.js", "../lib/first-sync-next-step.js", "@indigoai-us/hq-cloud"],
|
|
30
30
|
bot: [
|
|
31
|
+
"../commands/lanes.js",
|
|
32
|
+
"../lib/lanes/reconcile.js",
|
|
31
33
|
"../lib/flag-registry.js",
|
|
32
34
|
"../commands/access.js",
|
|
33
35
|
"../commands/sources.js",
|
|
@@ -176,6 +178,8 @@ const LOADERS = {
|
|
|
176
178
|
() => import("@indigoai-us/hq-cloud"),
|
|
177
179
|
],
|
|
178
180
|
bot: [
|
|
181
|
+
() => import("../commands/lanes.js"),
|
|
182
|
+
() => import("../lib/lanes/reconcile.js"),
|
|
179
183
|
() => import("../lib/flag-registry.js"),
|
|
180
184
|
() => import("../commands/access.js"),
|
|
181
185
|
// The signals engine's adapters step (lib/signals/steps/adapters.ts)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@indigoai-us/hq-cli",
|
|
3
|
-
"version": "5.345.
|
|
3
|
+
"version": "5.345.200",
|
|
4
4
|
"description": "HQ by Indigo management CLI — modules and cloud sync",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -116,11 +116,11 @@
|
|
|
116
116
|
"*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}": "eslint --cache --no-error-on-unmatched-pattern"
|
|
117
117
|
},
|
|
118
118
|
"optionalDependencies": {
|
|
119
|
-
"@indigoai-us/hq-hook-win32-x64": "5.345.
|
|
120
|
-
"@indigoai-us/hq-hook-win32-arm64": "5.345.
|
|
121
|
-
"@indigoai-us/hq-hook-darwin-x64": "5.345.
|
|
122
|
-
"@indigoai-us/hq-hook-darwin-arm64": "5.345.
|
|
123
|
-
"@indigoai-us/hq-hook-linux-x64": "5.345.
|
|
124
|
-
"@indigoai-us/hq-hook-linux-arm64": "5.345.
|
|
119
|
+
"@indigoai-us/hq-hook-win32-x64": "5.345.200",
|
|
120
|
+
"@indigoai-us/hq-hook-win32-arm64": "5.345.200",
|
|
121
|
+
"@indigoai-us/hq-hook-darwin-x64": "5.345.200",
|
|
122
|
+
"@indigoai-us/hq-hook-darwin-arm64": "5.345.200",
|
|
123
|
+
"@indigoai-us/hq-hook-linux-x64": "5.345.200",
|
|
124
|
+
"@indigoai-us/hq-hook-linux-arm64": "5.345.200"
|
|
125
125
|
}
|
|
126
126
|
}
|