npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

drive9

v0.1.4

Published

TypeScript SDK for drive9 — an agent-native filesystem with semantic search

Readme

drive9-js

TypeScript SDK for drive9 — an agent-native filesystem with semantic search.

For the full integration guide, see docs/guides/typescript-sdk-integration.md.

Installation

npm install drive9

Quick start

import { Client } from "drive9";

async function main() {
  const client = Client.defaultClient();
  // Or: const client = new Client("http://127.0.0.1:9009", "your-api-key");

  await client.write("/hello.txt", new TextEncoder().encode("hello world"));
  const data = await client.read("/hello.txt");
  console.log(new TextDecoder().decode(data));

  const info = await client.stat("/hello.txt");
  console.log(`size=${info.size} revision=${info.revision}`);
}

main().catch(console.error);

Config auto-loading

Client.defaultClient() reads ~/.drive9/config automatically:

const client = Client.defaultClient();

Expected config format:

{
  "server": "http://127.0.0.1:9009",
  "current_context": "default",
  "contexts": {
    "default": { "api_key": "d9_..." }
  }
}

Environment variables DRIVE9_SERVER or DRIVE9_BASE and DRIVE9_API_KEY take priority over the config file. DRIVE9_CONFIG can point to an alternate config file.

Client lifecycle & tuning

| Operation | Method | |-----------|--------| | Default client (env/config) | Client.defaultClient() | | Explicit constructor | new Client(baseUrl?, apiKey?) | | Small-file threshold builder | client.withSmallFileThreshold(n) | | Set X-Dat9-Actor header | client.setActor(actor) | | Base URL | client.baseURL() | | Auth + actor headers | client.authHeaders(init?) | | Build /v1/fs/<path> URL | client.fsUrl(path) | | Build /v1/vault/<path> URL | client.vaultUrl(path) | | Prime status cache (best-effort) | await client.warm() | | Tenant status | await client.status() | | Max single-upload bytes | await client.maxUploadBytes() | | Server inline threshold | await client.smallFileThresholdValue() | | Cached inline threshold | client.cachedSmallFileThreshold() | | Raw GET | await client.rawGet(endpoint) | | Raw POST | await client.rawPost(endpoint, body?) | | Raw DELETE | await client.rawDelete(endpoint, body?) |

Supported operations

Filesystem

| Operation | Method | |-----------|--------| | Write file | await client.write(path, data, options?) | | Write returning revision | await client.writeWithRevision(path, data, options?) | | Read file | await client.read(path) | | Create empty file | await client.createFile(path) | | List directory | await client.list(path) | | Stat (HEAD) | await client.stat(path) | | Enriched stat | await client.statMetadata(path) | | Enriched stat with fallback | await client.statMetadataCompat(path) | | Batch stat | await client.batchStat(paths) | | Batch read-small | await client.batchReadSmall(paths, maxBytes) | | Delete | await client.delete(path) | | Delete file (kind hint) | await client.deleteFile(path) | | Delete dir (kind hint) | await client.deleteDir(path) | | Recursive delete | await client.removeAll(path) | | Copy | await client.copy(src, dst) | | Rename | await client.rename(old, new) | | Mkdir | await client.mkdir(path, mode?) | | Chmod | await client.chmod(path, mode) | | Symlink | await client.symlink(target, linkPath) | | Hardlink | await client.hardlink(src, dst) |

Search & SQL

| Operation | Method | |-----------|--------| | SQL query | await client.sql(query) | | Grep | await client.grep(query, prefix, limit) | | Layer grep | await client.grepWithLayer(query, prefix, limit, layerRef) | | Find | await client.find(prefix, params?) |

Streaming & multipart

| Operation | Method | |-----------|--------| | Stream upload | await client.writeStream(path, stream, size, options?) | | Stream upload with summary | await client.writeStreamWithSummary(path, stream, size, options?) | | Resume upload | await client.resumeUpload(path, stream, totalSize) | | Read stream | await client.readStream(path) | | Read range stream | await client.readStreamRange(path, offset, length) | | Read range bytes | await client.readAt(path, offset, length) | | Download to local file | await client.downloadToFile(remotePath, localPath) | | Stream writer | client.newStreamWriter(path, totalSize, options?, abortOnError?) | | Append bytes | await client.append(path, data, options?) | | Append stream | await client.appendStream(path, stream, size, options?) |

append / appendStream use the native server append flow for existing S3-backed files. When native append is unavailable, the SDK only uses a bounded small-file rewrite and rejects large read/merge/write rewrites.

Download a directory tree

// Recursively download a remote directory to a local directory.
// Existing local destinations are never overwritten; a file source is rejected.
await client.downloadDir("/project", "./local-project");

Archive (tar.gz / zip)

// Stream a remote directory as a tar.gz (default) or zip archive.
const stream = await client.archive("/project", { format: "zip" });

// Or write the archive straight to a local file.
await client.archiveToFile("/project", "./project.tar.gz", {
  exclude: ["**/node_modules/**"],
  include: ["src/**"],
  flat: false,
  jobs: 16,
});

