apply
Set chaff up in the folder: chaff.yaml for the chosen genre, today's findings shelved in .chaff-baseline.json, and, when asked, a…
Analyse a CSV, TSV or Excel file, or answer a question about one (is B better than A, what is unusual here, what comes next month, what drives sales, who is likely to cancel), and show the answer in MulmoTerminal's Canvas as a spreadsheet with live formulas (presentSpreadsheet)
$ npx -y skills add receptron/mulmoterminal --skill mulmoterminal-data-analysis --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/mulmoterminal-data-analysisContext preview
The summary Claude sees to decide when to auto-load this skill.
Analyse a CSV, TSV or Excel file, or answer a question about one (is B better than A, what is unusual here, what comes next month, what drives sales, who is likely to cancel), and show the answer in MulmoTerminal's Canvas as a spreadsheet with live formulas (presentSpreadsheet)
name: mulmoterminal-data-analysis description: Analyse a CSV, TSV or Excel file, or answer a question about one (is B better than A, what is unusual here, what comes next month, what drives sales, who is likely to cancel), and show the answer in MulmoTerminal's Canvas as a spreadsheet with live formulas (presentSpreadsheet) and charts (presentChart) behind it. Reads the file, checks its columns, computes summary statistics with Node, puts what the user may want to change in formulas and the rest in values, picks charts that fit the data, and says where everything was saved. Use when the user asks to analyse, summarise, explore or chart tabular data or asks such a question of it, to read a spreadsheet again after editing it, or to keep an analysis as a skill of their own to run on the next file of the same kind. The user writes no code. Works in whatever language the user writes in.
The user hands you rows and columns and wants to understand them. They do not write code and do not reshape the data themselves; you do, with Node in the terminal, and show the result on the Canvas. Talk to them in their language, and in words about their data, not about this procedure.
was switched on before the session started. If you do not have them, say so and stop: tell the user to switch on Canvas in the launcher's MCP toggles and start a new session there.
large file is aggregated by a script instead (see *A large file* below).
Do not read a large file into the conversation. Look at its size first, then read only the head (`head -n 20`). Let a script read the whole file.
Write the scripts in a working directory of their own, never in the user's repository:
work=$(mktemp -d) && cd "$work" && npm init -y >/dev/null && npm install csv-parse simple-statistics
Parse with `csv-parse` rather than splitting on commas, because quoted fields hold commas and line breaks:
import { readFileSync } from "node:fs";
import { parse } from "csv-parse/sync";
const rows = parse(readFileSync(process.argv[2]), { columns: true, skip_empty_lines: true, bom: true });`delimiter: "\t"` reads a TSV. Every value comes back as a string; convert the numeric columns yourself (strip thousands separators first: `"1,200"` is not a number to `Number`), and count the ones that do not convert rather than dropping them silently.
Read an Excel file (`.xlsx`) with `exceljs`, installed in the same working directory (`cd "$work" && npm install exceljs`). Read one worksheet at a time, and say which worksheets it has and which you read. A cell there may hold a number, a date, a formula's cached result or text; check each column the same way.
Before analysing anything, tell the user what you found:
If a column's meaning is unclear (`amt`, `v2`), ask once, rather than guessing.
This section is for CSV and TSV. A large Excel file is read with `exceljs`'s streaming reader (`new ExcelJS.stream.xlsx.WorkbookReader(path)`) and aggregated in the script; a collection reads CSV only, so do not make one from it.
Look at the file's size first. If it is more than a few hundred kilobytes, count its records with one streaming pass, not `wc -l` (a quoted field can hold a line break, so lines are not records). The same pass counts, for each column you will treat as a number, the values that are empty, the values that do not convert, and the values that convert only after you strip thousands separators; and, for each column that could be the key (step 4 below), its empty values and whether any value repeats:
import { createReadStream } from "node:fs";
import { parse } from "csv-parse";
const parser = createReadStream(process.argv[2]).pipe(parse({ columns: true, skip_empty_lines: true, bom: true }));
for await (const row of parser) { /* count the record; check each numeric column */ }Up to about 5,000 records, read the file with a script as above and put every row on the Data sheet. **Above that, do not parse the whole file into memory and do not put its rows on a sheet.** Aggregate it instead, and put the aggregated table on the sheet.
**With `manageCollection`**, make the file a read-only collection and aggregate it with DuckDB — but only when every value in the numeric columns you will aggregate converts as it stands. The collection drops a value it cannot read as a number from `sum` and `avg` without saying so, and `"1,200"` is such a value; `min` and `max` compare the values as they are written, so in such a column they compare text. If the streaming pass found any, aggregate with the script instead (below), and say how many values were empty or unreadable.
1. The file must sit inside the root the collection lives in (the workspace, or the project). If it is elsewhere, ask before copying it into `data/` there; never move the user's file. If `data/` already holds a file of that name, do not overwrite it: copy under a name that does not exist yet, and tell the user which. 2. Pick the collection's slug: lowercase letters, digits and hyphens, named after the data (`sales-2025`). Never start it with `mc-` (reserved for the presets, which are overwritten on every start) or `mulmoterminal-`. A slug that is taken would be overwritten: check that `manageCollection` `getSchema` answers "unknown collection" for it and that no skill directory of that name exists, and pick another name if either does. 3. Read how to define one: `manageCollection` with `action: "schemaDocs"` and the topic
Run multiple coding-agent sessions in parallel — and see which one needs you. A browser terminal for parallel AI coding agents: several sessions side by side, each in its own cell, with the one that needs you marked in colour.
Set chaff up in the folder: chaff.yaml for the chosen genre, today's findings shelved in .chaff-baseline.json, and, when asked, a…
Measure what chaff reports on the named documents today, as the kind the person chose, and propose the setup — changing nothing…
Answer each question from the named documents only, quoting where the answer is written, or saying plainly that the documents do…
Add the approved answers to FAQ.md when the person chose to keep them, after what it already holds; change nothing otherwise.
Build the spec's operations as the Worker's API, with validation and tests.