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.
- {flowbase_cli-0.1.14/src/flowbase_cli.egg-info → flowbase_cli-0.1.15}/PKG-INFO +10 -1
- flowbase_cli-0.1.14/PKG-INFO → flowbase_cli-0.1.15/README.md +9 -9
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/pyproject.toml +1 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli/__init__.py +1 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli/endpoints.py +52 -4
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli/main.py +57 -1
- flowbase_cli-0.1.14/README.md → flowbase_cli-0.1.15/src/flowbase_cli.egg-info/PKG-INFO +18 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli.egg-info/SOURCES.txt +2 -1
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/tests/test_cli.py +1 -1
- flowbase_cli-0.1.15/tests/test_recent_commands.py +30 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/setup.cfg +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli/guide.py +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli.egg-info/dependency_links.txt +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli.egg-info/entry_points.txt +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/src/flowbase_cli.egg-info/top_level.txt +0 -0
- {flowbase_cli-0.1.14 → flowbase_cli-0.1.15}/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.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).
|
|
@@ -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
|
|
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).
|
|
@@ -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({
|
|
@@ -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
|