Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions examples/01-agent-reads-files/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# 01 — Agent reads files

The simplest relayfile example: connect to a workspace and read its contents.

## What it shows

| Method | Purpose |
|--------|---------|
| `listTree` | Browse the virtual filesystem tree |
| `readFile` | Fetch a single file's content and metadata |
| `queryFiles` | Search files by provider or semantic properties |

## Run

```bash
export RELAYFILE_TOKEN="ey…" # JWT with fs:read scope
export WORKSPACE_ID="ws_demo"

npx tsx index.ts
```

## Expected output

```
── listTree (depth 2) ──
📁 /github
📄 /github/pulls/42.json rev=r_abc123

── readFile /github/pulls/42.json ──
contentType : application/json
revision : r_abc123
provider : github
content : {"title":"Add auth middleware",…}…

── queryFiles (provider=github) ──
/github/pulls/42.json props={"status":"open"}

── queryFiles (property status=open) ──
matched 1 file(s)
/github/pulls/42.json

Done.
```
78 changes: 78 additions & 0 deletions examples/01-agent-reads-files/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* Example 01 — Agent reads files from a workspace
*
* Demonstrates the simplest relayfile flow: connect to a workspace and
* browse its virtual filesystem using listTree, readFile, and queryFiles.
*
* Run: npx tsx index.ts
* Env: RELAYFILE_TOKEN — JWT with fs:read scope
* WORKSPACE_ID — target workspace ID
*/

import { RelayFileClient } from "@relayfile/sdk";

const token = process.env.RELAYFILE_TOKEN;
const workspaceId = process.env.WORKSPACE_ID ?? "ws_demo";

if (!token) {
console.error("Set RELAYFILE_TOKEN before running this example.");
process.exit(1);
}

const client = new RelayFileClient({ token });

// ── 1. List the workspace tree ──────────────────────────────────────────────

console.log("── listTree (depth 2) ──");
const tree = await client.listTree(workspaceId, { depth: 2 });

for (const entry of tree.entries) {
const icon = entry.type === "dir" ? "📁" : "📄";
console.log(` ${icon} ${entry.path} rev=${entry.revision}`);
}

if (tree.nextCursor) {
console.log(` … more entries (cursor: ${tree.nextCursor})`);
}

// ── 2. Read a single file ───────────────────────────────────────────────────

const targetPath = tree.entries.find((e) => e.type === "file")?.path;

if (targetPath) {
console.log(`\n── readFile ${targetPath} ──`);
const file = await client.readFile(workspaceId, targetPath);
console.log(` contentType : ${file.contentType}`);
console.log(` revision : ${file.revision}`);
console.log(` provider : ${file.provider ?? "(agent-written)"}`);
console.log(` content : ${file.content.slice(0, 200)}…`);
} else {
console.log("\nNo files found in tree — skipping readFile.");
}

// ── 3. Query files by provider ──────────────────────────────────────────────

console.log("\n── queryFiles (provider=github) ──");
const query = await client.queryFiles(workspaceId, { provider: "github" });

if (query.items.length === 0) {
console.log(" (no github files — try ingesting a webhook first)");
} else {
for (const item of query.items.slice(0, 5)) {
console.log(` ${item.path} props=${JSON.stringify(item.properties)}`);
}
}

// ── 4. Query files by semantic property ─────────────────────────────────────

console.log("\n── queryFiles (property status=open) ──");
const byProp = await client.queryFiles(workspaceId, {
properties: { status: "open" },
});

console.log(` matched ${byProp.items.length} file(s)`);
for (const item of byProp.items.slice(0, 3)) {
console.log(` ${item.path}`);
}

console.log("\nDone.");
31 changes: 31 additions & 0 deletions examples/02-agent-writes-files/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# 02 — Agent writes files

Write files into the relayfile virtual filesystem, then read them back.

## What it shows

| Method | Purpose |
|--------|---------|
| `writeFile` | Create or update a single file with metadata |
| `writeFile` + `baseRevision` | Optimistic concurrency via If-Match |
| `RevisionConflictError` | Handle 409 conflicts when a revision is stale |
| `bulkWrite` | Atomically write multiple files in one request |
| `readFile` | Verify written content |

## Run

```bash
export RELAYFILE_TOKEN="ey…" # JWT with fs:read + fs:write scopes
export WORKSPACE_ID="ws_demo"

npx tsx index.ts
```

## Key concepts

**Optimistic locking** — pass the file's current `revision` as `baseRevision`
to ensure your write only succeeds if the file hasn't changed since you read it.
Use `"*"` to create-or-overwrite without checking.

**Bulk writes** — `bulkWrite` writes multiple files in a single request.
The response tells you how many succeeded and includes per-file errors.
140 changes: 140 additions & 0 deletions examples/02-agent-writes-files/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
/**
* Example 02 — Agent writes files and reads them back
*
* Demonstrates writing files into the relayfile virtual filesystem:
* single writes with writeFile, atomic multi-file writes with bulkWrite,
* and reading files back to verify.
*
* Run: npx tsx index.ts
* Env: RELAYFILE_TOKEN — JWT with fs:read + fs:write scopes
* WORKSPACE_ID — target workspace ID
*/

