@seliseblocks/cli-os 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AI_USAGE_GUIDE.md +20 -15
- package/README.md +5 -5
- package/dist/commands/auth/idp/create.d.ts +7 -0
- package/dist/commands/auth/idp/create.js +22 -3
- package/dist/commands/auth/idp/update.d.ts +7 -0
- package/dist/commands/auth/idp/update.js +22 -3
- package/dist/commands/data/files/delete.js +5 -4
- package/dist/commands/data/files/get-many.js +1 -1
- package/dist/commands/data/files/get.js +1 -1
- package/dist/commands/data/files/info.js +1 -1
- package/dist/commands/data/files/object-tree.d.ts +23 -0
- package/dist/commands/data/files/object-tree.js +238 -0
- package/dist/commands/data/files/presigned-upload-url.js +9 -2
- package/dist/commands/data/files/update-additional-info.js +2 -2
- package/dist/commands/data/files/upload-to-local-storage.js +2 -2
- package/dist/commands/data/files/upload.d.ts +2 -4
- package/dist/commands/data/files/upload.js +14 -28
- package/dist/commands/new/web.js +4 -0
- package/dist/index.js +100 -42
- package/dist/skills/blocks-data-storage/SKILL.md +194 -102
- package/dist/skills/blocks-data-storage/flows/object-management.md +124 -0
- package/dist/skills/blocks-iam-sso-oidc-configuration/SKILL.md +25 -9
- package/dist/skills/blocks-onboarding/SKILL.md +2 -2
- package/dist/skills/blocks-storage-configuration/SKILL.md +93 -93
- package/package.json +1 -1
- package/dist/commands/data/files/create-folder.d.ts +0 -1
- package/dist/commands/data/files/create-folder.js +0 -34
- package/dist/commands/data/files/delete-folder.d.ts +0 -1
- package/dist/commands/data/files/delete-folder.js +0 -25
- package/dist/commands/data/files/dms-list.d.ts +0 -1
- package/dist/commands/data/files/dms-list.js +0 -27
- package/dist/commands/data/files/dms-upload.d.ts +0 -6
- package/dist/commands/data/files/dms-upload.js +0 -41
|
@@ -1,161 +1,253 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: blocks-data-storage
|
|
3
|
-
description: "
|
|
3
|
+
description: "Build file and document-management features on SELISE Blocks Data: upload/download, directory trees, cursor-paginated browsing and search, file versions, rename/move/copy, soft delete/trash/restore, sharing, access policies, and inheritance. Use for attachments, file browsers, folders, shared files, permissions, or version history. Use 'blocks data files *' for terminal/admin work and @seliseblocks/client data.files/data.directories/data.objects for app code."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Blocks Data — Storage
|
|
6
|
+
# Blocks Data — Storage
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Treat storage as one permission-aware object tree containing **directories** and **files**. Uploading a new file now creates the file object in that tree; there is no second DMS registration step.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
- **`@seliseblocks/client`'s `data.files` / `data.dms` namespaces (SDK)** — wiring upload/download/browse into actual app code (a React component, a form submit handler).
|
|
10
|
+
Use:
|
|
12
11
|
|
|
13
|
-
|
|
12
|
+
- **`blocks data files *`** for supported terminal/admin operations.
|
|
13
|
+
- **`@seliseblocks/client`** for app code. Use the shared `blocksClient` created by blocks-onboarding.
|
|
14
|
+
- **blocks-storage-configuration** only to manage the named provider configuration used by `configurationName`.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
Select a project first with `blocks use <tenantId>`. If login/project state or the shared client setup is unknown, use blocks-onboarding before this skill.
|
|
16
17
|
|
|
17
|
-
|
|
18
|
-
import { blocksClient } from "../lib/blocks/client";
|
|
19
|
-
const { files, dms } = blocksClient.data;
|
|
20
|
-
```
|
|
18
|
+
Store the returned `fileId` in a Data record when attaching a file to domain data. Use blocks-data-gateway-crud for the record mutation.
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
## Choose the surface
|
|
23
21
|
|
|
24
|
-
|
|
22
|
+
The current CLI and SDK follow the backend's file, directory, and object resource groups.
|
|
25
23
|
|
|
26
24
|
```bash
|
|
27
|
-
|
|
28
|
-
blocks data files
|
|
29
|
-
|
|
25
|
+
blocks --version
|
|
26
|
+
blocks data files --help
|
|
27
|
+
```
|
|
30
28
|
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
- Use `blocks data files *` for terminal/admin work and inspect `blocks --help` for exact flags.
|
|
30
|
+
- Use `blocksClient.data.files` for bytes, metadata, versions, and file operations.
|
|
31
|
+
- Use `blocksClient.data.directories` for directory create/get/update/delete/move.
|
|
32
|
+
- Use `blocksClient.data.objects` for browse/search/trash/shared/restore/share/access/inheritance.
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
blocks data files dms-upload --file-storage-id <fileId> --artifact-name invoice.pdf --yes --json
|
|
34
|
+
Legacy `data.dms.*`, `dms-upload`, `dms-list`, `create-folder`, and `delete-folder` wrappers are retired. Uploads need no registration call.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
blocks data files dms-list --parent-id "" --json
|
|
39
|
-
blocks data files get <fileId> --json
|
|
36
|
+
## Upload a new file
|
|
40
37
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
38
|
+
Choose one path from the project's storage configuration.
|
|
39
|
+
|
|
40
|
+
| Provider category | Sequence |
|
|
41
|
+
|---|---|
|
|
42
|
+
| Cloud object storage | request a pre-signed URL, then PUT the bytes to it |
|
|
43
|
+
| Local/SFTP storage | send one authenticated multipart upload |
|
|
44
|
+
|
|
45
|
+
Both paths create the file object and initial version directly. Do not call `dms-upload` afterward.
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
### Cloud upload from the CLI
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Prefer the composed command. It previews and confirms both metadata creation and the provider PUT:
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
```bash
|
|
52
|
+
blocks data files upload --file ./invoice.pdf --parent-id <directoryId> \
|
|
53
|
+
--configuration-name Default --access-modifier Private --dry-run --json
|
|
54
|
+
blocks data files upload --file ./invoice.pdf --parent-id <directoryId> \
|
|
55
|
+
--configuration-name Default --access-modifier Private --yes --json
|
|
56
|
+
```
|
|
52
57
|
|
|
53
|
-
|
|
58
|
+
Use the two explicit commands only when the intermediate upload URL is required:
|
|
54
59
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
60
|
+
```bash
|
|
61
|
+
blocks data files presigned-upload-url \
|
|
62
|
+
--name invoice.pdf \
|
|
63
|
+
--parent-directory-id <directoryId> \
|
|
64
|
+
--configuration-name Default \
|
|
65
|
+
--access-modifier Private \
|
|
66
|
+
--dry-run --json
|
|
67
|
+
blocks data files presigned-upload-url \
|
|
68
|
+
--name invoice.pdf \
|
|
69
|
+
--parent-directory-id <directoryId> \
|
|
70
|
+
--configuration-name Default \
|
|
71
|
+
--access-modifier Private \
|
|
72
|
+
--yes --json
|
|
73
|
+
|
|
74
|
+
blocks data files upload-to-url \
|
|
75
|
+
--url "<uploadUrl>" \
|
|
76
|
+
--file ./invoice.pdf \
|
|
77
|
+
--content-type application/pdf \
|
|
78
|
+
--dry-run --json
|
|
79
|
+
blocks data files upload-to-url \
|
|
80
|
+
--url "<uploadUrl>" \
|
|
81
|
+
--file ./invoice.pdf \
|
|
82
|
+
--content-type application/pdf \
|
|
83
|
+
--yes --json
|
|
84
|
+
```
|
|
59
85
|
|
|
60
|
-
|
|
86
|
+
The presign response contains `uploadUrl`, `fileId`, and `isSuccess`. The first call creates the file metadata/version; the PUT fills its object-storage key. Handle PUT failure explicitly because it can leave metadata for missing bytes.
|
|
61
87
|
|
|
62
|
-
|
|
88
|
+
When `parentDirectoryId` is empty, the cloud upload resolves `moduleName` to that module's default directory. The backend default is module value `8` (`Default_Construct`), but pass the intended module or a concrete directory id instead of relying on that default.
|
|
63
89
|
|
|
64
|
-
|
|
90
|
+
### Cloud upload from app code
|
|
65
91
|
|
|
66
92
|
```ts
|
|
67
|
-
const presign = await files.presignedUploadUrl({
|
|
93
|
+
const presign = await blocksClient.data.files.presignedUploadUrl({
|
|
68
94
|
name: "invoice.pdf",
|
|
69
|
-
|
|
70
|
-
configurationName: "Default",
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
accessModifier: "Public" // "Public" (readable without auth) or "Private"
|
|
95
|
+
parentDirectoryId: directoryId,
|
|
96
|
+
configurationName: "Default",
|
|
97
|
+
accessModifier: "Private",
|
|
98
|
+
tags: "invoice,2026",
|
|
74
99
|
});
|
|
75
|
-
```
|
|
76
100
|
|
|
77
|
-
|
|
101
|
+
if (!presign || typeof presign !== "object") {
|
|
102
|
+
throw new Error("Unexpected upload response");
|
|
103
|
+
}
|
|
78
104
|
|
|
79
|
-
|
|
105
|
+
const result = presign as {
|
|
106
|
+
uploadUrl: string;
|
|
107
|
+
fileId: string;
|
|
108
|
+
isSuccess: boolean;
|
|
109
|
+
errors?: Record<string, string>;
|
|
110
|
+
};
|
|
80
111
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
112
|
+
if (!result.isSuccess) throw new Error(JSON.stringify(result.errors));
|
|
113
|
+
|
|
114
|
+
await blocksClient.data.files.uploadToUrl({
|
|
115
|
+
url: result.uploadUrl,
|
|
116
|
+
body: file,
|
|
117
|
+
contentType: file.type || "application/octet-stream",
|
|
86
118
|
});
|
|
87
119
|
```
|
|
88
120
|
|
|
89
|
-
|
|
121
|
+
`uploadToUrl` is provider-direct and sends no bearer token or `x-blocks-key`. The SDK adds Azure's `x-ms-blob-type: Blockblob` header unless overridden; ensure that header matches the signed provider policy.
|
|
90
122
|
|
|
91
|
-
|
|
123
|
+
### Local-storage upload
|
|
92
124
|
|
|
93
|
-
|
|
125
|
+
```bash
|
|
126
|
+
blocks data files upload-to-local-storage \
|
|
127
|
+
--file ./invoice.pdf \
|
|
128
|
+
--parent-directory-id <directoryId> \
|
|
129
|
+
--configuration-name Default \
|
|
130
|
+
--access-modifier Private \
|
|
131
|
+
--dry-run --json
|
|
132
|
+
blocks data files upload-to-local-storage \
|
|
133
|
+
--file ./invoice.pdf \
|
|
134
|
+
--parent-directory-id <directoryId> \
|
|
135
|
+
--configuration-name Default \
|
|
136
|
+
--access-modifier Private \
|
|
137
|
+
--yes --json
|
|
138
|
+
```
|
|
94
139
|
|
|
95
140
|
```ts
|
|
96
|
-
await files.uploadToLocalStorage({
|
|
97
|
-
name:
|
|
98
|
-
file
|
|
141
|
+
const uploaded = await blocksClient.data.files.uploadToLocalStorage({
|
|
142
|
+
name: file.name,
|
|
143
|
+
file,
|
|
144
|
+
parentDirectoryId: directoryId,
|
|
99
145
|
configurationName: "Default",
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
tags: ["invoice", "2026"]
|
|
146
|
+
accessModifier: "Private",
|
|
147
|
+
tags: ["invoice", "2026"],
|
|
103
148
|
});
|
|
104
149
|
```
|
|
105
150
|
|
|
106
|
-
|
|
151
|
+
This call creates the file object and uploads version 1 in one request. Unlike cloud presign, an empty local `parentDirectoryId` stays at the top level; it is not resolved through `moduleName`.
|
|
152
|
+
|
|
153
|
+
## Add a file version
|
|
107
154
|
|
|
108
|
-
|
|
155
|
+
Supplying an existing `itemId` to either upload flow creates another version after the caller passes the file's Edit check. For cloud storage, the dedicated command/method returns another pre-signed URL:
|
|
109
156
|
|
|
110
|
-
|
|
157
|
+
```bash
|
|
158
|
+
blocks data files versions <fileId> --limit 25 --json
|
|
159
|
+
blocks data files create-version <fileId> --configuration-name Default --dry-run --json
|
|
160
|
+
blocks data files create-version <fileId> --configuration-name Default --yes --json
|
|
161
|
+
```
|
|
111
162
|
|
|
112
163
|
```ts
|
|
113
|
-
await
|
|
114
|
-
|
|
115
|
-
fileStorageId: presign.fileId, // the fileId from presignedUploadUrl (or the equivalent id from uploadToLocalStorage's response)
|
|
116
|
-
artifactName: "invoice.pdf",
|
|
117
|
-
parentId: "", // "" for root, or a folder id
|
|
118
|
-
tags: ["invoice"]
|
|
119
|
-
}]
|
|
120
|
-
});
|
|
164
|
+
const history = await blocksClient.data.files.versions({ fileId, limit: 25 });
|
|
165
|
+
const next = await blocksClient.data.files.createVersion({ fileId, configurationName: "Default" });
|
|
121
166
|
```
|
|
122
167
|
|
|
123
|
-
|
|
168
|
+
Version history is newest-first and cursor-paginated. Use the returned opaque `nextCursor`; the current backend uses the last version number internally, but callers must not construct cursors. Limits are 1–100, default 25.
|
|
124
169
|
|
|
125
|
-
##
|
|
170
|
+
## Read and download
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
blocks data files get <fileId> --configuration-name Default --json
|
|
174
|
+
blocks data files get <fileId> --version <versionNo> --configuration-name Default --json
|
|
175
|
+
blocks data files get-many <fileId...> --configuration-name Default --json
|
|
176
|
+
```
|
|
126
177
|
|
|
127
178
|
```ts
|
|
128
|
-
const
|
|
129
|
-
|
|
179
|
+
const file = await blocksClient.data.files.get(fileId, {
|
|
180
|
+
configurationName: "Default",
|
|
181
|
+
version: 2,
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The response includes a download URL plus metadata. Download requires the caller's Download permission. Access-denied reads may deliberately look like missing resources so clients cannot probe hidden object ids.
|
|
186
|
+
|
|
187
|
+
## Directory and object workflows
|
|
188
|
+
|
|
189
|
+
For browsing, directories, search, move/copy/rename, trash, sharing, and ACL behavior, read [flows/object-management.md](flows/object-management.md).
|
|
190
|
+
|
|
191
|
+
## Update custom metadata
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
blocks data files update-additional-info <fileId> \
|
|
195
|
+
--additional-properties '{"status":"reviewed"}' \
|
|
196
|
+
--dry-run --json
|
|
197
|
+
blocks data files update-additional-info <fileId> \
|
|
198
|
+
--additional-properties '{"status":"reviewed"}' \
|
|
199
|
+
--yes --json
|
|
130
200
|
```
|
|
131
201
|
|
|
132
|
-
|
|
202
|
+
This updates `additionalProperties`; it does not rename, move, tag, or version the file.
|
|
203
|
+
|
|
204
|
+
## Delete safely
|
|
205
|
+
|
|
206
|
+
Deletion now distinguishes trash from permanent removal:
|
|
207
|
+
|
|
208
|
+
- `permanent: false` archives the file or directory so it can be restored.
|
|
209
|
+
- `permanent: true` removes it for good.
|
|
210
|
+
- The backend default is **`true`** when `permanent` is omitted.
|
|
211
|
+
|
|
212
|
+
The CLI defaults to safe soft deletion. Add `--permanent` only after explicit approval.
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
blocks data files delete <fileId> --dry-run --json
|
|
216
|
+
blocks data files delete <fileId> --yes --json
|
|
217
|
+
blocks data files delete <fileId> --permanent --dry-run --json
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
In app code, send the choice explicitly: `blocksClient.data.files.delete({ fileId, permanent: false })`.
|
|
221
|
+
|
|
222
|
+
## Permission model
|
|
223
|
+
|
|
224
|
+
Every storage request passes two checks:
|
|
225
|
+
|
|
226
|
+
1. The endpoint permission permits that class of action.
|
|
227
|
+
2. The object ACL permits the action on that specific file/directory.
|
|
133
228
|
|
|
134
|
-
|
|
229
|
+
Capabilities are ordered: `View`, `Download`, `Edit`, `Delete`, `Manage`, `Owner`. Higher capabilities imply lower ones. Directory children inherit ancestor access while `inheritsParentAccess` is true. New files inherit from their directory.
|
|
135
230
|
|
|
136
|
-
|
|
137
|
-
- **`files.info({ page, pageSize, sort, filter })`** — paged file metadata/listing for storage-browser UIs; unlike `presignedUploadUrl`, the SDK does not remap these field names to PascalCase — pass exactly what your app builds.
|
|
138
|
-
- **`files.updateAdditionalInfo({ itemId, additionalProperties })`** — attach searchable metadata to an uploaded file, e.g. a business reference or workflow status.
|
|
139
|
-
- **`files.delete({ fileId, configurationName?, eventQueueName? })`** — delete a file.
|
|
140
|
-
- **`dms.createFolder({ artifactName, parentId?, configurationName? })`** / **`dms.deleteFolder({ folderId, configurationName? })`** — DMS folder management.
|
|
231
|
+
Do not infer permission from a visible button or endpoint grant. Render actions from each object's returned `permissions` flags and still handle 403/404 races.
|
|
141
232
|
|
|
142
233
|
## Gotchas
|
|
143
234
|
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
- "
|
|
157
|
-
- "
|
|
158
|
-
- "
|
|
159
|
-
- "
|
|
160
|
-
- "
|
|
161
|
-
- "
|
|
235
|
+
- Upload now creates the visible file object; legacy DMS registration is wrong and may fail after the bytes were successfully PUT.
|
|
236
|
+
- Cloud presign creates metadata before the provider PUT. Treat the two steps as a recoverable workflow and surface partial failure.
|
|
237
|
+
- A name must contain an allowed extension. Directories may restrict extensions.
|
|
238
|
+
- Names are unique within a directory. File move/copy can fail on a name conflict or extension policy.
|
|
239
|
+
- `Private` is the safe default. Use `Public` only when unauthenticated download is intended.
|
|
240
|
+
- `configurationName` selects an existing provider record; it does not configure storage.
|
|
241
|
+
- Most legacy file SDK methods return `Promise<unknown>`; validate responses at the boundary.
|
|
242
|
+
- Never send Blocks auth headers to a pre-signed provider URL.
|
|
243
|
+
- Never use a raw API call to work around a missing CLI/SDK wrapper; update the client surface first.
|
|
244
|
+
|
|
245
|
+
## Example triggers
|
|
246
|
+
|
|
247
|
+
- "Upload this PDF into the Contracts folder."
|
|
248
|
+
- "Build a file browser with folders and search."
|
|
249
|
+
- "Show files shared with the current user."
|
|
250
|
+
- "Move this file to trash and let users restore it."
|
|
251
|
+
- "Add version history and upload a replacement version."
|
|
252
|
+
- "Share this directory with a role and let its children inherit access."
|
|
253
|
+
- "Move, copy, or rename a file."
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Object management
|
|
2
|
+
|
|
3
|
+
Use `blocks data files *` from a terminal and `blocksClient.data.directories` / `blocksClient.data.objects` / `blocksClient.data.files` in app code. Do not fall back to legacy `data.dms.*` helpers or raw HTTP.
|
|
4
|
+
|
|
5
|
+
## Browse a directory
|
|
6
|
+
|
|
7
|
+
Use the current `get-objects` operation with:
|
|
8
|
+
|
|
9
|
+
- `parentDirectoryId` for a concrete directory, or `moduleName` to resolve a module's default directory when no parent id is supplied.
|
|
10
|
+
- `cursor` from the previous response and `limit` from 1–200 (default 50).
|
|
11
|
+
- `type: "directory" | "file"` to narrow results.
|
|
12
|
+
- `search` for name filtering within that parent.
|
|
13
|
+
|
|
14
|
+
The result contains `items`, `nextCursor`, `hasMore`, and `totalChildCount`. Each item has a lowercase `type` discriminator and permission flags. Continue while `hasMore` using `nextCursor`; access filtering can produce a short page even when more results remain.
|
|
15
|
+
|
|
16
|
+
Use `search-objects` for a case-insensitive name search across descendants. Pass `query`, optional `directoryId`, optional type, cursor, and limit. Search text is treated literally rather than as a regular expression.
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
blocks data files list --parent-id <directoryId> --type file --limit 50 --json
|
|
20
|
+
blocks data files search invoice --directory-id <directoryId> --json
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const page = await blocksClient.data.objects.list({ parentDirectoryId, limit: 50 });
|
|
25
|
+
const matches = await blocksClient.data.objects.search({ query: "invoice", directoryId });
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Manage directories
|
|
29
|
+
|
|
30
|
+
- **Create:** provide `name`, an existing `parentDirectoryId`, optional `description`, `configurationName`, and `allowedFileExtensions`. If the parent is empty but `moduleName` is present, the backend resolves the module default. Creating a true root uses a separate owner-only capability and should not be a normal app action.
|
|
31
|
+
- **Read details:** get one directory by `directoryId`. The response includes path, ancestors, child counts, size, extension rules, inheritance, timestamps, and permissions.
|
|
32
|
+
- **Update:** provide `directoryId` and optional `name`/`description`. Default directories cannot be renamed.
|
|
33
|
+
- **Move:** provide `directoryId` and `targetDirectoryId`; an empty target means top level. Moving into self/descendants is rejected. Default directories cannot move.
|
|
34
|
+
- **Delete:** send `permanent: false` for trash. Permanent deletion requires an empty directory. Default directories cannot be deleted.
|
|
35
|
+
|
|
36
|
+
Directory names must be 1–255 characters, trimmed, not `.`/`..`, and contain no slash or backslash. Descriptions are limited to 2,000 characters.
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
blocks data files directory-create Contracts --parent-id <directoryId> \
|
|
40
|
+
--allowed-extensions pdf,docx --dry-run --json
|
|
41
|
+
blocks data files directory-get <directoryId> --json
|
|
42
|
+
blocks data files directory-update <directoryId> --name Agreements --dry-run --json
|
|
43
|
+
blocks data files directory-move <directoryId> --target-directory-id <targetId> --dry-run --json
|
|
44
|
+
blocks data files directory-delete <directoryId> --dry-run --json
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use the matching `blocksClient.data.directories.create/get/update/move/delete` methods in app code.
|
|
48
|
+
|
|
49
|
+
## Manage files
|
|
50
|
+
|
|
51
|
+
- **Rename:** needs Edit and a unique name in the current directory.
|
|
52
|
+
- **Move:** needs Delete on the source file and Edit on the target directory. Stored bytes and versions stay in place.
|
|
53
|
+
- **Copy:** needs View on the source and Edit on the target. The new file gets a new id; version rows reference the same immutable stored bytes. It inherits from the target. Set `copyAccessPolicies` only when the user intends to duplicate direct policy entries.
|
|
54
|
+
- **Versions:** list newest-first with a cursor and limit 1–100. Creating a cloud version returns `versionNo` and `uploadUrl`; PUT the bytes to that URL without Blocks auth headers.
|
|
55
|
+
|
|
56
|
+
Move/copy reject a target name collision and a file extension disallowed by the target directory.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
blocks data files rename <fileId> --name final.pdf --dry-run --json
|
|
60
|
+
blocks data files move <fileId> --target-directory-id <targetId> --dry-run --json
|
|
61
|
+
blocks data files copy <fileId> --target-directory-id <targetId> --dry-run --json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Use `blocksClient.data.files.rename`, `.move`, and `.copy` in app code.
|
|
65
|
+
|
|
66
|
+
## Trash and restore
|
|
67
|
+
|
|
68
|
+
Use soft delete (`permanent: false`) to archive an item. List archived files/directories with `get-trash`, optionally filtering by type and paging with cursor/limit. Restore with `restore-from-trash`; permanently purge an archived item with `delete-from-trash` only after explicit approval.
|
|
69
|
+
|
|
70
|
+
Restore returns the item to its original parent. Reads and mutations remain ACL-filtered while the item is archived.
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
blocks data files trash --type file --json
|
|
74
|
+
blocks data files restore <resourceId> --dry-run --json
|
|
75
|
+
blocks data files purge <resourceId> --dry-run --json
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Use `blocksClient.data.objects.trash`, `.restore`, and `.deleteFromTrash` in app code.
|
|
79
|
+
|
|
80
|
+
## Shared objects
|
|
81
|
+
|
|
82
|
+
Use `get-shared-objects` for live items shared with the caller. It is cursor-paginated and may be filtered by `directory`/`file`. Owned objects are excluded. Direct or inherited allow entries for the current user, role, or organization qualify as shares.
|
|
83
|
+
|
|
84
|
+
Use `share-object` for the common allow-only action. Provide:
|
|
85
|
+
|
|
86
|
+
- `resourceId`
|
|
87
|
+
- `resourceType`: `Directory` or `File`
|
|
88
|
+
- `principalType`: `User`, `Role`, `Organization`, or `Everyone`
|
|
89
|
+
- `principalId` for every type except `Everyone`
|
|
90
|
+
- `permission`: `View`, `Download`, `Edit`, `Delete`, `Manage`, or `Owner`
|
|
91
|
+
- optional future `expiresAt`
|
|
92
|
+
|
|
93
|
+
Sharing requires Manage on the resource.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
blocks data files shared --json
|
|
97
|
+
blocks data files share <resourceId> --resource-type Directory \
|
|
98
|
+
--principal-type Role --principal-id editors --permission Edit --dry-run --json
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Use `blocksClient.data.objects.shared` and `.share` in app code.
|
|
102
|
+
|
|
103
|
+
## Advanced access policies
|
|
104
|
+
|
|
105
|
+
Use the policy operations only for an access-management UI:
|
|
106
|
+
|
|
107
|
+
- `get-access-policies` lists direct entries on a resource.
|
|
108
|
+
- `grant-access` adds Allow or Deny with optional priority and expiry.
|
|
109
|
+
- `update-access-policy` replaces an entry and requires its `policyItemId`.
|
|
110
|
+
- `revoke-access-policy` removes an entry by `resourceId` and `policyItemId`.
|
|
111
|
+
- `resolve-access` returns the caller's `canView`, `canDownload`, `canEdit`, `canDelete`, `canManage`, and `canOwner` flags.
|
|
112
|
+
- `toggle-inheritance` changes whether ancestors participate in resolution.
|
|
113
|
+
|
|
114
|
+
Enums are JSON strings, not guessed numeric values. Priority must be non-negative and expiry must be in the future.
|
|
115
|
+
|
|
116
|
+
Policy guardrails:
|
|
117
|
+
|
|
118
|
+
- Manage is required to change access.
|
|
119
|
+
- A Deny aimed at the resource owner is rejected.
|
|
120
|
+
- Turning inheritance off is rejected until the resource has a direct Allow entry, preventing an orphaned resource.
|
|
121
|
+
- The request model for access-policy listing contains `includeInherited`, but the current controller does not use it; do not promise inherited entries in that response. Use `resolve-access` for effective permissions.
|
|
122
|
+
- Object reads may return 404 instead of 403 to avoid revealing hidden ids.
|
|
123
|
+
|
|
124
|
+
CLI access commands are `access-list`, `access-grant`, `access-update`, `access-revoke`, `access-resolve`, and `inheritance`. Their SDK equivalents are under `blocksClient.data.objects`.
|
|
@@ -29,23 +29,36 @@ All of these commands are project-scoped: they need a selected project (`blocks
|
|
|
29
29
|
```
|
|
30
30
|
blocks auth oidc-clients save \
|
|
31
31
|
--client-display-name "<app name>" \
|
|
32
|
+
--client-type public \
|
|
32
33
|
--redirect-uris "https://<app-domain>/login/callback" \
|
|
33
34
|
--require-pkce --active \
|
|
34
35
|
--scope "openid profile" \
|
|
35
36
|
--register-as-identity-provider \
|
|
36
37
|
[--dry-run] [--yes]
|
|
37
38
|
```
|
|
38
|
-
This mirrors exactly what `blocks new web`'s interactive OIDC-client prompt does when scaffolding a new web app. `--register-as-identity-provider` is what turns this from "just an OIDC client" into something the hosted-login redirect flow (`auth.idp.redirectToProvider()` / `auth.idp.callback()`) can authenticate against — per the CLI's own scaffold help text, this registers the client "as a Blocks OIDC identity provider" in the same call.
|
|
39
|
-
3. **
|
|
40
|
-
|
|
39
|
+
This mirrors exactly what `blocks new web`'s interactive OIDC-client prompt does when scaffolding a new web app. `--register-as-identity-provider` is what turns this from "just an OIDC client" into something the hosted-login redirect flow (`auth.idp.redirectToProvider()` / `auth.idp.callback()`) can authenticate against — per the CLI's own scaffold help text, this registers the client "as a Blocks OIDC identity provider" in the same call.
|
|
40
|
+
3. **Verify the auto-created provider before handing off.** `--register-as-identity-provider` creates the provider record for you — but check what landed in it with `blocks auth idp list --json`, because on the common path several fields come back null. See the footguns below. If `authorizationUrl` is null, hosted login will not redirect: `GET /iam/v4/idp/initiate` (what `auth.idp.redirectToProvider()` calls) builds its target as `provider.AuthorizationUrl ?? ""` plus a query string, so the browser navigates to the app's own origin with OIDC params attached. The repair, for a provider that already exists in that state:
|
|
41
|
+
```
|
|
42
|
+
blocks auth idp update <providerItemId> \
|
|
43
|
+
--authorization-url "<tenant authorize endpoint>" \
|
|
44
|
+
--token-url "<tenant token endpoint>" \
|
|
45
|
+
--user-info-url "<tenant userinfo endpoint>" \
|
|
46
|
+
[--dry-run] [--yes]
|
|
47
|
+
```
|
|
48
|
+
`idp update` is the only route that persists these three — IAM's create path accepts them in its request model and drops them, and the repository's update is a plain replace with no re-discovery, so values set here stick. **Read the tenant's discovery document for the correct endpoint values rather than composing them by hand** — see the last footgun.
|
|
49
|
+
4. **Inspect/manage the resulting identity-provider record** with `blocks auth idp list [--json]` / `blocks auth idp get <id> [--json]`. Use `blocks auth idp status <id> --active|--active=false` to enable/disable without deleting, and `blocks auth idp delete <id>` to remove it — deleting an identity provider **also deletes its related OIDC client registration**, so treat `idp delete` as the higher-blast-radius operation of the two.
|
|
50
|
+
5. **`blocks auth idp create`/`update` exist as a separate, more general path** for constructing an identity-provider record directly — most relevant when federating an *external* identity provider (Google, Azure AD, Okta, etc.) rather than using Blocks' own OIDC client as the login mechanism:
|
|
41
51
|
```
|
|
42
52
|
blocks auth idp create --provider <p> --provider-type <t> --protocol <proto> \
|
|
43
53
|
--client-id <id> [--client-secret <secret>] [--display-name] [--issuer] \
|
|
44
54
|
[--scope] [--redirect-uris a,b] [--active] \
|
|
45
|
-
[--
|
|
55
|
+
[--authorization-url] [--token-url] [--user-info-url] [--jwks-uri] \
|
|
56
|
+
[--well-known-url] [--response-type] [--grant-types a,b] [--require-pkce] \
|
|
57
|
+
[--token-endpoint-auth-method] [--initial-roles a,b] [--initial-permissions a,b] \
|
|
58
|
+
[--icon] [--body '<json>'|--file <path>] [--dry-run] [--yes]
|
|
46
59
|
```
|
|
47
|
-
`--provider`, `--provider-type`, `--protocol`, and `--client-id` are required on create, and are immutable afterward — `auth idp update <id>` accepts the same flags but IAM requires you to either omit them or echo the existing values exactly.
|
|
48
|
-
|
|
60
|
+
`--provider`, `--provider-type`, `--protocol`, and `--client-id` are required on create, and are immutable afterward — `auth idp update <id>` accepts the same flags but IAM requires you to either omit them or echo the existing values exactly. Apple-specific fields (`teamId`, `keyId`, `privateKey`, `appleAudience`) go through `--body`/`--file` so no private key lands in shell history. Note that `create` stores `issuer`, `jwksUri` and `wellKnownUrl` but silently drops `authorizationUrl`, `tokenUrl` and `userInfoUrl` — pass those to `idp update` in a second call. **How exactly a `clientId` passed here pairs with an OIDC client record is not shown anywhere documented** — the two collections are related (per the cascading delete behavior above) but the create/update commands don't expose an explicit "link to this OIDC client" field beyond passing the same id. If you need to federate an external provider, treat `idp create`'s field values as IAM's contract and confirm anything beyond the flags above against the tenant's actual behavior rather than guessing.
|
|
61
|
+
6. **Hand off.** Once a client id (and, if relevant, an identity-provider id) exists, the frontend wiring — login button, callback route, token handling, `client.auth.idp.initiate()`/`redirectToProvider()`/`.callback()` from `@seliseblocks/client` — is owned by **blocks-iam-sso-oidc-implementation**. Do not duplicate that work here; route to it.
|
|
49
62
|
|
|
50
63
|
## Mutation discipline
|
|
51
64
|
|
|
@@ -56,10 +69,13 @@ Every create/update/delete/status/rotate-secret command above follows the same p
|
|
|
56
69
|
|
|
57
70
|
Never raw `fetch`/`curl` these endpoints to route around the CLI's confirmation/dry-run discipline — use the commands above so the same guardrails apply.
|
|
58
71
|
|
|
59
|
-
##
|
|
72
|
+
## Verified footguns
|
|
60
73
|
|
|
61
|
-
-
|
|
62
|
-
- **
|
|
74
|
+
- **`--client-type public` is not cosmetic — omitting it stores a browser app as confidential.** IAM derives `tokenEndpointAuthMethod` from `clientType`: `public` (or any device-flow client) becomes `"none"`, anything else becomes `"client_secret_post"`. Leave `--client-type` off and a PKCE SPA is persisted as a confidential client that is also eligible for the `client_credentials` grant. Always pass `--client-type public` for a browser client. `--require-pkce` alone does not imply it.
|
|
75
|
+
- **The auto-created provider's endpoint URLs come from discovery, and discovery is driven by one field.** IAM's repository-level `CreateIdentityProviderAsync` runs `PopulateProviderEndpointsFromWellKnownAsync` before inserting: if `wellKnownUrl` is set it fetches the document and fills `authorizationUrl`, `tokenUrl`, `userInfoUrl`, `jwksUri` and `issuer` from it. The only input that reaches `wellKnownUrl` on this path is `oidc-clients save --external-discovery-endpoint`. Omit it and the `else` branch runs `GetSocialMetadata(provider)`, which matches only names containing `google` or `microsoft` — for an app-named provider it returns null, so all five fields are written null **and `scope` is overwritten with `"openid profile email"`**, discarding the `offline_access` the OIDC client had just been given. Check both `authorizationUrl` and `scope` on the provider after registering.
|
|
76
|
+
- **This only happens at create.** Re-saving the same OIDC client does not re-run discovery: the `existingProvider` branch never touches `wellKnownUrl`, and the repository's update is a plain replace. A provider already written with null URLs cannot be repaired by re-saving the client — use `idp update`, or delete and recreate.
|
|
77
|
+
- **PKCE and the discovery URL exist on both records and mean different things.** `requirePkce` on the OIDC client governs the app's own authorize flow; `--require-pkce` on `auth idp` governs the *upstream* handshake `/idp/initiate` performs. `--external-discovery-endpoint` on the client is read only as the linked provider's `wellKnownUrl`; on the provider record itself use `--well-known-url`.
|
|
78
|
+
- **Do not compose the tenant's own discovery or authorize URL from a template.** `DiscoveryController` declares `/{tenant_id}/.well-known/openid-configuration` as an absolute route, outside the `/iam/v4` prefix that every other IAM endpoint sits behind, and every `wellKnownUrl` example in IAM's own source and tests is an *external* provider (`accounts.google.com`, `login.microsoftonline.com`, `idp.example.com`) — there is no in-repo example of a Blocks tenant pointing at itself. Whether that route resolves through the `blocksapi.<domain>` gateway as-is or needs an extra segment is **not settled in source**. Fetch the tenant's discovery document and read the endpoints out of it, or ask the user; do not assert a shape you have not seen respond.
|
|
63
79
|
|
|
64
80
|
## Secondary, optional: the SDK's `identityProviders` admin methods
|
|
65
81
|
|
|
@@ -65,8 +65,8 @@ Run `blocks init` once per project directory to create `blocks.json`, `blocks/da
|
|
|
65
65
|
|
|
66
66
|
Then route to what the user actually wants:
|
|
67
67
|
- Building a frontend from scratch → resolve the app's public OIDC client first, then scaffold:
|
|
68
|
-
- `blocks auth oidc-clients list --json` — check whether a client already registered for this project fits. If none fits, create one directly (no portal visit needed): `blocks auth oidc-clients save --client-display-name <appName> --redirect-uris https://<domain>:5173/login/callback --scope "openid profile" --require-pkce --register-as-identity-provider --dry-run --json`, then re-run with `--yes` after showing the dry-run output and getting approval. See the blocks-iam-sso-oidc-configuration skill for the full decision tree and field-level gotchas.
|
|
69
|
-
- `blocks new web <name> --x-blocks-key <tenantId> --app-domain <domain> --client-id <the-resolved-client-id>`. **Always pass `--client-id` and `--app-domain` explicitly** — omitting either drops `new web` into an interactive pick-list prompt with no non-interactive escape (not even to "skip"), which hangs a scripted/agent run with no stdin to answer it. Omit `--blocks-api-url` unless the project uses a non-default gateway; the scaffold derives it from the app domain, e.g. `https://dqrsf.slsblx.com` -> `https://blocksapi.slsblx.com`.
|
|
68
|
+
- `blocks auth oidc-clients list --json` — check whether a client already registered for this project fits. If none fits, create one directly (no portal visit needed): `blocks auth oidc-clients save --client-display-name <appName> --client-type public --redirect-uris https://<domain>:5173/login/callback --scope "openid profile" --require-pkce --register-as-identity-provider --dry-run --json`, then re-run with `--yes` after showing the dry-run output and getting approval. `--client-type public` is required — IAM derives `tokenEndpointAuthMethod` from it, so omitting it stores a browser client as confidential. `--register-as-identity-provider` creates the linked identity provider in the same call; nothing further to run. See the blocks-iam-sso-oidc-configuration skill for the full decision tree and field-level gotchas.
|
|
69
|
+
- `blocks new web <name> --x-blocks-key <tenantId> --app-domain <domain> --client-id <the-resolved-client-id>`. **Always pass `--client-id` and `--app-domain` explicitly** — omitting either drops `new web` into an interactive pick-list prompt with no non-interactive escape (not even to "skip"), which hangs a scripted/agent run with no stdin to answer it. Omit `--blocks-api-url` unless the project uses a non-default gateway; the scaffold derives it from the app domain, e.g. `https://dqrsf.slsblx.com` -> `https://blocksapi.slsblx.com`.
|
|
70
70
|
- Defining data / CRUD / localization / release on an existing project → hand off to the matching skill; the project is already selected via `blocks use`, so its commands can proceed directly.
|
|
71
71
|
|
|
72
72
|
## Gotchas
|