Help us shape the future of Go! Take the 2026 Go Developer Survey

gopyter

command module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 34 Imported by: 0

README

◆ gopyter

A Jupyter-style notebook for Go that runs in your terminal.

gopyter builds on the work of GoNB, the Go kernel for Jupyter by Jan Pfeifer: it runs cells the same way, shares its notebook format and cell commands, and implements its gonbui API and widgets, so GoNB notebooks run in gopyter too. See Acknowledgements.

gopyter demo

  • Real Go, cell by cell. Declarations persist across cells, the last expression is displayed, and compile errors point at cell lines
  • IDE-style completion from gopls, aware of everything earlier cells declared
  • Live diagnostics. With gopls, errors and warnings are underlined as you type; the message under the cursor appears in the footer
  • Symbol info. alt+k (or K in vim) shows the signature and docs of the function, type or variable under the cursor
  • Keyboard and mouse. Jupyter's modal keys, plus clickable toolbar, cell actions, context menus and text selection
  • Standard .ipynb files with a GoNB kernelspec, so notebooks also open in Jupyter, and GoNB notebooks (cell commands, gonbui, widgets) run in gopyter
  • Themes. gopyter's own Go-blue look plus the themes of kit (catppuccin, dracula, tokyonight, gruvbox, nord…), with light and dark variants
  • Inline Go console. gopyter console runs a REPL on the notebook kernel, with history, completion and scrollback you can copy
  • Headless runs for scripts and CI: gopyter run notes.ipynb --save
  • Live reload. Edits made to the notebook file by other programs show up automatically
  • Optional AI. Off until you pick a model; then e asks it to write or change a cell as you describe, and f to fix a failing one. Every proposal is checked to compile, and you see the diff before anything changes

Install

curl -fsSL https://raw.githubusercontent.com/mark3labs/gopyter/master/install.sh | bash

The script installs a checksum-verified release binary for Linux or macOS (amd64/arm64). Alternatively:

go install github.com/mark3labs/gopyter@latest

With Nix, the flake builds the latest release from source and installs it into your profile:

nix profile install github:mark3labs/gopyter

The pin to the newest tag moves automatically after every release; a flake input cannot say "latest tag", so flake.nix names one and the release workflow bumps it. Intel Macs are served by the two commands above, since nixpkgs no longer supports them.

Requirements: the Go toolchain on your PATH, since cells are compiled with it. For completion, symbol info and live diagnostics, install go install golang.org/x/tools/gopls@latest (without it, gopyter falls back to basic completion). The Go toolchain is not part of the Nix closure: cells are compiled with whatever go is on your PATH, so install Go from Nix too if the profile is all you have.

Agent skill

To teach a coding agent (Claude Code, Codex, Cursor, …) to use gopyter and write notebooks, install the skill from skills.sh:

npx skills add mark3labs/gopyter

Quickstart

gopyter hello.ipynb     # opens (or creates) a notebook, in edit mode
  1. Type some Go and press ctrl+r (or shift+enter) to run it and move to a new cell:

    type Point struct{ X, Y float64 }
    
    func (p Point) Dist() float64 { return math.Hypot(p.X, p.Y) }
    
  2. Use it in the next cell. The last expression is displayed as Out[n]:

    p := Point{3, 4}
    p.Dist()
    
  3. Press esc for command mode: j/k move between cells, b inserts one, m turns a cell into markdown, dd deletes, and ? shows every shortcut.

  4. ctrl+s saves; q quits.

For a guided tour: gopyter examples/tour.ipynb, then press A to run all cells. More example notebooks show off the rest:

Notebook Shows
examples/tour.ipynb the basics: declarations, results, markdown, streaming
examples/images.ipynb images, animation with nb.DisplayID, Game of Life, plotting
examples/terminal.ipynb colors, progress bars, full-screen animation, reading input
examples/data.ipynb variables across cells, tables, charts, HTML, nb.Cache
examples/widgets.ipynb sliders, buttons and selects driving live output
examples/magics.ipynb ! commands, %%writefile, %%sh, flags, %test, %goflags

They run headless too: gopyter run examples/images.ipynb.

