flowbase-cli 0.1.14__tar.gz → 0.1.15__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.14
3
+ Version: 0.1.15
4
4
  Summary: Command line client for the FlowBase OpenAPI
5
5
  Author: DeepFlowAI
6
6
  License: Apache-2.0
@@ -280,3 +280,12 @@ flowbase roles update rol_xxx --json-file role.json
280
280
  - `docs catalogs page-buttons`, `workflow-branches`, `approval-rules`, and `dashboard-runtime` describe complex configuration and runtime limits.
281
281
 
282
282
  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.
283
+
284
+ ### Recent feature authoring
285
+
286
+ - `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`.
287
+ - `flowbase docs catalogs record-bundles`: initialize, atomically submit and recover a parent/detail submission. Reuse a submission ID only with the identical request body.
288
+ - `flowbase docs catalogs formula-authoring`: formula/rollup sources, validation, saved-record trials and runtime draft preview. Derived values are read-only.
289
+ - `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
+
291
+ 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).
@@ -1,12 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: flowbase-cli
3
- Version: 0.1.14
4
- Summary: Command line client for the FlowBase OpenAPI
5
- Author: DeepFlowAI
6
- License: Apache-2.0
7
- Requires-Python: >=3.11
8
- Description-Content-Type: text/markdown
9
-
10
1
  # FlowBase CLI
11
2
 
12
3
  `flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, approval flows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
@@ -280,3 +271,12 @@ flowbase roles update rol_xxx --json-file role.json
280
271
  - `docs catalogs page-buttons`, `workflow-branches`, `approval-rules`, and `dashboard-runtime` describe complex configuration and runtime limits.
281
272
 
282
273
  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.
