Authoring a template with an AI¶
bxp-cli templates are plain JSON5 — a capable AI (Claude, ChatGPT, …) can
write one for you. Two things are required context: the BXP docs (this
site, or its Markdown sources in the repository) AND bxp-cli.examples.json
(in the console archive and the GitHub repository). The docs define the language
and the target output spec; the examples.json carries working per-source
patterns the AI is expected to pattern-match against. Without
examples.json the AI should refuse to guess.
Give the assistant the tools¶
Everything below works without it, but an assistant that can run bxp instead
of reasoning about it gets there far faster and stops handing you templates it
could not check. bxp-mcp ships in both packages — beside bxp-cli in the
console archive, inside the bundle on desktop — and registering it with an
MCP-capable client is one entry:
On desktop, read the path off Settings inspector → Binaries (ctrl+shift+s) rather than guessing it: an AppImage mounts itself somewhere new on every launch, so the path is not something you can write down once.
The prompt¶
Paste this into your assistant, attach both files, then drop in 5 rows of your source's raw CSV:
"I use BXP. Please read the Guide and Target specs sections of the docs and the comments in
bxp-cli.examples.json. Here is a sample of my export:<paste 5 rows including the header>. Add a new entry underconversion_templatesin mybxp-cli.jsonthat converts this to Wealthfolio CSV, following the same patterns as the existing templates. Self-test your output (validate the config, then run it against the sample rows — via the bxp-mcp tools if available, elsebxp-cli --debug) before returning, return JSON5 with//comments explaining non-obvious decisions, and end your reply with a 'Things to check in bxp-gui' list for anything you couldn't fully verify."
After the AI proposes a template, paste it into the GUI's tree editor (or
directly into your bxp-cli.json) and run a dry-run. The GUI's inline
error chips will tell you exactly which expressions need fixing, and the
AI's "Things to check in bxp-gui" list (see Handing off)
tells you what to look for next.
Rules for an AI assistant¶
If you are an AI assistant generating a new template, follow these rules strictly:
bxp-cli.examples.jsonis required context. It ships working templates with rich inline comments. If you don't have it, ask the user to provide it before generating any template — do not guess at non-trade row patterns, action vocabulary, or source quirks. Pattern-match against whichever shipped template is closest in shape to the source at hand: one row per record, paired rows that need joining, an xlsx source, or a template targeting the other tracker. Read the real ids out of the file rather than assuming them — with the bxp-mcp server,bxp_list_templatesreturns a compact index (id plus the file patterns and description) andbxp_fetch_templatereturns a single template's JSON, so you can quote the one you are modelling rather than the whole file. Both take the config text, not a path.- Add, do not modify. Insert a new entry under
conversion_templates. Never rewrite existing templates unless the user explicitly asks. - Match the real CSV format. Set
csv_delimiter_in,csv_decimal_separator_in, andcsv_text_quote_into what the source actually exports — do not guess. - Put activity-type logic in
row_rules, notinput_schema.$actionmust be assigned inside arow_rules[].rows[]entry. Theinput_schemaonly extracts and transforms neutral values. - Use
pre_passonly for cross-row joins. If one input row needs a value from another row, usepre_passandLOOKUP. Otherwise omit it entirely.pre_passhas two shapes, and they select differentLOOKUParities — do not mix them up: a single block{ when, key, values }, read with the 2-argLOOKUP(key, 'field'); or named blocks{ name1: { … }, name2: { … } }, read with the 3-argLOOKUP('name1', key, 'field'). - Prefer named
maps. If the source's symbols overlap an existing named map, reference it by name withREMAP([Symbol], 'xtb'). Otherwise define a small inlineREMAP(s, k, v, ...). - One-to-many rows. When one input row must produce multiple output
rows (currency conversion = FEE + WITHDRAWAL + DEPOSIT; dividend with
tax), return multiple objects in the same
row_rules[].rowsarray. Each object can override$variables for its own output row. - Match the source's exact date shape. Use
DATE_CONVERTwith tokens that correspond to the input literally, character-by-character; use[*]to skip fractional seconds, trailingZ, or timezone suffixes. - Prices with embedded currency. For fields like
"$100.00"or"24.00 CZK", usePRICE_VALUE()for the number andPRICE_CURRENCY()for the ISO code. - Empty values. Set a
$variableto""to leave that output column blank. Drop a column fromoutput_schemato remove it. - Surface unmatched rows during development. On the CLI, set
row_rules_debug_missing: trueand run with--debug— the pair is what dumps rows matching no rule as JSON. (--debugconflicts with--quietand--trace.) The GUI does not use that flag: a dry-run reports skipped rows through the per-row trace instead, and clicking one shows its RULE RESULTS. - Self-test before returning. See below — predict each sample row's
outcome, then verify with the bxp-mcp tools (
bxp_validate,bxp_eval/bxp_eval_trace,bxp_simulate), orbxp-cli --debug. - Return commented JSON5, not bare JSON. Use
//comments to explain non-obvious decisions — the user reads your output as documentation; future-you reads it to extend the template later. - Hand off the unfinished business in plain language. End with a numbered "things to check in bxp-gui" list — see Handing off.
Self-testing the generated template¶
Validate before returning. Treat the steps like unit tests — predict the expected result, then compare against actual output.
The self-test surface depends on what you have wired:
- With the bxp-mcp server (agent path), nine tools:
bxp_validate,bxp_validate_expr,bxp_eval,bxp_eval_trace,bxp_eval_batch,bxp_list_templates,bxp_fetch_template,bxp_docs, andbxp_simulate(a full end-to-end run). Each takes config / expression text as arguments, so you never touch the filesystem. Reach forbxp_docsrather than guessing a builtin's signature — it is the same catalog the reference pages are generated from. - With only
bxp-cli(no MCP):bxp-cli --debugand a real run.
headers / fields are arrays of strings
Every eval tool takes the row context the same way — a native JSON
array, ["Price","Qty"]. bxp_eval and bxp_eval_trace still
accept the array encoded into a string
("[\"Price\",\"Qty\"]"), the shape their schema used to declare,
but there is no reason to write it.
Anything else is a tool failure naming the argument, never a
silently empty row: an earlier version dropped the context and still
answered {"ok":true}, so every [Column] read as "" and agents
concluded a correct expression was broken.
1. Schema + JSON5 syntax check. Call bxp_validate with the config
text. Expect no $err_* / $warn_* keys for the new template's path. If
$err_* appears, fix it before going further.
2. Predict, then verify. For each sample row, write down beforehand
which row_rules entry should match (and therefore $action), what each
$variable should evaluate to, and how many output rows the input row
should produce (0 / 1 / N).
- Step A — per-expression check. Before wiring an expression in,
evaluate it on its own against one sample row with
bxp_eval_trace(the{"t":"final","value":...}line is the result). Usebxp_validate_exprto catch authoring-time mistakes the lenient runtime swallows (e.g. a literalSPLIT_PART(…, 0)), andbxp_eval_batchfor several$variableexpressions against the same row at once.
bxp_eval / bxp_eval_trace:
{
"expr": "DATE_CONVERT([Time], 'YYYY-MM-DD hh:mm:ss', 'YYYY-MM-DD')",
"headers": ["Action", "Time", "Ticker"],
"fields": ["Market buy", "2024-04-25 07:00:35", "RIO"]
}
bxp_eval_batch — the same, plus exprs:
{
"headers": ["Action", "Time", "Ticker"],
"fields": ["Market buy", "2024-04-25 07:00:35", "RIO"],
"exprs": ["UPPER([Ticker])", "[Action]"]
}
bxp_eval_batch also accepts maps (named REMAP / REPLACE tables),
lookups and single_prepass_name — the only way to exercise LOOKUP
without a full run.
- Step B — run it end-to-end. With MCP, call
bxp_simulatewith the config, the template id, and the sample CSV. It stages and runs the realbxp-clipipeline and returns the produced output, a record-count diff, diagnostics, and a per-rowtrace— each filtered row carrying a reason (rule_skip/no_rule_match) and its 1-based input line, which is what tells you awhencondition never matched.ok: trueonly means the run happened; readexit_code/status/diagnosticsfor the verdict. Pass the optionalworkspaceargument to reuse one scratch directory across iterations instead of littering temp with a new one per call. Without MCP:
Output: human-readable summary + [expr error] $var = "expr": … lines
for any expression that failed at runtime + JSON dumps of unmatched
rows when row_rules_debug_missing: true.
- Step C — confirm the final output. With
bxp_simulate, read the returnedoutputs[].csv. Without MCP, run without--debugand read the generated.csvx:
Iterate until step B is silent (zero [expr error], zero unmatched rows)
and the .csvx from step C matches every prediction.
bxp-cli --trace is not part of this loop: it emits a binary BXTB frame
stream for the GUI's drill-down view, which is neither readable in a terminal
nor needed to self-test.
3. Inspect the .csvx. Header row matches output_schema keys, in
order. Spot-check at least one row of each $action type the template
emits.
4. If a prediction fails, diagnose by category.
| Symptom | Likely cause |
|---|---|
$date empty or wrong |
Date token mismatch (MM vs mm, missing [*] for timezone, etc.) |
[ColumnName] resolves to empty |
Column name typo / case mismatch / extra whitespace in source header |
$amount differs by sign |
Missed ABS() — see Target specs |
--debug lists unmatched rows |
Missing or wrong row_rules when condition |
$ticker empty for cash event |
Non-trade row pattern not applied — see Target specs |
Only return the template once every prediction matches and --debug
output is empty.