@delegance/claude-autopilot 5.5.2 → 7.2.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 +1776 -6
- package/README.md +65 -1
- package/bin/_launcher.js +38 -23
- package/dist/src/adapters/council/openai.js +12 -6
- package/dist/src/adapters/deploy/_http.d.ts +43 -0
- package/dist/src/adapters/deploy/_http.js +99 -0
- package/dist/src/adapters/deploy/fly.d.ts +206 -0
- package/dist/src/adapters/deploy/fly.js +696 -0
- package/dist/src/adapters/deploy/index.d.ts +2 -0
- package/dist/src/adapters/deploy/index.js +33 -0
- package/dist/src/adapters/deploy/render.d.ts +181 -0
- package/dist/src/adapters/deploy/render.js +550 -0
- package/dist/src/adapters/deploy/types.d.ts +67 -3
- package/dist/src/adapters/deploy/vercel.d.ts +17 -1
- package/dist/src/adapters/deploy/vercel.js +29 -49
- package/dist/src/adapters/pricing.d.ts +36 -0
- package/dist/src/adapters/pricing.js +40 -0
- package/dist/src/adapters/review-engine/codex.js +10 -7
- package/dist/src/cli/autopilot.d.ts +75 -0
- package/dist/src/cli/autopilot.js +750 -0
- package/dist/src/cli/brainstorm.d.ts +23 -0
- package/dist/src/cli/brainstorm.js +131 -0
- package/dist/src/cli/costs.d.ts +15 -1
- package/dist/src/cli/costs.js +99 -10
- package/dist/src/cli/dashboard/index.d.ts +5 -0
- package/dist/src/cli/dashboard/index.js +49 -0
- package/dist/src/cli/dashboard/login.d.ts +22 -0
- package/dist/src/cli/dashboard/login.js +260 -0
- package/dist/src/cli/dashboard/logout.d.ts +12 -0
- package/dist/src/cli/dashboard/logout.js +45 -0
- package/dist/src/cli/dashboard/status.d.ts +30 -0
- package/dist/src/cli/dashboard/status.js +65 -0
- package/dist/src/cli/dashboard/upload.d.ts +16 -0
- package/dist/src/cli/dashboard/upload.js +48 -0
- package/dist/src/cli/deploy.d.ts +3 -3
- package/dist/src/cli/deploy.js +34 -9
- package/dist/src/cli/engine-flag-deprecation.d.ts +14 -0
- package/dist/src/cli/engine-flag-deprecation.js +20 -0
- package/dist/src/cli/fix.d.ts +18 -0
- package/dist/src/cli/fix.js +105 -11
- package/dist/src/cli/help-text.d.ts +52 -0
- package/dist/src/cli/help-text.js +416 -0
- package/dist/src/cli/implement.d.ts +91 -0
- package/dist/src/cli/implement.js +196 -0
- package/dist/src/cli/index.d.ts +2 -1
- package/dist/src/cli/index.js +774 -245
- package/dist/src/cli/json-envelope.d.ts +187 -0
- package/dist/src/cli/json-envelope.js +270 -0
- package/dist/src/cli/json-mode.d.ts +33 -0
- package/dist/src/cli/json-mode.js +201 -0
- package/dist/src/cli/migrate.d.ts +111 -0
- package/dist/src/cli/migrate.js +305 -0
- package/dist/src/cli/plan.d.ts +81 -0
- package/dist/src/cli/plan.js +149 -0
- package/dist/src/cli/pr.d.ts +106 -0
- package/dist/src/cli/pr.js +191 -19
- package/dist/src/cli/preflight.js +26 -0
- package/dist/src/cli/review.d.ts +27 -0
- package/dist/src/cli/review.js +126 -0
- package/dist/src/cli/runs-watch-renderer.d.ts +45 -0
- package/dist/src/cli/runs-watch-renderer.js +275 -0
- package/dist/src/cli/runs-watch.d.ts +41 -0
- package/dist/src/cli/runs-watch.js +395 -0
- package/dist/src/cli/runs.d.ts +122 -0
- package/dist/src/cli/runs.js +902 -0
- package/dist/src/cli/scaffold.d.ts +39 -0
- package/dist/src/cli/scaffold.js +287 -0
- package/dist/src/cli/scan.d.ts +93 -0
- package/dist/src/cli/scan.js +166 -40
- package/dist/src/cli/setup.d.ts +30 -0
- package/dist/src/cli/setup.js +137 -0
- package/dist/src/cli/spec.d.ts +66 -0
- package/dist/src/cli/spec.js +132 -0
- package/dist/src/cli/validate.d.ts +29 -0
- package/dist/src/cli/validate.js +131 -0
- package/dist/src/core/config/schema.d.ts +9 -0
- package/dist/src/core/config/schema.js +7 -0
- package/dist/src/core/config/types.d.ts +11 -0
- package/dist/src/core/council/runner.d.ts +10 -1
- package/dist/src/core/council/runner.js +25 -3
- package/dist/src/core/council/types.d.ts +7 -0
- package/dist/src/core/errors.d.ts +1 -1
- package/dist/src/core/errors.js +11 -0
- package/dist/src/core/logging/redaction.d.ts +13 -0
- package/dist/src/core/logging/redaction.js +20 -0
- package/dist/src/core/migrate/schema-validator.js +15 -1
- package/dist/src/core/phases/static-rules.d.ts +5 -1
- package/dist/src/core/phases/static-rules.js +2 -5
- package/dist/src/core/run-state/budget.d.ts +88 -0
- package/dist/src/core/run-state/budget.js +141 -0
- package/dist/src/core/run-state/cli-internal.d.ts +21 -0
- package/dist/src/core/run-state/cli-internal.js +174 -0
- package/dist/src/core/run-state/events.d.ts +59 -0
- package/dist/src/core/run-state/events.js +512 -0
- package/dist/src/core/run-state/lock.d.ts +61 -0
- package/dist/src/core/run-state/lock.js +206 -0
- package/dist/src/core/run-state/phase-context.d.ts +60 -0
- package/dist/src/core/run-state/phase-context.js +108 -0
- package/dist/src/core/run-state/phase-registry.d.ts +137 -0
- package/dist/src/core/run-state/phase-registry.js +162 -0
- package/dist/src/core/run-state/phase-runner.d.ts +80 -0
- package/dist/src/core/run-state/phase-runner.js +447 -0
- package/dist/src/core/run-state/provider-readback.d.ts +130 -0
- package/dist/src/core/run-state/provider-readback.js +426 -0
- package/dist/src/core/run-state/replay-decision.d.ts +69 -0
- package/dist/src/core/run-state/replay-decision.js +144 -0
- package/dist/src/core/run-state/resolve-engine.d.ts +45 -0
- package/dist/src/core/run-state/resolve-engine.js +74 -0
- package/dist/src/core/run-state/resume-preflight.d.ts +66 -0
- package/dist/src/core/run-state/resume-preflight.js +116 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.d.ts +69 -0
- package/dist/src/core/run-state/run-phase-with-lifecycle.js +193 -0
- package/dist/src/core/run-state/runs.d.ts +57 -0
- package/dist/src/core/run-state/runs.js +288 -0
- package/dist/src/core/run-state/snapshot.d.ts +14 -0
- package/dist/src/core/run-state/snapshot.js +114 -0
- package/dist/src/core/run-state/state.d.ts +40 -0
- package/dist/src/core/run-state/state.js +164 -0
- package/dist/src/core/run-state/types.d.ts +284 -0
- package/dist/src/core/run-state/types.js +19 -0
- package/dist/src/core/run-state/ulid.d.ts +11 -0
- package/dist/src/core/run-state/ulid.js +95 -0
- package/dist/src/core/schema-alignment/extractor/index.d.ts +1 -1
- package/dist/src/core/schema-alignment/extractor/index.js +2 -2
- package/dist/src/core/schema-alignment/extractor/prisma.d.ts +13 -1
- package/dist/src/core/schema-alignment/extractor/prisma.js +65 -10
- package/dist/src/core/schema-alignment/git-history.d.ts +19 -0
- package/dist/src/core/schema-alignment/git-history.js +53 -0
- package/dist/src/core/static-rules/rules/brand-tokens.js +2 -2
- package/dist/src/core/static-rules/rules/schema-alignment.js +14 -4
- package/dist/src/dashboard/auto-upload.d.ts +26 -0
- package/dist/src/dashboard/auto-upload.js +107 -0
- package/dist/src/dashboard/config.d.ts +22 -0
- package/dist/src/dashboard/config.js +109 -0
- package/dist/src/dashboard/upload/canonical.d.ts +3 -0
- package/dist/src/dashboard/upload/canonical.js +16 -0
- package/dist/src/dashboard/upload/chain.d.ts +9 -0
- package/dist/src/dashboard/upload/chain.js +27 -0
- package/dist/src/dashboard/upload/snapshot.d.ts +23 -0
- package/dist/src/dashboard/upload/snapshot.js +66 -0
- package/dist/src/dashboard/upload/uploader.d.ts +54 -0
- package/dist/src/dashboard/upload/uploader.js +330 -0
- package/package.json +19 -3
- package/scripts/autoregress.ts +1 -1
- package/scripts/test-runner.mjs +4 -0
- package/skills/claude-autopilot.md +1 -1
- package/skills/make-interfaces-feel-better/SKILL.md +104 -0
- package/skills/simplify-ui/SKILL.md +103 -0
- package/skills/ui/SKILL.md +117 -0
- package/skills/ui-ux-pro-max/SKILL.md +90 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface AutoUploadOptions {
|
|
2
|
+
/** Caller's explicit opt-out (e.g. CLI --no-upload). */
|
|
3
|
+
disabled?: boolean;
|
|
4
|
+
/** Test seam — substitute fetch impl. */
|
|
5
|
+
fetchImpl?: typeof fetch;
|
|
6
|
+
/** Test seam — silence stdout. */
|
|
7
|
+
silent?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export interface AutoUploadResult {
|
|
10
|
+
attempted: boolean;
|
|
11
|
+
ok: boolean;
|
|
12
|
+
url: string | null;
|
|
13
|
+
skipped: boolean;
|
|
14
|
+
reason?: 'opt-out-flag' | 'env-off' | 'not-logged-in' | 'no-events' | 'aborted' | 'error' | 'limit-reached';
|
|
15
|
+
}
|
|
16
|
+
export declare function shouldAutoUpload(options?: AutoUploadOptions): {
|
|
17
|
+
ok: boolean;
|
|
18
|
+
reason?: AutoUploadResult['reason'];
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Run an auto-upload for the given runId. Wraps the foreground uploader
|
|
22
|
+
* in a SIGINT/SIGTERM handler so Ctrl-C is clean. Always returns a result
|
|
23
|
+
* — never throws. Caller preserves the run's original exit code.
|
|
24
|
+
*/
|
|
25
|
+
export declare function autoUploadAtComplete(runId: string, runDir: string, options?: AutoUploadOptions): Promise<AutoUploadResult>;
|
|
26
|
+
//# sourceMappingURL=auto-upload.d.ts.map
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// Auto-upload at run.complete — non-fatal hosted-product hook.
|
|
2
|
+
//
|
|
3
|
+
// Contract (per spec): never fails the run. Always preserves the original
|
|
4
|
+
// exit code. Failure prints a resume command. Empty events.ndjson skips
|
|
5
|
+
// upload cleanly (Phase 2.2's POST /api/upload-session 422s expectedChunkCount=0).
|
|
6
|
+
//
|
|
7
|
+
// Opt-outs:
|
|
8
|
+
// - explicit `--no-upload` flag → caller passes options.disabled=true
|
|
9
|
+
// - env CLAUDE_AUTOPILOT_UPLOAD=off
|
|
10
|
+
// - not logged in (no config)
|
|
11
|
+
// - events.ndjson missing or 0 bytes
|
|
12
|
+
import { promises as fs } from 'node:fs';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { readConfig } from "./config.js";
|
|
15
|
+
import { uploadRun, UploadLimitError } from "./upload/uploader.js";
|
|
16
|
+
export function shouldAutoUpload(options = {}) {
|
|
17
|
+
if (options.disabled)
|
|
18
|
+
return { ok: false, reason: 'opt-out-flag' };
|
|
19
|
+
const env = process.env.CLAUDE_AUTOPILOT_UPLOAD;
|
|
20
|
+
if (env && /^(off|false|0|no)$/i.test(env))
|
|
21
|
+
return { ok: false, reason: 'env-off' };
|
|
22
|
+
return { ok: true };
|
|
23
|
+
}
|
|
24
|
+
async function fileExistsNonEmpty(p) {
|
|
25
|
+
try {
|
|
26
|
+
const stat = await fs.stat(p);
|
|
27
|
+
return stat.isFile() && stat.size > 0;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run an auto-upload for the given runId. Wraps the foreground uploader
|
|
35
|
+
* in a SIGINT/SIGTERM handler so Ctrl-C is clean. Always returns a result
|
|
36
|
+
* — never throws. Caller preserves the run's original exit code.
|
|
37
|
+
*/
|
|
38
|
+
export async function autoUploadAtComplete(runId, runDir, options = {}) {
|
|
39
|
+
const gate = shouldAutoUpload(options);
|
|
40
|
+
if (!gate.ok) {
|
|
41
|
+
return { attempted: false, ok: true, url: null, skipped: true, reason: gate.reason ?? 'opt-out-flag' };
|
|
42
|
+
}
|
|
43
|
+
const cfg = await readConfig();
|
|
44
|
+
if (!cfg) {
|
|
45
|
+
return { attempted: false, ok: true, url: null, skipped: true, reason: 'not-logged-in' };
|
|
46
|
+
}
|
|
47
|
+
const eventsPath = path.join(runDir, 'events.ndjson');
|
|
48
|
+
const hasEvents = await fileExistsNonEmpty(eventsPath);
|
|
49
|
+
if (!hasEvents) {
|
|
50
|
+
return { attempted: false, ok: true, url: null, skipped: true, reason: 'no-events' };
|
|
51
|
+
}
|
|
52
|
+
const ac = new AbortController();
|
|
53
|
+
const sigintHandler = () => ac.abort();
|
|
54
|
+
process.on('SIGINT', sigintHandler);
|
|
55
|
+
process.on('SIGTERM', sigintHandler);
|
|
56
|
+
try {
|
|
57
|
+
const result = await uploadRun(runId, runDir, {
|
|
58
|
+
apiKey: cfg.apiKey,
|
|
59
|
+
signal: ac.signal,
|
|
60
|
+
...(options.fetchImpl !== undefined ? { fetchImpl: options.fetchImpl } : {}),
|
|
61
|
+
});
|
|
62
|
+
if (result.ok && result.url) {
|
|
63
|
+
if (!options.silent)
|
|
64
|
+
process.stdout.write(`[autopilot] uploaded to ${result.url}\n`);
|
|
65
|
+
return { attempted: true, ok: true, url: result.url, skipped: false };
|
|
66
|
+
}
|
|
67
|
+
if (result.ok && result.skipped) {
|
|
68
|
+
if (!options.silent)
|
|
69
|
+
process.stdout.write(`[autopilot] skipping upload — events.ndjson is empty\n`);
|
|
70
|
+
return { attempted: true, ok: true, url: null, skipped: true, reason: 'no-events' };
|
|
71
|
+
}
|
|
72
|
+
if (!options.silent) {
|
|
73
|
+
process.stderr.write(`[autopilot] upload failed: ${result.error}\n`);
|
|
74
|
+
process.stderr.write(` Resume with: claude-autopilot dashboard upload ${runId}\n`);
|
|
75
|
+
}
|
|
76
|
+
return { attempted: true, ok: false, url: null, skipped: false, reason: 'error' };
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
if (ac.signal.aborted) {
|
|
80
|
+
if (!options.silent) {
|
|
81
|
+
process.stderr.write(`\n[autopilot] upload interrupted. Run is saved locally.\n`);
|
|
82
|
+
process.stderr.write(` Resume with: claude-autopilot dashboard upload ${runId}\n`);
|
|
83
|
+
}
|
|
84
|
+
return { attempted: true, ok: false, url: null, skipped: false, reason: 'aborted' };
|
|
85
|
+
}
|
|
86
|
+
// Phase 3 — runs/storage cap reached. Print a friendly message but
|
|
87
|
+
// do NOT print the resume hint (resume would just hit 402 again until
|
|
88
|
+
// the user upgrades) and do NOT bubble the error so the run's exit
|
|
89
|
+
// code is preserved.
|
|
90
|
+
if (err instanceof UploadLimitError) {
|
|
91
|
+
if (!options.silent) {
|
|
92
|
+
process.stderr.write(`[autopilot] ${err.message}\n`);
|
|
93
|
+
}
|
|
94
|
+
return { attempted: true, ok: false, url: null, skipped: false, reason: 'limit-reached' };
|
|
95
|
+
}
|
|
96
|
+
if (!options.silent) {
|
|
97
|
+
process.stderr.write(`[autopilot] upload error: ${err.message}\n`);
|
|
98
|
+
process.stderr.write(` Resume with: claude-autopilot dashboard upload ${runId}\n`);
|
|
99
|
+
}
|
|
100
|
+
return { attempted: true, ok: false, url: null, skipped: false, reason: 'error' };
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
process.off('SIGINT', sigintHandler);
|
|
104
|
+
process.off('SIGTERM', sigintHandler);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=auto-upload.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface DashboardConfig {
|
|
2
|
+
schemaVersion: 1;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
fingerprint: string;
|
|
5
|
+
accountEmail: string;
|
|
6
|
+
loggedInAt: string;
|
|
7
|
+
lastUploadAt: string | null;
|
|
8
|
+
}
|
|
9
|
+
export declare function getConfigDir(): string;
|
|
10
|
+
export declare function getConfigPath(): string;
|
|
11
|
+
export declare function readConfig(): Promise<DashboardConfig | null>;
|
|
12
|
+
export declare function writeConfig(config: DashboardConfig): Promise<void>;
|
|
13
|
+
export declare function deleteConfig(): Promise<void>;
|
|
14
|
+
export declare function getAutopilotBaseUrl(): string;
|
|
15
|
+
export declare function _resetAutopilotBaseUrlWarning(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Returns a warning string if the config file is group/world-readable on
|
|
18
|
+
* a POSIX filesystem; null otherwise (or on Windows, where mode bits
|
|
19
|
+
* don't apply meaningfully).
|
|
20
|
+
*/
|
|
21
|
+
export declare function warnIfPermissive(): Promise<string | null>;
|
|
22
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// CLI dashboard config — atomic read/write of ~/.claude-autopilot/dashboard.json.
|
|
2
|
+
//
|
|
3
|
+
// Codex plan-pass WARNING: respect CLAUDE_AUTOPILOT_HOME env override so
|
|
4
|
+
// tests + experimentation never touch the developer's real home dir.
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as os from 'node:os';
|
|
8
|
+
const KEY_RE = /^clp_[0-9a-f]{64}$/;
|
|
9
|
+
function resolveHome() {
|
|
10
|
+
return process.env.CLAUDE_AUTOPILOT_HOME ?? path.join(os.homedir(), '.claude-autopilot');
|
|
11
|
+
}
|
|
12
|
+
export function getConfigDir() {
|
|
13
|
+
return resolveHome();
|
|
14
|
+
}
|
|
15
|
+
export function getConfigPath() {
|
|
16
|
+
return path.join(resolveHome(), 'dashboard.json');
|
|
17
|
+
}
|
|
18
|
+
export async function readConfig() {
|
|
19
|
+
try {
|
|
20
|
+
const raw = await fs.readFile(getConfigPath(), 'utf-8');
|
|
21
|
+
const parsed = JSON.parse(raw);
|
|
22
|
+
if (parsed.schemaVersion !== 1)
|
|
23
|
+
return null;
|
|
24
|
+
if (!KEY_RE.test(parsed.apiKey))
|
|
25
|
+
return null;
|
|
26
|
+
return parsed;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function writeConfig(config) {
|
|
33
|
+
if (!KEY_RE.test(config.apiKey)) {
|
|
34
|
+
throw new Error('writeConfig: invalid apiKey shape');
|
|
35
|
+
}
|
|
36
|
+
const dir = getConfigDir();
|
|
37
|
+
const file = getConfigPath();
|
|
38
|
+
await fs.mkdir(dir, { recursive: true, mode: 0o700 });
|
|
39
|
+
// Try to tighten dir mode even if it already existed.
|
|
40
|
+
try {
|
|
41
|
+
await fs.chmod(dir, 0o700);
|
|
42
|
+
}
|
|
43
|
+
catch { /* best effort */ }
|
|
44
|
+
// Atomic write: temp-file + rename.
|
|
45
|
+
const tmp = `${file}.tmp.${process.pid}.${Date.now()}`;
|
|
46
|
+
const payload = JSON.stringify(config, null, 2);
|
|
47
|
+
await fs.writeFile(tmp, payload, { mode: 0o600 });
|
|
48
|
+
await fs.chmod(tmp, 0o600);
|
|
49
|
+
await fs.rename(tmp, file);
|
|
50
|
+
}
|
|
51
|
+
export async function deleteConfig() {
|
|
52
|
+
try {
|
|
53
|
+
await fs.unlink(getConfigPath());
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* idempotent */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Phase 4 — resolve the dashboard / public base URL from env, with
|
|
61
|
+
* AUTOPILOT_PUBLIC_BASE_URL preferred and AUTOPILOT_DASHBOARD_BASE_URL
|
|
62
|
+
* accepted as a deprecated alias. Logs a one-time deprecation warning
|
|
63
|
+
* when only the older variable is set.
|
|
64
|
+
*
|
|
65
|
+
* Defaults to https://autopilot.dev when neither is present.
|
|
66
|
+
*/
|
|
67
|
+
let _deprecationWarned = false;
|
|
68
|
+
export function getAutopilotBaseUrl() {
|
|
69
|
+
const canonical = process.env.AUTOPILOT_PUBLIC_BASE_URL;
|
|
70
|
+
const legacy = process.env.AUTOPILOT_DASHBOARD_BASE_URL;
|
|
71
|
+
if (canonical)
|
|
72
|
+
return canonical;
|
|
73
|
+
if (legacy) {
|
|
74
|
+
if (!_deprecationWarned) {
|
|
75
|
+
_deprecationWarned = true;
|
|
76
|
+
// eslint-disable-next-line no-console
|
|
77
|
+
console.warn('[autopilot] AUTOPILOT_DASHBOARD_BASE_URL is deprecated; ' +
|
|
78
|
+
'use AUTOPILOT_PUBLIC_BASE_URL instead. Both are accepted for now.');
|
|
79
|
+
}
|
|
80
|
+
return legacy;
|
|
81
|
+
}
|
|
82
|
+
return 'https://autopilot.dev';
|
|
83
|
+
}
|
|
84
|
+
// Test seam — reset the one-shot warning flag.
|
|
85
|
+
export function _resetAutopilotBaseUrlWarning() {
|
|
86
|
+
_deprecationWarned = false;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Returns a warning string if the config file is group/world-readable on
|
|
90
|
+
* a POSIX filesystem; null otherwise (or on Windows, where mode bits
|
|
91
|
+
* don't apply meaningfully).
|
|
92
|
+
*/
|
|
93
|
+
export async function warnIfPermissive() {
|
|
94
|
+
if (process.platform === 'win32')
|
|
95
|
+
return null;
|
|
96
|
+
const file = getConfigPath();
|
|
97
|
+
try {
|
|
98
|
+
const stat = await fs.stat(file);
|
|
99
|
+
const mode = stat.mode & 0o777;
|
|
100
|
+
if ((mode & 0o077) !== 0) {
|
|
101
|
+
return `Warning: ${file} mode is ${mode.toString(8)} (group/world readable). Run: chmod 600 ${file}`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
/* file doesn't exist, no warning */
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Parity copy of apps/web/lib/upload/canonical.ts (RFC 8785 / JCS).
|
|
2
|
+
// CLI ↔ web byte-equality asserted in tests/dashboard/parity.test.ts.
|
|
3
|
+
import canonicalize from 'canonicalize';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
export function canonicalJsonBytes(value) {
|
|
6
|
+
// canonicalize implements RFC 8785 (JCS). Returns undefined only for
|
|
7
|
+
// inputs JSON cannot represent at the root; coerce to '' so callers
|
|
8
|
+
// always get a Buffer.
|
|
9
|
+
const str = canonicalize(value) ?? '';
|
|
10
|
+
return Buffer.from(str, 'utf-8');
|
|
11
|
+
}
|
|
12
|
+
export function sha256OfCanonical(value) {
|
|
13
|
+
const bytes = canonicalJsonBytes(value);
|
|
14
|
+
return createHash('sha256').update(bytes).digest('hex');
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const ZERO_HASH: string;
|
|
2
|
+
export declare function hashChunk(prevHashHex: string, body: Buffer): string;
|
|
3
|
+
/**
|
|
4
|
+
* Compute the chain root for an ordered sequence of chunks.
|
|
5
|
+
* Unambiguous loop form (per spec): prev=ZERO_HASH; for seq in 0..N-1:
|
|
6
|
+
* h[seq] = sha256(chunk[seq] || prev); prev = h[seq]; root = prev.
|
|
7
|
+
*/
|
|
8
|
+
export declare function computeChainRoot(chunks: Buffer[]): string;
|
|
9
|
+
//# sourceMappingURL=chain.d.ts.map
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// Parity copy of apps/web/lib/upload/chain.ts.
|
|
2
|
+
// CLI ↔ web hash agreement is asserted in tests/dashboard/parity.test.ts.
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
4
|
+
export const ZERO_HASH = '0'.repeat(64);
|
|
5
|
+
export function hashChunk(prevHashHex, body) {
|
|
6
|
+
if (!/^[0-9a-f]{64}$/.test(prevHashHex)) {
|
|
7
|
+
throw new Error(`hashChunk: prev hash must be 64 lowercase hex chars`);
|
|
8
|
+
}
|
|
9
|
+
const prevBytes = Buffer.from(prevHashHex, 'hex');
|
|
10
|
+
const hash = createHash('sha256');
|
|
11
|
+
hash.update(body);
|
|
12
|
+
hash.update(prevBytes);
|
|
13
|
+
return hash.digest('hex');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Compute the chain root for an ordered sequence of chunks.
|
|
17
|
+
* Unambiguous loop form (per spec): prev=ZERO_HASH; for seq in 0..N-1:
|
|
18
|
+
* h[seq] = sha256(chunk[seq] || prev); prev = h[seq]; root = prev.
|
|
19
|
+
*/
|
|
20
|
+
export function computeChainRoot(chunks) {
|
|
21
|
+
let prev = ZERO_HASH;
|
|
22
|
+
for (const chunk of chunks) {
|
|
23
|
+
prev = hashChunk(prev, chunk);
|
|
24
|
+
}
|
|
25
|
+
return prev;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=chain.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export interface SnapshotPaths {
|
|
2
|
+
snapshotDir: string;
|
|
3
|
+
events: string;
|
|
4
|
+
state: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SnapshotResult extends SnapshotPaths {
|
|
7
|
+
/** Bytes of the snapshot events file. May be 0. */
|
|
8
|
+
eventsBytes: number;
|
|
9
|
+
}
|
|
10
|
+
export declare class SnapshotMismatchError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Copy events.ndjson + state.json from `runDir` to `runDir/.upload-snapshot/`.
|
|
15
|
+
*
|
|
16
|
+
* Defense in depth: stat-before/stat-after on the source files, fail
|
|
17
|
+
* loudly if size or mtime changes during copy. Per spec, snapshot is
|
|
18
|
+
* post-`run.complete` only (writers are flushed) so this should never
|
|
19
|
+
* fire — but if it does, abort rather than upload a torn read.
|
|
20
|
+
*/
|
|
21
|
+
export declare function snapshotRun(runDir: string): Promise<SnapshotResult>;
|
|
22
|
+
export declare function deleteSnapshot(runDir: string): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=snapshot.d.ts.map
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Snapshot-before-upload — copy events.ndjson + state.json to
|
|
2
|
+
// <runDir>/.upload-snapshot/ atomically before chunking begins.
|
|
3
|
+
// The uploader then reads only the snapshot, so the writer streaming new
|
|
4
|
+
// events into events.ndjson can't shift bytes mid-upload.
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
export class SnapshotMismatchError extends Error {
|
|
8
|
+
constructor(message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'SnapshotMismatchError';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Copy events.ndjson + state.json from `runDir` to `runDir/.upload-snapshot/`.
|
|
15
|
+
*
|
|
16
|
+
* Defense in depth: stat-before/stat-after on the source files, fail
|
|
17
|
+
* loudly if size or mtime changes during copy. Per spec, snapshot is
|
|
18
|
+
* post-`run.complete` only (writers are flushed) so this should never
|
|
19
|
+
* fire — but if it does, abort rather than upload a torn read.
|
|
20
|
+
*/
|
|
21
|
+
export async function snapshotRun(runDir) {
|
|
22
|
+
const eventsSrc = path.join(runDir, 'events.ndjson');
|
|
23
|
+
const stateSrc = path.join(runDir, 'state.json');
|
|
24
|
+
const snapshotDir = path.join(runDir, '.upload-snapshot');
|
|
25
|
+
const eventsDst = path.join(snapshotDir, 'events.ndjson');
|
|
26
|
+
const stateDst = path.join(snapshotDir, 'state.json');
|
|
27
|
+
const eventsBefore = await fs.stat(eventsSrc);
|
|
28
|
+
let stateBefore = null;
|
|
29
|
+
try {
|
|
30
|
+
stateBefore = await fs.stat(stateSrc);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// state.json may not exist for an in-flight run; we still snapshot events.
|
|
34
|
+
}
|
|
35
|
+
await fs.mkdir(snapshotDir, { recursive: true });
|
|
36
|
+
await fs.copyFile(eventsSrc, eventsDst);
|
|
37
|
+
if (stateBefore) {
|
|
38
|
+
await fs.copyFile(stateSrc, stateDst);
|
|
39
|
+
}
|
|
40
|
+
const eventsAfter = await fs.stat(eventsSrc);
|
|
41
|
+
if (eventsAfter.size !== eventsBefore.size || eventsAfter.mtimeMs !== eventsBefore.mtimeMs) {
|
|
42
|
+
throw new SnapshotMismatchError(`events.ndjson changed during snapshot (size ${eventsBefore.size}->${eventsAfter.size}, mtime ${eventsBefore.mtimeMs}->${eventsAfter.mtimeMs})`);
|
|
43
|
+
}
|
|
44
|
+
if (stateBefore) {
|
|
45
|
+
const stateAfter = await fs.stat(stateSrc);
|
|
46
|
+
if (stateAfter.size !== stateBefore.size || stateAfter.mtimeMs !== stateBefore.mtimeMs) {
|
|
47
|
+
throw new SnapshotMismatchError(`state.json changed during snapshot (size ${stateBefore.size}->${stateAfter.size})`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
snapshotDir,
|
|
52
|
+
events: eventsDst,
|
|
53
|
+
state: stateDst,
|
|
54
|
+
eventsBytes: eventsAfter.size,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export async function deleteSnapshot(runDir) {
|
|
58
|
+
const snapshotDir = path.join(runDir, '.upload-snapshot');
|
|
59
|
+
try {
|
|
60
|
+
await fs.rm(snapshotDir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* idempotent */
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=snapshot.js.map
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface UploadOptions {
|
|
2
|
+
signal?: AbortSignal;
|
|
3
|
+
baseUrl?: string;
|
|
4
|
+
apiKey: string;
|
|
5
|
+
onProgress?: (event: ProgressEvent) => void;
|
|
6
|
+
/** Test seam — substitute fetch impl. Defaults to global fetch. */
|
|
7
|
+
fetchImpl?: typeof fetch;
|
|
8
|
+
}
|
|
9
|
+
export type ProgressEvent = {
|
|
10
|
+
kind: 'snapshot';
|
|
11
|
+
bytes: number;
|
|
12
|
+
} | {
|
|
13
|
+
kind: 'session';
|
|
14
|
+
resumed: boolean;
|
|
15
|
+
nextExpectedSeq: number;
|
|
16
|
+
} | {
|
|
17
|
+
kind: 'chunk-uploaded';
|
|
18
|
+
seq: number;
|
|
19
|
+
total: number;
|
|
20
|
+
} | {
|
|
21
|
+
kind: 'finalized';
|
|
22
|
+
};
|
|
23
|
+
export interface UploadResult {
|
|
24
|
+
ok: boolean;
|
|
25
|
+
url?: string;
|
|
26
|
+
skipped?: boolean;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class UploadError extends Error {
|
|
30
|
+
readonly status: number | null;
|
|
31
|
+
constructor(message: string, status?: number | null);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Phase 3 — thrown when /api/upload-session returns 402 with a structured
|
|
35
|
+
* `limit_reached` payload. Auto-upload entry point detects this subclass
|
|
36
|
+
* and prints a friendly message without retrying or overriding the run's
|
|
37
|
+
* exit code.
|
|
38
|
+
*/
|
|
39
|
+
export declare class UploadLimitError extends UploadError {
|
|
40
|
+
readonly payload: {
|
|
41
|
+
limit: string;
|
|
42
|
+
current: number;
|
|
43
|
+
max: number;
|
|
44
|
+
upgrade_url: string;
|
|
45
|
+
};
|
|
46
|
+
constructor(message: string, payload: {
|
|
47
|
+
limit: string;
|
|
48
|
+
current: number;
|
|
49
|
+
max: number;
|
|
50
|
+
upgrade_url: string;
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
export declare function uploadRun(runId: string, runDir: string, opts: UploadOptions): Promise<UploadResult>;
|
|
54
|
+
//# sourceMappingURL=uploader.d.ts.map
|