topicforge 0.6.0__tar.gz → 0.6.2__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.
- {topicforge-0.6.0 → topicforge-0.6.2}/CHANGELOG.md +56 -1
- {topicforge-0.6.0 → topicforge-0.6.2}/PKG-INFO +10 -4
- {topicforge-0.6.0 → topicforge-0.6.2}/README.md +9 -3
- topicforge-0.6.2/docs/CLIENTS.md +280 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/docs/TESTING.md +3 -3
- topicforge-0.6.2/docs/VALIDATION.md +15 -0
- topicforge-0.6.2/plugin/LICENSE +21 -0
- topicforge-0.6.2/plugin/README.md +60 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/pyproject.toml +2 -2
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/__init__.py +1 -1
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/cdr_decoder.py +11 -1
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/composite.py +5 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_live/adapter.py +10 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_mock/fixtures.py +12 -5
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/models/schemas.py +30 -5
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/bag_service.py +17 -5
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/health.py +16 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/tools/handlers.py +6 -1
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/ros2/test_live_adapter.py +24 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_bag_omnisim_humble.py +16 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_cdr_decoder.py +7 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_health.py +56 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_live_sample_notes.py +3 -1
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_mock_adapter.py +10 -0
- topicforge-0.6.2/tests/test_version_consistency.py +150 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/.gitignore +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/LICENSE +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/docs/DDS_QUICKSTART.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/docs/TROUBLESHOOTING.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/docs/TUTORIEL.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/docs/dds-interop-matrix.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/examples/dds/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/agent_eval/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/__main__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/base.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/dds_helpers.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/dds_introspection.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/discovery_tracker.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/endpoints.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/lifecycle.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/metrics_buffer.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/qos_endpoints.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/qos_normalize.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/qos_scan.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/common/xtypes.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_cyclone/adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_dust/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_dust/adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_fast/adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_opendds/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/dds_opendds/adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_live/echo_parser.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_live/echo_stream.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_live/parsers.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_live/process_tree.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/adapters/ros2_mock/adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/config/settings.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/constants.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/models/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/server/app.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/bag_stats.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/factory.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/inspector.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/telemetry/client.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/conftest.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/ros2/Dockerfile +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/ros2/__init__.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/ros2/entrypoint.sh +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/ros2/publisher.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/ros2/run_bench.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_analyze_bag_multi_format.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_bag_service.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_bag_stats.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_composite_adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_config.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_cyclone_adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dds_cross_vendor.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dds_helpers.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dds_inactive_reason.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dds_introspection.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dds_qos_normalization.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dds_schemas.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_discovery_tracker.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_dust_adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_echo_parser.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_echo_stream.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_endpoints.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_factory.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_fast_adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_history_and_hints.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_honest_outputs.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_inspector.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_lifecycle_buffer.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_live_adapter_graph.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_metrics_buffer.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_opendds_adapter.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_process_tree.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_qos_scan.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_sample_options.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_telemetry.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_tools_integration.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_topic_metrics.py +0 -0
- {topicforge-0.6.0 → topicforge-0.6.2}/tests/test_xtypes.py +0 -0
|
@@ -5,6 +5,59 @@ All notable changes to TopicForge are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.6.2] - 2026-10-06
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- [docs/CLIENTS.md](docs/CLIENTS.md): ready-to-paste local stdio configs for Claude
|
|
15
|
+
Code, Claude Desktop, Cursor, VS Code / Copilot, Windsurf / Devin, Cline, Roo Code,
|
|
16
|
+
Continue, Zed, JetBrains, Codex CLI, Gemini CLI, Goose, LM Studio, Amazon Q / Kiro
|
|
17
|
+
and Warp, with one-click install links for Cursor and VS Code, and a note on why
|
|
18
|
+
ChatGPT web and a hosted endpoint are out of scope. Linked from the README.
|
|
19
|
+
- `gemini-extension.json` and `GEMINI.md`: install in Gemini CLI with
|
|
20
|
+
`gemini extensions install https://github.com/yaniswav/TopicForge`.
|
|
21
|
+
- `mcpb/`: an MCP Bundle (`uv` server type, no vendored dependencies) for one-click
|
|
22
|
+
Claude Desktop install. The release workflow packs it and attaches it to the GitHub
|
|
23
|
+
release.
|
|
24
|
+
- `publish.yml` publishes `server.json` to the official MCP Registry after the PyPI
|
|
25
|
+
publish succeeds (pinned, checksum-verified `mcp-publisher`, GitHub OIDC), so the
|
|
26
|
+
registry no longer lags behind PyPI.
|
|
27
|
+
- `tests/test_version_consistency.py`: fails when the package, `server.json`, plugin,
|
|
28
|
+
Gemini extension, MCP Bundle and the pins in `docs/CLIENTS.md` disagree.
|
|
29
|
+
|
|
30
|
+
## [0.6.1] - 2026-10-06
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- A Claude plugin bundle in `plugin/`: the TopicForge MCP server (started with
|
|
35
|
+
`uvx`, version pinned) and two skills, `diagnose-dds-bus` and
|
|
36
|
+
`inspect-ros2-robot`. Install with `claude plugin marketplace add
|
|
37
|
+
yaniswav/TopicForge`.
|
|
38
|
+
- [docs/VALIDATION.md](docs/VALIDATION.md): the OmniSim team's external validation
|
|
39
|
+
against a simulated robot's ground truth, published as approved by them.
|
|
40
|
+
- `MessageSample.recorded_ns`: the bag record time of a `peek_bag_samples` sample.
|
|
41
|
+
- `MessageSample.stamp_source` value `recorded`: `timestamp_ns` is the bag record
|
|
42
|
+
time because the message has no top-level header.
|
|
43
|
+
- `HealthReport.sim_clock_published` (live ROS 2 only, else null): whether `/clock`
|
|
44
|
+
has a publisher, a hint that nodes may run on simulated time.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
|
|
48
|
+
- `peek_bag_samples` `timestamp_ns` is now `header.stamp` when the message has a
|
|
49
|
+
top-level header (`stamp_source` `header`), else the bag record time (`stamp_source`
|
|
50
|
+
`recorded`). It used to be the bag record time for every message, with
|
|
51
|
+
`stamp_source` null. The tool description says it returns the first `count`
|
|
52
|
+
messages, not the last.
|
|
53
|
+
- CI: `actions/checkout` v7, `actions/setup-python` v7, `actions/cache` v6. Jobs
|
|
54
|
+
that run tests or the demos use `ubuntu-24.04` instead of `ubuntu-latest`.
|
|
55
|
+
|
|
56
|
+
### Fixed
|
|
57
|
+
|
|
58
|
+
- `peek_bag_samples` payloads no longer carry `__msgtype__` in every nested object
|
|
59
|
+
nor a top-level `_msgtype`; the message type stays at the sample level.
|
|
60
|
+
|
|
8
61
|
## [0.6.0] - 2026-10-05
|
|
9
62
|
|
|
10
63
|
`sample_messages` (live) rewritten after the OmniSim team's report (count
|
|
@@ -1263,7 +1316,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1263
1316
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1264
1317
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1265
1318
|
|
|
1266
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.6.
|
|
1319
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.6.2...HEAD
|
|
1320
|
+
[0.6.2]: https://github.com/yaniswav/TopicForge/compare/v0.6.1...v0.6.2
|
|
1321
|
+
[0.6.1]: https://github.com/yaniswav/TopicForge/compare/v0.6.0...v0.6.1
|
|
1267
1322
|
[0.6.0]: https://github.com/yaniswav/TopicForge/compare/v0.5.6...v0.6.0
|
|
1268
1323
|
[0.5.6]: https://github.com/yaniswav/TopicForge/compare/v0.5.5...v0.5.6
|
|
1269
1324
|
[0.5.5]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...v0.5.5
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/yaniswav/TopicForge
|
|
6
6
|
Project-URL: Repository, https://github.com/yaniswav/TopicForge
|
|
@@ -98,7 +98,11 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
-
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
|
|
101
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
|
|
102
|
+
|
|
103
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlW2Rkc109PTAuNi4yIiwidG9waWNmb3JnZSJdLCJlbnYiOnsiVE9QSUNGT1JHRV9NT0RFIjoiYXV0byIsIlRPUElDRk9SR0VfRERTX0JBQ0tFTkQiOiJjeWNsb25lIiwiVE9QSUNGT1JHRV9ERFNfRE9NQUlOX0lEIjoiMCJ9fQ%3D%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%5Bdds%5D%3D%3D0.6.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
104
|
+
|
|
105
|
+
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
102
106
|
|
|
103
107
|
## Tools
|
|
104
108
|
|
|
@@ -158,6 +162,8 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
158
162
|
|
|
159
163
|
## Limitations
|
|
160
164
|
|
|
165
|
+
External validation against a simulated robot's ground truth: [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
166
|
+
|
|
161
167
|
- DDS validation is partial. The Cyclone adapter has run against a real bus, with Cyclone and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
|
|
162
168
|
- User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
|
|
163
169
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
@@ -166,7 +172,7 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
166
172
|
- DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
|
|
167
173
|
- Fast DDS serves no `list_endpoints`.
|
|
168
174
|
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..45, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation) or 0 for headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
169
|
-
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, and is served only by the ROS2 CLI adapter or the mock; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output.
|
|
175
|
+
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`) or, without a top-level header, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
170
176
|
- Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
|
|
171
177
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
172
178
|
|
|
@@ -189,7 +195,7 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
189
195
|
| `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
|
|
190
196
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
191
197
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
192
|
-
| `version` | `"0.6.
|
|
198
|
+
| `version` | `"0.6.2"` | TopicForge server version |
|
|
193
199
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
194
200
|
| `success` | `true` | Whether the handler returned or raised |
|
|
195
201
|
|
|
@@ -38,7 +38,11 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
38
38
|
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
|
|
41
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
|
|
42
|
+
|
|
43
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlW2Rkc109PTAuNi4yIiwidG9waWNmb3JnZSJdLCJlbnYiOnsiVE9QSUNGT1JHRV9NT0RFIjoiYXV0byIsIlRPUElDRk9SR0VfRERTX0JBQ0tFTkQiOiJjeWNsb25lIiwiVE9QSUNGT1JHRV9ERFNfRE9NQUlOX0lEIjoiMCJ9fQ%3D%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%5Bdds%5D%3D%3D0.6.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
44
|
+
|
|
45
|
+
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
42
46
|
|
|
43
47
|
## Tools
|
|
44
48
|
|
|
@@ -98,6 +102,8 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
98
102
|
|
|
99
103
|
## Limitations
|
|
100
104
|
|
|
105
|
+
External validation against a simulated robot's ground truth: [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
106
|
+
|
|
101
107
|
- DDS validation is partial. The Cyclone adapter has run against a real bus, with Cyclone and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`). The Fast DDS adapter has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
|
|
102
108
|
- User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
|
|
103
109
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
@@ -106,7 +112,7 @@ Samples with comments are in [`.env.example`](.env.example). Any invalid value s
|
|
|
106
112
|
- DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
|
|
107
113
|
- Fast DDS serves no `list_endpoints`.
|
|
108
114
|
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..45, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation) or 0 for headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
109
|
-
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, and is served only by the ROS2 CLI adapter or the mock; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output.
|
|
115
|
+
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`) or, without a top-level header, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
110
116
|
- Synchronous handlers: the tools run on the MCP event loop; on Windows a hung `ros2` launcher can block the server.
|
|
111
117
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
112
118
|
|
|
@@ -129,7 +135,7 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
129
135
|
| `tool_name` | `"list_topics"` | One of the twelve tools, never argument values |
|
|
130
136
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
131
137
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
132
|
-
| `version` | `"0.6.
|
|
138
|
+
| `version` | `"0.6.2"` | TopicForge server version |
|
|
133
139
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
134
140
|
| `success` | `true` | Whether the handler returned or raised |
|
|
135
141
|
|
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
# Using TopicForge from your MCP client
|
|
2
|
+
|
|
3
|
+
Last checked 2026-10-06.
|
|
4
|
+
|
|
5
|
+
TopicForge (ROS 2 / DDS) is a local, read-only MCP server that speaks stdio.
|
|
6
|
+
Every client below launches it as a child process with the same command:
|
|
7
|
+
|
|
8
|
+
```
|
|
9
|
+
uvx --from "topicforge[dds]==0.6.2" topicforge
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
and the same three environment variables:
|
|
13
|
+
|
|
14
|
+
| Variable | Value | Meaning |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| `TOPICFORGE_MODE` | `auto` | Use ROS 2 if it is on PATH, else fixtures. |
|
|
17
|
+
| `TOPICFORGE_DDS_BACKEND` | `cyclone` | Join the DDS bus as a read-only participant (the `[dds]` extra provides the binding). Use `mock` for a demo with no robot. |
|
|
18
|
+
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | The one DDS domain to observe (0-232). Fixed at startup: restart the server to change it. |
|
|
19
|
+
|
|
20
|
+
The registry name is `io.github.yaniswav/topicforge`. Where a client asks for a
|
|
21
|
+
display name, use **TopicForge (ROS 2 / DDS)**: an unrelated SEO product is also
|
|
22
|
+
called TopicForge in the MCP registries.
|
|
23
|
+
|
|
24
|
+
The version pin (`==0.6.2`) is moved on every release together with the other
|
|
25
|
+
version strings; a test keeps them in sync.
|
|
26
|
+
|
|
27
|
+
## Before you start
|
|
28
|
+
|
|
29
|
+
- **uv.** `uvx` ships with [uv](https://docs.astral.sh/uv/getting-started/installation/).
|
|
30
|
+
Install it once, then check `uvx --version` in a new terminal.
|
|
31
|
+
- **Desktop apps do not inherit your shell PATH.** On Windows especially, an app
|
|
32
|
+
started from the Start menu may not see `uvx`. If a client reports "command not
|
|
33
|
+
found", put the absolute path in `command` (find it with `where uvx` on Windows
|
|
34
|
+
or `which uvx` elsewhere, for example `C:/Users/you/.local/bin/uvx.exe`;
|
|
35
|
+
forward slashes are fine in JSON).
|
|
36
|
+
- **First start is slow.** `uvx` downloads the package and the Cyclone DDS binding
|
|
37
|
+
the first time. Clients with a short startup timeout may need a retry or a
|
|
38
|
+
larger timeout (noted per client below).
|
|
39
|
+
- **If resolution fails on a very new Python** (the Cyclone binding ships wheels for Python 3.10 to 3.13 only), add `"--python", "3.12"` before
|
|
40
|
+
`"--from"` in the args.
|
|
41
|
+
- **pip alternative.** `pip install "topicforge[dds]==0.6.2"`, then use
|
|
42
|
+
`"command": "topicforge"` (or `"command": "python", "args": ["-m", "topicforge"]`)
|
|
43
|
+
with no `args` for uvx. Use the absolute path of the binary if the client does
|
|
44
|
+
not see your venv.
|
|
45
|
+
- **No robot at hand?** Set `TOPICFORGE_DDS_BACKEND` to `mock` to try the tools
|
|
46
|
+
against deterministic fixtures.
|
|
47
|
+
|
|
48
|
+
## What cannot work: ChatGPT and hosted endpoints
|
|
49
|
+
|
|
50
|
+
ChatGPT on the web and in the desktop app cannot run local stdio MCP servers; it
|
|
51
|
+
only talks to remote MCP endpoints. Use Codex (below), which runs local stdio
|
|
52
|
+
servers, or any other client on this page.
|
|
53
|
+
|
|
54
|
+
A hosted TopicForge endpoint is out of scope on purpose. TopicForge has to sit on
|
|
55
|
+
the same network as the DDS participants it observes, and exposing a robot bus to
|
|
56
|
+
the internet would contradict the read-only safety promise the product is built
|
|
57
|
+
on.
|
|
58
|
+
|
|
59
|
+
## The common JSON shape
|
|
60
|
+
|
|
61
|
+
Most clients read an `mcpServers` object like this one. The per-client sections
|
|
62
|
+
say where the file lives and what differs.
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"mcpServers": {
|
|
67
|
+
"topicforge": {
|
|
68
|
+
"command": "uvx",
|
|
69
|
+
"args": ["--from", "topicforge[dds]==0.6.2", "topicforge"],
|
|
70
|
+
"env": {
|
|
71
|
+
"TOPICFORGE_MODE": "auto",
|
|
72
|
+
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
73
|
+
"TOPICFORGE_DDS_DOMAIN_ID": "0"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Claude Code
|
|
81
|
+
|
|
82
|
+
Plugin (server plus two skills, asks for backend and domain at install):
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
claude plugin marketplace add yaniswav/TopicForge
|
|
86
|
+
claude plugin install topicforge@topicforge
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Or just the server:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
claude mcp add topicforge --env TOPICFORGE_MODE=auto --env TOPICFORGE_DDS_BACKEND=cyclone --env TOPICFORGE_DDS_DOMAIN_ID=0 -- uvx --from "topicforge[dds]==0.6.2" topicforge
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Claude Desktop
|
|
96
|
+
|
|
97
|
+
From 0.6.2: download `topicforge-<version>.mcpb` from the latest GitHub release and double-click it. Claude Desktop asks for the DDS backend and domain id and manages Python and dependencies itself through uv.
|
|
98
|
+
|
|
99
|
+
Or edit the config file by hand: `claude_desktop_config.json` (Settings > Developer > Edit Config):
|
|
100
|
+
`%APPDATA%\Claude\claude_desktop_config.json` on Windows,
|
|
101
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS. Paste
|
|
102
|
+
the common JSON shape above and restart the app completely. On Windows, if
|
|
103
|
+
`uvx` is not found, use its absolute path as `command`.
|
|
104
|
+
|
|
105
|
+
## Cursor
|
|
106
|
+
|
|
107
|
+
The one-click links in this page and in the README are https redirects, because GitHub and PyPI strip `cursor://` and `vscode:` links. Only the raw schemes are documented by Cursor and VS Code; the https forms (`cursor.com/install-mcp`, `vscode.dev/redirect/mcp/install`) were observed to redirect correctly on 2026-10-06 but are not in the vendors' docs (unverified, last checked 2026-10-06). The raw links are given as a fallback.
|
|
108
|
+
|
|
109
|
+
One click: [Add TopicForge to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlW2Rkc109PTAuNi4yIiwidG9waWNmb3JnZSJdLCJlbnYiOnsiVE9QSUNGT1JHRV9NT0RFIjoiYXV0byIsIlRPUElDRk9SR0VfRERTX0JBQ0tFTkQiOiJjeWNsb25lIiwiVE9QSUNGT1JHRV9ERFNfRE9NQUlOX0lEIjoiMCJ9fQ%3D%3D)
|
|
110
|
+
|
|
111
|
+
If that page does not open Cursor, use the raw deeplink: `cursor://anysphere.cursor-deeplink/mcp/install?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlW2Rkc109PTAuNi4yIiwidG9waWNmb3JnZSJdLCJlbnYiOnsiVE9QSUNGT1JHRV9NT0RFIjoiYXV0byIsIlRPUElDRk9SR0VfRERTX0JBQ0tFTkQiOiJjeWNsb25lIiwiVE9QSUNGT1JHRV9ERFNfRE9NQUlOX0lEIjoiMCJ9fQ==`
|
|
112
|
+
|
|
113
|
+
Or paste the common JSON shape into `~/.cursor/mcp.json` (all projects) or
|
|
114
|
+
`.cursor/mcp.json` (one project).
|
|
115
|
+
|
|
116
|
+
## VS Code / GitHub Copilot
|
|
117
|
+
|
|
118
|
+
One click: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%5Bdds%5D%3D%3D0.6.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D)
|
|
119
|
+
|
|
120
|
+
Fallback, the raw URL handler: `vscode:mcp/install?%7B%22name%22%3A%22topicforge%22%2C%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%5Bdds%5D%3D%3D0.6.2%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D`
|
|
121
|
+
|
|
122
|
+
Or put this in `.vscode/mcp.json` (the key is `servers`, not `mcpServers`), or run
|
|
123
|
+
"MCP: Add Server" from the Command Palette:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"servers": {
|
|
128
|
+
"topicforge": {
|
|
129
|
+
"type": "stdio",
|
|
130
|
+
"command": "uvx",
|
|
131
|
+
"args": ["--from", "topicforge[dds]==0.6.2", "topicforge"],
|
|
132
|
+
"env": {
|
|
133
|
+
"TOPICFORGE_MODE": "auto",
|
|
134
|
+
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
135
|
+
"TOPICFORGE_DDS_DOMAIN_ID": "0"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## Windsurf / Devin (Cascade)
|
|
143
|
+
|
|
144
|
+
Windsurf's docs now redirect to Devin's (unverified, last checked 2026-10-06). Edit `%APPDATA%\devin\mcp_config.json`
|
|
145
|
+
(Windows) or `~/.config/devin/mcp_config.json` (macOS, Linux) and paste the common
|
|
146
|
+
JSON shape. Older installs may still use `~/.codeium/windsurf/mcp_config.json` (unverified, last checked 2026-10-06).
|
|
147
|
+
|
|
148
|
+
## Cline
|
|
149
|
+
|
|
150
|
+
Open the MCP Servers icon in the Cline panel, then Configure, then "Configure MCP
|
|
151
|
+
Servers", and paste the common JSON shape (the file is
|
|
152
|
+
`~/.cline/data/settings/cline_mcp_settings.json` per docs.cline.bot on 2026-10-06; older VS Code installs kept it elsewhere, unverified). Optional fields such as
|
|
153
|
+
`"disabled": false` and `"autoApprove": []` are accepted.
|
|
154
|
+
|
|
155
|
+
## Roo Code
|
|
156
|
+
|
|
157
|
+
Project file `.roo/mcp.json` (takes precedence) or the global `mcp_settings.json`
|
|
158
|
+
opened from the MCP view. Paste the common JSON shape. Roo's docs say Windows
|
|
159
|
+
needs a `cmd /c` wrapper for `npx`; for `uvx`, try it directly first and fall back
|
|
160
|
+
to `"command": "cmd", "args": ["/c", "uvx", "--from", "topicforge[dds]==0.6.2",
|
|
161
|
+
"topicforge"]` if it fails to start.
|
|
162
|
+
|
|
163
|
+
## Continue
|
|
164
|
+
|
|
165
|
+
Create `.continue/mcpServers/topicforge.yaml` (MCP works in agent mode only):
|
|
166
|
+
|
|
167
|
+
```yaml
|
|
168
|
+
name: TopicForge (ROS 2 / DDS)
|
|
169
|
+
version: 0.0.1
|
|
170
|
+
schema: v1
|
|
171
|
+
mcpServers:
|
|
172
|
+
- name: topicforge
|
|
173
|
+
type: stdio
|
|
174
|
+
command: uvx
|
|
175
|
+
args:
|
|
176
|
+
- "--from"
|
|
177
|
+
- "topicforge[dds]==0.6.2"
|
|
178
|
+
- "topicforge"
|
|
179
|
+
env:
|
|
180
|
+
TOPICFORGE_MODE: auto
|
|
181
|
+
TOPICFORGE_DDS_BACKEND: cyclone
|
|
182
|
+
TOPICFORGE_DDS_DOMAIN_ID: "0"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## Zed
|
|
186
|
+
|
|
187
|
+
In `settings.json`, under `context_servers`:
|
|
188
|
+
|
|
189
|
+
```json
|
|
190
|
+
{
|
|
191
|
+
"context_servers": {
|
|
192
|
+
"topicforge": {
|
|
193
|
+
"command": "uvx",
|
|
194
|
+
"args": ["--from", "topicforge[dds]==0.6.2", "topicforge"],
|
|
195
|
+
"env": {
|
|
196
|
+
"TOPICFORGE_MODE": "auto",
|
|
197
|
+
"TOPICFORGE_DDS_BACKEND": "cyclone",
|
|
198
|
+
"TOPICFORGE_DDS_DOMAIN_ID": "0"
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## JetBrains AI Assistant
|
|
206
|
+
|
|
207
|
+
Settings > Tools > AI Assistant > Model Context Protocol (MCP) > Add, then paste the
|
|
208
|
+
common JSON shape. You can also use "Import from Claude" if you already set it up
|
|
209
|
+
there.
|
|
210
|
+
|
|
211
|
+
## OpenAI Codex CLI
|
|
212
|
+
|
|
213
|
+
Codex runs local stdio servers (CLI, IDE extension and desktop app share
|
|
214
|
+
`~/.codex/config.toml`). The default startup timeout is 10 s, which a first `uvx`
|
|
215
|
+
run with the dds extra can exceed, so set it to 60 s:
|
|
216
|
+
|
|
217
|
+
```toml
|
|
218
|
+
[mcp_servers.topicforge]
|
|
219
|
+
command = "uvx"
|
|
220
|
+
args = ["--from", "topicforge[dds]==0.6.2", "topicforge"]
|
|
221
|
+
startup_timeout_sec = 60
|
|
222
|
+
|
|
223
|
+
[mcp_servers.topicforge.env]
|
|
224
|
+
TOPICFORGE_MODE = "auto"
|
|
225
|
+
TOPICFORGE_DDS_BACKEND = "cyclone"
|
|
226
|
+
TOPICFORGE_DDS_DOMAIN_ID = "0"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Gemini CLI
|
|
230
|
+
|
|
231
|
+
As an extension (reads `gemini-extension.json` from this repository):
|
|
232
|
+
|
|
233
|
+
```
|
|
234
|
+
gemini extensions install https://github.com/yaniswav/TopicForge
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Or paste the common JSON shape into `~/.gemini/settings.json` (or
|
|
238
|
+
`.gemini/settings.json` in a project). Add `"timeout": 60000` (milliseconds) for
|
|
239
|
+
the slow first start, and do not set `"trust": true`.
|
|
240
|
+
|
|
241
|
+
## Goose
|
|
242
|
+
|
|
243
|
+
In `~/.config/goose/config.yaml` (macOS, Linux; the Windows location is unverified, last checked 2026-10-06), or via the extensions screen:
|
|
244
|
+
|
|
245
|
+
```yaml
|
|
246
|
+
extensions:
|
|
247
|
+
topicforge:
|
|
248
|
+
name: TopicForge (ROS 2 / DDS)
|
|
249
|
+
type: stdio
|
|
250
|
+
cmd: uvx
|
|
251
|
+
args: [--from, "topicforge[dds]==0.6.2", topicforge]
|
|
252
|
+
enabled: true
|
|
253
|
+
envs: { "TOPICFORGE_MODE": "auto", "TOPICFORGE_DDS_BACKEND": "cyclone", "TOPICFORGE_DDS_DOMAIN_ID": "0" }
|
|
254
|
+
timeout: 300
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## LM Studio
|
|
258
|
+
|
|
259
|
+
Program tab > Install > Edit `mcp.json` (same notation as Cursor), then paste the
|
|
260
|
+
common JSON shape. LM Studio's docs only show remote examples; local command
|
|
261
|
+
servers follow the Cursor notation but are not documented explicitly. Small
|
|
262
|
+
local models may struggle with twelve tools.
|
|
263
|
+
|
|
264
|
+
## Amazon Q Developer / Kiro
|
|
265
|
+
|
|
266
|
+
Kiro: `~/.kiro/settings/mcp.json` (user) or `.kiro/settings/mcp.json` (workspace,
|
|
267
|
+
takes precedence); paste the common JSON shape. Amazon Q Developer CLI uses the
|
|
268
|
+
same shape in `~/.aws/amazonq/mcp.json` (the Amazon Q path and the move to Kiro CLI are unverified, last checked 2026-10-06).
|
|
269
|
+
|
|
270
|
+
## Warp
|
|
271
|
+
|
|
272
|
+
Settings > MCP > "+ Add", then paste the common JSON shape (unverified, last checked 2026-10-06: the Warp docs page could not be fetched).
|
|
273
|
+
|
|
274
|
+
## Troubleshooting
|
|
275
|
+
|
|
276
|
+
- Run the command by hand first: `uvx --from "topicforge[dds]==0.6.2" topicforge --version`.
|
|
277
|
+
- Server starts but sees no DDS participants: check the domain id, and that the
|
|
278
|
+
client machine is on the same network as the robot (DDS discovery uses
|
|
279
|
+
multicast UDP). Ask the assistant to call `health_check`.
|
|
280
|
+
- More in [TROUBLESHOOTING.md](TROUBLESHOOTING.md) and [TESTING.md](TESTING.md).
|
|
@@ -15,7 +15,7 @@ How to get a working ROS2 environment to point TopicForge at, and how to wire it
|
|
|
15
15
|
```bash
|
|
16
16
|
python -m venv .venv && source .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1
|
|
17
17
|
pip install topicforge
|
|
18
|
-
python -m topicforge --version # -> topicforge 0.6.
|
|
18
|
+
python -m topicforge --version # -> topicforge 0.6.2
|
|
19
19
|
TOPICFORGE_MODE=mock python -m topicforge # blocks on stdio; MCP clients spawn it
|
|
20
20
|
```
|
|
21
21
|
|
|
@@ -41,7 +41,7 @@ python3 -m venv ~/topicforge-venv && source ~/topicforge-venv/bin/activate
|
|
|
41
41
|
pip install topicforge # Ubuntu 22.04 ships Python 3.10, which is enough
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
Then run live mode with three terminals. In the first, `ros2 run demo_nodes_cpp talker` publishes `/chatter` at about 1 Hz. In the second, `ros2 topic list` should show `/chatter`. In the third, source ROS2, activate the venv and run `TOPICFORGE_MODE=live python -m topicforge`. The startup line reads `topicforge 0.6.
|
|
44
|
+
Then run live mode with three terminals. In the first, `ros2 run demo_nodes_cpp talker` publishes `/chatter` at about 1 Hz. In the second, `ros2 topic list` should show `/chatter`. In the third, source ROS2, activate the venv and run `TOPICFORGE_MODE=live python -m topicforge`. The startup line reads `topicforge 0.6.2 ready (mode=live, requested_mode=live, adapter=ros2_cli, telemetry=off)`; `mode=mock` means `ros2` was not found and the server fell back to fixtures.
|
|
45
45
|
|
|
46
46
|
## Linux native
|
|
47
47
|
|
|
@@ -102,4 +102,4 @@ TopicForge speaks MCP over stdio; any compliant client can spawn it with a comma
|
|
|
102
102
|
}
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
That is the Claude Desktop shape (`claude_desktop_config.json`); restart the app and the twelve tools appear under the hammer icon. For Claude Code run `claude mcp add topicforge -- topicforge`. Cursor, Continue and Cline accept the same stdio config. If the `topicforge` script is not on PATH, use `"command": "python", "args": ["-m", "topicforge"]`, or the absolute path of the binary inside your venv: desktop clients do not inherit your shell's PATH or venv activation.
|
|
105
|
+
That is the Claude Desktop shape (`claude_desktop_config.json`); restart the app and the twelve tools appear under the hammer icon. For Claude Code run `claude mcp add topicforge -- topicforge`. Cursor, Continue and Cline accept the same stdio config; ready-to-paste configs for every major client are in [`CLIENTS.md`](CLIENTS.md). If the `topicforge` script is not on PATH, use `"command": "python", "args": ["-m", "topicforge"]`, or the absolute path of the binary inside your venv: desktop clients do not inherit your shell's PATH or venv activation.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# External validation
|
|
2
|
+
|
|
3
|
+
## OmniSim (October 2026)
|
|
4
|
+
|
|
5
|
+
The OmniSim team ran TopicForge against a simulated robot and compared its answers with the simulator's ground truth.
|
|
6
|
+
|
|
7
|
+
Setup: [OmniSim v9.1.3](https://github.com/omnilink-tech/omnisim/releases/tag/v9.1.3), a simulated Clearpath Husky with a 541-beam SICK LMS111 lidar in a room with walls at known positions, robot stationary. ROS 2 Humble on Ubuntu 22.04 (WSL2), Fast DDS. TopicForge 0.5.3 in live mode for the ROS 2 tools, and 0.5.5 with the Cyclone backend for the DDS tools.
|
|
8
|
+
|
|
9
|
+
What matched:
|
|
10
|
+
- Every lidar scan TopicForge sampled was bit-identical to the simulator's raw data on the beams it delivered.
|
|
11
|
+
- `list_topics` and `get_topic_info` returned the same names, types and publisher/subscriber counts as the `ros2` CLI.
|
|
12
|
+
- `analyze_bag` returned the same duration and message counts as `ros2 bag info` and an independent read of the bag.
|
|
13
|
+
- With the Cyclone backend, TopicForge saw all seven Fast DDS participants of the ROS 2 graph without extra configuration, and reported participants leaving within about 10 ms of the actual time.
|
|
14
|
+
|
|
15
|
+
What it found: seven discrepancies on TopicForge's side, including lidar arrays cut at 128 values, one message per `sample_messages` call, zero timestamps on simulated time, and Humble bags that `peek_bag_samples` could not decode. All seven are addressed in 0.5.6 and 0.6.0 (full arrays through a `max_array_length` option) and covered by a ROS 2 Humble/Jazzy test bench; the OmniSim run itself has not been repeated on these versions yet. The bag recorded during the run is part of TopicForge's test fixtures (`tests/fixtures/bags/omnisim_humble/`).
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yanis ETHVIGNOT
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# TopicForge plugin for Claude
|
|
2
|
+
|
|
3
|
+
TopicForge is a read-only MCP server for ROS 2 and DDS. This plugin bundles the server with two short skills so Claude can inspect a robot stack and diagnose a DDS bus. It cannot publish, command a robot, or change QoS: the server has no write path.
|
|
4
|
+
|
|
5
|
+
## What is included
|
|
6
|
+
|
|
7
|
+
- MCP server `topicforge`, started with `uvx` from the `topicforge` package on PyPI (version pinned to 0.6.2). It exposes twelve read-only tools: `health_check`, `list_topics`, `get_topic_info`, `sample_messages`, `analyze_bag`, `peek_bag_samples`, `list_participants`, `list_endpoints`, `detect_qos_mismatches`, `participant_events`, `peek_dds_samples`, `topic_metrics`.
|
|
8
|
+
- Skill `diagnose-dds-bus`: what to call, and in which order, when nodes do not talk, a topic gets no data, or a node crashed or restarts.
|
|
9
|
+
- Skill `inspect-ros2-robot`: listing topics, sampling messages and reading bags, including large arrays and simulation time.
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
- [`uv`](https://docs.astral.sh/uv/) on PATH, which provides `uvx`. The first start downloads the package and the Cyclone DDS binding, so it can take a little while.
|
|
14
|
+
- A local MCP server: it runs on your machine, in Claude Code and in Cowork sessions that run on your computer. It does not run in claude.ai chat.
|
|
15
|
+
- ROS 2 on PATH is optional. Without it the ROS 2 tools fall back to fixtures (a fictional demo robot) and `health_check` reports `mode: mock`. The DDS tools work without ROS 2.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
From a marketplace, once the plugin is listed:
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
/plugin install topicforge
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Meanwhile, from this repository (Claude Code):
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
claude plugin marketplace add yaniswav/TopicForge
|
|
29
|
+
claude plugin install topicforge@topicforge
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or load the folder for one session: `claude --plugin-dir ./plugin`.
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
|
|
36
|
+
Claude Code asks for two options when the plugin is enabled:
|
|
37
|
+
|
|
38
|
+
| Option | Default | Meaning |
|
|
39
|
+
| --- | --- | --- |
|
|
40
|
+
| `dds_backend` | `cyclone` | `cyclone` joins the DDS bus as a read-only participant. `mock` serves fixtures. `auto` picks the best available. |
|
|
41
|
+
| `dds_domain_id` | `0` | The one DDS domain observed (0 to 232). Fixed at startup. |
|
|
42
|
+
|
|
43
|
+
The server runs with `TOPICFORGE_MODE=auto`: live ROS 2 when `ros2` is on PATH, fixtures otherwise. Cowork does not prompt for options and uses the defaults. To use other settings there, edit `.mcp.json`.
|
|
44
|
+
|
|
45
|
+
Without DDS, set `dds_backend` to `mock`, or use the standalone install `uvx topicforge`.
|
|
46
|
+
|
|
47
|
+
## Privacy
|
|
48
|
+
|
|
49
|
+
The server reads the local ROS 2 graph and the DDS discovery traffic on the machine and network it runs on, and returns the result to the Claude session. It makes no outbound network calls. Anonymous telemetry exists but is off by default and is not enabled by this plugin. See the main [README](https://github.com/yaniswav/TopicForge#telemetry) for the telemetry contract and [SECURITY.md](https://github.com/yaniswav/TopicForge/blob/main/SECURITY.md).
|
|
50
|
+
|
|
51
|
+
## Limits
|
|
52
|
+
|
|
53
|
+
TopicForge sees what DDS discovery announces. It cannot see whether data flows, a writer that is alive but silent, other DDS domains, or secured (DDS Security) endpoints. The skills tell Claude to say so.
|
|
54
|
+
|
|
55
|
+
## Links
|
|
56
|
+
|
|
57
|
+
- Project: https://github.com/yaniswav/TopicForge
|
|
58
|
+
- Site: https://topicforge.horizonvista.xyz
|
|
59
|
+
- Issues: https://github.com/yaniswav/TopicForge/issues
|
|
60
|
+
- License: MIT
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "topicforge"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.2"
|
|
8
8
|
description = "ROS Topic Inspector & Bag Analyzer MCP server for AI agents"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -81,7 +81,7 @@ packages = ["src/topicforge"]
|
|
|
81
81
|
|
|
82
82
|
[tool.hatch.build.targets.sdist]
|
|
83
83
|
include = ["src/topicforge", "tests", "docs", "README.md", "CHANGELOG.md", "LICENSE", "pyproject.toml"]
|
|
84
|
-
exclude = [".claude", "docs/projet-file/**", "docs/projet-file", "tests/fixtures/bags/**", "CLAUDE.md", "*.pdf", ".pytest_cache", "pytest-cache-files-*", "pro"]
|
|
84
|
+
exclude = [".claude", "docs/projet-file/**", "docs/projet-file", "tests/fixtures/bags/**", "CLAUDE.md", "*.pdf", ".pytest_cache", "pytest-cache-files-*", "pro", "mcpb"]
|
|
85
85
|
|
|
86
86
|
[tool.pytest.ini_options]
|
|
87
87
|
testpaths = ["tests"]
|
|
@@ -59,8 +59,18 @@ def iter_field_names(sample: Any) -> list[str]:
|
|
|
59
59
|
"""List field names on a dynamic-type sample.
|
|
60
60
|
|
|
61
61
|
Tries `__dataclass_fields__`, `__fields__`, `__slots__`, then public
|
|
62
|
-
`__dict__` keys. Returns an empty list when none apply.
|
|
62
|
+
`__dict__` keys. Returns an empty list when none apply. Dunder names
|
|
63
|
+
such as the `__msgtype__` marker that `rosbags` messages carry are
|
|
64
|
+
not data and are left out.
|
|
63
65
|
"""
|
|
66
|
+
return [name for name in _raw_field_names(sample) if not _is_dunder(name)]
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_dunder(name: str) -> bool:
|
|
70
|
+
return name.startswith("__") and name.endswith("__")
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _raw_field_names(sample: Any) -> list[str]:
|
|
64
74
|
fields = getattr(sample, "__dataclass_fields__", None)
|
|
65
75
|
if fields:
|
|
66
76
|
return list(fields)
|
|
@@ -81,6 +81,11 @@ class CompositeAdapter:
|
|
|
81
81
|
def analyze_bag(self, path: str) -> BagAnalysis:
|
|
82
82
|
return self._ros.analyze_bag(path)
|
|
83
83
|
|
|
84
|
+
def sim_clock_published(self) -> bool | None:
|
|
85
|
+
"""The ROS 2 half's `/clock` probe, `None` when it has none."""
|
|
86
|
+
probe = getattr(self._ros, "sim_clock_published", None)
|
|
87
|
+
return probe() if callable(probe) else None
|
|
88
|
+
|
|
84
89
|
# ----- DDS surface -> DDS adapter -----
|
|
85
90
|
|
|
86
91
|
def observer_status(self) -> dict[str, Any] | None:
|