@haystackeditor/cli 0.15.31 → 0.16.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/README.md +111 -60
- package/dist/assets/hooks/scripts/commit-msg.sh +3 -0
- package/dist/assets/hooks/scripts/post-commit.sh +3 -0
- package/dist/assets/hooks/scripts/pre-commit.sh +11 -6
- package/dist/assets/hooks/scripts/pre-push.sh +3 -0
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
- package/dist/commands/case-batch-contract.js +608 -0
- package/dist/commands/case-batch.js +713 -0
- package/dist/commands/cloud-verifier-identity-census.js +5 -2
- package/dist/commands/combination-search-hook.js +139 -0
- package/dist/commands/dismiss.js +2 -1
- package/dist/commands/hooks.js +66 -7
- package/dist/commands/install-session-hooks.js +131 -44
- package/dist/commands/policy.js +32 -45
- package/dist/commands/precompute-delivery.js +6 -1
- package/dist/commands/scaffold-provisional-universe.js +8 -10
- package/dist/commands/setup.js +32 -10
- package/dist/commands/submit.js +7 -2
- package/dist/commands/telemetry.js +17 -2
- package/dist/commands/triage.js +2 -1
- package/dist/commands/verify-explore.js +1 -0
- package/dist/commands/verify-hosted-mcp.js +3 -12
- package/dist/commands/verify-hosted-reproducibility.js +49 -547
- package/dist/commands/verify-hosted.js +66 -168
- package/dist/commands/verify-precompute.js +57 -10
- package/dist/commands/verify.js +51 -139
- package/dist/index.js +238 -350
- package/dist/lazy.js +8 -0
- package/dist/schema.js +1 -0
- package/dist/tools/detect.js +3 -24
- package/dist/triage/prompts.js +46 -45
- package/dist/triage/runner.js +119 -33
- package/dist/types.js +3 -3
- package/dist/utils/auth.js +14 -2
- package/dist/utils/git.js +60 -29
- package/dist/utils/github-api.js +14 -1
- package/dist/utils/haystack-api.js +26 -7
- package/dist/utils/hooks.js +43 -6
- package/dist/utils/prompter.js +25 -10
- package/dist/utils/safe-write.js +31 -0
- package/dist/utils/secret-paths.js +115 -0
- package/dist/utils/secrets.js +0 -1
- package/dist/utils/telemetry.js +34 -14
- package/dist/utils/update-check.js +151 -0
- package/package.json +18 -14
- package/schemas/case-batch.v1.json +184 -0
- package/schemas/cloud-verifier.v1.json +55 -100
- package/dist/commands/ask.d.ts +0 -14
- package/dist/commands/cloud-verifier-behaviors.d.ts +0 -27
- package/dist/commands/cloud-verifier-data-store-census.d.ts +0 -47
- package/dist/commands/cloud-verifier-data-store-drift.d.ts +0 -42
- package/dist/commands/cloud-verifier-identity-census.d.ts +0 -88
- package/dist/commands/cloud-verifier-materialization.d.ts +0 -16
- package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +0 -29
- package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +0 -27
- package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +0 -51
- package/dist/commands/cloud-verifier-universe.d.ts +0 -31
- package/dist/commands/config.d.ts +0 -46
- package/dist/commands/design-verify.d.ts +0 -33
- package/dist/commands/dismiss.d.ts +0 -29
- package/dist/commands/hooks.d.ts +0 -13
- package/dist/commands/inbox.d.ts +0 -65
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/install-session-hooks.d.ts +0 -17
- package/dist/commands/login.d.ts +0 -8
- package/dist/commands/mcp.d.ts +0 -1
- package/dist/commands/policy.d.ts +0 -31
- package/dist/commands/pr-status.d.ts +0 -144
- package/dist/commands/pr.d.ts +0 -40
- package/dist/commands/precompute-delivery-contract.d.ts +0 -68
- package/dist/commands/precompute-delivery.d.ts +0 -20
- package/dist/commands/prepare-universe-review.d.ts +0 -115
- package/dist/commands/production-source-deny-policy.d.ts +0 -15
- package/dist/commands/request-review.d.ts +0 -26
- package/dist/commands/review.d.ts +0 -25
- package/dist/commands/rules.d.ts +0 -4
- package/dist/commands/scaffold-provisional-universe.d.ts +0 -468
- package/dist/commands/schema-cmd.d.ts +0 -2
- package/dist/commands/setup.d.ts +0 -28
- package/dist/commands/skills.d.ts +0 -8
- package/dist/commands/status.d.ts +0 -4
- package/dist/commands/submit.d.ts +0 -30
- package/dist/commands/system-map.d.ts +0 -42
- package/dist/commands/telemetry.d.ts +0 -53
- package/dist/commands/tokens.d.ts +0 -14
- package/dist/commands/triage.d.ts +0 -35
- package/dist/commands/verify-core.d.ts +0 -449
- package/dist/commands/verify-core.js +0 -789
- package/dist/commands/verify-explore.d.ts +0 -14
- package/dist/commands/verify-history.d.ts +0 -14
- package/dist/commands/verify-hosted-mcp.d.ts +0 -12
- package/dist/commands/verify-hosted-reproducibility.d.ts +0 -90
- package/dist/commands/verify-hosted.d.ts +0 -92
- package/dist/commands/verify-mcp.d.ts +0 -8
- package/dist/commands/verify-mcp.js +0 -517
- package/dist/commands/verify-ops.d.ts +0 -158
- package/dist/commands/verify-ops.js +0 -1148
- package/dist/commands/verify-precompute.d.ts +0 -30
- package/dist/commands/verify-reproducibility.d.ts +0 -85
- package/dist/commands/verify-reproducibility.js +0 -494
- package/dist/commands/verify-reseal.d.ts +0 -9
- package/dist/commands/verify-reseal.js +0 -148
- package/dist/commands/verify-sandboxes.d.ts +0 -95
- package/dist/commands/verify-sandboxes.js +0 -352
- package/dist/commands/verify.d.ts +0 -28
- package/dist/commands/webhooks.d.ts +0 -30
- package/dist/index.d.ts +0 -22
- package/dist/schema.d.ts +0 -28
- package/dist/states.d.ts +0 -29
- package/dist/tools/detect.d.ts +0 -50
- package/dist/triage/prompts.d.ts +0 -24
- package/dist/triage/runner.d.ts +0 -34
- package/dist/triage/types.d.ts +0 -42
- package/dist/types/verify-history.d.ts +0 -38
- package/dist/types.d.ts +0 -1684
- package/dist/utils/action-output.d.ts +0 -24
- package/dist/utils/analysis-api.d.ts +0 -187
- package/dist/utils/auth.d.ts +0 -79
- package/dist/utils/config.d.ts +0 -24
- package/dist/utils/design-verifier-api.d.ts +0 -208
- package/dist/utils/design-verifier-history.d.ts +0 -46
- package/dist/utils/design-verifier-result.d.ts +0 -74
- package/dist/utils/detect.d.ts +0 -43
- package/dist/utils/git.d.ts +0 -135
- package/dist/utils/github-api.d.ts +0 -104
- package/dist/utils/haystack-api.d.ts +0 -37
- package/dist/utils/hooks.d.ts +0 -12
- package/dist/utils/pending-state.d.ts +0 -40
- package/dist/utils/pr-ref.d.ts +0 -27
- package/dist/utils/prompter.d.ts +0 -85
- package/dist/utils/secrets.d.ts +0 -47
- package/dist/utils/telemetry.d.ts +0 -19
- /package/dist/commands/{precompute-delivery-worker.d.ts → combination-search-hook-contract.js} +0 -0
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
// `haystack verify reseal <specimen>` — rebuild a sealed specimen bundle and
|
|
2
|
-
// restart the control plane so it actually serves it.
|
|
3
|
-
//
|
|
4
|
-
// The control plane resolves specimen checkouts at import time: rebuilding a
|
|
5
|
-
// bundle under a running server leaves every new run on the OLD checkout, and
|
|
6
|
-
// deleting the checkout is worse (runs die in tar). The safe sequence, encoded
|
|
7
|
-
// here, is: reseal (the script's own diff guard decides whether to ship),
|
|
8
|
-
// then restart the dev server and wait until the re-materialized checkout's
|
|
9
|
-
// refs match the new bundle. Restart is triggered by touching vite.config.ts —
|
|
10
|
-
// Vite watches its config file (which imports the control-plane plugin) and
|
|
11
|
-
// performs a full restart, re-importing specimens.ts, which re-materializes
|
|
12
|
-
// the bundle atomically (replace, never delete-first).
|
|
13
|
-
import { execFileSync, spawnSync } from 'node:child_process';
|
|
14
|
-
import * as fs from 'node:fs';
|
|
15
|
-
import * as os from 'node:os';
|
|
16
|
-
import * as path from 'node:path';
|
|
17
|
-
import chalk from 'chalk';
|
|
18
|
-
import { discoverVerifyRoot, fetchRunFromServer, resolveServer, setVerifyToken, TERMINAL_RUN_STATUSES, VerifyUnauthorizedError, } from './verify-core.js';
|
|
19
|
-
// Kept in step with demo/cloud-verifier/specimenRepo.ts by hand (same
|
|
20
|
-
// arrangement as the SPECIMENS list in verify.ts).
|
|
21
|
-
const RESEAL_TARGETS = {
|
|
22
|
-
'posthog-js-3191': {
|
|
23
|
-
bundleName: 'posthog-js-pr-3191.bundle',
|
|
24
|
-
checkoutName: 'posthog-js-pr-3191',
|
|
25
|
-
defaultSourceDir: path.join(os.homedir(), 'haystack-demo-specimens', 'ph3191-specimen'),
|
|
26
|
-
},
|
|
27
|
-
'posthog-js-478': {
|
|
28
|
-
bundleName: 'posthog-js-pr-478.bundle',
|
|
29
|
-
checkoutName: 'posthog-js-pr-478',
|
|
30
|
-
defaultSourceDir: null,
|
|
31
|
-
},
|
|
32
|
-
'bootstrap-text-muted': {
|
|
33
|
-
bundleName: 'bootstrap-text-muted.bundle',
|
|
34
|
-
checkoutName: 'bootstrap-text-muted',
|
|
35
|
-
defaultSourceDir: null,
|
|
36
|
-
},
|
|
37
|
-
};
|
|
38
|
-
function git(args, cwd) {
|
|
39
|
-
return execFileSync('git', args, { ...(cwd ? { cwd } : {}), encoding: 'utf8' }).trim();
|
|
40
|
-
}
|
|
41
|
-
function bundleRefs(bundlePath) {
|
|
42
|
-
const refs = new Map();
|
|
43
|
-
for (const line of git(['bundle', 'list-heads', bundlePath]).split('\n')) {
|
|
44
|
-
const match = line.match(/^([0-9a-f]{40}) refs\/heads\/(.+)$/);
|
|
45
|
-
if (match)
|
|
46
|
-
refs.set(match[2], match[1]);
|
|
47
|
-
}
|
|
48
|
-
return refs;
|
|
49
|
-
}
|
|
50
|
-
function checkoutMatches(checkoutDir, expected) {
|
|
51
|
-
if (!fs.existsSync(path.join(checkoutDir, '.git')))
|
|
52
|
-
return false;
|
|
53
|
-
try {
|
|
54
|
-
for (const [ref, sha] of expected) {
|
|
55
|
-
if (git(['rev-parse', `refs/heads/${ref}`], checkoutDir) !== sha)
|
|
56
|
-
return false;
|
|
57
|
-
}
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
catch {
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
async function serverAlive(server) {
|
|
65
|
-
try {
|
|
66
|
-
// Any HTTP answer means the dev server is up; 404 for a missing run is fine.
|
|
67
|
-
await fetch(`${server}/api/cloud-verifier/runs/latest`, { signal: AbortSignal.timeout(3000) });
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
export async function verifyResealCommand(specimen, options) {
|
|
75
|
-
setVerifyToken(options.token);
|
|
76
|
-
const target = RESEAL_TARGETS[specimen];
|
|
77
|
-
if (!target) {
|
|
78
|
-
throw new Error(`No reseal mapping for "${specimen}". Known: ${Object.keys(RESEAL_TARGETS).join(', ')}.`);
|
|
79
|
-
}
|
|
80
|
-
const sourceDir = path.resolve(options.source || target.defaultSourceDir || '');
|
|
81
|
-
if (!options.source && !target.defaultSourceDir) {
|
|
82
|
-
throw new Error(`Specimen "${specimen}" has no known source directory — pass --source <dir> pointing at the directory that holds reseal.sh.`);
|
|
83
|
-
}
|
|
84
|
-
const script = path.join(sourceDir, 'reseal.sh');
|
|
85
|
-
if (!fs.existsSync(script)) {
|
|
86
|
-
throw new Error(`${script} does not exist. Point --source at the specimen source directory.`);
|
|
87
|
-
}
|
|
88
|
-
const repoRoot = discoverVerifyRoot(options.repoRoot);
|
|
89
|
-
if (!repoRoot || !fs.existsSync(path.join(repoRoot, 'demo', 'cloud-verifier', 'specimens'))) {
|
|
90
|
-
throw new Error('Could not locate the control-plane repository (demo/cloud-verifier/specimens). Run from it or set HAYSTACK_VERIFY_REPO.');
|
|
91
|
-
}
|
|
92
|
-
const bundlePath = path.join(repoRoot, 'demo', 'cloud-verifier', 'specimens', target.bundleName);
|
|
93
|
-
const checkoutDir = path.join(repoRoot, '.haystack', 'verify', 'specimens', target.checkoutName);
|
|
94
|
-
const server = resolveServer(options.server);
|
|
95
|
-
// Never restart the control plane out from under an active run — the
|
|
96
|
-
// orchestrator lives in the dev-server process and dies with it.
|
|
97
|
-
const alive = await serverAlive(server);
|
|
98
|
-
if (alive) {
|
|
99
|
-
const latest = await fetchRunFromServer(server, 'latest');
|
|
100
|
-
// An unauthorized answer means the guard could not check anything — do not
|
|
101
|
-
// restart a control plane whose runs we cannot see.
|
|
102
|
-
if (latest.outcome === 'unauthorized')
|
|
103
|
-
throw new VerifyUnauthorizedError(server);
|
|
104
|
-
if (latest.outcome === 'ok' && !TERMINAL_RUN_STATUSES.has(latest.run.status)) {
|
|
105
|
-
throw new Error(`Run ${latest.run.runId} is still ${latest.run.status}; resealing restarts the control plane and would kill it. `
|
|
106
|
-
+ 'Wait for it (haystack verify watch) or accept the loss by re-running after it finishes.');
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
console.error(chalk.dim(`Resealing ${specimen} from ${sourceDir}…`));
|
|
110
|
-
const reseal = spawnSync('bash', [script], { cwd: sourceDir, stdio: 'inherit' });
|
|
111
|
-
if (reseal.status !== 0) {
|
|
112
|
-
// The script's diff guard prints its own refusal; keep it authoritative.
|
|
113
|
-
throw new Error(`reseal.sh exited ${reseal.status} — bundle NOT shipped.`);
|
|
114
|
-
}
|
|
115
|
-
const refs = bundleRefs(bundlePath);
|
|
116
|
-
if (refs.size === 0)
|
|
117
|
-
throw new Error(`Reseal reported success but ${bundlePath} has no heads.`);
|
|
118
|
-
console.error(`${chalk.green('✓')} bundle sealed: ${[...refs.entries()].map(([ref, sha]) => `${ref}=${sha.slice(0, 8)}`).join(' ')}`);
|
|
119
|
-
if (options.noRestart) {
|
|
120
|
-
console.error(chalk.yellow('Restart skipped (--no-restart). The running control plane still serves the OLD checkout until restarted.'));
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (!alive) {
|
|
124
|
-
console.log(`${chalk.green('✓')} Control plane is not running; it will materialize the new bundle on next start (pnpm dev).`);
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
const viteConfig = path.join(repoRoot, 'vite.config.ts');
|
|
128
|
-
if (!fs.existsSync(viteConfig)) {
|
|
129
|
-
throw new Error(`Cannot restart: ${viteConfig} not found. Restart the dev server by hand; the new bundle materializes at boot.`);
|
|
130
|
-
}
|
|
131
|
-
console.error(chalk.dim('Restarting the control plane (touching vite.config.ts) and waiting for re-materialization…'));
|
|
132
|
-
const now = new Date();
|
|
133
|
-
fs.utimesSync(viteConfig, now, now);
|
|
134
|
-
const deadline = Date.now() + 120_000;
|
|
135
|
-
for (;;) {
|
|
136
|
-
if (checkoutMatches(checkoutDir, refs) && await serverAlive(server)) {
|
|
137
|
-
console.log(`${chalk.green('✓')} Control plane restarted on the new bundle; ${path.relative(repoRoot, checkoutDir)} matches ${target.bundleName}.`);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (Date.now() > deadline) {
|
|
141
|
-
process.exitCode = 2;
|
|
142
|
-
console.error(chalk.red('The checkout still does not match the new bundle after 120s. Restart the dev server manually '
|
|
143
|
-
+ '(Ctrl-C the `pnpm dev` process and start it again); it will re-materialize the bundle at boot.'));
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
147
|
-
}
|
|
148
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
export declare function resolveE2BApiKey(): string;
|
|
2
|
-
export interface E2BSandboxInfo {
|
|
3
|
-
sandboxId: string;
|
|
4
|
-
templateId: string;
|
|
5
|
-
alias: string | null;
|
|
6
|
-
startedAt: string | null;
|
|
7
|
-
endAt: string | null;
|
|
8
|
-
cpuCount: number | null;
|
|
9
|
-
memoryMB: number | null;
|
|
10
|
-
}
|
|
11
|
-
export declare function listE2BSandboxes(apiKey: string): Promise<E2BSandboxInfo[]>;
|
|
12
|
-
export declare function killE2BSandbox(apiKey: string, sandboxId: string): Promise<void>;
|
|
13
|
-
export interface SandboxAttribution {
|
|
14
|
-
runId: string;
|
|
15
|
-
chapterId: string;
|
|
16
|
-
role: 'golden' | 'base' | 'head' | 'kept-alive' | 'bisect-probe';
|
|
17
|
-
cell: string | null;
|
|
18
|
-
runStatus: string;
|
|
19
|
-
expiresAt: string | null;
|
|
20
|
-
}
|
|
21
|
-
export interface AttributionIndex {
|
|
22
|
-
bySandboxId: Map<string, SandboxAttribution>;
|
|
23
|
-
/** Golden snapshot ids (fork template ids), with and without the `:tag` suffix. */
|
|
24
|
-
snapshotIds: Set<string>;
|
|
25
|
-
newestRunByChapter: Map<string, string>;
|
|
26
|
-
}
|
|
27
|
-
export declare function collectAttribution(repoRoot: string): AttributionIndex;
|
|
28
|
-
/**
|
|
29
|
-
* A run.json can say `running` forever after the dev server restarted mid-run;
|
|
30
|
-
* its mid-flight universes then leak until hard expiry and disk alone will not
|
|
31
|
-
* mark them stale. The control plane reports such runs as failed — fold its
|
|
32
|
-
* answer into the attribution. Unreachable server = leave non-terminal runs
|
|
33
|
-
* alone (conservative: never kill what might still be alive).
|
|
34
|
-
*/
|
|
35
|
-
export declare function reconcileAttributionWithServer(index: AttributionIndex, server: string): Promise<void>;
|
|
36
|
-
export interface AnnotatedSandbox extends E2BSandboxInfo {
|
|
37
|
-
verifier: boolean;
|
|
38
|
-
attribution: SandboxAttribution | null;
|
|
39
|
-
/** Attributed via golden-snapshot template but absent from any run record. */
|
|
40
|
-
templateOnly: boolean;
|
|
41
|
-
stale: boolean;
|
|
42
|
-
staleReason: string | null;
|
|
43
|
-
age: string | null;
|
|
44
|
-
endsIn: string | null;
|
|
45
|
-
}
|
|
46
|
-
export declare function annotateSandboxes(sandboxes: E2BSandboxInfo[], index: AttributionIndex | null): AnnotatedSandbox[];
|
|
47
|
-
export interface SandboxInventory {
|
|
48
|
-
schema_version: '1.0.0';
|
|
49
|
-
total: number;
|
|
50
|
-
verifier_count: number;
|
|
51
|
-
other_count: number;
|
|
52
|
-
stale_count: number;
|
|
53
|
-
attribution_available: boolean;
|
|
54
|
-
sandboxes: Array<{
|
|
55
|
-
sandbox_id: string;
|
|
56
|
-
template: string;
|
|
57
|
-
verifier: boolean;
|
|
58
|
-
stale: boolean;
|
|
59
|
-
stale_reason: string | null;
|
|
60
|
-
role: string | null;
|
|
61
|
-
run_id: string | null;
|
|
62
|
-
chapter_id: string | null;
|
|
63
|
-
cell: string | null;
|
|
64
|
-
age: string | null;
|
|
65
|
-
ends: string | null;
|
|
66
|
-
}>;
|
|
67
|
-
}
|
|
68
|
-
export declare function inventoryOf(annotated: AnnotatedSandbox[], attributionAvailable: boolean): SandboxInventory;
|
|
69
|
-
export declare function fetchInventoryFromServer(server: string): Promise<SandboxInventory | null>;
|
|
70
|
-
export declare function killSandboxViaServer(server: string, sandboxId: string, force: boolean): Promise<{
|
|
71
|
-
ok: boolean;
|
|
72
|
-
error?: string;
|
|
73
|
-
}>;
|
|
74
|
-
export interface ResolvedInventory {
|
|
75
|
-
inventory: SandboxInventory;
|
|
76
|
-
via: 'server' | 'e2b';
|
|
77
|
-
kill: (sandboxId: string, force: boolean) => Promise<{
|
|
78
|
-
ok: boolean;
|
|
79
|
-
error?: string;
|
|
80
|
-
}>;
|
|
81
|
-
}
|
|
82
|
-
/** Inventory from the control plane when it has the operator API, else direct E2B. */
|
|
83
|
-
export declare function resolveInventory(options: {
|
|
84
|
-
server: string;
|
|
85
|
-
repoRoot: string | null;
|
|
86
|
-
}): Promise<ResolvedInventory>;
|
|
87
|
-
export interface VerifySandboxesOptions {
|
|
88
|
-
repoRoot?: string;
|
|
89
|
-
server?: string;
|
|
90
|
-
token?: string;
|
|
91
|
-
json?: boolean;
|
|
92
|
-
killStale?: boolean;
|
|
93
|
-
killRun?: string;
|
|
94
|
-
}
|
|
95
|
-
export declare function verifySandboxesCommand(options: VerifySandboxesOptions): Promise<void>;
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
// E2B sandbox inventory and cleanup for the cloud verifier.
|
|
2
|
-
//
|
|
3
|
-
// The E2B account is SHARED with other workstreams, and the executor tags
|
|
4
|
-
// sandboxes with no metadata — attribution comes entirely from the run records
|
|
5
|
-
// on disk: each cell's base/head sandboxId, execution-results.json keptAlive +
|
|
6
|
-
// snapshotId, bisect probe sandboxIds, and the golden-sandbox ids the event
|
|
7
|
-
// stream mentions. Anything we cannot attribute is listed but NEVER killed.
|
|
8
|
-
import * as fs from 'node:fs';
|
|
9
|
-
import * as os from 'node:os';
|
|
10
|
-
import * as path from 'node:path';
|
|
11
|
-
import chalk from 'chalk';
|
|
12
|
-
import { discoverVerifyRoot, fetchRunFromServer, listRunDirs, loadRunFromDisk, formatAge, formatUntil, resolveServer, runDir, setVerifyToken, TERMINAL_RUN_STATUSES, verifyAuthHeaders, VerifyUnauthorizedError, } from './verify-core.js';
|
|
13
|
-
const E2B_API = process.env.E2B_API_URL || 'https://api.e2b.dev';
|
|
14
|
-
/** Template alias every specimen manifest provisions from. */
|
|
15
|
-
const VERIFIER_TEMPLATE_ALIAS = 'haystack-cloud-verifier';
|
|
16
|
-
export function resolveE2BApiKey() {
|
|
17
|
-
if (process.env.E2B_API_KEY)
|
|
18
|
-
return process.env.E2B_API_KEY;
|
|
19
|
-
const configPath = path.join(os.homedir(), '.e2b', 'config.json');
|
|
20
|
-
let config;
|
|
21
|
-
try {
|
|
22
|
-
config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
23
|
-
}
|
|
24
|
-
catch (error) {
|
|
25
|
-
throw new Error(`E2B_API_KEY is not set and ${configPath} could not be read: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
-
}
|
|
27
|
-
if (typeof config.teamApiKey !== 'string' || !config.teamApiKey) {
|
|
28
|
-
throw new Error(`E2B_API_KEY is not set and ${configPath} has no teamApiKey`);
|
|
29
|
-
}
|
|
30
|
-
return config.teamApiKey;
|
|
31
|
-
}
|
|
32
|
-
export async function listE2BSandboxes(apiKey) {
|
|
33
|
-
const response = await fetch(`${E2B_API}/sandboxes`, { headers: { 'X-API-KEY': apiKey } });
|
|
34
|
-
if (!response.ok) {
|
|
35
|
-
throw new Error(`E2B list failed: HTTP ${response.status} ${await response.text().then(t => t.slice(0, 300))}`);
|
|
36
|
-
}
|
|
37
|
-
const raw = (await response.json());
|
|
38
|
-
return raw.map(entry => ({
|
|
39
|
-
sandboxId: String(entry.sandboxID ?? entry.sandboxId ?? ''),
|
|
40
|
-
templateId: String(entry.templateID ?? entry.templateId ?? ''),
|
|
41
|
-
alias: typeof entry.alias === 'string' ? entry.alias : null,
|
|
42
|
-
startedAt: typeof entry.startedAt === 'string' ? entry.startedAt : null,
|
|
43
|
-
endAt: typeof entry.endAt === 'string' ? entry.endAt : null,
|
|
44
|
-
cpuCount: typeof entry.cpuCount === 'number' ? entry.cpuCount : null,
|
|
45
|
-
memoryMB: typeof entry.memoryMB === 'number' ? entry.memoryMB : null,
|
|
46
|
-
})).filter(entry => entry.sandboxId);
|
|
47
|
-
}
|
|
48
|
-
export async function killE2BSandbox(apiKey, sandboxId) {
|
|
49
|
-
const response = await fetch(`${E2B_API}/sandboxes/${encodeURIComponent(sandboxId)}`, {
|
|
50
|
-
method: 'DELETE',
|
|
51
|
-
headers: { 'X-API-KEY': apiKey },
|
|
52
|
-
});
|
|
53
|
-
if (!response.ok && response.status !== 404) {
|
|
54
|
-
throw new Error(`E2B kill ${sandboxId} failed: HTTP ${response.status}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
export function collectAttribution(repoRoot) {
|
|
58
|
-
const bySandboxId = new Map();
|
|
59
|
-
const snapshotIds = new Set();
|
|
60
|
-
const newestRunByChapter = new Map();
|
|
61
|
-
for (const entry of listRunDirs(repoRoot)) {
|
|
62
|
-
const run = loadRunFromDisk(repoRoot, entry.runId);
|
|
63
|
-
if (!run)
|
|
64
|
-
continue;
|
|
65
|
-
for (const chapter of run.chapters) {
|
|
66
|
-
if (!newestRunByChapter.has(chapter.chapterId)) {
|
|
67
|
-
newestRunByChapter.set(chapter.chapterId, run.runId);
|
|
68
|
-
}
|
|
69
|
-
const record = (sandboxId, role, cell, expiresAt) => {
|
|
70
|
-
if (!sandboxId || bySandboxId.has(sandboxId))
|
|
71
|
-
return;
|
|
72
|
-
bySandboxId.set(sandboxId, {
|
|
73
|
-
runId: run.runId,
|
|
74
|
-
chapterId: chapter.chapterId,
|
|
75
|
-
role,
|
|
76
|
-
cell,
|
|
77
|
-
runStatus: run.status,
|
|
78
|
-
expiresAt: expiresAt ?? null,
|
|
79
|
-
});
|
|
80
|
-
};
|
|
81
|
-
for (const cell of chapter.riskCells ?? []) {
|
|
82
|
-
const keptAlive = cell.head.status === 'running' && Boolean(cell.head.browserUrl);
|
|
83
|
-
record(cell.head.sandboxId, keptAlive ? 'kept-alive' : 'head', `${cell.ordinal}. ${cell.title}`, cell.head.expiresAt);
|
|
84
|
-
record(cell.base.sandboxId, 'base', `${cell.ordinal}. ${cell.title}`, cell.base.expiresAt);
|
|
85
|
-
}
|
|
86
|
-
for (const probe of chapter.bisect?.probes ?? []) {
|
|
87
|
-
record(probe.sandboxId, 'bisect-probe', probe.shortSha);
|
|
88
|
-
}
|
|
89
|
-
record(chapter.bisect?.incidentProbe?.sandboxId, 'bisect-probe', 'incident');
|
|
90
|
-
// Golden sandboxes and snapshots only appear in stage artifacts/events.
|
|
91
|
-
const chapterDir = path.join(runDir(repoRoot, run.runId), 'chapters', chapter.chapterId);
|
|
92
|
-
for (const file of ['execution-results.json', 'snapshot-cleanup.json']) {
|
|
93
|
-
try {
|
|
94
|
-
const parsed = JSON.parse(fs.readFileSync(path.join(chapterDir, file), 'utf8'));
|
|
95
|
-
if (typeof parsed.snapshotId === 'string') {
|
|
96
|
-
snapshotIds.add(parsed.snapshotId);
|
|
97
|
-
snapshotIds.add(parsed.snapshotId.split(':')[0]);
|
|
98
|
-
}
|
|
99
|
-
const cleanupResources = Array.isArray(parsed.cleanupResources)
|
|
100
|
-
? parsed.cleanupResources
|
|
101
|
-
: Array.isArray(parsed.resources)
|
|
102
|
-
? parsed.resources
|
|
103
|
-
: [];
|
|
104
|
-
for (const resource of cleanupResources) {
|
|
105
|
-
if (resource?.kind === 'e2b-snapshot'
|
|
106
|
-
&& typeof resource.id === 'string') {
|
|
107
|
-
snapshotIds.add(resource.id);
|
|
108
|
-
snapshotIds.add(resource.id.split(':')[0]);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch { /* stage artifact absent for this chapter — nothing to index */ }
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
for (const event of run.events) {
|
|
116
|
-
const golden = event.message.match(/Golden sandbox (\S+):/);
|
|
117
|
-
if (golden && !bySandboxId.has(golden[1])) {
|
|
118
|
-
bySandboxId.set(golden[1], {
|
|
119
|
-
runId: run.runId,
|
|
120
|
-
chapterId: event.chapterId ?? run.chapters[0]?.chapterId ?? '?',
|
|
121
|
-
role: 'golden',
|
|
122
|
-
cell: null,
|
|
123
|
-
runStatus: run.status,
|
|
124
|
-
expiresAt: null,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
return { bySandboxId, snapshotIds, newestRunByChapter };
|
|
130
|
-
}
|
|
131
|
-
/**
|
|
132
|
-
* A run.json can say `running` forever after the dev server restarted mid-run;
|
|
133
|
-
* its mid-flight universes then leak until hard expiry and disk alone will not
|
|
134
|
-
* mark them stale. The control plane reports such runs as failed — fold its
|
|
135
|
-
* answer into the attribution. Unreachable server = leave non-terminal runs
|
|
136
|
-
* alone (conservative: never kill what might still be alive).
|
|
137
|
-
*/
|
|
138
|
-
export async function reconcileAttributionWithServer(index, server) {
|
|
139
|
-
const nonTerminalRuns = new Set();
|
|
140
|
-
for (const attribution of index.bySandboxId.values()) {
|
|
141
|
-
if (!TERMINAL_RUN_STATUSES.has(attribution.runStatus))
|
|
142
|
-
nonTerminalRuns.add(attribution.runId);
|
|
143
|
-
}
|
|
144
|
-
for (const runId of nonTerminalRuns) {
|
|
145
|
-
const fetched = await fetchRunFromServer(server, runId);
|
|
146
|
-
if (fetched.outcome !== 'ok')
|
|
147
|
-
continue;
|
|
148
|
-
for (const attribution of index.bySandboxId.values()) {
|
|
149
|
-
if (attribution.runId === runId)
|
|
150
|
-
attribution.runStatus = fetched.run.status;
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
export function annotateSandboxes(sandboxes, index) {
|
|
155
|
-
return sandboxes.map(sandbox => {
|
|
156
|
-
const attribution = index?.bySandboxId.get(sandbox.sandboxId) ?? null;
|
|
157
|
-
const templateMatch = sandbox.alias === VERIFIER_TEMPLATE_ALIAS
|
|
158
|
-
|| sandbox.templateId === VERIFIER_TEMPLATE_ALIAS
|
|
159
|
-
|| (index ? index.snapshotIds.has(sandbox.templateId) : false);
|
|
160
|
-
const verifier = Boolean(attribution) || templateMatch;
|
|
161
|
-
let stale = false;
|
|
162
|
-
let staleReason = null;
|
|
163
|
-
if (attribution && index) {
|
|
164
|
-
const newest = index.newestRunByChapter.get(attribution.chapterId);
|
|
165
|
-
const runTerminal = TERMINAL_RUN_STATUSES.has(attribution.runStatus);
|
|
166
|
-
if (newest && attribution.runId !== newest) {
|
|
167
|
-
stale = true;
|
|
168
|
-
staleReason = `superseded — newest ${attribution.chapterId} run is ${newest}`;
|
|
169
|
-
}
|
|
170
|
-
else if (runTerminal && (attribution.role === 'base' || attribution.role === 'head' || attribution.role === 'golden')) {
|
|
171
|
-
stale = true;
|
|
172
|
-
staleReason = `run ${attribution.runId} is ${attribution.runStatus} and should have released this ${attribution.role} sandbox`;
|
|
173
|
-
}
|
|
174
|
-
// A kept-alive universe of the newest run is the live exploration
|
|
175
|
-
// environment — intentionally running until its hard expiry.
|
|
176
|
-
}
|
|
177
|
-
return {
|
|
178
|
-
...sandbox,
|
|
179
|
-
verifier,
|
|
180
|
-
attribution,
|
|
181
|
-
templateOnly: verifier && !attribution,
|
|
182
|
-
stale,
|
|
183
|
-
staleReason,
|
|
184
|
-
age: formatAge(sandbox.startedAt ?? undefined),
|
|
185
|
-
endsIn: sandbox.endAt ? formatUntil(sandbox.endAt) : null,
|
|
186
|
-
};
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
export function inventoryOf(annotated, attributionAvailable) {
|
|
190
|
-
return {
|
|
191
|
-
schema_version: '1.0.0',
|
|
192
|
-
total: annotated.length,
|
|
193
|
-
verifier_count: annotated.filter(s => s.verifier).length,
|
|
194
|
-
other_count: annotated.filter(s => !s.verifier).length,
|
|
195
|
-
stale_count: annotated.filter(s => s.stale).length,
|
|
196
|
-
attribution_available: attributionAvailable,
|
|
197
|
-
sandboxes: annotated.map(sandbox => ({
|
|
198
|
-
sandbox_id: sandbox.sandboxId,
|
|
199
|
-
template: sandbox.alias ?? sandbox.templateId,
|
|
200
|
-
verifier: sandbox.verifier,
|
|
201
|
-
stale: sandbox.stale,
|
|
202
|
-
stale_reason: sandbox.staleReason,
|
|
203
|
-
role: sandbox.attribution?.role ?? (sandbox.templateOnly ? 'verifier (unrecorded)' : null),
|
|
204
|
-
run_id: sandbox.attribution?.runId ?? null,
|
|
205
|
-
chapter_id: sandbox.attribution?.chapterId ?? null,
|
|
206
|
-
cell: sandbox.attribution?.cell ?? null,
|
|
207
|
-
age: sandbox.age,
|
|
208
|
-
ends: sandbox.endsIn,
|
|
209
|
-
})),
|
|
210
|
-
};
|
|
211
|
-
}
|
|
212
|
-
// ---------- server-first resolution ----------
|
|
213
|
-
//
|
|
214
|
-
// A control plane with the operator API owns the E2B account and the run
|
|
215
|
-
// records, so it both lists (with attribution computed against live
|
|
216
|
-
// orchestrator state) and kills (with the shared-account guard enforced
|
|
217
|
-
// server-side). The direct-E2B path stays as the fallback for older servers
|
|
218
|
-
// and for operating while the control plane is down.
|
|
219
|
-
export async function fetchInventoryFromServer(server) {
|
|
220
|
-
let response;
|
|
221
|
-
try {
|
|
222
|
-
response = await fetch(`${server}/api/cloud-verifier/sandboxes`, { headers: verifyAuthHeaders() });
|
|
223
|
-
}
|
|
224
|
-
catch {
|
|
225
|
-
return null;
|
|
226
|
-
}
|
|
227
|
-
if (response.status === 401)
|
|
228
|
-
throw new VerifyUnauthorizedError(server);
|
|
229
|
-
if (!response.ok)
|
|
230
|
-
return null;
|
|
231
|
-
const body = (await response.json());
|
|
232
|
-
return Array.isArray(body.sandboxes) ? body : null;
|
|
233
|
-
}
|
|
234
|
-
export async function killSandboxViaServer(server, sandboxId, force) {
|
|
235
|
-
const response = await fetch(`${server}/api/cloud-verifier/sandboxes/${encodeURIComponent(sandboxId)}/kill`, {
|
|
236
|
-
method: 'POST',
|
|
237
|
-
headers: { 'content-type': 'application/json', ...verifyAuthHeaders() },
|
|
238
|
-
body: JSON.stringify({ force }),
|
|
239
|
-
});
|
|
240
|
-
if (response.status === 401)
|
|
241
|
-
throw new VerifyUnauthorizedError(server);
|
|
242
|
-
if (response.ok)
|
|
243
|
-
return { ok: true };
|
|
244
|
-
let error = `HTTP ${response.status}`;
|
|
245
|
-
try {
|
|
246
|
-
error = (await response.json()).error ?? error;
|
|
247
|
-
}
|
|
248
|
-
catch { /* keep the status text */ }
|
|
249
|
-
return { ok: false, error };
|
|
250
|
-
}
|
|
251
|
-
/** Inventory from the control plane when it has the operator API, else direct E2B. */
|
|
252
|
-
export async function resolveInventory(options) {
|
|
253
|
-
const fromServer = await fetchInventoryFromServer(options.server);
|
|
254
|
-
if (fromServer) {
|
|
255
|
-
return {
|
|
256
|
-
inventory: fromServer,
|
|
257
|
-
via: 'server',
|
|
258
|
-
kill: (sandboxId, force) => killSandboxViaServer(options.server, sandboxId, force),
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
const apiKey = resolveE2BApiKey();
|
|
262
|
-
const index = options.repoRoot ? collectAttribution(options.repoRoot) : null;
|
|
263
|
-
if (index)
|
|
264
|
-
await reconcileAttributionWithServer(index, options.server);
|
|
265
|
-
const annotated = annotateSandboxes(await listE2BSandboxes(apiKey), index);
|
|
266
|
-
const byId = new Map(annotated.map(sandbox => [sandbox.sandboxId, sandbox]));
|
|
267
|
-
return {
|
|
268
|
-
inventory: inventoryOf(annotated, Boolean(index)),
|
|
269
|
-
via: 'e2b',
|
|
270
|
-
kill: async (sandboxId, force) => {
|
|
271
|
-
const target = byId.get(sandboxId);
|
|
272
|
-
if (target && !target.verifier && !force) {
|
|
273
|
-
return { ok: false, error: 'not attributed to the verifier and the E2B account is shared (pass force to override)' };
|
|
274
|
-
}
|
|
275
|
-
try {
|
|
276
|
-
await killE2BSandbox(apiKey, sandboxId);
|
|
277
|
-
return { ok: true };
|
|
278
|
-
}
|
|
279
|
-
catch (error) {
|
|
280
|
-
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
281
|
-
}
|
|
282
|
-
},
|
|
283
|
-
};
|
|
284
|
-
}
|
|
285
|
-
export async function verifySandboxesCommand(options) {
|
|
286
|
-
setVerifyToken(options.token);
|
|
287
|
-
const server = resolveServer(options.server);
|
|
288
|
-
const repoRoot = discoverVerifyRoot(options.repoRoot);
|
|
289
|
-
const { inventory, via, kill } = await resolveInventory({ server, repoRoot });
|
|
290
|
-
const rows = inventory.sandboxes;
|
|
291
|
-
const wantKill = Boolean(options.killStale || options.killRun);
|
|
292
|
-
if (wantKill && !inventory.attribution_available) {
|
|
293
|
-
throw new Error('Cannot kill without attribution: no run records available (run from the repository, set HAYSTACK_VERIFY_REPO, or use a control plane with the operator API). The E2B account is shared — unattributed sandboxes are never killed.');
|
|
294
|
-
}
|
|
295
|
-
let targets = [];
|
|
296
|
-
if (options.killRun) {
|
|
297
|
-
targets = rows.filter(row => row.run_id === options.killRun);
|
|
298
|
-
if (targets.length === 0 && !options.json) {
|
|
299
|
-
console.log(chalk.yellow(`No live sandboxes attributed to ${options.killRun}.`));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
else if (options.killStale) {
|
|
303
|
-
targets = rows.filter(row => row.stale);
|
|
304
|
-
}
|
|
305
|
-
const killed = [];
|
|
306
|
-
const killErrors = [];
|
|
307
|
-
for (const target of targets) {
|
|
308
|
-
const result = await kill(target.sandbox_id, false);
|
|
309
|
-
if (result.ok)
|
|
310
|
-
killed.push(target.sandbox_id);
|
|
311
|
-
else
|
|
312
|
-
killErrors.push(`${target.sandbox_id}: ${result.error}`);
|
|
313
|
-
}
|
|
314
|
-
if (options.json) {
|
|
315
|
-
process.stdout.write(`${JSON.stringify({ ...inventory, via, killed, kill_errors: killErrors }, null, 2)}\n`);
|
|
316
|
-
if (killErrors.length > 0)
|
|
317
|
-
process.exitCode = 1;
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
if (rows.length === 0) {
|
|
321
|
-
console.log(chalk.dim('No sandboxes are running on the E2B account.'));
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
console.log(chalk.bold(`${inventory.total} running sandboxes — ${inventory.verifier_count} verifier, ${inventory.other_count} other workstreams${inventory.stale_count ? chalk.yellow(`, ${inventory.stale_count} stale`) : ''} ${chalk.dim(`(via ${via === 'server' ? 'control plane' : 'E2B API'})`)}`));
|
|
325
|
-
if (!inventory.attribution_available) {
|
|
326
|
-
console.log(chalk.yellow('No run records available — attribution limited to template match; nothing can be killed.'));
|
|
327
|
-
}
|
|
328
|
-
console.log('');
|
|
329
|
-
for (const row of rows) {
|
|
330
|
-
const flag = killed.includes(row.sandbox_id) ? chalk.red('KILLED')
|
|
331
|
-
: row.stale ? chalk.yellow('stale')
|
|
332
|
-
: row.verifier ? chalk.green('verifier')
|
|
333
|
-
: chalk.dim('other');
|
|
334
|
-
const who = row.run_id
|
|
335
|
-
? `${row.run_id} ${row.role ?? ''}${row.cell ? ` · ${row.cell.slice(0, 50)}` : ''}`
|
|
336
|
-
: row.verifier ? 'verifier template, no run record' : `template ${row.template}`;
|
|
337
|
-
console.log(` ${flag.padEnd(8)} ${row.sandbox_id} ${chalk.dim(who)}`);
|
|
338
|
-
console.log(` ${chalk.dim(`up ${row.age ?? '?'}${row.ends ? ` · ends ${row.ends}` : ''}${row.stale_reason ? ` · ${row.stale_reason}` : ''}`)}`);
|
|
339
|
-
}
|
|
340
|
-
if (killed.length > 0) {
|
|
341
|
-
console.log('');
|
|
342
|
-
console.log(`${chalk.green('✓')} killed ${killed.length} sandbox(es).`);
|
|
343
|
-
}
|
|
344
|
-
for (const failure of killErrors)
|
|
345
|
-
console.error(chalk.red(`kill failed — ${failure}`));
|
|
346
|
-
if (killErrors.length > 0)
|
|
347
|
-
process.exitCode = 1;
|
|
348
|
-
if (!wantKill && inventory.stale_count > 0) {
|
|
349
|
-
console.log('');
|
|
350
|
-
console.log(chalk.dim('Reclaim capacity with: haystack verify sandboxes --kill-stale (only verifier-attributed sandboxes are ever killed).'));
|
|
351
|
-
}
|
|
352
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
export declare const VERIFY_SPECIMENS: Set<string>;
|
|
2
|
-
export interface VerifyOptions {
|
|
3
|
-
server?: string;
|
|
4
|
-
token?: string;
|
|
5
|
-
repo?: string;
|
|
6
|
-
base?: string;
|
|
7
|
-
head?: string;
|
|
8
|
-
intentTranscript?: string;
|
|
9
|
-
intentFile?: string;
|
|
10
|
-
idempotencyKey?: string;
|
|
11
|
-
account?: string;
|
|
12
|
-
wait?: boolean;
|
|
13
|
-
interval?: string;
|
|
14
|
-
timeout?: string;
|
|
15
|
-
json?: boolean;
|
|
16
|
-
open?: boolean;
|
|
17
|
-
/** Internal reproducibility option: execute an already-frozen plan. */
|
|
18
|
-
planSourceRunId?: string;
|
|
19
|
-
}
|
|
20
|
-
export interface StartRunResponse {
|
|
21
|
-
runId: string;
|
|
22
|
-
status: string;
|
|
23
|
-
resultsUrl: string;
|
|
24
|
-
}
|
|
25
|
-
export declare function parseStartRunResponse(raw: string): StartRunResponse;
|
|
26
|
-
export declare function verifierErrorReason(raw: string, contentType: string | null): string;
|
|
27
|
-
export declare function startVerificationRun(specimenArg: string | undefined, options: VerifyOptions): Promise<StartRunResponse>;
|
|
28
|
-
export declare function verifyCommand(specimenArg: string | undefined, options: VerifyOptions): Promise<void>;
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/** currentRepo() for token-account resolution. Outside a git repo there is
|
|
2
|
-
* simply no repo context — expected for id-based commands run from anywhere,
|
|
3
|
-
* so that case stays quiet. A remote that EXISTS but isn't recognized is a
|
|
4
|
-
* different story: repo-scoped account defaults silently won't apply, so say
|
|
5
|
-
* so (classified by error type per PR007). */
|
|
6
|
-
export declare function tryCurrentRepo(): string | null;
|
|
7
|
-
/** Token-resolution scope for adminFetch: explicit repo wins, else the cwd's
|
|
8
|
-
* origin remote, else no scope (active account). Exported for tests. */
|
|
9
|
-
export declare function resolveTokenScope(repoFullName?: string): {
|
|
10
|
-
owner: string;
|
|
11
|
-
repo: string;
|
|
12
|
-
} | Record<string, never>;
|
|
13
|
-
export declare function registerWebhook(opts: {
|
|
14
|
-
url: string;
|
|
15
|
-
events?: string[];
|
|
16
|
-
secretEnv?: string;
|
|
17
|
-
repo?: string;
|
|
18
|
-
}): Promise<void>;
|
|
19
|
-
export declare function listWebhooks(opts: {
|
|
20
|
-
repo?: string;
|
|
21
|
-
json?: boolean;
|
|
22
|
-
}): Promise<void>;
|
|
23
|
-
export declare function rotateWebhookSecret(id: string, repo?: string): Promise<void>;
|
|
24
|
-
export declare function setWebhookEnabled(id: string, enabled: boolean, repo?: string): Promise<void>;
|
|
25
|
-
export declare function listDeliveries(opts: {
|
|
26
|
-
repo?: string;
|
|
27
|
-
limit?: number;
|
|
28
|
-
json?: boolean;
|
|
29
|
-
}): Promise<void>;
|
|
30
|
-
export declare function replayDelivery(id: string, repo?: string): Promise<void>;
|
package/dist/index.d.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Haystack CLI
|
|
4
|
-
*
|
|
5
|
-
* Set up your project for Haystack.
|
|
6
|
-
* Automated PR review, triage, and merge queue for AI-assisted development.
|
|
7
|
-
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* npx @haystackeditor/cli init # Set up .haystack.json
|
|
10
|
-
* npx @haystackeditor/cli setup # Interactive onboarding wizard
|
|
11
|
-
* npx @haystackeditor/cli status # Check configuration
|
|
12
|
-
* npx @haystackeditor/cli login # Authenticate with GitHub
|
|
13
|
-
* npx @haystackeditor/cli submit # Create a PR (auto-merge or review)
|
|
14
|
-
* npx @haystackeditor/cli triage # Check last submitted PR
|
|
15
|
-
* npx @haystackeditor/cli triage 123 # View analysis results for a PR
|
|
16
|
-
* npx @haystackeditor/cli dismiss 123 # Dismiss findings for a PR
|
|
17
|
-
* npx @haystackeditor/cli mark-reviewed 123 # Mark review as not needed
|
|
18
|
-
* npx @haystackeditor/cli request-review 123 # Tag a PR as needing human review
|
|
19
|
-
* npx @haystackeditor/cli pr-status 123 # Show PR status in Haystack pipeline
|
|
20
|
-
* npx @haystackeditor/cli config # Manage preferences
|
|
21
|
-
*/
|
|
22
|
-
export {};
|