@indigoai-us/hq-cli 5.275.0 → 5.277.0
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 +112 -0
- package/README.md +12 -0
- package/assets/bot-workers/setup/context/USER-GUIDE.md +4 -4
- package/assets/scaffold/core/scripts/hq-delegate-pickup.sh +99 -0
- package/assets/scaffold/core/scripts/hq-delegate-repo.sh +158 -0
- package/assets/scaffold/core/scripts/hq-delegate-resolve.sh +145 -0
- package/assets/scaffold/core/scripts/hq-delegate-secrets.sh +129 -0
- package/assets/scaffold/core/scripts/hq-delegate-transfer.sh +175 -0
- package/assets/scaffold/core/scripts/lib/secret-patterns.sh +58 -0
- package/dist/command-catalog.generated.d.ts +239 -1
- package/dist/command-catalog.generated.js +302 -1
- package/dist/commands/core-checkpoint.d.ts +4 -2
- package/dist/commands/core-checkpoint.js +34 -3
- package/dist/commands/core-command-catalog.d.ts +4 -2
- package/dist/commands/core-command-catalog.js +5 -2
- package/dist/commands/core-delegate.d.ts +6 -0
- package/dist/commands/core-delegate.js +987 -0
- package/dist/commands/core-ontology.js +3 -3
- package/dist/commands/core.js +11 -1
- package/dist/commands/daemon.d.ts +4 -0
- package/dist/commands/daemon.js +175 -2
- package/dist/commands/dm.d.ts +2 -0
- package/dist/commands/dm.js +11 -4
- package/dist/commands/feedback.d.ts +6 -0
- package/dist/commands/feedback.js +86 -37
- package/dist/commands/lanes-accounts.js +44 -2
- package/dist/commands/lanes-message.js +69 -12
- package/dist/commands/lanes-resume.js +2 -0
- package/dist/commands/lanes-shared.d.ts +3 -0
- package/dist/commands/lanes-shared.js +12 -2
- package/dist/commands/lanes.d.ts +5 -1
- package/dist/commands/lanes.js +402 -49
- package/dist/commands/run-feature-telemetry.d.ts +3 -0
- package/dist/commands/run-feature-telemetry.js +6 -0
- package/dist/commands/run.js +3 -2
- package/dist/commands/secrets.d.ts +16 -1
- package/dist/commands/secrets.js +80 -45
- package/dist/commands/sync-mode.d.ts +11 -0
- package/dist/commands/sync-mode.js +30 -0
- package/dist/commands/whoami.d.ts +4 -6
- package/dist/commands/whoami.js +39 -8
- package/dist/fast-core.d.ts +6 -7
- package/dist/fast-core.js +22 -8
- package/dist/fast-scaffold.d.ts +33 -7
- package/dist/fast-scaffold.js +104 -8
- package/dist/index.js +32 -1
- package/dist/lanes-drain-hook.js +154 -7
- package/dist/lib/agent-kit/run/inbox.js +3 -2
- package/dist/lib/billing/plan-lock.d.ts +3 -1
- package/dist/lib/billing/plan-lock.js +7 -1
- package/dist/lib/checkpoint-ontology/apply.d.ts +6 -0
- package/dist/lib/checkpoint-ontology/apply.js +60 -3
- package/dist/lib/checkpoint-ontology/company.d.ts +8 -4
- package/dist/lib/checkpoint-ontology/company.js +15 -7
- package/dist/lib/checkpoint-ontology/plan.d.ts +36 -0
- package/dist/lib/checkpoint-ontology/plan.js +66 -4
- package/dist/lib/checkpoint-ontology/queued-touches.d.ts +32 -0
- package/dist/lib/checkpoint-ontology/queued-touches.js +193 -0
- package/dist/lib/checkpoint-ontology/tree.d.ts +2 -0
- package/dist/lib/checkpoint-ontology/tree.js +34 -0
- package/dist/lib/core-utils/jq-json-serializer.d.ts +5 -0
- package/dist/lib/core-utils/jq-json-serializer.js +10 -0
- package/dist/lib/core-utils/secret-patterns.d.ts +41 -0
- package/dist/lib/core-utils/secret-patterns.js +33 -0
- package/dist/lib/core-utils/timeout-guard-native.d.ts +7 -0
- package/dist/lib/core-utils/timeout-guard-native.js +40 -0
- package/dist/lib/core-utils/timeout-guard.d.ts +3 -3
- package/dist/lib/core-utils/timeout-guard.js +28 -13
- package/dist/lib/daemon/catalog.d.ts +18 -2
- package/dist/lib/daemon/catalog.js +18 -2
- package/dist/lib/daemon/config.d.ts +6 -0
- package/dist/lib/daemon/config.js +15 -2
- package/dist/lib/daemon/requests.d.ts +4 -0
- package/dist/lib/daemon/requests.js +2 -0
- package/dist/lib/daemon/run.js +94 -8
- package/dist/lib/daemon/supervisor.d.ts +8 -0
- package/dist/lib/daemon/supervisor.js +101 -1
- package/dist/lib/daemon/sync-runner.d.ts +58 -0
- package/dist/lib/daemon/sync-runner.js +277 -0
- package/dist/lib/doctor/__testing__/run-doctor.d.ts +8 -0
- package/dist/lib/doctor/__testing__/run-doctor.js +52 -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/flag-registry.d.ts +12 -7
- package/dist/lib/flag-registry.js +66 -75
- package/dist/lib/flag-snapshot-file.d.ts +20 -0
- package/dist/lib/flag-snapshot-file.js +91 -0
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +46 -8
- package/dist/lib/lanes/config.js +15 -0
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +2 -0
- package/dist/lib/lanes/hire-grant-dispatch.js +41 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +3 -0
- package/dist/lib/lanes/keep-alive.js +3 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +4 -0
- package/dist/lib/lanes/outpost-dispatch.js +11 -2
- package/dist/lib/lanes/reconcile.js +20 -4
- package/dist/lib/lanes/spawn.js +17 -10
- package/dist/lib/lanes/store.js +45 -1
- package/dist/lib/lanes/types.d.ts +30 -1
- package/dist/lib/lanes/types.js +2 -2
- package/dist/lib/lanes/watcher.js +3 -2
- package/dist/lib/process-flag-company-context.d.ts +8 -0
- package/dist/lib/process-flag-company-context.js +67 -0
- package/dist/main.js +3 -1
- package/dist/native-fast-route.d.ts +3 -0
- package/dist/native-fast-route.js +39 -0
- package/dist/secrets-env-fast-path.d.ts +5 -0
- package/dist/secrets-env-fast-path.js +16 -0
- package/dist/secrets-env-fast-route.d.ts +18 -0
- package/dist/secrets-env-fast-route.js +54 -0
- package/dist/utils/cli-telemetry.d.ts +26 -2
- package/dist/utils/cli-telemetry.js +178 -3
- package/dist/utils/hq-root-resolver.d.ts +13 -0
- package/dist/utils/hq-root-resolver.js +103 -0
- package/dist/utils/scaffold-fast-import-guard.js +6 -1
- package/dist/utils/vault-api.d.ts +8 -0
- package/dist/utils/vault-api.js +13 -1
- package/package.json +2 -2
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { withCliFeatureUsed } from '../utils/cli-telemetry.js';
|
|
2
|
+
/** Attribute hq run's batch secret read to its resolved company UID. */
|
|
3
|
+
export function withRunSecretReadTelemetry(companyUid, action) {
|
|
4
|
+
return withCliFeatureUsed('secret_read', action, companyUid);
|
|
5
|
+
}
|
|
6
|
+
//# sourceMappingURL=run-feature-telemetry.js.map
|
package/dist/commands/run.js
CHANGED
|
@@ -11,6 +11,7 @@ import { discoverSchemas } from '../run/discover-schemas.js';
|
|
|
11
11
|
import { installHqPlugin, prewarmHqSecrets } from '../run/hq-plugin.js';
|
|
12
12
|
import { assertEnvGraphLoaded, resolveEnvValuesOrThrow, EnvGraphLoadError, EnvResolutionError, } from '../run/env-graph-guard.js';
|
|
13
13
|
import { hadFatalRejection } from '../unhandled-rejection-boundary.js';
|
|
14
|
+
import { withRunSecretReadTelemetry } from './run-feature-telemetry.js';
|
|
14
15
|
const SECRET_LOAD_TIMEOUT_MS = 30_000;
|
|
15
16
|
export async function buildRunUsage(scriptPath, scriptId) {
|
|
16
17
|
if (scriptId && !scriptPath) {
|
|
@@ -87,7 +88,7 @@ export function registerRunCommand(program) {
|
|
|
87
88
|
allowOfflineScopeFallback: true,
|
|
88
89
|
});
|
|
89
90
|
const usage = await buildRunUsage(opts.script, opts.scriptId);
|
|
90
|
-
const fetchBatch = async (companyUid, names, requestUsage) => {
|
|
91
|
+
const fetchBatch = async (companyUid, names, requestUsage) => withRunSecretReadTelemetry(companyUid, async () => {
|
|
91
92
|
const res = await vaultApiFetch({
|
|
92
93
|
token,
|
|
93
94
|
path: `/secrets/${encodeURIComponent(companyUid)}/load`,
|
|
@@ -113,7 +114,7 @@ export function registerRunCommand(program) {
|
|
|
113
114
|
throw new Error(`Failed to batch-load secrets: ${message}`);
|
|
114
115
|
}
|
|
115
116
|
return res.json();
|
|
116
|
-
};
|
|
117
|
+
});
|
|
117
118
|
const pluginOpts = {
|
|
118
119
|
companyOverride: opts.company,
|
|
119
120
|
principalUid: cognitoCachePrincipalUid(token) ?? undefined,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
1
|
+
import type { Command } from "commander";
|
|
2
2
|
import { writeSync } from "node:fs";
|
|
3
3
|
import { vaultApiFetch, getCompanyUid, getEntityUid } from "../utils/vault-api.js";
|
|
4
4
|
export type { VaultApiOptions } from "../utils/vault-api.js";
|
|
@@ -88,5 +88,20 @@ export declare function loadRevealedSecrets(token: string, companyUid: string, k
|
|
|
88
88
|
* `exit` and `write` are injectable for tests; production uses the real ones.
|
|
89
89
|
*/
|
|
90
90
|
export declare function emitEnvExportsAndExit(payload: string, exit?: (code: number) => never, write?: typeof writeSync): never;
|
|
91
|
+
/**
|
|
92
|
+
* Shared `secrets env` action for Commander and the default-off lightweight
|
|
93
|
+
* entrypoint. The fast entrypoint still runs the same credential, usage-audit,
|
|
94
|
+
* server authorization, cache-fallback, redaction, and output code.
|
|
95
|
+
*/
|
|
96
|
+
export declare function runSecretsEnvCommand(opts: {
|
|
97
|
+
only: string[];
|
|
98
|
+
script?: string;
|
|
99
|
+
scriptId?: string;
|
|
100
|
+
}, parentScope: {
|
|
101
|
+
company?: string;
|
|
102
|
+
personal?: boolean;
|
|
103
|
+
}, options?: {
|
|
104
|
+
fallbackOnAuthFailure?: boolean;
|
|
105
|
+
}): Promise<"handled" | "fallback">;
|
|
91
106
|
export declare function registerSecretsCommand(program: Command): void;
|
|
92
107
|
//# sourceMappingURL=secrets.d.ts.map
|
package/dist/commands/secrets.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import * as readline from "node:readline";
|
|
3
|
-
import { spawn } from "node:child_process";
|
|
4
2
|
import { writeSync } from "node:fs";
|
|
5
3
|
import * as nodePath from "node:path";
|
|
6
4
|
import { cognitoCachePrincipalUid, ensureCognitoToken } from "../utils/cognito-session.js";
|
|
5
|
+
import { isAuthError } from "../utils/auth-error.js";
|
|
7
6
|
import { DEFAULT_SECRETS_CACHE_TTL_MS, hasCacheEntry, readCache, writeCache, removeCacheEntry, clearAllCache, } from "../utils/secrets-cache.js";
|
|
8
7
|
import { computeSha256 } from "../utils/integrity.js";
|
|
9
8
|
import { SECRET_NAME_PATTERN, GROUP_ID_PATTERN, EMAIL_PATTERN } from "./_patterns.js";
|
|
@@ -11,7 +10,6 @@ import { describeSecretsScope, formatSecretSaved, formatSecretsListEmpty, format
|
|
|
11
10
|
import { vaultApiFetch, getCompanyUid, getEntityUid, looksLikeCompanyUid, } from "../utils/vault-api.js";
|
|
12
11
|
import { HQ_API_KEY_PREFIX, formatApiKeyCapabilityDenial, requireApiKeyCapability, resolveVaultCredentialForCapability, } from "../utils/resolve-vault-credential.js";
|
|
13
12
|
import { CACHED_SECRETS_SERVER_UNAVAILABLE_MESSAGE, isSecretLoadUnavailable, isUnavailableSecretLoadHttpStatus, SecretLoadUnavailableError, } from "../utils/secret-load-availability.js";
|
|
14
|
-
import { SandboxRunnerClient, } from "../utils/sandbox-runner-client.js";
|
|
15
13
|
export { vaultApiFetch, getCompanyUid, getEntityUid };
|
|
16
14
|
/**
|
|
17
15
|
* Mirrors `RESOURCE_SURFACES` in hq-pro
|
|
@@ -158,14 +156,16 @@ function parseDuration(input) {
|
|
|
158
156
|
return null;
|
|
159
157
|
}
|
|
160
158
|
function confirmPrompt(message) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
rl.
|
|
168
|
-
|
|
159
|
+
return import("node:readline").then(({ createInterface }) => {
|
|
160
|
+
const rl = createInterface({
|
|
161
|
+
input: process.stdin,
|
|
162
|
+
output: process.stdout,
|
|
163
|
+
});
|
|
164
|
+
return new Promise((resolve) => {
|
|
165
|
+
rl.question(`${message} [y/N] `, (answer) => {
|
|
166
|
+
rl.close();
|
|
167
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
168
|
+
});
|
|
169
169
|
});
|
|
170
170
|
});
|
|
171
171
|
}
|
|
@@ -820,6 +820,61 @@ export function emitEnvExportsAndExit(payload, exit = process.exit, write = writ
|
|
|
820
820
|
}
|
|
821
821
|
return exit(0);
|
|
822
822
|
}
|
|
823
|
+
/**
|
|
824
|
+
* Shared `secrets env` action for Commander and the default-off lightweight
|
|
825
|
+
* entrypoint. The fast entrypoint still runs the same credential, usage-audit,
|
|
826
|
+
* server authorization, cache-fallback, redaction, and output code.
|
|
827
|
+
*/
|
|
828
|
+
export async function runSecretsEnvCommand(opts, parentScope, options = {}) {
|
|
829
|
+
let secretLoadStarted = false;
|
|
830
|
+
try {
|
|
831
|
+
const redact = process.stdout.isTTY;
|
|
832
|
+
if (redact) {
|
|
833
|
+
console.error(chalk.yellow("stdout is a terminal — values redacted. Use: source <(hq secrets env --only KEY1,KEY2)"));
|
|
834
|
+
}
|
|
835
|
+
const keys = parseSecretNameList(opts.only);
|
|
836
|
+
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets env", secretsCredentialOptions());
|
|
837
|
+
const companyUid = cred.kind === "api-key"
|
|
838
|
+
? "__api_key__"
|
|
839
|
+
: await getEntityUid(cred.token, scopeOpts(parentScope), {
|
|
840
|
+
allowOfflineScopeFallback: true,
|
|
841
|
+
});
|
|
842
|
+
const usage = cred.kind === "cognito"
|
|
843
|
+
? await buildSecretUsage("env", opts.script, opts.scriptId)
|
|
844
|
+
: undefined;
|
|
845
|
+
secretLoadStarted = true;
|
|
846
|
+
const revealed = await loadRevealedSecrets(cred.token, companyUid, keys, usage, { allowOfflineFallback: true });
|
|
847
|
+
let payload = "";
|
|
848
|
+
for (const key of keys) {
|
|
849
|
+
const value = revealed.get(key);
|
|
850
|
+
// loadRevealedSecrets throws on any unresolved key, so a miss here is
|
|
851
|
+
// unreachable — guard rather than print an `undefined` export line.
|
|
852
|
+
if (value === undefined) {
|
|
853
|
+
throw new Error(`Failed to fetch secret '${key}': not returned by vault`);
|
|
854
|
+
}
|
|
855
|
+
const out = redact ? "[REDACTED]" : value;
|
|
856
|
+
payload += `export ${key}=${shellSingleQuote(out)}\n`;
|
|
857
|
+
}
|
|
858
|
+
// Write synchronously and exit unconditionally — never depend on the
|
|
859
|
+
// event loop draining or the stdout write callback firing. See
|
|
860
|
+
// emitEnvExportsAndExit for the full rationale (orphaned-process leak).
|
|
861
|
+
emitEnvExportsAndExit(payload);
|
|
862
|
+
return "handled";
|
|
863
|
+
}
|
|
864
|
+
catch (err) {
|
|
865
|
+
if (options.fallbackOnAuthFailure &&
|
|
866
|
+
!secretLoadStarted &&
|
|
867
|
+
(isAuthError(err) ||
|
|
868
|
+
(err instanceof Error &&
|
|
869
|
+
(/^No valid HQ session\b/.test(err.message) ||
|
|
870
|
+
/^HQ_API_KEY must start with /.test(err.message))))) {
|
|
871
|
+
return "fallback";
|
|
872
|
+
}
|
|
873
|
+
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
874
|
+
process.exit(1);
|
|
875
|
+
return "handled";
|
|
876
|
+
}
|
|
877
|
+
}
|
|
823
878
|
export function registerSecretsCommand(program) {
|
|
824
879
|
const secrets = program
|
|
825
880
|
.command("secrets")
|
|
@@ -1444,6 +1499,7 @@ export function registerSecretsCommand(program) {
|
|
|
1444
1499
|
const token = await requireCognitoTokenForSecrets("secrets sandbox", "secrets:read");
|
|
1445
1500
|
const scope = scopeOpts(mergeScopeOpts(secrets.opts(), opts));
|
|
1446
1501
|
const companyUid = await getEntityUid(token, scope);
|
|
1502
|
+
const { SandboxRunnerClient } = await import("../utils/sandbox-runner-client.js");
|
|
1447
1503
|
const client = new SandboxRunnerClient();
|
|
1448
1504
|
const started = await client.startJob(token, {
|
|
1449
1505
|
companyUid,
|
|
@@ -1498,9 +1554,19 @@ export function registerSecretsCommand(program) {
|
|
|
1498
1554
|
const companyUid = cred.kind === "api-key"
|
|
1499
1555
|
? "__api_key__"
|
|
1500
1556
|
: await getEntityUid(cred.token, scopeOpts(secrets.opts()), { allowOfflineScopeFallback: true });
|
|
1501
|
-
const
|
|
1557
|
+
const readSecrets = async () => loadRevealedSecrets(cred.token, companyUid, keys, cred.kind === "cognito"
|
|
1502
1558
|
? await buildSecretUsage("exec", _opts.script, _opts.scriptId)
|
|
1503
1559
|
: undefined, { allowOfflineFallback: true });
|
|
1560
|
+
// API-key auth has no signed-in person identity for the CLI telemetry
|
|
1561
|
+
// endpoint. Do not attribute that read to an unrelated cached session.
|
|
1562
|
+
let revealed;
|
|
1563
|
+
if (cred.kind === "cognito") {
|
|
1564
|
+
const { withCliFeatureUsed } = await import("../utils/cli-telemetry.js");
|
|
1565
|
+
revealed = await withCliFeatureUsed("secret_read", readSecrets, companyUid);
|
|
1566
|
+
}
|
|
1567
|
+
else {
|
|
1568
|
+
revealed = await readSecrets();
|
|
1569
|
+
}
|
|
1504
1570
|
const secretEnv = {};
|
|
1505
1571
|
for (const key of keys) {
|
|
1506
1572
|
const value = revealed.get(key);
|
|
@@ -1512,6 +1578,7 @@ export function registerSecretsCommand(program) {
|
|
|
1512
1578
|
secretEnv[key] = value;
|
|
1513
1579
|
}
|
|
1514
1580
|
const [childCmd, ...childCmdArgs] = childArgs;
|
|
1581
|
+
const { spawn } = await import("node:child_process");
|
|
1515
1582
|
const child = spawn(childCmd, childCmdArgs, {
|
|
1516
1583
|
stdio: "inherit",
|
|
1517
1584
|
env: { ...process.env, ...secretEnv },
|
|
@@ -1539,39 +1606,7 @@ export function registerSecretsCommand(program) {
|
|
|
1539
1606
|
.option("--script <path>", "Attach local script identity for script-locked secrets")
|
|
1540
1607
|
.option("--script-id <id>", "Stable script identifier approved by policy")
|
|
1541
1608
|
.action(async (opts) => {
|
|
1542
|
-
|
|
1543
|
-
const redact = process.stdout.isTTY;
|
|
1544
|
-
if (redact) {
|
|
1545
|
-
console.error(chalk.yellow("stdout is a terminal — values redacted. Use: source <(hq secrets env --only KEY1,KEY2)"));
|
|
1546
|
-
}
|
|
1547
|
-
const keys = parseSecretNameList(opts.only);
|
|
1548
|
-
const cred = await resolveVaultCredentialForCapability("secrets:read", "secrets env", secretsCredentialOptions());
|
|
1549
|
-
const companyUid = cred.kind === "api-key"
|
|
1550
|
-
? "__api_key__"
|
|
1551
|
-
: await getEntityUid(cred.token, scopeOpts(secrets.opts()), { allowOfflineScopeFallback: true });
|
|
1552
|
-
const revealed = await loadRevealedSecrets(cred.token, companyUid, keys, cred.kind === "cognito"
|
|
1553
|
-
? await buildSecretUsage("env", opts.script, opts.scriptId)
|
|
1554
|
-
: undefined, { allowOfflineFallback: true });
|
|
1555
|
-
let payload = "";
|
|
1556
|
-
for (const key of keys) {
|
|
1557
|
-
const value = revealed.get(key);
|
|
1558
|
-
// loadRevealedSecrets throws on any unresolved key, so a miss here is
|
|
1559
|
-
// unreachable — guard rather than print an `undefined` export line.
|
|
1560
|
-
if (value === undefined) {
|
|
1561
|
-
throw new Error(`Failed to fetch secret '${key}': not returned by vault`);
|
|
1562
|
-
}
|
|
1563
|
-
const out = redact ? "[REDACTED]" : value;
|
|
1564
|
-
payload += `export ${key}=${shellSingleQuote(out)}\n`;
|
|
1565
|
-
}
|
|
1566
|
-
// Write synchronously and exit unconditionally — never depend on the
|
|
1567
|
-
// event loop draining or the stdout write callback firing. See
|
|
1568
|
-
// emitEnvExportsAndExit for the full rationale (orphaned-process leak).
|
|
1569
|
-
emitEnvExportsAndExit(payload);
|
|
1570
|
-
}
|
|
1571
|
-
catch (err) {
|
|
1572
|
-
console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
|
|
1573
|
-
process.exit(1);
|
|
1574
|
-
}
|
|
1609
|
+
await runSecretsEnvCommand(opts, secrets.opts());
|
|
1575
1610
|
});
|
|
1576
1611
|
secrets
|
|
1577
1612
|
.command("generate-link <name>")
|
|
@@ -70,6 +70,15 @@ export interface SetSyncModeResult {
|
|
|
70
70
|
export interface ShowSyncModesOptions {
|
|
71
71
|
vaultClient: SyncModeVaultClient;
|
|
72
72
|
}
|
|
73
|
+
export interface GetSyncModeOptions {
|
|
74
|
+
companySlug: string;
|
|
75
|
+
vaultClient: SyncModeVaultClient;
|
|
76
|
+
}
|
|
77
|
+
export interface GetSyncModeResult {
|
|
78
|
+
membershipId: string;
|
|
79
|
+
companySlug: string;
|
|
80
|
+
config: MembershipSyncConfig;
|
|
81
|
+
}
|
|
73
82
|
export interface ShowSyncModesRow {
|
|
74
83
|
companySlug: string;
|
|
75
84
|
companyName?: string;
|
|
@@ -100,6 +109,8 @@ export declare function setSyncMode(options: SetSyncModeOptions): Promise<SetSyn
|
|
|
100
109
|
* stable table output.
|
|
101
110
|
*/
|
|
102
111
|
export declare function showSyncModes(options: ShowSyncModesOptions): Promise<ShowSyncModesRow[]>;
|
|
112
|
+
/** Read one company's effective sync mode without hiding API read failures. */
|
|
113
|
+
export declare function getSyncModeForCompany(options: GetSyncModeOptions): Promise<GetSyncModeResult>;
|
|
103
114
|
/**
|
|
104
115
|
* Render the `--show` table as plain text. No external dep — hq-cli doesn't
|
|
105
116
|
* use cli-table3, so we hand-format columns to match the existing chalk +
|
|
@@ -163,6 +163,36 @@ export async function showSyncModes(options) {
|
|
|
163
163
|
rows.sort((a, b) => a.companySlug.localeCompare(b.companySlug));
|
|
164
164
|
return rows;
|
|
165
165
|
}
|
|
166
|
+
/** Read one company's effective sync mode without hiding API read failures. */
|
|
167
|
+
export async function getSyncModeForCompany(options) {
|
|
168
|
+
const { companySlug, vaultClient } = options;
|
|
169
|
+
const memberships = await vaultClient.listMyMemberships();
|
|
170
|
+
const enriched = await Promise.all(memberships.map(async (membership) => {
|
|
171
|
+
try {
|
|
172
|
+
const entity = await vaultClient.entity.get(membership.companyUid);
|
|
173
|
+
return { ok: true, membership, slug: entity.slug };
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return { ok: false, membership, error };
|
|
177
|
+
}
|
|
178
|
+
}));
|
|
179
|
+
const match = enriched.find((row) => row.ok && row.slug === companySlug);
|
|
180
|
+
if (!match) {
|
|
181
|
+
const failedLookup = enriched.find((row) => !row.ok);
|
|
182
|
+
if (failedLookup && !failedLookup.ok)
|
|
183
|
+
throw failedLookup.error;
|
|
184
|
+
throw new Error(`No membership found for company '${companySlug}'.`);
|
|
185
|
+
}
|
|
186
|
+
for (const failedLookup of enriched) {
|
|
187
|
+
if (!failedLookup.ok) {
|
|
188
|
+
const detail = failedLookup.error instanceof Error ? failedLookup.error.message : String(failedLookup.error);
|
|
189
|
+
console.error(`getSyncModeForCompany: entity lookup failed for membership ${failedLookup.membership.membershipKey} while resolving '${companySlug}': ${detail}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
const membershipId = match.membership.membershipKey;
|
|
193
|
+
const config = await vaultClient.getMembershipSyncConfig(membershipId);
|
|
194
|
+
return { membershipId, companySlug, config };
|
|
195
|
+
}
|
|
166
196
|
// ── Table rendering (used by CLI action, separable for tests) ──────────────
|
|
167
197
|
/**
|
|
168
198
|
* Render the `--show` table as plain text. No external dep — hq-cli doesn't
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
import { Command } from 'commander';
|
|
5
5
|
import { type PlanLimitStatus } from "../lib/billing/plan-lock.js";
|
|
6
|
+
export declare const CLI_LIMIT_INVITE_TEAMMATE_FLAG = "cli.limit-invite-teammate-v1";
|
|
6
7
|
export interface WhoamiTokenIdentity {
|
|
7
8
|
email?: string;
|
|
8
9
|
sub?: string;
|
|
@@ -20,11 +21,8 @@ export interface WhoamiDisplayIdentity {
|
|
|
20
21
|
* email with the token subject of the delegating machine.
|
|
21
22
|
*/
|
|
22
23
|
export declare function resolveWhoamiIdentity(identity: WhoamiTokenIdentity): WhoamiDisplayIdentity;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
* orientation when it is Starter and healthy, and nothing at all otherwise.
|
|
27
|
-
*/
|
|
28
|
-
export declare function renderWhoamiPlanBlock(status: PlanLimitStatus): string | null;
|
|
24
|
+
export declare function renderWhoamiPlanBlock(status: PlanLimitStatus, opts?: {
|
|
25
|
+
inviteTeammateEnabled?: boolean;
|
|
26
|
+
}): string | null;
|
|
29
27
|
export declare function registerWhoamiCommand(program: Command): void;
|
|
30
28
|
//# sourceMappingURL=whoami.d.ts.map
|
package/dist/commands/whoami.js
CHANGED
|
@@ -6,6 +6,9 @@ import { loadCachedTokens, isExpiring, loadMachineCreds, } from '@indigoai-us/hq
|
|
|
6
6
|
import { peekIdToken as decodeIdToken } from "../utils/id-token.js";
|
|
7
7
|
import { ensureCognitoToken, loadMachineCachedTokens, resolveCognitoTokenSource, } from "../utils/cognito-session.js";
|
|
8
8
|
import { colorizePlanLimitNotice, fetchPlanLimitStatus, renderPlanLine, renderPlanLimitNotice, } from "../lib/billing/plan-lock.js";
|
|
9
|
+
import { resolveCompanyFlagGate, } from "../lib/flag-registry.js";
|
|
10
|
+
import { queuePlanLimitInviteTeammateExposure } from "../utils/cli-telemetry.js";
|
|
11
|
+
export const CLI_LIMIT_INVITE_TEAMMATE_FLAG = "cli.limit-invite-teammate-v1";
|
|
9
12
|
/**
|
|
10
13
|
* Resolves the person represented by an ID token without pairing a delegated
|
|
11
14
|
* email with the token subject of the delegating machine.
|
|
@@ -62,7 +65,13 @@ async function readPlanLimits(company) {
|
|
|
62
65
|
// default `interactive: true`, a box with no cached session would launch a
|
|
63
66
|
// browser sign-in and block the caller instead of resolving to UNKNOWN.
|
|
64
67
|
const token = await ensureCognitoToken({ interactive: false });
|
|
65
|
-
|
|
68
|
+
const status = await fetchPlanLimitStatus(token, slug);
|
|
69
|
+
if (!status)
|
|
70
|
+
return null;
|
|
71
|
+
return {
|
|
72
|
+
status,
|
|
73
|
+
idToken: loadCachedTokens()?.idToken,
|
|
74
|
+
};
|
|
66
75
|
}
|
|
67
76
|
catch {
|
|
68
77
|
return null;
|
|
@@ -73,19 +82,41 @@ async function readPlanLimits(company) {
|
|
|
73
82
|
* workspace is over its Starter limits, the one-line `Plan: Starter — …`
|
|
74
83
|
* orientation when it is Starter and healthy, and nothing at all otherwise.
|
|
75
84
|
*/
|
|
76
|
-
|
|
85
|
+
function shouldOfferInviteTeammateSuggestion(status) {
|
|
86
|
+
if (!status.over ||
|
|
87
|
+
status.plan !== "free" ||
|
|
88
|
+
status.members?.used !== 1 ||
|
|
89
|
+
!status.companyUid) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
async function inviteTeammateSuggestionEnabled(status, idToken) {
|
|
95
|
+
if (!shouldOfferInviteTeammateSuggestion(status) || !status.companyUid) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
return resolveCompanyFlagGate(status.companyUid, idToken ?? "", CLI_LIMIT_INVITE_TEAMMATE_FLAG, { fallback: false }, () => false);
|
|
99
|
+
}
|
|
100
|
+
export function renderWhoamiPlanBlock(status, opts = {}) {
|
|
77
101
|
if (status.over) {
|
|
78
|
-
|
|
102
|
+
const inviteTeammateEnabled = opts.inviteTeammateEnabled === true;
|
|
103
|
+
return colorizePlanLimitNotice(renderPlanLimitNotice(status, { inviteTeammateEnabled }));
|
|
79
104
|
}
|
|
80
105
|
return renderPlanLine(status);
|
|
81
106
|
}
|
|
82
107
|
async function printPlanBlock(company) {
|
|
83
|
-
const
|
|
84
|
-
if (!
|
|
108
|
+
const planLimits = await readPlanLimits(company);
|
|
109
|
+
if (!planLimits)
|
|
110
|
+
return;
|
|
111
|
+
const { status, idToken } = planLimits;
|
|
112
|
+
const inviteTeammateEnabled = await inviteTeammateSuggestionEnabled(status, idToken);
|
|
113
|
+
const block = renderWhoamiPlanBlock(status, { inviteTeammateEnabled });
|
|
114
|
+
if (!block)
|
|
85
115
|
return;
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
|
|
116
|
+
console.log(block);
|
|
117
|
+
if (inviteTeammateEnabled && status.companyUid) {
|
|
118
|
+
queuePlanLimitInviteTeammateExposure(status.companyUid);
|
|
119
|
+
}
|
|
89
120
|
}
|
|
90
121
|
export function registerWhoamiCommand(program) {
|
|
91
122
|
program
|
package/dist/fast-core.d.ts
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fast-path dispatcher for
|
|
2
|
+
* Fast-path dispatcher for selected core commands.
|
|
3
3
|
*
|
|
4
4
|
* Reached from the entrypoint BEFORE `main.ts` (and its heavy command graph) is
|
|
5
|
-
* ever imported, so
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `core.ts` remains the complete path for any invocation this does not match.
|
|
5
|
+
* ever imported, so hot hook commands avoid the full commander graph. Shell
|
|
6
|
+
* entries use the bundled-script runner; native entries import their one handler
|
|
7
|
+
* module. The registration in `core.ts` remains the complete fallback path.
|
|
9
8
|
*/
|
|
10
9
|
/**
|
|
11
|
-
* Run the fast-path
|
|
10
|
+
* Run the fast-path core command named in `argv` and exit with its code.
|
|
12
11
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
|
13
12
|
* doing anything.
|
|
14
13
|
*/
|
|
15
|
-
export declare function runFastCore(argv: readonly string[]): void
|
|
14
|
+
export declare function runFastCore(argv: readonly string[]): Promise<void>;
|
|
16
15
|
//# sourceMappingURL=fast-core.d.ts.map
|
package/dist/fast-core.js
CHANGED
|
@@ -1,21 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fast-path dispatcher for
|
|
2
|
+
* Fast-path dispatcher for selected core commands.
|
|
3
3
|
*
|
|
4
4
|
* Reached from the entrypoint BEFORE `main.ts` (and its heavy command graph) is
|
|
5
|
-
* ever imported, so
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* `core.ts` remains the complete path for any invocation this does not match.
|
|
5
|
+
* ever imported, so hot hook commands avoid the full commander graph. Shell
|
|
6
|
+
* entries use the bundled-script runner; native entries import their one handler
|
|
7
|
+
* module. The registration in `core.ts` remains the complete fallback path.
|
|
9
8
|
*/
|
|
10
9
|
import { resolveLiveRoot } from "./utils/hq-roots.js";
|
|
11
10
|
import { runBundledScript } from "./utils/run-bundled-script.js";
|
|
12
11
|
import { parseFastCore } from "./fast-scaffold.js";
|
|
13
12
|
/**
|
|
14
|
-
* Run the fast-path
|
|
13
|
+
* Run the fast-path core command named in `argv` and exit with its code.
|
|
15
14
|
* Only call when {@link isFastCoreRequest} is true; a non-match returns without
|
|
16
15
|
* doing anything.
|
|
17
16
|
*/
|
|
18
|
-
export function runFastCore(argv) {
|
|
17
|
+
export async function runFastCore(argv) {
|
|
19
18
|
const parsed = parseFastCore(argv);
|
|
20
19
|
if (!parsed)
|
|
21
20
|
return;
|
|
@@ -31,12 +30,27 @@ export function runFastCore(argv) {
|
|
|
31
30
|
catch {
|
|
32
31
|
hqRoot = undefined;
|
|
33
32
|
}
|
|
33
|
+
if (parsed.entry.kind === "native") {
|
|
34
|
+
const handlerModule = await import(parsed.entry.modulePath);
|
|
35
|
+
const handler = handlerModule[parsed.entry.exportName];
|
|
36
|
+
if (typeof handler !== "function") {
|
|
37
|
+
throw new Error(`Native fast handler ${parsed.entry.exportName} was not exported`);
|
|
38
|
+
}
|
|
39
|
+
const chunks = [];
|
|
40
|
+
for await (const chunk of process.stdin)
|
|
41
|
+
chunks.push(chunk);
|
|
42
|
+
const code = await handler(argv, Buffer.concat(chunks).toString("utf8"), process.cwd(), hqRoot);
|
|
43
|
+
// Let Node drain handler output before terminating. The timeout guard's
|
|
44
|
+
// stderr message must survive the native route's process boundary.
|
|
45
|
+
process.exitCode = code;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
34
48
|
// Keep the CHILD in the caller's cwd, unlike the generic "live" dispatch. The
|
|
35
49
|
// relocated scripts read their data tree from the injected root, not cwd, and
|
|
36
50
|
// hq-session's Work Mesh registration serializes $PWD — so changing directory
|
|
37
51
|
// to the HQ root would mislabel a bind made from inside a project/worktree.
|
|
38
52
|
const { code } = runBundledScript({
|
|
39
|
-
asset: parsed.asset,
|
|
53
|
+
asset: parsed.entry.asset,
|
|
40
54
|
args: parsed.args,
|
|
41
55
|
cwd: process.cwd(),
|
|
42
56
|
hqRoot,
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -1,32 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Fast-path manifest for
|
|
2
|
+
* Fast-path manifest for selected core commands, kept deliberately
|
|
3
3
|
* dependency-light.
|
|
4
4
|
*
|
|
5
5
|
* `main.ts` eagerly imports the CLI's entire (~60-module) command graph, so any
|
|
6
6
|
* command routed through it pays seconds of startup — fine for a human typing a
|
|
7
7
|
* command, ruinous for the hot plumbing this hosts: `hq core hq-session` is
|
|
8
8
|
* called several times per skill/hook, and `hq core checkpoint-stop-gate` runs
|
|
9
|
-
* on every turn's Stop. The entrypoint routes
|
|
10
|
-
*
|
|
11
|
-
* routing needs WITHOUT importing anything heavy. `core.ts` names the same
|
|
12
|
-
* assets, and a test pins the two lists together so they cannot drift.
|
|
9
|
+
* on every turn's Stop. The entrypoint routes eligible commands before loading
|
|
10
|
+
* the graph, and this module holds the parser + table that routing needs.
|
|
13
11
|
*/
|
|
14
12
|
export type FastScaffoldScript = {
|
|
15
13
|
/** Direct subcommand name — `hq core <name>`. */
|
|
16
14
|
name: string;
|
|
15
|
+
kind: "shell";
|
|
17
16
|
/** Path under `assets/scaffold/`, e.g. `core/scripts/foo.sh`. */
|
|
18
17
|
asset: string;
|
|
18
|
+
} | {
|
|
19
|
+
/** Direct subcommand name — `hq core <name>`. */
|
|
20
|
+
name: string;
|
|
21
|
+
kind: "native";
|
|
22
|
+
/** Relative ESM module specifier, resolved from fast-core.js. */
|
|
23
|
+
modulePath: string;
|
|
24
|
+
/** Named export which accepts the native fast-handler contract. */
|
|
25
|
+
exportName: string;
|
|
26
|
+
/** Default-off registry flag for this native route. */
|
|
27
|
+
flagKey: string;
|
|
19
28
|
};
|
|
20
29
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
21
30
|
export declare const FAST_SCAFFOLD_SCRIPTS: readonly FastScaffoldScript[];
|
|
22
31
|
export type ParsedFastCore = {
|
|
23
|
-
/** The
|
|
24
|
-
|
|
32
|
+
/** The manifest entry to run. */
|
|
33
|
+
entry: FastScaffoldScript;
|
|
25
34
|
/** `--hq-root <path>` value, if the caller passed the group option. */
|
|
26
35
|
hqRootArg?: string;
|
|
27
36
|
/** Everything after the command name, passed to the script verbatim. */
|
|
28
37
|
args: string[];
|
|
29
38
|
};
|
|
39
|
+
export type ParsedSecretsEnvFastRequest = {
|
|
40
|
+
options: {
|
|
41
|
+
only: string[];
|
|
42
|
+
script?: string;
|
|
43
|
+
scriptId?: string;
|
|
44
|
+
};
|
|
45
|
+
scope: {
|
|
46
|
+
company?: string;
|
|
47
|
+
personal?: boolean;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Parse the small, non-ambiguous `hq secrets env` surface for the gated entry
|
|
52
|
+
* fast path. Anything this parser does not understand stays with Commander in
|
|
53
|
+
* the full CLI, which remains the source of truth for help and errors.
|
|
54
|
+
*/
|
|
55
|
+
export declare function parseSecretsEnvFastRequest(argv: readonly string[]): ParsedSecretsEnvFastRequest | null;
|
|
30
56
|
/**
|
|
31
57
|
* Parse `hq core [--hq-root <path>] <name> [args...]` when `<name>` is a
|
|
32
58
|
* fast-path script. Returns null for anything else so the caller defers to the
|