Skip to content

Add Flow360 CLI resource inspection commands#2027

Closed
maciej-flexcompute wants to merge 15 commits into
mainfrom
maciej/codex/flow360-cli-pr2-resource-inspection
Closed

Add Flow360 CLI resource inspection commands#2027
maciej-flexcompute wants to merge 15 commits into
mainfrom
maciej/codex/flow360-cli-pr2-resource-inspection

Conversation

@maciej-flexcompute
Copy link
Copy Markdown
Collaborator

@maciej-flexcompute maciej-flexcompute commented May 1, 2026

Summary

  • Adds lightweight CLI resource inspection for V2 Geometry, SurfaceMesh, VolumeMesh, Case, and Draft resources.
  • Adds shared webapi abstractions for assets, drafts, workspaces, browser URLs, state polling, and simulation summary rendering.
  • Keeps command output JSON-first and keeps help paths lightweight so CLI startup does not require full SDK model imports.

Added commands

Command Purpose
flow360 geometry info <geometry_id> Fetch geometry metadata.
flow360 geometry state <geometry_id> Fetch normalized geometry state.
flow360 geometry simulation get <geometry_id> Fetch geometry simulation.json.
flow360 geometry summary <geometry_id> Fetch intent-focused simulation summary.
flow360 surface-mesh info <surface_mesh_id> Fetch surface mesh metadata.
flow360 surface-mesh state <surface_mesh_id> Fetch normalized surface mesh state.
flow360 surface-mesh simulation get <surface_mesh_id> Fetch surface mesh simulation.json.
flow360 surface-mesh summary <surface_mesh_id> Fetch intent-focused simulation summary.
flow360 volume-mesh info <volume_mesh_id> Fetch volume mesh metadata.
flow360 volume-mesh state <volume_mesh_id> Fetch normalized volume mesh state.
flow360 volume-mesh simulation get <volume_mesh_id> Fetch volume mesh simulation.json.
flow360 volume-mesh summary <volume_mesh_id> Fetch intent-focused simulation summary.
flow360 case info <case_id> Fetch case metadata.
flow360 case state <case_id> Fetch normalized case state.
flow360 case simulation get <case_id> Fetch case simulation.json.
flow360 case summary <case_id> Fetch intent-focused simulation summary.
flow360 draft list --project-id <project_id> List drafts for a project.
flow360 draft info <draft_id> Fetch draft metadata.
flow360 draft state <draft_id> Fetch normalized draft state.
flow360 draft simulation get <draft_id> Fetch draft simulation.json.
flow360 open <ref_id> Print or open the workbench URL for project, folder, case, mesh, geometry, or draft refs.
flow360 wait <ref_id> Poll a resource until it reaches a terminal state.

Hidden compatibility aliases remain for transitional use: get aliases info, and draft ls aliases draft list.

Local command timings

Measured on 2026-05-04 with the installed console script directly, not through poetry run, because poetry run adds local Poetry interpreter-selection overhead. API timings use live production resources and include network/backend latency. Browser-open timings were run with BROWSER=true to avoid opening a local browser while still exercising URL resolution.

Resources used: Simple Airplane project prj-7cd91cca-3049-47cd-a5c8-d7cf0ad1cbc0, geometry geo-55cf9c04-02e2-4584-870d-9352d397a789, surface mesh sm-1a308cad-f76d-4ef7-a908-9b4cc5be50e0, volume mesh vm-e84a7911-2370-406f-99a8-fc0a35a9b87a, case case-7b77792d-ed36-4760-921a-a7b9614a867d, draft dft-5bf5de98-9597-457e-95e7-067380870080.

Category Command Time Exit
help flow360 draft --help 0.206s 0
help flow360 draft simulation --help 0.127s 0
help flow360 geometry --help 0.125s 0
help flow360 geometry simulation --help 0.133s 0
help flow360 surface-mesh --help 0.129s 0
help flow360 surface-mesh simulation --help 0.123s 0
help flow360 volume-mesh --help 0.124s 0
help flow360 volume-mesh simulation --help 0.124s 0
help flow360 case --help 0.130s 0
help flow360 case simulation --help 0.124s 0
help flow360 open --help 0.129s 0
help flow360 wait --help 0.126s 0
live API flow360 draft list --project-id <project_id> 1.397s 0
live API flow360 draft info <draft_id> 1.148s 0
live API flow360 draft state <draft_id> 1.154s 0
live API flow360 draft simulation get <draft_id> 1.254s 0
live API flow360 geometry info <geometry_id> 1.406s 0
live API flow360 geometry state <geometry_id> 1.164s 0
live API flow360 geometry summary <geometry_id> 2.221s 0
live API flow360 geometry simulation get <geometry_id> 1.192s 0
live API flow360 surface-mesh info <surface_mesh_id> 1.095s 0
live API flow360 surface-mesh state <surface_mesh_id> 1.125s 0
live API flow360 surface-mesh summary <surface_mesh_id> 1.968s 0
live API flow360 surface-mesh simulation get <surface_mesh_id> 1.188s 0
live API flow360 volume-mesh info <volume_mesh_id> 1.165s 0
live API flow360 volume-mesh state <volume_mesh_id> 1.388s 0
live API flow360 volume-mesh summary <volume_mesh_id> 2.143s 0
live API flow360 volume-mesh simulation get <volume_mesh_id> 1.216s 0
live API flow360 case info <case_id> 1.162s 0
live API flow360 case state <case_id> 1.215s 0
live API flow360 case summary <case_id> 1.981s 0
live API flow360 case simulation get <case_id> 1.156s 0
live API flow360 open <project_id> 0.144s 0
live API flow360 open <case_id> 1.215s 0
live API flow360 open <folder_id> 1.511s 0
live API flow360 wait <case_id> 1.381s 0

