Repository navigation
Releases: nurmohseni/tinkr
Release list
Tinkr v0.4.6
Built from 4266cda
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
Tinkr v0.4.5
Built from 5dd6e8c
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
Tinkr v0.4.4
Built from 44afe0c
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
Tinkr v0.4.3
Built from 4ba8abc
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
Tinkr v0.4.2
Built from 49d9e1d
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
Tinkr v0.4.1
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(mode0600) 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
vendorandnode_modules, offering every installed package with acomposer.jsonas 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
Tinkr v0.4.0
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, mode0600, 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-servertyped 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
Tinkr v0.3.2
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
Tinkr v0.3.1
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
Tinkr v0.3.0
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
importednames 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