How cells run

  • Top-level func, type, var, const and import declarations persist. Re-running a cell replaces what it declared. Everything else runs inside a generated main() in a fresh process.

  • Variables declared with := at the top level of a cell carry over too:

    a := 1           // cell 1
    fmt.Println(a)   // cell 2 prints 1
    

    gopyter declares them at package level. Each cell's program restores their values when it starts and saves them with encoding/gob when it ends, even after a panic, so changes made in later cells stick. Only what gob can encode is kept, which means exported struct fields. A non-nil error keeps its message. Values gob can't encode (channels, mutexes, *regexp.Regexp, ...) aren't kept, and gopyter says so. Pointers are restored as copies. f := func(...) {...} becomes a persisted declaration. Variables inside blocks (for, if, ...) stay local.

  • A persisted var is re-initialized in every later cell (each cell is a new process). To compute an expensive value once, wrap it in nb.Cache or nb.CacheErr, which store the result (gob-encoded, so exported fields only) in the kernel workspace:

    var resp, err = nb.CacheErr("resp", func() (*jev.Response, error) {
        return client.SystemOne(ctx, msg, criteria)
    })
    

    CacheErr does not store failed results. %cache clear resp forces a recompute.

  • Imports are added automatically, and third-party modules are fetched on first use. Until a cell importing a third-party module has been built, live diagnostics may report missing imports or undefined package names. Diagnostics also omit parse errors located in generated wrapper code (such as an unclosed block at EOF).

Rich output

gopyter's API is package nb (github.com/mark3labs/gopyter/nb), which cells use without importing it:

Function Shows
nb.Display(v...) values; an image.Image is drawn
nb.DisplayMarkdown(s) rendered markdown
nb.DisplayPNG(data) PNG bytes
nb.DisplayID(id, v...) like Display, replacing the last output with the same id
nb.DisplayMarkdownID(id, s) like DisplayMarkdown, replacing by id
nb.Cache, nb.CacheErr (see above)

A cell's trailing expression is displayed too. Notebooks written before nb can keep calling Display, DisplayMarkdown, Cache, ... without the nb. prefix: gopyter still declares these names, but only when the notebook doesn't declare them itself, so a notebook can have its own Display.

  • Images are drawn in the output: nb.Display an image.Image (or end the cell with one), or pass PNG bytes to nb.DisplayPNG(data). They are drawn with the Kitty graphics protocol on kitty and Ghostty, or colored half blocks (two pixels per character) elsewhere, scaled to fit the cell, and saved in the notebook as image/png, so Jupyter shows them too. Images in notebooks from Jupyter (PNG, JPEG, GIF) are shown as well. gopyter run draws them when its output is a color terminal and prints [image WxH] otherwise.
  • nb.DisplayID(id, v...) and nb.DisplayMarkdownID(id, s) replace the output they showed earlier with the same id, for animations and progress.
  • Program output keeps its colors, and is shown like a terminal would show it: \r, cursor movement and clearing the screen work, so progress bars and redrawing demos look right.
  • Programs can read their standard input. While a cell runs, an input line appears under it: press enter on the cell (or alt+i, or click it), type, and press enter to send a line or ctrl+d to end the input (see Widgets for what else it ends). gopyter run passes its own standard input to the programs.

Cells can also hold commands and magics. Most follow GoNB's special commands and behave the same way:

Cell command Effect
!cmd run a shell command in the kernel workspace (e.g. !go get pkg@v1); a trailing \ continues it; it runs in a terminal, so its output keeps colors, progress bars and spinners (see below)
%reset forget all declarations and saved variables
%reset go.mod start the workspace's go.mod over
%env K=V set environment variables for your programs
%args a b set program arguments
%% [args] call flag.Parse() first, with these arguments for this cell (also %main)
%exec fn [args] run fn() after parsing flags
%test [flags] build with go test and run the cell's tests and benchmarks
%goflags [flags] show or set extra go build flags (-race, -tags=x); %goflags "" clears them
%autoget / %noautoget fetch missing modules automatically, or not
%capture [-a] file also write the cell's output to a file
%ls list persisted declarations
%rm name... forget declarations or imports
%cache list cached values
%cache clear [key...] delete cached values
%workspace print the kernel workspace directory
%version print the gopyter and Go versions
%help show this list

Cell magics must be the first line of a cell and take the rest of it:

Cell magic Effect
%%writefile [-a] file write the cell to a file (-a appends)
%%bash, %%sh run the cell as a shell script
%%script cmd run cmd with the cell as its input (e.g. %%script python3)

Files and scripts are relative to the directory programs run in, so a program can read what %%writefile wrote. Commands can also be written as //gonb:%..., and !*cmd is accepted like !cmd, so GoNB notebooks run unchanged. Each cell can have its own func init(); GoNB's init_xxx() functions work too.

Shell commands in a terminal

