@replayio/app-building 1.25.0 → 1.26.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio/app-building",
3
- "version": "1.25.0",
3
+ "version": "1.26.0",
4
4
  "description": "Library for managing agentic app-building containers",
5
5
  "type": "module",
6
6
  "exports": {
@@ -13,6 +13,10 @@
13
13
  "dist"
14
14
  ],
15
15
  "scripts": {
16
- "prepublishOnly": "tsc -p tsconfig.json"
16
+ "build": "tsup",
17
+ "prepublishOnly": "tsup"
18
+ },
19
+ "devDependencies": {
20
+ "tsup": "^8.5.1"
17
21
  }
18
22
  }
@@ -1,25 +0,0 @@
1
- import type { AgentState } from "./container";
2
- export interface RegistryEntry extends AgentState {
3
- startedAt: string;
4
- stoppedAt?: string;
5
- }
6
- export interface ContainerRegistry {
7
- log(state: AgentState): void;
8
- markStopped(containerName?: string): void;
9
- clearStopped(containerName: string): void;
10
- getRecent(limit?: number): RegistryEntry[];
11
- find(containerName: string): RegistryEntry | null;
12
- findAlive(): Promise<RegistryEntry[]>;
13
- }
14
- export declare class FileContainerRegistry implements ContainerRegistry {
15
- private filePath;
16
- constructor(filePath: string);
17
- private readRegistry;
18
- private updateEntry;
19
- log(state: AgentState): void;
20
- markStopped(containerName?: string): void;
21
- clearStopped(containerName: string): void;
22
- getRecent(limit?: number): RegistryEntry[];
23
- find(containerName: string): RegistryEntry | null;
24
- findAlive(): Promise<RegistryEntry[]>;
25
- }
@@ -1,78 +0,0 @@
1
- import { readFileSync, writeFileSync, appendFileSync, existsSync } from "fs";
2
- import { probeAlive } from "./container-utils";
3
- export class FileContainerRegistry {
4
- filePath;
5
- constructor(filePath) {
6
- this.filePath = filePath;
7
- }
8
- readRegistry() {
9
- if (!existsSync(this.filePath))
10
- return { lines: [], entries: [] };
11
- const lines = readFileSync(this.filePath, "utf-8").split("\n").filter((l) => l.trim());
12
- const entries = lines.map((line) => {
13
- try {
14
- return JSON.parse(line);
15
- }
16
- catch {
17
- return null;
18
- }
19
- });
20
- return { lines, entries };
21
- }
22
- updateEntry(match, update) {
23
- const { lines, entries } = this.readRegistry();
24
- for (let i = entries.length - 1; i >= 0; i--) {
25
- const entry = entries[i];
26
- if (entry && match(entry)) {
27
- update(entry);
28
- lines[i] = JSON.stringify(entry);
29
- writeFileSync(this.filePath, lines.join("\n") + "\n");
30
- return;
31
- }
32
- }
33
- }
34
- log(state) {
35
- const entry = {
36
- ...state,
37
- startedAt: new Date().toISOString(),
38
- };
39
- appendFileSync(this.filePath, JSON.stringify(entry) + "\n");
40
- }
41
- markStopped(containerName) {
42
- this.updateEntry((e) => !e.stoppedAt && (!containerName || e.containerName === containerName), (e) => { e.stoppedAt = new Date().toISOString(); });
43
- }
44
- clearStopped(containerName) {
45
- this.updateEntry((e) => e.containerName === containerName && !!e.stoppedAt, (e) => { delete e.stoppedAt; });
46
- }
47
- getRecent(limit = 20) {
48
- const { entries } = this.readRegistry();
49
- return entries.filter((e) => e !== null).slice(-limit);
50
- }
51
- find(containerName) {
52
- const entries = this.getRecent(100);
53
- for (let i = entries.length - 1; i >= 0; i--) {
54
- if (entries[i].containerName === containerName) {
55
- return entries[i];
56
- }
57
- }
58
- return null;
59
- }
60
- async findAlive() {
61
- const entries = this.getRecent();
62
- const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;
63
- const candidates = entries.filter((e) => new Date(e.startedAt).getTime() > oneDayAgo);
64
- const aliveResults = await Promise.all(candidates.map(async (entry) => ({
65
- entry,
66
- alive: await probeAlive(entry),
67
- })));
68
- for (const r of aliveResults) {
69
- if (r.alive && r.entry.stoppedAt) {
70
- this.clearStopped(r.entry.containerName);
71
- }
72
- else if (!r.alive && !r.entry.stoppedAt) {
73
- this.markStopped(r.entry.containerName);
74
- }
75
- }
76
- return aliveResults.filter((r) => r.alive).map((r) => r.entry);
77
- }
78
- }
@@ -1,5 +0,0 @@
1
- import type { AgentState } from "./container";
2
- import type { RegistryEntry } from "./container-registry";
3
- import type { HttpOptions } from "./http-client";
4
- export declare function httpOptsFor(state: AgentState): HttpOptions;
5
- export declare function probeAlive(entry: RegistryEntry): Promise<boolean>;
@@ -1,30 +0,0 @@
1
- export function httpOptsFor(state) {
2
- if (state.type === "remote" && state.flyMachineId) {
3
- return { headers: { "fly-force-instance-id": state.flyMachineId } };
4
- }
5
- return {};
6
- }
7
- export async function probeAlive(entry) {
8
- try {
9
- const headers = {};
10
- if (entry.type === "remote" && entry.flyMachineId) {
11
- headers["fly-force-instance-id"] = entry.flyMachineId;
12
- }
13
- const res = await fetch(`${entry.baseUrl}/status`, {
14
- headers,
15
- signal: AbortSignal.timeout(5000),
16
- });
17
- if (!res.ok)
18
- return false;
19
- // Verify the response is actually from the expected container,
20
- // not a different machine on the same Fly app.
21
- const body = await res.json();
22
- if (body.containerName && body.containerName !== entry.containerName) {
23
- return false;
24
- }
25
- return true;
26
- }
27
- catch {
28
- return false;
29
- }
30
- }
@@ -1,58 +0,0 @@
1
- import type { ContainerRegistry, RegistryEntry } from "./container-registry";
2
- import type { InfisicalConfig } from "./secrets";
3
- export interface AgentState {
4
- type: "local" | "remote";
5
- containerName: string;
6
- port: number;
7
- baseUrl: string;
8
- flyApp?: string;
9
- flyMachineId?: string;
10
- flyVolumeId?: string;
11
- }
12
- export interface ContainerConfig {
13
- projectRoot?: string;
14
- /** Infisical credentials — required for all containers. */
15
- infisical: InfisicalConfig;
16
- registry: ContainerRegistry;
17
- flyToken?: string;
18
- flyApp?: string;
19
- imageRef?: string;
20
- webhookUrl?: string;
21
- webhookSecret?: string;
22
- /** GET endpoint to fetch the next task when the local queue is empty. Uses webhookSecret for auth. */
23
- taskWebhookUrl?: string;
24
- /** POST endpoint to receive tasks added by the add-task script. When set, add-task POSTs tasks here instead of writing to the local task file. Uses webhookSecret for auth. */
25
- addTaskWebhookUrl?: string;
26
- /** Start the container in detached mode. It will exit after processing all messages and tasks. */
27
- detached?: boolean;
28
- /** Initial prompt to queue at container startup (before the HTTP server accepts external requests). */
29
- initialPrompt?: string;
30
- /** Override the host port for local containers (default: auto-selected). */
31
- localPort?: number;
32
- /** Absorb task files from other containers at startup. Default: false. */
33
- absorbTasks?: boolean;
34
- /** Container name prefix. Default: "app-building". */
35
- namePrefix?: string;
36
- /** Agent command override (e.g. "codex", "gemini"). Default: "claude". */
37
- agentCommand?: string;
38
- }
39
- export interface RepoOptions {
40
- repoUrl: string;
41
- cloneBranch: string;
42
- pushBranch: string;
43
- }
44
- export declare function loadDotEnv(projectRoot: string): Record<string, string>;
45
- export declare function buildImage(config: ContainerConfig): void;
46
- /**
47
- * Start a container (local Docker or remote Fly.io based on config).
48
- * If flyToken and flyApp are set, starts remotely; otherwise locally.
49
- */
50
- export declare function startContainer(config: ContainerConfig, repo: RepoOptions): Promise<AgentState>;
51
- /**
52
- * Stop a container by its state or registry entry.
53
- */
54
- export declare function stopContainer(config: ContainerConfig, state: AgentState | RegistryEntry): Promise<void>;
55
- /**
56
- * Spawn an interactive test container (local only).
57
- */
58
- export declare function spawnTestContainer(config: ContainerConfig): Promise<void>;
package/dist/container.js DELETED
@@ -1,361 +0,0 @@
1
- import { execFileSync, spawn } from "child_process";
2
- import { readFileSync, existsSync } from "fs";
3
- import { resolve } from "path";
4
- import { createMachine, waitForMachine, destroyMachine, listMachines } from "./fly";
5
- import { getImageRef } from "./image-ref";
6
- const IMAGE_NAME = "app-building";
7
- function debugLog(...args) {
8
- if (process.env.DEBUG)
9
- console.log("[container]", ...args);
10
- }
11
- export function loadDotEnv(projectRoot) {
12
- const envPath = resolve(projectRoot, ".env");
13
- if (!existsSync(envPath)) {
14
- return {};
15
- }
16
- const content = readFileSync(envPath, "utf-8");
17
- const vars = {};
18
- for (const line of content.split("\n")) {
19
- const trimmed = line.trim();
20
- if (!trimmed || trimmed.startsWith("#"))
21
- continue;
22
- const eqIndex = trimmed.indexOf("=");
23
- if (eqIndex === -1)
24
- continue;
25
- const key = trimmed.slice(0, eqIndex).trim();
26
- let value = trimmed.slice(eqIndex + 1).trim();
27
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
28
- value = value.slice(1, -1);
29
- }
30
- vars[key] = value;
31
- }
32
- return vars;
33
- }
34
- export function buildImage(config) {
35
- if (!config.projectRoot)
36
- throw new Error("projectRoot is required for local Docker operations");
37
- console.log("Building Docker image...");
38
- execFileSync("docker", ["build", "--platform", "linux/amd64", "--network", "host", "-t", IMAGE_NAME, config.projectRoot], {
39
- stdio: "inherit",
40
- timeout: 600000,
41
- });
42
- console.log("Docker image built successfully.");
43
- }
44
- function ensureImageExists(projectRoot) {
45
- try {
46
- execFileSync("docker", ["image", "inspect", IMAGE_NAME], {
47
- stdio: "ignore",
48
- });
49
- }
50
- catch {
51
- console.log("Building Docker image...");
52
- execFileSync("docker", ["build", "--platform", "linux/amd64", "--network", "host", "-t", IMAGE_NAME, projectRoot], {
53
- stdio: "inherit",
54
- timeout: 600000,
55
- });
56
- console.log("Docker image built successfully.");
57
- }
58
- }
59
- function findFreePort() {
60
- let port = 3100;
61
- try {
62
- const out = execFileSync("ss", ["-tlnH"], {
63
- encoding: "utf-8",
64
- timeout: 5000,
65
- });
66
- const usedPorts = new Set();
67
- for (const match of out.matchAll(/:(\d+)\s/g)) {
68
- usedPorts.add(parseInt(match[1], 10));
69
- }
70
- while (usedPorts.has(port))
71
- port++;
72
- }
73
- catch {
74
- // ss not available, just use default
75
- }
76
- return port;
77
- }
78
- function buildContainerEnv(repo, infisical, extra = {}) {
79
- const env = {
80
- REPO_URL: repo.repoUrl,
81
- CLONE_BRANCH: repo.cloneBranch,
82
- PUSH_BRANCH: repo.pushBranch,
83
- GIT_AUTHOR_NAME: "App Builder",
84
- GIT_AUTHOR_EMAIL: "app-builder@localhost",
85
- GIT_COMMITTER_NAME: "App Builder",
86
- GIT_COMMITTER_EMAIL: "app-builder@localhost",
87
- PLAYWRIGHT_BROWSERS_PATH: "/opt/playwright",
88
- INFISICAL_TOKEN: infisical.token,
89
- INFISICAL_PROJECT_ID: infisical.projectId,
90
- INFISICAL_ENVIRONMENT: infisical.environment,
91
- ...extra,
92
- };
93
- if (process.env.DEBUG) {
94
- env.DEBUG = process.env.DEBUG;
95
- }
96
- return env;
97
- }
98
- function buildExtraEnv(config, containerName) {
99
- const extra = {
100
- PORT: "3000",
101
- CONTAINER_NAME: containerName,
102
- };
103
- if (config.webhookUrl)
104
- extra.WEBHOOK_URL = config.webhookUrl;
105
- if (config.taskWebhookUrl)
106
- extra.TASK_WEBHOOK_URL = config.taskWebhookUrl;
107
- if (config.addTaskWebhookUrl)
108
- extra.ADD_TASK_WEBHOOK_URL = config.addTaskWebhookUrl;
109
- if (config.webhookSecret)
110
- extra.WEBHOOK_SECRET = config.webhookSecret;
111
- if (config.detached)
112
- extra.DETACHED = "1";
113
- if (config.initialPrompt)
114
- extra.INITIAL_PROMPT = config.initialPrompt;
115
- if (config.absorbTasks)
116
- extra.ABSORB_TASKS = "1";
117
- if (config.agentCommand)
118
- extra.AGENT_COMMAND = config.agentCommand;
119
- return extra;
120
- }
121
- function isRemote(config) {
122
- return !!(config.flyToken && config.flyApp);
123
- }
124
- // ---------------------------------------------------------------------------
125
- // Local container
126
- // ---------------------------------------------------------------------------
127
- async function startLocalContainer(config, repo) {
128
- buildImage(config);
129
- const uniqueId = Math.random().toString(36).slice(2, 8);
130
- const prefix = config.namePrefix ?? "app-building";
131
- const containerName = `${prefix}-${uniqueId}`;
132
- const containerPort = 3000;
133
- const hostPort = config.localPort ?? findFreePort();
134
- const extra = buildExtraEnv(config, containerName);
135
- extra.PORT = String(containerPort);
136
- const containerEnv = buildContainerEnv(repo, config.infisical, extra);
137
- const args = ["run", "--platform", "linux/amd64", "-d", "--rm", "--name", containerName];
138
- args.push("-p", `${hostPort}:${containerPort}`);
139
- for (const [k, v] of Object.entries(containerEnv)) {
140
- args.push("--env", `${k}=${v}`);
141
- }
142
- args.push(IMAGE_NAME);
143
- const containerId = execFileSync("docker", args, {
144
- encoding: "utf-8",
145
- timeout: 30000,
146
- }).trim();
147
- console.log(`Container started: ${containerId.slice(0, 12)} (${containerName})`);
148
- const baseUrl = `http://127.0.0.1:${hostPort}`;
149
- const maxWait = 120000;
150
- const interval = 1000;
151
- const start = Date.now();
152
- let ready = false;
153
- while (Date.now() - start < maxWait) {
154
- try {
155
- execFileSync("docker", ["inspect", "--format", "{{.State.Running}}", containerName], { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"], timeout: 5000 });
156
- }
157
- catch {
158
- let logs = "";
159
- try {
160
- logs = execFileSync("docker", ["logs", "--tail", "30", containerName], {
161
- encoding: "utf-8",
162
- timeout: 5000,
163
- });
164
- }
165
- catch {
166
- // Container already removed (--rm)
167
- }
168
- throw new Error(`Container exited during startup.${logs ? `\n\n--- container logs ---\n${logs}` : " (no logs available, container was removed)"}`);
169
- }
170
- try {
171
- const res = await fetch(`${baseUrl}/status`);
172
- if (res.ok) {
173
- ready = true;
174
- break;
175
- }
176
- }
177
- catch {
178
- // Not ready yet
179
- }
180
- await new Promise((r) => setTimeout(r, interval));
181
- }
182
- if (!ready) {
183
- throw new Error("Container did not become ready within timeout");
184
- }
185
- return { type: "local", containerName, port: hostPort, baseUrl };
186
- }
187
- function stopLocalContainer(containerName) {
188
- try {
189
- execFileSync("docker", ["stop", containerName], { stdio: "ignore", timeout: 30000 });
190
- }
191
- catch {
192
- // Container may already be stopped
193
- }
194
- }
195
- // ---------------------------------------------------------------------------
196
- // Remote container (Fly.io)
197
- // ---------------------------------------------------------------------------
198
- async function startRemoteContainerImpl(config, repo) {
199
- if (!config.flyToken)
200
- throw new Error("flyToken is required for remote containers");
201
- if (!config.flyApp)
202
- throw new Error("flyApp is required for remote containers");
203
- const imageRef = config.imageRef ?? getImageRef();
204
- const uniqueId = Math.random().toString(36).slice(2, 8);
205
- const prefix = config.namePrefix ?? "app-building";
206
- const machineName = `${prefix}-${uniqueId}`;
207
- const extra = buildExtraEnv(config, machineName);
208
- const containerEnv = buildContainerEnv(repo, config.infisical, extra);
209
- const existing = await listMachines(config.flyApp, config.flyToken);
210
- if (existing.length > 0) {
211
- console.log(`${existing.length} existing machine(s) in ${config.flyApp}:`);
212
- for (const m of existing) {
213
- console.log(` ${m.id} (${m.name}) — ${m.state}`);
214
- }
215
- }
216
- console.log("Creating Fly machine (with volume)...");
217
- let machineId = "";
218
- let volumeId = "";
219
- for (let attempt = 0; attempt < 5; attempt++) {
220
- try {
221
- const result = await createMachine(config.flyApp, config.flyToken, imageRef, containerEnv, machineName);
222
- machineId = result.machineId;
223
- volumeId = result.volumeId;
224
- break;
225
- }
226
- catch (err) {
227
- const msg = err instanceof Error ? err.message : String(err);
228
- if (msg.includes("MANIFEST_UNKNOWN") && attempt < 4) {
229
- console.log("Image not yet available in registry, retrying in 5s...");
230
- await new Promise((r) => setTimeout(r, 5000));
231
- continue;
232
- }
233
- throw err;
234
- }
235
- }
236
- console.log(`Machine created: ${machineId} (volume: ${volumeId})`);
237
- const baseUrl = `https://${config.flyApp}.fly.dev`;
238
- const agentState = {
239
- type: "remote",
240
- containerName: machineName,
241
- port: 443,
242
- baseUrl,
243
- flyApp: config.flyApp,
244
- flyMachineId: machineId,
245
- flyVolumeId: volumeId,
246
- };
247
- console.log("Waiting for machine to start...");
248
- await waitForMachine(config.flyApp, config.flyToken, machineId);
249
- console.log("Machine started.");
250
- const maxWait = 180000;
251
- const interval = 2000;
252
- const start = Date.now();
253
- let ready = false;
254
- while (Date.now() - start < maxWait) {
255
- try {
256
- const res = await fetch(`${baseUrl}/status`, {
257
- headers: { "fly-force-instance-id": machineId },
258
- });
259
- if (res.ok) {
260
- ready = true;
261
- break;
262
- }
263
- }
264
- catch {
265
- // Not ready yet
266
- }
267
- await new Promise((r) => setTimeout(r, interval));
268
- }
269
- if (!ready) {
270
- console.log("Timed out waiting for machine, destroying...");
271
- await destroyMachine(config.flyApp, config.flyToken, machineId, volumeId).catch(() => { });
272
- throw new Error("Remote container did not become ready within timeout");
273
- }
274
- return agentState;
275
- }
276
- async function stopRemoteContainerImpl(config, state) {
277
- if (!state.flyApp || !state.flyMachineId) {
278
- throw new Error("Missing flyApp or flyMachineId in agent state");
279
- }
280
- if (!config.flyToken)
281
- throw new Error("flyToken is required to stop remote container");
282
- console.log(`Destroying Fly machine ${state.flyMachineId}...`);
283
- await destroyMachine(state.flyApp, config.flyToken, state.flyMachineId, state.flyVolumeId);
284
- console.log("Machine destroyed.");
285
- }
286
- // ---------------------------------------------------------------------------
287
- // Public API
288
- // ---------------------------------------------------------------------------
289
- /**
290
- * Start a container (local Docker or remote Fly.io based on config).
291
- * If flyToken and flyApp are set, starts remotely; otherwise locally.
292
- */
293
- export async function startContainer(config, repo) {
294
- const { token, projectId, environment } = config.infisical;
295
- if (!token || !projectId || !environment) {
296
- const missing = [
297
- !token && "token",
298
- !projectId && "projectId",
299
- !environment && "environment",
300
- ].filter(Boolean);
301
- throw new Error(`Missing Infisical credentials: ${missing.join(", ")}. Containers cannot start without Infisical.`);
302
- }
303
- debugLog("startContainer config:", {
304
- projectRoot: config.projectRoot,
305
- flyApp: config.flyApp,
306
- imageRef: config.imageRef,
307
- webhookUrl: config.webhookUrl,
308
- detached: config.detached,
309
- remote: isRemote(config),
310
- initialPrompt: config.initialPrompt ? `${config.initialPrompt.slice(0, 100)}...` : undefined,
311
- });
312
- debugLog("startContainer repo:", repo);
313
- const state = isRemote(config)
314
- ? await startRemoteContainerImpl(config, repo)
315
- : await startLocalContainer(config, repo);
316
- config.registry.log(state);
317
- return state;
318
- }
319
- /**
320
- * Stop a container by its state or registry entry.
321
- */
322
- export async function stopContainer(config, state) {
323
- if (state.type === "remote") {
324
- await stopRemoteContainerImpl(config, state);
325
- }
326
- else {
327
- stopLocalContainer(state.containerName);
328
- }
329
- config.registry.markStopped(state.containerName);
330
- }
331
- /**
332
- * Spawn an interactive test container (local only).
333
- */
334
- export function spawnTestContainer(config) {
335
- if (!config.projectRoot)
336
- throw new Error("projectRoot is required for local Docker operations");
337
- ensureImageExists(config.projectRoot);
338
- const uniqueId = Math.random().toString(36).slice(2, 8);
339
- const containerName = `app-building-test-${uniqueId}`;
340
- const args = ["run", "--platform", "linux/amd64", "-it", "--rm", "--name", containerName];
341
- args.push("-v", `${config.projectRoot}:/repo`);
342
- args.push("-w", "/repo");
343
- args.push("--network", "host");
344
- args.push("--user", `${process.getuid()}:${process.getgid()}`);
345
- args.push("--env", "HOME=/repo/.agent-home");
346
- args.push("--env", "PLAYWRIGHT_BROWSERS_PATH=/opt/playwright");
347
- args.push("--env", `INFISICAL_TOKEN=${config.infisical.token}`);
348
- args.push("--env", `INFISICAL_PROJECT_ID=${config.infisical.projectId}`);
349
- args.push("--env", `INFISICAL_ENVIRONMENT=${config.infisical.environment}`);
350
- args.push(IMAGE_NAME, "bash");
351
- return new Promise((resolvePromise, reject) => {
352
- const child = spawn("docker", args, { stdio: "inherit" });
353
- child.on("close", (code) => {
354
- if (code === 0)
355
- resolvePromise();
356
- else
357
- reject(new Error(`Container exited with code ${code}`));
358
- });
359
- child.on("error", reject);
360
- });
361
- }
package/dist/fly.d.ts DELETED
@@ -1,55 +0,0 @@
1
- /**
2
- * Create a Fly app via the Machines API and allocate IPs so .fly.dev DNS works.
3
- */
4
- export declare function createApp(token: string, name: string, org?: string): Promise<void>;
5
- /**
6
- * Create a Fly Volume in the given region.
7
- * Returns the volume ID.
8
- */
9
- export declare function createVolume(app: string, token: string, name: string, region: string, sizeGb?: number): Promise<string>;
10
- /**
11
- * Delete a Fly Volume.
12
- */
13
- export declare function deleteVolume(app: string, token: string, volumeId: string): Promise<void>;
14
- export interface CreateMachineResult {
15
- machineId: string;
16
- volumeId: string;
17
- }
18
- /**
19
- * Create a Fly Machine with the given image and env vars.
20
- * Creates a volume mounted at /repo for storage.
21
- * Returns the machine ID and volume ID.
22
- */
23
- export declare function createMachine(app: string, token: string, image: string, env: Record<string, string>, name: string): Promise<CreateMachineResult>;
24
- /**
25
- * Wait for a Fly Machine to reach the "started" state.
26
- */
27
- export declare function waitForMachine(app: string, token: string, machineId: string, timeoutMs?: number): Promise<void>;
28
- /**
29
- * Destroy a Fly Machine (force) and its attached volume.
30
- */
31
- export declare function destroyMachine(app: string, token: string, machineId: string, volumeId?: string): Promise<void>;
32
- export interface FlyMachineInfo {
33
- id: string;
34
- name: string;
35
- state: string;
36
- created_at: string;
37
- region: string;
38
- }
39
- /**
40
- * List all machines for a Fly app.
41
- */
42
- export declare function listMachines(app: string, token: string): Promise<FlyMachineInfo[]>;
43
- export interface FlyVolumeInfo {
44
- id: string;
45
- name: string;
46
- state: string;
47
- size_gb: number;
48
- region: string;
49
- created_at: string;
50
- attached_machine_id: string | null;
51
- }
52
- /**
53
- * List all volumes for a Fly app.
54
- */
55
- export declare function listVolumes(app: string, token: string): Promise<FlyVolumeInfo[]>;