flowbase-cli 0.1.15__tar.gz → 0.1.17__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbase-cli
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Command line client for the FlowBase OpenAPI
5
5
  Author: DeepFlowAI
6
6
  License: Apache-2.0
@@ -76,7 +76,7 @@ flowbase
76
76
  ├── docs list | show | example | operations Offline endpoint catalog
77
77
  ├── guide Offline product and task guide (--format markdown|json)
78
78
  ├── organization employees | departments
79
- ├── apps list | get | create | update | set-enabled | delete
79
+ ├── apps list | get | create | update | set-enabled | delete | navigation
80
80
  │ add-objects | remove-object | set-group | reorder-objects
81
81
  │ add-dashboards | remove-dashboard | set-dashboard-group | reorder-members
82
82
  ├── groups create | reorder | update | delete App groups (under an app)
@@ -88,6 +88,7 @@ flowbase
88
88
  ├── approvals list | get | create | update | reorder | set-enabled | delete
89
89
  ├── pages list | options | get | create | update | set-default | clear-default | delete | restore | evaluate
90
90
  ├── dashboards list | get | create | update | copy | delete | run
91
+ │ record-view-options | record-view-preview | records
91
92
  ├── changes commit | release | direct-release
92
93
  ├── versions list | show | releases Read-only history
93
94
  ├── environments restart Destructive test-env reset
@@ -138,7 +139,8 @@ Most `update` commands replace a **full snapshot** and accept an optional `baseR
138
139
 
139
140
  ```bash
140
141
  flowbase dashboards get dsh_xxx > current.json # authoritative snapshot
141
- # edit current.json: change fields, keep filters/components, use draftRevision as baseRevision
142
+ # Keep only label/description/icon/filters/components; use draftRevision as baseRevision.
143
+ # Edit the desired fields while preserving all other components and filters.
142
144
  flowbase dashboards update dsh_xxx --json-file current.json
143
145
  ```
144
146
 
@@ -160,6 +162,14 @@ flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"polic
160
162
 
161
163
  `records create` returns `{recordId}`; use it with `records get`.
162
164
 
165
+ To add existing objects to an application, pass their IDs with `--object-ids`:
166
+
167
+ ```bash
168
+ flowbase apps add-objects app_xxx --object-ids obj_a,obj_b
169
+ ```
170
+
171
+ Use a comma-separated list for multiple objects. Object IDs are not positional arguments. Run `flowbase docs show apps.objects.add` for the endpoint details.
172
+
163
173
  Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`/`reg_`/`cmp_`-prefixed; see `docs catalogs id-prefixes`.
164
174
 
165
175
  ### 6.2 Edit and publish configuration on a test environment
@@ -178,6 +188,31 @@ flowbase changes release --message "Ship sales dashboard" --yes
178
188
 
179
189
  Dashboard component code runs server-side in a sandbox and returns ECharts config / metric / table / markdown. Components and filters are validated on save; use `flowbase dashboards get <id>` to discover component IDs, then `flowbase dashboards run <dashboard_id> <component_id>` to inspect `ok/output/error/logs` in a read-only draft preview (requires metadata:read and records:read). Visual chart rendering still requires the web UI.
180
190
 
191
+ App sections and record-view components:
192
+
193
+ ```bash
194
+ flowbase apps get app_xxx
195
+ flowbase docs catalogs app-sections
196
+ flowbase apps navigation app_xxx --json '{"type":"appSection.create","name":"Sales"}' --yes
197
+ flowbase groups create app_xxx --name Orders --section-id sec_xxx
198
+ flowbase apps add-objects app_xxx --object-ids obj_xxx --section-id sec_xxx
199
+ # add-dashboards and add-iframes also accept --section-id.
200
+ flowbase apps navigation app_xxx --json '{"type":"appMember.move","id":"obj_xxx","sectionId":"sec_xxx","groupId":"grp_xxx","expectedSectionId":"sec_xxx","expectedGroupId":null}' --yes
201
+
202
+ flowbase docs catalogs dashboard-record-views
203
+ flowbase dashboards record-view-options
204
+ flowbase docs example dashboards.record-view-preview --output preview.json
205
+ # Replace placeholder IDs and configure component/filters before previewing.
206
+ flowbase dashboards record-view-preview dsh_xxx --json-file preview.json
207
+ flowbase dashboards records dsh_xxx dcm_xxx --page 1 --query Orders --json '{"filterValues":{}}'
208
+ ```
209
+
210
+ `apps get` preserves section definitions and group/member `sectionId`. Navigation supports section creation, rename, empty-section deletion, full section ordering, group moves and member moves. Moves require explicit source and target positions; use JSON `null` for unassigned/ungrouped. See `app-sections` for all six request bodies. Navigation requires confirmation (`--yes` for automation), and writes only a draft.
211
+
212
+ Record-view previews and queries require both `metadata:read` and `records:read`, use the current Host's records and draft configuration, and return `mode: draft-preview` plus `draftRevision`. They use API-key visibility rather than an employee's workspace identity; hidden fields stay excluded and hidden filter fields are rejected. Page size is configured on the component (10/20/50). Search is ignored when `showSearch` is false. `dashboards run` is for code components.
213
+
214
+ For `dashboards update`, construct the body from the latest `get`: preserve `label`, `description`, `icon`, `filters`, and **all** `components`; set `baseRevision` to the returned `draftRevision`. Omit response-only fields. Keep existing component IDs and `recordView` unchanged when editing other properties; omit IDs on new components. Omitting `components` or `filters` clears them.
215
+
181
216
  ### 6.3 Production hotfix
182
217
 
