flowbase-cli 0.1.14__tar.gz → 0.1.16__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.
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/PKG-INFO +83 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/README.md +82 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/pyproject.toml +1 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli/__init__.py +1 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli/endpoints.py +115 -8
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli/main.py +125 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli.egg-info/PKG-INFO +83 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli.egg-info/SOURCES.txt +3 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/tests/test_cli.py +2 -2
- flowbase_cli-0.1.16/tests/test_iframes.py +30 -0
- flowbase_cli-0.1.16/tests/test_recent_commands.py +30 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/setup.cfg +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli/guide.py +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli.egg-info/dependency_links.txt +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli.egg-info/entry_points.txt +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/src/flowbase_cli.egg-info/top_level.txt +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.16}/tests/test_autonomy.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: Command line client for the FlowBase OpenAPI
|
|
5
5
|
Author: DeepFlowAI
|
|
6
6
|
License: Apache-2.0
|
|
@@ -160,6 +160,14 @@ flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"polic
|
|
|
160
160
|
|
|
161
161
|
`records create` returns `{recordId}`; use it with `records get`.
|
|
162
162
|
|
|
163
|
+
To add existing objects to an application, pass their IDs with `--object-ids`:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
flowbase apps add-objects app_xxx --object-ids obj_a,obj_b
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
163
171
|
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
172
|
|
|
165
173
|
### 6.2 Edit and publish configuration on a test environment
|
|
@@ -280,3 +288,77 @@ flowbase roles update rol_xxx --json-file role.json
|
|
|
280
288
|
- `docs catalogs page-buttons`, `workflow-branches`, `approval-rules`, and `dashboard-runtime` describe complex configuration and runtime limits.
|
|
281
289
|
|
|
282
290
|
Transport requests time out after 60 seconds. A lost write response has an unknown outcome: read the resource and current revision before retrying. The CLI never automatically retries writes.
|
|
291
|
+
|
|
292
|
+
### Recent feature authoring
|
|
293
|
+
|
|
294
|
+
- `flowbase docs catalogs planning`: tree/Gantt view snapshots, candidate queries and optimistic plan updates. Read a full view with `views list` before updating; preserve `displayMode`, `parentFieldId` and `gantt`.
|
|
295
|
+
- `flowbase docs catalogs record-bundles`: initialize, atomically submit and recover a parent/detail submission. Reuse a submission ID only with the identical request body.
|
|
296
|
+
- `flowbase docs catalogs formula-authoring`: formula/rollup sources, validation, saved-record trials and runtime draft preview. Derived values are read-only.
|
|
297
|
+
- `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`.
|
|
298
|
+
|
|
299
|
+
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).
|
|
300
|
+
|
|
301
|
+
### Reusable iframe configuration (4.11)
|
|
302
|
+
|
|
303
|
+
`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).
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
flowbase iframes create --json-file iframe.json
|
|
307
|
+
flowbase iframes get ifr_example
|
|
308
|
+
flowbase iframes update ifr_example --json-file iframe-update.json
|
|
309
|
+
flowbase apps add-iframes app_example ifr_example
|
|
310
|
+
flowbase apps set-iframe-group app_example ifr_example --group-id grp_example
|
|
311
|
+
flowbase apps set-iframe-group app_example ifr_example --ungrouped
|
|
312
|
+
flowbase apps reorder-members app_example --json '{"items":[{"type":"iframe","id":"ifr_example"}]}'
|
|
313
|
+
flowbase apps remove-iframe app_example ifr_example --yes
|
|
314
|
+
flowbase iframes delete ifr_example --json '{"baseRevision":12}' --yes
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
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.
|
|
318
|
+
|
|
319
|
+
### Custom object views (4.12)
|
|
320
|
+
|
|
321
|
+
Read `flowbase docs catalogs custom-views` and `flowbase views list OBJECT` first. Save the following as `view.json`, replacing `baseRevision` with the current `draftRevision`:
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"baseRevision": 4,
|
|
326
|
+
"name": "Custom overview",
|
|
327
|
+
"enabled": false,
|
|
328
|
+
"defaultFieldIds": [],
|
|
329
|
+
"displayMode": "custom",
|
|
330
|
+
"custom": {
|
|
331
|
+
"runtimeVersion": "cv1",
|
|
332
|
+
"source": "import React from 'react'; export default function View(){return <div>Hello</div>}"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
flowbase views create obj_example --json-file view.json
|
|
339
|
+
flowbase views list obj_example
|
|
340
|
+
flowbase views update obj_example viw_example --json-file view.json
|
|
341
|
+
flowbase views set-enabled obj_example viw_example --base-revision 6 --enabled --yes
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
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.
|
|
345
|
+
|
|
346
|
+
### Record detail embedded pages (4.13)
|
|
347
|
+
|
|
348
|
+
`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`):
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"kind": "recordEmbed",
|
|
353
|
+
"label": "External panel",
|
|
354
|
+
"showTitle": true,
|
|
355
|
+
"url": "https://example.com/panel",
|
|
356
|
+
"height": 480,
|
|
357
|
+
"allowRead": false,
|
|
358
|
+
"allowedFieldIds": []
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
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.
|
|
363
|
+
|
|
364
|
+
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.
|
|
@@ -151,6 +151,14 @@ flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"polic
|
|
|
151
151
|
|
|
152
152
|
`records create` returns `{recordId}`; use it with `records get`.
|
|
153
153
|
|
|
154
|
+
To add existing objects to an application, pass their IDs with `--object-ids`:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
flowbase apps add-objects app_xxx --object-ids obj_a,obj_b
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
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.
|
|
161
|
+
|
|
154
162
|
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
163
|
|
|
156
164
|
### 6.2 Edit and publish configuration on a test environment
|
|
@@ -271,3 +279,77 @@ flowbase roles update rol_xxx --json-file role.json
|
|
|
271
279
|
- `docs catalogs page-buttons`, `workflow-branches`, `approval-rules`, and `dashboard-runtime` describe complex configuration and runtime limits.
|
|
272
280
|
|
|
273
281
|
Transport requests time out after 60 seconds. A lost write response has an unknown outcome: read the resource and current revision before retrying. The CLI never automatically retries writes.
|
|
282
|
+
|
|
283
|
+
### Recent feature authoring
|
|
284
|
+
|
|
285
|
+
- `flowbase docs catalogs planning`: tree/Gantt view snapshots, candidate queries and optimistic plan updates. Read a full view with `views list` before updating; preserve `displayMode`, `parentFieldId` and `gantt`.
|
|
286
|
+
- `flowbase docs catalogs record-bundles`: initialize, atomically submit and recover a parent/detail submission. Reuse a submission ID only with the identical request body.
|
|
287
|
+
- `flowbase docs catalogs formula-authoring`: formula/rollup sources, validation, saved-record trials and runtime draft preview. Derived values are read-only.
|
|
288
|
+
- `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`.
|
|
289
|
+
|
|
290
|
+
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).
|
|
291
|
+
|
|
292
|
+
### Reusable iframe configuration (4.11)
|
|
293
|
+
|
|
294
|
+
`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).
|
|
295
|
+
|
|
296
|
+
```bash
|
|
297
|
+
flowbase iframes create --json-file iframe.json
|
|
298
|
+
flowbase iframes get ifr_example
|
|
299
|
+
flowbase iframes update ifr_example --json-file iframe-update.json
|
|
300
|
+
flowbase apps add-iframes app_example ifr_example
|
|
301
|
+
flowbase apps set-iframe-group app_example ifr_example --group-id grp_example
|
|
302
|
+
flowbase apps set-iframe-group app_example ifr_example --ungrouped
|
|
303
|
+
flowbase apps reorder-members app_example --json '{"items":[{"type":"iframe","id":"ifr_example"}]}'
|
|
304
|
+
flowbase apps remove-iframe app_example ifr_example --yes
|
|
305
|
+
flowbase iframes delete ifr_example --json '{"baseRevision":12}' --yes
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
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.
|
|
309
|
+
|
|
310
|
+
### Custom object views (4.12)
|
|
311
|
+
|
|
312
|
+
Read `flowbase docs catalogs custom-views` and `flowbase views list OBJECT` first. Save the following as `view.json`, replacing `baseRevision` with the current `draftRevision`:
|
|
313
|
+
|
|
314
|
+
```json
|
|
315
|
+
{
|
|
316
|
+
"baseRevision": 4,
|
|
317
|
+
"name": "Custom overview",
|
|
318
|
+
"enabled": false,
|
|
319
|
+
"defaultFieldIds": [],
|
|
320
|
+
"displayMode": "custom",
|
|
321
|
+
"custom": {
|
|
322
|
+
"runtimeVersion": "cv1",
|
|
323
|
+
"source": "import React from 'react'; export default function View(){return <div>Hello</div>}"
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
flowbase views create obj_example --json-file view.json
|
|
330
|
+
flowbase views list obj_example
|
|
331
|
+
flowbase views update obj_example viw_example --json-file view.json
|
|
332
|
+
flowbase views set-enabled obj_example viw_example --base-revision 6 --enabled --yes
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
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.
|
|
336
|
+
|
|
337
|
+
### Record detail embedded pages (4.13)
|
|
338
|
+
|
|
339
|
+
`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`):
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"kind": "recordEmbed",
|
|
344
|
+
"label": "External panel",
|
|
345
|
+
"showTitle": true,
|
|
346
|
+
"url": "https://example.com/panel",
|
|
347
|
+
"height": 480,
|
|
348
|
+
"allowRead": false,
|
|
349
|
+
"allowedFieldIds": []
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
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.
|
|
354
|
+
|
|
355
|
+
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.
|
|
@@ -22,6 +22,53 @@ class Endpoint:
|
|
|
22
22
|
|
|
23
23
|
|
|
24
24
|
ENDPOINTS = (
|
|
25
|
+
Endpoint("iframes.list", "flowbase_iframes_list", "GET", "/iframes", "metadata:read", "List iframe configuration.", "flowbase iframes list", requires_confirmation=False, request_body=None),
|
|
26
|
+
Endpoint("iframes.get", "flowbase_iframes_get", "GET", "/iframes/{iframe_id}", "metadata:read", "Get iframe configuration.", "flowbase iframes get", requires_confirmation=False, request_body=None),
|
|
27
|
+
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"}'),
|
|
28
|
+
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}'),
|
|
29
|
+
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}'),
|
|
30
|
+
Endpoint(
|
|
31
|
+
"apps.iframes.add",
|
|
32
|
+
"flowbase_apps_iframes_add",
|
|
33
|
+
"POST",
|
|
34
|
+
"/apps/{app_id}/iframes",
|
|
35
|
+
"metadata:write",
|
|
36
|
+
"Add iframes to an app (association only).",
|
|
37
|
+
"flowbase apps add-iframes app_xxx ifr_xxx",
|
|
38
|
+
request_body='{"iframeIds":["ifr_xxx"]}',
|
|
39
|
+
),
|
|
40
|
+
Endpoint(
|
|
41
|
+
"apps.iframes.remove",
|
|
42
|
+
"flowbase_apps_iframes_remove",
|
|
43
|
+
"DELETE",
|
|
44
|
+
"/apps/{app_id}/iframes/{iframe_id}",
|
|
45
|
+
"metadata:write",
|
|
46
|
+
"Remove a iframe association from an app.",
|
|
47
|
+
"flowbase apps remove-iframe app_xxx ifr_xxx --yes",
|
|
48
|
+
True,
|
|
49
|
+
),
|
|
50
|
+
Endpoint(
|
|
51
|
+
"apps.iframes.set-group",
|
|
52
|
+
"flowbase_apps_iframes_set_group",
|
|
53
|
+
"POST",
|
|
54
|
+
"/apps/{app_id}/iframes/{iframe_id}/group",
|
|
55
|
+
"metadata:write",
|
|
56
|
+
"Move an app-linked iframe into a group or ungroup it.",
|
|
57
|
+
"flowbase apps set-iframe-group app_xxx ifr_xxx --group-id grp_xxx",
|
|
58
|
+
request_body='{"groupId":"grp_xxx"}',
|
|
59
|
+
),
|
|
60
|
+
|
|
61
|
+
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'),
|
|
62
|
+
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'),
|
|
63
|
+
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}'),
|
|
64
|
+
Endpoint('records.formula-preview', 'flowbase_records_formula_preview', 'POST', '/objects/{object_id}/formula-preview', 'records:read', 'Preview runtime formulas without saving. New rollups remain pending; saved rollups use saved data.', 'flowbase records formula-preview obj_xxx --json-file preview.json', request_body='{"values":{"amount":12.50}}'),
|
|
65
|
+
Endpoint('records.bundle-init', 'flowbase_records_bundle_init', 'GET', '/objects/{object_id}/bundle/init', 'records:read', 'Load configuration and collection versions before an atomic write.', 'flowbase records bundle-init obj_xxx --record-id rec_xxx'),
|
|
66
|
+
Endpoint('records.bundle-save', 'flowbase_records_bundle_save', 'POST', '/objects/{object_id}/bundle', 'records:write', 'Atomically save parent and configured related rows. Reuse submissionId only for identical retries.', 'flowbase records bundle-save obj_xxx --json-file bundle.json', request_body='{"submissionId":"unique-submission-0001","configVersion":"from-bundle-init","values":{"name":"Order"},"components":[]}'),
|
|
67
|
+
Endpoint('records.bundle-status', 'flowbase_records_bundle_status', 'GET', '/objects/{object_id}/bundle/submissions/{submission_id}', 'records:read', "Query this API key's submission receipt after a timeout; unknown does not prove failure.", 'flowbase records bundle-status obj_xxx unique-submission-0001'),
|
|
68
|
+
Endpoint('records.hierarchy-candidates', 'flowbase_records_hierarchy_candidates', 'GET', '/objects/{object_id}/hierarchy/candidates', 'records:read', 'Discover eligible parent records, excluding self and descendants.', 'flowbase records hierarchy-candidates obj_xxx --field fld_parent --exclude rec_xxx'),
|
|
69
|
+
Endpoint('records.gantt-candidates', 'flowbase_records_gantt_candidates', 'GET', '/objects/{object_id}/gantt/candidates', 'records:read', 'Discover eligible predecessors, excluding cycles and hierarchy conflicts.', 'flowbase records gantt-candidates obj_xxx --field fld_predecessors --exclude rec_xxx'),
|
|
70
|
+
Endpoint('workflows.python-runtime', 'flowbase_workflows_python_runtime', 'GET', '/objects/{object_id}/workflows/{workflow_id}/python-runtime', 'metadata:read', 'Discover Python APIs and creation methods. Use workflow ID new before creation.', 'flowbase workflows python-runtime obj_xxx new'),
|
|
71
|
+
Endpoint('workflows.retry', 'flowbase_workflows_retry', 'POST', '/objects/{object_id}/workflows/{workflow_id}/executions/{execution_id}/retry', 'metadata:write', 'Retry a failed Python execution; also requires records:write.', 'flowbase workflows retry obj_xxx wfl_xxx exe_xxx --yes', requires_confirmation=True),
|
|
25
72
|
Endpoint("pages.evaluate", "flowbase_pages_evaluate", "POST", "/pages/{page_id}/evaluate", "metadata:read", "Evaluate draft page rule effects on sample field-key values; no writes or visual rendering.", "flowbase pages evaluate pag_xxx --json-file values.json", request_body='{"values":{"done":true}}'),
|
|
26
73
|
Endpoint("dashboards.run", "flowbase_dashboards_run", "POST", "/dashboards/{dashboard_id}/components/{component_id}/run", "metadata:read", "Run a saved draft component; also requires records:read. Inspect ok/output/error, not visual rendering.", "flowbase dashboards run dsh_xxx dcm_xxx --json-file filters.json", request_body='{"filterValues":{}}'),
|
|
27
74
|
Endpoint("organization.employees", "flowbase_organization_employees", "GET", "/organization/employees", "metadata:read", "Paginated employee IDs/names; follow pages with --page, optionally --query.", "flowbase organization employees --page 1"),
|
|
@@ -245,15 +292,15 @@ ENDPOINTS = (
|
|
|
245
292
|
'"filters":['
|
|
246
293
|
'{"fieldId":"fld_status","operator":"eq","value":"opt_xxx"},'
|
|
247
294
|
'{"fieldId":"fld_done","operator":"is_true"}'
|
|
248
|
-
'],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null}'
|
|
295
|
+
'],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null,"displayMode":"table","parentFieldId":null,"gantt":{}}'
|
|
249
296
|
)),
|
|
250
|
-
Endpoint("views.update", "flowbase_views_update", "PUT", "/objects/{object_id}/views/{view_id}", "metadata:write", "Update
|
|
297
|
+
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",
|
|
251
298
|
request_body=(
|
|
252
299
|
'{"baseRevision":4,"name":"Active","enabled":true,"match":"all",'
|
|
253
300
|
'"filters":['
|
|
254
301
|
'{"fieldId":"fld_status","operator":"eq","value":"opt_xxx"},'
|
|
255
302
|
'{"fieldId":"fld_done","operator":"is_true"}'
|
|
256
|
-
'],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null}'
|
|
303
|
+
'],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null,"displayMode":"table","parentFieldId":null,"gantt":{}}'
|
|
257
304
|
)),
|
|
258
305
|
Endpoint("views.reorder", "flowbase_views_reorder", "PUT", "/objects/{object_id}/views/order", "metadata:write", "Reorder object views.", "flowbase views reorder obj_xxx --json '{\"baseRevision\":4,\"viewIds\":[\"viw_xxx\"]}'",
|
|
259
306
|
request_body='{"baseRevision":4,"viewIds":["viw_a","viw_b"]}'),
|
|
@@ -625,6 +672,17 @@ def _field_type(
|
|
|
625
672
|
|
|
626
673
|
|
|
627
674
|
FIELD_TYPES = (
|
|
675
|
+
_field_type(
|
|
676
|
+
"rollup", ("source", "operation", "resultType", "scale", "currency", "unit"),
|
|
677
|
+
writable=False, record_value="derived scalar | null; omit on writes; see formulaStates",
|
|
678
|
+
extra={"permissions": ["readonly", "hidden"], "operations": ["sum", "avg", "count", "count_values", "min", "max"], "sourceModes": ["reverse", "forward", "children"]},
|
|
679
|
+
),
|
|
680
|
+
_field_type(
|
|
681
|
+
"formula", ("expression", "resultType", "scale", "currency", "bindings", "languageVersion", "collections"),
|
|
682
|
+
writable=False, record_value="derived scalar | null; omit on writes; see formulaStates for failures",
|
|
683
|
+
extra={"permissions": ["readonly", "hidden"], "resultTypes": ["number", "number.currency", "number.percent", "text.singleLine", "date", "boolean"],
|
|
684
|
+
"createExample": {"label": "Total", "key": "total", "description": "Calculated total", "type": "formula", "typeVersion": "v1", "permission": "readonly", "constraints": {"expression": "quantity * unit_price", "resultType": "number", "scale": 2}}},
|
|
685
|
+
),
|
|
628
686
|
_field_type(
|
|
629
687
|
"text.singleLine", ("maxLength", "minLength", "placeholder", "unique"),
|
|
630
688
|
filter_operators=_TEXT_FILTERS, record_value="string | null",
|
|
@@ -1018,7 +1076,7 @@ APPROVAL_NODE_TYPES = (
|
|
|
1018
1076
|
)
|
|
1019
1077
|
|
|
1020
1078
|
FUNCTION_PERMISSIONS = (
|
|
1021
|
-
"workspace.access", "app.manage", "object.manage", "dashboard.manage", "form.manage",
|
|
1079
|
+
"workspace.access", "app.manage", "object.manage", "dashboard.manage", "form.manage", "iframe.manage",
|
|
1022
1080
|
"version.manage", "org.manage", "system.manage",
|
|
1023
1081
|
)
|
|
1024
1082
|
|
|
@@ -1066,7 +1124,7 @@ PAGE_COMPONENTS = {
|
|
|
1066
1124
|
),
|
|
1067
1125
|
"detailOnlyKinds": (
|
|
1068
1126
|
"relatedList", "approval", "detailSummary", "stageBar", "tabs",
|
|
1069
|
-
"relatedSummary", "attachmentCard", "noticeBlock",
|
|
1127
|
+
"relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
|
|
1070
1128
|
),
|
|
1071
1129
|
},
|
|
1072
1130
|
"kinds": (
|
|
@@ -1099,7 +1157,8 @@ PAGE_COMPONENTS = {
|
|
|
1099
1157
|
"kind", "label", "hideLabel", "objectId", "relationFieldId",
|
|
1100
1158
|
"columnFieldIds", "sort", "limit", "showViewAll", "createAction",
|
|
1101
1159
|
),
|
|
1102
|
-
"optionalKeys": ("id", "filters", "carryIns"),
|
|
1160
|
+
"optionalKeys": ("id", "filters", "carryIns", "editMode", "editOptions", "editColumns"),
|
|
1161
|
+
"editMode": {"withParent": "Atomic editing on create/detail pages; initialize with records bundle-init, then bundle-save."},
|
|
1103
1162
|
"limit": (5, 10, 20),
|
|
1104
1163
|
"sortDirections": ("asc", "desc"),
|
|
1105
1164
|
"carryInSources": (
|
|
@@ -1184,7 +1243,7 @@ PAGE_COMPONENTS = {
|
|
|
1184
1243
|
"minimumComponents": 1,
|
|
1185
1244
|
"allowedChildKinds": (
|
|
1186
1245
|
"field", "relatedList", "approval", "stageBar",
|
|
1187
|
-
"relatedSummary", "attachmentCard", "noticeBlock",
|
|
1246
|
+
"relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
|
|
1188
1247
|
),
|
|
1189
1248
|
},
|
|
1190
1249
|
"note": "defaultTabId must reference a tab in this component; tabs cannot be nested.",
|
|
@@ -1225,6 +1284,22 @@ PAGE_COMPONENTS = {
|
|
|
1225
1284
|
"titleLengthWhenShown": (1, 50),
|
|
1226
1285
|
},
|
|
1227
1286
|
},
|
|
1287
|
+
{
|
|
1288
|
+
"kind": "recordEmbed",
|
|
1289
|
+
"name": "record detail embedded page",
|
|
1290
|
+
"pageTypes": ("detail",),
|
|
1291
|
+
"placement": ("topLevel", "tabs"),
|
|
1292
|
+
"requiredKeys": ("kind", "label", "url"),
|
|
1293
|
+
"optionalKeys": ("id", "showTitle", "height", "allowRead", "allowedFieldIds"),
|
|
1294
|
+
"defaults": {"showTitle": True, "height": 480, "allowRead": False, "allowedFieldIds": []},
|
|
1295
|
+
"constraints": {
|
|
1296
|
+
"labelLength": (1, 80),
|
|
1297
|
+
"height": "integer, 240–1600",
|
|
1298
|
+
"url": "External HTTPS only; no credentials, identity/context parameters, templates, localhost or FlowBase hosts.",
|
|
1299
|
+
"allowedFieldIds": "Unique active field IDs on the Page object; at least one when allowRead=true. Runtime reads also enforce current record and field permissions.",
|
|
1300
|
+
},
|
|
1301
|
+
"runtime": "Browser SDK /sdk/record-embed.v1.js; read-only bridge. CLI authors configuration and does not render or execute the embed.",
|
|
1302
|
+
},
|
|
1228
1303
|
{
|
|
1229
1304
|
"kind": "noticeBlock",
|
|
1230
1305
|
"name": "notice block",
|
|
@@ -1300,7 +1375,7 @@ PAGE_RULES = {
|
|
|
1300
1375
|
"states": ("visible", "hidden"),
|
|
1301
1376
|
"targetKinds": (
|
|
1302
1377
|
"relatedList", "approval", "detailSummary", "stageBar", "tabs",
|
|
1303
|
-
"relatedSummary", "attachmentCard", "noticeBlock",
|
|
1378
|
+
"relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
|
|
1304
1379
|
),
|
|
1305
1380
|
"excludedTargets": ("field", "region", "tab", "button", "systemArea"),
|
|
1306
1381
|
"sameRuleUniqueTarget": True,
|
|
@@ -1519,6 +1594,38 @@ FORMS = {
|
|
|
1519
1594
|
}
|
|
1520
1595
|
|
|
1521
1596
|
CATALOGS = {
|
|
1597
|
+
"custom-views": {
|
|
1598
|
+
"commands": "views list/create/update/set-enabled/delete; create/update accept --json-file.",
|
|
1599
|
+
"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>}"}},
|
|
1600
|
+
"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.",
|
|
1601
|
+
"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.",
|
|
1602
|
+
"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.",
|
|
1603
|
+
},
|
|
1604
|
+
"planning": {
|
|
1605
|
+
"viewUpdate": "Read views.list, preserve the complete snapshot, then views.update with baseRevision. Omitting displayMode/parentFieldId/gantt resets them to table/null/{}.",
|
|
1606
|
+
"tree": {"displayMode": "tree", "parentFieldId": "fld_parent", "gantt": {}},
|
|
1607
|
+
"gantt": {"displayMode": "gantt", "parentFieldId": None, "gantt": {"startFieldId": "fld_start", "endFieldId": "fld_end", "predecessorFieldId": "fld_dependencies"}},
|
|
1608
|
+
"read": "records list OBJECT --view-id VIEW --view-layout; use --parent, --root, --locate and --hierarchy-version for tree navigation. Default is flat.",
|
|
1609
|
+
"write": {"values": {"start": "2026-10-24", "end": "2026-10-25"}, "planViewId": "viw_plan", "updatedAt": "from-records-get"},
|
|
1610
|
+
"candidates": "records hierarchy-candidates / gantt-candidates use field IDs. Record values use field keys.",
|
|
1611
|
+
},
|
|
1612
|
+
"record-bundles": {
|
|
1613
|
+
"steps": ["Configure relatedList editMode=withParent on the default create/detail page.", "records bundle-init OBJECT [--record-id RECORD] returns configVersion, updatedAt and component collection versions.", "records bundle-save OBJECT --json-file bundle.json commits the entire write set or rolls back.", "After a timeout, bundle-status OBJECT SUBMISSION; retry only the identical body with the same submissionId."],
|
|
1614
|
+
"example": {"submissionId": "unique-submission-0001", "configVersion": "from-init", "recordId": "rec_parent", "updatedAt": "from-init", "values": {}, "components": [{"componentId": "cmp_lines", "version": 1, "rows": [{"clientId": "line-1", "operation": "create", "values": {"quantity": 2}}]}]},
|
|
1615
|
+
"rules": "Copy every visible component and its version. Update/delete rows also require recordId and updatedAt. Relation is injected by the server; omit it from row values. API-key receipts are isolated per key. Conflict: reload, reconcile and use a new submissionId. Max 200 resulting detail rows.",
|
|
1616
|
+
},
|
|
1617
|
+
"formula-authoring": {
|
|
1618
|
+
"steps": ["formulas sources OBJECT discovers scalar paths; formulas collections OBJECT discovers stable object/field IDs.", "formulas validate OBJECT --json-file formula.json validates constraints; trial=true uses samples or a saved recordId.", "fields create/update saves metadata. records formula-preview previews runtime values without saving."],
|
|
1619
|
+
"rollupExample": {"fieldType": "rollup", "constraints": {"source": {"mode": "reverse", "objectId": "obj_lines", "relationFieldId": "fld_order", "fieldId": "fld_amount"}, "operation": "sum", "resultType": "number", "scale": 2}, "trial": True, "recordId": "rec_order"},
|
|
1620
|
+
"collectionExample": {"expression": "sum(lines)", "resultType": "number", "scale": 2, "languageVersion": 2, "collections": {"lines": {"mode": "reverse", "objectId": "obj_lines", "relationFieldId": "fld_order", "fieldId": "fld_amount"}}},
|
|
1621
|
+
"readOnly": "formula/rollup values cannot be written, filtered, sorted or SQL-aggregated through the employee CLI. Read results and formulaStates instead.",
|
|
1622
|
+
},
|
|
1623
|
+
"workflow-python": {
|
|
1624
|
+
"discover": "workflows python-runtime OBJECT new (or WORKFLOW) returns creationObjects, relation bindings and limits.",
|
|
1625
|
+
"example": 'import flowbase\n\ndef run():\n first = flowbase.records.create(object="task", values={"title": "Review"})\n others = flowbase.records.create_many(object="task", records=[{"title": "Prepare"}])\n flowbase.records.update(first, values={"title": "Review order"})\n',
|
|
1626
|
+
"atomicity": "Creates and updates commit together after successful execution. Use object/field keys, writable ordinary fields, and returned handles. At most 100 created+updated records.",
|
|
1627
|
+
"retry": "workflows executions OBJECT WORKFLOW lists results; workflows retry OBJECT WORKFLOW EXECUTION --yes retries eligible failed tasks using the original task contract.",
|
|
1628
|
+
},
|
|
1522
1629
|
"workflow-branches": WORKFLOW_BRANCHES,
|
|
1523
1630
|
"approval-rules": APPROVAL_RULES,
|
|
1524
1631
|
"page-buttons": PAGE_BUTTONS,
|
|
@@ -467,6 +467,35 @@ def handle_apps(args: argparse.Namespace) -> int:
|
|
|
467
467
|
path_params={"app_id": args.app_id, "dashboard_id": args.dashboard_id},
|
|
468
468
|
body=body,
|
|
469
469
|
)
|
|
470
|
+
if action == "add-iframes":
|
|
471
|
+
ids = [item.strip() for item in args.iframe_ids if item.strip()]
|
|
472
|
+
if not ids:
|
|
473
|
+
raise CliError("Provide at least one iframe ID.")
|
|
474
|
+
return run_request(
|
|
475
|
+
args,
|
|
476
|
+
"apps.iframes.add",
|
|
477
|
+
path_params={"app_id": args.app_id},
|
|
478
|
+
body={"iframeIds": ids},
|
|
479
|
+
)
|
|
480
|
+
if action == "remove-iframe":
|
|
481
|
+
return run_request(
|
|
482
|
+
args,
|
|
483
|
+
"apps.iframes.remove",
|
|
484
|
+
path_params={"app_id": args.app_id, "iframe_id": args.iframe_id},
|
|
485
|
+
)
|
|
486
|
+
if action == "set-iframe-group":
|
|
487
|
+
if args.ungrouped:
|
|
488
|
+
body = {"groupId": None}
|
|
489
|
+
elif args.group_id is not None:
|
|
490
|
+
body = {"groupId": args.group_id}
|
|
491
|
+
else:
|
|
492
|
+
raise CliError("Provide --group-id or --ungrouped.")
|
|
493
|
+
return run_request(
|
|
494
|
+
args,
|
|
495
|
+
"apps.iframes.set-group",
|
|
496
|
+
path_params={"app_id": args.app_id, "iframe_id": args.iframe_id},
|
|
497
|
+
body=body,
|
|
498
|
+
)
|
|
470
499
|
if action == "reorder-members":
|
|
471
500
|
body = _payload_from_args(args, {}, ("items",))
|
|
472
501
|
if not isinstance(body["items"], list):
|
|
@@ -522,6 +551,13 @@ def handle_dashboards(args: argparse.Namespace) -> int:
|
|
|
522
551
|
)
|
|
523
552
|
|
|
524
553
|
|
|
554
|
+
def handle_iframes(args: argparse.Namespace) -> int:
|
|
555
|
+
action = args.iframes_action
|
|
556
|
+
params = {"iframe_id": args.iframe_id} if action in ("get", "update", "delete") else None
|
|
557
|
+
body = load_json_payload(args.json, args.json_file) if action in ("create", "update", "delete") else None
|
|
558
|
+
return run_request(args, f"iframes.{action}", path_params=params, body=body)
|
|
559
|
+
|
|
560
|
+
|
|
525
561
|
def handle_forms(args: argparse.Namespace) -> int:
|
|
526
562
|
action = args.forms_action
|
|
527
563
|
if action == "list":
|
|
@@ -681,6 +717,10 @@ def handle_workflows(args: argparse.Namespace) -> int:
|
|
|
681
717
|
path = {"object_id": args.object_id}
|
|
682
718
|
if hasattr(args, "workflow_id"):
|
|
683
719
|
path["workflow_id"] = args.workflow_id
|
|
720
|
+
if action in ("python-runtime", "retry"):
|
|
721
|
+
if action == "retry":
|
|
722
|
+
path["execution_id"] = args.execution_id
|
|
723
|
+
return run_request(args, f"workflows.{action}", path_params=path)
|
|
684
724
|
if action == "list":
|
|
685
725
|
return run_request(args, "workflows.list", path_params=path)
|
|
686
726
|
if action == "get":
|
|
@@ -941,8 +981,26 @@ def handle_environments(args: argparse.Namespace) -> int:
|
|
|
941
981
|
return 0
|
|
942
982
|
|
|
943
983
|
|
|
984
|
+
def handle_formulas(args: argparse.Namespace) -> int:
|
|
985
|
+
body = load_json_payload(args.json, args.json_file) if args.formulas_action == "validate" else None
|
|
986
|
+
return run_request(args, f"formulas.{args.formulas_action}", path_params={"object_id": args.object_id}, body=body)
|
|
987
|
+
|
|
988
|
+
|
|
944
989
|
def handle_records(args: argparse.Namespace) -> int:
|
|
945
990
|
action = args.records_action
|
|
991
|
+
if action in ("bundle-init", "bundle-status", "bundle-save", "formula-preview", "hierarchy-candidates", "gantt-candidates"):
|
|
992
|
+
path = {"object_id": args.object_id}
|
|
993
|
+
query = {}
|
|
994
|
+
body = None
|
|
995
|
+
if action == "bundle-init":
|
|
996
|
+
query = {"record_id": args.record_id}
|
|
997
|
+
elif action == "bundle-status":
|
|
998
|
+
path["submission_id"] = args.submission_id
|
|
999
|
+
elif action in ("bundle-save", "formula-preview"):
|
|
1000
|
+
body = load_json_payload(args.json, args.json_file)
|
|
1001
|
+
else:
|
|
1002
|
+
query = {"field": args.field, "exclude": args.exclude, "q": args.query}
|
|
1003
|
+
return run_request(args, f"records.{action}", path_params=path, query=query, body=body)
|
|
946
1004
|
path = {"object_id": args.object_id}
|
|
947
1005
|
if action == "upload-attachment":
|
|
948
1006
|
body, content_type = multipart_upload(args.file)
|
|
@@ -972,7 +1030,7 @@ def handle_records(args: argparse.Namespace) -> int:
|
|
|
972
1030
|
if hasattr(args, "record_id"):
|
|
973
1031
|
path["record_id"] = args.record_id
|
|
974
1032
|
if action == "list":
|
|
975
|
-
return run_request(args, "records.list", path_params=path, query={"page": args.page, "viewId": args.view_id})
|
|
1033
|
+
return run_request(args, "records.list", path_params=path, query={"page": args.page, "viewId": args.view_id, **({"flat": "false"} if args.view_layout else {}), **{key: value for key, value in {"root": args.root, "parent": args.parent, "locate": args.locate, "hierarchyVersion": args.hierarchy_version}.items() if value is not None}})
|
|
976
1034
|
if action == "get" or action == "delete":
|
|
977
1035
|
return run_request(args, f"records.{action}", path_params=path)
|
|
978
1036
|
body = load_json_payload(args.json, args.json_file)
|
|
@@ -1140,6 +1198,26 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1140
1198
|
action="store_true",
|
|
1141
1199
|
help="Remove the dashboard from its current group in this app.",
|
|
1142
1200
|
)
|
|
1201
|
+
app_add_iframes = app_actions.add_parser("add-iframes")
|
|
1202
|
+
app_add_iframes.add_argument("app_id")
|
|
1203
|
+
app_add_iframes.add_argument(
|
|
1204
|
+
"iframe_ids",
|
|
1205
|
+
nargs="+",
|
|
1206
|
+
help="One or more iframe IDs to associate with the app.",
|
|
1207
|
+
)
|
|
1208
|
+
app_remove_iframe = app_actions.add_parser("remove-iframe")
|
|
1209
|
+
app_remove_iframe.add_argument("app_id")
|
|
1210
|
+
app_remove_iframe.add_argument("iframe_id")
|
|
1211
|
+
add_yes(app_remove_iframe)
|
|
1212
|
+
app_set_iframe_group = app_actions.add_parser("set-iframe-group")
|
|
1213
|
+
app_set_iframe_group.add_argument("app_id")
|
|
1214
|
+
app_set_iframe_group.add_argument("iframe_id")
|
|
1215
|
+
app_set_iframe_group.add_argument("--group-id", help="Target group ID. Omit with --ungrouped.")
|
|
1216
|
+
app_set_iframe_group.add_argument(
|
|
1217
|
+
"--ungrouped",
|
|
1218
|
+
action="store_true",
|
|
1219
|
+
help="Remove the iframe from its current group in this app.",
|
|
1220
|
+
)
|
|
1143
1221
|
app_reorder_members = app_actions.add_parser("reorder-members")
|
|
1144
1222
|
app_reorder_members.add_argument("app_id")
|
|
1145
1223
|
add_json_args(app_reorder_members, endpoint_key="apps.members.reorder")
|
|
@@ -1170,6 +1248,18 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1170
1248
|
add_yes(dashboard_delete)
|
|
1171
1249
|
dashboards.set_defaults(handler=handle_dashboards)
|
|
1172
1250
|
|
|
1251
|
+
iframes = groups.add_parser("iframes", help="Manage reusable iframe configurations.")
|
|
1252
|
+
iframe_actions = iframes.add_subparsers(dest="iframes_action", required=True)
|
|
1253
|
+
for action in ("list", "get", "create", "update", "delete"):
|
|
1254
|
+
command = iframe_actions.add_parser(action)
|
|
1255
|
+
if action in ("get", "update", "delete"):
|
|
1256
|
+
command.add_argument("iframe_id")
|
|
1257
|
+
if action in ("create", "update", "delete"):
|
|
1258
|
+
add_json_args(command, endpoint_key=f"iframes.{action}")
|
|
1259
|
+
if action == "delete":
|
|
1260
|
+
add_yes(command)
|
|
1261
|
+
iframes.set_defaults(handler=handle_iframes)
|
|
1262
|
+
|
|
1173
1263
|
forms = groups.add_parser("forms", help="Manage custom forms (public collection links).")
|
|
1174
1264
|
form_actions = forms.add_subparsers(dest="forms_action", required=True)
|
|
1175
1265
|
form_actions.add_parser("list")
|
|
@@ -1336,6 +1426,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1336
1426
|
workflow_executions.add_argument("object_id")
|
|
1337
1427
|
workflow_executions.add_argument("workflow_id")
|
|
1338
1428
|
workflow_executions.add_argument("--result", choices=("success", "failure"))
|
|
1429
|
+
runtime_help = workflow_actions.add_parser("python-runtime")
|
|
1430
|
+
runtime_help.add_argument("object_id")
|
|
1431
|
+
runtime_help.add_argument("workflow_id", help="Use new for a workflow not yet created.")
|
|
1432
|
+
retry = workflow_actions.add_parser("retry")
|
|
1433
|
+
retry.add_argument("object_id")
|
|
1434
|
+
retry.add_argument("workflow_id")
|
|
1435
|
+
retry.add_argument("execution_id")
|
|
1436
|
+
add_yes(retry)
|
|
1339
1437
|
workflows.set_defaults(handler=handle_workflows)
|
|
1340
1438
|
|
|
1341
1439
|
approvals = groups.add_parser("approvals", help="Manage object approval flow metadata.")
|
|
@@ -1473,6 +1571,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1473
1571
|
record_list = record_actions.add_parser("list")
|
|
1474
1572
|
record_list.add_argument("object_id")
|
|
1475
1573
|
record_list.add_argument("--page", type=int, default=1)
|
|
1574
|
+
record_list.add_argument("--view-layout", action="store_true", help="Return tree/Gantt projection instead of the default flat list.")
|
|
1575
|
+
record_list.add_argument("--root")
|
|
1576
|
+
record_list.add_argument("--parent")
|
|
1577
|
+
record_list.add_argument("--locate")
|
|
1578
|
+
record_list.add_argument("--hierarchy-version")
|
|
1476
1579
|
record_list.add_argument("--view-id", help="Select a view returned by views list.")
|
|
1477
1580
|
record_get = record_actions.add_parser("get")
|
|
1478
1581
|
record_get.add_argument("object_id")
|
|
@@ -1497,6 +1600,19 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1497
1600
|
record_delete.add_argument("object_id")
|
|
1498
1601
|
record_delete.add_argument("record_id")
|
|
1499
1602
|
add_yes(record_delete)
|
|
1603
|
+
for action in ("bundle-init", "bundle-status", "bundle-save", "formula-preview", "hierarchy-candidates", "gantt-candidates"):
|
|
1604
|
+
item = record_actions.add_parser(action)
|
|
1605
|
+
item.add_argument("object_id")
|
|
1606
|
+
if action == "bundle-init":
|
|
1607
|
+
item.add_argument("--record-id")
|
|
1608
|
+
elif action == "bundle-status":
|
|
1609
|
+
item.add_argument("submission_id")
|
|
1610
|
+
elif action in ("bundle-save", "formula-preview"):
|
|
1611
|
+
add_json_args(item, endpoint_key=f"records.{action}")
|
|
1612
|
+
else:
|
|
1613
|
+
item.add_argument("--field", required=True, help="Stable field ID.")
|
|
1614
|
+
item.add_argument("--exclude", required=action == "gantt-candidates", default="")
|
|
1615
|
+
item.add_argument("--query", default="")
|
|
1500
1616
|
records.set_defaults(handler=handle_records)
|
|
1501
1617
|
|
|
1502
1618
|
roles = groups.add_parser("roles", help="Manage organization roles (production Host only).")
|
|
@@ -1516,6 +1632,14 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
1516
1632
|
role_delete.add_argument("role_id")
|
|
1517
1633
|
add_yes(role_delete)
|
|
1518
1634
|
roles.set_defaults(handler=handle_roles)
|
|
1635
|
+
formulas = groups.add_parser("formulas", help="Discover, validate and trial formula/rollup fields.")
|
|
1636
|
+
formula_actions = formulas.add_subparsers(dest="formulas_action", required=True)
|
|
1637
|
+
for action in ("sources", "collections", "validate"):
|
|
1638
|
+
item = formula_actions.add_parser(action)
|
|
1639
|
+
item.add_argument("object_id")
|
|
1640
|
+
if action == "validate":
|
|
1641
|
+
add_json_args(item, endpoint_key="formulas.validate")
|
|
1642
|
+
formulas.set_defaults(handler=handle_formulas)
|
|
1519
1643
|
return parser
|
|
1520
1644
|
|
|
1521
1645
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flowbase-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.16
|
|
4
4
|
Summary: Command line client for the FlowBase OpenAPI
|
|
5
5
|
Author: DeepFlowAI
|
|
6
6
|
License: Apache-2.0
|
|
@@ -160,6 +160,14 @@ flowbase objects cli-edit-policy update obj_xxx --json '{"baseRevision":4,"polic
|
|
|
160
160
|
|
|
161
161
|
`records create` returns `{recordId}`; use it with `records get`.
|
|
162
162
|
|
|
163
|
+
To add existing objects to an application, pass their IDs with `--object-ids`:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
flowbase apps add-objects app_xxx --object-ids obj_a,obj_b
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
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.
|
|
170
|
+
|
|
163
171
|
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
172
|
|
|
165
173
|
### 6.2 Edit and publish configuration on a test environment
|
|
@@ -280,3 +288,77 @@ flowbase roles update rol_xxx --json-file role.json
|
|
|
280
288
|
- `docs catalogs page-buttons`, `workflow-branches`, `approval-rules`, and `dashboard-runtime` describe complex configuration and runtime limits.
|
|
281
289
|
|
|
282
290
|
Transport requests time out after 60 seconds. A lost write response has an unknown outcome: read the resource and current revision before retrying. The CLI never automatically retries writes.
|
|
291
|
+
|
|
292
|
+
### Recent feature authoring
|
|
293
|
+
|
|
294
|
+
- `flowbase docs catalogs planning`: tree/Gantt view snapshots, candidate queries and optimistic plan updates. Read a full view with `views list` before updating; preserve `displayMode`, `parentFieldId` and `gantt`.
|
|
295
|
+
- `flowbase docs catalogs record-bundles`: initialize, atomically submit and recover a parent/detail submission. Reuse a submission ID only with the identical request body.
|
|
296
|
+
- `flowbase docs catalogs formula-authoring`: formula/rollup sources, validation, saved-record trials and runtime draft preview. Derived values are read-only.
|
|
297
|
+
- `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`.
|
|
298
|
+
|
|
299
|
+
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).
|
|
300
|
+
|
|
301
|
+
### Reusable iframe configuration (4.11)
|
|
302
|
+
|
|
303
|
+
`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).
|
|
304
|
+
|
|
305
|
+
```bash
|
|
306
|
+
flowbase iframes create --json-file iframe.json
|
|
307
|
+
flowbase iframes get ifr_example
|
|
308
|
+
flowbase iframes update ifr_example --json-file iframe-update.json
|
|
309
|
+
flowbase apps add-iframes app_example ifr_example
|
|
310
|
+
flowbase apps set-iframe-group app_example ifr_example --group-id grp_example
|
|
311
|
+
flowbase apps set-iframe-group app_example ifr_example --ungrouped
|
|
312
|
+
flowbase apps reorder-members app_example --json '{"items":[{"type":"iframe","id":"ifr_example"}]}'
|
|
313
|
+
flowbase apps remove-iframe app_example ifr_example --yes
|
|
314
|
+
flowbase iframes delete ifr_example --json '{"baseRevision":12}' --yes
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
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.
|
|
318
|
+
|
|
319
|
+
### Custom object views (4.12)
|
|
320
|
+
|
|
321
|
+
Read `flowbase docs catalogs custom-views` and `flowbase views list OBJECT` first. Save the following as `view.json`, replacing `baseRevision` with the current `draftRevision`:
|
|
322
|
+
|
|
323
|
+
```json
|
|
324
|
+
{
|
|
325
|
+
"baseRevision": 4,
|
|
326
|
+
"name": "Custom overview",
|
|
327
|
+
"enabled": false,
|
|
328
|
+
"defaultFieldIds": [],
|
|
329
|
+
"displayMode": "custom",
|
|
330
|
+
"custom": {
|
|
331
|
+
"runtimeVersion": "cv1",
|
|
332
|
+
"source": "import React from 'react'; export default function View(){return <div>Hello</div>}"
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
flowbase views create obj_example --json-file view.json
|
|
339
|
+
flowbase views list obj_example
|
|
340
|
+
flowbase views update obj_example viw_example --json-file view.json
|
|
341
|
+
flowbase views set-enabled obj_example viw_example --base-revision 6 --enabled --yes
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
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.
|
|
345
|
+
|
|
346
|
+
### Record detail embedded pages (4.13)
|
|
347
|
+
|
|
348
|
+
`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`):
|
|
349
|
+
|
|
350
|
+
```json
|
|
351
|
+
{
|
|
352
|
+
"kind": "recordEmbed",
|
|
353
|
+
"label": "External panel",
|
|
354
|
+
"showTitle": true,
|
|
355
|
+
"url": "https://example.com/panel",
|
|
356
|
+
"height": 480,
|
|
357
|
+
"allowRead": false,
|
|
358
|
+
"allowedFieldIds": []
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
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.
|
|
363
|
+
|
|
364
|
+
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.
|
|
@@ -191,7 +191,7 @@ class FlowBaseCliTest(unittest.TestCase):
|
|
|
191
191
|
self.assertEqual(code, 0)
|
|
192
192
|
field_types = json.loads(stdout.getvalue())
|
|
193
193
|
type_names = {item["type"] for item in field_types}
|
|
194
|
-
self.assertEqual(len(field_types),
|
|
194
|
+
self.assertEqual(len(field_types), 27)
|
|
195
195
|
self.assertIn("select.single", type_names)
|
|
196
196
|
self.assertIn("lookup.employee", type_names)
|
|
197
197
|
self.assertTrue({
|
|
@@ -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()
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""New discovery commands and retry confirmation remain usable without a browser."""
|
|
2
|
+
import io
|
|
3
|
+
from unittest.mock import patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from flowbase_cli.main import main
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.parametrize('args,endpoint,path,query', [
|
|
11
|
+
(['formulas', 'sources', 'obj_x'], 'formulas.sources', {'object_id': 'obj_x'}, None),
|
|
12
|
+
(['formulas', 'collections', 'obj_x'], 'formulas.collections', {'object_id': 'obj_x'}, None),
|
|
13
|
+
(['records', 'bundle-init', 'obj_x', '--record-id', 'rec_x'], 'records.bundle-init', {'object_id': 'obj_x'}, {'record_id': 'rec_x'}),
|
|
14
|
+
(['records', 'bundle-status', 'obj_x', 'submission'], 'records.bundle-status', {'object_id': 'obj_x', 'submission_id': 'submission'}, {}),
|
|
15
|
+
(['records', 'hierarchy-candidates', 'obj_x', '--field', 'fld_x'], 'records.hierarchy-candidates', {'object_id': 'obj_x'}, {'field': 'fld_x', 'exclude': '', 'q': ''}),
|
|
16
|
+
(['records', 'gantt-candidates', 'obj_x', '--field', 'fld_x', '--exclude', 'rec_x'], 'records.gantt-candidates', {'object_id': 'obj_x'}, {'field': 'fld_x', 'exclude': 'rec_x', 'q': ''}),
|
|
17
|
+
(['workflows', 'python-runtime', 'obj_x', 'new'], 'workflows.python-runtime', {'object_id': 'obj_x', 'workflow_id': 'new'}, None),
|
|
18
|
+
])
|
|
19
|
+
def test_discovery_request(args, endpoint, path, query):
|
|
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['query'] == query
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_retry_noninteractive_requires_explicit_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(['workflows', 'retry', 'obj_x', 'wfl_x', 'exe_x']) == 1
|
|
30
|
+
request.assert_not_called()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|