Scripting and command line
Start CytoWeave the way a pipeline needs, run an analysis on a folder of files without a window, and drive it from R, Python, Jupyter or a shell script with the same actions an AI agent uses.
Command line
cytoweave [flags] [FCS files, folders, workspaces (.cwz), Gating-ML or FlowJo .wsp files...]
cytoweave mcp [flags]
cytoweave run [flags] [FCS files or folders...]
cytoweave verify [flags] certificate.acs| Flag | Default | Effect |
|---|---|---|
--window app|browser|none | app | Open an app window (Chrome, Edge, Brave or Chromium, with its own profile), the default browser, or nothing |
--no-open | Same as --window none | |
--keep-running | Keep serving after the window is closed | |
--port | 8770 | Preferred port; the next free one is used if it is taken |
--host | 127.0.0.1 | Interface to bind. Keep the default unless you mean to serve other machines (for example 0.0.0.0 on a trusted network), which turns off the DNS-rebinding check |
--data-dir | user config dir | Folder of the workspace library |
--no-library | Keep workspaces in the browser's own storage instead | |
--remote-control | Accept actions from programs on this computer (below) | |
--dev | Serve web/ from the working directory (for development) | |
--version | Print the version |
CytoWeave prints the address it serves, for example http://127.0.0.1:8770. With --window none, open that address in any browser yourself. Files named when CytoWeave is already running are handed to the open window.
Headless runs
cytoweave run applies an analysis template to FCS files without a window and writes its results to a folder. It is meant for a core's nightly runs, pipelines and continuous integration. A run:
- starts CytoWeave on a private port, with no workspace library;
- opens it in a headless Chrome (or Chromium, Edge or Brave);
- applies the analysis with the same code the window uses, writes the outputs, and stops.
Save a finished analysis as a template file (Workspace → Save as a template, downloaded as a .cwt file; see Templates), then:
$ cytoweave run --template panel.cwt --annotations samples.csv --output results/ plate-12/The run opens the files (each folder becomes a group) and the annotations table. It then applies the template, with its channels matched by marker. It accepts what it applied: the run stands for you, so no proposal is left open and nothing is attributed to an agent. Finally it writes:
| File | What it holds |
|---|---|
tables.xlsx | Every table of the template, with the sheets that say where the numbers come from (columns, samples and their checksums, gating, methods) |
<table>.csv | Each table as the Tables view's CSV button writes it |
report.pdf | The batch report of the template's last figure, a page per sample (or per --report-by annotation) |
workspace.cwz | The workspace, to open in the window beside the FCS files and review or adjust |
methods.txt | The methods paragraph with its references |
run.json | What the run read (each file's SHA-256), each step's outcome and message, every population's count in every sample, and what it wrote (sizes and SHA-256) |
| Flag | Default | Effect |
|---|---|---|
--template | The analysis template (.cwt) to apply | |
--output | The folder for the outputs (made if missing) | |
--annotations | A CSV of sample annotations (the first column names the sample) or a plate layout (a well column, or plate maps) | |
--qc | Run acquisition QC first, with a "QC pass" population above the template's | |
--report | pdf | pdf, pptx or none |
--report-by | the figure's | sample, or an annotation such as subject |
--flowjo | Also write a FlowJo workspace (workspace.wsp) | |
--review | Also write a review report (review.html): the analysis as one HTML file, every number traced, for a PI or reviewer | |
--certificate | Also write a reproducibility certificate (certificate.acs) of the analysis with its files, which cytoweave verify checks | |
--steps | Run the steps of a JSON file instead (below) | |
--overwrite | Replace outputs already in the folder (otherwise the run refuses before it starts) | |
--chrome | CHROME, else one installed | The browser to run in |
--timeout | 2h | Stop a run that takes longer |
The exit status is:
- 0 when every step succeeded;
- 1 when a step failed, which
run.jsonsays (a failure to open the files or apply the template stops the run; any other failure is recorded and the rest still run); - 2 for a mistake on the command line.
Each step prints a line as it finishes.
Your own steps
--steps runs any of the agent actions in order: a JSON list of {"action": …, "args": {…}}. Paths are resolved as follows:
"$inputs"inopen_files' paths stands for the files and folders named on the command line;templateFilereads a template file intoapply_template;- other relative input paths are taken from the steps file's folder;
- relative output paths are taken from
--output.
accept_proposals accepts what the steps before it proposed. Each step's result data goes into run.json. For example, QC, the template, a paired comparison and a table of counts:
[
{"action": "open_files", "args": {"paths": ["$inputs", "annotations.csv"]}},
{"action": "run_qc", "args": {"addGate": true}},
{"action": "apply_template", "args": {"templateFile": "panel.cwt"}},
{"action": "accept_proposals"},
{"action": "compare", "args": {"population": "Tregs", "groupBy": "condition", "pairBy": "subject"}},
{"action": "export_table", "args": {"statistic": "count", "path": "counts.csv"}},
{"action": "methods", "args": {"path": "methods.txt"}}
]$ cytoweave run --steps steps.json --output results/ plate-12/A run gives the same results as the same analysis done in the window. The validation checks this on the PBMC example, comparing each output with what the window's own buttons export and every count with an independent computation (Validation).
Verifying certificates
cytoweave verify verifies a reproducibility certificate without a window, as a run does: it checks every file's SHA-256, the workspace, the change log's chain and the certificate's fingerprint, computes every number again from the files and compares them.
$ cytoweave verify analysis.certificate.acs
CytoWeave 0.8.1: verifying analysis.certificate.acs in Google Chrome
Confirmed: all 468 numbers of "Panel B" were computed again from the 12 files and are identical. Fingerprint c1a6-4ec2-1429-735a.
Files: 12 of 12 match their SHA-256. Numbers: 468 identical of 468 (1.2 s).| Flag | Default | Effect |
|---|---|---|
--data | A folder (or an FCS file) with the data, for a certificate made without it; may be repeated. Files are recognized by their checksums, whatever their names. | |
--report | Also write what was found as JSON: the verdict, the files, the change log, the numbers checked and the first that differ | |
--overwrite | Replace the report file if it exists | |
--chrome | CHROME, else one installed | The browser to verify in |
--timeout | 2h | Stop a verification that takes longer |
The exit status is:
- 0 when the certificate is confirmed;
- 1 when it is not: a file, the workspace,
certificate.jsonor the change log changed, or numbers differ; - 2 for a mistake on the command line;
- 3 when it is incomplete: files are missing (give
--data).
A run makes a certificate with --certificate, so a pipeline can verify its own results, for example in CI, or a core can certify each night's analysis. Agents and scripts use export_certificate and verify_certificate.
Remote control from your programs
cytoweave --remote-control accepts the actions of the MCP tools from programs on this computer. The window must be open: it performs each action with the same code as the user interface, so you watch the script work. A script's changes are proposals too, shown in the window under the script's name, for you to accept or reject. Actions reach the window one at a time.
$ cytoweave --remote-controlThe R and Python clients
The clients in clients/ make every action a function, with its arguments and help, and find the running CytoWeave themselves. They are installed from the repository (they are not on CRAN or PyPI):
remotes::install_github("robert-mcdermott/cytoweave", subdir = "clients/r")$ pip install "git+https://github.com/robert-mcdermott/cytoweave#subdirectory=clients/python"library(cytoweave)
cw_connect() # finds the running CytoWeave
cw_open_files(c("/data/2026-09-30_panel1/")) # each folder becomes a group
cw_auto_gate(method = "singlets", x = "FSC-A", y = "FSC-H", name = "Single cells")
frequencies <- as.data.frame(cw_statistics_table(statistic = "freqParent"))
states <- cw_differential_analysis("condition", groups = c("Unstimulated", "Stimulated"),
pair_by = "subject", clusters = "FlowSOM cluster")
as.data.frame(states) # marker by cluster, lowest p firstimport cytoweave
cw = cytoweave.connect() # finds the running CytoWeave
cw.open_example(id="pbmc-immunophenotyping")
print(cw.list_populations().message)
table = cw.statistics_table(statistic="freqParent").frame() # a pandas DataFrame
cw.render_plot("CD3", population="T cells", file="t-cells.png")
cw.export_table("/tmp/frequencies.xlsx", statistic="freqParent")Arguments are those of the MCP tools in snake case (group_by, pair_by); each function's help (?cw_differential_analysis, help(cw.differential_analysis)) lists them. Every result has the action's message and its data; as.data.frame() in R and .frame() in Python (with pandas) turn the table in it into a data frame. An action CytoWeave refuses is an error with its reason. cw_call() and cw.call() send any action by its name, and cw_tools() and cw.tools() list them.
Without arguments, the clients connect through the remote.json file CytoWeave writes to its data folder when remote control is on: its address and token, readable by you only, removed when CytoWeave stops. Give the address and token yourself, or set CYTOWEAVE_URL and CYTOWEAVE_TOKEN, or CYTOWEAVE_DATA_DIR when CytoWeave runs with --data-dir. The clients' version is CytoWeave's, and each release's clients are tested against that release.
Any language: JSON over HTTP
Each action is JSON posted to /api/remote/action, as {"action": …, "args": {…}, "client": …}; every reply has ok, a plain-language message and the data. GET /api/remote/tools lists the actions with their arguments.
import requests
URL = "http://127.0.0.1:8770/api/remote/action"
def act(action, client="Plate pipeline", **args):
reply = requests.post(URL, json={"action": action, "args": args, "client": client}).json()
if not reply.get("ok"):
raise RuntimeError(reply.get("message") or reply.get("error"))
return reply
act("open_example", id="pbmc-immunophenotyping")
act("auto_gate", method="singlets", x="FSC-A", y="FSC-H", name="Single cells")
print(act("list_populations")["message"])
for row in act("statistics_table", statistic="freqParent")["data"]["rows"]:
print(row["sample"], row["meta"].get("condition"), row["values"])Opening and writing files
open_files and the export actions make the program read or write files, so they also need the X-CytoWeave-Token header with the token CytoWeave prints when it starts (the clients send it):
TOKEN = "…" # printed by cytoweave --remote-control, and in remote.json
requests.post(URL, headers={"X-CytoWeave-Token": TOKEN},
json={"action": "open_files", "args": {"paths": ["/data/2026-09-30_panel1/"]}})Paths must be absolute. Each folder becomes a sample group. Exports go only to the path given, in a folder that exists, and never replace a file unless overwrite is given.
What scripts are good for
- Batch runs: open each day's plate, apply the lab's gating strategy, review the gates across samples, and write the statistics table to a file.
- Notebooks: pull
statistics_tableanddifferential_analysisresults into R or pandas, next to the plots CytoWeave renders withrender_plot. - Pipelines: keep the analysis visible and reviewable in the window while a program does the repetitive part.
The arguments of every action are those of the MCP tools; docs/MCP.md lists them, with the coordinate conventions for each gate type.
Security
Remote control is off unless you start CytoWeave with --remote-control (cytoweave mcp turns it on for its own session). The server accepts actions only from this computer, and actions that read or write files need the per-session token, which only you can read in remote.json. Leave --host at its default while remote control is on.