An encrypted personal cloud built on the AT Protocol.
1# Document Operations
2
3## Upload
4
5Encrypts a file and uploads it as an opaque blob with a metadata record.
6
7```mermaid
8sequenceDiagram
9 participant User
10 participant CLI
11 participant Crypto
12 participant PDS
13
14 User->>CLI: opake upload photo.jpg
15
16 CLI->>CLI: Read file from disk, detect MIME type
17 CLI->>Crypto: generate_content_key()
18 Crypto-->>CLI: random AES-256-GCM key K
19
20 CLI->>Crypto: encrypt_blob(K, plaintext)
21 Crypto-->>CLI: { ciphertext, nonce }
22
23 CLI->>PDS: com.atproto.repo.uploadBlob (ciphertext)
24 PDS-->>CLI: blob ref { $link, size }
25
26 CLI->>Crypto: wrap_key(K, owner_pubkey, owner_did)
27 Crypto-->>CLI: wrappedKey (x25519-hkdf-a256kw)
28
29 CLI->>Crypto: encrypt_metadata(K, {name, mimeType, size, tags, ...})
30 Crypto-->>CLI: encryptedMetadata { ciphertext, nonce }
31
32 CLI->>PDS: com.atproto.repo.createRecord (document)
33 PDS-->>CLI: { uri, cid }
34
35 CLI->>User: Uploaded: at://did/app.opake.document/<tid>
36```
37
38## Download (Own Files)
39
40Fetches a document you own, unwraps the content key, and decrypts.
41
42```mermaid
43sequenceDiagram
44 participant User
45 participant CLI
46 participant PDS
47 participant Crypto
48
49 User->>CLI: opake download photo.jpg
50
51 CLI->>CLI: Resolve filename → AT-URI (via listRecords if needed)
52
53 CLI->>PDS: com.atproto.repo.getRecord (document)
54 PDS-->>CLI: Document record (envelope, blob ref)
55
56 CLI->>CLI: Find wrappedKey matching own DID
57 CLI->>Crypto: unwrap_key(wrappedKey, private_key)
58 Crypto-->>CLI: content key K
59
60 CLI->>PDS: com.atproto.sync.getBlob (did, cid)
61 PDS-->>CLI: ciphertext bytes
62
63 CLI->>Crypto: decrypt_blob(K, nonce, ciphertext)
64 Crypto-->>CLI: plaintext
65
66 CLI->>CLI: Write plaintext to disk
67 CLI->>User: Saved to ./photo.jpg
68```
69
70## Download (Shared Files — Cross-PDS)
71
72Downloads a file shared with you by another user. Requires the grant URI (auto-discovery via `inbox` is not yet implemented).
73
74```mermaid
75sequenceDiagram
76 participant User
77 participant CLI
78 participant PLC as PLC Directory
79 participant OwnerPDS as Owner's PDS
80 participant Crypto
81
82 User->>CLI: opake download --grant at://did:plc:owner/.../grant-tid
83
84 CLI->>CLI: Parse grant URI, extract owner DID
85
86 CLI->>PLC: GET /did:plc:owner (DID document)
87 PLC-->>CLI: { service: [{ #atproto_pds: owner-pds-url }] }
88
89 CLI->>OwnerPDS: com.atproto.repo.getRecord (grant)
90 OwnerPDS-->>CLI: Grant record { document, wrappedKey }
91
92 CLI->>Crypto: unwrap_key(grant.wrappedKey, private_key)
93 Crypto-->>CLI: content key K
94
95 CLI->>OwnerPDS: com.atproto.repo.getRecord (document)
96 OwnerPDS-->>CLI: Document record { blob, encryption.nonce }
97
98 CLI->>OwnerPDS: com.atproto.sync.getBlob (did, cid)
99 OwnerPDS-->>CLI: ciphertext bytes
100
101 CLI->>Crypto: decrypt_blob(K, nonce, ciphertext)
102 Crypto-->>CLI: plaintext
103
104 CLI->>CLI: Write to disk
105 CLI->>User: Saved to ./shared-file.txt
106```
107
108Data never leaves the owner's PDS. The recipient fetches everything directly from the source.
109
110## List
111
112Lists document records on your PDS with optional tag filtering.
113
114```mermaid
115sequenceDiagram
116 participant User
117 participant CLI
118 participant PDS
119
120 User->>CLI: opake ls --tag vacation --long
121
122 loop Paginate until no cursor
123 CLI->>PDS: com.atproto.repo.listRecords (collection, cursor)
124 PDS-->>CLI: { records: [...], cursor? }
125 end
126
127 CLI->>CLI: For each document, unwrap content key
128 CLI->>Crypto: decrypt_metadata(K, encryptedMetadata)
129 Crypto-->>CLI: { name, mimeType, size, tags, description }
130
131 CLI->>CLI: Filter by tag, format output
132 CLI->>User: Display table (name, size, tags, URI)
133```
134
135## Delete
136
137Deletes a document record. The blob becomes orphaned and is eventually garbage-collected by the PDS. If the document is tracked in a directory, the parent's entry list is updated.
138
139For path-based deletion (`Photos/beach.jpg`), recursive directory deletion, and directory-related flows, see [directories.md](directories.md).
140
141```mermaid
142sequenceDiagram
143 participant User
144 participant CLI
145 participant PDS
146
147 User->>CLI: opake rm photo.jpg
148
149 Note over CLI: Bare name → fast path (document-only resolution)
150 CLI->>PDS: listRecords (document collection, paginated)
151 PDS-->>CLI: match found → AT-URI
152
153 CLI->>User: delete photo.jpg? [y/N]
154 User-->>CLI: y
155
156 CLI->>PDS: com.atproto.repo.deleteRecord (collection, rkey)
157 PDS-->>CLI: 200 OK
158
159 CLI->>User: deleted at://did/.../document/<rkey>
160```
161
162The fast path resolves bare document names with a single paginated `listRecords` call, the same cost as the pre-directory implementation. AT-URIs skip resolution entirely. Only path references (`dir/file`) and directory targets trigger a full tree load — see [directories.md](directories.md#path-resolution) for details.