ArchiveOptions supports format ("tar.gz" | "zip"), exclude/include/profile pattern filters (same three pattern forms as the Go CLI: **/x/**, prefix/**, name glob like *.log), flat (strip directory hierarchy), jobs (concurrent downloads), and signal (AbortSignal).

Patch (partial update)

await client.patchFile(path, newSize, dirtyParts, readPartFn, progressFn?, partSize?);

Vault

| Operation | Method | |-----------|--------| | Create secret | await client.createVaultSecret(name, fields) | | Update secret | await client.updateVaultSecret(name, fields) | | Delete secret | await client.deleteVaultSecret(name) | | List secrets | await client.listVaultSecrets() | | Issue token | await client.issueVaultToken(agentId, taskId, scope, ttlSeconds) | | Revoke token | await client.revokeVaultToken(tokenId) | | Issue grant | await client.issueVaultGrant(request) | | Revoke grant | await client.revokeVaultGrant(grantId, request?) | | Query audit | await client.queryVaultAudit(secretName?, limit?) | | List readable secrets | await client.listReadableVaultSecrets() | | Read secret (scoped) | await client.readVaultSecret(name) | | Read field (scoped) | await client.readVaultSecretField(name, field) | | Owner read secret | await client.readVaultSecretAsOwner(name) | | Owner read field | await client.readVaultSecretFieldAsOwner(name, field) |

Scoped tokens

| Operation | Method | |-----------|--------| | Issue fs-scoped token | await client.issueScopedToken(request) | | Revoke by token id | await client.revokeScopedToken(tokenId) | | Revoke by API key | await client.revokeScopedTokenByAPIKey(apiKey) |

Events (SSE)

| Operation | Method | |-----------|--------| | Watch change/reset events | await client.watchEvents(actor, handler, options?) | | Watch with lifecycle | await client.watchEventsWithLifecycle(actor, handler, lifecycle, options?) |

WatchEventsOptions accepts signal (AbortSignal) to stop the watcher and initialSince / initialBackoffMs for the reconnect loop. EventLifecycle provides onDisconnected(err) and onCurrent(seq) callbacks.

LayerFS

| Operation | Method | |-----------|--------| | Create layer | await client.createFSLayer(request) | | List layers | await client.listFSLayers() | | Get layer | await client.getFSLayer(layerId) | | Diff layer | await client.diffFSLayer(layerId, maxSeq?) | | Replay layer | await client.replayFSLayer(layerId, maxSeq?) | | Upsert entry | await client.upsertFSLayerEntry(layerId, request) | | Upload layer file | await client.uploadFSLayerFile(layerId, path, data, opts?) | | Read layer file | await client.readFSLayerFile(layerId, path, maxSeq?) | | Read layer file stream | await client.readFSLayerFileStream(layerId, path, maxSeq?) | | Get layer entry | await client.getFSLayerEntry(layerId, path, maxSeq?) | | Checkpoint layer | await client.checkpointFSLayer(layerId, request) | | Get checkpoint | await client.getFSLayerCheckpoint(checkpointId) | | List layer events | await client.listFSLayerEvents(layerId, since?) | | Rollback layer | await client.rollbackFSLayer(layerId) | | Commit layer | await client.commitFSLayer(layerId) |

Git workspaces

| Operation | Method | |-----------|--------| | Upsert workspace | await client.upsertGitWorkspace(request) | | Get by root path | await client.getGitWorkspaceByRoot(rootPath) | | Get by id | await client.getGitWorkspace(workspaceId) | | Delete workspace | await client.deleteGitWorkspace(workspaceId) | | List workspaces | await client.listGitWorkspaces() | | Replace tree | await client.replaceGitTree(workspaceId, request) | | List tree | await client.listGitTree(workspaceId, commitSHA) | | Upsert git state | await client.upsertGitState(workspaceId, request) | | Get git state | await client.getGitState(workspaceId) | | Put object pack | await client.putGitObjectPack(workspaceId, request) | | List object packs | await client.listGitObjectPacks(workspaceId) | | Get object pack | await client.getGitObjectPack(workspaceId, packId) | | Put overlay entry | await client.putGitOverlayEntry(workspaceId, request) | | Get overlay entry | await client.getGitOverlayEntry(workspaceId, relPath) | | List overlay entries | await client.listGitOverlayEntries(workspaceId) |

Journals

| Operation | Method | |-----------|--------| | Create journal | await client.createJournal(request) | | Append entries | await client.appendJournalEntries(journalId, appendId, entries) | | Read entries | await client.readJournalEntries(journalId, afterSeq?, limit?) | | Search | await client.searchJournal(request) | | Verify | await client.verifyJournal(journalId) |

StreamWriter

client.newStreamWriter(path, totalSize, options?, abortOnError?) returns a resumable v2 multipart writer:

const sw = client.newStreamWriter("/big.bin", 16 * 1024 * 1024);
await sw.writePart(1, part1); // lazily initiates the upload
await sw.complete(finalPartNum, finalPartData);
// or: await sw.abort();

Testing

npm run lint
npm test
npm run build

The integration suite (tests/integration.test.ts) runs against a live drive9-server-local and is gated by the DRIVE9_INTEGRATION env var; see scripts/sdk-integration-tests.sh for the one-click cross-SDK runner.

License

Apache-2.0