@heyditto/cli 2.4.0 → 2.5.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 +89 -1
- package/dist/agents/launch.js +6 -1
- package/dist/agents/launch.js.map +1 -1
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands.js +1 -1
- package/dist/commands.js.map +1 -1
- package/dist/teleport/api.d.ts +139 -0
- package/dist/teleport/api.js +61 -0
- package/dist/teleport/api.js.map +1 -0
- package/dist/teleport/bundle.d.ts +35 -0
- package/dist/teleport/bundle.js +102 -0
- package/dist/teleport/bundle.js.map +1 -0
- package/dist/teleport/chunks.d.ts +13 -0
- package/dist/teleport/chunks.js +57 -0
- package/dist/teleport/chunks.js.map +1 -0
- package/dist/teleport/commands.d.ts +74 -0
- package/dist/teleport/commands.js +488 -0
- package/dist/teleport/commands.js.map +1 -0
- package/dist/teleport/discover.d.ts +13 -0
- package/dist/teleport/discover.js +64 -0
- package/dist/teleport/discover.js.map +1 -0
- package/dist/teleport/git.d.ts +18 -0
- package/dist/teleport/git.js +41 -0
- package/dist/teleport/git.js.map +1 -0
- package/dist/teleport/harness.d.ts +29 -0
- package/dist/teleport/harness.js +121 -0
- package/dist/teleport/harness.js.map +1 -0
- package/dist/teleport/offload.d.ts +32 -0
- package/dist/teleport/offload.js +83 -0
- package/dist/teleport/offload.js.map +1 -0
- package/dist/teleport/pull.d.ts +12 -0
- package/dist/teleport/pull.js +118 -0
- package/dist/teleport/pull.js.map +1 -0
- package/dist/teleport/push.d.ts +49 -0
- package/dist/teleport/push.js +261 -0
- package/dist/teleport/push.js.map +1 -0
- package/dist/teleport/restore.d.ts +16 -0
- package/dist/teleport/restore.js +163 -0
- package/dist/teleport/restore.js.map +1 -0
- package/dist/teleport/storage.d.ts +53 -0
- package/dist/teleport/storage.js +43 -0
- package/dist/teleport/storage.js.map +1 -0
- package/dist/teleport/types.d.ts +100 -0
- package/dist/teleport/types.js +85 -0
- package/dist/teleport/types.js.map +1 -0
- package/dist/teleport/worktree.d.ts +22 -0
- package/dist/teleport/worktree.js +94 -0
- package/dist/teleport/worktree.js.map +1 -0
- 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":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACrF,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3C,OAAO,EAAsC,OAAO,EAAE,MAAM,YAAY,CAAC;AAYzE,SAAS,UAAU;IACjB,OAAO,OAAO,CAAC,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,SAAS;IAChB,OAAO,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;AAC7E,CAAC;AAED,+EAA+E;AAC/E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,SAAiB,EAAE,GAAW;IAC/D,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;IAC1D,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,KAAK,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,CAAC;IACtB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IAChD,IAAI,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,MAAM,SAAS,CAAC,MAAM,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;AACvE,CAAC;AAED,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,QAAgB;IAChD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,EAAE,UAAU,CAAC,CAAC;IACpD,IAAI,CAAC,CAAC,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,MAAM,OAAO,GAAG,CAAC,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAChD,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CACnE,CAAC;IACF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,IAAiB,EACjB,SAA6B,EAC7B,GAAW;IAEX,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,IAAI,KAAK,aAAa;QAAE,OAAO,YAAY,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;IAChE,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAoB,EACpB,OAAe,EACf,cAA2B,eAAe,EAAE;IAE5C,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC5F,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAClC,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,MAAM,IAAI,GAAG,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1D,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE;QACzF,KAAK,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QAC5B,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI;KAC5B,CAAC,CAAC;IACH,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxG,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,CAAC;AACxC,CAAC;AAED;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,OAAe,EACf,WAAwB,EACxB,SAAiB,EACjB,WAAmB,EACnB,SAAiB;IAEjB,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC1B,MAAM,IAAI,GAAG,WAAW,KAAK,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC;IAC1D,MAAM,GAAG,GAAG,SAAS,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC,CAAC;IACvG,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC9F,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC;QAAE,OAAO;IAClE,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC;IAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;IACtE,MAAM,KAAK,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,SAAS,QAAQ,CAAC,CAAC;IAC3D,IAAI,MAAM,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;QAC5B,MAAM,SAAS,GAAG,CAAC,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzF,MAAM,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,GAAG,SAAS,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC;IACrE,CAAC;IACD,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAC9C,IAAI,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;QAC1B,KAAK,MAAM,CAAC,IAAI,MAAM,SAAS,CAAC,OAAO,CAAC,EAAE,CAAC;YACzC,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC7C,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,CAAC;YACrD,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;YACrD,MAAM,SAAS,CAAC,IAAI,EAAE,MAAM,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,MAAM,CAAC,CAAS;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,CAAC,CAAC,CAAC;QAChB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAC,GAAW;IAClC,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5E,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC,WAAW,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;aACrD,IAAI,CAAC,CAAC,MAAM,EAAE;YAAE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,CAAS;IACtC,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9B,CAAC"}
|
|
@@ -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;
|