Example outputs

flow360 case info case-...

{
  "id": "case-...",
  "name": "Alpha -18",
  "project_id": "prj-...",
  "parent_id": null,
  "solver_version": "release-25.9",
  "status": "completed",
  "tags": [],
  "type": "Case",
  "created_at": "2026-04-01T12:00:00Z",
  "updated_at": "2026-04-01T12:30:00Z",
  "mesh_id": "vm-..."
}

flow360 volume-mesh state vm-...

{
  "id": "vm-...",
  "type": "VolumeMesh",
  "status": "completed",
  "is_terminal": true,
  "is_success": true,
  "updated_at": "2026-04-01T12:30:00Z"
}

flow360 case simulation get case-7b77792d-ed36-4760-921a-a7b9614a867d excerpt

{
  "simulation": {
    "version": "25.8.3",
    "unit_system": {"name": "SI"},
    "meshing": {
      "type_name": "MeshingParams",
      "defaults": {
        "boundary_layer_first_layer_thickness": {"units": "mm", "value": 0.01},
        "surface_max_edge_length": {"units": "m", "value": 0.2}
      }
    },
    "models": [
      {"type": "Fluid", "turbulence_model_solver": {"type_name": "SpalartAllmaras"}},
      {"name": "Wall", "type": "Wall"},
      {"name": "Freestream", "type": "Freestream"}
    ],
    "operating_condition": {
      "alpha": {"units": "degree", "value": 10},
      "beta": {"units": "degree", "value": 0},
      "type_name": "AerospaceCondition",
      "velocity_magnitude": {"units": "m/s", "value": 100}
    },
    "time_stepping": {"max_steps": 2000, "type_name": "Steady"}
  }
}

flow360 case summary case-7b77792d-ed36-4760-921a-a7b9614a867d

{
  "id": "case-7b77792d-ed36-4760-921a-a7b9614a867d",
  "summary": {
    "meshing": {
      "defaults": {
        "boundary_layer_first_layer_thickness": {"units": "mm", "value": 0.01},
        "surface_max_edge_length": {"units": "m", "value": 0.2}
      },
      "type_name": "MeshingParams"
    },
    "models": [
      {"type": "Fluid"},
      {
        "entities": {"_count": 3, "_sample": ["leftWing", "fuselage", "rightWing"]},
        "type": "Wall"
      },
      {"type": "Freestream"}
    ],
    "operating_condition": {
      "alpha": {"units": "degree", "value": 10},
      "type_name": "AerospaceCondition",
      "velocity_magnitude": {"units": "m/s", "value": 100}
    },
    "outputs": [
      {
        "entities": {"_count": 3, "_sample": ["leftWing", "fuselage", "rightWing"]},
        "output_type": "SurfaceOutput"
      }
    ],
    "run_control": {"type_name": "RunControl"},
    "time_stepping": {"type_name": "Steady"}
  }
}

flow360 draft list --project-id prj-...

{
  "records": [
    {
      "id": "dft-...",
      "name": "Draft 1",
      "project_id": "prj-...",
      "solver_version": "release-25.9",
      "source_item_id": "geo-...",
      "source_item_type": "Geometry",
      "fork_case": false,
      "type": "Draft"
    }
  ]
}

flow360 open case-...

{
  "id": "case-...",
  "type": "Case",
  "url": "https://flow360.simulation.cloud/workbench/prj-...?id=case-...&type=Case",
  "opened": false
}

flow360 wait case-...

{
  "id": "case-...",
  "type": "Case",
  "status": "completed",
  "is_terminal": true,
  "is_success": true,
  "updated_at": "2026-04-01T12:30:00Z",
  "mesh_id": "vm-..."
}

Verification

rtk env PYTHONPATH=/tmp/flex-github-main/share/flow360-schema/src poetry run pytest \
  tests/test_cli_login.py::test_reload_user_config_preserves_runtime_validation_toggle \
  tests/v1/test_dev_flow360_params.py \
  tests/cli/test_cli_assets.py tests/cli/test_cli_draft.py tests/cli/test_cli_open.py \
  tests/cli/test_cli_resource_refs.py tests/cli/test_cli_simulation_summary.py \
  tests/cli/test_cli_wait.py tests/cli/test_cli_webapi_integration.py \
  tests/cli/test_workspace_webapi.py tests/cli/test_cli_auth_guidance.py \
  tests/test_lazy_imports.py -q
105 passed in 0.93s

Note

