@heyditto/cli 2.4.1 → 2.5.1

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.
Files changed (49) hide show
  1. package/README.md +93 -0
  2. package/dist/agents/launch.d.ts +6 -0
  3. package/dist/agents/launch.js +13 -1
  4. package/dist/agents/launch.js.map +1 -1
  5. package/dist/cli.js +2 -0
  6. package/dist/cli.js.map +1 -1
  7. package/dist/teleport/api.d.ts +139 -0
  8. package/dist/teleport/api.js +61 -0
  9. package/dist/teleport/api.js.map +1 -0
  10. package/dist/teleport/bundle.d.ts +35 -0
  11. package/dist/teleport/bundle.js +102 -0
  12. package/dist/teleport/bundle.js.map +1 -0
  13. package/dist/teleport/chunks.d.ts +13 -0
  14. package/dist/teleport/chunks.js +57 -0
  15. package/dist/teleport/chunks.js.map +1 -0
  16. package/dist/teleport/commands.d.ts +76 -0
  17. package/dist/teleport/commands.js +492 -0
  18. package/dist/teleport/commands.js.map +1 -0
  19. package/dist/teleport/discover.d.ts +13 -0
  20. package/dist/teleport/discover.js +64 -0
  21. package/dist/teleport/discover.js.map +1 -0
  22. package/dist/teleport/git.d.ts +18 -0
  23. package/dist/teleport/git.js +41 -0
  24. package/dist/teleport/git.js.map +1 -0
  25. package/dist/teleport/harness.d.ts +29 -0
  26. package/dist/teleport/harness.js +121 -0
  27. package/dist/teleport/harness.js.map +1 -0
  28. package/dist/teleport/offload.d.ts +32 -0
  29. package/dist/teleport/offload.js +83 -0
  30. package/dist/teleport/offload.js.map +1 -0
  31. package/dist/teleport/pull.d.ts +12 -0
  32. package/dist/teleport/pull.js +118 -0
  33. package/dist/teleport/pull.js.map +1 -0
  34. package/dist/teleport/push.d.ts +49 -0
  35. package/dist/teleport/push.js +261 -0
  36. package/dist/teleport/push.js.map +1 -0
  37. package/dist/teleport/restore.d.ts +16 -0
  38. package/dist/teleport/restore.js +163 -0
  39. package/dist/teleport/restore.js.map +1 -0
  40. package/dist/teleport/storage.d.ts +53 -0
  41. package/dist/teleport/storage.js +43 -0
  42. package/dist/teleport/storage.js.map +1 -0
  43. package/dist/teleport/types.d.ts +100 -0
  44. package/dist/teleport/types.js +85 -0
  45. package/dist/teleport/types.js.map +1 -0
  46. package/dist/teleport/worktree.d.ts +28 -0
  47. package/dist/teleport/worktree.js +120 -0
  48. package/dist/teleport/worktree.js.map +1 -0
  49. package/package.json +1 -1
