> ## Documentation Index
> Fetch the complete documentation index at: https://docs.archil.com/llms.txt
> Use this file to discover all available pages before exploring further.

# TypeScript SDK

> Programmatic access to Archil disks from TypeScript and Node.js applications

The Archil TypeScript SDK is the [`disk`](https://www.npmjs.com/package/disk) package on npm. It's a pure-JavaScript control-plane client and CLI: create disks, list and inspect them, manage who can mount them, and run commands against them with [`disk.exec`](/compute/serverless-execution).

```bash theme={null}
npm install disk
```

`disk` has no native dependencies and works on any platform Node.js does. For the rare case where you need raw protocol access from Node (inodes, delegations, byte-level reads), there's a separate [`@archildata/native`](#low-level-protocol-access) package — most users will not need it.

| Package                                                                  | When to use                                                                                                                                |
| ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ |
| [`disk`](https://www.npmjs.com/package/disk)                             | Manage disks, run commands with `disk.exec`, manage API keys. The default.                                                                 |
| [`@archildata/native`](https://www.npmjs.com/package/@archildata/native) | Speak the Archil filesystem protocol directly from Node. Linux/macOS only. Not recommended unless you're building a custom client runtime. |

<Note>
  Looking for the FUSE mount client? That's the [`archil` CLI](/mounting/linux), which mounts a disk as a real local filesystem. The TypeScript SDK does not mount disks; it talks to the control plane and runs serverless commands.
</Note>

## Configuration

The recommended pattern is a module-namespace import with a one-time `configure` call:

```typescript theme={null}
import * as archil from "disk";

archil.configure({
  apiKey: process.env.ARCHIL_API_KEY,
  region: "aws-us-east-1",
});
```

Both options fall back to environment variables (`ARCHIL_API_KEY`, `ARCHIL_REGION`) if omitted, so in most environments `configure({})` — or skipping `configure` entirely — is enough.

For multi-tenant scripts that need multiple credentials in one process, instantiate `Archil` directly instead:

```typescript theme={null}
import { Archil } from "disk";

const prod = new Archil({ apiKey: prodKey, region: "aws-us-east-1" });
const staging = new Archil({ apiKey: stagingKey, region: "aws-us-east-1" });
```

<Note>
  The API key is an account-level credential and is **not** the same thing as a disk token. API keys authenticate calls to the control plane (everything in this page); a disk token grants mount access to a single disk. See the [disk users concept page](/concepts/disk-users#disk-token-authorization).
</Note>

## Managing disks

```typescript theme={null}
import * as archil from "disk";

// Create a disk. The returned token is the disk token — save it; it isn't
// retrievable again.
const { disk, token } = await archil.createDisk({
  name: "my-disk",
  mounts: [{
    type: "s3",
    bucketName: "my-bucket",
    accessKeyId: "AKIA...",
    secretAccessKey: "...",
  }],
});

// List and look up disks
const all = await archil.listDisks();
const d = await archil.getDisk(disk.id);
```

Per-disk operations are methods on the `Disk` object itself, not top-level functions:

```typescript theme={null}
const d = await archil.getDisk("dsk-abc123");

// Add an additional mount token
const user = await d.createToken("ci");
// save user.token — it's only returned once

// Revoke access
await d.removeUser("token", user.identifier);

// Delete the disk (this does not delete data in your bucket)
await d.delete();
```

## Executing commands

`Disk.exec(command)` runs a bash command inside a container with the file system already mounted, and returns stdout, stderr, exit code, and timing. See the [Serverless Execution concept page](/compute/serverless-execution) for the full picture.

```typescript theme={null}
const d = await archil.getDisk("dsk-abc123");

const { stdout, stderr, exitCode, timing } = await d.exec(
  "du -sh logs",
);

console.log(`ran in ${timing.executeMs}ms (queued ${timing.queueMs}ms)`);
```

The disk is the working directory inside the container — commands can reference files using relative paths.

For multi-disk execs (mount several disks at once, optionally pinned to a subdirectory or read-only), call `archil.exec({ disks, command })` instead of `Disk.exec`. See [Mounting multiple disks in one command](/compute/serverless-execution#mounting-multiple-disks-in-one-command).

### `ExecResult`

```typescript theme={null}
interface ExecResult {
  exitCode: number;
  stdout: string;
  stderr: string;
  timing: ExecTiming;
}

interface ExecTiming {
  /** End-to-end wall-clock measured on the server. */
  totalMs: number;
  /** Time spent queueing, scheduling, booting a container, and mounting the filesystem. */
  queueMs: number;
  /** Time the user's command itself ran, measured by the runtime. */
  executeMs: number;
}
```

Billing is based on `executeMs` — the wall-clock time your command runs — in 1ms increments, with a 100ms minimum per call. Queue time is not billed. The HTTP response returns after 5 minutes, and stdout and stderr are each capped at 128 KiB per invocation — pipe larger outputs to a file on the disk instead.

### Fan-out

Because each `exec` runs in its own container, `Promise.all` is a map-reduce:

```typescript theme={null}
const files = (await d.exec("ls logs")).stdout.trim().split("\n");

const results = await Promise.all(
  files.map((f) => d.exec(`wc -l logs/${f}`)),
);
```

For the common case of *searching* files for a pattern, don't hand-roll this — use [`Disk.grep`](#searching-files), which fans the same work out for you and returns structured matches. See the [bash tool for agents guide](/guides/ai/bash-tool) for an end-to-end example wiring `disk.exec` into an AI agent loop.

## Searching files

`Disk.grep(opts)` searches the files on a disk for lines matching a regular expression, fanning the listing and matching out across many ephemeral containers so the search scales across many machines instead of one. It's the productized version of the fan-out above — reach for it instead of `Promise.all` over `exec("grep …")` whenever you just want matching lines. See [Search Files](/compute/search-files) for the full model.

```typescript theme={null}
const d = await archil.getDisk("dsk-abc123");

const { matches, stoppedReason, durationMs } = await d.grep({
  directory: "logs",
  pattern: "ERROR|FATAL",
  recursive: true,
});

for (const m of matches) {
  console.log(`${m.file}:${m.line}: ${m.text}`);
}
console.log(`${matches.length} matches in ${durationMs}ms (${stoppedReason})`);
```

You control cost and latency with three knobs:

* `maxDurationSeconds` — wall-clock deadline (default 30, capped at 30).
* `concurrency` — max parallel workers (default 50). More workers scan a large dataset faster, at proportionally more compute.
* `maxResults` — short-circuit once this many matches are collected (default 1000).

Always check `stoppedReason` — it tells you whether the search was exhaustive. When it stops early, the returned `matches` are a sample of whichever workers reported first, **not** the lexicographically first N:

| `stoppedReason` | Meaning                                                                                                         |
| --------------- | --------------------------------------------------------------------------------------------------------------- |
| `completed`     | Every file under the directory was scanned successfully. The matches are exhaustive.                            |
| `max_results`   | Stopped after collecting `maxResults` matches before scanning everything.                                       |
| `deadline`      | Hit `maxDurationSeconds` before scanning everything.                                                            |
| `incomplete`    | The pipeline finished but one or more batches errored (invalid regex, unreadable file). Results may be partial. |
| `list_failed`   | Directory listing failed; only partial results, if any, are present.                                            |

### `GrepResult`

```typescript theme={null}
interface GrepResult {
  matches: GrepMatch[];
  stoppedReason: GrepStoppedReason; // "completed" | "incomplete" | "max_results" | "deadline" | "list_failed"
  filesScanned: number;
  containersDispatched: number;
  computeSecondsUsed: number; // ≈ billable container-seconds
  durationMs: number;         // end-to-end wall clock
  listingMs: number;          // overlaps grepMs, so the two can sum past durationMs
  grepMs: number;
}

interface GrepMatch {
  file: string; // relative to the disk root
  line: number; // 1-based
  text: string; // the matching line
}
```

Grep runs on the same container runtime as `exec`, so it bills the same way — on `computeSecondsUsed`, the summed execution time across the containers it dispatched.

## Reading and writing objects

A `Disk` doubles as an S3-compatible bucket: read, write, delete, and list its files by key without mounting it. These methods talk to Archil's S3 endpoint using your same API key — no separate S3 credentials or SigV4 signing on your part.

```typescript theme={null}
const d = await archil.getDisk("dsk-abc123");
const report = { generated: "2026-01", rows: 1234 };

// Write — body is a string, Uint8Array/Buffer, or ArrayBuffer. contentType is
// optional (defaults to application/octet-stream). Returns the etag.
const { etag } = await d.putObject(
  "reports/2026-01/data.json",
  JSON.stringify(report),
  "application/json",
);

// Read — returns a Uint8Array.
const bytes = await d.getObject("reports/2026-01/data.json");
const text = new TextDecoder().decode(bytes);

// Metadata / existence without downloading the body
const meta = await d.headObject("reports/2026-01/data.json"); // null if absent
if (await d.objectExists("reports/2026-01/data.json")) {
  // ...
}

// Delete (idempotent — deleting a missing key resolves successfully)
await d.deleteObject("reports/2026-01/data.json");
```

`putObject` is a whole-object upload — multipart isn't supported, so a single body is capped at 5 GB. For larger writes, [mount the disk](/mounting/linux).

`listObjects` auto-paginates by default, returning every matching key. The first argument is a key prefix; a non-recursive listing (the default) returns the immediate level as `objects` plus subdirectory `commonPrefixes`:

```typescript theme={null}
const oneLevel = await d.listObjects("reports/");                  // one level
const subtree = await d.listObjects("reports/", { recursive: true }); // whole subtree
const first100 = await d.listObjects("reports/", { limit: 100 });   // cap the total

// Stream pages instead of buffering everything (large listings):
for await (const page of d.listObjectsPages("reports/")) {
  for (const obj of page.objects) {
    console.log(obj.key, obj.size, obj.lastModified);
  }
}

// Or drive pagination yourself:
const page = await d.listObjects("reports/", { singlePage: true });
if (page.isTruncated) {
  const next = await d.listObjects("reports/", {
    singlePage: true,
    continuationToken: page.nextContinuationToken,
  });
}
```

Object-API failures throw `ArchilS3Error` (a subclass of `ArchilError`) with `status` (HTTP status), `code` (the S3 error code, e.g. `"NoSuchKey"`), `requestId`, and the raw XML body on `raw`. `getObject` on a missing key throws a 404 — use `headObject` / `objectExists` to probe without catching.

```typescript theme={null}
import { ArchilS3Error } from "disk";

try {
  await d.getObject("reports/missing.json");
} catch (e) {
  if (e instanceof ArchilS3Error) {
    console.log(e.status, e.code, e.requestId);
  }
}
```

## Managing API keys

API keys are account-level, so these helpers live at the top level rather than on a `Disk`:

```typescript theme={null}
await archil.listApiKeys();
const k = await archil.createApiKey({ name: "ci-bot", description: "GitHub Actions" });
// save k.token — it's only returned once
await archil.deleteApiKey("key-abc123");
```

## CLI

`disk` ships a CLI under the same name. See the [`disk` CLI reference](/reference/disk-cli) for the full command list.

```bash theme={null}
npx disk create my-disk
npx disk list
npx disk dsk-abc123 exec "ls -la"
```

## Low-level protocol access

For the small number of integrations that need to speak Archil's filesystem protocol directly from Node — inodes, delegations, byte-level reads, paginated directory enumeration — install [`@archildata/native`](https://www.npmjs.com/package/@archildata/native) alongside `disk`:

```bash theme={null}
npm install disk @archildata/native
```

Then call `Disk.mount()`, which lazy-loads the native client:

```typescript theme={null}
import { getDisk } from "disk";

const d = await getDisk("dsk-abc123");
const client = await d.mount({ authToken: process.env.ARCHIL_DISK_TOKEN });

// `client` is an ArchilClient from @archildata/native — see that package's
// README on npm for the full inode-level API.
await client.close();
```

`@archildata/native` ships prebuilt binaries for **Linux** (x64, arm64, glibc) and **macOS** (arm64). On other platforms, `mount()` throws; the rest of `disk` still works.

<Note>
  If you're tempted to reach for `@archildata/native` to "run a bash command on a disk," use [`disk.exec`](#executing-commands) instead — it gives you a real shell with the filesystem mounted in a container, with no native dependencies on the caller side.
</Note>

## Need a bash executor inside Node?

The legacy [`@archildata/just-bash`](/mounting/just-bash) package implements a JavaScript bash interpreter that talks to a disk through `@archildata/native`. It's still published, but for almost every use case `disk.exec` is the better answer — it runs your command in a real container with the filesystem mounted, returns stdout/stderr/exit code, and has no native dependencies on the caller.
