/** * Alice — the file CLIENT. Demonstrates the typed `@shade/files` API: * * bun run examples/08-files-browser/alice-cli.ts list / * bun run examples/08-files-browser/alice-cli.ts mkdir /docs * bun run examples/08-files-browser/alice-cli.ts upload ./README.md /docs/readme.md * bun run examples/08-files-browser/alice-cli.ts download /docs/readme.md ./out.md * bun run examples/08-files-browser/alice-cli.ts stat /docs/readme.md * bun run examples/08-files-browser/alice-cli.ts delete /docs/readme.md */ import { readFile, writeFile, stat as fsstat } from 'node:fs/promises'; import { createShade } from '@shade/sdk'; const PREKEY = 'http://localhost:9992'; const ALICE_BASE_URL = 'http://localhost:9993'; const BOB_BASE_URL = 'http://localhost:9994'; const alice = await createShade({ prekeyServer: PREKEY, address: 'alice' }); alice.configureTransfers({ resolveBaseUrl: async (peer) => { if (peer === 'bob') return BOB_BASE_URL; throw new Error(`alice: unknown peer ${peer}`); }, }); const app = await alice.transferRoute(); Bun.serve({ port: 9993, fetch: app.fetch }); const fs = await alice.files.client('bob', { defaultTimeoutMs: 30_000 }); const [cmd, ...args] = process.argv.slice(2); try { switch (cmd) { case 'list': { const path = args[0] ?? '/'; const result = await fs.list(path); for (const e of result.entries) { console.log(`${e.kind === 'dir' ? 'd' : '-'} ${String(e.size).padStart(10)} ${e.name}`); } break; } case 'stat': { const path = args[0]!; const e = await fs.stat(path); console.log(JSON.stringify(e, null, 2)); break; } case 'mkdir': { const path = args[0]!; const r = await fs.mkdir(path, { recursive: true }); console.log(`created ${r.entry.name}`); break; } case 'delete': { const path = args[0]!; const r = await fs.delete(path, { recursive: true }); console.log(`deleted ${r.deletedCount} entrie(s)`); break; } case 'upload': { const localPath = args[0]!; const remotePath = args[1]!; const bytes = new Uint8Array(await readFile(localPath)); const s = await fsstat(localPath); console.log(`uploading ${s.size} bytes → ${remotePath}`); const r = await fs.write(remotePath, bytes, { overwrite: true }); console.log(`done — remote size: ${r.entry.size}`); break; } case 'download': { const remotePath = args[0]!; const localPath = args[1]!; const result = await fs.read(remotePath); if (result.kind === 'inline') { await writeFile(localPath, result.bytes); console.log(`downloaded ${result.bytes.byteLength} bytes (inline) sha256=${result.sha256}`); } else { const reader = result.stream.getReader(); const chunks: Uint8Array[] = []; let total = 0; while (true) { const { value, done } = await reader.read(); if (done) break; if (value !== undefined) { chunks.push(value); total += value.byteLength; } } reader.releaseLock(); const out = new Uint8Array(total); let offset = 0; for (const c of chunks) { out.set(c, offset); offset += c.byteLength; } await writeFile(localPath, out); await result.done(); console.log(`downloaded ${total} bytes (streamed) sha256=${result.sha256}`); } break; } default: console.error('Usage: alice-cli.ts [args...]'); process.exit(1); } } finally { await alice.shutdown(); process.exit(0); }