import { RelayFileClient, RevisionConflictError } from "@relayfile/sdk";

const token = process.env.RELAYFILE_TOKEN;
const workspaceId = process.env.WORKSPACE_ID ?? "ws_demo";

if (!token) {
console.error("Set RELAYFILE_TOKEN before running this example.");
process.exit(1);
}

const client = new RelayFileClient({ token });

// ── 1. Write a single file ──────────────────────────────────────────────────

console.log("── writeFile /agents/summariser/config.json ──");

const writeResult = await client.writeFile({
workspaceId,
path: "/agents/summariser/config.json",
baseRevision: "*", // create-or-overwrite
content: JSON.stringify(
{
name: "summariser",
model: "claude-sonnet-4-6",
maxTokens: 4096,
schedule: "on-webhook",
},
null,
2
),
contentType: "application/json",
semantics: {
properties: { owner: "platform-team", env: "staging" },
relations: ["agent:summariser"],
},
});

console.log(` opId : ${writeResult.opId}`);
console.log(` revision : ${writeResult.targetRevision}`);

// ── 2. Read it back ─────────────────────────────────────────────────────────

console.log("\n── readFile /agents/summariser/config.json ──");
const file = await client.readFile(
workspaceId,
"/agents/summariser/config.json"
);
console.log(` revision : ${file.revision}`);
console.log(` content : ${file.content}`);

// ── 3. Optimistic update (If-Match) ────────────────────────────────────────

console.log("\n── writeFile with If-Match (optimistic update) ──");

const updated = JSON.parse(file.content);
updated.maxTokens = 8192;

const updateResult = await client.writeFile({
workspaceId,
path: "/agents/summariser/config.json",
baseRevision: file.revision, // only succeeds if nobody else changed it
content: JSON.stringify(updated, null, 2),
contentType: "application/json",
});

console.log(` new revision : ${updateResult.targetRevision}`);

// ── 4. Demonstrate conflict detection ───────────────────────────────────────

console.log("\n── writeFile with stale revision (expect 409) ──");

try {
await client.writeFile({
workspaceId,
path: "/agents/summariser/config.json",
baseRevision: file.revision, // stale — already updated above
content: '{"stale": true}',
contentType: "application/json",
});
} catch (err) {
if (err instanceof RevisionConflictError) {
console.log(` Caught RevisionConflictError`);
console.log(` expected : ${err.expectedRevision}`);
console.log(` current : ${err.currentRevision}`);
} else {
throw err;
}
}

// ── 5. Bulk write multiple files atomically ─────────────────────────────────

console.log("\n── bulkWrite 3 analysis files ──");

const bulkResult = await client.bulkWrite({
workspaceId,
files: [
{
path: "/analysis/2026-03-29/sentiment.json",
content: JSON.stringify({ score: 0.82, label: "positive" }),
contentType: "application/json",
},
{
path: "/analysis/2026-03-29/topics.json",
content: JSON.stringify({ topics: ["auth", "performance", "ux"] }),
contentType: "application/json",
},
{
path: "/analysis/2026-03-29/summary.md",
content:
"# Daily Summary\n\nSentiment is positive. Key topics: auth, performance, UX.",
contentType: "text/markdown",
},
],
});

console.log(` written : ${bulkResult.written}`);
console.log(` errorCount : ${bulkResult.errorCount}`);

// ── 6. Read back a bulk-written file ────────────────────────────────────────

console.log("\n── readFile /analysis/2026-03-29/summary.md ──");
const summary = await client.readFile(
workspaceId,
"/analysis/2026-03-29/summary.md"
);
console.log(` content:\n${summary.content}`);

console.log("\nDone.");
36 changes: 36 additions & 0 deletions examples/03-webhook-to-vfs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# 03 — Webhook to VFS

Ingest external webhooks into the relayfile virtual filesystem.

## What it shows

| Concept | Purpose |
|---------|---------|
| `computeCanonicalPath` | Map provider objects to deterministic file paths |
| `ingestWebhook` | Push external events (GitHub, Slack, etc.) into the VFS |
| `readFile` | Read back the ingested data as a file |

## Run

```bash
export RELAYFILE_TOKEN="ey…" # JWT with sync:trigger + fs:read scopes
export WORKSPACE_ID="ws_demo"

npx tsx index.ts
```

## How it works

```
GitHub webhook ──► ingestWebhook ──► /github/pulls/42.json
readFile ◄────────────┘
```

`computeCanonicalPath("github", "pulls", "42")` always returns
`/github/pulls/42.json` — every agent reading or writing that PR
agrees on the same path, making collaboration deterministic.

The webhook payload is queued as an **envelope**. The server deduplicates
by `delivery_id`, coalesces rapid updates, and writes the final content
to the canonical path.
Loading
Loading