274
+
275
+ ### Recent feature authoring
276
+
277
+ - `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`.
278
+ - `flowbase docs catalogs record-bundles`: initialize, atomically submit and recover a parent/detail submission. Reuse a submission ID only with the identical request body.
279
+ - `flowbase docs catalogs formula-authoring`: formula/rollup sources, validation, saved-record trials and runtime draft preview. Derived values are read-only.
280
+ - `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
+
282
+ 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).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flowbase-cli"
7
- version = "0.1.14"
7
+ version = "0.1.15"
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.14"
3
+ __version__ = "0.1.15"
@@ -22,6 +22,17 @@ class Endpoint:
22
22
 
23
23
 
24
24
  ENDPOINTS = (
25
+ 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
+ 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
+ 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}'),
28
+ 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}}'),
29
+ 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'),
30
+ 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":[]}'),
31
+ 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'),
32
+ 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'),
33
+ 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'),
34
+ 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'),
35
+ 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
36
  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
37
  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
38
  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 +256,15 @@ ENDPOINTS = (
245
256
  '"filters":['
246
257
  '{"fieldId":"fld_status","operator":"eq","value":"opt_xxx"},'
247
258
  '{"fieldId":"fld_done","operator":"is_true"}'
248
- '],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null}'
259
+ '],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null,"displayMode":"table","parentFieldId":null,"gantt":{}}'
249
260
  )),
250
- Endpoint("views.update", "flowbase_views_update", "PUT", "/objects/{object_id}/views/{view_id}", "metadata:write", "Update an object view draft.", "flowbase views update obj_xxx viw_xxx --json-file view.json",
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",
251
262
  request_body=(
252
263
  '{"baseRevision":4,"name":"Active","enabled":true,"match":"all",'
253
264
  '"filters":['
254
265
  '{"fieldId":"fld_status","operator":"eq","value":"opt_xxx"},'
255
266
  '{"fieldId":"fld_done","operator":"is_true"}'
256
- '],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null}'
267
+ '],"defaultFieldIds":["fld_a","fld_b"],"groupFieldId":null,"displayMode":"table","parentFieldId":null,"gantt":{}}'
257
268
  )),
258
269
  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
270
  request_body='{"baseRevision":4,"viewIds":["viw_a","viw_b"]}'),
@@ -625,6 +636,17 @@ def _field_type(
625
636
 
626
637
 
627
638
  FIELD_TYPES = (
639
+ _field_type(
640
+ "rollup", ("source", "operation", "resultType", "scale", "currency", "unit"),
641
+ writable=False, record_value="derived scalar | null; omit on writes; see formulaStates",
642
+ extra={"permissions": ["readonly", "hidden"], "operations": ["sum", "avg", "count", "count_values", "min", "max"], "sourceModes": ["reverse", "forward", "children"]},
643
+ ),
644
+ _field_type(
645
+ "formula", ("expression", "resultType", "scale", "currency", "bindings", "languageVersion", "collections"),
646
+ writable=False, record_value="derived scalar | null; omit on writes; see formulaStates for failures",
647
+ extra={"permissions": ["readonly", "hidden"], "resultTypes": ["number", "number.currency", "number.percent", "text.singleLine", "date", "boolean"],
648
+ "createExample": {"label": "Total", "key": "total", "description": "Calculated total", "type": "formula", "typeVersion": "v1", "permission": "readonly", "constraints": {"expression": "quantity * unit_price", "resultType": "number", "scale": 2}}},
649
+ ),
628
650
  _field_type(
629
651
  "text.singleLine", ("maxLength", "minLength", "placeholder", "unique"),
630
652
  filter_operators=_TEXT_FILTERS, record_value="string | null",
@@ -1099,7 +1121,8 @@ PAGE_COMPONENTS = {
1099
1121
  "kind", "label", "hideLabel", "objectId", "relationFieldId",
1100
1122
  "columnFieldIds", "sort", "limit", "showViewAll", "createAction",
1101
1123
  ),
1102
- "optionalKeys": ("id", "filters", "carryIns"),
1124
+ "optionalKeys": ("id", "filters", "carryIns", "editMode", "editOptions", "editColumns"),
1125
+ "editMode": {"withParent": "Atomic editing on create/detail pages; initialize with records bundle-init, then bundle-save."},
1103
1126
  "limit": (5, 10, 20),
1104
1127
  "sortDirections": ("asc", "desc"),
1105
1128
  "carryInSources": (
@@ -1519,6 +1542,31 @@ FORMS = {
1519
1542
  }
1520
1543
 
1521
1544
  CATALOGS = {
1545
+ "planning": {
1546
+ "viewUpdate": "Read views.list, preserve the complete snapshot, then views.update with baseRevision. Omitting displayMode/parentFieldId/gantt resets them to table/null/{}.",
1547
+ "tree": {"displayMode": "tree", "parentFieldId": "fld_parent", "gantt": {}},
1548
+ "gantt": {"displayMode": "gantt", "parentFieldId": None, "gantt": {"startFieldId": "fld_start", "endFieldId": "fld_end", "predecessorFieldId": "fld_dependencies"}},
1549
+ "read": "records list OBJECT --view-id VIEW --view-layout; use --parent, --root, --locate and --hierarchy-version for tree navigation. Default is flat.",
1550
+ "write": {"values": {"start": "2026-10-24", "end": "2026-10-25"}, "planViewId": "viw_plan", "updatedAt": "from-records-get"},
1551
+ "candidates": "records hierarchy-candidates / gantt-candidates use field IDs. Record values use field keys.",
1552
+ },
1553
+ "record-bundles": {
1554
+ "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."],
1555
+ "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}}]}]},
1556
+ "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.",
1557
+ },
1558
+ "formula-authoring": {
1559
+ "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."],
1560
+ "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"},
1561
+ "collectionExample": {"expression": "sum(lines)", "resultType": "number", "scale": 2, "languageVersion": 2, "collections": {"lines": {"mode": "reverse", "objectId": "obj_lines", "relationFieldId": "fld_order", "fieldId": "fld_amount"}}},
1562
+ "readOnly": "formula/rollup values cannot be written, filtered, sorted or SQL-aggregated through the employee CLI. Read results and formulaStates instead.",
1563
+ },
1564
+ "workflow-python": {
1565
+ "discover": "workflows python-runtime OBJECT new (or WORKFLOW) returns creationObjects, relation bindings and limits.",
1566
+ "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',
1567
+ "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.",
1568
+ "retry": "workflows executions OBJECT WORKFLOW lists results; workflows retry OBJECT WORKFLOW EXECUTION --yes retries eligible failed tasks using the original task contract.",
1569
+ },
1522
1570
  "workflow-branches": WORKFLOW_BRANCHES,
1523
1571
  "approval-rules": APPROVAL_RULES,
1524
1572
  "page-buttons": PAGE_BUTTONS,
@@ -681,6 +681,10 @@ def handle_workflows(args: argparse.Namespace) -> int:
681
681
  path = {"object_id": args.object_id}
682
682
  if hasattr(args, "workflow_id"):
683
683
  path["workflow_id"] = args.workflow_id
684
+ if action in ("python-runtime", "retry"):
685
+ if action == "retry":
686
+ path["execution_id"] = args.execution_id
687
+ return run_request(args, f"workflows.{action}", path_params=path)
684
688
  if action == "list":
685
689
  return run_request(args, "workflows.list", path_params=path)
686
690
  if action == "get":
@@ -941,8 +945,26 @@ def handle_environments(args: argparse.Namespace) -> int:
941
945
  return 0
942
946
 
943
947
 
948
+ def handle_formulas(args: argparse.Namespace) -> int:
949
+ body = load_json_payload(args.json, args.json_file) if args.formulas_action == "validate" else None
950
+ return run_request(args, f"formulas.{args.formulas_action}", path_params={"object_id": args.object_id}, body=body)
951
+
952
+
944
953
  def handle_records(args: argparse.Namespace) -> int:
945
954
  action = args.records_action
955
+ if action in ("bundle-init", "bundle-status", "bundle-save", "formula-preview", "hierarchy-candidates", "gantt-candidates"):
956
+ path = {"object_id": args.object_id}
957
+ query = {}
958
+ body = None
959
+ if action == "bundle-init":
960
+ query = {"record_id": args.record_id}
961
+ elif action == "bundle-status":
962
+ path["submission_id"] = args.submission_id
963
+ elif action in ("bundle-save", "formula-preview"):
964
+ body = load_json_payload(args.json, args.json_file)
965
+ else:
966
+ query = {"field": args.field, "exclude": args.exclude, "q": args.query}
967
+ return run_request(args, f"records.{action}", path_params=path, query=query, body=body)
946
968
  path = {"object_id": args.object_id}
947
969
  if action == "upload-attachment":
948
970
  body, content_type = multipart_upload(args.file)
@@ -972,7 +994,7 @@ def handle_records(args: argparse.Namespace) -> int:
972
994
  if hasattr(args, "record_id"):
973
995
  path["record_id"] = args.record_id
974
996
  if action == "list":
975
- return run_request(args, "records.list", path_params=path, query={"page": args.page, "viewId": args.view_id})
997
+ 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
998
  if action == "get" or action == "delete":
977
999
  return run_request(args, f"records.{action}", path_params=path)
978
1000
  body = load_json_payload(args.json, args.json_file)
@@ -1336,6 +1358,14 @@ def build_parser() -> argparse.ArgumentParser:
1336
1358
  workflow_executions.add_argument("object_id")
1337
1359
  workflow_executions.add_argument("workflow_id")
1338
1360
  workflow_executions.add_argument("--result", choices=("success", "failure"))
1361
+ runtime_help = workflow_actions.add_parser("python-runtime")
1362
+ runtime_help.add_argument("object_id")
1363
+ runtime_help.add_argument("workflow_id", help="Use new for a workflow not yet created.")
1364
+ retry = workflow_actions.add_parser("retry")
1365
+ retry.add_argument("object_id")
1366
+ retry.add_argument("workflow_id")
1367
+ retry.add_argument("execution_id")
1368
+ add_yes(retry)
1339
1369
  workflows.set_defaults(handler=handle_workflows)
1340
1370
 
1341
1371
  approvals = groups.add_parser("approvals", help="Manage object approval flow metadata.")
@@ -1473,6 +1503,11 @@ def build_parser() -> argparse.ArgumentParser:
1473
1503
  record_list = record_actions.add_parser("list")
1474
1504
  record_list.add_argument("object_id")
1475
1505
  record_list.add_argument("--page", type=int, default=1)
1506
+ record_list.add_argument("--view-layout", action="store_true", help="Return tree/Gantt projection instead of the default flat list.")
1507
+ record_list.add_argument("--root")
1508
+ record_list.add_argument("--parent")
1509
+ record_list.add_argument("--locate")
1510
+ record_list.add_argument("--hierarchy-version")
1476
1511
  record_list.add_argument("--view-id", help="Select a view returned by views list.")
1477
1512
  record_get = record_actions.add_parser("get")
1478
1513
  record_get.add_argument("object_id")
@@ -1497,6 +1532,19 @@ def build_parser() -> argparse.ArgumentParser:
1497
1532
  record_delete.add_argument("object_id")
1498
1533
  record_delete.add_argument("record_id")
1499
1534
  add_yes(record_delete)
1535
+ for action in ("bundle-init", "bundle-status", "bundle-save", "formula-preview", "hierarchy-candidates", "gantt-candidates"):
1536
+ item = record_actions.add_parser(action)
1537
+ item.add_argument("object_id")
1538
+ if action == "bundle-init":
1539
+ item.add_argument("--record-id")
1540
+ elif action == "bundle-status":
1541
+ item.add_argument("submission_id")
1542
+ elif action in ("bundle-save", "formula-preview"):
1543
+ add_json_args(item, endpoint_key=f"records.{action}")
1544
+ else:
1545
+ item.add_argument("--field", required=True, help="Stable field ID.")
1546
+ item.add_argument("--exclude", required=action == "gantt-candidates", default="")
1547
+ item.add_argument("--query", default="")
1500
1548
  records.set_defaults(handler=handle_records)
1501
1549
 
1502
1550
  roles = groups.add_parser("roles", help="Manage organization roles (production Host only).")
@@ -1516,6 +1564,14 @@ def build_parser() -> argparse.ArgumentParser:
1516
1564
  role_delete.add_argument("role_id")
1517
1565
  add_yes(role_delete)
1518
1566
  roles.set_defaults(handler=handle_roles)
1567
+ formulas = groups.add_parser("formulas", help="Discover, validate and trial formula/rollup fields.")
1568
+ formula_actions = formulas.add_subparsers(dest="formulas_action", required=True)
1569
+ for action in ("sources", "collections", "validate"):
1570
+ item = formula_actions.add_parser(action)
1571
+ item.add_argument("object_id")
1572
+ if action == "validate":
1573
+ add_json_args(item, endpoint_key="formulas.validate")
1574
+ formulas.set_defaults(handler=handle_formulas)
1519
1575
  return parser
1520
1576
 
1521
1577
 
@@ -1,3 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: flowbase-cli
3
+ Version: 0.1.15
4
+ Summary: Command line client for the FlowBase OpenAPI
5
+ Author: DeepFlowAI
6
+ License: Apache-2.0
7
+ Requires-Python: >=3.11
8
+ Description-Content-Type: text/markdown
9
+
1
10
  # FlowBase CLI
2
11
 
3
12
  `flowbase` is the official command line client for the FlowBase OpenAPI. It manages apps, objects, fields, views, custom pages, workflows, approval flows, dashboards, business records, roles, and configuration versions across the three FlowBase environments.
@@ -271,3 +280,12 @@ flowbase roles update rol_xxx --json-file role.json
271
280
  - `docs catalogs page-buttons`, `workflow-branches`, `approval-rules`, and `dashboard-runtime` describe complex configuration and runtime limits.
272
281
 
273
282
  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.
283
+
284
+ ### Recent feature authoring
285
+
286
+ - `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`.
287
+ - `flowbase docs catalogs record-bundles`: initialize, atomically submit and recover a parent/detail submission. Reuse a submission ID only with the identical request body.
288
+ - `flowbase docs catalogs formula-authoring`: formula/rollup sources, validation, saved-record trials and runtime draft preview. Derived values are read-only.
289
+ - `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
+
291
+ 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).
@@ -10,4 +10,5 @@ src/flowbase_cli.egg-info/dependency_links.txt
10
10
  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
- tests/test_cli.py
13
+ tests/test_cli.py
14
+ tests/test_recent_commands.py
@@ -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), 25)
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({
@@ -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