@indigoai-us/hq-cli 5.345.134 → 5.345.136
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/worktree-gc.sh +98 -8
- package/dist/command-catalog.generated.d.ts +62 -6
- package/dist/command-catalog.generated.js +79 -7
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/core-bucket-c-handoff-knowledge-native.d.ts +3 -1
- package/dist/commands/core-bucket-c-handoff-knowledge-native.js +8 -4
- package/dist/commands/core-checkpoint-native.d.ts +10 -0
- package/dist/commands/core-checkpoint-native.js +60 -15
- package/dist/commands/core-handoff.d.ts +2 -0
- package/dist/commands/core-handoff.js +3 -1
- package/dist/commands/core-pipeline-workflow-runner-native.js +7 -5
- package/dist/commands/daemon.js +3 -1
- package/dist/commands/fingerprint.d.ts +10 -0
- package/dist/commands/fingerprint.js +191 -0
- package/dist/commands/integrations-core.d.ts +22 -0
- package/dist/commands/integrations-core.js +25 -0
- package/dist/commands/integrations-manage.d.ts +24 -1
- package/dist/commands/integrations-manage.js +92 -17
- package/dist/commands/integrations.d.ts +11 -0
- package/dist/commands/integrations.js +100 -19
- package/dist/commands/lanes-loop.d.ts +9 -0
- package/dist/commands/lanes-loop.js +9 -1
- package/dist/commands/lanes.js +78 -55
- package/dist/commands/meetings.d.ts +2 -0
- package/dist/commands/meetings.js +57 -14
- package/dist/commands/mesh.d.ts +19 -1
- package/dist/commands/mesh.js +158 -70
- package/dist/commands/reindex.js +80 -5
- package/dist/commands/storage-offload.d.ts +93 -28
- package/dist/commands/storage-offload.js +290 -34
- package/dist/commands/storage.js +60 -7
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2550 -1748
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/index.js +4 -0
- package/dist/lib/capacity/route.js +24 -0
- package/dist/lib/core-utils/session-company.d.ts +11 -0
- package/dist/lib/core-utils/session-company.js +34 -0
- package/dist/lib/core-utils/worktree-resume.js +44 -4
- package/dist/lib/core-utils/worktree-session-owner.d.ts +27 -0
- package/dist/lib/core-utils/worktree-session-owner.js +157 -0
- package/dist/lib/daemon/catalog.d.ts +2 -0
- package/dist/lib/daemon/catalog.js +3 -3
- package/dist/lib/daemon/hq-child-bootstrap-stage.d.ts +5 -0
- package/dist/lib/daemon/hq-child-bootstrap-stage.js +3 -11
- package/dist/lib/daemon/server.js +10 -1
- package/dist/lib/daemon/supervisor.js +6 -0
- package/dist/lib/daemon/sync-runner-bundle.d.ts +5 -0
- package/dist/lib/daemon/sync-runner-bundle.js +4 -12
- package/dist/lib/doctor/checks/claude-wiring.js +2 -1
- package/dist/lib/doctor/fix/apply.d.ts +20 -0
- package/dist/lib/doctor/fix/apply.js +63 -3
- package/dist/lib/doctor/fix/remediation.d.ts +1 -1
- package/dist/lib/doctor/fix/remediation.js +16 -0
- package/dist/lib/doctor/shared.d.ts +11 -1
- package/dist/lib/doctor/shared.js +16 -2
- package/dist/lib/fingerprint/fixtures.d.ts +19 -0
- package/dist/lib/fingerprint/fixtures.js +61 -0
- package/dist/lib/fingerprint/format.d.ts +50 -0
- package/dist/lib/fingerprint/format.js +105 -0
- package/dist/lib/fingerprint/offline.d.ts +16 -0
- package/dist/lib/fingerprint/offline.js +44 -0
- package/dist/lib/fingerprint/registry-client.d.ts +74 -0
- package/dist/lib/fingerprint/registry-client.js +141 -0
- package/dist/lib/fingerprint/resolve-local.d.ts +14 -0
- package/dist/lib/fingerprint/resolve-local.js +67 -0
- package/dist/lib/fingerprint/self-core.d.ts +139 -0
- package/dist/lib/fingerprint/self-core.js +371 -0
- package/dist/lib/fingerprint/self.d.ts +44 -0
- package/dist/lib/fingerprint/self.js +191 -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/lanes/monitor.js +14 -2
- package/dist/lib/lanes/spawn.d.ts +19 -1
- package/dist/lib/lanes/spawn.js +50 -6
- package/dist/lib/lanes/story-ledger.d.ts +1 -0
- package/dist/lib/lanes/story-ledger.js +3 -1
- package/dist/lib/lanes/types.d.ts +12 -0
- package/dist/lib/lanes/types.js +12 -1
- package/dist/lib/mesh/live/daemon/install.d.ts +12 -0
- package/dist/lib/mesh/live/daemon/install.js +22 -0
- package/dist/lib/mesh/live/flush.js +106 -3
- package/dist/lib/mesh/live/session-events-client.d.ts +9 -0
- package/dist/lib/mesh/live/session-events-client.js +15 -0
- package/dist/lib/work-context/state.d.ts +10 -0
- package/dist/lib/work-context/state.js +5 -0
- package/dist/utils/cli-network-lifecycle.d.ts +2 -0
- package/dist/utils/cli-network-lifecycle.js +5 -1
- package/dist/utils/cognito-session.d.ts +2 -21
- package/dist/utils/cognito-session.js +3 -84
- package/dist/utils/fsync-directory.d.ts +17 -0
- package/dist/utils/fsync-directory.js +27 -0
- package/dist/utils/person-token-cache.d.ts +32 -0
- package/dist/utils/person-token-cache.js +95 -0
- package/dist/utils/vault-api.d.ts +21 -1
- package/dist/utils/vault-api.js +54 -5
- package/dist/utils/windows-hide-default.d.ts +42 -0
- package/dist/utils/windows-hide-default.js +98 -0
- package/package.json +2 -2
|
@@ -8,11 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
10
|
import * as path from "node:path";
|
|
11
|
-
import { randomUUID } from "node:crypto";
|
|
11
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import { execFileSync, spawn } from "node:child_process";
|
|
13
13
|
import { homedir } from "node:os";
|
|
14
14
|
import { toHqStateWriteError } from "../utils/hq-state-write-error.js";
|
|
15
15
|
import { checkpointStampWriteErrorFor, isCheckpointStampWriteError, } from "../utils/checkpoint-stamp-write-error.js";
|
|
16
|
+
import { isExecutableOnPath } from "../utils/resolve-executable.js";
|
|
16
17
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
17
18
|
const DEFAULT_TRIGGER = "stop-gate";
|
|
18
19
|
const BACKENDS = new Set(["auto", "claude", "codex", "grok", "none"]);
|
|
@@ -73,6 +74,14 @@ const BACKEND_SETTING = "HQ_CHECKPOINT_BACKEND";
|
|
|
73
74
|
export const CRASHED_RUN_MAX_AGE_MS = 6 * 60 * 60 * 1000;
|
|
74
75
|
/** A backend that cannot answer `--version` this fast is treated as broken. */
|
|
75
76
|
const BACKEND_PROBE_TIMEOUT_MS = 10_000;
|
|
77
|
+
/**
|
|
78
|
+
* After `--backend auto` finds no allowed backend, later checkpoints skip the
|
|
79
|
+
* PATH scan and `--version` probes for this long, unless PATH changes. Each
|
|
80
|
+
* probe is a child process, and on Windows each one could open a console
|
|
81
|
+
* window; a Stop hook that checkpoints every turn re-ran them every minute or
|
|
82
|
+
* two, all day, to reach the same answer (feedback_351a40d5).
|
|
83
|
+
*/
|
|
84
|
+
export const NO_BACKEND_RETRY_MS = 6 * 60 * 60 * 1000;
|
|
76
85
|
/** A sibling still holding the lock after this long is treated as abandoned. */
|
|
77
86
|
const SIBLING_LOCK_TTL_MS = 60 * 60 * 1000;
|
|
78
87
|
/**
|
|
@@ -392,6 +401,7 @@ function readGitState(repoDir) {
|
|
|
392
401
|
const read = (args) => execFileSync("git", ["-C", repoDir, ...args], {
|
|
393
402
|
encoding: "utf8",
|
|
394
403
|
stdio: ["ignore", "pipe", "ignore"],
|
|
404
|
+
windowsHide: true,
|
|
395
405
|
}).trim();
|
|
396
406
|
if (read(["rev-parse", "--is-inside-work-tree"]) !== "true")
|
|
397
407
|
return null;
|
|
@@ -415,7 +425,7 @@ function readGitState(repoDir) {
|
|
|
415
425
|
}
|
|
416
426
|
function commitsSince(repoDir, initialCommit) {
|
|
417
427
|
try {
|
|
418
|
-
const output = execFileSync("git", ["-C", repoDir, "log", "--no-decorate", "--pretty=format:%h: %s", `${initialCommit}..HEAD`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
428
|
+
const output = execFileSync("git", ["-C", repoDir, "log", "--no-decorate", "--pretty=format:%h: %s", `${initialCommit}..HEAD`], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], windowsHide: true }).trim();
|
|
419
429
|
return output ? output.split("\n") : [];
|
|
420
430
|
}
|
|
421
431
|
catch {
|
|
@@ -590,17 +600,8 @@ function backendOnPath(name) {
|
|
|
590
600
|
const pathValue = process.env.PATH;
|
|
591
601
|
if (!pathValue)
|
|
592
602
|
return false;
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
return false;
|
|
596
|
-
try {
|
|
597
|
-
fs.accessSync(path.join(directory, name), fs.constants.X_OK);
|
|
598
|
-
return true;
|
|
599
|
-
}
|
|
600
|
-
catch {
|
|
601
|
-
return false;
|
|
602
|
-
}
|
|
603
|
-
});
|
|
603
|
+
// PATHEXT-aware on Windows, where the binary is claude.exe, not claude.
|
|
604
|
+
return isExecutableOnPath(name, pathValue);
|
|
604
605
|
}
|
|
605
606
|
/**
|
|
606
607
|
* An installed binary is not necessarily a working one: a partial install, a
|
|
@@ -613,6 +614,7 @@ function backendResponds(name) {
|
|
|
613
614
|
execFileSync(name, ["--version"], {
|
|
614
615
|
timeout: BACKEND_PROBE_TIMEOUT_MS,
|
|
615
616
|
stdio: "ignore",
|
|
617
|
+
windowsHide: true,
|
|
616
618
|
});
|
|
617
619
|
return true;
|
|
618
620
|
}
|
|
@@ -835,7 +837,43 @@ function backendSetting(liveRoot) {
|
|
|
835
837
|
const lookup = settingsEnvValue(liveRoot, BACKEND_SETTING);
|
|
836
838
|
return lookup.kind === "value" ? lookup.value.trim().toLowerCase() || undefined : undefined;
|
|
837
839
|
}
|
|
838
|
-
|
|
840
|
+
function noBackendMarkerPath(liveRoot) {
|
|
841
|
+
return path.join(liveRoot, "workspace", "orchestrator", "hook-state", "checkpoint-no-backend.json");
|
|
842
|
+
}
|
|
843
|
+
function pathFingerprint() {
|
|
844
|
+
return createHash("sha256").update(process.env.PATH ?? "").digest("hex").slice(0, 16);
|
|
845
|
+
}
|
|
846
|
+
/** True while a recent "no allowed backend" result still holds for this PATH. */
|
|
847
|
+
export function noBackendRecentlyFound(liveRoot, now = Date.now()) {
|
|
848
|
+
try {
|
|
849
|
+
const marker = JSON.parse(fs.readFileSync(noBackendMarkerPath(liveRoot), "utf8"));
|
|
850
|
+
if (typeof marker.at !== "number" || marker.path !== pathFingerprint())
|
|
851
|
+
return false;
|
|
852
|
+
return now - marker.at >= 0 && now - marker.at < NO_BACKEND_RETRY_MS;
|
|
853
|
+
}
|
|
854
|
+
catch {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
function recordNoBackend(liveRoot, now = Date.now()) {
|
|
859
|
+
try {
|
|
860
|
+
const marker = noBackendMarkerPath(liveRoot);
|
|
861
|
+
fs.mkdirSync(path.dirname(marker), { recursive: true });
|
|
862
|
+
fs.writeFileSync(marker, `${JSON.stringify({ at: now, path: pathFingerprint() })}\n`);
|
|
863
|
+
}
|
|
864
|
+
catch {
|
|
865
|
+
// Best effort: without the marker the next checkpoint probes again.
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function clearNoBackend(liveRoot) {
|
|
869
|
+
try {
|
|
870
|
+
fs.rmSync(noBackendMarkerPath(liveRoot), { force: true });
|
|
871
|
+
}
|
|
872
|
+
catch {
|
|
873
|
+
// Best effort.
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
async function resolveBackend(requested, liveRoot, company, options = {}) {
|
|
839
877
|
let value = assertKnownBackend(requested);
|
|
840
878
|
if (value === "auto") {
|
|
841
879
|
const setting = backendSetting(liveRoot);
|
|
@@ -849,12 +887,19 @@ async function resolveBackend(requested, liveRoot, company) {
|
|
|
849
887
|
// An explicitly named backend is honoured as given; only `auto` shops around.
|
|
850
888
|
if (value !== "auto")
|
|
851
889
|
return value;
|
|
890
|
+
// A recent identical miss: skip the probes rather than spawn them again.
|
|
891
|
+
if (noBackendRecentlyFound(liveRoot))
|
|
892
|
+
return "none";
|
|
852
893
|
const available = autoBackendPreference(await companyPrefersGrok(liveRoot, company)).filter((name) => backendOnPath(name) && backendResponds(name));
|
|
853
894
|
if (available.length === 0) {
|
|
854
895
|
printError("checkpoint: sibling skipped: no allowed backend available (claude, grok); " +
|
|
855
896
|
`codex runs only with --backend codex or ${BACKEND_SETTING}=codex`);
|
|
897
|
+
if (!options.dryRun)
|
|
898
|
+
recordNoBackend(liveRoot);
|
|
856
899
|
return "none";
|
|
857
900
|
}
|
|
901
|
+
if (!options.dryRun)
|
|
902
|
+
clearNoBackend(liveRoot);
|
|
858
903
|
const crashed = crashedBackends(path.join(liveRoot, "workspace", "checkpoints", "sibling"));
|
|
859
904
|
// If every candidate failed last time, still try the preferred one: a stale
|
|
860
905
|
// failure is a weaker signal than running no maintenance sibling at all.
|
|
@@ -1255,7 +1300,7 @@ async function runCheckpoint(options, command, group, preResolvedHqRoot) {
|
|
|
1255
1300
|
const siblingOptOut = options.agent === false || !siblingEnabledBySetting(liveRoot);
|
|
1256
1301
|
// Validated even when nothing will spawn, so `--backend bogus` is still an error.
|
|
1257
1302
|
const requestedBackend = assertKnownBackend(options.backend);
|
|
1258
|
-
const backend = siblingOptOut ? "none" : await resolveBackend(requestedBackend, liveRoot, input.company);
|
|
1303
|
+
const backend = siblingOptOut ? "none" : await resolveBackend(requestedBackend, liveRoot, input.company, { dryRun: options.dryRun });
|
|
1259
1304
|
const now = new Date();
|
|
1260
1305
|
const threadId = `T-${formatTimestamp(now)}-auto-${summarySlug(input.summary)}`;
|
|
1261
1306
|
const threadPath = path.join(liveRoot, "workspace", "threads", `${threadId}.json`);
|
|
@@ -13,6 +13,8 @@ type HandoffContext = {
|
|
|
13
13
|
now?: () => Date;
|
|
14
14
|
/** Test-only override for the finalizer's historical fixed /tmp paths. */
|
|
15
15
|
tmpDir?: string;
|
|
16
|
+
/** Builds the HQ-Fingerprint commit trailer value. Defaults to the offline builder. */
|
|
17
|
+
buildFingerprint?: (root: string) => string | null;
|
|
16
18
|
};
|
|
17
19
|
export declare function runHandoffFinalize(args: string[], context: HandoffContext): RunResult;
|
|
18
20
|
export declare function runHandoffPost(args: string[], context: HandoffContext): RunResult | Promise<RunResult>;
|
|
@@ -14,6 +14,7 @@ import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
|
14
14
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
15
15
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
16
16
|
import { shellDisplayPath } from "../lib/core-utils/shell-display-path.js";
|
|
17
|
+
import { fingerprintTrailerArgs, offlineFingerprint } from "../lib/fingerprint/offline.js";
|
|
17
18
|
import { compactJson, isRecord, jqTruthy, prettyJson, runHandoffOpenSteps, shellDate, } from "./core-handoff-open-steps.js";
|
|
18
19
|
export { runHandoffOpenSteps };
|
|
19
20
|
const ASSETS = {
|
|
@@ -585,7 +586,8 @@ export function runHandoffFinalize(args, context) {
|
|
|
585
586
|
if (probe.hasGit && staged > 0) {
|
|
586
587
|
const pending = git(["diff", "--cached", "--name-only"], root).stdout.split(/\r?\n/).filter(Boolean);
|
|
587
588
|
if (pending.length > 0) {
|
|
588
|
-
const
|
|
589
|
+
const fingerprint = (context.buildFingerprint ?? ((hqRoot) => offlineFingerprint(hqRoot, process.env, "hq handoff", { readOnly: true })))(root);
|
|
590
|
+
const committed = git(["commit", "-m", `checkpoint: handoff ${threadId}`, ...fingerprintTrailerArgs(fingerprint)], root);
|
|
589
591
|
if (committed.code === 0) {
|
|
590
592
|
hqCommitted = true;
|
|
591
593
|
commitStatus = "committed";
|
|
@@ -340,18 +340,20 @@ function readSessionCompanySlugs(sid) {
|
|
|
340
340
|
return [...new Set(slugs)];
|
|
341
341
|
}
|
|
342
342
|
function parentSessionId() {
|
|
343
|
-
|
|
344
|
-
|| process.env.HQ_SESSION_ID
|
|
343
|
+
const own = process.env.HQ_SESSION_ID
|
|
345
344
|
|| process.env.CLAUDE_CODE_SESSION_ID
|
|
346
345
|
|| process.env.CLAUDE_SESSION_ID
|
|
347
346
|
|| process.env.CODEX_SESSION_ID
|
|
348
|
-
|| process.env.CODEX_THREAD_ID
|
|
349
|
-
|
|
347
|
+
|| process.env.CODEX_THREAD_ID;
|
|
348
|
+
// Inside a lane worker HQ_PARENT_SESSION_ID names the session that started
|
|
349
|
+
// the worker; the worker's own session is the parent of what it spawns.
|
|
350
|
+
const laneWorker = Boolean(process.env.HQ_LANE_ID?.trim()) && Boolean(own?.trim());
|
|
351
|
+
return String((laneWorker ? own : process.env.HQ_PARENT_SESSION_ID || own) || '').replace(/\s+/g, '');
|
|
350
352
|
}
|
|
351
353
|
function engineChildEnv() {
|
|
352
354
|
const extra = {};
|
|
353
355
|
const parent = parentSessionId();
|
|
354
|
-
if (
|
|
356
|
+
if (parent && parent !== process.env.HQ_PARENT_SESSION_ID)
|
|
355
357
|
extra.HQ_PARENT_SESSION_ID = parent;
|
|
356
358
|
if (!process.env.HQ_SPAWN_COMPANY) {
|
|
357
359
|
const capability = readSessionCompanyCapability(parent);
|
package/dist/commands/daemon.js
CHANGED
|
@@ -112,7 +112,9 @@ async function loadDefaults(over) {
|
|
|
112
112
|
err: over.err ?? ((l) => console.error(l)),
|
|
113
113
|
run,
|
|
114
114
|
nodeBinary: over.nodeBinary ?? mesh.resolveAbsoluteNodeBinary(),
|
|
115
|
-
|
|
115
|
+
// `node <hqBinary>` runs the task and every child service, so on Windows
|
|
116
|
+
// this must be the package's JS entry rather than the npm hq.cmd shim.
|
|
117
|
+
hqBinary: over.hqBinary ?? mesh.nodeRunnableHqEntry(mesh.resolveHqBinary(), platform),
|
|
116
118
|
uid: over.uid ?? (typeof process.getuid === "function" ? process.getuid() : 0),
|
|
117
119
|
isRoot: over.isRoot ?? (typeof process.getuid === "function" && process.getuid() === 0),
|
|
118
120
|
fileExists: over.fileExists ?? fs.existsSync,
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* The HQ root for `self`: HQ_ROOT, else the nearest ancestor of `cwd` (itself
|
|
4
|
+
* included) that holds `.claude` or `workers` and core/core.yaml. Null when
|
|
5
|
+
* there is none. Unlike findHqRoot it never falls back to `cwd`, so a commit
|
|
6
|
+
* in an unrelated repo does not create `.hq/` or session files there.
|
|
7
|
+
*/
|
|
8
|
+
export declare function findSelfHqRoot(env?: NodeJS.ProcessEnv, cwd?: string, existsSync?: (candidate: string) => boolean): string | null;
|
|
9
|
+
export declare function registerFingerprintCommand(program: Command): void;
|
|
10
|
+
//# sourceMappingURL=fingerprint.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hq fingerprint - print or resolve HQ session fingerprints.
|
|
3
|
+
*
|
|
4
|
+
* hq fingerprint self [--json] [--offline] [--no-register]
|
|
5
|
+
* hq fingerprint <value> [--json]
|
|
6
|
+
*
|
|
7
|
+
* `self` records the fingerprint with the hq-pro registry once per session
|
|
8
|
+
* after printing it (skipped with --no-register, --offline, and
|
|
9
|
+
* HQ_FINGERPRINT_OFFLINE=1). `<value>` checks local session records first and
|
|
10
|
+
* asks the registry only when none matches.
|
|
11
|
+
*
|
|
12
|
+
* Exit codes for `hq fingerprint <value>`: 0 found, 1 no fingerprint record,
|
|
13
|
+
* 2 not an HQ fingerprint. `self` exits 0 whether or not a value is built or
|
|
14
|
+
* registered.
|
|
15
|
+
*
|
|
16
|
+
* Outside an HQ tree (no HQ_ROOT and no ancestor with `.claude` or `workers`
|
|
17
|
+
* plus core/core.yaml) `self` prints a fingerprint with machine `-`, writes
|
|
18
|
+
* nothing, skips registration, and reports `reason: "no HQ root"` in --json.
|
|
19
|
+
*/
|
|
20
|
+
import * as fs from "node:fs";
|
|
21
|
+
import * as path from "node:path";
|
|
22
|
+
import { isFingerprint } from "../lib/fingerprint/format.js";
|
|
23
|
+
import { resolveLocalFingerprint } from "../lib/fingerprint/resolve-local.js";
|
|
24
|
+
import { buildSelfFingerprint, registerSelfFingerprint } from "../lib/fingerprint/self.js";
|
|
25
|
+
import { findHqRoot } from "../utils/manifest.js";
|
|
26
|
+
function hqRootFromEnv() {
|
|
27
|
+
return process.env.HQ_ROOT || findHqRoot();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The HQ root for `self`: HQ_ROOT, else the nearest ancestor of `cwd` (itself
|
|
31
|
+
* included) that holds `.claude` or `workers` and core/core.yaml. Null when
|
|
32
|
+
* there is none. Unlike findHqRoot it never falls back to `cwd`, so a commit
|
|
33
|
+
* in an unrelated repo does not create `.hq/` or session files there.
|
|
34
|
+
*/
|
|
35
|
+
export function findSelfHqRoot(env = process.env, cwd = process.cwd(), existsSync = fs.existsSync) {
|
|
36
|
+
const fromEnv = env.HQ_ROOT?.trim();
|
|
37
|
+
if (fromEnv)
|
|
38
|
+
return fromEnv;
|
|
39
|
+
let dir = path.resolve(cwd);
|
|
40
|
+
while (true) {
|
|
41
|
+
const marked = existsSync(path.join(dir, ".claude")) || existsSync(path.join(dir, "workers"));
|
|
42
|
+
if (marked && existsSync(path.join(dir, "core", "core.yaml")))
|
|
43
|
+
return dir;
|
|
44
|
+
const parent = path.dirname(dir);
|
|
45
|
+
if (parent === dir)
|
|
46
|
+
return null;
|
|
47
|
+
dir = parent;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function loadIdToken() {
|
|
51
|
+
const { loadCachedTokens } = await import("../utils/cognito-session.js");
|
|
52
|
+
return loadCachedTokens()?.idToken ?? null;
|
|
53
|
+
}
|
|
54
|
+
/** Asks the registry about a fingerprint with no local record. Never throws. */
|
|
55
|
+
async function lookupRegistry(value) {
|
|
56
|
+
// Loaded here so a local hit never loads the registry client.
|
|
57
|
+
const registry = await import("../lib/fingerprint/registry-client.js");
|
|
58
|
+
let token;
|
|
59
|
+
try {
|
|
60
|
+
token = await loadIdToken();
|
|
61
|
+
}
|
|
62
|
+
catch (err) {
|
|
63
|
+
registry.appendFingerprintLog("lookup", `failed: could not read the cached token: ${err.name}`);
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (!token)
|
|
67
|
+
return null;
|
|
68
|
+
const result = await registry.lookupFingerprint(value, token);
|
|
69
|
+
if (!result.ok) {
|
|
70
|
+
registry.appendFingerprintLog("lookup", `failed: ${registry.describeFailure(result)} fingerprint=${value}`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return result.found ? result.record : null;
|
|
74
|
+
}
|
|
75
|
+
export function registerFingerprintCommand(program) {
|
|
76
|
+
const fingerprint = program
|
|
77
|
+
.command("fingerprint")
|
|
78
|
+
.description("Print this session's HQ fingerprint, or resolve a fingerprint to its session")
|
|
79
|
+
.argument("[value]", "fingerprint value to resolve (hqfp1:...)")
|
|
80
|
+
.option("--json", "output JSON")
|
|
81
|
+
.action(async (value, opts) => {
|
|
82
|
+
if (!value) {
|
|
83
|
+
fingerprint.help();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (!isFingerprint(value)) {
|
|
87
|
+
process.stderr.write("not an HQ fingerprint\n");
|
|
88
|
+
process.exitCode = 2;
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const record = resolveLocalFingerprint(hqRootFromEnv(), value);
|
|
92
|
+
if (record) {
|
|
93
|
+
if (opts.json) {
|
|
94
|
+
process.stdout.write(`${JSON.stringify({ ...record, found: true })}\n`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const lines = [
|
|
98
|
+
`fingerprint: ${record.fingerprint}`,
|
|
99
|
+
`identity: ${record.identityUid ?? "-"} (${record.identityKind ?? "unknown"})`,
|
|
100
|
+
`machine: ${record.machineId ?? "-"} (${record.hostKind ?? "unknown"})`,
|
|
101
|
+
`session: ${record.sessionId ?? "-"}`,
|
|
102
|
+
`company: ${record.company ?? "-"}`,
|
|
103
|
+
`source: ${record.source}`,
|
|
104
|
+
];
|
|
105
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
let remote = null;
|
|
109
|
+
try {
|
|
110
|
+
remote = await lookupRegistry(value);
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
// lookupRegistry already turns request failures into null; this is a
|
|
114
|
+
// failure to load the client itself.
|
|
115
|
+
process.stderr.write(`hq fingerprint: registry lookup failed: ${err.message}\n`);
|
|
116
|
+
}
|
|
117
|
+
if (!remote) {
|
|
118
|
+
if (opts.json) {
|
|
119
|
+
process.stdout.write(`${JSON.stringify({ fingerprint: value, found: false })}\n`);
|
|
120
|
+
}
|
|
121
|
+
process.stderr.write("no fingerprint record\n");
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (opts.json) {
|
|
126
|
+
process.stdout.write(`${JSON.stringify({
|
|
127
|
+
fingerprint: value,
|
|
128
|
+
...remote,
|
|
129
|
+
source: "registry",
|
|
130
|
+
label: remote.machineLabel,
|
|
131
|
+
firstSeenAt: remote.firstSeenAt,
|
|
132
|
+
found: true,
|
|
133
|
+
})}\n`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const identityName = remote.identityName ? `${remote.identityName} ` : "";
|
|
137
|
+
const machineLabel = remote.machineLabel ? `${remote.machineLabel} ` : "";
|
|
138
|
+
const lines = [
|
|
139
|
+
`fingerprint: ${value}`,
|
|
140
|
+
`identity: ${identityName}(${remote.identityUid}, ${remote.identityKind})`,
|
|
141
|
+
`machine: ${machineLabel}(${remote.hostKind})`,
|
|
142
|
+
`session: ${remote.sessionId}`,
|
|
143
|
+
`company: ${remote.companySlug}`,
|
|
144
|
+
`first seen: ${remote.firstSeenAt}`,
|
|
145
|
+
`source: registry`,
|
|
146
|
+
];
|
|
147
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
148
|
+
});
|
|
149
|
+
fingerprint
|
|
150
|
+
.command("self")
|
|
151
|
+
.description("Print the fingerprint for the current HQ session")
|
|
152
|
+
.option("--json", "output JSON")
|
|
153
|
+
.option("--offline", "never make a network call (identity lookup or registration)")
|
|
154
|
+
.option("--no-register", "do not record this fingerprint with the HQ registry")
|
|
155
|
+
.action(async (_opts, cmd) => {
|
|
156
|
+
// The parent also defines --json and commander hands it to the parent,
|
|
157
|
+
// so read the merged options.
|
|
158
|
+
const opts = cmd.optsWithGlobals();
|
|
159
|
+
const offline = opts.offline === true || process.env.HQ_FINGERPRINT_OFFLINE === "1";
|
|
160
|
+
const foundRoot = findSelfHqRoot();
|
|
161
|
+
const noRoot = foundRoot === null;
|
|
162
|
+
const hqRoot = foundRoot ?? process.cwd();
|
|
163
|
+
// The commit-msg hook calls this on every commit, so it never throws
|
|
164
|
+
// and always exits 0.
|
|
165
|
+
let result;
|
|
166
|
+
let built = null;
|
|
167
|
+
try {
|
|
168
|
+
built = await buildSelfFingerprint({ hqRoot, offline, noRoot });
|
|
169
|
+
result = built;
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
process.stderr.write(`hq fingerprint: ${err.message}\n`);
|
|
173
|
+
result = { fingerprint: null, reason: `could not build fingerprint: ${err.message}` };
|
|
174
|
+
}
|
|
175
|
+
if (opts.json) {
|
|
176
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
177
|
+
}
|
|
178
|
+
else if (result.fingerprint) {
|
|
179
|
+
process.stdout.write(`${result.fingerprint}\n`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
process.stdout.write(`no fingerprint: ${result.reason ?? "unknown"}\n`);
|
|
183
|
+
}
|
|
184
|
+
// Registration runs after the output is written and reports only to
|
|
185
|
+
// ~/.hq/fingerprint.log; registerSelfFingerprint does not throw.
|
|
186
|
+
if (built?.fingerprint && !noRoot) {
|
|
187
|
+
await registerSelfFingerprint(built, { hqRoot, offline, register: opts.register !== false });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=fingerprint.js.map
|
|
@@ -224,6 +224,28 @@ export declare function toolPrefixForProvider(provider: string): string;
|
|
|
224
224
|
* projection over it.
|
|
225
225
|
*/
|
|
226
226
|
export declare function fetchAdminSurface(token: string, companyUid: string): Promise<AdminSurface>;
|
|
227
|
+
/** One page of the company's integration audit feed. */
|
|
228
|
+
export interface AuditPage {
|
|
229
|
+
/**
|
|
230
|
+
* True when the server answered the paged audit view. False means an older
|
|
231
|
+
* server ignored `view=audit` and returned the full admin surface, whose
|
|
232
|
+
* audit feed is capped at the latest 50 events and has no cursor.
|
|
233
|
+
*/
|
|
234
|
+
paged: boolean;
|
|
235
|
+
audit: AdminAuditEvent[];
|
|
236
|
+
nextCursor?: string;
|
|
237
|
+
}
|
|
238
|
+
/** Most rows the paged audit view returns per request. */
|
|
239
|
+
export declare const AUDIT_PAGE_MAX = 100;
|
|
240
|
+
/**
|
|
241
|
+
* Read one page of the audit feed via the admin route's paged view
|
|
242
|
+
* (`view=audit&limit=&cursor=`), newest first. Falls back to the surface's
|
|
243
|
+
* capped feed when the server does not know the view.
|
|
244
|
+
*/
|
|
245
|
+
export declare function fetchAuditPage(token: string, companyUid: string, opts: {
|
|
246
|
+
limit: number;
|
|
247
|
+
cursor?: string;
|
|
248
|
+
}): Promise<AuditPage>;
|
|
227
249
|
export declare function fetchConnections(token: string, companyUid: string): Promise<AdminConnection[]>;
|
|
228
250
|
/**
|
|
229
251
|
* One app the caller can use right now through the HQ gateway. Field names
|
|
@@ -352,6 +352,31 @@ export async function fetchAdminSurface(token, companyUid) {
|
|
|
352
352
|
audit: data.audit ?? [],
|
|
353
353
|
};
|
|
354
354
|
}
|
|
355
|
+
/** Most rows the paged audit view returns per request. */
|
|
356
|
+
export const AUDIT_PAGE_MAX = 100;
|
|
357
|
+
/**
|
|
358
|
+
* Read one page of the audit feed via the admin route's paged view
|
|
359
|
+
* (`view=audit&limit=&cursor=`), newest first. Falls back to the surface's
|
|
360
|
+
* capped feed when the server does not know the view.
|
|
361
|
+
*/
|
|
362
|
+
export async function fetchAuditPage(token, companyUid, opts) {
|
|
363
|
+
const limit = Math.max(1, Math.min(AUDIT_PAGE_MAX, Math.floor(opts.limit)));
|
|
364
|
+
const query = { companyUid, view: "audit", limit: String(limit) };
|
|
365
|
+
if (opts.cursor)
|
|
366
|
+
query.cursor = opts.cursor;
|
|
367
|
+
const res = await vaultApiFetch({ token, path: "/v1/integrations/admin", query });
|
|
368
|
+
if (!res.ok)
|
|
369
|
+
await raiseForResponse(res, "Failed to read integration activity");
|
|
370
|
+
const data = (await res.json());
|
|
371
|
+
const audit = Array.isArray(data.audit) ? data.audit : [];
|
|
372
|
+
if (data.view !== "audit")
|
|
373
|
+
return { paged: false, audit };
|
|
374
|
+
return {
|
|
375
|
+
paged: true,
|
|
376
|
+
audit,
|
|
377
|
+
...(typeof data.nextCursor === "string" && data.nextCursor ? { nextCursor: data.nextCursor } : {}),
|
|
378
|
+
};
|
|
379
|
+
}
|
|
355
380
|
export async function fetchConnections(token, companyUid) {
|
|
356
381
|
return (await fetchAdminSurface(token, companyUid)).connections;
|
|
357
382
|
}
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* and layered under the connection's write policy.
|
|
14
14
|
*/
|
|
15
15
|
import { Command } from "commander";
|
|
16
|
-
import { type GranteeType, type Permission, type WriteAllowlistGrant } from "./integrations-core.js";
|
|
16
|
+
import { type AdminAuditEvent, type GranteeType, type Permission, type WriteAllowlistGrant } from "./integrations-core.js";
|
|
17
17
|
interface Principal {
|
|
18
18
|
granteeType: GranteeType;
|
|
19
19
|
granteeId?: string;
|
|
@@ -46,5 +46,28 @@ export declare function mergeGrant(current: WriteAllowlistGrant[], toolName: str
|
|
|
46
46
|
* dropped entirely rather than persisted as an empty rule.
|
|
47
47
|
*/
|
|
48
48
|
export declare function removeGrant(current: WriteAllowlistGrant[], toolName: string, principal: Principal): WriteAllowlistGrant[] | null;
|
|
49
|
+
/** Upper bound on audit pages one `hq integrations audit` run reads. */
|
|
50
|
+
export declare const AUDIT_MAX_PAGES = 200;
|
|
51
|
+
export interface CollectedAudit {
|
|
52
|
+
rows: AdminAuditEvent[];
|
|
53
|
+
/** False when the server only returned its capped surface feed. */
|
|
54
|
+
paged: boolean;
|
|
55
|
+
/** Rows in the surface feed (fallback path only), before filtering. */
|
|
56
|
+
feedSize: number;
|
|
57
|
+
/** More matching rows may exist past what was collected. */
|
|
58
|
+
more: boolean;
|
|
59
|
+
pageCapHit: boolean;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Page the audit feed (newest first) until `limit` rows match, the cursor runs
|
|
63
|
+
* out, a row is older than `sinceMs`, or AUDIT_MAX_PAGES pages were read.
|
|
64
|
+
* `provider` is matched client-side on the bare slug, so the qualified
|
|
65
|
+
* `factory:linear` form copied from JSON output matches bare stored rows.
|
|
66
|
+
*/
|
|
67
|
+
export declare function collectAuditRows(token: string, companyUid: string, opts: {
|
|
68
|
+
limit: number;
|
|
69
|
+
provider?: string;
|
|
70
|
+
sinceMs?: number;
|
|
71
|
+
}): Promise<CollectedAudit>;
|
|
49
72
|
export {};
|
|
50
73
|
//# sourceMappingURL=integrations-manage.d.ts.map
|
|
@@ -17,7 +17,7 @@ import chalk from "chalk";
|
|
|
17
17
|
import { ensureCognitoIdToken } from "../utils/cognito-session.js";
|
|
18
18
|
import { getCompanyUid } from "../utils/vault-api.js";
|
|
19
19
|
import { redactSensitiveUrl, redactUrlsInText } from "../utils/redact-url.js";
|
|
20
|
-
import { IntegrationsCliError, bareProvider, printHealthNotice, fetchConnections, fetchAdminSurface, printJson, revokedConnectionDetails, resolveConnection, selectConnection, } from "./integrations-core.js";
|
|
20
|
+
import { IntegrationsCliError, bareProvider, printHealthNotice, fetchConnections, fetchAdminSurface, fetchAuditPage, AUDIT_PAGE_MAX, printJson, revokedConnectionDetails, resolveConnection, selectConnection, } from "./integrations-core.js";
|
|
21
21
|
import { fetchPendingApprovals, getConnectionAccess, mutateConnectionAccess, purgeConnection, setReadSafe, uninstallIntegration, updateGovernance, } from "./integrations-api.js";
|
|
22
22
|
const WRITE_POLICIES = ["auto-allow", "confirm", "deny"];
|
|
23
23
|
const PERMISSIONS = ["read", "write", "admin"];
|
|
@@ -520,30 +520,36 @@ export function registerManageCommands(integrations) {
|
|
|
520
520
|
.option("--company <slug>", "Company slug, e.g. indigo")
|
|
521
521
|
.option("--provider <slug>", "Only this app")
|
|
522
522
|
.option("--limit <n>", "How many events to show", "20")
|
|
523
|
+
.option("--since <iso>", "Only events at or after this ISO timestamp, e.g. 2026-10-01T00:00:00Z")
|
|
523
524
|
.option("--json", "Machine-readable output")
|
|
524
525
|
.action(async (opts) => {
|
|
525
|
-
const token = await ensureCognitoIdToken();
|
|
526
|
-
const companyUid = await getCompanyUid(token, opts.company);
|
|
527
|
-
const surface = await fetchAdminSurface(token, companyUid);
|
|
528
526
|
const limit = Number(opts.limit);
|
|
529
527
|
if (!Number.isFinite(limit) || limit < 1) {
|
|
530
528
|
throw new IntegrationsCliError("--limit must be a positive number.", { expected: true });
|
|
531
529
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
530
|
+
let sinceMs;
|
|
531
|
+
if (opts.since !== undefined) {
|
|
532
|
+
sinceMs = Date.parse(opts.since);
|
|
533
|
+
if (Number.isNaN(sinceMs)) {
|
|
534
|
+
throw new IntegrationsCliError("--since must be an ISO timestamp, e.g. 2026-10-01T00:00:00Z.", { expected: true });
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
const token = await ensureCognitoIdToken();
|
|
538
|
+
const companyUid = await getCompanyUid(token, opts.company);
|
|
539
|
+
const result = await collectAuditRows(token, companyUid, {
|
|
540
|
+
limit: Math.floor(limit),
|
|
541
|
+
provider: opts.provider,
|
|
542
|
+
sinceMs,
|
|
543
|
+
});
|
|
544
|
+
const { rows } = result;
|
|
540
545
|
if (opts.json) {
|
|
541
546
|
printJson(rows);
|
|
547
|
+
if (result.pageCapHit)
|
|
548
|
+
console.error(auditPageCapNote());
|
|
542
549
|
return;
|
|
543
550
|
}
|
|
544
551
|
if (rows.length === 0) {
|
|
545
552
|
console.log("No recorded activity yet.");
|
|
546
|
-
return;
|
|
547
553
|
}
|
|
548
554
|
for (const row of rows) {
|
|
549
555
|
const who = row.memberOrAgentName ?? row.memberOrAgent;
|
|
@@ -551,10 +557,21 @@ export function registerManageCommands(integrations) {
|
|
|
551
557
|
if (row.reason)
|
|
552
558
|
console.log(chalk.dim(` ${redactUrlsInText(row.reason)}`));
|
|
553
559
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
560
|
+
if (!result.paged) {
|
|
561
|
+
// An older hq-pro ignores view=audit and returns its surface feed,
|
|
562
|
+
// capped at 50 events; say so rather than letting a truncated list
|
|
563
|
+
// read as "that is everything that ever happened".
|
|
564
|
+
if (result.feedSize > rows.length && rows.length > 0) {
|
|
565
|
+
console.log(chalk.dim(`\nShowing ${rows.length} of ${result.feedSize} recent events.`));
|
|
566
|
+
}
|
|
567
|
+
console.log(chalk.dim("This server returns only the latest 50 events."));
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
if (result.pageCapHit) {
|
|
571
|
+
console.log(chalk.dim(`\n${auditPageCapNote()}`));
|
|
572
|
+
}
|
|
573
|
+
else if (result.more && rows.length > 0) {
|
|
574
|
+
console.log(chalk.dim(`\nShowing ${rows.length} events. Raise --limit or use --since to see more.`));
|
|
558
575
|
}
|
|
559
576
|
});
|
|
560
577
|
integrations
|
|
@@ -757,4 +774,62 @@ export function removeGrant(current, toolName, principal) {
|
|
|
757
774
|
}
|
|
758
775
|
return removed ? next : null;
|
|
759
776
|
}
|
|
777
|
+
/** Upper bound on audit pages one `hq integrations audit` run reads. */
|
|
778
|
+
export const AUDIT_MAX_PAGES = 200;
|
|
779
|
+
function auditPageCapNote() {
|
|
780
|
+
return `Stopped after reading ${AUDIT_MAX_PAGES} pages of activity; narrow the search with --since or --provider.`;
|
|
781
|
+
}
|
|
782
|
+
/**
|
|
783
|
+
* Page the audit feed (newest first) until `limit` rows match, the cursor runs
|
|
784
|
+
* out, a row is older than `sinceMs`, or AUDIT_MAX_PAGES pages were read.
|
|
785
|
+
* `provider` is matched client-side on the bare slug, so the qualified
|
|
786
|
+
* `factory:linear` form copied from JSON output matches bare stored rows.
|
|
787
|
+
*/
|
|
788
|
+
export async function collectAuditRows(token, companyUid, opts) {
|
|
789
|
+
const want = opts.provider ? bareProvider(opts.provider.trim()).toLowerCase() : undefined;
|
|
790
|
+
const matches = (row) => !want || bareProvider(row.provider ?? "").toLowerCase() === want;
|
|
791
|
+
const tooOld = (row) => {
|
|
792
|
+
if (opts.sinceMs === undefined)
|
|
793
|
+
return false;
|
|
794
|
+
const at = Date.parse(row.timestamp);
|
|
795
|
+
return !Number.isNaN(at) && at < opts.sinceMs;
|
|
796
|
+
};
|
|
797
|
+
const rows = [];
|
|
798
|
+
let cursor;
|
|
799
|
+
for (let page = 0; page < AUDIT_MAX_PAGES; page += 1) {
|
|
800
|
+
const remaining = opts.limit - rows.length;
|
|
801
|
+
// With a provider filter most rows may not match, so read full pages.
|
|
802
|
+
const pageLimit = want ? AUDIT_PAGE_MAX : Math.min(AUDIT_PAGE_MAX, remaining);
|
|
803
|
+
const res = await fetchAuditPage(token, companyUid, { limit: pageLimit, cursor });
|
|
804
|
+
if (!res.paged) {
|
|
805
|
+
if (page > 0)
|
|
806
|
+
return { rows, paged: true, feedSize: 0, more: false, pageCapHit: false };
|
|
807
|
+
const fallback = res.audit.filter((row) => !tooOld(row) && matches(row));
|
|
808
|
+
return {
|
|
809
|
+
rows: fallback.slice(0, opts.limit),
|
|
810
|
+
paged: false,
|
|
811
|
+
feedSize: res.audit.length,
|
|
812
|
+
more: fallback.length > opts.limit,
|
|
813
|
+
pageCapHit: false,
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
for (let i = 0; i < res.audit.length; i += 1) {
|
|
817
|
+
const row = res.audit[i];
|
|
818
|
+
if (tooOld(row))
|
|
819
|
+
return { rows, paged: true, feedSize: 0, more: false, pageCapHit: false };
|
|
820
|
+
if (!matches(row))
|
|
821
|
+
continue;
|
|
822
|
+
if (rows.length >= opts.limit) {
|
|
823
|
+
return { rows, paged: true, feedSize: 0, more: true, pageCapHit: false };
|
|
824
|
+
}
|
|
825
|
+
rows.push(row);
|
|
826
|
+
}
|
|
827
|
+
if (!res.nextCursor)
|
|
828
|
+
return { rows, paged: true, feedSize: 0, more: false, pageCapHit: false };
|
|
829
|
+
if (rows.length >= opts.limit)
|
|
830
|
+
return { rows, paged: true, feedSize: 0, more: true, pageCapHit: false };
|
|
831
|
+
cursor = res.nextCursor;
|
|
832
|
+
}
|
|
833
|
+
return { rows, paged: true, feedSize: 0, more: true, pageCapHit: true };
|
|
834
|
+
}
|
|
760
835
|
//# sourceMappingURL=integrations-manage.js.map
|