window.chatoss.drive is your app's own real directory tree, owned by ChatOSS: no picker, no prompt, and the user can browse it in the built-in Files app under your app's name. This is where app OUTPUT belongs — see files vs drive. Capability: drive (declared, never prompts).
// app.json: "capabilities": ["drive"]
await window.chatoss.drive.mkdir('reports/2026');
const { path, size, sha256 } = await window.chatoss.drive.writeFile(
'reports/2026/august.md', '# August\n\nAll good.', { mime: 'text/markdown' });
const text = await window.chatoss.drive.readFile('reports/2026/august.md');
const b64 = await window.chatoss.drive.readFile('logo.png', { binary: true }); // base64
const entries = await window.chatoss.drive.list('reports'); // '' or omitted = container root
const entry = await window.chatoss.drive.stat('reports/2026/august.md'); // null if absent
await window.chatoss.drive.move('reports/2026/august.md', 'archive/august.md');
await window.chatoss.drive.copy('archive/august.md', 'archive/august-copy.md');
await window.chatoss.drive.remove('archive/august-copy.md'); // soft → .trash/
await window.chatoss.drive.remove('scratch.tmp', { permanent: true }); // really gone
const { bytes, files, quotaBytes } = await window.chatoss.drive.usage();
const unsub = window.chatoss.drive.onChanged(() => refreshFileList());
Rules
- A path is a KEY, not an OS path: relative, forward-slash, no
.., no absolute prefix, no empty segments. Write'reports/2026/august.md'on every platform — never'reports\\2026'and never a joined OS path. writeFile(path, contents, opts?)takes contents as a plain string (base64 when{ binary: true }) — unlikefiles.writeFile, it does NOT accept an ArrayBuffer/Uint8Array/Blob. It creates parent directories, and thesha256it returns is the change-detection key cloud backup will use.list()/stat()entries are{ name, path, kind: 'file'|'dir', size, mime?, createdAt, updatedAt, backupState }wherebackupStateis'local'|'queued'|'syncing'|'synced'|'failed'|'excluded'.remove()is a soft delete by default (into.trash/, restorable from the Drive app); pass{ permanent: true }only when the user explicitly asked for that.- No size limits. Drive is storage on the user's OWN DISK, so there is no per-file cap, no per-container quota and no per-directory cap. The only bounds are path safety (path ≤ 1024 chars, depth ≤ 32, no
..) and the disk itself;usage()returnsquotaBytes: null.
Cloud backup is the USER's, not yours
The user can point Drive at their own S3-compatible bucket (AWS S3, Cloudflare R2, Backblaze B2, Supabase Storage, Google Cloud Storage, DigitalOcean Spaces, Wasabi, MinIO) in Drive → Backup, and every file an app writes is uploaded there. An app does NOT configure or trigger backup and has no API for it — just write to Drive and it is covered. Each entry's backupState tells you where it stands (local → queued → syncing, or failed/excluded).
Crossing to the user's real disk
exportTo/importFrom additionally require fileAccess, and are refused headless:
// app.json: "capabilities": ["drive", "fileAccess"]
const dest = await window.chatoss.files.saveDialog({ defaultPath: 'august.md' });
if (dest) await window.chatoss.drive.exportTo('reports/2026/august.md', dest); // save-as
const folder = await window.chatoss.files.pickFolder();
if (folder) await window.chatoss.drive.importFrom(folder + '/notes.txt', 'imported/notes.txt');
importFrom's source must be inside a folder this app already picked — it's files' consent rule, not a second one.
The shared space
The same drive.* calls with a path under _shared/ reach the one area other apps' containers can see. That is a different trust decision, so it needs the separate driveShared capability, which is prompt-gated (the only prompting drive path). onChanged watches your own container only, never _shared. Declare driveShared only when cross-app hand-off is genuinely the feature.