Shell commands (!cmd, %%bash, %%script) run in a pseudo-terminal as wide as the cell's output, so they see the terminal a command expects: they colorize their output and draw progress bars and spinners, and gopyter replays that while the command runs. crush's bang mode (same idea) asks for color with the environment alone; a real terminal also gives the cursor movement and erasing that redraws need. The terminal's own line endings don't reach the output, and what a command writes to stderr comes out on the same stream as its stdout, as in a terminal. gopyter only fills in what the environment is missing, so TERM, COLORTERM or FORCE_COLOR that you set are kept, and NO_COLOR turns the color off.

This is for the terminal UI. gopyter run writes to a file or a pipe, so its shell commands keep plain pipes and no escape codes reach its output.

Widgets and GoNB notebooks

gopyter implements GoNB's notebook packages (gonbui, widgets, comms, dom), adapted from GoNB's own, so GoNB notebooks run unchanged and cells can use its widgets. GoNB draws them in Jupyter with HTML and JavaScript; gopyter draws them in the terminal:

import "github.com/janpfeifer/gonb/gonbui/widgets"

%%
iters := widgets.Slider(10, 500, 100).Done()
for n := range iters.Listen().LatestOnly().C {
    nb.DisplayID("fractal", Mandelbrot(320, 240, n)) // your own func
}
  • Widgets: widgets.Button, widgets.Slider and widgets.Select, with GoNB's builder API (Done, Listen, Value, SetValue, AppendTo...). They are drawn in the cell's output while it runs. Press enter on the running cell to focus the first one and tab to move between them and the input line. ←/→ move a slider (pgup/pgdown by 10%) or change a select, and enter presses a button or lists a select's options. The mouse works too: click a slider's track, a button, or a select.
  • Done: the ✓ done button under the cell (or ctrl+d) tells the program the user is done. It closes every Listen channel, so for v := range ch.C loops end, and ends stdin. Interrupting (ctrl+c) stops the program instead. With gopyter run nobody can use the widgets, so they are done at once.
  • gonbui: DisplayHTML, DisplayMarkdown, DisplayImage, UpdateHTML and UpdateMarkdown (updatable by id), RequestInput (focuses the input line, hidden for passwords), EmbedImageAsPNGSrc...
  • HTML output is drawn as text: formatting, headings, lists, tables, preformatted text and data: images; styles and scripts are ignored, SVG shows a placeholder. It's saved as text/html, so Jupyter shows the real thing.
  • dom changes displayed HTML by element id (Append, SetInnerHtml, SetInnerText, GetInnerHtml, Remove...), and comms exchanges values with the front-end by address (Listen, Send, ReadValue).
  • JavaScript can't run in a terminal: dom.TransientJavascript is ignored, script loaders return an error, and gonbui/plotly and %wasm aren't available.

These packages ship with gopyter and need no download: the kernel workspace's go.mod points github.com/janpfeifer/gonb and github.com/mark3labs/gopyter at local copies. They are gopyter's implementation, not GoNB itself: things that need a browser are missing (see above), and the rest may differ in details. In a cell starting with %%, as in GoNB, variables stay in that cell.

Keys

gopyter is modal like Jupyter: command mode (blue) works on cells, edit mode (green) edits text. Press ? for the full list, or ctrl+p (or :) to search and run any command.

Key Action
enter / esc edit mode / command mode
ctrl+r or shift+enter run cell and advance
ctrl+j or ctrl+enter run cell
A / R / ctrl+c run all / restart kernel / interrupt
alt+i type input for the running program
enter on a running cell focus its widgets (tab next, ctrl+d done)
j k g G move selection
a / b insert cell above / below
dd / z delete / undo delete, merge or split
alt+↑ / alt+↓, shift+click extend cell selection (command mode)
alt+a select all cells (command mode)
alt+m merge selection, or merge with next
alt+- split at cursor (edit mode, including vim)
x c v, K / J cut / copy / paste, move cell up / down
m / y / r convert to markdown / code / raw (ctrl+t toggles code/markdown while editing)
I hide/show code or raw input (outputs stay visible)
< / > scroll text output left/right; first > stops wrapping, < at column zero restores it
E edit the selected cell in $VISUAL / $EDITOR (fallback: vi)
C copy the cell's output to the clipboard
alt+r restart kernel and clear idle cells' outputs (both modes)
alt+s save as a new path (both modes)
alt+shift+f format the selected Go cell with gofmt
o / O fold long output / clear output
D clear every cell's output
ctrl+b sidebar: contents and variables (tab switches)
ctrl+s / q save / quit
T pick a color theme
ctrl+p or : command palette: type to find a command, enter runs it
V turn vim bindings on / off (saved)
M pick the AI model, or turn AI on / off
e ask AI to write or change a cell (when AI is on)
f fix a failing cell with AI (when AI is on)

The sidebar starts hidden. Contents lists markdown headings (levels 1–3); Variables shows persisted declarations with types and one-line value previews from the last saving run. tab switches panels, arrows move, enter jumps to the declaring cell, and esc returns focus to the notebook. It automatically hides in narrow terminals and returns when there is room again.

Run, delete, cut/copy/paste, move, convert and clear-output commands apply to every selected cell. Other cell commands use the cursor cell. esc, navigation, plain click and entering edit mode collapse the range. Paste selects the pasted cells. Merge keeps the first cell's type and metadata, comments prose into Go and fences Go into markdown; running or queued cells cannot be merged or split. Split keeps outputs on the original cell. Cell-level z retains the last 100 delete/merge/split operations, separate from editor undo; undoing a split after editing either half reports can't undo split: cell changed rather than losing edits.

Some terminals intercept alt+arrow; shift+click is the selection fallback. In macOS Terminal, enable Use Option as Meta (otherwise Option word motions or alt+- typing – may be intercepted).

Format a code cell with alt+shift+f (in command or edit mode), or the fmt button at the bottom right of the selected/hovered code cell. Formatting uses Go's gofmt formatter, preserves notebook commands and source order, and is one undo step; it does not execute code. Non-Go cell-magics such as %%bash and %%writefile are left untouched. gopyter format file.ipynb formats all code cells in place, preserving metadata and outputs; if any cell has invalid Go syntax, the file is left unchanged. Markdown and raw cells are never formatted.

In edit mode: tab or ctrl+space completes, shift+arrows select, ctrl+c/ctrl+x copy/cut, ctrl+z/ctrl+y undo/redo, and ↑/↓ flow between cells. alt+k (or F1) shows the signature and documentation of the symbol under the cursor, or of the enclosing call when the cursor is among its arguments; pgup/pgdn scroll it and any other key dismisses it. shift+enter and ctrl+enter need a terminal with the kitty keyboard protocol (kitty, Ghostty, WezTerm, foot…); ctrl+r and ctrl+j work everywhere.

Custom keys

The table above lists defaults. Run gopyter keys to list all command names, current keys, defaults and descriptions (* marks configured commands). Add a keys object to config.json:

{
  "theme": "nord",
  "keys": {
    "run_all": ["A", "ctrl+a"],
    "quit": [],
    "move_up": ["alt+k"]
  }
}

Each list replaces that command's defaults; [] or null unbinds it. Omitted commands keep their defaults. escape cannot be left unbound. Keys use Bubble Tea names such as ctrl+s, alt+enter, shift+up, A, ?, f1, pgdown and space. Modifiers are normalized to ctrl, alt, shift, meta, hyper, super order. Use A, not shift+a; with modifiers use ctrl+shift+a, not ctrl+A. Delete still requires pressing its key twice.

Unknown commands, malformed keys and conflicts produce warnings on stderr and in the startup status bar; the rest of the settings still apply. A key cannot belong to two commands in the same mode; global bindings conflict with both command and edit modes. The overridden command loses the conflicting key; if both were overridden, the later command in gopyter keys loses it. Command and edit bindings do not conflict with each other. Help, hints and tooltips reflect the configured keys, and mouse actions work even when unbound.

User bindings take priority over fixed editor keys (for example rebinding undo to ctrl+u replaces kill-to-start); these shadows are not warned about. Vim, dialogs, menus, pickers, input lines, widgets and alt+shift+f are not configurable here. There are no per-notebook or command-line key overrides.

Vim bindings

Press V in command mode to turn vim keys on or off. The setting is saved alongside the theme ("vim": true in the config file described under Themes), so it sticks across sessions; gopyter --vim or --vim=false overrides it for one session. With vim keys on, edit mode has vim's own modes, shown in the footer: enter opens a cell in NORMAL, esc goes from INSERT to NORMAL and from NORMAL back to command mode. New cells open in INSERT.

Keys Action
i a I A o O insert / append / open a line
h j k l w b e W B E move (j/k flow between cells)
0 ^ $ gg G line start / end, first / last line
d c y + motion, dd cc yy delete / change / yank (with counts: 3dw, 2dd)
x X D C s S Y J the usual shorthands
p P put after / before
v V, then d c y o visual and visual-line mode
u / ctrl+r undo / redo
K symbol info (signature and docs)

Yanks also go to the system clipboard, and a mouse selection turns into a visual selection. Since ctrl+r is redo in NORMAL mode, run cells from there with shift+enter or ctrl+j (ctrl+r still runs from INSERT and command mode).

Raw cells are plain text and never run. Hiding input is saved as Jupyter's metadata.jupyter.source_hidden; entering edit mode shows it again. Duplicated cells start with visible input. Sideways scrolling affects stdout, stderr and text results only, and is not saved. Programs that wrap their own terminal output keep doing so. Restart-and-clear interrupts a running cell, but leaves its output until it finishes, just like clearing outputs with D. Save-as moves file watching to the new path, but does not move the kernel's run directory.

Mouse

Click the sidebar's panel headers to switch panels, or an entry to jump to its cell. The wheel over the sidebar scrolls its list independently of the notebook.

Click a cell to edit it, and drag, double-click or triple-click to select text. Click a hidden-input placeholder to show the source. Under unwrapped text output, click ◂ / ▸ to scroll or wrap to restore wrapping. Hover [n] and click [▶] to run a cell; the buttons on a cell's border run, convert, move, duplicate or delete it. Right-click opens a context menu, and the toolbar under the title covers running, adding cells, saving, themes and help. Hovering any button shows what it does and its shortcut. Hold shift while dragging to use your terminal's own selection instead (alt/option in some terminals).

Themes

Press T (or click ◐ theme) to pick a theme. Moving through the list previews each one live; enter keeps it and esc restores the previous one. The default is gopyter's own theme; the others are the built-in themes of kit. Each has a light and a dark variant, chosen from your terminal's background.

The choice is saved to $XDG_CONFIG_HOME/gopyter/config.json (usually ~/.config/gopyter/config.json; ~/Library/Application Support/gopyter/ on macOS):

{ "theme": "catppuccin", "keys": { "run_all": ["ctrl+a"], "quit": [] } }
gopyter themes                     # list themes, marking the active one
gopyter --theme dracula notes.ipynb  # use a theme for this session only
gopyter --syntax-theme monokai     # override the code highlighting (any chroma style)

Images

--graphics=auto (the default) detects kitty and Ghostty from the environment, without terminal queries. --graphics=kitty forces Unicode-placeholder images; --graphics=blocks always uses portable half blocks. Both the TUI and gopyter run accept the flag. The saved default can be set in the settings file:

{ "graphics": "blocks" }

Kitty graphics needs at least 256 colors. In tmux (3.3+), enable passthrough with set -g allow-passthrough on; otherwise auto falls back to blocks and a forced kitty request warns. WezTerm is not auto-detected because its Kitty support does not yet include Unicode placeholders. Images scroll and clip like text, and menus cover them normally (images themselves are not dimmed).

AI (optional)

AI features are off, and invisible, until you choose a model. gopyter uses kit, so any provider kit supports works, including local models through Ollama.

Press M to pick a model from kit's catalog: type to filter, enter to use it. Models whose provider has no API key are dimmed, with the variable to set. Ollama lists the models installed on your Ollama server (OLLAMA_HOST, localhost:11434 by default). Off turns AI off but remembers the model, and the picker opens on whichever does the opposite of the current state, so M enter toggles AI on and off. M is only listed in the help (?) while AI is off; nothing else shows. The same from the command line:

gopyter model                      # show the setting and which providers have a key
gopyter model anthropic            # a provider's default model
gopyter model openai/gpt-5.6       # or any provider/model
gopyter model ollama/qwen3-coder   # a local model, no key needed
gopyter model off                  # turn AI off (the model is remembered)
gopyter --model off notes.ipynb    # for one session only (like --theme)

API keys come from the provider's usual environment variable, like ANTHROPIC_API_KEY or OPENAI_API_KEY. gopyter never stores them. The model is saved as "ai_model" (and "ai_off": true while off) in the settings file described under Themes.

To have a code cell written or changed, press e (or click ✦ on the cell, or use the context menu) and say what it should do: "read data.csv and sum the second column", "draw a histogram of xs", "make this concurrent". On an empty cell the model writes it from scratch. What you typed is kept until a change is applied, so after Discard or an error, e lets you refine the request instead of retyping it.

When a cell fails, press f (or click ✦ fix on the cell, or use the context menu) to have it fixed.

Either way, the model gets the cell, its error if it failed, and the code of the cells above it, but not their outputs. Its proposals are compiled with the rest of the notebook, without running anything, and it retries until one compiles. You then see a diff: Apply replaces the cell (undo with ctrl+z while editing), Discard keeps it. The cell isn't run for you. esc cancels a request in progress; one request runs at a time.

The agent only has that compile check as a tool: it can't read files, run commands or run your code. It also ignores kit's own setup, like .kit.yml, AGENTS.md, skills and MCP servers, so opening a notebook in a project that configures kit doesn't change what it can do. To build gopyter without AI support, and without the kit dependency, see Development.

Headless

gopyter run notes.ipynb            # run every code cell, print the outputs
gopyter run notes.ipynb --save     # …and write the outputs into the notebook
gopyter run notes.ipynb --fail-fast
gopyter format notes.ipynb         # format code cells in place, without running

It exits non-zero if any cell fails, so it works as a CI check for notebooks (130 or 143 when stopped by SIGINT or SIGTERM). On kitty/Ghostty, images use Kitty graphics, with the same --graphics override as the TUI. Graphics commands are never written to redirected output, even when colors are forced; it keeps half blocks or [image WxH] descriptions. On a terminal, outputs that update (nb.DisplayID, gonbui.UpdateHTML, DOM changes) are redrawn in place. When the output isn't a terminal, they're printed once, in their final state, when the cell ends, so logs don't get a line per animation frame.

Console

gopyter console                      # interactive Go, on one notebook kernel
gopyter console --save session.ipynb # save every executed input and its outputs on exit
gopyter console < snippet.go          # line mode for scripts and agents

Declarations and := values persist just as in notebooks; a trailing expression prints => value. Magics and !shell lines work too. Finished inputs are syntax-highlighted in normal terminal scrollback (no alternate screen or mouse capture); the live editor is plain text. Outputs use the headless runner's rendering, without success timing lines. Widgets are inactive; use the notebook UI to interact with them.

  • enter runs complete input, or adds a newline with the current indentation. Open brackets, raw strings, block comments, trailing operators and continued ! commands wait for more input. End a %% cell magic with a blank line.
  • alt+enter always adds a newline; ctrl+r / ctrl+j force execution even when input is incomplete, so the compiler can report errors.
  • up on the first line / down on the last line browse history, restoring your draft after the newest entry. History is stored as JSON lines in console_history beside config.json, with permissions 0600, capped at 1000 inputs and without consecutive duplicates. Inputs can contain secrets: avoid typing them into history, or remove the history file afterwards.
  • tab completes a word (gopls, or the basic fallback), lists alternatives and cycles through them; in leading whitespace it indents. --no-complete disables completion.
  • While running, stdin> sends a line to the program on enter; ctrl+d closes its input. The unfinished output line stays visible above it.
  • ctrl+c interrupts execution, or clears idle input (on empty input it shows an exit hint). ctrl+d exits on empty idle input, otherwise deletes the next character. ctrl+l clears the screen.

%save file.ipynb saves the inputs executed so far, with execution counts and outputs; %save -f file.ipynb permits overwriting. The save command itself is not recorded. --save FILE saves on exit, overwriting the destination. Open the result with gopyter file.ipynb or Jupyter with GoNB.

When stdin is not a terminal, complete inputs are grouped by the same rules and run without a TUI. Programs receive no stdin (it belongs to the script), and any failed input makes the command exit non-zero. --workdir selects a persistent kernel workspace as with the notebook commands.

Serving over SSH

gopyter serve runs a separate notebook TUI, temporary kernel workspace and completion engine for every SSH session, inspired by euporie-hub.

gopyter serve ~/notes --authorized-keys ~/.ssh/authorized_keys
ssh -t -p 2222 localhost analysis.ipynb   # open or create ~/notes/analysis.ipynb
ssh -t -p 2222 localhost sub/plots.ipynb  # paths are relative to ~/notes
ssh -t -p 2222 localhost                 # new unnamed notebook

The directory defaults to .. A PTY is required (ssh -t). Commands must be one relative .ipynb path; command paths and save-as names cannot escape the directory, including through symlinks. Missing notebooks are created on save. Disconnecting cancels running cells and AI requests and closes that session's completion engine and kernel.

Flag Default / purpose
--authorized-keys FILE Required OpenSSH public-key allowlist; unreadable, empty or malformed files prevent startup
--listen ADDRESS 127.0.0.1:2222; choose a non-loopback address deliberately to expose it to the network
--host-key FILE gopyter/ssh_host_ed25519 in the user config directory; a missing ed25519 key is generated with mode 0600
--theme NAME, --light saved theme, dark by default; one shared theme for all sessions, with the theme picker disabled
--vim, --no-complete, --model same per-session defaults as the local TUI; --no-complete avoids per-session gopls

--workdir is not supported: each session needs its own workspace. The startup log prints the address, authorized_keys path and host key's SHA256 fingerprint. Compare that fingerprint with SSH's first-connect prompt before accepting it (or inspect the public key with ssh-keygen -lf HOST_KEY.pub). Password and keyboard-interactive authentication are never offered; there is no anonymous mode.

Security: Everyone whose key is in --authorized-keys can run any code as the user running gopyter serve: use it for yourself or a team that already shares that account. This is not a multi-tenant notebook hub. Cells and shell commands have that user's files, credentials, network and environment, including API keys; remote AI uses the server's AI configuration and credentials. Path confinement prevents mistakes, not a sandbox: a cell can still write anywhere that OS user can. Treat giving someone access exactly like giving them a shell. Each session also costs a workspace, gopls process (unless disabled), and Go builds; session limits are not provided.

Sessions opening the same notebook keep separate in-memory copies and kernels. A notice warns when another session already has the file open. Saves use atomic replacement, so files are not torn, but the last save wins. Clean sessions reload within a second; dirty sessions show Reload / Keep mine, as described in Editing outside gopyter.

Editing outside gopyter

Press E in command mode to edit a cell in $VISUAL, then $EDITOR, falling back to vi. Space-separated arguments such as code --wait are supported. Saving and quitting applies the source as one undo step without running it. Jupyter's source_hidden metadata is kept when saving and reloading notebooks.

gopyter checks the notebook file once a second. If another program changes it, for example an editor or gopyter run --save, gopyter reloads it and keeps your place. If you have unsaved changes, or the cell you're editing changed, it asks first. Reload loads the file and discards your changes. Keep mine (or esc) keeps your version, and the next save overwrites the file. Reloading doesn't reset the kernel, so declarations from cells you already ran stay in effect. External edits to cell text can be undone.

Acknowledgements

The Contents and Variables sidebar is inspired by euporie (behavior, not code).

Multi-cell selection, merge and cursor split follow euporie's notebook operations.

gopyter owes a lot to GoNB, the Go kernel for Jupyter by Jan Pfeifer:

  • How cells run. Like GoNB, gopyter keeps each cell's declarations, writes a Go program with a main() for the statements, builds it with go build and runs it. Compile errors are mapped back to cell lines.
  • Notebook format. Notebooks use GoNB's kernelspec, so the same files open in Jupyter with GoNB.
  • Cell commands. !, %%/%main, %args, %env, %exec, %test, %goflags, %capture, %autoget, %ls/%rm, %reset, the cell magics (%%writefile, %%script...) and //gonb: prefixes follow GoNB's special commands, as do init_xxx() functions.
  • gonbui, widgets, comms and dom. gopyter's versions of these packages keep GoNB's API and adapt its documentation, and parts of its code (such as comms.ConvertTo), so GoNB notebooks run unchanged. They're in internal/kernel/runtime/gonb, with GoNB's MIT license.

gopyter's own parts (the terminal UI, drawing images, HTML and widgets as text, the nb package, variables kept across cells, completion, AI) are separate work. To run Go notebooks in Jupyter itself, use GoNB.

The inline console's scrollback-first interaction and conversion to a notebook follow euporie's console; its execution model remains the same GoNB-inspired kernel used by notebooks.

The Kitty Unicode-placeholder image approach is borrowed from euporie: transmit once, place virtually, and let ordinary text cells handle scrolling, clipping and compositing.

The SSH notebook server model is borrowed from euporie-hub in euporie.

Development

go test -race ./...
golangci-lint run ./...
go run . examples/tour.ipynb
go build -tags noai .      # without AI support: no kit dependency, a much smaller binary

See AGENTS.md for the architecture, conventions and testing workflow. Releases are cut by pushing a v* tag; GoReleaser builds and publishes the binaries.

License