Medium Risk
Adds multiple new CLI entrypoints plus new lightweight V2 web-API wrappers and simulation-summary logic; risk is mainly around correctness of resource type parsing/URL resolution and state polling behavior, not security-critical changes.

Overview
Adds new Flow360 CLI resource-inspection surface. Introduces geometry, surface-mesh, volume-mesh, case, and draft command groups to fetch metadata (info), lifecycle state (state), and simulation payloads (simulation get), plus a summary view that compacts/prunes simulation JSON.

Adds cross-cutting CLI helpers and commands. Implements typed resource ref parsing (resource_refs), browser URL resolution + flow360 open, and generic polling via flow360 wait with proper exit codes/timeouts; also refactors project tree building to use a lightweight build_project_tree helper (shared with the SDK) instead of importing the full project SDK.

Improves auth/config ergonomics. Centralizes missing-API-key guidance messaging, adds user_config.configure_apikey + reload_user_config (used by login/configure callers), normalizes stored environment names, and broadens LazyFlow360Group exception passthrough for clean CLI exits.

Reviewed by Cursor Bugbot for commit 62f5159. Bugbot is set up for automated code reviews on this repo. Configure here.

Base automatically changed from maciej/codex/flow360-cli-pr1-5-lazy-import to main May 4, 2026 10:14
@maciej-flexcompute maciej-flexcompute force-pushed the maciej/codex/flow360-cli-pr2-resource-inspection branch from cd24dfe to ab7ff50 Compare May 4, 2026 10:26
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 4, 2026

Coverage report (flow360)

Click to see where and how coverage changed

FileStatementsMissingCoverageCoverage
(new stmts)
Lines missing
  flow360
  user_config.py 39, 43, 46, 106-112, 206-209, 252, 268
  flow360/cli
  api_set_func.py 3-6, 18
  app.py
  assets.py 39
  auth.py
  auth_guidance.py 37, 44
  browser_links.py 13, 18-40, 45-47, 51-71, 114, 128-131
  draft.py 19-20, 43
  open_resource.py 24-25
  project.py 113-114
  resource_refs.py 37
  resource_state.py 58, 71-72, 86-88
  simulation_summary.py 22, 71, 79, 91-92, 129, 156, 198, 257-262, 264, 268-269, 281, 336-337, 356, 364
  wait.py
  flow360/cloud
  http_util.py 40-46
  flow360/component
  interfaces.py
  project.py
  flow360/component/simulation/web
  asset_webapi.py 30, 46, 52
  project_tree.py
  workspace_webapi.py
Project Total  

This report was generated by python-coverage-comment-action

@maciej-flexcompute maciej-flexcompute marked this pull request as ready for review May 4, 2026 17:00
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 62f5159. Configure here.

cleaned[key] = cleaned_child
return dict(cleaned)
if isinstance(value, list):
return [_clean_empty(item) for item in value if item not in ({}, [], None)]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_clean_empty list branch keeps items that become empty

Low Severity

The list branch of _clean_empty filters items before recursively cleaning them, unlike the dict branch which filters after cleaning. A list item like {"nested": {}} passes the raw not in ({}, [], None) check, but _clean_empty then reduces it to {}, which remains in the output. The dict branch correctly applies _clean_empty(child) first and then checks if cleaned_child in ({}, [], None). The list comprehension on line 436 needs to clean items first and then filter the results.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 62f5159. Configure here.

Comment thread flow360/cli/assets.py

@case.command("get", hidden=True)
@click.argument("case_id")
def get_case_alias(case_id):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for?

Comment thread flow360/cli/assets.py
_emit_volume_mesh_info(volume_mesh_id)


@volume_mesh.command("state")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a way to not having to list one by one but define a base Group to define all the shared/common functions? It seems that a lot of the code here are very similar and just operates on different assets.

Comment thread flow360/cli/assets.py
_emit_asset_summary(CaseWebApi, case_id)


@case.group("simulation")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel "simulation" is not a very intuitive representation for simulation.json?

A side question I guess is how is this going to be used? The user has to know we have a dedicated function (non-Pydantic native) to deserialize the acquired JSON. (services.py --> validate_model())

f"{resource_ref.resource_type} {resource_ref.id} does not expose a projectId."
)
path = _get_workbench_path(project_id, resource_ref.id, resource_ref.resource_type)
url = Env.current.get_web_real_url(path)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is the entry to set the env here?

Comment thread flow360/cli/draft.py
@draft.command("get", hidden=True)
@click.argument("draft_id")
def get_draft_alias(draft_id):
"""Backward-compatible alias for draft info."""
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, is this AI hallucination?

Comment thread flow360/cli/draft.py
_emit_draft_list(project_id)


@draft.command("ls", hidden=True)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here?

previous_disable_level = logging.root.manager.disable
logging.disable(logging.WARNING)
try:
params_dict = SimulationParams._sanitize_params_dict( # pylint: disable=protected-access
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to go through updater and deserialization here? Why not show simulation_json as is?

from flow360.cli.resource_refs import ResourceRefError, parse_resource_ref

SUCCESS_STATES = {"completed", "processed"}
TERMINAL_STATES = SUCCESS_STATES | {"failed", "error", "deleted"}
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diverged etc?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants