Skip to main content

gridcalc

PyPI Python License: MIT

A programmable spreadsheet for developers and technical users, with Excel-ish formulas, Python escape hatches, XLSX interop, goal seek, and LP/MIP solving. It ships two frontends over one engine: a curses terminal app, and a desktop window that renders the same workbook in a webview.

Inspired by Serge Zaitsev's kalk.

pip install gridcalc
gridcalc budget.json        # terminal
gridcalc-web budget.json    # desktop window  (needs the [web] extra -- see below)

What it gives you

  • Excel-compatible formulas (=IF(A1>=B1, A1*0.05, 0)) and arrays (=SUM(A1:A10 * B1:B10)) without leaving the terminal.
  • Multi-sheet workbooks with cross-sheet refs (=Sheet2!A1) and a proper dep graph.
  • Three formula modes per file: strict Excel, Excel-plus-py.*, or full Python eval with numpy/pandas.
  • xlsx interop via OpenXLSX C++ -- read every sheet's formulas + values; export with cached values.
  • Linear, mixed-integer & quadratic programming built in via HiGHS -- :opt max B4 vars A4:A5 st D4:D6 solves from cells in the sheet, with shadow prices, infeasibility diagnosis, and RHS sweeps. Models persist in the workbook.
  • Goal-seek -- :goal B10 = 100 by A1 adjusts a variable so a formula hits a target value.
  • Vim-style command line (:w, :e, :q, /search, y/p, visual selection, undo), with system-clipboard copy/paste -- yank pushes values as TSV, paste pulls TSV in from other apps.
  • A desktop app as well as a terminal one -- the same engine behind a mouse-driven grid with menus, a Ctrl-K command palette, and solver results painted onto the sheet. Most : commands are a shared registry both frontends dispatch, so they behave identically in each. See Desktop app.

Try it on the provided examples:

gridcalc example_excel.json          # sales report, named ranges, IF/MATCH
gridcalc example_hybrid.json         # progressive tax via py.* + aggregations
gridcalc example.json                # PYTHON: numpy/pandas, list-comprehensions
gridcalc example_multisheet.json     # 3-sheet budget, cross-sheet formulas
gridcalc example_lp.json             # LP/MIP demo -- type :opt to solve
gridcalc example_goal.json           # goal-seek demo -- :goal B1 = 11 by A1

Install

Two options:

pip install gridcalc            # core: zero third-party runtime deps
pip install 'gridcalc[extras]'  # adds numpy, pandas, pygments

Or with uv: uv tool install 'gridcalc[extras]'.

The core install has zero third-party runtime dependencies on Linux and macOS -- the full 300+ Excel function library (statistical distributions, financial functions, LINEST/TREND regression, ...) works on stdlib alone. (The 3.10 wheel pulls tomli for config-file parsing; 3.11+ uses stdlib tomllib. On Windows only, windows-curses is pulled in because curses is not in the Windows stdlib.)

The [extras] bundle enables, all at once:

  • np.array(...) in formulas; LAPACK-backed solvers; faster LINEST.
  • pd.DataFrame(...), :pd load/save, DataFrame cell display.
  • Pygments syntax-highlight in the load-time trust prompt.

Desktop app (web frontend)

An editable grid in a native desktop window, driven by the same engine as the terminal app. The engine runs in-process on CPython -- keeping the C++ extensions -- and a pywebview window renders a viewport over it, calling Python directly across the js_api bridge. There is no server, no port, and no HTTP.

Both frontends read and write the same files, format cells through the same code, and share most of the : command set through a frontend-neutral registry -- so :sort in the terminal and Sort rows in the palette are one implementation, not two that drift. A conformance test fails if either frontend loses a shared command.

pip install 'gridcalc[web]'
gridcalc-web                    # demo workbook, or: gridcalc-web book.json

From a source checkout, build the UI bundle first: make web-build compiles the React client into src/gridcalc/web/static/index.html, which gridcalc-web loads. It is a build artifact and is not in git, so a checkout that has not run it exits with a message telling you to. Released wheels and sdists ship the bundle already built -- make wheel/make sdist and every CI build job compile it first, and the release pipeline fails if a distribution is missing it.

Editing. In-cell and formula-bar editing, keyboard navigation, rectangular selection by drag or shift-click, and clicking a row or column header to select the whole line. Copy/cut/paste (formula references adjust, $ absolutes do not), fill down and right, undo/redo, insert and delete rows and columns sized to the selection. Sheets can be added, renamed, deleted, and reordered. Column edges drag to resize and the width is saved with the workbook. A status bar reports the selection's aggregates and an unsaved-changes marker; closing with unsaved work asks first.

Formula point mode works as it does in the terminal: while typing a = formula, clicking or dragging on the grid inserts the reference at the caret.

Ctrl-K opens a command palette over the whole command set, matched on subsequences (fld finds Fill down). It is the home for commands no menu would justify -- column width, named ranges, sort, formula mode, freeze panes, manual recalculation -- and prompts for arguments the way the : line does. Ctrl-F finds text or computed values, so a formula is findable by its result as well as its source.

