Changelog

Every release of Tinkr, newest first, written from its commits.

0.4.3

On GitHub

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

Fixed

  • editor: let every press of Run be a run f285be9

0.4.2

On GitHub

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

Fixed

  • editor: send a run when it is pressed, to the document on screen 29ef194

Other

  • release: sign and notarize the macOS build 1bc76e4
  • say the mac build is unsigned, and how to open it 65eae00

0.4.1

On GitHub

Tinkr 0.4.1

Laravel Cloud connections, a Save button for connection edits, and three container fixes found by running the app against deployed projects.

Laravel Cloud

A project deployed on Laravel Cloud can now be a connection. Paste an API token in Settings → Laravel Cloud, pick an application and one of its environments, and Run sends your code there.

  • The token is checked against the API before it is kept. It lives in app/secrets.json (mode 0600) and is never shown again; the settings say one is kept and offer Replace and Forget.
  • The token needs Applications: View applications and Environments: View environments, View environment instances, Create environment commands, View environment logs. Everything else can stay at None.
  • The application and environment are picked from your account, with each environment's status and PHP version. A saved choice the account no longer lists is kept and marked as missing, not silently cleared.
  • A run goes through Laravel Cloud's documented Commands API as one command, php artisan tinker --execute=…, so the value, printed output, queries, rollback and sandbox dry runs answer as they do on any other connection.
  • Laravel Cloud cannot cancel a command. Stopping a run, or reaching the run timeout, ends the wait, and the answer says the command keeps running there until it ends.
  • Point the connection at a local checkout of the same app if you want class names completed.
  • The MCP server runs code on Laravel Cloud connections too, through tinkr_run_php.

Not available on Laravel Cloud: the log viewer, and method completion and closure-valued form fields, each of which would be a multi-second command on production, recorded in the environment's command history.

Connection edits wait for Save

Editing a saved connection no longer writes every keystroke. Changes are written when you press Save, and closing the dialog with the close button or Esc leaves the connection as it was. The dialog's footer says whether there are unsaved changes. A new connection is still saved when you press Connect.

Fixed

  • Containers on a server were listed as "nothing is running" when the server only accepts a key: the container listing now uses the connection's SSH key.
  • Finding the project inside a container walked vendor and node_modules, offering every installed package with a composer.json as a project. Those directories are now skipped.
  • The PHP version said "no PHP" for a deployed container whose host has no PHP, while Tinker ran fine inside it. The version is now read inside the container, the same way a run reaches it.

macOS (arm64), Windows, and Linux (AppImage, deb, rpm, tar.gz) are attached.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • connections: save edits with a Save button instead of autosave 30489ab
  • connections: run code in a Laravel Cloud environment 7ef4d96

Fixed

  • connections: list containers over ssh with the connection's key bf97863
  • connections: skip vendor and node_modules when finding projects in a container ee7806b
  • php: detect the php version inside the container 64e2cac

Other

0.4.0

On GitHub

Tinkr 0.4.0

Two kinds of connection that could not be reached before, a connection dialog rebuilt around where the code runs, a log viewer that pages as you scroll, and an end-to-end suite that drives the real app.

Log in to an SSH connection with a password

A server that takes a password, not a key, could not be reached: ssh typed its prompt into the terminal, and that is the one place the app cannot answer. Now the password is saved with the connection and answered by the app's own askpass helper.

  • The password is kept out of the connection. It lives in app/secrets.json, mode 0600, one entry per connection, and the connection only records that one is there. It never travels over IPC, never reaches the window, and is never shown again. The form says one is kept and offers to forget it.
  • The helper only answers its own connection's prompt, so ssh other-server typed into the terminal is never handed this password.

A container on a server

A project deployed by Docker Compose, Dokploy or Coolify is a container on a server, and neither connection kind could reach it. A connection can now be both: the session goes over ssh and then into the container.

ssh -t host -- 'docker exec -it -w /var/www/html app php artisan tinker'

container_path is where the container sees the project; path stays where the server keeps it. The class index and the log viewer ask their questions inside the container over the same connection. The container and the project inside it are picked from what is running, not typed.

A new connection dialog

The dialog shows the route your code takes: the connection's name as the title, a live line naming the server, container, directory and command a session uses, a rail of the four kinds, and each section in the order you reach it. A sidebar row now only connects; its settings open from the pencil on the row or Edit in its right-click menu. Each connection shows its tag as a coloured light in the sidebar, the titlebar and the dialog.

The log viewer pages as you scroll

Older and Newer are gone. The viewer reads 32 KB pages, holds at most four of them (or 3000 entries), and loads the next one when you come within 160 px of an edge. The entry you are reading stays put when older pages arrive above it, following a file keeps the history already on screen, and a level or search filter stays on the pages already loaded, so paging back never drops the matches it is showing.

Fixed

  • The log level filter showed only the levels it had excluded.
  • A form row hidden for another connection kind stayed on screen.
  • Quick Connect, and reconnecting it, lost the directory that had been picked.
  • The window's first connection was not watched for snippets saved from outside the app.
  • The language could stay stuck on Loading when the first messages arrived early.
  • A closure-valued snippet form field wrote its answer back as code.
  • The default shortcuts that use Shift never matched the keys they named.

Tested end to end

81 Playwright tests launch the real app (main process, preload bridge, renderer and a real pty) and cover connections, sessions, results, tabs, snippets, history, logs, preferences, AI, MCP, updates, containers and ssh, against a tinker-compatible fixture that needs no composer install.


macOS (arm64), Windows, and Linux (AppImage, deb, rpm, tar.gz) are attached.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • log in to an ssh connection with a password 2989710
  • reach a container that lives on a server c174c33
  • logs: read log pages as the reader scrolls 8fddc67
  • connections: redesign the dialog, edit from a pencil, colour each stage 79ff57f

Fixed

Other

  • e2e: launch the real app and cover every surface 356d996
  • mcp: keep the client path fixture in its own home 20f7a01
  • give the E2E job its fonts, a window manager and focus 70a9b55
  • e2e: wait for the clipboard write before reading it 04928a4

0.3.2

On GitHub

Tinkr 0.3.2

Two bugs found by running the app against a real project (Laravel 11, PHP 8.2, MySQL).

dd() printed nothing

psysh dumps to the terminal on its own handle and aborts before the wrapper's start marker is flushed, so the dump arrived ahead of the marker — and everything ahead of the marker was treated as the session booting. echo 'x'; dd('y') showed x and lost y; dd looked like it printed nothing at all.

Text arriving after the wrapped command is echoed and before the marker is now kept as the run's own output, which is the only place a dump exists. The psysh line note is also matched loosely now: tinker 0.12.18 writes ExecutionLoopClosure.php(55) : eval()'d code(184) : eval()'d code:1, which the old pattern did not strip.

A form field's name could carry a space

PHP's identifier rule is not \x80-\xff: a non-breaking space is U+00A0, inside that byte range and not a name character. A snippet holding one between a name and its = gave a field called $field_name — a name that matched no assignment when the form was written back, so the run threw a ParseError on the line the form had just touched. The name is trimmed, and the gap the writer owns (after the =) is written as one space whatever the snippet had there.


macOS (arm64), Windows, and Linux (AppImage, deb, rpm, tar.gz) are attached.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

Fixed

  • what dd() prints, and a form field's name 58b8a67

Other

0.3.1

On GitHub

Tinkr 0.3.1

Opening a snippet's form failed with DynamicPhpForm.instance is not a function. The form is read by a module of that name now, whose instance is a function rather than the static the old package had, and the service the window asks went on calling the static.

Nothing else changed. The reading, the PHP 8 constructs it accepts and the form writing back into its snippet are the same as 0.3.0; the module the window goes through had no test, which is how this reached a release. It has one now.


macOS (arm64), Windows, and Linux (AppImage, deb, rpm, tar.gz) are attached.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

Fixed

Other

  • keep the agent's memory out of the repository 0271043

0.3.0

On GitHub

Tinkr 0.3.0

Every tab keeps its own answer

A window with several snippets open used to share one result: running the second tab wiped the first tab's answer, and switching between them showed whichever answer landed last. Each tab now keeps its own result, its printed output and its run marker, so switching back finds the answer it left.

An answer is written under the tab that sent it, not under whichever tab happens to be open when it lands — moving away while a run is going no longer loses it.

A run has a time limit

Settings → General → Run timeout (30 s by default). A run that reaches it is stopped, which reaches the PHP behind it inside a container or on a server. While a run is going the editor is read-only, the Run button counts the wait, and the answer says how long the whole of it took.

The snippet form reads PHP 8

The form was read by a WebAssembly PHP 7 parser: a nullsafe ?->, named arguments, match, an enum or a union type anywhere in a snippet took the whole form down with syntax error: unexpected T_OBJECT_OPERATOR. It is read by a scan now, so a form opens whatever PHP the snippet is written in. Writing a form back no longer turns an expression into a quoted string, and numbers and null keep their type.

SSH connections initialize again

Two things were wrong. -t was written after the destination, where ssh reads it as part of the remote command — the far shell ran a program called -t (bash: - : invalid option) and the session came back without a terminal. And the session's command was typed into the terminal ssh was started on, so ssh's own questions answered it: on a first connection, Are you sure you want to continue connecting? ate php artisan tinker. Both fixed; a snippet over ssh runs again.

The sandbox reports what it caught

  • a request that was never stubbed is refused instead of answered with an empty 200
  • jobs dispatched through the bus are counted, not only the queue's
  • notifications the run sent are counted
  • a run's imported names the classes it aliased, and is {} rather than missing when it aliased none

MCP

  • tinkr_delete_snippet — delete a snippet by its id
  • a snippet an agent saves or deletes reaches the app without a restart
  • a connection whose command never boots now answers in milliseconds with what the command printed, instead of waiting out the timeout

Fixes

  • an AppImage points clients at a copy of the MCP server in the app's data directory, which survives the app closing — it used to hand out a path inside its own mount
  • switching tabs no longer shows one document's result for another

macOS (arm64), Windows, and Linux (AppImage, deb, rpm, tar.gz) are attached.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

Fixed

  • the six open issues, each verified against a real session 9523286
  • the form, the ssh session, the run's time, and the tools that write 337bd3c
  • editor: a run belongs to its tab, not to the window 7e1ba84

0.2.4

On GitHub

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • window: the app draws its own titlebar, and the platform's frame goes c18a2e1
  • logs: a range of moments, found in the file rather than in the window 449e8a0
  • logs: the panel carries the range on every read 2b9e093
  • logs: a filter panel, not a dialog, and every level monolog writes 0c3b09a
  • logs: the log viewer is a window of its own, and only ever one 719cfff
  • logs: the logs button beside settings, and the editor's logs cut out f41860d

Fixed

  • window: no default right-click menu on the app's own panes 9ab63bb
  • settings: the desk's edge stops scrolling with its content e62a440
  • logs: a bracket at the start of a line is not a header 750e389

Other

  • the log viewer's filter, in the list the repository opens with 53fe236

0.2.3

On GitHub

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • mcp: a production connection is refused, and a run can be asked to be a dry run 90c1b40
  • core: a sandboxed dry run, where a write that was taken back cannot still send the email 67b18c3
  • ui: a stop for a run that is going, and a sandbox switch on the dry run 9729f1d
  • connections: a container is a connection you can pick eecefb5
  • mcp: a tool that explains the app, so a model can use it without being told 5007be1

Fixed

  • core: a dry run says what it did, and a run that stops still answers de8eeaf
  • forms: a field holds a value, never code bdb90dd
  • session: a kill stops the code, and a run uses the connection's command 2208718
  • the pane says what a dry run did, the stores survive a bad file, and the MCP server stops losing writes b8c42ef
  • ui: the production tag is on the row that picks it, and a head fits a narrow pane fbda1ba
  • ssh: every field of a connection is one word, and an impossible one is refused 009f066
  • main: a project's own files are indexed, a link cannot leave it, and the php is the one the connection runs c400b3a

Other

  • the app describes itself by the features it has d4191c9
  • a README that leads with what Tinkr is for b266a3a
  • the README claims the connections the app can make 472d788

0.2.2

On GitHub

What is new

  • Readings: a result is read the way its value wants to be read - the tree of any structure, the table of a list of records, the graph of anything with something under it, the frame for markup, and the raw text the run printed. A table is copied or saved as csv.
  • Queries: the statements the run sent to the database, with their bindings, the time each took and the connection it went to.
  • Dry run: a run inside a transaction you can keep or take back, so a write can be looked at before it is committed. A run over a connection tagged production asks before it is allowed to write.
  • Log viewer, AI panel, suggestions, MCP server: unchanged in this version, described in the README.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • a log file is a document, and a 128 KB window of it at a time 110a273
  • core: the run's envelope carries the guard and the queries, and waits to be sent fc165f2
  • main: the guard the editor chooses, and a result saved to a file f602321
  • i18n: the words for the readings, the queries and the guard a769357
  • a value is read as a table, a graph, a frame, or the text it printed 4da884b
  • the queries a run sent, in a view of their own 856acdf
  • a dry run the pane offers to keep, and a question before a production write c4ce076

Fixed

  • the ssh project, read without touching the session or a keystroke 74ebec5
  • ship node-pty's own runtime scripts beside the bundle d003b22
  • release: the workspace pins follow the version bump e02fcaa

Other

  • the local code graph keeps its index out of the repository 8e690bc
  • the features this version added, in the list the repository opens with a4ee505

0.2.1

On GitHub

Everything 0.2.0 changed, plus what a connection over ssh could not do.

Fixed

A connection over ssh was read from this machine. The log viewer looked for storage/logs at a path that is on the server, the editor's suggestions came back empty whenever the connection was over ssh, the :: method popup wrote a temporary file the remote never wrote where this host could read it, and the AI panel's @ list was empty. All four ask the connection now — one shell or ssh command for the log listing and its tail, one question to the session for the project's class names and for a class's public methods, through the same channel a snippet form's value already travels on. Only bytes cross: the parsing stays where it is tested.

  • The AI panel could not load its own module, which left the whole editor window blank.
  • The app's own scheme now names .otf, the format Commit Mono's italic comments are in.

Changed

The logs have a pane with room. A log entry is as wide as the line it wrote, and it was being read in a third of the output panel, under a filter bar that took two rows of it. The file list is the sidebar's now — Log files, under the connection's own lists — and a click opens the file in the editor pane, where the code is edited. The way back is a button at the foot of the sidebar that says what it will do: "Switch to the log viewer" while the code is in front of you, "Switch to tinker" while a log is. Its mark changes with the pane — the return bars for the tinker, the lines a log is made of for the logs.

The output panel keeps the run's output, the session's terminal and the AI panel, and the strip above the pane is documents and their own actions again, as it was.

Notes

The interface is written down in design.md: the type, the palette, the spacing, the motion, the component voice and the contrast ledger. The two ITF faces are fetched at build time, since the license lets the app embed them but not redistribute them.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • the logs, read in the pane that has the room 32b284d

Fixed

  • the type the renderer is served an .otf as e3aad66
  • the connection's own machine answers for its project e78cdc4
  • the way between the pane's readings, at the foot of the sidebar f70b0fe
  • the switch's margins, and the class it was wearing 5fcad8c

0.2.0

On GitHub

A new way to run code, in a new interface. The app's identity is the glyph a session prints before a value it returned: PsySH answers with =, so that is the mark — two bars cut at 29 degrees — and the same three-glyph language everywhere an outcome is said: = returned, - printed, ≠ threw. It is drawn in the brand, in the History ledger down the sidebar, at the head of every result, after the Output tab's label, and in the statusbar when a run dies. An outcome is never a pill, and never colour alone.

One palette in two readings. Every neutral sits on hue 150, so the floor, the desk the panes sit on and the wells the code and the values live in are all a faint celadon; a single verdigris at hue 172 carries the value channel, and vermilion carries a throw. The blue-to-purple gradient, the glow behind the Run button, and the second warm palette the terminal and evergreen were carrying are gone — monaco, both terminals and evergreen now read the same tokens. Switzer and Sentient replace Inter, Commit Mono replaces JetBrains Mono, and the app opens dark.

Fixed

  • The editor window came up blank: utils/aiMention.js was written as CommonJS while the AI panel imported two names from it, and vite resolves a source file as a module.
  • A History row wrapped its own timestamp onto two lines and squeezed the code it shows into about seven characters. It is a grid now, and the outcome glyph has a column of its own.
  • The split handle was a 2px line; it is the = itself, with a 9px hit area, and it turns with the panes.
  • --text-faint on a filled row fell to 3.55:1. Any row that takes a fill hands its meta text the muted step, and 94 colour pairs per mode now measure at or above their threshold.
  • A run's value stopped repeating the word the outcome beside it already says.
  • Tooltip.jsx (unused, and white on white in the light theme), filterUndefined.js and the color dependency are gone.

What it looks like

a run's output, and the value it returned

the same window in the light palette

the ai panel proposing a snippet

Notes

The system is written down in design.md: the type, the palette, the spacing, the motion, the component voice and the contrast ledger. The two ITF faces are fetched at build time (the license lets the app embed them but not redistribute them), and the code's face is OFL and lives in the repository.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • an mcp server runs code in your project 6201b31
  • point your ai tools at the server from the settings 2b12102
  • the runs a connection kept, for a client to read 8a2b398
  • the ai panel sees the run and the files you name 3cb2f74
  • the application's logs, in a view of their own 840a024
  • the interface, drawn from the return bars d5d7ab5

Fixed

  • the editor window, which the ai panel's import took down 94578fb
  • the workspace dependency the version bump missed 1ae2b2b

Other

  • the project is called tinkr aa63604
  • what runs code stops needing a window 32569b4
  • the readme, the contributing guide and the screenshots cfe0c1c

0.1.0-beta.3

On GitHub

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • ask a connection for its php, and warn when it has none c1d86ef

Fixed

  • a session starts in the home directory on windows too da05b37
  • the renderer url is built from a path, not by hand 03cb160
  • the workflow validates again, so a push stops failing 53a5912
  • a build that cannot install over itself stops asking to f24f0bc
  • the renderer is served over the app's own scheme 281de2a
  • the session starts on windows 085dd12
  • the session is not handed a posix login argument on windows cd4cafc

Other

  • a version tag publishes the release, not a pre-release a8097fe
  • a build keeps its installers, so a system can be tried before a release 6a5fb6a
  • a build runs when it is asked for, not on every push 9690794
  • a build can be made for another system from here a17dfe9
  • the boot says what it is doing 593b4d1
  • the renderer's own words go into the log 9ca32e7
  • the boot says where it is, one line at a time c3ef03a
  • the page says whether its own loop is still turning 148b686
  • the boot stops narrating itself 811b910

0.1.0-beta.2

On GitHub

Tinkr 0.1.0-beta.2 — what changed since the first beta.

The code beside the result

The window splits either way now: the code on the left with the result on the right, or one above the other, whichever you set in Preferences → General → Layout.

And a run answers for every statement in it, not only the last one, each result carrying the line it came from — so $user = User::find(1); shows the model and $user->name; shows the name under it. Statements, in the same settings page, is every statement or only the last.

Five languages, and a way to switch

Español, Deutsch and Português join English and 中文, and the language is a row in Preferences → General at last — before this it could only be changed by editing the config file and restarting. Every word follows it, the result pane and the status bar included, along with the labels a screen reader is read. Castilian Spanish and Brazilian Portuguese.

The window does less reloading

  • Switching connection no longer reloads the window. The snippets, the runs, the tabs, the session and the result panel change with the connection; the window, the preferences and the layout stay put.
  • Opening a run from the history opens its own tab, named for the time it ran, instead of writing that code into whatever you had open. The list shows the newest twenty runs.
  • A bin in the History header forgets a connection's runs, after asking first.
  • Every navigation goes through the app's own router now, which fixed a URL that could be rewritten to file:/// — an empty window waiting for the next load.

Smaller things

  • Run and Form are icons, with their words in the tooltip; the settings affordance in the top strip is a cog rather than the sun-like ring it was.
  • The app updates itself only when asked. It checks as it opens and every few hours after that, then a panel offers the download; nothing is installed until you agree, and it lands on the next quit or when you press Restart.
  • Linux could not have updated at all — the release was missing latest-linux.yml, the file an AppImage reads. That is fixed.

Every change in it

feat: the app asks before it updates itself
feat: run and form are icons, with their words in the tooltip
fix: the settings affordance is a cog, not a sun
feat: a bin clears the runs, and every navigation goes through the hash
feat: switching connection switches what belongs to a connection
feat: a run opens in its own tab, and the list stays short
feat: the last english words, and the trap they came with
feat: every word, in the language the app is in
feat: spanish, german and portuguese, and a way to switch
feat: the code beside the result, and a result for every statement

This is a beta and is published as a pre-release: GitHub does not hand it to watchers of this repository's releases, and an installed stable copy is never moved onto it. An installed beta, though, is offered this one by the update panel above — which is the point of it.

Installers are attached below: macOS (arm64), Linux (AppImage, deb, rpm, tar.gz) and Windows (installer and portable).

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app

New

  • the code beside the result, and a result for every statement 82b6615
  • spanish, german and portuguese, and a way to switch 4cff76b
  • every word, in the language the app is in 6290d94
  • the last english words, and the trap they came with b610a8d
  • a run opens in its own tab, and the list stays short 3417a2c
  • switching connection switches what belongs to a connection 67c62b8
  • a bin clears the runs, and every navigation goes through the hash b9b9c2f
  • run and form are icons, with their words in the tooltip a92336e
  • the app asks before it updates itself 6b18835

Fixed

  • the settings affordance is a cog, not a sun f57aa23

0.1.0-beta.1

Pre-release

On GitHub

Tinkr 0.1.0-beta.1 — the first release of this app under its own name: a desktop scratchpad for running PHP against a Laravel project, and for reading what came back.

Connect to a project on this machine, in a container or over ssh, write a snippet, and run it against a real tinker session.

Connections

  • Local, container or ssh. A connection is a directory and the command that starts the session (php artisan tinker by default), so anything that gets you a tinker prompt works — docker compose exec, a remote shell, your own wrapper.
  • Quick connect ships out of the box, and you can keep any number of your own, each with a name, a path, its own command and a tag (local, testing, development, staging, production).
  • Over ssh: server, port, user and a private key — or your own ssh config if you leave the key empty, with the session running inside the remote project.
  • Connect, clone or delete from a connection's context menu.

Snippets, sessions and the sidebar

  • Snippets are kept per connection: create, rename, delete, and search them with Ctrl K (⌘K).
  • Any number of sessions open at once, each its own tab with its own code and caret. A session is not written to the connection — Save is what turns it into a snippet.
  • The snippet form reads the code's field_-prefixed variables (the prefix is yours to set) and gives you a form to change their values in, so a snippet can be driven without editing it. The tab switches back and forth between the form and the code.

The editor

  • Monaco, with a php-snippet language of its own, and Ctrl R to run — the shortcut is yours to set.
  • Completions that know the project: the classes of a local connection's composer index, project namespaces first, so Aeat finds AeatEnvio rather than Symfony.
  • After ::, the methods of that class — its own, its traits' and its parents' — with their parameters and return types, resolved by asking the session itself.
  • The classes a snippet names are aliased into the session before it runs, so AeatEnvio::first() needs no use statement, and the run tells you what it imported.
  • AI completions at the cursor: OpenAI, Claude, Ollama, OpenCode Zen, OpenCode Go, OpenRouter or your own endpoint, with the model, a token kept on your machine, the wait before it asks, and a switch to turn it off.
  • The pager is taken out of the way, so a long dump comes back whole instead of sitting behind less.

Reading a run

  • The value comes back as data, not as text. Arrays and objects arrive as a tree you open and close node by node, each row copying its own value, and the whole value as JSON is a click away.
  • What the run printed — a dump, an echo — is drawn above it, as the session's own words.
  • The same result in two readings: organized (the tree) or raw (the session's text), and the reading is kept for the next run.
  • Exceptions get a surface of their own: the class and the message, ready to copy.
  • Every run boots a fresh tinker session, so the code you just changed on disk is the code that runs.

The AI panel

  • Ask for a snippet in words — dump the config keys of the app — and apply or discard what comes back. A proposal replaces the editor's code only when you take it.
  • Ask about the code in the editor — what does this return? — and get an answer in words.
  • Every ask carries the code in the editor and the turns before it, so now make it a select is read against what came before.
  • The thread lives in the window: your asks, its answers, and what became of each proposal.

Terminal, history and the rest

  • The Terminal tab is the session itself, the raw pty: type into it, and it stays for the life of the window. Reconnect is a click away.
  • History per connection: the code that ran, when, and how it ended — returned, printed, threw — kept across restarts, and a click puts a run's code back in the editor.
  • Preferences: English and 中文, a dark theme for the whole chrome including the terminals and the editor, the default command for a quick connect, the prompt before closing an unsaved session, and both shortcuts with a key-capture dialog.
  • Checks for updates in the background — pre-releases are skipped, which is what this is.

Downloads

Installers are attached below for macOS (arm64), Linux (AppImage, deb, rpm, tar.gz) and Windows (installer and portable).

This is a beta and is published as a pre-release: GitHub does not hand it to watchers of this repository's releases, and the app's updater skips pre-releases, so no installed stable copy is moved onto it.

macOS: this build is not signed and notarized yet, so macOS reports the app as damaged. Drag Tinkr to Applications, then run this once:

xattr -dr com.apple.quarantine /Applications/Tinkr.app