183
218
  ```bash
@@ -289,3 +324,68 @@ Transport requests time out after 60 seconds. A lost write response has an unkno
289
324
  - `flowbase docs catalogs workflow-python`: record creation examples and failed-task retry. `workflows python-runtime OBJECT new` discovers current object bindings; `workflows executions` includes Python attempts, logs and `canRetry`.
290
325
 
291
326
  Use `records list OBJECT --view-id VIEW --view-layout` for tree/Gantt projections; the default remains a flat list. `records update` accepts `updatedAt` and `planViewId` in its JSON body for version-checked plan edits. Formula validation/trials require both metadata read and record read; workflow retry requires both metadata write and record write and explicit CLI confirmation (`--yes` in automation).
327
+
328
+ ### Reusable iframe configuration (4.11)
329
+
330
+ `flowbase iframes list|get|create|update|delete` manages environment-specific iframe drafts. Create/update use `--json-file`; fields are `label`, `icon` (default `IconWorld`), `color` (default `#2563EB`), and a complete HTTPS `url` without credentials. Update and delete require `baseRevision` from `iframes get`; deletion also requires confirmation (`--yes` for noninteractive use).
331
+
332
+ ```bash
333
+ flowbase iframes create --json-file iframe.json
334
+ flowbase iframes get ifr_example
335
+ flowbase iframes update ifr_example --json-file iframe-update.json
336
+ flowbase apps add-iframes app_example ifr_example
337
+ flowbase apps set-iframe-group app_example ifr_example --group-id grp_example
338
+ flowbase apps set-iframe-group app_example ifr_example --ungrouped
339
+ flowbase apps reorder-members app_example --json '{"items":[{"type":"iframe","id":"ifr_example"}]}'
340
+ flowbase apps remove-iframe app_example ifr_example --yes
341
+ flowbase iframes delete ifr_example --json '{"baseRevision":12}' --yes
342
+ ```
343
+
344
+ Removal only unlinks one app. Deletion unlinks all apps atomically. Changes follow the existing commit/release/rollback workflow; they do not fetch, proxy or modify the external website. API keys use `metadata:read`/`metadata:write`; browser roles use `iframe.manage` for shared resources and `app.manage` for app associations.
345
+
346
+ ### Custom object views (4.12)
347
+
348
+ Read `flowbase docs catalogs custom-views` and `flowbase views list OBJECT` first. Save the following as `view.json`, replacing `baseRevision` with the current `draftRevision`:
349
+
350
+ ```json
351
+ {
352
+ "baseRevision": 4,
353
+ "name": "Custom overview",
354
+ "enabled": false,
355
+ "defaultFieldIds": [],
356
+ "displayMode": "custom",
357
+ "custom": {
358
+ "runtimeVersion": "cv1",
359
+ "source": "import React from 'react'; export default function View(){return <div>Hello</div>}"
360
+ }
361
+ }
362
+ ```
363
+
364
+ ```bash
365
+ flowbase views create obj_example --json-file view.json
366
+ flowbase views list obj_example
367
+ flowbase views update obj_example viw_example --json-file view.json
368
+ flowbase views set-enabled obj_example viw_example --base-revision 6 --enabled --yes
369
+ ```
370
+
371
+ Use the revision returned by the preceding write, rather than the example numbers. Custom views start disabled; preview in the browser and enable when ready. Updates replace the writable snapshot: preserve `custom.source`, `custom.runtimeVersion`, `displayMode`, filters and other configuration from `views list`, excluding the response-only `id` and `status`. Built-in/custom view types cannot be converted. JSX source is limited to 512 KiB UTF-8; the runtime supports `react`, `three` and `flowbase`. CLI configuration does not execute browser previews.
372
+
373
+ ### Record detail embedded pages (4.13)
374
+
375
+ `flowbase docs catalogs page-components` documents `recordEmbed`, supported at the top level or inside tabs of a detail Page. Add this component to an existing region column (or a tab's `components`):
376
+
377
+ ```json
378
+ {
379
+ "kind": "recordEmbed",
380
+ "label": "External panel",
381
+ "showTitle": true,
382
+ "url": "https://example.com/panel",
383
+ "height": 480,
384
+ "allowRead": false,
385
+ "allowedFieldIds": []
386
+ }
387
+ ```
388
+
389
+ Read `flowbase pages get PAGE`, preserve the existing components and IDs, and remove the read-only Page keys listed in `docs catalogs page-components`. Wrap the editable snapshot as `{"baseRevision":4,"page":{...}}`, then use `flowbase pages update PAGE --json-file page.json` (or `pages create --json-file page.json` for a new detail Page). For creation, omit the Page ID and IDs of new regions/components so the server allocates them.
390
+
391
+ Height must be an integer from 240 to 1600. URLs must use external HTTPS and exclude credentials, identity/context parameters and templates. To permit SDK reads, set `allowRead: true` and select at least one unique active field ID in `allowedFieldIds`; current record/field permissions still apply. The browser SDK is `/sdk/record-embed.v1.js`. The CLI configures the component; rendering and SDK calls run in the browser. `flowbase-user` has no metadata management commands.
@@ -67,7 +67,7 @@ flowbase
67
67
  ├── docs list | show | example | operations Offline endpoint catalog
68
68
  ├── guide Offline product and task guide (--format markdown|json)
69
69
  ├── organization employees | departments
70
- ├── apps list | get | create | update | set-enabled | delete
70
+ ├── apps list | get | create | update | set-enabled | delete | navigation
71
71
  │ add-objects | remove-object | set-group | reorder-objects
72
72
  │ add-dashboards | remove-dashboard | set-dashboard-group | reorder-members
73
73
  ├── groups create | reorder | update | delete App groups (under an app)
@@ -79,6 +79,7 @@ flowbase
79
79
  ├── approvals list | get | create | update | reorder | set-enabled | delete
80
80
  ├── pages list | options | get | create | update | set-default | clear-default | delete | restore | evaluate
81
81
  ├── dashboards list | get | create | update | copy | delete | run
82
+ │ record-view-options | record-view-preview | records
82
83
  ├── changes commit | release | direct-release
83
84
  ├── versions list | show | releases Read-only history
84
85
  ├── environments restart Destructive test-env reset
@@ -129,7 +130,8 @@ Most `update` commands replace a **full snapshot** and accept an optional `baseR
129
130
 
130
131
  ```bash
131
132
  flowbase dashboards get dsh_xxx > current.json # authoritative snapshot
132
- # edit current.json: change fields, keep filters/components, use draftRevision as baseRevision
133
+ # Keep only label/description/icon/filters/components; use draftRevision as baseRevision.
134
+ # Edit the desired fields while preserving all other components and filters.
133
135
  flowbase dashboards update dsh_xxx --json-file current.json
134
136
  ```
135
137
 
@@ -151,6 +153,14 @@ flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"polic
151
153
 
152
154
  `records create` returns `{recordId}`; use it with `records get`.
153
155
 
156
+ To add existing objects to an application, pass their IDs with `--object-ids`:
157
+
158
+ ```bash
159
+ flowbase apps add-objects app_xxx --object-ids obj_a,obj_b
160
+ ```
161
+
162
+ Use a comma-separated list for multiple objects. Object IDs are not positional arguments. Run `flowbase docs show apps.objects.add` for the endpoint details.
163
+
154
164
  Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`/`reg_`/`cmp_`-prefixed; see `docs catalogs id-prefixes`.
155
165
 
156
166
  ### 6.2 Edit and publish configuration on a test environment
@@ -169,6 +179,31 @@ flowbase changes release --message "Ship sales dashboard" --yes
169
179
 
170
180
  Dashboard component code runs server-side in a sandbox and returns ECharts config / metric / table / markdown. Components and filters are validated on save; use `flowbase dashboards get <id>` to discover component IDs, then `flowbase dashboards run <dashboard_id> <component_id>` to inspect `ok/output/error/logs` in a read-only draft preview (requires metadata:read and records:read). Visual chart rendering still requires the web UI.
171
181
 
182
+ App sections and record-view components:
183
+
184
+ ```bash
185
+ flowbase apps get app_xxx
186
+ flowbase docs catalogs app-sections
187
+ flowbase apps navigation app_xxx --json '{"type":"appSection.create","name":"Sales"}' --yes
188
+ flowbase groups create app_xxx --name Orders --section-id sec_xxx
189
+ flowbase apps add-objects app_xxx --object-ids obj_xxx --section-id sec_xxx
190
+ # add-dashboards and add-iframes also accept --section-id.
191
+ flowbase apps navigation app_xxx --json '{"type":"appMember.move","id":"obj_xxx","sectionId":"sec_xxx","groupId":"grp_xxx","expectedSectionId":"sec_xxx","expectedGroupId":null}' --yes
192
+
193
+ flowbase docs catalogs dashboard-record-views
194
+ flowbase dashboards record-view-options
195
+ flowbase docs example dashboards.record-view-preview --output preview.json
196
+ # Replace placeholder IDs and configure component/filters before previewing.
197
+ flowbase dashboards record-view-preview dsh_xxx --json-file preview.json
198
+ flowbase dashboards records dsh_xxx dcm_xxx --page 1 --query Orders --json '{"filterValues":{}}'
199
+ ```
200
+
201
+ `apps get` preserves section definitions and group/member `sectionId`. Navigation supports section creation, rename, empty-section deletion, full section ordering, group moves and member moves. Moves require explicit source and target positions; use JSON `null` for unassigned/ungrouped. See `app-sections` for all six request bodies. Navigation requires confirmation (`--yes` for automation), and writes only a draft.
202
+
203
+ Record-view previews and queries require both `metadata:read` and `records:read`, use the current Host's records and draft configuration, and return `mode: draft-preview` plus `draftRevision`. They use API-key visibility rather than an employee's workspace identity; hidden fields stay excluded and hidden filter fields are rejected. Page size is configured on the component (10/20/50). Search is ignored when `showSearch` is false. `dashboards run` is for code components.
204
+
205
+ For `dashboards update`, construct the body from the latest `get`: preserve `label`, `description`, `icon`, `filters`, and **all** `components`; set `baseRevision` to the returned `draftRevision`. Omit response-only fields. Keep existing component IDs and `recordView` unchanged when editing other properties; omit IDs on new components. Omitting `components` or `filters` clears them.
206
+
172
207
  ### 6.3 Production hotfix
173
208
 
174
209
  ```bash
@@ -280,3 +315,68 @@ Transport requests time out after 60 seconds. A lost write response has an unkno
280
315
  - `flowbase docs catalogs workflow-python`: record creation examples and failed-task retry. `workflows python-runtime OBJECT new` discovers current object bindings; `workflows executions` includes Python attempts, logs and `canRetry`.
281
316
 
282
317
  Use `records list OBJECT --view-id VIEW --view-layout` for tree/Gantt projections; the default remains a flat list. `records update` accepts `updatedAt` and `planViewId` in its JSON body for version-checked plan edits. Formula validation/trials require both metadata read and record read; workflow retry requires both metadata write and record write and explicit CLI confirmation (`--yes` in automation).
318
+
319
+ ### Reusable iframe configuration (4.11)
320
+
321
+ `flowbase iframes list|get|create|update|delete` manages environment-specific iframe drafts. Create/update use `--json-file`; fields are `label`, `icon` (default `IconWorld`), `color` (default `#2563EB`), and a complete HTTPS `url` without credentials. Update and delete require `baseRevision` from `iframes get`; deletion also requires confirmation (`--yes` for noninteractive use).
322
+
323
+ ```bash
324
+ flowbase iframes create --json-file iframe.json
325
+ flowbase iframes get ifr_example
326
+ flowbase iframes update ifr_example --json-file iframe-update.json
327
+ flowbase apps add-iframes app_example ifr_example
328
+ flowbase apps set-iframe-group app_example ifr_example --group-id grp_example
329
+ flowbase apps set-iframe-group app_example ifr_example --ungrouped
330
+ flowbase apps reorder-members app_example --json '{"items":[{"type":"iframe","id":"ifr_example"}]}'
331
+ flowbase apps remove-iframe app_example ifr_example --yes
332
+ flowbase iframes delete ifr_example --json '{"baseRevision":12}' --yes
333
+ ```
334
+
335
+ Removal only unlinks one app. Deletion unlinks all apps atomically. Changes follow the existing commit/release/rollback workflow; they do not fetch, proxy or modify the external website. API keys use `metadata:read`/`metadata:write`; browser roles use `iframe.manage` for shared resources and `app.manage` for app associations.
336
+
337
+ ### Custom object views (4.12)
338
+
339
+ Read `flowbase docs catalogs custom-views` and `flowbase views list OBJECT` first. Save the following as `view.json`, replacing `baseRevision` with the current `draftRevision`:
340
+
341
+ ```json
342
+ {
343
+ "baseRevision": 4,
344
+ "name": "Custom overview",
345
+ "enabled": false,
346
+ "defaultFieldIds": [],
347
+ "displayMode": "custom",
348
+ "custom": {
349
+ "runtimeVersion": "cv1",
350
+ "source": "import React from 'react'; export default function View(){return <div>Hello</div>}"
351
+ }
352
+ }
353
+ ```
354
+
355
+ ```bash
356
+ flowbase views create obj_example --json-file view.json
357
+ flowbase views list obj_example
358
+ flowbase views update obj_example viw_example --json-file view.json
359
+ flowbase views set-enabled obj_example viw_example --base-revision 6 --enabled --yes
360
+ ```
361
+
362
+ Use the revision returned by the preceding write, rather than the example numbers. Custom views start disabled; preview in the browser and enable when ready. Updates replace the writable snapshot: preserve `custom.source`, `custom.runtimeVersion`, `displayMode`, filters and other configuration from `views list`, excluding the response-only `id` and `status`. Built-in/custom view types cannot be converted. JSX source is limited to 512 KiB UTF-8; the runtime supports `react`, `three` and `flowbase`. CLI configuration does not execute browser previews.
363
+
364
+ ### Record detail embedded pages (4.13)
365
+
366
+ `flowbase docs catalogs page-components` documents `recordEmbed`, supported at the top level or inside tabs of a detail Page. Add this component to an existing region column (or a tab's `components`):
367
+
368
+ ```json
369
+ {
370
+ "kind": "recordEmbed",
371
+ "label": "External panel",
372
+ "showTitle": true,
373
+ "url": "https://example.com/panel",
374
+ "height": 480,
375
+ "allowRead": false,
376
+ "allowedFieldIds": []
377
+ }
378
+ ```
379
+
380
+ Read `flowbase pages get PAGE`, preserve the existing components and IDs, and remove the read-only Page keys listed in `docs catalogs page-components`. Wrap the editable snapshot as `{"baseRevision":4,"page":{...}}`, then use `flowbase pages update PAGE --json-file page.json` (or `pages create --json-file page.json` for a new detail Page). For creation, omit the Page ID and IDs of new regions/components so the server allocates them.
381
+
382
+ Height must be an integer from 240 to 1600. URLs must use external HTTPS and exclude credentials, identity/context parameters and templates. To permit SDK reads, set `allowRead: true` and select at least one unique active field ID in `allowedFieldIds`; current record/field permissions still apply. The browser SDK is `/sdk/record-embed.v1.js`. The CLI configures the component; rendering and SDK calls run in the browser. `flowbase-user` has no metadata management commands.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flowbase-cli"
7
- version = "0.1.15"
7
+ version = "0.1.17"
8
8
  description = "Command line client for the FlowBase OpenAPI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1,3 +1,3 @@
1
1
  """FlowBase OpenAPI CLI."""
2
2
 
3
- __version__ = "0.1.15"
3
+ __version__ = "0.1.17"
@@ -22,6 +22,46 @@ class Endpoint:
22
22
 
23
23
 
24
24
  ENDPOINTS = (
25
+ Endpoint("apps.navigation", "flowbase_apps_navigation", "POST", "/apps/{app_id}/navigation", "metadata:write", "Edit app sections and navigation positions; read apps get first. See app-sections catalog.", "flowbase apps navigation app_xxx --json-file navigation.json --yes", True, request_body='{"type":"appSection.create","name":"Sales"}'),
26
+ Endpoint("dashboards.record-view-options", "flowbase_dashboards_record_view_options", "GET", "/dashboards/record-view-options", "metadata:read", "Discover draft objects and fields for record-view components.", "flowbase dashboards record-view-options"),
27
+ Endpoint("dashboards.record-view-preview", "flowbase_dashboards_record_view_preview", "POST", "/dashboards/{dashboard_id}/record-view-preview", "metadata:read", "Preview an unsaved record-view component; also requires records:read. No writes; current Host data and draft config.", "flowbase dashboards record-view-preview dsh_xxx --json-file preview.json", request_body='{"component":{"type":"record_view","title":"Orders","layout":{"x":0,"y":0,"w":6,"h":10},"recordView":{"objectId":"obj_xxx","match":"all","filters":[],"fieldIds":["fld_xxx"],"pageSize":10,"showSearch":true,"followFilters":true}},"filters":[],"filterValues":{},"page":1,"q":""}'),
28
+ Endpoint("dashboards.records", "flowbase_dashboards_records", "POST", "/dashboards/{dashboard_id}/components/{component_id}/records", "metadata:read", "Query a saved draft record-view component; also requires records:read. No writes; not employee workspace permissions.", "flowbase dashboards records dsh_xxx dcm_xxx --page 1 --json-file filters.json", request_body='{"filterValues":{},"page":1,"q":""}'),
29
+ Endpoint("iframes.list", "flowbase_iframes_list", "GET", "/iframes", "metadata:read", "List iframe configuration.", "flowbase iframes list", requires_confirmation=False, request_body=None),
30
+ Endpoint("iframes.get", "flowbase_iframes_get", "GET", "/iframes/{iframe_id}", "metadata:read", "Get iframe configuration.", "flowbase iframes get", requires_confirmation=False, request_body=None),
31
+ Endpoint("iframes.create", "flowbase_iframes_create", "POST", "/iframes", "metadata:write", "Create iframe configuration.", "flowbase iframes create", requires_confirmation=False, request_body='{"label": "Help", "url": "https://example.com", "icon": "IconWorld", "color": "#2563EB"}'),
32
+ Endpoint("iframes.update", "flowbase_iframes_update", "PUT", "/iframes/{iframe_id}", "metadata:write", "Update iframe configuration.", "flowbase iframes update", requires_confirmation=False, request_body='{"label": "Help", "url": "https://example.com", "icon": "IconWorld", "color": "#2563EB", "baseRevision": 0}'),
33
+ Endpoint("iframes.delete", "flowbase_iframes_delete", "POST", "/iframes/{iframe_id}/delete", "metadata:write", "Delete iframe configuration.", "flowbase iframes delete", requires_confirmation=True, request_body='{"baseRevision": 0}'),
34
+ Endpoint(
35
+ "apps.iframes.add",
36
+ "flowbase_apps_iframes_add",
37
+ "POST",
38
+ "/apps/{app_id}/iframes",
39
+ "metadata:write",
40
+ "Add iframes to an app (association only).",
41
+ "flowbase apps add-iframes app_xxx ifr_xxx",
42
+ request_body='{"iframeIds":["ifr_xxx"]}',
43
+ ),
44
+ Endpoint(
45
+ "apps.iframes.remove",
46
+ "flowbase_apps_iframes_remove",
47
+ "DELETE",
48
+ "/apps/{app_id}/iframes/{iframe_id}",
49
+ "metadata:write",
50
+ "Remove a iframe association from an app.",
51
+ "flowbase apps remove-iframe app_xxx ifr_xxx --yes",
52
+ True,
53
+ ),
54
+ Endpoint(
55
+ "apps.iframes.set-group",
56
+ "flowbase_apps_iframes_set_group",
57
+ "POST",
58
+ "/apps/{app_id}/iframes/{iframe_id}/group",
59
+ "metadata:write",
60
+ "Move an app-linked iframe into a group or ungroup it.",
61
+ "flowbase apps set-iframe-group app_xxx ifr_xxx --group-id grp_xxx",
62
+ request_body='{"groupId":"grp_xxx"}',
63
+ ),
64
+
25
65
  Endpoint('formulas.sources', 'flowbase_formulas_sources', 'GET', '/objects/{object_id}/formula/sources', 'metadata:read', 'Discover scalar formula sources in the draft.', 'flowbase formulas sources obj_xxx'),
26
66
  Endpoint('formulas.collections', 'flowbase_formulas_collections', 'GET', '/objects/{object_id}/formula/collections', 'metadata:read', 'Discover collection object and field IDs in the draft.', 'flowbase formulas collections obj_xxx'),
27
67
  Endpoint('formulas.validate', 'flowbase_formulas_validate', 'POST', '/objects/{object_id}/formula/validate', 'metadata:read', 'Validate formula or rollup; trial also supported. Requires records:read. No writes.', 'flowbase formulas validate obj_xxx --json-file formula.json', request_body='{"constraints":{"expression":"amount * 2","resultType":"number","scale":2},"samples":{"amount":12.50},"trial":true}'),
@@ -52,7 +92,7 @@ ENDPOINTS = (
52
92
  "POST",
53
93
  "/apps/{app_id}/groups",
54
94
  "metadata:write",
55
- "Create an app group draft.",
95
+ "Create an app group draft; optional sectionId selects a section (omitted means unassigned).",
56
96
  "flowbase groups create app_xxx --name Sales --icon IconFolder",
57
97
  request_body='{"name":"Sales","icon":"IconFolder"}',
58
98
  ),
@@ -258,7 +298,7 @@ ENDPOINTS = (
258
298
  '{"fieldId":"fld_done","operator":"is_true"}'
259
299
  '],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null,"displayMode":"table","parentFieldId":null,"gantt":{}}'
260
300
  )),
261
- Endpoint("views.update", "flowbase_views_update", "PUT", "/objects/{object_id}/views/{view_id}", "metadata:write", "Update a complete view snapshot. Preserve displayMode, parentFieldId and gantt from views.list; omission resets to table.", "flowbase views update obj_xxx viw_xxx --json-file view.json",
301
+ Endpoint("views.update", "flowbase_views_update", "PUT", "/objects/{object_id}/views/{view_id}", "metadata:write", "Update a complete view snapshot. Preserve displayMode, parentFieldId, gantt and custom from views.list; Read custom-views catalog for JSX configuration; omitted fields use defaults.", "flowbase views update obj_xxx viw_xxx --json-file view.json",
262
302
  request_body=(
263
303
  '{"baseRevision":4,"name":"Active","enabled":true,"match":"all",'
264
304
  '"filters":['
@@ -1040,7 +1080,7 @@ APPROVAL_NODE_TYPES = (
1040
1080
  )
1041
1081
 
1042
1082
  FUNCTION_PERMISSIONS = (
1043
- "workspace.access", "app.manage", "object.manage", "dashboard.manage", "form.manage",
1083
+ "workspace.access", "app.manage", "object.manage", "dashboard.manage", "form.manage", "iframe.manage",
1044
1084
  "version.manage", "org.manage", "system.manage",
1045
1085
  )
1046
1086
 
@@ -1088,7 +1128,7 @@ PAGE_COMPONENTS = {
1088
1128
  ),
1089
1129
  "detailOnlyKinds": (
1090
1130
  "relatedList", "approval", "detailSummary", "stageBar", "tabs",
1091
- "relatedSummary", "attachmentCard", "noticeBlock",
1131
+ "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
1092
1132
  ),
1093
1133
  },
1094
1134
  "kinds": (
@@ -1207,7 +1247,7 @@ PAGE_COMPONENTS = {
1207
1247
  "minimumComponents": 1,
1208
1248
  "allowedChildKinds": (
1209
1249
  "field", "relatedList", "approval", "stageBar",
1210
- "relatedSummary", "attachmentCard", "noticeBlock",
1250
+ "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
1211
1251
  ),
1212
1252
  },
1213
1253
  "note": "defaultTabId must reference a tab in this component; tabs cannot be nested.",
@@ -1248,6 +1288,22 @@ PAGE_COMPONENTS = {
1248
1288
  "titleLengthWhenShown": (1, 50),
1249
1289
  },
1250
1290
  },
1291
+ {
1292
+ "kind": "recordEmbed",
1293
+ "name": "record detail embedded page",
1294
+ "pageTypes": ("detail",),
1295
+ "placement": ("topLevel", "tabs"),
1296
+ "requiredKeys": ("kind", "label", "url"),
1297
+ "optionalKeys": ("id", "showTitle", "height", "allowRead", "allowedFieldIds"),
1298
+ "defaults": {"showTitle": True, "height": 480, "allowRead": False, "allowedFieldIds": []},
1299
+ "constraints": {
1300
+ "labelLength": (1, 80),
1301
+ "height": "integer, 240–1600",
1302
+ "url": "External HTTPS only; no credentials, identity/context parameters, templates, localhost or FlowBase hosts.",
1303
+ "allowedFieldIds": "Unique active field IDs on the Page object; at least one when allowRead=true. Runtime reads also enforce current record and field permissions.",
1304
+ },
1305
+ "runtime": "Browser SDK /sdk/record-embed.v1.js; read-only bridge. CLI authors configuration and does not render or execute the embed.",
1306
+ },
1251
1307
  {
1252
1308
  "kind": "noticeBlock",
1253
1309
  "name": "notice block",
@@ -1323,7 +1379,7 @@ PAGE_RULES = {
1323
1379
  "states": ("visible", "hidden"),
1324
1380
  "targetKinds": (
1325
1381
  "relatedList", "approval", "detailSummary", "stageBar", "tabs",
1326
- "relatedSummary", "attachmentCard", "noticeBlock",
1382
+ "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
1327
1383
  ),
1328
1384
  "excludedTargets": ("field", "region", "tab", "button", "systemArea"),
1329
1385
  "sameRuleUniqueTarget": True,
@@ -1337,7 +1393,7 @@ PAGE_RULES = {
1337
1393
  }
1338
1394
 
1339
1395
  RESOURCE_ID_PREFIXES = {
1340
- "app_": "app", "grp_": "app group", "obj_": "object", "fld_": "field",
1396
+ "app_": "app", "grp_": "app group", "sec_": "app section", "obj_": "object", "fld_": "field",
1341
1397
  "viw_": "view", "wfl_": "workflow", "apr_": "approval flow", "pag_": "custom page",
1342
1398
  "reg_": "page region", "cmp_": "page component",
1343
1399
  "dsh_": "dashboard", "dcm_": "dashboard component", "rol_": "role",
@@ -1542,6 +1598,37 @@ FORMS = {
1542
1598
  }
1543
1599
 
1544
1600
  CATALOGS = {
1601
+ "app-sections": {
1602
+ "read": "apps get APP returns sections and sectionId on groups/objects/dashboards/iframes. Null means unassigned; groups define their members' section.",
1603
+ "write": "apps navigation APP --json-file navigation.json --yes writes one atomic draft operation and returns id/draftRevision. Read current locations first; stale moves fail without changes.",
1604
+ "operations": [
1605
+ {"type": "appSection.create", "name": "Sales"},
1606
+ {"type": "appSection.update", "id": "sec_xxx", "name": "Revenue"},
1607
+ {"type": "appSection.delete", "id": "sec_xxx"},
1608
+ {"type": "appSection.reorder", "sectionIds": ["sec_second", "sec_first"]},
1609
+ {"type": "appGroup.moveToSection", "id": "grp_xxx", "sectionId": "sec_target", "expectedSectionId": None},
1610
+ {"type": "appMember.move", "id": "obj_xxx", "sectionId": "sec_target", "groupId": None, "expectedSectionId": None, "expectedGroupId": None},
1611
+ ],
1612
+ "rules": "Names are trimmed, 1-50 characters, case-insensitively unique; Unassigned/未分栏 are reserved. Only empty sections can be deleted. Reorder supplies all section IDs. Moves require explicit source/target fields, using null for unassigned/ungrouped; target group must belong to target section. Member id may be obj_/dsh_/ifr_.",
1613
+ "add": "groups create and apps add-objects/add-dashboards/add-iframes accept --section-id; omit for unassigned. Existing associations are not moved by add; use navigation.",
1614
+ "order": "Use groups reorder or apps reorder-members for order within a location; preserve the complete app-level order and other sections/groups from apps get.",
1615
+ },
1616
+ "dashboard-record-views": {
1617
+ "discover": "dashboards record-view-options returns draft source object and field IDs; dashboards get returns components and draftRevision.",
1618
+ "component": {"type": "record_view", "title": "Orders", "layout": {"x": 0, "y": 0, "w": 6, "h": 10}, "recordView": {"objectId": "obj_xxx", "match": "all", "filters": [], "fieldIds": ["fld_xxx"], "pageSize": 10, "showSearch": True, "followFilters": True}},
1619
+ "update": "dashboards update is a full snapshot: copy label/description/icon/filters/components from get, preserve all components and their IDs (including code/recordView), set baseRevision from draftRevision. Omit response-only id/key/componentCount/appIds/updatedAt/draftRevision. New components omit id; save allocates it. Omitting components or filters clears them.",
1620
+ "query": "dashboards records DASHBOARD COMPONENT [--query TEXT] [--page N] --json '{\"filterValues\":{}}' queries a saved draft component. dashboards record-view-preview DASHBOARD --json-file preview.json uses unsaved component and filters; docs example dashboards.record-view-preview supplies the body.",
1621
+ "permissions": "Queries require metadata:read and records:read and use current Host data with draft config (mode=draft-preview). API-key identity, not employee workspace access; hidden fields are excluded, hidden filter fields rejected. No writes or app association required.",
1622
+ "rules": "pageSize is 10/20/50; fieldIds are ordered stable field IDs. Fixed all/any conditions intersect dashboard filters and search. showSearch=false ignores q; followFilters=false ignores dashboard filters. Conditions use view-filters catalog. No object-view dependency. Code components use dashboards run.",
1623
+ "response": "columns/records/total/page/pageSize/objectId/title/showSearch/filterValues/conditions/match/mode/draftRevision; resolved absolute dates are returned in filterValues. Iterate until total is covered.",
1624
+ },
1625
+ "custom-views": {
1626
+ "commands": "views list/create/update/set-enabled/delete; create/update accept --json-file.",
1627
+ "example": {"baseRevision": 4, "name": "Custom", "enabled": False, "defaultFieldIds": [], "displayMode": "custom", "custom": {"runtimeVersion": "cv1", "source": "import React from 'react'; export default function View(){return <div>Hello</div>}"}},
1628
+ "update": "Read views.list; preserve custom.source, custom.runtimeVersion, displayMode and the other writable snapshot fields. Use draftRevision as baseRevision; omit response-only id and status from the body.",
1629
+ "constraints": "Custom views start disabled; enable separately after validation. Built-in and custom views cannot convert into each other. custom contains exactly source and runtimeVersion=cv1; source must be nonempty and at most 512 KiB UTF-8.",
1630
+ "runtime": "Sandboxed browser JSX, with react/three/flowbase imports. CLI saves configuration; browser preview/read bridge is not exposed as an API-key CLI command.",
1631
+ },
1545
1632
  "planning": {
1546
1633
  "viewUpdate": "Read views.list, preserve the complete snapshot, then views.update with baseRevision. Omitting displayMode/parentFieldId/gantt resets them to table/null/{}.",
1547
1634
  "tree": {"displayMode": "tree", "parentFieldId": "fld_parent", "gantt": {}},
@@ -6,6 +6,8 @@ GUIDE = {
6
6
  "workflow": [
7
7
  "先确认 --host / FLOWBASE_HOST 和 API Key;环境只由 Host 决定。",
8
8
  "flowbase apps list;flowbase objects list:发现已有应用和对象,优先复用,不猜测 ID。",
9
+ "应用分栏先 apps get 和 docs catalogs app-sections;apps navigation --json-file --yes 修改分栏/位置,添加内容和创建分组可指定 --section-id。",
10
+ "仪表盘记录视图先 dashboards record-view-options 和 docs catalogs dashboard-record-views;record-view-preview 预览未保存配置,records 查询已保存草稿,run 仅运行代码组件。",
9
11
  "flowbase objects get <object_id>:读取字段 key、类型、约束和真实选项 ID;关联字段继续读取目标对象。",
10
12
  "flowbase docs catalogs authoring;flowbase docs list;flowbase docs show <endpoint_key>:查请求和更新方式。",
11
13
  "配置页面前读取 pages options、page-components、page-rules、page-buttons;配置流程前读取 workflow-nodes、workflow-branches 或 approval-rules;配置打印模板前读取 print-templates;配置自定义表单前读取 forms。",
@@ -376,6 +376,9 @@ def _payload_from_args(args: argparse.Namespace, direct: dict[str, Any], require
376
376
 
377
377
  def handle_apps(args: argparse.Namespace) -> int:
378
378
  action = args.apps_action
379
+ if action == "navigation":
380
+ body = _payload_from_args(args, {}, ("type",))
381
+ return run_request(args, "apps.navigation", path_params={"app_id": args.app_id}, body=body)
379
382
  if action == "list":
380
383
  return run_request(args, "apps.list")
381
384
  if action == "get":
@@ -409,7 +412,7 @@ def handle_apps(args: argparse.Namespace) -> int:
409
412
  args,
410
413
  "apps.objects.add",
411
414
  path_params={"app_id": args.app_id},
412
- body={"objectIds": ids},
415
+ body={"objectIds": ids, **({"sectionId": args.section_id} if args.section_id is not None else {})},
413
416
  )
414
417
  if action == "remove-object":
415
418
  return run_request(
@@ -446,7 +449,7 @@ def handle_apps(args: argparse.Namespace) -> int:
446
449
  args,
447
450
  "apps.dashboards.add",
448
451
  path_params={"app_id": args.app_id},
449
- body={"dashboardIds": ids},
452
+ body={"dashboardIds": ids, **({"sectionId": args.section_id} if args.section_id is not None else {})},
450
453
  )
451
454
  if action == "remove-dashboard":
452
455
  return run_request(
@@ -467,6 +470,35 @@ def handle_apps(args: argparse.Namespace) -> int:
467
470
  path_params={"app_id": args.app_id, "dashboard_id": args.dashboard_id},
468
471
  body=body,
469
472
  )
473
+ if action == "add-iframes":
474
+ ids = [item.strip() for item in args.iframe_ids if item.strip()]
475
+ if not ids:
476
+ raise CliError("Provide at least one iframe ID.")
477
+ return run_request(
478
+ args,
479
+ "apps.iframes.add",
480
+ path_params={"app_id": args.app_id},
481
+ body={"iframeIds": ids, **({"sectionId": args.section_id} if args.section_id is not None else {})},
482
+ )
483
+ if action == "remove-iframe":
484
+ return run_request(
485
+ args,
486
+ "apps.iframes.remove",
487
+ path_params={"app_id": args.app_id, "iframe_id": args.iframe_id},
488
+ )
489
+ if action == "set-iframe-group":
490
+ if args.ungrouped:
491
+ body = {"groupId": None}
492
+ elif args.group_id is not None:
493
+ body = {"groupId": args.group_id}
494
+ else:
495
+ raise CliError("Provide --group-id or --ungrouped.")
496
+ return run_request(
497
+ args,
498
+ "apps.iframes.set-group",
499
+ path_params={"app_id": args.app_id, "iframe_id": args.iframe_id},
500
+ body=body,
501
+ )
470
502
  if action == "reorder-members":
471
503
  body = _payload_from_args(args, {}, ("items",))
472
504
  if not isinstance(body["items"], list):
@@ -482,6 +514,14 @@ def handle_apps(args: argparse.Namespace) -> int:
482
514
 
483
515
  def handle_dashboards(args: argparse.Namespace) -> int:
484
516
  action = args.dashboards_action
517
+ if action == "record-view-options":
518
+ return run_request(args, "dashboards.record-view-options")
519
+ if action in {"records", "record-view-preview"}:
520
+ body = _payload_from_args(args, {"q": args.query, "page": args.page}, ("component",) if action == "record-view-preview" else ())
521
+ path = {"dashboard_id": args.dashboard_id}
522
+ if action == "records":
523
+ path["component_id"] = args.component_id
524
+ return run_request(args, f"dashboards.{action}", path_params=path, body=body)
485
525
  if action == "run":
486
526
  return run_request(args, "dashboards.run", path_params={"dashboard_id": args.dashboard_id, "component_id": args.component_id}, body=load_json_payload(args.json, args.json_file))
487
527
  if action == "list":
@@ -522,6 +562,13 @@ def handle_dashboards(args: argparse.Namespace) -> int:
522
562
  )
523
563
 
524
564
 
565
+ def handle_iframes(args: argparse.Namespace) -> int:
566
+ action = args.iframes_action
567
+ params = {"iframe_id": args.iframe_id} if action in ("get", "update", "delete") else None
568
+ body = load_json_payload(args.json, args.json_file) if action in ("create", "update", "delete") else None
569
+ return run_request(args, f"iframes.{action}", path_params=params, body=body)
570
+
571
+
525
572
  def handle_forms(args: argparse.Namespace) -> int:
526
573
  action = args.forms_action
527
574
  if action == "list":
@@ -560,7 +607,7 @@ def handle_groups(args: argparse.Namespace) -> int:
560
607
  action = args.groups_action
561
608
  path = {"app_id": args.app_id}
562
609
  if action == "create":
563
- body = _payload_from_args(args, {"name": args.name, "icon": args.icon}, ("name",))
610
+ body = _payload_from_args(args, {"name": args.name, "icon": args.icon, "sectionId": args.section_id}, ("name",))
564
611
  return run_request(args, "apps.groups.create", path_params=path, body=body)
565
612
  if action == "reorder":
566
613
  body = _payload_from_args(args, {}, ("groupIds",))
@@ -1100,6 +1147,10 @@ def build_parser() -> argparse.ArgumentParser:
1100
1147
  app_actions.add_parser("list")
1101
1148
  app_get = app_actions.add_parser("get")
1102
1149
  app_get.add_argument("app_id")
1150
+ app_navigation = app_actions.add_parser("navigation", help="Edit sections or move content; see docs catalogs app-sections.")
1151
+ app_navigation.add_argument("app_id")
1152
+ add_json_args(app_navigation, endpoint_key="apps.navigation")
1153
+ add_yes(app_navigation)
1103
1154
  app_create = app_actions.add_parser("create")
1104
1155
  app_create.add_argument("--label")
1105
1156
  app_create.add_argument("--key")
@@ -1126,6 +1177,7 @@ def build_parser() -> argparse.ArgumentParser:
1126
1177
  app_add_objects = app_actions.add_parser("add-objects")
1127
1178
  app_add_objects.add_argument("app_id")
1128
1179
  app_add_objects.add_argument("--object-ids", required=True, help="Comma-separated object IDs.")
1180
+ app_add_objects.add_argument("--section-id", help="Target section; omitted means unassigned.")
1129
1181
  app_remove_object = app_actions.add_parser("remove-object")
1130
1182
  app_remove_object.add_argument("app_id")
1131
1183
  app_remove_object.add_argument("object_id")
@@ -1144,6 +1196,7 @@ def build_parser() -> argparse.ArgumentParser:
1144
1196
  add_json_args(app_reorder_objects, endpoint_key="apps.objects.reorder")
1145
1197
  app_add_dashboards = app_actions.add_parser("add-dashboards")
1146
1198
  app_add_dashboards.add_argument("app_id")
1199
+ app_add_dashboards.add_argument("--section-id", help="Target section; omitted means unassigned.")
1147
1200
  app_add_dashboards.add_argument(
1148
1201
  "dashboard_ids",
1149
1202
  nargs="+",
@@ -1162,6 +1215,27 @@ def build_parser() -> argparse.ArgumentParser:
1162
1215
  action="store_true",
1163
1216
  help="Remove the dashboard from its current group in this app.",
1164
1217
  )
1218
+ app_add_iframes = app_actions.add_parser("add-iframes")
1219
+ app_add_iframes.add_argument("app_id")
1220
+ app_add_iframes.add_argument("--section-id", help="Target section; omitted means unassigned.")
1221
+ app_add_iframes.add_argument(
1222
+ "iframe_ids",
1223
+ nargs="+",
1224
+ help="One or more iframe IDs to associate with the app.",
1225
+ )
1226
+ app_remove_iframe = app_actions.add_parser("remove-iframe")
1227
+ app_remove_iframe.add_argument("app_id")
1228
+ app_remove_iframe.add_argument("iframe_id")
1229
+ add_yes(app_remove_iframe)
1230
+ app_set_iframe_group = app_actions.add_parser("set-iframe-group")
1231
+ app_set_iframe_group.add_argument("app_id")
1232
+ app_set_iframe_group.add_argument("iframe_id")
1233
+ app_set_iframe_group.add_argument("--group-id", help="Target group ID. Omit with --ungrouped.")
1234
+ app_set_iframe_group.add_argument(
1235
+ "--ungrouped",
1236
+ action="store_true",
1237
+ help="Remove the iframe from its current group in this app.",
1238
+ )
1165
1239
  app_reorder_members = app_actions.add_parser("reorder-members")
1166
1240
  app_reorder_members.add_argument("app_id")
1167
1241
  add_json_args(app_reorder_members, endpoint_key="apps.members.reorder")
@@ -1169,6 +1243,15 @@ def build_parser() -> argparse.ArgumentParser:
1169
1243
 
1170
1244
  dashboards = groups.add_parser("dashboards", help="Manage dashboard metadata.")
1171
1245
  dashboard_actions = dashboards.add_subparsers(dest="dashboards_action", required=True)
1246
+ dashboard_actions.add_parser("record-view-options", help="Discover draft objects and fields for record-view components.")
1247
+ for action in ("records", "record-view-preview"):
1248
+ command = dashboard_actions.add_parser(action, help="Read-only draft record-view query; requires metadata:read and records:read.")
1249
+ command.add_argument("dashboard_id")
1250
+ if action == "records":
1251
+ command.add_argument("component_id")
1252
+ command.add_argument("--query", help="Component search text; ignored when showSearch is false.")
1253
+ command.add_argument("--page", type=int, help="Page number, starting at 1.")
1254
+ add_json_args(command, endpoint_key=f"dashboards.{action}")
1172
1255
  dashboard_run = dashboard_actions.add_parser("run", help="Run a saved draft component; requires metadata:read and records:read.")
1173
1256
  dashboard_run.add_argument("dashboard_id")
1174
1257
  dashboard_run.add_argument("component_id")
@@ -1192,6 +1275,18 @@ def build_parser() -> argparse.ArgumentParser:
1192
1275
  add_yes(dashboard_delete)
1193
1276
  dashboards.set_defaults(handler=handle_dashboards)
1194
1277
 
1278
+ iframes = groups.add_parser("iframes", help="Manage reusable iframe configurations.")
1279
+ iframe_actions = iframes.add_subparsers(dest="iframes_action", required=True)
1280
+ for action in ("list", "get", "create", "update", "delete"):
1281
+ command = iframe_actions.add_parser(action)
1282
+ if action in ("get", "update", "delete"):
1283
+ command.add_argument("iframe_id")
1284
+ if action in ("create", "update", "delete"):
1285
+ add_json_args(command, endpoint_key=f"iframes.{action}")
1286
+ if action == "delete":
1287
+ add_yes(command)
1288
+ iframes.set_defaults(handler=handle_iframes)
1289
+
1195
1290
  forms = groups.add_parser("forms", help="Manage custom forms (public collection links).")
1196
1291
  form_actions = forms.add_subparsers(dest="forms_action", required=True)
1197
1292
  form_actions.add_parser("list")
@@ -1229,6 +1324,7 @@ def build_parser() -> argparse.ArgumentParser:
1229
1324
  group_create.add_argument("app_id")
1230
1325
  group_create.add_argument("--name")
1231
1326
  group_create.add_argument("--icon")
1327
+ group_create.add_argument("--section-id", help="Target section; omitted means unassigned.")
1232
1328
  add_json_args(group_create, endpoint_key="apps.groups.create")
1233
1329
  group_reorder = group_actions.add_parser("reorder")
1234
1330
  group_reorder.add_argument("app_id")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbase-cli
3
- Version: 0.1.15
3
+ Version: 0.1.17
4
4
  Summary: Command line client for the FlowBase OpenAPI
5
5
  Author: DeepFlowAI
6
6
  License: Apache-2.0
@@ -76,7 +76,7 @@ flowbase
76
76
  ├── docs list | show | example | operations Offline endpoint catalog
77
77
  ├── guide Offline product and task guide (--format markdown|json)
78
78
  ├── organization employees | departments
79
- ├── apps list | get | create | update | set-enabled | delete
79
+ ├── apps list | get | create | update | set-enabled | delete | navigation
80
80
  │ add-objects | remove-object | set-group | reorder-objects
81
81
  │ add-dashboards | remove-dashboard | set-dashboard-group | reorder-members
82
82
  ├── groups create | reorder | update | delete App groups (under an app)
@@ -88,6 +88,7 @@ flowbase
88
88
  ├── approvals list | get | create | update | reorder | set-enabled | delete
89
89
  ├── pages list | options | get | create | update | set-default | clear-default | delete | restore | evaluate
90
90
  ├── dashboards list | get | create | update | copy | delete | run
91
+ │ record-view-options | record-view-preview | records
91
92
  ├── changes commit | release | direct-release
92
93
  ├── versions list | show | releases Read-only history
93
94
  ├── environments restart Destructive test-env reset
@@ -138,7 +139,8 @@ Most `update` commands replace a **full snapshot** and accept an optional `baseR
138
139
 
139
140
  ```bash
140
141
  flowbase dashboards get dsh_xxx > current.json # authoritative snapshot
141
- # edit current.json: change fields, keep filters/components, use draftRevision as baseRevision
142
+ # Keep only label/description/icon/filters/components; use draftRevision as baseRevision.
143
+ # Edit the desired fields while preserving all other components and filters.
142
144
  flowbase dashboards update dsh_xxx --json-file current.json
143
145
  ```
144
146
 
@@ -160,6 +162,14 @@ flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"polic
160
162
 
161
163
  `records create` returns `{recordId}`; use it with `records get`.
162
164
 
165
+ To add existing objects to an application, pass their IDs with `--object-ids`:
166
+
167
+ ```bash
168
+ flowbase apps add-objects app_xxx --object-ids obj_a,obj_b
169
+ ```
170
+
171
+ Use a comma-separated list for multiple objects. Object IDs are not positional arguments. Run `flowbase docs show apps.objects.add` for the endpoint details.
172
+
163
173
  Record `values` keys are **field keys** (`name`), not internal IDs (`fld_...`). IDs are `obj_`/`fld_`/`dsh_`/`wfl_`/`pag_`/`reg_`/`cmp_`-prefixed; see `docs catalogs id-prefixes`.
164
174
 
165
175
  ### 6.2 Edit and publish configuration on a test environment
@@ -178,6 +188,31 @@ flowbase changes release --message "Ship sales dashboard" --yes
178
188
 
179
189
  Dashboard component code runs server-side in a sandbox and returns ECharts config / metric / table / markdown. Components and filters are validated on save; use `flowbase dashboards get <id>` to discover component IDs, then `flowbase dashboards run <dashboard_id> <component_id>` to inspect `ok/output/error/logs` in a read-only draft preview (requires metadata:read and records:read). Visual chart rendering still requires the web UI.
180
190
 
191
+ App sections and record-view components:
192
+
193
+ ```bash
194
+ flowbase apps get app_xxx
195
+ flowbase docs catalogs app-sections
196
+ flowbase apps navigation app_xxx --json '{"type":"appSection.create","name":"Sales"}' --yes
197
+ flowbase groups create app_xxx --name Orders --section-id sec_xxx
198
+ flowbase apps add-objects app_xxx --object-ids obj_xxx --section-id sec_xxx
199
+ # add-dashboards and add-iframes also accept --section-id.
200
+ flowbase apps navigation app_xxx --json '{"type":"appMember.move","id":"obj_xxx","sectionId":"sec_xxx","groupId":"grp_xxx","expectedSectionId":"sec_xxx","expectedGroupId":null}' --yes
201
+
202
+ flowbase docs catalogs dashboard-record-views
203
+ flowbase dashboards record-view-options
204
+ flowbase docs example dashboards.record-view-preview --output preview.json
205
+ # Replace placeholder IDs and configure component/filters before previewing.
206
+ flowbase dashboards record-view-preview dsh_xxx --json-file preview.json
207
+ flowbase dashboards records dsh_xxx dcm_xxx --page 1 --query Orders --json '{"filterValues":{}}'
208
+ ```
209
+
210
+ `apps get` preserves section definitions and group/member `sectionId`. Navigation supports section creation, rename, empty-section deletion, full section ordering, group moves and member moves. Moves require explicit source and target positions; use JSON `null` for unassigned/ungrouped. See `app-sections` for all six request bodies. Navigation requires confirmation (`--yes` for automation), and writes only a draft.
211
+
212
+ Record-view previews and queries require both `metadata:read` and `records:read`, use the current Host's records and draft configuration, and return `mode: draft-preview` plus `draftRevision`. They use API-key visibility rather than an employee's workspace identity; hidden fields stay excluded and hidden filter fields are rejected. Page size is configured on the component (10/20/50). Search is ignored when `showSearch` is false. `dashboards run` is for code components.
213
+
214
+ For `dashboards update`, construct the body from the latest `get`: preserve `label`, `description`, `icon`, `filters`, and **all** `components`; set `baseRevision` to the returned `draftRevision`. Omit response-only fields. Keep existing component IDs and `recordView` unchanged when editing other properties; omit IDs on new components. Omitting `components` or `filters` clears them.
215
+
181
216
  ### 6.3 Production hotfix
182
217
 
183
218
  ```bash
@@ -289,3 +324,68 @@ Transport requests time out after 60 seconds. A lost write response has an unkno
289
324
  - `flowbase docs catalogs workflow-python`: record creation examples and failed-task retry. `workflows python-runtime OBJECT new` discovers current object bindings; `workflows executions` includes Python attempts, logs and `canRetry`.
290
325
 
291
326
  Use `records list OBJECT --view-id VIEW --view-layout` for tree/Gantt projections; the default remains a flat list. `records update` accepts `updatedAt` and `planViewId` in its JSON body for version-checked plan edits. Formula validation/trials require both metadata read and record read; workflow retry requires both metadata write and record write and explicit CLI confirmation (`--yes` in automation).
327
+
328
+ ### Reusable iframe configuration (4.11)
329
+
330
+ `flowbase iframes list|get|create|update|delete` manages environment-specific iframe drafts. Create/update use `--json-file`; fields are `label`, `icon` (default `IconWorld`), `color` (default `#2563EB`), and a complete HTTPS `url` without credentials. Update and delete require `baseRevision` from `iframes get`; deletion also requires confirmation (`--yes` for noninteractive use).
331
+
332
+ ```bash
333
+ flowbase iframes create --json-file iframe.json
334
+ flowbase iframes get ifr_example
335
+ flowbase iframes update ifr_example --json-file iframe-update.json
336
+ flowbase apps add-iframes app_example ifr_example
337
+ flowbase apps set-iframe-group app_example ifr_example --group-id grp_example
338
+ flowbase apps set-iframe-group app_example ifr_example --ungrouped
339
+ flowbase apps reorder-members app_example --json '{"items":[{"type":"iframe","id":"ifr_example"}]}'
340
+ flowbase apps remove-iframe app_example ifr_example --yes
341
+ flowbase iframes delete ifr_example --json '{"baseRevision":12}' --yes
342
+ ```
343
+
344
+ Removal only unlinks one app. Deletion unlinks all apps atomically. Changes follow the existing commit/release/rollback workflow; they do not fetch, proxy or modify the external website. API keys use `metadata:read`/`metadata:write`; browser roles use `iframe.manage` for shared resources and `app.manage` for app associations.
345
+
346
+ ### Custom object views (4.12)
347
+
348
+ Read `flowbase docs catalogs custom-views` and `flowbase views list OBJECT` first. Save the following as `view.json`, replacing `baseRevision` with the current `draftRevision`:
349
+
350
+ ```json
351
+ {
352
+ "baseRevision": 4,
353
+ "name": "Custom overview",
354
+ "enabled": false,
355
+ "defaultFieldIds": [],
356
+ "displayMode": "custom",
357
+ "custom": {
358
+ "runtimeVersion": "cv1",
359
+ "source": "import React from 'react'; export default function View(){return <div>Hello</div>}"
360
+ }
361
+ }
362
+ ```
363
+
364
+ ```bash
365
+ flowbase views create obj_example --json-file view.json
366
+ flowbase views list obj_example
367
+ flowbase views update obj_example viw_example --json-file view.json
368
+ flowbase views set-enabled obj_example viw_example --base-revision 6 --enabled --yes
369
+ ```
370
+
371
+ Use the revision returned by the preceding write, rather than the example numbers. Custom views start disabled; preview in the browser and enable when ready. Updates replace the writable snapshot: preserve `custom.source`, `custom.runtimeVersion`, `displayMode`, filters and other configuration from `views list`, excluding the response-only `id` and `status`. Built-in/custom view types cannot be converted. JSX source is limited to 512 KiB UTF-8; the runtime supports `react`, `three` and `flowbase`. CLI configuration does not execute browser previews.
372
+
373
+ ### Record detail embedded pages (4.13)
374
+
375
+ `flowbase docs catalogs page-components` documents `recordEmbed`, supported at the top level or inside tabs of a detail Page. Add this component to an existing region column (or a tab's `components`):
376
+
377
+ ```json
378
+ {
379
+ "kind": "recordEmbed",
380
+ "label": "External panel",
381
+ "showTitle": true,
382
+ "url": "https://example.com/panel",
383
+ "height": 480,
384
+ "allowRead": false,
385
+ "allowedFieldIds": []
386
+ }
387
+ ```
388
+
389
+ Read `flowbase pages get PAGE`, preserve the existing components and IDs, and remove the read-only Page keys listed in `docs catalogs page-components`. Wrap the editable snapshot as `{"baseRevision":4,"page":{...}}`, then use `flowbase pages update PAGE --json-file page.json` (or `pages create --json-file page.json` for a new detail Page). For creation, omit the Page ID and IDs of new regions/components so the server allocates them.
390
+
391
+ Height must be an integer from 240 to 1600. URLs must use external HTTPS and exclude credentials, identity/context parameters and templates. To permit SDK reads, set `allowRead: true` and select at least one unique active field ID in `allowedFieldIds`; current record/field permissions still apply. The browser SDK is `/sdk/record-embed.v1.js`. The CLI configures the component; rendering and SDK calls run in the browser. `flowbase-user` has no metadata management commands.
@@ -11,4 +11,5 @@ src/flowbase_cli.egg-info/entry_points.txt
11
11
  src/flowbase_cli.egg-info/top_level.txt
12
12
  tests/test_autonomy.py
13
13
  tests/test_cli.py
14
+ tests/test_iframes.py
14
15
  tests/test_recent_commands.py
@@ -261,7 +261,7 @@ class FlowBaseCliTest(unittest.TestCase):
261
261
  kinds = {item.get("kind") for item in page_components["kinds"]}
262
262
  self.assertEqual(kinds, {
263
263
  None, "relatedList", "approval", "detailSummary", "stageBar",
264
- "tabs", "relatedSummary", "attachmentCard", "noticeBlock",
264
+ "tabs", "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
265
265
  })
266
266
  approval = next(
267
267
  item for item in page_components["kinds"] if item.get("kind") == "approval"
@@ -0,0 +1,30 @@
1
+ """Iframe CLI uses the same API contracts as browser management."""
2
+ import io
3
+ from unittest.mock import patch
4
+ import pytest
5
+ from flowbase_cli.main import main
6
+
7
+
8
+ @pytest.mark.parametrize("args,endpoint,path,body", [
9
+ (["iframes", "list"], "iframes.list", None, None),
10
+ (["iframes", "get", "ifr_x"], "iframes.get", {"iframe_id": "ifr_x"}, None),
11
+ (["iframes", "create", "--json", '{"label":"Help","url":"https://example.com"}'], "iframes.create", None, {"label":"Help","url":"https://example.com"}),
12
+ (["iframes", "update", "ifr_x", "--json", '{"label":"Help","url":"https://example.com","baseRevision":3}'], "iframes.update", {"iframe_id":"ifr_x"}, {"label":"Help","url":"https://example.com","baseRevision":3}),
13
+ (["iframes", "delete", "ifr_x", "--yes", "--json", '{"baseRevision":4}'], "iframes.delete", {"iframe_id":"ifr_x"}, {"baseRevision":4}),
14
+ (["apps", "add-iframes", "app_x", "ifr_x", "ifr_y"], "apps.iframes.add", {"app_id":"app_x"}, {"iframeIds":["ifr_x","ifr_y"]}),
15
+ (["apps", "remove-iframe", "app_x", "ifr_x", "--yes"], "apps.iframes.remove", {"app_id":"app_x","iframe_id":"ifr_x"}, None),
16
+ (["apps", "set-iframe-group", "app_x", "ifr_x", "--ungrouped"], "apps.iframes.set-group", {"app_id":"app_x","iframe_id":"ifr_x"}, {"groupId":None}),
17
+ (["apps", "reorder-members", "app_x", "--json", '{"items":[{"type":"iframe","id":"ifr_x"}]}'], "apps.members.reorder", {"app_id":"app_x"}, {"items":[{"type":"iframe","id":"ifr_x"}]}),
18
+ ])
19
+ def test_iframe_cli_request(args, endpoint, path, body):
20
+ with patch("flowbase_cli.main.OpenApiClient.request", return_value=b"{}") as request, patch("sys.stdout", io.StringIO()):
21
+ assert main(args) == 0
22
+ assert request.call_args.args[0] == endpoint
23
+ assert request.call_args.kwargs["path_params"] == path
24
+ assert request.call_args.kwargs["body"] == body
25
+
26
+
27
+ def test_delete_requires_confirmation():
28
+ with patch("flowbase_cli.main.OpenApiClient.request") as request, patch("sys.stdin.isatty", return_value=False), patch("sys.stderr", io.StringIO()):
29
+ assert main(["iframes", "delete", "ifr_x", "--json", '{"baseRevision":0}']) == 1
30
+ request.assert_not_called()
File without changes