@@ -0,0 +1,41 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ /** Runs git synchronously; never throws, callers check `ok`. */
3
+ export function git(args, cwd, input) {
4
+ const res = spawnSync("git", args, { cwd, encoding: "utf8", input, maxBuffer: 256 * 1024 * 1024 });
5
+ return { ok: res.status === 0, status: res.status, stdout: res.stdout ?? "", stderr: res.stderr ?? "" };
6
+ }
7
+ export function gitOrThrow(args, cwd, input) {
8
+ const res = git(args, cwd, input);
9
+ if (!res.ok)
10
+ throw new Error(`git ${args.join(" ")} failed in ${cwd}: ${res.stderr.trim() || res.stdout.trim()}`);
11
+ return res.stdout;
12
+ }
13
+ export function isGitRepo(dir) {
14
+ const res = git(["rev-parse", "--is-inside-work-tree"], dir);
15
+ return res.ok && res.stdout.trim() === "true";
16
+ }
17
+ /** Top-level directory of the repository containing `dir`. */
18
+ export function repoRoot(dir) {
19
+ const res = git(["rev-parse", "--show-toplevel"], dir);
20
+ return res.ok ? res.stdout.trim() : undefined;
21
+ }
22
+ export function binaryAvailable(command) {
23
+ const probe = process.platform === "win32" ? "where" : "which";
24
+ return spawnSync(probe, [command], { stdio: "ignore" }).status === 0;
25
+ }
26
+ /** Runs a command asynchronously with inherited env, resolving on exit. */
27
+ export function runAsync(command, args, opts = {}) {
28
+ return new Promise((resolve, reject) => {
29
+ const child = spawn(command, args, { cwd: opts.cwd, stdio: [opts.input === undefined ? "ignore" : "pipe", "pipe", "pipe"] });
30
+ let stdout = "";
31
+ let stderr = "";
32
+ child.stdout?.on("data", (c) => (stdout += c));
33
+ child.stderr?.on("data", (c) => (stderr += c));
34
+ child.on("error", reject);
35
+ child.on("close", (status) => resolve({ ok: status === 0, status, stdout, stderr }));
36
+ if (opts.input !== undefined) {
37
+ child.stdin?.end(opts.input);
38
+ }
39
+ });
40
+ }
41
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/teleport/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import { type Compression, type HarnessKind } from "./types.js";
2
+ /** Where a harness stores the session's transcript, relative to $HOME. */
3
+ export interface HarnessLocation {
4
+ kind: HarnessKind;
5
+ sessionId: string;
6
+ /** Files to capture, absolute paths. */
7
+ files: string[];
8
+ /** Absolute directory the session is keyed under (Claude project dir). */
9
+ keyDir?: string;
10
+ }
11
+ /** Locates Claude Code transcript files for a session recorded under `cwd`. */
12
+ export declare function locateClaude(sessionId: string, cwd: string): Promise<HarnessLocation | null>;
13
+ /** Locates a Codex rollout transcript by thread id. */
14
+ export declare function locateCodex(threadId: string): Promise<HarnessLocation | null>;
15
+ export declare function locateHarness(kind: HarnessKind, sessionId: string | undefined, cwd: string): Promise<HarnessLocation | null>;
16
+ export declare function pickCompression(): Compression;
17
+ /** Tars a harness's transcript files (as $HOME-relative paths) into `outFile`. */
18
+ export declare function captureHarness(loc: HarnessLocation, outFile: string, compression?: Compression): Promise<{
19
+ file: string;
20
+ compression: Compression;
21
+ } | null>;
22
+ /**
23
+ * Restores a Claude transcript under a (possibly different) target cwd: extract
24
+ * to a temp home, then rewrite the project-dir slug and any embedded absolute
25
+ * cwd inside the jsonl so `claude --resume <id>` finds the session at the new
26
+ * path. Returns the restored session id.
27
+ */
28
+ export declare function restoreClaudeTranscript(tarFile: string, compression: Compression, sessionId: string, originalCwd: string, targetCwd: string): Promise<void>;
29
+ export declare function fileSize(p: string): Promise<number>;
@@ -0,0 +1,121 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { access, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { binaryAvailable } from "./git.js";
6
+ import { cwdSlug } from "./types.js";
7
+ function claudeHome() {
8
+ return process.env.CLAUDE_CONFIG_DIR?.trim() || path.join(os.homedir(), ".claude");
9
+ }
10
+ function codexHome() {
11
+ return process.env.CODEX_HOME?.trim() || path.join(os.homedir(), ".codex");
12
+ }
13
+ /** Locates Claude Code transcript files for a session recorded under `cwd`. */
14
+ export async function locateClaude(sessionId, cwd) {
15
+ const projectDir = path.join(claudeHome(), "projects", cwdSlug(cwd));
16
+ const jsonl = path.join(projectDir, `${sessionId}.jsonl`);
17
+ if (!(await exists(jsonl)))
18
+ return null;
19
+ const files = [jsonl];
20
+ const subdir = path.join(projectDir, sessionId);
21
+ if (await exists(subdir)) {
22
+ for (const f of await walkFiles(subdir))
23
+ files.push(f);
24
+ }
25
+ return { kind: "claude-code", sessionId, files, keyDir: projectDir };
26
+ }
27
+ /** Locates a Codex rollout transcript by thread id. */
28
+ export async function locateCodex(threadId) {
29
+ const sessions = path.join(codexHome(), "sessions");
30
+ if (!(await exists(sessions)))
31
+ return null;
32
+ const matches = (await walkFiles(sessions)).filter((f) => f.endsWith(".jsonl") && path.basename(f).includes(threadId));
33
+ if (matches.length === 0)
34
+ return null;
35
+ return { kind: "codex", sessionId: threadId, files: matches };
36
+ }
37
+ export async function locateHarness(kind, sessionId, cwd) {
38
+ if (!sessionId)
39
+ return null;
40
+ if (kind === "claude-code")
41
+ return locateClaude(sessionId, cwd);
42
+ if (kind === "codex")
43
+ return locateCodex(sessionId);
44
+ return null;
45
+ }
46
+ export function pickCompression() {
47
+ return binaryAvailable("zstd") ? "zstd" : "gzip";
48
+ }
49
+ /** Tars a harness's transcript files (as $HOME-relative paths) into `outFile`. */
50
+ export async function captureHarness(loc, outFile, compression = pickCompression()) {
51
+ const home = os.homedir();
52
+ const rel = loc.files.map((f) => path.relative(home, f)).filter((r) => !r.startsWith(".."));
53
+ if (rel.length === 0)
54
+ return null;
55
+ await mkdir(path.dirname(outFile), { recursive: true });
56
+ const flag = compression === "zstd" ? "--zstd" : "--gzip";
57
+ const res = spawnSync("tar", ["-c", flag, "-f", outFile, "-C", home, "--null", "-T", "-"], {
58
+ input: `${rel.join("\0")}\0`,
59
+ maxBuffer: 64 * 1024 * 1024,
60
+ });
61
+ if (res.status !== 0)
62
+ throw new Error(`tar failed for harness state: ${res.stderr?.toString().trim()}`);
63
+ return { file: outFile, compression };
64
+ }
65
+ /**
66
+ * Restores a Claude transcript under a (possibly different) target cwd: extract
67
+ * to a temp home, then rewrite the project-dir slug and any embedded absolute
68
+ * cwd inside the jsonl so `claude --resume <id>` finds the session at the new
69
+ * path. Returns the restored session id.
70
+ */
71
+ export async function restoreClaudeTranscript(tarFile, compression, sessionId, originalCwd, targetCwd) {
72
+ const home = os.homedir();
73
+ const flag = compression === "zstd" ? "--zstd" : "--gzip";
74
+ const res = spawnSync("tar", ["-x", flag, "-f", tarFile, "-C", home], { maxBuffer: 64 * 1024 * 1024 });
75
+ if (res.status !== 0)
76
+ throw new Error(`tar extract failed: ${res.stderr?.toString().trim()}`);
77
+ if (path.resolve(originalCwd) === path.resolve(targetCwd))
78
+ return;
79
+ const fromDir = path.join(claudeHome(), "projects", cwdSlug(originalCwd));
80
+ const toDir = path.join(claudeHome(), "projects", cwdSlug(targetCwd));
81
+ await mkdir(toDir, { recursive: true });
82
+ const fromJsonl = path.join(fromDir, `${sessionId}.jsonl`);
83
+ if (await exists(fromJsonl)) {
84
+ const rewritten = (await readFile(fromJsonl, "utf8")).split(originalCwd).join(targetCwd);
85
+ await writeFile(path.join(toDir, `${sessionId}.jsonl`), rewritten);
86
+ }
87
+ const fromSub = path.join(fromDir, sessionId);
88
+ if (await exists(fromSub)) {
89
+ for (const f of await walkFiles(fromSub)) {
90
+ const relFromSub = path.relative(fromSub, f);
91
+ const dest = path.join(toDir, sessionId, relFromSub);
92
+ await mkdir(path.dirname(dest), { recursive: true });
93
+ await writeFile(dest, await readFile(f));
94
+ }
95
+ }
96
+ }
97
+ async function exists(p) {
98
+ try {
99
+ await access(p);
100
+ return true;
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ async function walkFiles(dir) {
107
+ const out = [];
108
+ const entries = await readdir(dir, { withFileTypes: true }).catch(() => []);
109
+ for (const e of entries) {
110
+ const full = path.join(dir, e.name);
111
+ if (e.isDirectory())
112
+ out.push(...(await walkFiles(full)));
113
+ else if (e.isFile())
114
+ out.push(full);
115
+ }
116
+ return out;
117
+ }
118
+ export async function fileSize(p) {
119
+ return (await stat(p)).size;
120
+ }
121
+ //# sourceMappingURL=harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.js","sourceRoot":"","sources":["../../src/teleport/harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ import * as tapi from "./api.js";
2
+ export interface UnpushedRepo {
3
+ relPath: string;
4
+ branch: string | null;
5
+ ahead: number;
6
+ reason: string;
7
+ }
8
+ /**
9
+ * Repositories under `root` that hold work no remote has: commits ahead of an
10
+ * upstream, or a branch with no upstream at all. Offload refuses on these
11
+ * unless the user overrides, because deleting them loses the only copy.
12
+ */
13
+ export declare function unpushedRepos(root: string): Promise<UnpushedRepo[]>;
14
+ export interface OffloadReadiness {
15
+ ready: boolean;
16
+ mirrors: tapi.MirrorStatus[];
17
+ }
18
+ /** Polls capsule status until every required mirror is complete + verified, or timeout. */
19
+ export declare function waitForOffloadReady(capsuleId: string, opts?: {
20
+ timeoutMs?: number;
21
+ intervalMs?: number;
22
+ onPoll?: (s: tapi.CapsuleStatus) => void;
23
+ }): Promise<OffloadReadiness>;
24
+ /**
25
+ * Removes the local root once its capsule is verified. On macOS the root is
26
+ * moved into the user's Trash so it is trivially recoverable; elsewhere it is
27
+ * deleted. Never called unless `waitForOffloadReady` returned ready.
28
+ */
29
+ export declare function deleteLocalRoot(root: string): Promise<{
30
+ method: "trash" | "rm";
31
+ location?: string;
32
+ }>;
@@ -0,0 +1,83 @@
1
+ import { rename, rm, stat } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { git, isGitRepo } from "./git.js";
5
+ import * as tapi from "./api.js";
6
+ import { discoverRepos } from "./discover.js";
7
+ /**
8
+ * Repositories under `root` that hold work no remote has: commits ahead of an
9
+ * upstream, or a branch with no upstream at all. Offload refuses on these
10
+ * unless the user overrides, because deleting them loses the only copy.
11
+ */
12
+ export async function unpushedRepos(root) {
13
+ const discovery = await discoverRepos(root);
14
+ const out = [];
15
+ for (const rel of discovery.repos) {
16
+ const dir = rel === "." ? discovery.root : path.join(discovery.root, rel);
17
+ if (!isGitRepo(dir))
18
+ continue;
19
+ const branchRes = git(["symbolic-ref", "--quiet", "--short", "HEAD"], dir);
20
+ const branch = branchRes.ok ? branchRes.stdout.trim() : null;
21
+ const upstream = branch ? git(["rev-parse", "--abbrev-ref", "@{u}"], dir) : undefined;
22
+ if (!branch) {
23
+ out.push({ relPath: rel, branch, ahead: 0, reason: "detached HEAD" });
24
+ continue;
25
+ }
26
+ if (!upstream?.ok) {
27
+ const hasCommits = git(["rev-parse", "--verify", "HEAD"], dir).ok;
28
+ const configured = git(["config", `branch.${branch}.merge`], dir).ok;
29
+ if (hasCommits && configured) {
30
+ // Upstream is configured but its tracking ref is absent (a capsule
31
+ // restored without upstream tips, or a never-fetched remote): we
32
+ // cannot tell what is pushed, so refuse rather than guess.
33
+ out.push({ relPath: rel, branch, ahead: -1, reason: "tracking state unknown; fetch first or pass --allow-unpushed" });
34
+ }
35
+ else if (hasCommits) {
36
+ out.push({ relPath: rel, branch, ahead: 0, reason: "branch has no upstream" });
37
+ }
38
+ continue;
39
+ }
40
+ const ahead = git(["rev-list", "--count", "@{u}..HEAD"], dir);
41
+ const n = ahead.ok ? Number(ahead.stdout.trim()) : 0;
42
+ if (n > 0)
43
+ out.push({ relPath: rel, branch, ahead: n, reason: `${n} commit(s) not on ${upstream.stdout.trim()}` });
44
+ }
45
+ return out;
46
+ }
47
+ /** Polls capsule status until every required mirror is complete + verified, or timeout. */
48
+ export async function waitForOffloadReady(capsuleId, opts = {}) {
49
+ const timeout = opts.timeoutMs ?? 10 * 60_000;
50
+ const interval = opts.intervalMs ?? 5_000;
51
+ const deadline = Date.now() + timeout;
52
+ for (;;) {
53
+ const status = await tapi.capsuleStatus(capsuleId);
54
+ opts.onPoll?.(status);
55
+ if (status.offloadReady)
56
+ return { ready: true, mirrors: status.mirrors };
57
+ if (Date.now() >= deadline)
58
+ return { ready: false, mirrors: status.mirrors };
59
+ await new Promise((r) => setTimeout(r, interval));
60
+ }
61
+ }
62
+ /**
63
+ * Removes the local root once its capsule is verified. On macOS the root is
64
+ * moved into the user's Trash so it is trivially recoverable; elsewhere it is
65
+ * deleted. Never called unless `waitForOffloadReady` returned ready.
66
+ */
67
+ export async function deleteLocalRoot(root) {
68
+ const resolved = path.resolve(root);
69
+ await stat(resolved); // throws if already gone
70
+ if (process.platform === "darwin") {
71
+ const trash = path.join(os.homedir(), ".Trash", `${path.basename(resolved)}-teleport-${Date.now()}`);
72
+ try {
73
+ await rename(resolved, trash);
74
+ return { method: "trash", location: trash };
75
+ }
76
+ catch {
77
+ // cross-device rename into Trash can fail; fall back to rm
78
+ }
79
+ }
80
+ await rm(resolved, { recursive: true, force: true });
81
+ return { method: "rm" };
82
+ }
83
+ //# sourceMappingURL=offload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offload.js","sourceRoot":"","sources":["../../src/teleport/offload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ import { type RestoreResult } from "./restore.js";
2
+ /**
3
+ * Downloads a generation's chunks and rebuilds the capsule into `destRoot`.
4
+ * Chunks are verified by sha256 as they land; a mismatch aborts the pull.
5
+ */
6
+ export declare function pullCapsule(capsuleId: string, generation: number | undefined, destRoot: string, opts?: {
7
+ restoreHarness?: boolean;
8
+ }): Promise<RestoreResult>;
9
+ /** Local record of a capsule's last generation, for thin bundles on the next push. */
10
+ export declare function manifestCachePath(configDir: string, capsuleId: string): string;
11
+ export declare function readCachedManifest(configDir: string, capsuleId: string): Promise<any>;
12
+ export declare function writeCachedManifest(configDir: string, capsuleId: string, manifest: unknown): Promise<void>;
@@ -0,0 +1,118 @@
1
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import * as tapi from "./api.js";
5
+ import { restoreCapsule } from "./restore.js";
6
+ const GET_CONCURRENCY = 4;
7
+ /**
8
+ * Downloads a generation's chunks and rebuilds the capsule into `destRoot`.
9
+ * Chunks are verified by sha256 as they land; a mismatch aborts the pull.
10
+ */
11
+ export async function pullCapsule(capsuleId, generation, destRoot, opts = {}) {
12
+ const resolved = await tapi.resolveGeneration(capsuleId, generation);
13
+ const { manifest } = resolved;
14
+ const chunks = [...resolved.chunks];
15
+ await completePackChains(capsuleId, manifest, chunks);
16
+ const chunkDir = await mkdtemp(path.join(os.tmpdir(), "teleport-chunks-"));
17
+ try {
18
+ await downloadChunks(chunks, chunkDir);
19
+ return await restoreCapsule(manifest, (sha) => path.join(chunkDir, sha), destRoot, opts);
20
+ }
21
+ finally {
22
+ await rm(chunkDir, { recursive: true, force: true });
23
+ }
24
+ }
25
+ /**
26
+ * Fallback for manifests whose repos carry only thin packs (older pushes did
27
+ * not reference their basis packs): walk `basisGeneration` back through the
28
+ * capsule's generations until a full pack is found, prepending each basis
29
+ * generation's packs and chunk URLs so the restore has complete history.
30
+ */
31
+ async function completePackChains(capsuleId, manifest, chunks) {
32
+ const known = new Set(chunks.map((c) => c.sha256));
33
+ const resolvedGenerations = new Map();
34
+ for (const repo of manifest.repos) {
35
+ let guard = 0;
36
+ while (!repo.packs.some((p) => p.kind === "full") && repo.packs.length > 0 && guard++ < 64) {
37
+ const oldest = repo.packs.reduce((a, b) => ((a.basisGeneration ?? 0) <= (b.basisGeneration ?? 0) ? a : b));
38
+ const basisGen = oldest.basisGeneration;
39
+ if (!basisGen)
40
+ break; // thin pack with no recorded basis: nothing more to fetch
41
+ let basis = resolvedGenerations.get(basisGen);
42
+ if (!basis) {
43
+ basis = await tapi.resolveGeneration(capsuleId, basisGen);
44
+ resolvedGenerations.set(basisGen, basis);
45
+ }
46
+ const basisRepo = basis.manifest.repos.find((r) => r.relPath === repo.relPath);
47
+ if (!basisRepo || basisRepo.packs.length === 0)
48
+ break;
49
+ const have = new Set(repo.packs.map((p) => p.chunks.map((c) => c.sha256).join(",")));
50
+ for (const pack of basisRepo.packs) {
51
+ if (have.has(pack.chunks.map((c) => c.sha256).join(",")))
52
+ continue;
53
+ repo.packs.unshift(pack);
54
+ for (const c of pack.chunks) {
55
+ if (known.has(c.sha256))
56
+ continue;
57
+ const url = basis.chunks.find((x) => x.sha256 === c.sha256);
58
+ if (url) {
59
+ chunks.push(url);
60
+ known.add(c.sha256);
61
+ }
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ async function downloadChunks(chunks, dir) {
68
+ const { createHash } = await import("node:crypto");
69
+ let cursor = 0;
70
+ const workers = Array.from({ length: Math.min(GET_CONCURRENCY, chunks.length) }, async () => {
71
+ for (;;) {
72
+ const i = cursor++;
73
+ if (i >= chunks.length)
74
+ return;
75
+ const { sha256, getUrl } = chunks[i];
76
+ const dest = path.join(dir, sha256);
77
+ const buf = await getWithRetry(getUrl);
78
+ const got = createHash("sha256").update(buf).digest("hex");
79
+ if (got !== sha256)
80
+ throw new Error(`chunk ${sha256} failed integrity check (got ${got})`);
81
+ await writeFile(dest, buf);
82
+ }
83
+ });
84
+ await Promise.all(workers);
85
+ }
86
+ async function getWithRetry(url, attempts = 4) {
87
+ let delay = 400;
88
+ for (let attempt = 1;; attempt++) {
89
+ const res = await fetch(url);
90
+ if (res.ok)
91
+ return Buffer.from(await res.arrayBuffer());
92
+ if (attempt >= attempts || (res.status < 500 && res.status !== 429)) {
93
+ throw new Error(`chunk download failed: HTTP ${res.status}`);
94
+ }
95
+ await new Promise((r) => setTimeout(r, delay));
96
+ delay *= 2;
97
+ }
98
+ }
99
+ /** Local record of a capsule's last generation, for thin bundles on the next push. */
100
+ export function manifestCachePath(configDir, capsuleId) {
101
+ return path.join(configDir, "teleport", `${capsuleId}.json`);
102
+ }
103
+ export async function readCachedManifest(configDir, capsuleId) {
104
+ try {
105
+ return JSON.parse(await readFile(manifestCachePath(configDir, capsuleId), "utf8"));
106
+ }
107
+ catch (err) {
108
+ if (err.code === "ENOENT")
109
+ return undefined;
110
+ throw err;
111
+ }
112
+ }
113
+ export async function writeCachedManifest(configDir, capsuleId, manifest) {
114
+ const file = manifestCachePath(configDir, capsuleId);
115
+ await mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
116
+ await writeFile(file, `${JSON.stringify(manifest, null, 2)}\n`, { mode: 0o600 });
117
+ }
118
+ //# sourceMappingURL=pull.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pull.js","sourceRoot":"","sources":["../../src/teleport/pull.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import * as tapi from "./api.js";
2
+ import { type HarnessKind, type Manifest } from "./types.js";
3
+ export interface PushInput {
4
+ root: string;
5
+ capsuleId: string;
6
+ parentGeneration: number | null;
7
+ /** Previous generation's manifest, for thin bundles. */
8
+ previousManifest?: Manifest;
9
+ harness: {
10
+ kind: HarnessKind;
11
+ sessionId?: string;
12
+ cwd?: string;
13
+ };
14
+ ignoredIncludes: string[];
15
+ rootName: string;
16
+ rootKind: "repo" | "folder";
17
+ /** Who is committing: the CLI on a user's machine, or the Ditto Code runner. */
18
+ committedBy?: tapi.CommittedBy;
19
+ }
20
+ export interface PushOutput {
21
+ generation: number;
22
+ bytesTotal: number;
23
+ dedupedBytes: number;
24
+ chunkCount: number;
25
+ uploaded: number;
26
+ reused: number;
27
+ /** Bytes actually sent to storage this push. */
28
+ uploadedBytes: number;
29
+ /** Bytes the generation references in total (every chunk, counted once per reference). */
30
+ logicalBytes: number;
31
+ /** logicalBytes − uploadedBytes: what dedup against earlier pushes saved. */
32
+ reusedBytes: number;
33
+ /** reusedBytes / logicalBytes, 0 when nothing was referenced. */
34
+ savingsRatio: number;
35
+ }
36
+ /**
37
+ * Builds a generation, negotiates which chunks are missing, uploads them via
38
+ * presigned PUTs, then commits the manifest. The manifest is content-addressed
39
+ * too, so a re-push with no changes uploads nothing.
40
+ *
41
+ * Every generation's manifest carries each repo's complete pack chain (the
42
+ * basis packs by reference plus this generation's thin pack), so a pull of any
43
+ * generation rebuilds full history from that one manifest. When the server no
44
+ * longer holds a referenced basis chunk, the affected repos are re-bundled in
45
+ * full and the push retried once.
46
+ */
47
+ export declare function pushCapsule(input: PushInput, forceFull?: Set<string>): Promise<PushOutput>;
48
+ /** The Ditto Code runner authenticates with a ditto_agent_ token and pins the harness session via env. */
49
+ export declare function detectCommitter(): tapi.CommittedBy;