gopyter is released under the MIT License. The GoNB compatibility packages in internal/kernel/runtime/gonb are adapted from GoNB, also under the MIT License: see its license, which release archives include as third_party/gonb/LICENSE.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
ai
Package ai provides gopyter's optional AI assistance, built on the kit SDK (github.com/mark3labs/kit).
Package ai provides gopyter's optional AI assistance, built on the kit SDK (github.com/mark3labs/kit).
complete
Package complete provides code completion for notebook cells, backed by gopls when available and by a simple identifier completer otherwise.
Package complete provides code completion for notebook cells, backed by gopls when available and by a simple identifier completer otherwise.
config
Package config persists user settings across gopyter sessions.
Package config persists user settings across gopyter sessions.
console
Package console implements an inline REPL on the notebook kernel.
Package console implements an inline REPL on the notebook kernel.
htmlview
Package htmlview draws HTML output in the terminal and applies the front-end operations of a cell's program to it (DOM changes, widget values), for GoNB's gonbui, dom and widgets packages (https://github.com/janpfeifer/gonb), which draw these in a browser.
Package htmlview draws HTML output in the terminal and applies the front-end operations of a cell's program to it (DOM changes, widget values), for GoNB's gonbui, dom and widgets packages (https://github.com/janpfeifer/gonb), which draw these in a browser.
kernel
Package kernel implements a Go "kernel": it accumulates declarations across notebook cells, generates a Go program for each execution, compiles it and streams its output back.
Package kernel implements a Go "kernel": it accumulates declarations across notebook cells, generates a Go program for each execution, compiles it and streams its output back.
kernel/runtime/gonb/gonbui
Package gonbui is gopyter's implementation of GoNB's gonbui package, so notebooks written for GoNB (https://github.com/janpfeifer/gonb) run in gopyter.
Package gonbui is gopyter's implementation of GoNB's gonbui package, so notebooks written for GoNB (https://github.com/janpfeifer/gonb) run in gopyter.
kernel/runtime/gonb/gonbui/comms
Package comms is gopyter's implementation of GoNB's gonbui/comms package: values exchanged with the front-end, by address.
Package comms is gopyter's implementation of GoNB's gonbui/comms package: values exchanged with the front-end, by address.
kernel/runtime/gonb/gonbui/dom
Package dom is gopyter's implementation of GoNB's gonbui/dom package: changes to HTML already displayed, by element id.
Package dom is gopyter's implementation of GoNB's gonbui/dom package: changes to HTML already displayed, by element id.
kernel/runtime/gonb/gonbui/protocol
Package protocol mirrors GoNB's gonbui/protocol package: the types exchanged with the front-end.
Package protocol mirrors GoNB's gonbui/protocol package: the types exchanged with the front-end.
kernel/runtime/gonb/gonbui/widgets
Package widgets is gopyter's implementation of GoNB's gonbui/widgets package: buttons, sliders and selects in the cell's output, drawn by gopyter in the terminal.
Package widgets is gopyter's implementation of GoNB's gonbui/widgets package: buttons, sliders and selects in the cell's output, drawn by gopyter in the terminal.
kernel/runtime/gopyter/nb
Package nb is gopyter's notebook API: rich output and caching for cells.
Package nb is gopyter's notebook API: rich output and caching for cells.
kernel/runtime/gopyter/nb/wire
Package wire connects a cell's program to gopyter: rich output goes to file descriptor 3 and events from the UI (widget values, replies, "done") come from file descriptor 4, both as JSON lines.
Package wire connects a cell's program to gopyter: rich output goes to file descriptor 3 and events from the UI (widget values, replies, "done") come from file descriptor 4, both as JSON lines.
lsp
Package lsp is a minimal Language Server Protocol client, sufficient to drive gopls for code completion.
Package lsp is a minimal Language Server Protocol client, sufficient to drive gopls for code completion.
markdown
Package markdown renders Markdown for the terminal with herald-md, themed from gopyter's palette and wrapped to a width.
Package markdown renders Markdown for the terminal with herald-md, themed from gopyter's palette and wrapped to a width.
notebook
Package notebook reads and writes Jupyter notebooks (nbformat v4), using a kernelspec compatible with GoNB (https://github.com/janpfeifer/gonb) so notebooks can be shared with Jupyter.
Package notebook reads and writes Jupyter notebooks (nbformat v4), using a kernelspec compatible with GoNB (https://github.com/janpfeifer/gonb) so notebooks can be shared with Jupyter.
palette
Package palette defines gopyter's default colors, shared by the UI and runner.
Package palette defines gopyter's default colors, shared by the UI and runner.
runner
Package runner executes notebooks headlessly, printing styled output.
Package runner executes notebooks headlessly, printing styled output.
serve
Package serve runs one notebook TUI and kernel per SSH session, inspired by euporie-hub (https://github.com/joouha/euporie).
Package serve runs one notebook TUI and kernel per SSH session, inspired by euporie-hub (https://github.com/joouha/euporie).
termimg
Package termimg draws images as text: each terminal cell shows two vertically stacked pixels with the upper half block "▀" (foreground = top pixel, background = bottom pixel).
Package termimg draws images as text: each terminal cell shows two vertically stacked pixels with the upper half block "▀" (foreground = top pixel, background = bottom pixel).
ui
Package ui implements the gopyter terminal notebook.
Package ui implements the gopyter terminal notebook.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL