Remote command execution for Sprites, with an API that mirrors Node.js child_process.
- Node.js 24.0.0 or later
- No external dependencies (uses only Node.js standard library)
npm install @fly/spritesimport { SpritesClient } from '@fly/sprites';
const client = new SpritesClient(process.env.SPRITES_TOKEN!);
const sprite = client.sprite('my-sprite');
// Event-based API (most Node.js-like)
const cmd = sprite.spawn('ls', ['-la']);
cmd.stdout.on('data', (chunk) => {
process.stdout.write(chunk);
});
cmd.on('exit', (code) => {
console.log(`Exited with code ${code}`);
});
// Promise-based API
const { stdout } = await sprite.exec('echo hello');
console.log(stdout); // 'hello\n'Main client for interacting with the Sprites API.
const client = new SpritesClient(token, options);Options:
baseURL: API base URL (default: https://api.sprites.dev)timeout: HTTP request timeout in ms (default: 30000)controlMode: use multiplexed control connections for supported operations (default: false)
Methods:
sprite(name: string): Sprite- Get a handle to a spritecreateSprite(name: string, options?: CreateSpriteOptions): Promise<Sprite>- Create a new spritecreateSprite(name: string, config?: SpriteConfig, options?: Omit<CreateSpriteOptions, 'config'>): Promise<Sprite>- Backward-compatible creation formgetSprite(name: string): Promise<Sprite>- Get sprite informationlistSprites(options?: ListOptions): Promise<SpriteList>- List spriteswatchSprites(options?): Promise<SpriteListStream>- Stream live sprite state and organization countslistAllSprites(prefix?: string): Promise<Sprite[]>- List all sprites (handles pagination)deleteSprite(name: string): Promise<void>- Delete a spriteupgradeSprite(name: string): Promise<void>- Upgrade a spriterestartSprite(name: string): Promise<RestartSpriteResult>- Restart the backing machinecheckSprite(name: string): Promise<SpriteCheck>- Check sprite healthupdateSprite(name: string, options: UpdateSpriteOptions): Promise<Sprite>- Update URL settings and/or labelsupdateURLSettings(name: string, settings: URLSettings): Promise<void>- Update only URL access settingsstatic createToken(flyMacaroon: string, orgSlug: string, inviteCode?: string): Promise<string>- Create an access token
Creation options include config, environment, urlSettings, labels, waitForCapacity, and the default or dev runtime. List options include prefix, maxResults, continuationToken, and bulkLoad.
Represents a sprite instance.
const sprite = client.sprite('my-sprite');Command Execution Methods:
// Event-based (mirrors child_process.spawn)
spawn(command: string, args?: string[], options?: SpawnOptions): SpriteCommand
// Promise-based (mirrors child_process.exec)
exec(command: string, options?: ExecOptions): Promise<ExecResult>
// Promise-based with separate args (mirrors child_process.execFile)
execFile(file: string, args?: string[], options?: ExecOptions): Promise<ExecResult>Session Methods:
createSession(command: string, args?: string[], options?: SpawnOptions): SpriteCommand- Create a detachable sessionattachSession(sessionId: string, options?: SpawnOptions): SpriteCommand- Attach to a sessionlistSessions(): Promise<Session[]>- List active sessions
Management Methods:
delete(): Promise<void>- Delete the spritedestroy(): Promise<void>- Alias for deleteupgrade(): Promise<void>- Upgrade the spriterestart(): Promise<RestartSpriteResult>- Restart the backing machinecheck(): Promise<SpriteCheck>- Check sprite healthupdate(options: UpdateSpriteOptions): Promise<Sprite>- Update URL settings and/or labelsupdateURLSettings(settings: URLSettings): Promise<void>- Update URL access settings
Sprite also exposes checkpoint, service, network-policy, filesystem, port-proxy, and control-connection methods. All resource names and IDs are safely path-encoded by the SDK.
Current environment APIs include:
execFileHTTP(...)for non-TTY execution without WebSocketskillSession(...)with an async iterable progress streamwatchPorts()for snapshots and live port eventsrestartService(...)andgetServiceLogs(...)- network, privileges, and resource policy get/update/delete operations
- filesystem ownership changes and live filesystem watching
HTTP exec protocol limitation: The current
execFileHTTPresponse format prefixes each frame with a type byte but does not include a frame length. HTTP intermediaries are allowed to split or combine transport chunks, so the SDK cannot reliably reconstruct large or high-volume output. The method rejects unrecognized frame types and supportssignalandtimeoutcancellation, but combined frames can remain ambiguous. Prefer the WebSocket-basedexecorexecFilemethods until the server protocol provides explicit frame lengths.
Represents a running command. Extends EventEmitter.
Properties:
stdin: Writable- Standard input streamstdout: Readable- Standard output streamstderr: Readable- Standard error stream
Methods:
wait(): Promise<number>- Wait for exit and return exit codekill(signal?: string): void- Kill the commandclose(): void- Close the command's WebSocket and end its output streamsresize(cols: number, rows: number): void- Resize TTY (if TTY mode enabled)exitCode(): number- Get exit code (-1 if not exited)
Events:
exit-(code: number) => void- Emitted when command exitserror-(error: Error) => void- Emitted on errormessage-(msg: any) => void- Emitted for text messages (e.g., port notifications)
interface SpawnOptions {
cwd?: string; // Working directory
env?: Record<string, string>; // Environment variables
tty?: boolean; // Enable TTY mode
rows?: number; // TTY rows
cols?: number; // TTY columns
detachable?: boolean; // Create detachable session
sessionId?: string; // Attach to existing session
controlMode?: boolean; // Enable control mode
}Extends SpawnOptions with:
encoding?: BufferEncoding- Output encoding (default: 'utf8')maxBuffer?: number- Maximum buffer size (default: 10MB)signal?: AbortSignal- Abort execution and close its WebSockettimeout?: number- Abort execution after this many milliseconds (0disables the timeout)
// Streaming output
const cmd = sprite.spawn('ls', ['-la']);
cmd.stdout.pipe(process.stdout);
cmd.stderr.pipe(process.stderr);
await cmd.wait();
// Capture output
const { stdout, stderr } = await sprite.exec('ls -la');
console.log(stdout);WebSocket commands may remain quiet for extended periods; the SDK waits for the server to report command completion instead of treating a lack of output as a connection failure.
const cmd = sprite.spawn('bash', [], {
tty: true,
rows: 24,
cols: 80,
});
process.stdin.pipe(cmd.stdin);
cmd.stdout.pipe(process.stdout);
// Resize terminal
cmd.resize(100, 30);const cmd = sprite.spawn('python', ['app.py']);
cmd.on('message', (msg) => {
if (msg.type === 'port_opened') {
console.log(`Port ${msg.port} opened by PID ${msg.pid}`);
// Start local proxy, etc.
}
});// Create a detachable session
const session = sprite.createSession('bash');
await session.wait();
// List sessions
const sessions = await sprite.listSessions();
console.log(sessions);
// Attach to a session
const attached = sprite.attachSession(sessions[0].id);import { ExecError } from '@fly/sprites';
try {
await sprite.exec('false');
} catch (error) {
if (error instanceof ExecError) {
console.log('Exit code:', error.exitCode);
console.log('Stdout:', error.stdout);
console.log('Stderr:', error.stderr);
}
}The SDK adds privacy-safe client signals to Sprites API requests and WebSocket
handshakes. These include coarse process context in Fly-Client-* headers and
the SDK version in User-Agent; they do not include command arguments or
environment values.
Set SPRITES_CLIENT_SIGNALS=0 to disable client-signal detection and headers.
The SDK version remains in User-Agent. The values off, false, no, and
disabled are also accepted.
Signals are detected once per process, on the first API call, so set this before the first request. Changing it afterwards has no effect.
// Create a sprite
const sprite = await client.createSprite('my-sprite', {
config: {
ramMB: 512,
cpus: 1,
region: 'ord',
},
urlSettings: { auth: 'sprite', privateAccess: 'admins' },
labels: ['development'],
waitForCapacity: true,
runtime: 'dev',
});
// List sprites
const sprites = await client.listAllSprites();
// Update and inspect lifecycle state
await sprite.update({ labels: ['development', 'typescript'] });
const health = await sprite.check();
await sprite.restart();
// Delete a sprite
await sprite.delete();MIT