# ResultTable system: overview and usage This document explains the `ResultTable` system used across the CLI: how tables are built, how plugins integrate with them, and how `@N` selection, row replay, and plugin selectors work. ## TL;DR - `ResultTable` is the unified object used to render tabular results and drive `@N` behavior. - Plugins return `SearchResult` objects or dicts and can either supply row selection args or implement a `selector()` method. - Rows can also carry an explicit `selection_action`, which the CLI replays verbatim. - Table metadata helps plugins attach context. Some metadata keys still use older `provider` names internally. --- ## Key concepts ### ResultTable `SYS/result_table.py` renders rows as a rich table and stores metadata used for selection expansion. Important APIs: - `add_result()` - `set_table()` - `set_source_command()` - `set_row_selection_args()` - `set_row_selection_action()` - `set_table_metadata()` ### ResultRow A row holds columns, payload data, selection args, and optionally a full `selection_action` token list. When `selection_action` is present, `@N` uses it instead of reconstructing a command from the source command and row args. ### Plugin selector If a plugin implements `selector(selected_items, ctx=..., stage_is_last=True)`, that selector runs first when `@N` is used. If the selector returns `True`, it has handled the selection, for example by publishing a nested table. ### Table metadata `ResultTable.set_table_metadata(dict)` attaches plugin-specific context for selectors and follow-up stages. You will still see legacy keys such as `provider` in some metadata because parts of the runtime still consume them. --- ## Building a table Typical plugin flow: ```py from SYS.result_table import ResultTable table = ResultTable("Plugin: X results").set_preserve_order(True) table.set_table("plugin_name") table.set_table_metadata({"provider": "plugin_name", "view": "folders"}) table.set_source_command("search-file", ["-plugin", "plugin_name", "query"]) for result in results: table.add_result(result) ctx.set_last_result_table(table, payloads) ctx.set_current_stage_table(table) ``` Notes: - To drive a direct replay, call `table.set_row_selection_args(row_index, ["-open", ""])`. - For drill-in or interactive behavior, implement `plugin.selector()` and return `True` when handled. - For exact row replay, call `table.set_row_selection_action(row_index, tokens)`. --- ## Selection flow When a user enters `@N`: 1. The CLI chooses the active table. 2. It gathers the selected payloads. 3. `PipelineExecutor._maybe_run_class_selector()` runs plugin `selector()` hooks for the plugin inferred from the table or payloads. 4. If no selector handles the row, the CLI checks `row.selection_action` first. 5. If no explicit row action exists, the CLI expands `source_command + source_args + row_selection_args`. 6. Multi-selection results are piped downstream instead of being collapsed to one row replay. --- ## Columns and display - Plugins can pass a `columns` list in the result dict or `SearchResult` to control column order and display. - Otherwise, `ResultTable` uses sensible defaults. - Rendering helpers such as `to_rich`, `format_json`, and `format_compact` support different CLI display paths. --- ## Plugin-specific examples ### AllDebrid AllDebrid exposes a list of magnets as folder rows and the files inside each magnet as file rows. The plugin uses `selector()` to drill into a magnet and publish a new `ResultTable` of files. Example commands: ```powershell search-file -plugin alldebrid "*" @3 ``` Illustrative magnet row: ```py SearchResult( table="alldebrid", title="My Magnet Title", path="alldebrid:magnet:123", media_kind="folder", full_metadata={ "magnet_id": 123, "provider": "alldebrid", "provider_view": "folders", }, ) ``` Illustrative file row: ```py SearchResult( table="alldebrid", title="Episode 01.mkv", path="https://.../unlocked_direct_url", media_kind="file", full_metadata={ "magnet_id": 123, "provider": "alldebrid", "provider_view": "files", }, ) ``` Selection and download behavior: - `@3` on a magnet row runs the plugin selector and shows a file table. - `@2 | download-file` downloads the selected file row. - `@1 | add-file -plugin local -instance ` triggers add-file's plugin-aware handoff flow. Configure the AllDebrid plugin in `.config plugins` and add its API key before testing these flows. ### Bandcamp Bandcamp search supports `artist:` queries. The Bandcamp plugin implements a `selector()` that detects artist rows and expands them into a discography table. Example: ```powershell search-file -plugin bandcamp "artist:radiohead" @1 ``` Plugin selectors are the right tool when you need to replace one table with another, such as artist to discography drill-in. --- ## Plugin author checklist - Implement `search(query, limit, filters)` and return `SearchResult` objects or dicts. - Include useful `full_metadata` values for drill-in and selection replay. - Implement `download(result, output_dir)` when the plugin can materialize file bytes. - Implement `selector(...)` for drill-in or interactive transforms. - Add tests that cover search, select, and download flows. --- ## Debugging tips - Use `ctx.set_last_result_table(table, payloads)` to publish a table while developing a selector. - Add `log(...)` messages in plugin code to capture fail points. - Inspect `full_metadata` when a selector or replay path is missing required context. --- ## Quick reference - ResultTable implementation: `SYS/result_table.py` - Pipeline helpers: `SYS/pipeline.py` - Row replay and selection flow: `SYS/pipeline.py` - Plugin authoring guide: `docs/provider_authoring.md`