flowbase-cli 0.1.15__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbase-cli
3
- Version: 0.1.15
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
@@ -289,3 +297,68 @@ Transport requests time out after 60 seconds. A lost write response has an unkno
289
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`.
290
298
 
291
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
@@ -280,3 +288,68 @@ Transport requests time out after 60 seconds. A lost write response has an unkno
280
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`.
281
289
 
282
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.
@@ -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.16"
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.16"
@@ -22,6 +22,42 @@ 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
+
25
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'),
26
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'),
27
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}'),
@@ -258,7 +294,7 @@ ENDPOINTS = (
258
294
  '{"fieldId":"fld_done","operator":"is_true"}'
259
295
  '],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null,"displayMode":"table","parentFieldId":null,"gantt":{}}'
260
296
  )),
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",
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",
262
298
  request_body=(
263
299
  '{"baseRevision":4,"name":"Active","enabled":true,"match":"all",'
264
300
  '"filters":['
@@ -1040,7 +1076,7 @@ APPROVAL_NODE_TYPES = (
1040
1076
  )
1041
1077
 
1042
1078
  FUNCTION_PERMISSIONS = (
1043
- "workspace.access", "app.manage", "object.manage", "dashboard.manage", "form.manage",
1079
+ "workspace.access", "app.manage", "object.manage", "dashboard.manage", "form.manage", "iframe.manage",
1044
1080
  "version.manage", "org.manage", "system.manage",
1045
1081
  )
1046
1082
 
@@ -1088,7 +1124,7 @@ PAGE_COMPONENTS = {
1088
1124
  ),
1089
1125
  "detailOnlyKinds": (
1090
1126
  "relatedList", "approval", "detailSummary", "stageBar", "tabs",
1091
- "relatedSummary", "attachmentCard", "noticeBlock",
1127
+ "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
1092
1128
  ),
1093
1129
  },
1094
1130
  "kinds": (
@@ -1207,7 +1243,7 @@ PAGE_COMPONENTS = {
1207
1243
  "minimumComponents": 1,
1208
1244
  "allowedChildKinds": (
1209
1245
  "field", "relatedList", "approval", "stageBar",
1210
- "relatedSummary", "attachmentCard", "noticeBlock",
1246
+ "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
1211
1247
  ),
1212
1248
  },
1213
1249
  "note": "defaultTabId must reference a tab in this component; tabs cannot be nested.",
@@ -1248,6 +1284,22 @@ PAGE_COMPONENTS = {
1248
1284
  "titleLengthWhenShown": (1, 50),
1249
1285
  },
1250
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
+ },
1251
1303
  {
1252
1304
  "kind": "noticeBlock",
1253
1305
  "name": "notice block",
@@ -1323,7 +1375,7 @@ PAGE_RULES = {
1323
1375
  "states": ("visible", "hidden"),
1324
1376
  "targetKinds": (
1325
1377
  "relatedList", "approval", "detailSummary", "stageBar", "tabs",
1326
- "relatedSummary", "attachmentCard", "noticeBlock",
1378
+ "relatedSummary", "attachmentCard", "noticeBlock", "recordEmbed",
1327
1379
  ),
1328
1380
  "excludedTargets": ("field", "region", "tab", "button", "systemArea"),
1329
1381
  "sameRuleUniqueTarget": True,
@@ -1542,6 +1594,13 @@ FORMS = {
1542
1594
  }
1543
1595
 
1544
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
+ },
1545
1604
  "planning": {
1546
1605
  "viewUpdate": "Read views.list, preserve the complete snapshot, then views.update with baseRevision. Omitting displayMode/parentFieldId/gantt resets them to table/null/{}.",
1547
1606
  "tree": {"displayMode": "tree", "parentFieldId": "fld_parent", "gantt": {}},
@@ -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":
@@ -1162,6 +1198,26 @@ def build_parser() -> argparse.ArgumentParser:
1162
1198
  action="store_true",
1163
1199
  help="Remove the dashboard from its current group in this app.",
1164
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
+ )
1165
1221
  app_reorder_members = app_actions.add_parser("reorder-members")
1166
1222
  app_reorder_members.add_argument("app_id")
1167
1223
  add_json_args(app_reorder_members, endpoint_key="apps.members.reorder")
@@ -1192,6 +1248,18 @@ def build_parser() -> argparse.ArgumentParser:
1192
1248
  add_yes(dashboard_delete)
1193
1249
  dashboards.set_defaults(handler=handle_dashboards)
1194
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
+
1195
1263
  forms = groups.add_parser("forms", help="Manage custom forms (public collection links).")
1196
1264
  form_actions = forms.add_subparsers(dest="forms_action", required=True)
1197
1265
  form_actions.add_parser("list")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowbase-cli
3
- Version: 0.1.15
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
@@ -289,3 +297,68 @@ Transport requests time out after 60 seconds. A lost write response has an unkno
289
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`.
290
298
 
291
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.
@@ -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