Optimization is where it goes past the terminal. Optimize reads a model off a selected block or loads one saved in the workbook (the same models :opt run executes), solves it, and then paints the result onto the sheet: the objective, the decision cells, and each constraint marked binding or slack, with shadow prices and RHS ranging in the hover text. A shadow price means considerably more sitting on the constraint row it belongs to than in a column of cell references. Goal Seek and a parametric Sweep -- the objective plotted against a constraint's right-hand side, breakpoints marked -- round it out.

Not there yet: the object editor for Vec/ndarray/DataFrame cells, code-block editing (:e), pandas import/export, and :move/:replicate. Workbooks in HYBRID or PYTHON mode load formulas only -- their code block is never executed, so code-dependent cells show an error state until a trust flow exists. See docs/web.md for the full parity table and docs/gui.md for why this direction was chosen.

The [web] extra pulls a native webview stack, deliberately kept out of the lean terminal build; the curses app has no such dependency.

Quick tour

Cells hold a number, a label (any non-=-prefixed string), or a formula (prefixed with =). Arrow keys move; Enter commits and moves down; Tab commits and moves right. The rest of this guide shows the terminal frontend, but the model, formulas, file format, and most commands are the same in the desktop app.

        A          B          C
1  Revenue   Cost       Margin
2  1000      600        =(A2-B2)/A2*100      <- formula
3  1200      700        =(A3-B3)/A3*100
4  Total     =SUM(B2:B3) =AVG(C2:C3)

Press : for the command line. The basics:

Command Purpose
:w [file] save (extension .json or .xlsx)
:o file open
:q, :q! quit, force-quit
:e edit the workbook's Python code block in $EDITOR
u, Ctrl-R undo / redo
v enter visual selection mode (then y yanks, p pastes)
/text search (n/N to cycle matches)
> go to a named cell (e.g. > AA10)

A full command reference lives in the Reference section below.

Modes

Each workbook has one of three evaluation modes, controlling which formulas parse and what's reachable from them:

Mode Grammar Python escape hatch Sandbox Use case
EXCEL strict Excel none not needed (no eval) xlsx interop, untrusted files
HYBRID Excel + py.* code-block functions reachable as py.foo(...) code blocks only most new sheets
PYTHON Python eval() full Python expressions full AST sandbox numpy/pandas-heavy work

Switch with :mode <name> -- the change is refused if any current formula doesn't parse in the target mode. Files without an explicit mode field load as PYTHON (back-compat). :xlsx load switches to EXCEL automatically.

Formulas

=A1 + B1 * 2                          arithmetic, Excel precedence
=(A1 + A2) / 2                        grouping
=2^10                                 exponent (PYTHON: ** also works)
=50%                                  percent postfix -> 0.5
="hello " & A1                        string concat
=IF(A1 > 0, "pos", "neg")             conditionals
=IFERROR(B1/C1, 0)                    error catch -- #DIV/0!, #VALUE!, #N/A, ...
=SUM(A1:A10)                          range -> 1D array
=SUM(A1:A3 * B1:B3)                   element-wise array arithmetic
=LET(x, SUM(A1:A9), x/COUNT(A1:A9))   local bindings -- compute once, reuse
=FILTER(A1:A9, B1:B9 > 0)             dynamic arrays: FILTER/SORT/UNIQUE
=SUM(revenue)                         named range
=py.margin(A1, B1)                    HYBRID: call a code-block function

