promigence 0.1.0-beta.12
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/NOTICE +14 -0
- package/README.md +88 -0
- package/dist/account.js +850 -0
- package/dist/agent.js +374 -0
- package/dist/argv.js +159 -0
- package/dist/bin.js +332 -0
- package/dist/bundle.js +133 -0
- package/dist/capacity.js +69 -0
- package/dist/commands.d.ts +110 -0
- package/dist/commands.js +2675 -0
- package/dist/connectreq.js +125 -0
- package/dist/credfile.js +188 -0
- package/dist/credits.js +197 -0
- package/dist/devcontainer.d.ts +99 -0
- package/dist/devcontainer.js +744 -0
- package/dist/device.js +129 -0
- package/dist/dirsnap.js +378 -0
- package/dist/errfix.js +262 -0
- package/dist/generated.d.ts +35 -0
- package/dist/generated.js +307 -0
- package/dist/github.js +307 -0
- package/dist/gitlab.js +210 -0
- package/dist/guide.d.ts +19 -0
- package/dist/guide.js +366 -0
- package/dist/handlers.js +511 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.js +7 -0
- package/dist/init.d.ts +40 -0
- package/dist/init.js +352 -0
- package/dist/install.d.ts +23 -0
- package/dist/install.js +127 -0
- package/dist/io.d.ts +37 -0
- package/dist/io.js +318 -0
- package/dist/live.js +469 -0
- package/dist/login.js +1063 -0
- package/dist/mcp.js +889 -0
- package/dist/money.js +5 -0
- package/dist/orgs.js +475 -0
- package/dist/pricing.d.ts +52 -0
- package/dist/pricing.js +181 -0
- package/dist/report.js +132 -0
- package/dist/routes.js +135 -0
- package/dist/runners-github.js +257 -0
- package/dist/runners-gitlab.js +125 -0
- package/dist/runners-spec.js +137 -0
- package/dist/runners.js +1458 -0
- package/dist/sdk/blake3.d.ts +10 -0
- package/dist/sdk/blake3.js +111 -0
- package/dist/sdk/bundle.d.ts +96 -0
- package/dist/sdk/bundle.js +227 -0
- package/dist/sdk/ca-guard.js +166 -0
- package/dist/sdk/caller.d.ts +73 -0
- package/dist/sdk/caller.js +47 -0
- package/dist/sdk/client.d.ts +3015 -0
- package/dist/sdk/client.js +2583 -0
- package/dist/sdk/command-errors.d.ts +41 -0
- package/dist/sdk/command-errors.js +36 -0
- package/dist/sdk/context.d.ts +119 -0
- package/dist/sdk/context.js +588 -0
- package/dist/sdk/core.d.ts +285 -0
- package/dist/sdk/core.js +266 -0
- package/dist/sdk/credstore.d.ts +90 -0
- package/dist/sdk/credstore.js +206 -0
- package/dist/sdk/dispatcher.js +86 -0
- package/dist/sdk/github.d.ts +158 -0
- package/dist/sdk/github.js +78 -0
- package/dist/sdk/gitlab.d.ts +104 -0
- package/dist/sdk/gitlab.js +20 -0
- package/dist/sdk/handles.d.ts +82 -0
- package/dist/sdk/handles.js +135 -0
- package/dist/sdk/hint.js +7 -0
- package/dist/sdk/http2.js +1921 -0
- package/dist/sdk/index.d.ts +34 -0
- package/dist/sdk/index.js +15 -0
- package/dist/sdk/keyed.gen.js +7 -0
- package/dist/sdk/models.gen.d.ts +6162 -0
- package/dist/sdk/models.gen.js +1 -0
- package/dist/sdk/proxy.js +249 -0
- package/dist/sdk/pty.d.ts +171 -0
- package/dist/sdk/pty.js +496 -0
- package/dist/sdk/sandbox.d.ts +537 -0
- package/dist/sdk/sandbox.js +725 -0
- package/dist/sdk/ssh.d.ts +25 -0
- package/dist/sdk/ssh.js +114 -0
- package/dist/sdk/trace.js +80 -0
- package/dist/sdk/transport-worker.js +466 -0
- package/dist/sdk/warm.js +40 -0
- package/dist/ssh.js +267 -0
- package/dist/surface.d.ts +95 -0
- package/dist/surface.js +1452 -0
- package/dist/templates.js +53 -0
- package/dist/term.js +50 -0
- package/dist/timing.js +71 -0
- package/dist/untar.js +324 -0
- package/dist/uploadguard.js +75 -0
- package/llms.txt +209 -0
- package/package.json +52 -0
package/dist/init.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const LOCKFILES = [
|
|
5
|
+
["bun.lock", "bun"], ["bun.lockb", "bun"],
|
|
6
|
+
["pnpm-lock.yaml", "pnpm"],
|
|
7
|
+
["yarn.lock", "yarn"],
|
|
8
|
+
["package-lock.json", "npm"],
|
|
9
|
+
["uv.lock", "uv"],
|
|
10
|
+
["poetry.lock", "poetry"],
|
|
11
|
+
["Pipfile.lock", "pipenv"],
|
|
12
|
+
["requirements.txt", "pip"],
|
|
13
|
+
["pyproject.toml", "pyproject"],
|
|
14
|
+
["Cargo.lock", "cargo"],
|
|
15
|
+
["go.sum", "go"], ["go.mod", "go"],
|
|
16
|
+
["Gemfile.lock", "bundler"],
|
|
17
|
+
["composer.lock", "composer"],
|
|
18
|
+
];
|
|
19
|
+
const SCRIPT_PRIORITY = ["typecheck", "type-check", "tsc", "check", "test", "lint", "build"];
|
|
20
|
+
const NODE_INSTALL = {
|
|
21
|
+
bun: "bun install --frozen-lockfile",
|
|
22
|
+
pnpm: "pnpm install --frozen-lockfile",
|
|
23
|
+
yarn: "yarn install --immutable",
|
|
24
|
+
npm: "npm ci",
|
|
25
|
+
};
|
|
26
|
+
const NODE_RUN = {
|
|
27
|
+
bun: "bun run", pnpm: "pnpm run", yarn: "yarn run", npm: "npm run",
|
|
28
|
+
};
|
|
29
|
+
function readJson(path) {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function pythonVerify(dir, run) {
|
|
38
|
+
const has = (f) => existsSync(join(dir, f));
|
|
39
|
+
if (has("mypy.ini") || (has("pyproject.toml") && /\[tool\.mypy\]/.test(readFileSync(join(dir, "pyproject.toml"), "utf8"))))
|
|
40
|
+
return { verify: `${run} mypy .`, why: "mypy is configured" };
|
|
41
|
+
if (has("tests") || has("test") || has("pytest.ini") || has("conftest.py"))
|
|
42
|
+
return { verify: `${run} pytest -q`, why: "a pytest suite is present" };
|
|
43
|
+
return { verify: `${run} python -c 'import sys; print(sys.version)'`, why: "no test suite found — replace this with the command that must pass" };
|
|
44
|
+
}
|
|
45
|
+
export function detect(dir) {
|
|
46
|
+
const hit = LOCKFILES.find(([f]) => existsSync(join(dir, f)));
|
|
47
|
+
const ecosystem = hit?.[1] ?? "unknown";
|
|
48
|
+
const lockfile = ecosystem === "pyproject" ? undefined : hit?.[0];
|
|
49
|
+
if (ecosystem === "bun" || ecosystem === "pnpm" || ecosystem === "yarn" || ecosystem === "npm") {
|
|
50
|
+
const pkg = readJson(join(dir, "package.json"));
|
|
51
|
+
const scripts = (pkg?.scripts ?? {});
|
|
52
|
+
const picked = SCRIPT_PRIORITY.find((s) => typeof scripts[s] === "string");
|
|
53
|
+
const run = NODE_RUN[ecosystem];
|
|
54
|
+
const toolchain = ecosystem === "bun" ? ["bun"] : ["node", ecosystem === "npm" ? "npm" : ecosystem];
|
|
55
|
+
return {
|
|
56
|
+
ecosystem,
|
|
57
|
+
...(lockfile ? { lockfile } : {}),
|
|
58
|
+
setup: [NODE_INSTALL[ecosystem]],
|
|
59
|
+
verify: picked ? `${run} ${picked}` : `${run} test`,
|
|
60
|
+
toolchain,
|
|
61
|
+
why: picked
|
|
62
|
+
? `package.json has a "${picked}" script`
|
|
63
|
+
: 'package.json declares no typecheck/check/test script — "test" is a guess; override with --verify',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
switch (ecosystem) {
|
|
67
|
+
case "uv": {
|
|
68
|
+
const v = pythonVerify(dir, "uv run");
|
|
69
|
+
return { ecosystem, ...(lockfile ? { lockfile } : {}), setup: ["uv sync --frozen"], verify: v.verify, toolchain: ["uv", "python3"], why: v.why };
|
|
70
|
+
}
|
|
71
|
+
case "poetry": {
|
|
72
|
+
const v = pythonVerify(dir, "poetry run");
|
|
73
|
+
return { ecosystem, ...(lockfile ? { lockfile } : {}), setup: ["poetry install --no-interaction --no-ansi"], verify: v.verify, toolchain: ["poetry", "python3"], why: v.why };
|
|
74
|
+
}
|
|
75
|
+
case "pipenv": {
|
|
76
|
+
const v = pythonVerify(dir, "pipenv run");
|
|
77
|
+
return { ecosystem, ...(lockfile ? { lockfile } : {}), setup: ["pipenv sync"], verify: v.verify, toolchain: ["pipenv", "python3"], why: v.why };
|
|
78
|
+
}
|
|
79
|
+
case "pip": {
|
|
80
|
+
const v = pythonVerify(dir, ".venv/bin/python -m");
|
|
81
|
+
return {
|
|
82
|
+
ecosystem, ...(lockfile ? { lockfile } : {}),
|
|
83
|
+
setup: ["python3 -m venv .venv", ".venv/bin/pip install --no-input -r requirements.txt"],
|
|
84
|
+
verify: v.verify.replace(".venv/bin/python -m python -c", ".venv/bin/python -c"),
|
|
85
|
+
toolchain: ["python3"], why: v.why,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
case "pyproject": {
|
|
89
|
+
const v = pythonVerify(dir, ".venv/bin/python -m");
|
|
90
|
+
const pytest = v.verify.includes("pytest") ? " pytest" : "";
|
|
91
|
+
return {
|
|
92
|
+
ecosystem,
|
|
93
|
+
setup: ["python3 -m venv .venv", `.venv/bin/pip install --no-input -e '.[test]'${pytest}`],
|
|
94
|
+
verify: v.verify.replace(".venv/bin/python -m python -c", ".venv/bin/python -c"),
|
|
95
|
+
toolchain: ["python3"],
|
|
96
|
+
why: `pyproject.toml with no lockfile: installed with pip, editable; ${v.why}`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
case "cargo":
|
|
100
|
+
return {
|
|
101
|
+
ecosystem, ...(lockfile ? { lockfile } : {}),
|
|
102
|
+
setup: ["cargo fetch --locked"],
|
|
103
|
+
verify: "cargo check --locked --all-targets",
|
|
104
|
+
toolchain: ["cargo", "rustc"],
|
|
105
|
+
why: "cargo check is the cheapest command that proves the tree builds",
|
|
106
|
+
};
|
|
107
|
+
case "go":
|
|
108
|
+
return {
|
|
109
|
+
ecosystem, ...(lockfile ? { lockfile } : {}),
|
|
110
|
+
setup: ["go mod download"],
|
|
111
|
+
verify: "go build ./...",
|
|
112
|
+
toolchain: ["go"],
|
|
113
|
+
why: "go build ./... is the cheapest command that proves the tree builds",
|
|
114
|
+
};
|
|
115
|
+
case "bundler":
|
|
116
|
+
return {
|
|
117
|
+
ecosystem, ...(lockfile ? { lockfile } : {}),
|
|
118
|
+
setup: ["bundle install --jobs 4"],
|
|
119
|
+
verify: existsSync(join(dir, "spec")) ? "bundle exec rspec" : "bundle exec rake test",
|
|
120
|
+
toolchain: ["ruby", "bundle"], why: existsSync(join(dir, "spec")) ? "a spec/ directory is present" : "no spec/ — rake test is a guess; override with --verify",
|
|
121
|
+
};
|
|
122
|
+
case "composer":
|
|
123
|
+
return {
|
|
124
|
+
ecosystem, ...(lockfile ? { lockfile } : {}),
|
|
125
|
+
setup: ["composer install --no-interaction --no-progress"],
|
|
126
|
+
verify: "composer run-script test",
|
|
127
|
+
toolchain: ["php", "composer"], why: "composer's declared test script",
|
|
128
|
+
};
|
|
129
|
+
default:
|
|
130
|
+
return {
|
|
131
|
+
ecosystem: "unknown",
|
|
132
|
+
setup: [],
|
|
133
|
+
verify: "",
|
|
134
|
+
toolchain: [],
|
|
135
|
+
why: "no lockfile found (bun/pnpm/yarn/npm, uv/poetry/pipenv/pip/pyproject.toml, cargo, go, bundler, composer)",
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function git(dir, args) {
|
|
140
|
+
try {
|
|
141
|
+
return execFileSync("git", ["-C", dir, ...args], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim() || undefined;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
export function stripRemoteCredentials(url) {
|
|
148
|
+
const bare = url.replace(/[?#].*$/, "");
|
|
149
|
+
const m = /^([a-z][a-z0-9+.-]*:\/\/)([^/?#]*)@([^/?#]*)(.*)$/i.exec(bare);
|
|
150
|
+
if (!m)
|
|
151
|
+
return bare;
|
|
152
|
+
const [, scheme, userinfo, host, rest] = m;
|
|
153
|
+
const user = /ssh:\/\/$/i.test(scheme) ? userinfo.split(":")[0] ?? "" : "";
|
|
154
|
+
return `${scheme}${user ? `${user}@` : ""}${host}${rest}`;
|
|
155
|
+
}
|
|
156
|
+
export function normalizeRemote(url) {
|
|
157
|
+
const ssh = /^ssh:\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/(.+?)(?:\.git)?\/?$/i.exec(url);
|
|
158
|
+
if (ssh)
|
|
159
|
+
return `https://${ssh[1]}/${ssh[2]}`;
|
|
160
|
+
const scp = /^(?:ssh:\/\/)?(?:[\w.-]+)@([\w.-]+):(.+?)(?:\.git)?\/?$/.exec(url);
|
|
161
|
+
if (scp)
|
|
162
|
+
return `https://${scp[1]}/${scp[2]}`;
|
|
163
|
+
return stripRemoteCredentials(url).replace(/\.git$/, "").replace(/\/$/, "");
|
|
164
|
+
}
|
|
165
|
+
export function gitFacts(dir) {
|
|
166
|
+
const out = {};
|
|
167
|
+
const remote = git(dir, ["config", "--get", "remote.origin.url"]);
|
|
168
|
+
if (remote) {
|
|
169
|
+
out.repo = normalizeRemote(remote);
|
|
170
|
+
if (stripRemoteCredentials(remote) !== remote)
|
|
171
|
+
out.credentialsRemoved = true;
|
|
172
|
+
}
|
|
173
|
+
const commit = git(dir, ["rev-parse", "HEAD"]);
|
|
174
|
+
if (commit && /^[0-9a-f]{40}$/.test(commit))
|
|
175
|
+
out.commit = commit;
|
|
176
|
+
const branch = git(dir, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
177
|
+
if (branch)
|
|
178
|
+
out.branch = branch;
|
|
179
|
+
const status = git(dir, ["status", "--porcelain"]);
|
|
180
|
+
if (status !== undefined)
|
|
181
|
+
out.dirty = status.length > 0;
|
|
182
|
+
else if (commit)
|
|
183
|
+
out.dirty = false;
|
|
184
|
+
if (out.commit) {
|
|
185
|
+
const remotes = git(dir, ["for-each-ref", "--count=1", "--format=%(refname)", "refs/remotes"]);
|
|
186
|
+
if (remotes) {
|
|
187
|
+
const containing = git(dir, ["branch", "-r", "--contains", out.commit]);
|
|
188
|
+
out.onRemote = containing !== undefined && containing.trim().length > 0;
|
|
189
|
+
}
|
|
190
|
+
else if (remote) {
|
|
191
|
+
out.onRemote = false;
|
|
192
|
+
out.noRemoteRefs = true;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return out;
|
|
196
|
+
}
|
|
197
|
+
export const INIT_IMAGES = {
|
|
198
|
+
bun: "node:22", pnpm: "node:22", yarn: "node:22", npm: "node:22",
|
|
199
|
+
uv: "python:3.12", poetry: "python:3.12", pipenv: "python:3.12", pip: "python:3.12", pyproject: "python:3.12",
|
|
200
|
+
cargo: "rust:1", go: "golang:1.23", bundler: "ruby:3.3", composer: "composer:2",
|
|
201
|
+
};
|
|
202
|
+
const PREP_STEP = {
|
|
203
|
+
bun: "npm install -g bun", pnpm: "corepack enable", yarn: "corepack enable",
|
|
204
|
+
uv: "pip install uv", poetry: "pip install poetry", pipenv: "pip install pipenv",
|
|
205
|
+
};
|
|
206
|
+
const COMMAND_TOOLS = [
|
|
207
|
+
[/^bunx?$/, "bun"], [/^pnpm$/, "pnpm"], [/^yarn$/, "yarn"], [/^(npm|npx|node)$/, "npm"],
|
|
208
|
+
[/^uv$/, "uv"], [/^poetry$/, "poetry"], [/^pipenv$/, "pipenv"],
|
|
209
|
+
[/^(pip|python)(3(\.\d+)?)?$|^pytest$/, "pip"],
|
|
210
|
+
[/^cargo$/, "cargo"], [/^go$/, "go"], [/^(bundle|ruby|rake)$/, "bundler"], [/^(composer|php)$/, "composer"],
|
|
211
|
+
];
|
|
212
|
+
export function ecosystemOfCommands(commands) {
|
|
213
|
+
for (const c of commands) {
|
|
214
|
+
for (const part of c.split(/&&|\|\||[;|]/)) {
|
|
215
|
+
const words = part.trim().split(/\s+/).filter((w) => w && !/^[A-Za-z_][A-Za-z0-9_]*=/.test(w));
|
|
216
|
+
while (words[0] === "sudo" || words[0] === "env" || words[0] === "exec")
|
|
217
|
+
words.shift();
|
|
218
|
+
const prog = words[0]?.split("/").at(-1);
|
|
219
|
+
if (!prog || prog === "cd")
|
|
220
|
+
continue;
|
|
221
|
+
const hit = COMMAND_TOOLS.find(([re]) => re.test(prog));
|
|
222
|
+
if (hit)
|
|
223
|
+
return hit[1];
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return undefined;
|
|
227
|
+
}
|
|
228
|
+
export function imageCreateCommand(c, ecosystem) {
|
|
229
|
+
const image = INIT_IMAGES[ecosystem] ?? "buildpack-deps:bookworm";
|
|
230
|
+
const prep = PREP_STEP[ecosystem];
|
|
231
|
+
return [
|
|
232
|
+
"promigence snapshot create",
|
|
233
|
+
`--image ${image}`,
|
|
234
|
+
`--setup ${JSON.stringify(`git clone ${c.repo} /work/repo && git -C /work/repo checkout ${c.commit}`)}`,
|
|
235
|
+
...(prep ? [`--setup ${JSON.stringify(prep)}`] : []),
|
|
236
|
+
...c.setup.map((s) => `--setup ${JSON.stringify(`cd /work/repo && ${s}`)}`),
|
|
237
|
+
`--verify ${JSON.stringify(`cd /work/repo && ${c.verify}`)}`,
|
|
238
|
+
...(c.tier ? [`--tier ${c.tier}`] : []),
|
|
239
|
+
"--wait",
|
|
240
|
+
].join(" ");
|
|
241
|
+
}
|
|
242
|
+
const tomlString = (s) => `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
243
|
+
const tomlArray = (a) => `[${a.map(tomlString).join(", ")}]`;
|
|
244
|
+
export function readConfig(text) {
|
|
245
|
+
const out = {};
|
|
246
|
+
const lines = text.split(/\r?\n/);
|
|
247
|
+
for (let n = 0; n < lines.length; n++) {
|
|
248
|
+
const line = lines[n].trim();
|
|
249
|
+
if (line === "" || line.startsWith("#"))
|
|
250
|
+
continue;
|
|
251
|
+
if (line.startsWith("["))
|
|
252
|
+
break;
|
|
253
|
+
const bad = (why) => new Error(`line ${n + 1}: ${why}`);
|
|
254
|
+
const eq = line.indexOf("=");
|
|
255
|
+
if (eq <= 0)
|
|
256
|
+
throw bad("not `key = value`");
|
|
257
|
+
const key = line.slice(0, eq).trim();
|
|
258
|
+
let at = eq + 1;
|
|
259
|
+
const rest = line;
|
|
260
|
+
const skipWs = () => { while (at < rest.length && (rest[at] === " " || rest[at] === "\t"))
|
|
261
|
+
at++; };
|
|
262
|
+
const readString = () => {
|
|
263
|
+
if (rest[at] !== '"')
|
|
264
|
+
throw bad(`\`${key}\` is not a quoted string`);
|
|
265
|
+
let v = "";
|
|
266
|
+
for (at++; at < rest.length; at++) {
|
|
267
|
+
const c = rest[at];
|
|
268
|
+
if (c === '"') {
|
|
269
|
+
at++;
|
|
270
|
+
return v;
|
|
271
|
+
}
|
|
272
|
+
if (c === "\\") {
|
|
273
|
+
const e = rest[++at];
|
|
274
|
+
if (e === "\\" || e === '"')
|
|
275
|
+
v += e;
|
|
276
|
+
else if (e === "n")
|
|
277
|
+
v += "\n";
|
|
278
|
+
else if (e === "t")
|
|
279
|
+
v += "\t";
|
|
280
|
+
else
|
|
281
|
+
throw bad(`unsupported escape \\${e ?? ""} in \`${key}\``);
|
|
282
|
+
}
|
|
283
|
+
else
|
|
284
|
+
v += c;
|
|
285
|
+
}
|
|
286
|
+
throw bad(`unterminated string in \`${key}\``);
|
|
287
|
+
};
|
|
288
|
+
skipWs();
|
|
289
|
+
let value;
|
|
290
|
+
if (rest[at] === "[") {
|
|
291
|
+
const items = [];
|
|
292
|
+
at++;
|
|
293
|
+
for (;;) {
|
|
294
|
+
skipWs();
|
|
295
|
+
if (rest[at] === "]") {
|
|
296
|
+
at++;
|
|
297
|
+
break;
|
|
298
|
+
}
|
|
299
|
+
items.push(readString());
|
|
300
|
+
skipWs();
|
|
301
|
+
if (rest[at] === ",") {
|
|
302
|
+
at++;
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
if (rest[at] === "]") {
|
|
306
|
+
at++;
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
throw bad(`\`${key}\` is not a one-line array of strings`);
|
|
310
|
+
}
|
|
311
|
+
value = items;
|
|
312
|
+
}
|
|
313
|
+
else {
|
|
314
|
+
value = readString();
|
|
315
|
+
}
|
|
316
|
+
skipWs();
|
|
317
|
+
if (at < rest.length && rest[at] !== "#")
|
|
318
|
+
throw bad(`unexpected text after \`${key}\``);
|
|
319
|
+
if (key === "setup") {
|
|
320
|
+
if (!Array.isArray(value))
|
|
321
|
+
throw bad("`setup` is an array of commands");
|
|
322
|
+
out.setup = value;
|
|
323
|
+
}
|
|
324
|
+
else if (key === "repo" || key === "commit" || key === "tier" || key === "alias" || key === "verify") {
|
|
325
|
+
if (typeof value !== "string")
|
|
326
|
+
throw bad(`\`${key}\` is a string`);
|
|
327
|
+
out[key] = value;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return out;
|
|
331
|
+
}
|
|
332
|
+
export function renderConfig(c, notes = [], build) {
|
|
333
|
+
const lines = [
|
|
334
|
+
"# promigence.toml — the environment this repo forks from.",
|
|
335
|
+
"# Written by `promigence init`. Build it with the image form init printed (building straight",
|
|
336
|
+
"# from a repository is not open in this beta):",
|
|
337
|
+
`# ${build ?? "promigence snapshot create --image IMAGE --setup 'git clone REPO /work/repo && git -C /work/repo checkout COMMIT' --verify 'cd /work/repo && VERIFY' --wait"}`,
|
|
338
|
+
...notes.map((n) => `# ${n}`),
|
|
339
|
+
"",
|
|
340
|
+
`repo = ${tomlString(c.repo)}`,
|
|
341
|
+
"# A branch is not a pin: this is the 40-character sha the snapshot is built at.",
|
|
342
|
+
`commit = ${tomlString(c.commit)}`,
|
|
343
|
+
`tier = ${tomlString(c.tier)}`,
|
|
344
|
+
];
|
|
345
|
+
if (c.alias)
|
|
346
|
+
lines.push(`alias = ${tomlString(c.alias)}`);
|
|
347
|
+
lines.push("", "# Ordered; each must exit 0.", `setup = ${tomlArray(c.setup)}`, "", "# The command that decides whether the snapshot exists at all. If it does not exit 0,", "# nothing is stored (the build's time is billed).", `verify = ${tomlString(c.verify)}`, "", "[guards]", "# Checked by the builder before anything is stored. A failed guard names itself.", `toolchain_present = ${tomlArray(c.toolchain)}`);
|
|
348
|
+
if (c.lockfile)
|
|
349
|
+
lines.push(`lockfile_unchanged = ${tomlString(c.lockfile)}`);
|
|
350
|
+
lines.push("install_sane = true", "verify_warm_ratio = true", "");
|
|
351
|
+
return lines.join("\n");
|
|
352
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export declare const FENCE_BEGIN = "<!-- promigence:begin -->";
|
|
2
|
+
export declare const FENCE_END = "<!-- promigence:end -->";
|
|
3
|
+
export declare const FENCE_MAX_LINES = 8;
|
|
4
|
+
export declare function fenceBody(): string;
|
|
5
|
+
export interface Artifact {
|
|
6
|
+
path: string;
|
|
7
|
+
kind: "fence" | "file";
|
|
8
|
+
action: "created" | "updated" | "unchanged" | "would-create" | "would-update" | "skipped";
|
|
9
|
+
reason?: string;
|
|
10
|
+
diff: string[];
|
|
11
|
+
}
|
|
12
|
+
export interface InstallResult {
|
|
13
|
+
dir: string;
|
|
14
|
+
artifacts: Artifact[];
|
|
15
|
+
changed: boolean;
|
|
16
|
+
memoryFile?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function spliceFence(text: string, fence: string): string;
|
|
19
|
+
export declare function install(dir: string, { write, create }: {
|
|
20
|
+
write: boolean;
|
|
21
|
+
create: boolean;
|
|
22
|
+
}): InstallResult;
|
|
23
|
+
export declare function installLines(res: InstallResult, checking: boolean): string[];
|
package/dist/install.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative } from "node:path";
|
|
3
|
+
import { renderGuide } from "./guide.js";
|
|
4
|
+
import { DOCS_URL, GOLDEN_PATH, LIVE_SUMMARY, VERSION, WAIT_TIMEOUT_DEFAULT } from "./surface.js";
|
|
5
|
+
export const FENCE_BEGIN = "<!-- promigence:begin -->";
|
|
6
|
+
export const FENCE_END = "<!-- promigence:end -->";
|
|
7
|
+
export const FENCE_MAX_LINES = 8;
|
|
8
|
+
export function fenceBody() {
|
|
9
|
+
return [
|
|
10
|
+
FENCE_BEGIN,
|
|
11
|
+
`## Promigence (promigence@${VERSION}) — run \`promigence agent-guide\` before using this tool`,
|
|
12
|
+
"Environment runtime: `verify → snapshot → fork(N) → exec → validity report`, billed per second.",
|
|
13
|
+
`Golden path: \`${GOLDEN_PATH}\``,
|
|
14
|
+
"Always `--json` (one envelope: `ok`, `error{code,message,fix}`, `next[]`) and always `--cap USD`.",
|
|
15
|
+
`Exits: 3 = env (ours, replayable) · 4 = task (yours) · 5 = over --cap · 11 = wait expired (${WAIT_TIMEOUT_DEFAULT}, nothing killed).`,
|
|
16
|
+
"Never put a key on argv: `auth login` takes `--from-env VAR` or stdin; `secrets set` also `--from-file P`.",
|
|
17
|
+
FENCE_END,
|
|
18
|
+
].join("\n");
|
|
19
|
+
}
|
|
20
|
+
const MEMORY_CANDIDATES = ["AGENTS.md", "CLAUDE.md"];
|
|
21
|
+
function skillMd() {
|
|
22
|
+
return [
|
|
23
|
+
"---",
|
|
24
|
+
"name: promigence",
|
|
25
|
+
`description: >-`,
|
|
26
|
+
` Run verified, reproducible, forkable execution environments with the promigence CLI: fork N`,
|
|
27
|
+
` sandboxes from a verified snapshot, exec a command in each, and read the validity report`,
|
|
28
|
+
` that separates environment failures from task failures. Use whenever a task needs many`,
|
|
29
|
+
` identical, disposable, pre-built repo environments (evals, RL rollouts, batch verification).`,
|
|
30
|
+
"---",
|
|
31
|
+
"",
|
|
32
|
+
renderGuide(),
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
function cursorMdc() {
|
|
36
|
+
return [
|
|
37
|
+
"---",
|
|
38
|
+
"description: promigence — verified, forkable execution environments (environment runtime)",
|
|
39
|
+
"alwaysApply: true",
|
|
40
|
+
"---",
|
|
41
|
+
"",
|
|
42
|
+
renderGuide(),
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
export function spliceFence(text, fence) {
|
|
46
|
+
const begin = text.indexOf(FENCE_BEGIN);
|
|
47
|
+
const end = text.indexOf(FENCE_END);
|
|
48
|
+
if (begin >= 0 && end > begin) {
|
|
49
|
+
return text.slice(0, begin) + fence + text.slice(end + FENCE_END.length);
|
|
50
|
+
}
|
|
51
|
+
if (text === "")
|
|
52
|
+
return fence + "\n";
|
|
53
|
+
const sep = text.endsWith("\n\n") ? "" : text.endsWith("\n") ? "\n" : "\n\n";
|
|
54
|
+
return text + sep + fence + "\n";
|
|
55
|
+
}
|
|
56
|
+
function diffLines(before, after, cap = 40) {
|
|
57
|
+
if (before === after)
|
|
58
|
+
return [];
|
|
59
|
+
const a = before === "" ? [] : before.split("\n");
|
|
60
|
+
const b = after.split("\n");
|
|
61
|
+
let head = 0;
|
|
62
|
+
while (head < a.length && head < b.length && a[head] === b[head])
|
|
63
|
+
head++;
|
|
64
|
+
let tail = 0;
|
|
65
|
+
while (tail < a.length - head && tail < b.length - head && a[a.length - 1 - tail] === b[b.length - 1 - tail])
|
|
66
|
+
tail++;
|
|
67
|
+
const removed = a.slice(head, a.length - tail).map((l) => `-${l}`);
|
|
68
|
+
const added = b.slice(head, b.length - tail).map((l) => `+${l}`);
|
|
69
|
+
const out = [`@@ line ${head + 1} @@`, ...removed, ...added];
|
|
70
|
+
return out.length <= cap ? out : [...out.slice(0, cap), `… ${out.length - cap} more lines`];
|
|
71
|
+
}
|
|
72
|
+
function plan(dir, rel, kind, want, write, create) {
|
|
73
|
+
const full = join(dir, rel);
|
|
74
|
+
const exists = existsSync(full);
|
|
75
|
+
const before = exists ? readFileSync(full, "utf8") : "";
|
|
76
|
+
const after = kind === "fence" ? spliceFence(before, want) : want;
|
|
77
|
+
const diff = diffLines(before, after, kind === "fence" ? 40 : 4);
|
|
78
|
+
if (!diff.length)
|
|
79
|
+
return { path: rel, kind, action: "unchanged", diff };
|
|
80
|
+
if (!write)
|
|
81
|
+
return { path: rel, kind, action: exists ? "would-update" : "would-create", diff };
|
|
82
|
+
if (!exists && !create && kind === "file")
|
|
83
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
84
|
+
else if (!exists)
|
|
85
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
86
|
+
writeFileSync(full, after);
|
|
87
|
+
return { path: rel, kind, action: exists ? "updated" : "created", diff };
|
|
88
|
+
}
|
|
89
|
+
export function install(dir, { write, create }) {
|
|
90
|
+
const artifacts = [];
|
|
91
|
+
const fence = fenceBody();
|
|
92
|
+
const found = MEMORY_CANDIDATES.filter((f) => existsSync(join(dir, f)));
|
|
93
|
+
const targets = found.length ? found : create ? ["AGENTS.md"] : [];
|
|
94
|
+
if (!targets.length) {
|
|
95
|
+
artifacts.push({
|
|
96
|
+
path: MEMORY_CANDIDATES.join(" / "),
|
|
97
|
+
kind: "fence",
|
|
98
|
+
action: "skipped",
|
|
99
|
+
reason: "neither AGENTS.md nor CLAUDE.md exists; pass --create to write AGENTS.md",
|
|
100
|
+
diff: [],
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
for (const f of targets)
|
|
104
|
+
artifacts.push(plan(dir, f, "fence", fence, write, create));
|
|
105
|
+
artifacts.push(plan(dir, join(".claude", "skills", "promigence", "SKILL.md"), "file", skillMd(), write, create));
|
|
106
|
+
artifacts.push(plan(dir, join(".cursor", "rules", "promigence.mdc"), "file", cursorMdc(), write, create));
|
|
107
|
+
const memory = targets[0];
|
|
108
|
+
return {
|
|
109
|
+
dir,
|
|
110
|
+
artifacts,
|
|
111
|
+
changed: artifacts.some((a) => a.action !== "unchanged" && a.action !== "skipped"),
|
|
112
|
+
...(memory ? { memoryFile: memory } : {}),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
export function installLines(res, checking) {
|
|
116
|
+
const lines = [`${checking ? "promigence agent-guide --check" : "promigence agent-guide --install"}: ${relative(process.cwd(), res.dir) || "."}`];
|
|
117
|
+
for (const a of res.artifacts) {
|
|
118
|
+
lines.push(` ${a.action.padEnd(12)} ${a.path}${a.reason ? ` (${a.reason})` : ""}`);
|
|
119
|
+
for (const d of a.diff)
|
|
120
|
+
lines.push(` ${d}`);
|
|
121
|
+
}
|
|
122
|
+
if (!res.changed)
|
|
123
|
+
lines.push(" already current — nothing written");
|
|
124
|
+
lines.push(` guide: promigence agent-guide · ${DOCS_URL}`);
|
|
125
|
+
lines.push(` live: ${LIVE_SUMMARY}`);
|
|
126
|
+
return lines;
|
|
127
|
+
}
|
package/dist/io.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { PromigenceError, type AccountNotice, type ForkStats, type GradeStats, type ReportSandbox, type RunReport, type Totals } from "./sdk/index.js";
|
|
2
|
+
import { type Envelope, type NextHint } from "./surface.js";
|
|
3
|
+
export type Mode = "json" | "text";
|
|
4
|
+
export declare const seenGuide: () => boolean;
|
|
5
|
+
export declare function markGuideSeen(): void;
|
|
6
|
+
export declare function isGoneReader(code: string | undefined): boolean;
|
|
7
|
+
export declare function writeBytes(stream: NodeJS.WriteStream, data: Uint8Array): Promise<void>;
|
|
8
|
+
export declare function write(stream: NodeJS.WriteStream, text: string): Promise<void>;
|
|
9
|
+
export declare function setBeforeExit(fn: (() => Promise<unknown>) | undefined): void;
|
|
10
|
+
export declare function exitWith(code: number): Promise<never>;
|
|
11
|
+
export declare function recordNotice(n: AccountNotice): void;
|
|
12
|
+
export declare function noticeLine(n: AccountNotice): string;
|
|
13
|
+
export declare const PLACEHOLDER: RegExp;
|
|
14
|
+
export declare function emit(mode: Mode, env: Envelope, textLines: string[], exit: number, pointer: boolean, textTo?: "payload" | "stderr"): Promise<never>;
|
|
15
|
+
export declare function okEnvelope(command: string, fields: Record<string, unknown>, next: NextHint[]): Envelope;
|
|
16
|
+
export declare function noCommandFix(example: string, platform?: NodeJS.Platform): string;
|
|
17
|
+
export declare function usageError(message: string, fix: string): PromigenceError;
|
|
18
|
+
export declare const USAGE_DOCS = "https://www.promigence.ai/docs/errors#usage";
|
|
19
|
+
export declare function overMost(flag: string, given: string, most: number, mostText: string, page?: boolean): number;
|
|
20
|
+
export declare function toPromigenceError(err: unknown): PromigenceError;
|
|
21
|
+
export declare function errorEnvelope(command: string, err: PromigenceError, next: NextHint[]): Envelope;
|
|
22
|
+
export declare function errorTextLines(command: string, err: PromigenceError, exit: number, next: NextHint[]): string[];
|
|
23
|
+
export declare function pickExit(codes: readonly number[]): number;
|
|
24
|
+
export declare function parseDuration(value: string, flag: string): number;
|
|
25
|
+
export declare function parseCommandTimeout(value: string, flag: string): number;
|
|
26
|
+
export declare function parseCount(value: string, flag: string): number;
|
|
27
|
+
export declare function parseUsd(value: string, flag: string): number;
|
|
28
|
+
export declare const MAX_OUTPUT_CEILING: number;
|
|
29
|
+
export declare function parseMaxOutput(value: string, flag?: string): number;
|
|
30
|
+
export declare function parseBytes(value: string, flag: string): number;
|
|
31
|
+
export declare const ms: (n: number | undefined) => string;
|
|
32
|
+
export declare function forkLine(fork: ForkStats | undefined): string;
|
|
33
|
+
export declare function totalsLine(t: Totals | undefined): string;
|
|
34
|
+
export declare function forkSummary(fork: ForkStats | undefined): Record<string, unknown>;
|
|
35
|
+
export declare function reportSummaryLines(report: RunReport): string[];
|
|
36
|
+
export declare function sandboxGrade(s: Pick<ReportSandbox, "passed" | "score" | "holdout_passed" | "holdout_score">): string;
|
|
37
|
+
export declare function gradeLine(g: GradeStats | undefined): string | undefined;
|