Excel error values (#DIV/0!, #N/A, #NAME?, #REF!, #VALUE!, #NUM!, #NULL!) propagate through arithmetic and are catchable with IFERROR/IFNA.

Built-in functions (always available): SUM, AVG, MIN, MAX, COUNT, ABS, SQRT, INT, plus everything in math (sin, cos, log, pi, e, ...).

Excel-compatible library (auto-loaded in EXCEL/HYBRID): IF, IFERROR, AND, OR, NOT, ROUND, AVERAGE, MEDIAN, SUMIF, COUNTIF, AVERAGEIF, LET, VLOOKUP, HLOOKUP, XLOOKUP, XMATCH, INDEX, MATCH, FILTER, SORT, UNIQUE, SEQUENCE, CONCATENATE, LEFT, RIGHT, MID, LEN, TRIM, UPPER, LOWER, SUBSTITUTE, and 280+ others. Dynamic-array functions return whole rows/columns and compose (=INDEX(SORT(A1:B9), 1, 2)).

PYTHON-only extras: the math module, Python builtins (sum, min, max, abs, len), list comprehensions, and -- when the relevant extras are installed -- np.array(...), np.linalg, matrix multiply (@), and pd.DataFrame(...).

Named ranges & custom functions

:name revenue A1:A12       Define a named range (workbook-global)
:names                     List
:unname revenue            Remove

Used directly in formulas: =SUM(revenue), =MAX(revenue - costs).

Open the per-workbook Python code block with :e. Anything defined there becomes callable from formulas:

def margin(rev, cost):
    return (rev - cost) / rev * 100

In HYBRID: =py.margin(A1, B1). In PYTHON: =margin(A1, B1). EXCEL mode forbids code blocks entirely.

Cell references

$A$1 fixes both; $A1 fixes the column; A$1 fixes the row. References adjust automatically on insert/delete/replicate.

Multi-sheet workbooks

:sheets                    Interactive picker: list sheets, select one to switch
:sheet                     List sheets inline (active marked *)
:sheet Inputs              Switch by name
:sheet 1                   Switch by zero-based index
:sheet add Outputs         Append (does not switch)
:sheet del Tmp             Remove (refused if last sheet)
:sheet rename Old New      Rename, rewriting `Old!` prefixes in formulas
:sheet move Inputs 0       Reorder

A workbook with more than one sheet shows a tab strip on the bottom line (active tab highlighted, with an i/n position counter); single-sheet workbooks leave that line clear. The status bar also prefixes the active sheet name (Inputs!A1) whenever a workbook has multiple sheets.

Reference cells on other sheets with Sheet!cell:

=Sheet2!A1
=SUM(Sheet2!A1:A10)
=Sheet1!A1 + Sheet2!B1

The dep graph is keyed on (sheet, col, row) so cross-sheet recalc works transparently. Cross-sheet ranges (Sheet1!A1:Sheet2!B5) are not supported (Excel doesn't either).

Optimization

:opt solves linear and mixed-integer programs defined by cells in the active sheet, via a vendored copy of HiGHS (MIT). Models are workbook-persistent: define once, save the file, re-run on reopen.

:opt                                                                       Run the saved 'default' model
:opt max|min            (with a visual selection)                          Infer the model from the selected block
:opt max|min <cell> vars <cells> st <cells> [bounds <spec>] [int <cells>] [bin <cells>]
                                                                           Solve inline AND save as 'default'
:opt def <name> max|min <cell> ...                                         Save under <name>; does not execute
:opt run [<name>]                                                          Execute a saved model
:opt sens [<name>] [into[!] <cell>]                                        Sensitivity report -- paged, or written into cells
:opt sweep <cell> <lo>:<hi> [steps] [<name>]                               Re-solve across a range of RHS values
:opt list                                                                  List saved models
:opt undef <name>                                                          Remove a saved model

The model is sheet-resident: an objective formula in one cell, decision-variable cells holding values, and constraint cells holding comparison formulas like =A1+A2<=10. The constraint cells keep evaluating during recalc, so the sheet shows live feasibility (TRUE/FALSE) before and after the solve.

A worked example (also at examples/example_lp.json):

A B C D
3 Decision Objective Constraints
4 0 =3*A4+5*A5 =A4<=4
5 0 =2*A5<=12
6 =3*A4+2*A5<=18
:opt max B4 vars A4:A5 st D4:D6

Status bar shows opt: OPTIMAL obj=36; A4 and A5 become 2.0 and 6.0; u rolls back.

Quadratic objectives

Objectives may contain squared decision variables and cross terms -- =(A1-3)*(A1-3), =A1^2 + A2^2, =A1*A2, =2*A1^2 + 3*A1 -- which covers least-squares fitting, quadratic cost curves, target tracking, and covariance-style objectives.

:opt min C1 vars A1:A2 st D1
opt: OPTIMAL  obj=0  (quadratic)

Solved exactly as a QP; there is no approximation and no accuracy knob.

The objective must be convex for a minimisation (or concave for a maximisation). Otherwise the optimum sits at a corner of the feasible region, which is a different and much harder problem, and gridcalc refuses it rather than returning a plausible wrong answer:

opt: objective is not convex, so it has no interior minimum -- ...

Convexity is checked directly on the Hessian (symmetric elimination, no numpy required), so the message names the real problem rather than surfacing a solver failure.

Sensitivity analysis and infeasibility diagnosis are withheld for quadratic models: their duals do not carry the shadow-price reading the report describes. Same call as for MIPs. Integer variables cannot be combined with a quadratic objective.

Inferring the model from a selection

The layout above already says what the model is. Select the block with v, then type :opt max (or min) and the components are read off it:

Cell contents Read as
formula rooted in a comparison (=A4<=4) a constraint
any other formula (=3*A4+5*A5) the objective
a plain number a decision variable
labels, blanks ignored

For the sheet above, selecting A3:D6 and typing :opt max is equivalent to :opt max B4 vars A4:A5 st D4:D6.

Exactly one non-comparison formula must be in the selection; more than one is ambiguous and reports the candidates so you can narrow it. Blanks are deliberately not treated as decision variables -- a selected rectangle is mostly whitespace, and promoting every gap to a variable would build a model you never described.

The inferred model is saved as default, so the block only has to be selected once: plain :opt re-runs it afterwards, and :w persists it.

Clauses (any order after st):

  • bounds A1=lo:hi, B2=lo:hi -- per-variable bounds. lo/hi accept inf, +inf, -inf. Default is [0, +inf).
  • int <cells> -- decision variables are integer-valued (branch-and-bound).
  • bin <cells> -- decision variables are binary ({0,1}); bounds clamped to [0,1].

Cell lists everywhere accept ranges (A1:A5), comma-separated refs (A1,A3,B5), or a mix.

Saved models live under "models": {<name>: ...} in the JSON file and round-trip verbatim (the spec strings the user typed are stored, not pre-resolved coords).

Sensitivity analysis

:opt sens [<name>] solves the model and then opens a report answering the question a bare optimum does not: what would change the answer?

Variable cells
   cell      value   reduced  obj coef coef from coef till
   A4            2         0         3         0       7.5
   A5            6         0         5         2       inf

Constraints   (* = binding)
   cell     shadow       rhs  activity     slack  rhs from  rhs till
   D4            0         4         2         2      -inf       inf
 * D5          1.5        12        12         0         6        18
 * D6            1        18        18         0        12        24
  • shadow price -- objective gain per extra unit of right-hand side. D5 is worth 1.5 per unit and D6 is worth 1, so buying more of the D5 resource pays better. D4 has slack and is worth nothing.
  • rhs from/till -- the range over which that shadow price holds. Past it the optimal basis changes and the price no longer applies.
  • reduced cost -- for a variable stuck at a bound, how much the objective would move per unit if it were forced in. Zero for any variable already active.
  • coef from/till -- how far an objective coefficient can move before the optimal mix changes.
  • * -- the constraint is binding (zero slack). Derived from slack rather than from a non-zero shadow price, since a degenerate optimum can bind at a price of zero.

Sensitivity is not reported for integer or binary models: a branch-and-bound dual describes one LP relaxation rather than the integer problem, so there is no valid shadow-price reading. :opt sens on such a model still solves it and says why the report is absent.

Writing the report into cells

:opt sens into <cell> writes the report into the sheet instead of paging it, anchored at the given cell:

:opt sens into F1

The numbers land as values, not text, so downstream formulas can reference them:

F13:  =G7*100        -> 150     (G7 holds a shadow price of 1.5)

That is the reason to write into cells rather than read a report: the results become part of the sheet's own computation. Re-running the command refreshes the block in place.

Layout, anchored at the target cell -- a blank row separates the two tables, and positions are stable so formulas keep working across re-runs:

Variables    value  reduced  obj coef  coef from  coef till
<one row per decision variable>

Constraints  shadow  rhs  activity  slack  rhs from  rhs till
<one row per constraint>

The write refuses to overwrite non-empty cells and names the first one blocking it; use into! to overwrite anyway. The whole rectangle belongs to the report, including the separator row, so stray values cannot end up sitting inside it. The write is a single undo step.

Unbounded ranging values are written as infinities and display as inf / -inf.

Parametric sweep

A shadow price answers what is the next unit worth. It cannot answer how much more should I buy, because it stops being valid at the edge of its rhs from/till range. :opt sweep re-solves across a range and shows where the value changes:

:opt sweep D5 6:24 9
D5 right-hand side from 6 to 24   (* = marginal value changed)
            rhs objective     delta    shadow  status
              6        27        --       2.5
   *          8        30         3       1.5
             12        36         3       1.5
             18        45         3       1.5
   *         20        45         0         0
             24        45         0         0

Read that as: capacity is worth 1.5 per unit up to 18, and nothing at all beyond it. Buy up to 18.

steps is the number of intervals (default 10), so the report has steps + 1 rows spanning the range inclusive. The optional trailing name selects a saved model other than default.

The sweep is read-only -- each point substitutes the right-hand side internally rather than editing the constraint formula, so the sheet is untouched and there is nothing to undo. Points where the model becomes infeasible or unbounded are kept in the series with their status, since learning that a level is unattainable answers the question too.

Available programmatically as opt.sweep(...), and the underlying substitution as solve(rhs_override={cell: value}) for one-off what-if questions.

Infeasibility diagnosis

An infeasible model reports which constraints contradict each other, not just that the model failed:

opt: INFEASIBLE  conflict: D1, D2 (2 of 5 constraints)

The named cells are an irreducible conflicting set: together they are still infeasible, and dropping any one of them makes the model solvable again. Constraints that merely happen to be present are not listed, which is the whole point -- narrowing 30 constraints to the 2 that actually fight is the difference between a dead end and a fix.

Found by a deletion filter (one solve per constraint, on the failure path only), so a three-way conflict with no contradictory pair is reported correctly where a pairwise check would miss it. Variable bounds are held fixed rather than dropped, so a constraint that contradicts its variable's bounds is reported as the conflict.

This runs automatically on every infeasible :opt; there is no separate command.

Unboundedness diagnosis

The mirror case. An unbounded model names the variable that can run away, rather than only reporting that no optimum exists:

opt: UNBOUNDED  unbounded: A5 -- add an upper bound or a constraint

A variable is reported when the constraints permit it to move without limit in the direction that improves the objective -- established by re-solving over the same feasible region with that variable as the objective, so it is an exact answer rather than a large-number heuristic. Variables with no objective coefficient are never blamed: moving them cannot change the objective, so they are not the cause even when they are themselves unbounded.

Like the infeasibility case, this runs automatically.

Programmatic access:

from gridcalc.engine import Grid
from gridcalc.opt import solve

g = Grid()
g.jsonload("examples/example_lp.json")
g.recalc()
r = solve(g, objective_cell=(1, 3), decision_vars=[(0, 3), (0, 4)],
          constraint_cells=[(3, 3), (3, 4), (3, 5)], maximize=True)
print(r.status_name, r.objective, r.values)

Goal-seek

For 1-D what-if ("what input makes this output equal X?"), use :goal:

:goal <formula_cell> = <target> by <var_cell> [in <lo>:<hi>]
:goal B10 = 100 by A1                 auto-bracket from A1's current value
:goal B10 = 0 by A1 in -50:50         explicit search bracket

Uses bisection over Grid.recalc(); converges in milliseconds at spreadsheet scale. The variable cell must hold a value (not a formula). On success the variable cell is overwritten; u rolls back. Unlike :opt, goal-seek isn't persisted -- the three args fit on one line, so retyping is faster than naming.

Formatting

:f b                Toggle bold (also Ctrl-B)
:f u                Toggle underline (also Ctrl-U)
:f i                Toggle italic
:f bi               Combine: bold + italic

:f $                Dollar (2 decimal places)
:f %                Percentage (value*100, 2 decimals)
:f I                Integer (truncate)
:f *                Bar chart (asterisks proportional to value)
:f L | R | G | D    Left / right / general / use-global-format

:f ,.2f             Any Python format spec: 1,234.50
:f .1%              15.7%
:f .2e              1.23e+04

:gf <fmt> sets the workbook-wide default format. :width <n> sets column width (4-40). Labels longer than the column width spill into adjacent empty cells, Excel-style.

Import / export

Command Reads Writes Notes
:csv save/load CSV CSV Plain text, fast
:xlsx save/load .xlsx formulas + values EXCEL-mode: formulas + cached values; other modes: values only :xlsx load switches to EXCEL
:pd save/load CSV/TSV/Excel/JSON/Parquet same Uses pandas; row 1 as headers

:xlsx load translates Excel formulas into gridcalc's EXCEL grammar and reads every sheet. INDIRECT and 3D ranges (Sheet1:Sheet3!A1:B2) are deliberately unsupported -- they'd defeat the static dep graph. Functions outside the auto-loaded library produce #NAME?.

File format

JSON, v2. v1 (single sheet, top-level cells) still loads.

{
  "version": 2,
  "mode": "HYBRID",
  "active": "Inputs",
  "code": "def margin(rev, cost):\n    return (rev - cost) / rev * 100\n",
  "names":  { "revenue": "A1:A12", "costs": "B1:B12" },
  "models": { "default": { "sense": "max", "objective": "B4",
                           "vars": "A4:A5", "constraints": "D4:D6" } },
  "sheets": [
    { "name": "Inputs", "cells": [["Rev","Cost"],[1000,600],[1200,700]] },
    { "name": "Summary","cells": [["Total","=SUM(Inputs!A2:A3)"]] }
  ],
  "format": { "width": 10 }
}
  • mode: "EXCEL" | "HYBRID" | "PYTHON". Absent → PYTHON.
  • sheets (v2): each is {name, cells} with a 2D cells array.
  • active (v2): name of the sheet to focus on load.
  • names: workbook-global named ranges (sheet-relative when used).
  • models: persisted LP/MIP definitions (see Optimization).
  • code: per-workbook Python module string, editable via :e.

Configuration

Optional gridcalc.toml (lookup: $PWD then $XDG_CONFIG_HOME/gridcalc/):

sandbox = true             # AST validation of formulas + code blocks
width   = 12               # default column width
format  = "G"              # default cell format

[keys.grid]
next_sheet  = ["Tab", "F4"]
prev_sheet  = ["S-Tab", "F3"]
cursor_left = ["Left", "h"]
cursor_down = ["Down", "j"]
cursor_up   = ["Up", "k"]
cursor_right= ["Right", "l"]

Every TUI context (grid, entry, visual, cmdline, search) is rebindable. User bindings fire before the hardcoded fallback chain, so Tab → next_sheet replaces the default cursor-right meaning. See docs/keybindings.md for the keyspec grammar (Tab, S-Tab, C-x, C-Right, F3, ...) and rejected combinations.

Command reference

File          :w [file]   :wq   :q   :q!   :o file   :e
Edit          :b   :clear   :dr   :dc   :ir   :ic   :m   :r
              :sort [col] [desc]   yank/paste: y/p (syncs system clipboard)
              undo/redo: u / Ctrl-R   (aliases: Ctrl-Z / Ctrl-Y)
              :recalc (or !) recompute every formula
Format        :f <spec>   :gf <spec>   :width <n>   Ctrl-B / Ctrl-U
Search        /pattern   n   N
Sheets        :sheets (picker)   :sheet [name|N|add|del|rename|move]
Names         :name <n> [range]   :names   :unname <n>
Modes         :mode [excel|hybrid|python]
Import/export :csv save/load   :xlsx save/load   :pd save/load
Optimization  :opt   :opt def   :opt run   :opt sens   :opt sweep
              :opt list   :opt undef
              :goal <cell> = <target> by <cell> [in <lo>:<hi>]
View          :view   E   :title <v|h|b|n>  (aliases :tv/:th/:tb/:tn)

Limitations

  • INDIRECT is unsupported (would defeat the static dep graph).
  • LAMBDA and its higher-order helpers (MAP, REDUCE, BYROW, ...) are unsupported; LET is supported. Dynamic-array results are packed into their origin cell rather than spilling into neighbours.
  • xlsx export of formulas is EXCEL-mode only -- PYTHON/HYBRID syntax (**, list comprehensions, py.*) isn't strict Excel.
  • 3D range refs (Sheet1:Sheet3!A1:B2) are unsupported (returns nan). Workaround: expand manually with +.
  • Cross-sheet ranges (Sheet1!A1:Sheet2!B5) are rejected at parse time -- Excel doesn't support them either.
  • xlsx dates and styles aren't read or written; date serials arrive as floats.

Development

make build      # rebuild the C++ extensions (_core, _opt)
make test       # unit tests
make test-tty   # PTY-driven curses integration tests (slow, requires xterm-256color)
make lint       # ruff check
make typecheck  # mypy
make qa         # lint + typecheck + test + format (Python and TypeScript)

make web-build  # compile the desktop app's React client into web/static/
make web-run    # launch the desktop app (needs web-build first)
make web-dev    # Vite dev server with HMR, against a mock bridge
make web-jstest # vitest suite for the client
make test-web   # Playwright tests driving the built bundle in headless Chromium

make wheel       # cpXX-cpXX wheel for current Python
make wheel-abi3  # single cp312-abi3 wheel (Python>=3.12)
make sdist       # source distribution
make publish     # upload to PyPI (after make check)

The abi3 build is gated on GRIDCALC_STABLE_ABI=ON (CMake) + wheel.py-api=cp312 (scikit-build-core). Per-version wheels and the abi3 wheel have separate CI workflows under .github/workflows/.

Prior Art

  • sc-im: A ncurses spreadsheet program for terminal
  • sheets: A terminal based spreadsheet tool
  • rustxl: A fast, keyboard-driven spreadsheet with vim-style navigation and Excel-compatible formulas.

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gridcalc-0.3.2.tar.gz (2.8 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

gridcalc-0.3.2-cp313-cp313-win_amd64.whl (2.8 MB view details)

Uploaded CPython 3.13Windows x86-64

gridcalc-0.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

gridcalc-0.3.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.26+ ARM64manylinux: glibc 2.28+ ARM64

gridcalc-0.3.2-cp313-cp313-macosx_11_0_arm64.whl (2.6 MB view details)

Uploaded CPython 3.13macOS 11.0+ ARM64

gridcalc-0.3.2-cp313-cp313-macosx_10_15_x86_64.whl (2.8 MB view details)

Uploaded CPython 3.13macOS 10.15+ x86-64

gridcalc-0.3.2-cp312-cp312-win_amd64.whl (2.8 MB view details)

Uploaded CPython 3.12Windows x86-64

gridcalc-0.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

gridcalc-0.3.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.12manylinux: glibc 2.26+ ARM64manylinux: glibc 2.28+ ARM64

gridcalc-0.3.2-cp312-cp312-macosx_11_0_arm64.whl (2.6 MB view details)

Uploaded CPython 3.12macOS 11.0+ ARM64

gridcalc-0.3.2-cp312-cp312-macosx_10_15_x86_64.whl (2.8 MB view details)

Uploaded CPython 3.12macOS 10.15+ x86-64

gridcalc-0.3.2-cp312-abi3-win_amd64.whl (2.8 MB view details)

Uploaded CPython 3.12+Windows x86-64

gridcalc-0.3.2-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.6 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

gridcalc-0.3.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.12+manylinux: glibc 2.26+ ARM64manylinux: glibc 2.28+ ARM64

gridcalc-0.3.2-cp312-abi3-macosx_11_0_arm64.whl (2.6 MB view details)

Uploaded CPython 3.12+macOS 11.0+ ARM64

gridcalc-0.3.2-cp312-abi3-macosx_10_15_x86_64.whl (2.8 MB view details)

Uploaded CPython 3.12+macOS 10.15+ x86-64

gridcalc-0.3.2-cp311-cp311-win_amd64.whl (2.8 MB view details)

Uploaded CPython 3.11Windows x86-64

gridcalc-0.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

gridcalc-0.3.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.11manylinux: glibc 2.26+ ARM64manylinux: glibc 2.28+ ARM64

gridcalc-0.3.2-cp311-cp311-macosx_11_0_arm64.whl (2.6 MB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

gridcalc-0.3.2-cp311-cp311-macosx_10_15_x86_64.whl (2.8 MB view details)

Uploaded CPython 3.11macOS 10.15+ x86-64

gridcalc-0.3.2-cp310-cp310-win_amd64.whl (2.8 MB view details)

Uploaded CPython 3.10Windows x86-64

gridcalc-0.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (3.7 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

gridcalc-0.3.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl (3.4 MB view details)

Uploaded CPython 3.10manylinux: glibc 2.26+ ARM64manylinux: glibc 2.28+ ARM64

gridcalc-0.3.2-cp310-cp310-macosx_11_0_arm64.whl (2.6 MB view details)

Uploaded CPython 3.10macOS 11.0+ ARM64

gridcalc-0.3.2-cp310-cp310-macosx_10_15_x86_64.whl (2.8 MB view details)

Uploaded CPython 3.10macOS 10.15+ x86-64

File details

Details for the file gridcalc-0.3.2.tar.gz.

File metadata

  • Download URL: gridcalc-0.3.2.tar.gz
  • Upload date:
  • Size: 2.8 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for gridcalc-0.3.2.tar.gz
Algorithm Hash digest
SHA256 3717381e14b77d8029bf81d8ba269f8eafd8417b4546033bdf1bac7f2e0de4ba
MD5 7d732e2ebb6ada78f338fe04315fad3f
BLAKE2b-256 fa6e9755de8aa378f568dd7d04a08b028f5775575390bf54b09c88f7da77da63

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp313-cp313-win_amd64.whl.

File metadata

  • Download URL: gridcalc-0.3.2-cp313-cp313-win_amd64.whl
  • Upload date:
  • Size: 2.8 MB
  • Tags: CPython 3.13, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for gridcalc-0.3.2-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 4abb63726207e0e56a4723f53ed6f8ba45ec4146d9f1026d83a7034cdc1c2bc1
MD5 66c788cab5585b5959fde0241d733afe
BLAKE2b-256 dffb80940a2a9fc263c29762a75ec2afcea4661aa622b4f9ba3c727830b80ba8

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 f108a75d464416d4e2bbd554bea285e9b9e8958b214560fe7abeb5801cd8f6d1
MD5 728d7de4428911b4eb0e019f43adbc55
BLAKE2b-256 078e81c00882e714732a8ae2bf725f1695b10f95651c0a8c86370a00048f6f87

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp313-cp313-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 96fd24f27fa8ec84395ca3333b5de5aca1e136169dae99f60695a9e367dbd9d1
MD5 f42b50353984e6686fd442f8999ce15e
BLAKE2b-256 070b7ce79eef7d4e2c5f6034f8a57f082ace92b1821e502a942e41edc800f820

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp313-cp313-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp313-cp313-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 86cd05ed841fa331cbdce4f31af2de64992ea10099459125338b342120fe33bd
MD5 8e467d07daca4708c9f8b6f2cfa22fa0
BLAKE2b-256 8b39ebae4fc3a8654385febed954dad1bf14bfb07831b51be7e5645838d6a2d4

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp313-cp313-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp313-cp313-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 3e1649e5fdb7ee9a49378503fab7906ac1a9f92dd05db6fb2bb69a6a5c28a447
MD5 4a9a45eea64dc1a65ae12955b6ac6ce9
BLAKE2b-256 5dc89b9d22c0ee269ea1714beecd58f35112c81dd20e884d057a0077d5992f46

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-cp312-win_amd64.whl.

File metadata

  • Download URL: gridcalc-0.3.2-cp312-cp312-win_amd64.whl
  • Upload date:
  • Size: 2.8 MB
  • Tags: CPython 3.12, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for gridcalc-0.3.2-cp312-cp312-win_amd64.whl
Algorithm Hash digest
SHA256 a416adbb84c8064146d3e3c757b897fb4c4fb2c15a3b3f4bcd3e9366182f9155
MD5 715ede7f481b08e1e85886c2c34db0d7
BLAKE2b-256 6760469937609e928b18d70df44993903c725db4d9cb759f8702e2892e3b38fa

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 d5af3a049af3eb3c4b5ccaa3a8c4f9833a41b4fd0314ebe340d7d4103c97f70e
MD5 eadbcb3958437408ff993135df15a3b9
BLAKE2b-256 01c4cfd465e8a0a8d220923612e2a28fd2561b242eedff6f634ea74545260c63

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 6ebd2f8f90cf4bb88e29f61c7ff4d20d368c90c05ba5a3c12c5990c7939d0cd3
MD5 b7268b5a6e392d9b79283ed97b65f6c2
BLAKE2b-256 8e8a1085dfcfbd07c02d48f6714d2a33b9e614f863cb027eb165d2bf32b4f6e5

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-cp312-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-cp312-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 0a9af457399406853bbcc5a3195c081cff0ee3601c570c5a17c84cf008fc69d4
MD5 adc09b8770d5527fb8772fd9e6f2d13a
BLAKE2b-256 a92590f8bbb09db4b2dae6d60b15b44ec8ef15ef7d36b798fb2dd7a6269f06e7

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-cp312-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-cp312-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 f932c576928dd43d34ea662f6e90d825a7963f9c8c65827b868ace547a135366
MD5 be270ea0ef8ce8724b5437c603516f15
BLAKE2b-256 cd9f65efacab78297b89913c8946dbe6a67589470372ab473fee6e334486d8ed

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-abi3-win_amd64.whl.

File metadata

  • Download URL: gridcalc-0.3.2-cp312-abi3-win_amd64.whl
  • Upload date:
  • Size: 2.8 MB
  • Tags: CPython 3.12+, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for gridcalc-0.3.2-cp312-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 61b0ca8bce63bb2203b42eac3dfb87aec35f940726fbe411fad0005f48b8e01a
MD5 60b40be86fad619f50e4fc707e110908
BLAKE2b-256 0a0962a43ddbe7311177060b26c437651c7858b6b836abb362cd31294409a310

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c0b6c84c46e9d27eef29e9d77b0e6b65802ac6428a964dc3d9491c69963b9b9e
MD5 4bc3c2ae7de2e57bc28df8eecc7b9852
BLAKE2b-256 457e6e4adad86e0322ab324cbd2b12bccd643c9b28082f99a04dd28b7985d087

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 312a5856d51a7c3300644d5a9887707711e9b7b62714811d7e6d2fce2ff073e9
MD5 fce5ae6714ccafc34dc30606bdb41624
BLAKE2b-256 a5d224f69b80decfced4eca98dd29458a7b28045cbd05d5428719c751ccee2f0

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 3f75a77a29f168cc2d30a349967ff80909d5ee99e5ec06aa8a6d3a2222761adb
MD5 3391a18261c0729b34c31bc0a02a0521
BLAKE2b-256 7efcc4e72011645778bd08e85150169b5b43e588e03739e49ebdec0e9c02b1a7

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp312-abi3-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp312-abi3-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 da7a74d5ab38c79cbe9648b5637fb50d5ca30c32e278871d52148aa986d7b6ee
MD5 93cba108d30c8518831b7f2c12614255
BLAKE2b-256 39ede033749af383abc5ce9addb73b505f9ee2ef97106e4b51cdc3d0b8d45506

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: gridcalc-0.3.2-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 2.8 MB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for gridcalc-0.3.2-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 8715141b0f843b576d4469f2e0471a926a6da8b110b17dbf5a9e52d81722c5d2
MD5 6547c2dd93e22ae245a8968f2b5c0157
BLAKE2b-256 33152f55356d966ab2613d638b6161db8c76b0064dff2400f0324c7f35816778

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c28fedc31f1941f5d40f84a012d3135ca0fd325aaf1ba735cf156d4eb814dd0b
MD5 e087ef94cfc0a3c9a7e71ae50de51a90
BLAKE2b-256 1b73b2cca07698a10eac272987e24a3de8164596bb6ee004697dd2d9dda9a855

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 7ed023f9315a45305cda9cbc0e089b9069a52f16e570ff018915fcbded67efae
MD5 fed85238213cc0287874e84923e22efd
BLAKE2b-256 3fa9c458e527c4a47959cb7df5328549ee0be2fff58390999b0645351233ebef

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 94d20a518431b84f471c6b3cd672142b4d724331bd4f1a946a46a7b3467eacfc
MD5 c0f39c1c546ad8ffac829416d40feddb
BLAKE2b-256 7cbee3b1f9714ab92bff0529a2d1b9f9a4ac581c3788cfd24e82682020115125

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp311-cp311-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp311-cp311-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 b72feced6cdc0873f164ce0bab68582df281bf8f2c4e4219d3d3d87a53ff75ca
MD5 45725c0a05de3d6fae3e2086105d86b8
BLAKE2b-256 19333087fedcfffc85fc3470c64c39179045e6c93e08cc597d310fa7466d26c6

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp310-cp310-win_amd64.whl.

File metadata

  • Download URL: gridcalc-0.3.2-cp310-cp310-win_amd64.whl
  • Upload date:
  • Size: 2.8 MB
  • Tags: CPython 3.10, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for gridcalc-0.3.2-cp310-cp310-win_amd64.whl
Algorithm Hash digest
SHA256 4af47bc5d7f29441c9f8cb1b54379903433ce5ae6605cb64567febdca26337b1
MD5 e66e59a8372ad7eb0a0b1b1c44328fe3
BLAKE2b-256 76c2f26edcfdbe7422191bb40bd8c59e84538dfc03999c3ea4b236075f5c3612

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 b9f26a3de3612310049f4559387c45623cb925f5fa72ce8e699b7446a1538a1f
MD5 be0a42c3afae72c0ee82c73847074f04
BLAKE2b-256 d59cb08168b3a502f6ae2566d6061b734359fb6c860d35927a0c7e1a60a4334a

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 c37392fe33fb7d42b804df97afdfcd38b6ba854f2eac0fec4ffec61044644a2f
MD5 9ddbf82647093a9c2593062b5c637076
BLAKE2b-256 d8382f5b72f9ca1a7ce43dbc5d14af270fc37d4d9aba9bcef3a48515abfbdf27

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp310-cp310-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp310-cp310-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 2bef3ac00d286a835254d684c53b815dd79f2497ee1f98753afbf809e074d026
MD5 842e76b74b6ae1fee98deadb66be029b
BLAKE2b-256 f8713df7af5a171e90b6612f93d900daad94306d133d777e02f94700a1ab6b24

See more details on using hashes here.

File details

Details for the file gridcalc-0.3.2-cp310-cp310-macosx_10_15_x86_64.whl.

File metadata

File hashes

Hashes for gridcalc-0.3.2-cp310-cp310-macosx_10_15_x86_64.whl
Algorithm Hash digest
SHA256 92b0c546c8b5726fbfb9178881a1f345c07fc3947d48ca22edbf8a6462abcafe
MD5 f87a1ff491ebb219f3e88b06a95defcd
BLAKE2b-256 78f9088310fda66526e4c0c5e431759b88ac5381d3b9689d87443c0c9c6d2968

See more details on using hashes here.

Release history Release notifications | RSS feed

0.5.1

26 files

0.5.0

26 files

0.4.0

26 files

0.3.3

25 files

This release

0.3.2 This release

26 files

0.3.0

26 files

0.2.0

26 files

0.1.3

2 files

0.1.2

2 files

0.1.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page