🇪🇸 Versión en castellano: README.es.md
iworkflow is an ADE (Agentic Development Environment): a self-hosted web app to launch, follow and review several AI coding agents in parallel. Instead of editing files yourself, you manage tasks: each one runs an agent in its own git worktree, and you review the diff before anything reaches your branch.
Chain tasks on a board; when one is merged, the tasks that depend on it start on their own.
Status: early and actively developed. It runs on your own machine, has no user accounts (only an optional access token for network access) and is source-available (PolyForm Shield 1.0.0), not OSI open source. See Quick start and License.
- Parallel agents, one place: launch and steer
gentle-shell,pi,claude,cursororagyside by side. - Isolated by design: every task gets its own git worktree, so agents never overwrite each other.
- Review, don't redo: when an agent finishes you read its diff and either merge it (
merge --no-ff) or discard it. - Task graph: chain tasks on a board; a task starts by itself once its predecessors are merged.
- Integrated: GitLab / GitHub (MR/PR and CI status) and Jira Server / Data Center.
- Mobile-first: check progress, answer an agent's question and merge from your phone (through Tailscale).
- Tabbed IDE-like workspace: board, task, diff, terminal and file editor as draggable, splittable tabs.
iworkflow imposes no workflow: each agent uses the configuration and plugins you already have installed, plus the project's AGENTS.md. Under the hood agents are driven over RPC (or ACP / stream-json, depending on the runtime). It is built with Go (net/http + chi), SSR with templ and htmx, Tailwind CSS, sqlc and SQLite (modernc.org/sqlite).
- Designed for mobile
- Quick start
- Tabbed workspace
- Workspace board
- Task lifecycle
- GitLab and GitHub integration
- Jira integration (Server / Data Center)
- Agent profiles and models
- Configuration
- Production
- Desktop app (Electron)
- Developing from your phone with Tailscale
- Development
- License
- Support the project
iworkflow is built mobile-first. Since your job is to launch, follow and review agents (not to type code), it works well from a phone:
- The UI is responsive: on small screens the side menu becomes a drawer opened from a top bar, and the tabbed workspace adapts to the viewport.
- The agent keeps running on your machine while you are away. From the phone you check progress, answer agent questions, send a message, review the diff and merge or discard.
- On iOS (Safari → Share → Add to Home Screen) and Android (Chrome → menu → Add to Home screen) you can pin it as a shortcut. For now it opens in the browser; it does not yet ship a web app manifest, so it is not a standalone full-screen app.
The server runs on your computer and the phone is only a browser, so you need a secure way to reach it: see Developing from your phone with Tailscale.
Requirements: git, Go (the version in go.mod), templ, tailwindcss and at least one agent CLI installed and logged in (gentle-shell, pi, claude, agent for Cursor, or agy for Antigravity).
git clone https://github.com/plopezm/iworkflow.git
cd iworkflow
make dev # http://127.0.0.1:3000- Workspaces → register a folder with git (it needs at least one commit).
- Tasks → pick a workspace, write the prompt and launch.
- In the task detail you follow the agent live and can:
- send messages (a prompt if it is waiting; steer or follow-up if it is working),
- answer agent dialogs (
ask_user_question, confirmations…), - abort the current run.
- Finish and review → closes the agent, commits whatever is pending and shows the diff.
- Merge (integrates with
--no-ffand deletes the worktree) or Discard.
Security: iworkflow is protected only by an optional access token and launches agents that run commands as your user. Do not expose it to untrusted networks; see Production and Tailscale. Development listens on
127.0.0.1:3000; the production script on0.0.0.0:9000.
The interface is an IDE-like workspace: every screen (board, task, diff, terminal, settings, files…) opens as a tab inside groups you can split by dragging. Tabs can be reordered by dragging them within their bar, moved to another group or dropped into a new split.
- File explorer and editor (with syntax highlighting) over a workspace or over a task's worktree. In the task panel: Review → Explore files.
- Modified, added, untracked, deleted or conflicted files and folders are colored according to
git status. - While you edit, the left gutter marks added, modified and deleted lines against
HEAD(as in VS Code). - When editing a task, the CPU and memory limits live under Advanced options.
- Related projects: the create and edit task screens list the other workspaces under Related projects. A task keeps the ones you tick; when its agent starts it is told their paths and not to modify them, may read their files, and, when present, query each project's codegraph (
.codegraph/) and its engram memory (only ifengramis installed). Claude also gets the folders through--add-dir, and its edit tools (Edit,Write,NotebookEdit) are denied inside them. Shell commands are not sandboxed, so this is not a hard guarantee. Typing@in the prompt completes files of the current project and of the ticked ones, written as@<project>:<path>(spaces in the project name become dashes).
Each workspace has its own board: a canvas of task nodes joined by arrows (/board/<workspace>; / opens the first workspace by name). Clicking a workspace in the sidebar opens its board, and the workspace chips at the top of the board switch between workspaces. The sidebar item menu keeps View files (the project tree), Open terminal and Manage workspaces.
- Dependency graph: drag from a node's right port onto another task to make it wait for the first one. Select an arrow to delete it. Self-references, duplicates, cycles, arrows across workspaces and arrows into a task that is no longer in To do are rejected with a message.
- Merge trigger: a task with predecessors starts on its own, on its configured base branch, once all of them are
merged. A To do task without predecessors is started manually with Start. Arrows are drawn muted while the predecessor is pending, with a flowing dashed line while it works, and solid green once it is merged. (Edge conditions are not implemented yet; the model leaves room for them.) - Nested and parallel tasks: Add next task creates a task that depends on the selected one; Add parallel task creates one with the same predecessors. Both are created in To do.
- Canvas: drag the background to pan, use the wheel to zoom and Fit view to frame everything. Node positions are saved; tasks without a saved position are laid out automatically in layers.
- Loose tasks panel: on the left, tasks with no arrows and no saved position (every task made with New task starts here) are listed with their usual actions (Start, Edit, Delete, Open, Merge…), so they can run standalone without any chain. Drag a card onto the canvas, or press Add to graph, to make it a node; Add next / parallel task tasks go straight into the graph. Remove from graph on a node deletes all its arrows and saved position (atomically) and sends it back to this panel; successors left without predecessors become ordinary standalone tasks. The panel is collapsible and counts its tasks; on screens under 768 px it becomes a Loose (N) / Graph tab switch.
One task end to end: launch, follow the agent live, review the diff, merge.
starting → running ⇄ idle → finishing → awaiting_review → merged
│ ↘ discarded
↓
conflict · failed · interrupted ── Resume (gentle-shell --continue)
| Situation | What the app does |
|---|---|
| Conflict while integrating | merge --abort; the task moves to conflict and keeps its worktree. Resume the agent to resolve it. |
| Base branch with uncommitted changes | Rejects the merge without touching anything. |
| Base branch not checked out | Integrates in a temporary worktree. |
| Server restart | Active tasks move to interrupted; they can be resumed. |
From each task you can create a merge request (GitLab) or pull request (GitHub), watch its CI and merge it. A workspace can be a repository on either: the provider is detected from the host of the origin remote and that provider's configuration is applied.
- Settings has one card per provider.
- GitLab: your instance URL (e.g.
https://gitlab.mycompany.local) and a personal token with theapiscope. - GitHub:
https://github.com(proposed by default) or your GitHub Enterprise URL, and a personal token (reposcope; fine-grained: Pull requests and Contents read/write, Actions read). Use "Test connection" to validate them. If your certificate comes from an internal CA or is self-signed, tick "Accept untrusted TLS certificate".
- GitLab: your instance URL (e.g.
- The project is derived from the
originremote (https, ssh or scp). Its host must match the URL of a configured provider; if it matches none you are warned, and if it is github.com and GitHub is not configured you are asked to configure it. On GitHub the repository isowner/repository. - In the task panel, PR / MR tab: create the MR/PR (task branch → base branch), see its status and the latest pipeline (GitLab) or workflow (GitHub) with its jobs, and Merge (asks for confirmation).
Notes:
- Creating the MR/PR publishes the task branch to
originwithgit pushif it is not there yet. The push goes to<provider URL>/<project>.gitover HTTPS and authenticates with the token configured here (HTTP Basic, userx-access-tokenon GitHub andoauth2on GitLab), so it works even iforiginis an SSH remote and needs no git credentials of your own. The token is handed to git through the process environment (never the command line), only for that instance, and any credential helper is ignored. - The target is the task's base branch as named on the server: a remote reference such as
origin/developis sent asdevelop. If that branch is not onorigin, or it is the task's own branch, creation is rejected with an explicit message instead of the provider's raw error. - The token is stored in clear text in the local database (
workspaces.dbin the data folder) and is never sent to the browser. Leaving it empty when editing keeps the stored one, unless you change the URL: then it has to be entered again, so it is not sent to a different server. - HTTP redirects are disabled so the token does not leak to another host.
The Jira tab connects to an on-premise Jira using the server URL and a personal access token (PAT, Authorization: Bearer, REST API v2). On save, the connection is validated against Jira; the token is stored in the local database and never returns to the browser (leaving it empty when editing keeps the stored one).
- Import: "Sync" fetches the issues matching the connection's JQL (default
assignee = currentUser() AND resolution = Unresolved ORDER BY updated DESC, at most 500). The local state (new / converted / ignored) is preserved across syncs; new issues Jira no longer returns are removed. - Manage: list with search and filters, detail with description and comments, and writing back to Jira: change the status (workflow transitions) and comment. Ignore/restore is local only.
- Convert: the "Convert to task" button opens a form with the title, prompt and priority proposed from the Jira issue (priority is mapped from the Jira name); you choose the workspace and base branch, edit whatever you want and the task is created (optionally started). The Jira issue is marked as converted and linked.
- If the server uses a self-signed certificate or one from an internal CA the system does not know, tick "Accept unverified certificate" in the connection.
This feature is the integration with Gentle AI, the agent harness behind gentle-shell and its gentle-pi package. Profiles and per-agent model routing are Gentle AI concepts: iworkflow does not define its own, it reads and writes the same configuration files (profiles.json, models.json), so changes made here are seen by the Gentle AI TUI and vice versa. Install and configure Gentle AI first; without it these screens have nothing to manage.
Two screens equivalent to the gentle-pi commands, working on the same files:
| Screen | Equivalent to | What it does |
|---|---|---|
/profiles |
/gentle:profiles |
Create, duplicate, rename, delete, import and export profiles (profiles.json), edit model and effort per agent and create new agent roles. |
/models |
/gentle:models |
Edit the global routing (models.json): model and effort per agent. |
Profiles are chosen in the agent settings dropdown, which reads the same profiles.json. These screens do not activate a profile globally; they only manage them. The write lock prevents clobbering within one process, not against another process (e.g. the gentle-pi TUI) editing at the same time.
Environment variables (.env is supported, see .env.example):
| Variable | Default |
|---|---|
IWF_HOME |
~/.iworkflow. Data folder; everything below is derived from it and cannot be set separately: workspaces.db (SQLite, automatic migrations), worktrees/, attachments/ (images attached to tasks; deleted when the task ends), agents-files/ (AGENTS.md library), cursor-sessions/ and agy-sessions/ (each task's ACP session id, to resume it), models/ (speech model for voice dictation; make model downloads it), tls/ and logs/ |
IWF_GENTLE_SHELL_BIN |
gentle-shell (launched as --mode rpc with GENTLE_SHELL_INTERACTIVE_HOST=1) |
IWF_PI_BIN |
pi (pi runtime, launched as --mode rpc) |
IWF_CLAUDE_BIN |
claude (claude runtime: Claude Code with -p and the stream-json protocol; chosen in the runtime selector of the side menu; accepts file edits, asks for confirmation for the rest and reads the project's AGENTS.md) |
IWF_CURSOR_BIN |
agent (cursor runtime: Cursor CLI in ACP server mode, agent acp; requires having signed in with agent login) |
IWF_AGY_BIN |
agy (agy runtime: Antigravity CLI in ACP server mode, agy acp) |
IWF_GENTLE_PI_CONFIG_HOME |
~/.pi/gentle-ai (contains profiles.json and models.json) |
IWF_TLS |
unset (plain HTTP). auto serves HTTPS with a self-signed certificate created in tls/ in the data folder and reused (see HTTPS) |
IWF_TLS_PORT |
HTTP port + 443 (HTTPS listener port when TLS is on) |
IWF_TLS_CERT / IWF_TLS_KEY |
PEM files of your own certificate and key; set both or neither. They win over IWF_TLS=auto |
Moving the data folder: open Settings → Resources, type the new absolute folder in the Data folder card and press Move folder. The server restarts, copies everything (database, worktrees, attachments, models...), checks the copy, repoints the stored paths and the git worktrees, switches to the new folder and only then deletes the old one. It refuses while a task is starting, running or finishing (note: Claude and pi resume a conversation by folder, so tasks that already started begin a new conversation after the move; Cursor and agy tasks keep theirs), and if the new folder is not empty, not writable, nested with the old one or too small. If anything fails the old folder stays untouched and in use, and the card shows why. The choice is stored in a one-line pointer file in the per-user configuration folder (~/.config/iworkflow/data-home on Linux, ~/Library/Application Support/iworkflow/data-home on macOS, %APPDATA%\iworkflow\data-home on Windows), outside the data folder, so updates and reinstalls keep it. IWF_HOME (environment or .env) wins over the pointer; while it is set the card is read-only. Alternatively set IWF_HOME yourself (on Windows the installer asks for it and stores it as a user environment variable) after copying the old ~/.iworkflow there. On Windows the home directory is taken from USERPROFILE when HOME is missing.
The server writes to stderr and, at the same time, to $IWF_HOME/logs/iworkflow.log (default ~/.iworkflow/logs/iworkflow.log). The file location is fixed; these variables tune it:
| Variable | Default | Effect |
|---|---|---|
IWF_LOG_LEVEL |
info |
debug also logs every HTTP request and extra detail; any other value keeps info. |
IWF_LOG_MAX_MB |
10 |
Size in MB at which the log file is rotated. A value below 1 or not a number falls back to the default. |
IWF_LOG_BACKUPS |
5 |
Rotated files kept, gzip-compressed; older ones are deleted. Same fallback as above. |
The startup line with the access link contains the token, so treat the log as sensitive.
The prompt field of the create and edit task forms has a microphone button. Record, listen to the recording, confirm it and the server transcribes it with sherpa-onnx and the Qwen3-ASR 0.6B model; the text is appended to the prompt as a new paragraph. The browser needs a secure origin (HTTPS or localhost) to open the microphone, and recordings are limited to 60 seconds.
make modeldownloads the pre-converted model (about 840 MB) into$IWF_HOME/models/(default~/.iworkflow/models/) and verifies it against the sha256 sums pinned inasr-model.sha256.- The speech recognizer is the only cgo code in the project, behind the
sherpabuild tag.make build,make devandmake distenable it (GO_TAGS=sherpa);make build GO_TAGS=gives a CGO-free binary where dictation answers "not available".make distalso copies the sherpa-onnx shared libraries of the current OS next to the binary (lib/on Linux and macOS, the same folder on Windows). Only one transcription runs at a time; a second request gets429and is retried by the user.
Browsers expose the microphone (voice dictation) only in a secure context: https:// or localhost. Over plain http:// on the network the mic button reports that recording is not available. HTTPS is opt-in:
make dev-https # like make dev, over HTTPS
make run-https # like make run, over HTTPS (after make dist)
IWF_TLS=auto ./iworkflow # any other way to start itWith IWF_TLS=auto the server creates a self-signed certificate on first use in $IWF_HOME/tls/, valid for localhost, 127.0.0.1 and the machine's network IP. It is reused until it nears expiry (30 days) or the IP changes, and then recreated at the next start. The browser warns about an untrusted certificate: accept it once per device. To use your own certificate, set IWF_TLS_CERT and IWF_TLS_KEY.
With TLS on the server opens two listeners with the same app: HTTP on the usual port (the desktop app keeps using it) and HTTPS on the HTTP port plus 443 (9000 → 9443; override with IWF_TLS_PORT, 1024–65535). Settings → Network access shows the HTTPS link and QR code first, and plain HTTP (no microphone) as a collapsed option. --addr / IWORKFLOW_ADDR set the HTTP address only; HTTPS uses the same host.
The runtime selector in the side menu supports gentle-shell, pi, claude, cursor and agy. The choice is fixed when each task starts.
| Runtime | Binary | Capabilities |
|---|---|---|
gentle-shell / pi |
IWF_GENTLE_SHELL_BIN / IWF_PI_BIN |
all (steer, follow-up, dialogs, resume, model, thinking, subagents) |
claude |
IWF_CLAUDE_BIN |
dialogs, resume, model and thinking |
cursor |
IWF_CURSOR_BIN |
dialogs, resume and model (the model list is obtained from the agent itself over ACP). File edits are accepted; everything else asks for confirmation |
agy |
IWF_AGY_BIN |
dialogs, resume, model (obtained over ACP) and thinking. File edits are accepted; everything else asks for confirmation |
On startup, iworkflow opens the web UI in the browser (http://<host>:<port> of the server; if it listens on 0.0.0.0, 127.0.0.1 is used).
| Argument | Effect |
|---|---|
--no-open |
Starts only the server, without opening the browser. |
--addr HOST:PORT |
Listen address (also the IWORKFLOW_ADDR variable). Pins the address: the saved network settings are ignored. Default 127.0.0.1:3000 (:9000 when packaged). |
--site-root DIR |
Folder with the static assets. Default: site/ next to the executable, else web/static. |
-h, --help |
Shows the help. |
Examples: ./iworkflow --no-open, make run ARGS=--no-open, make dev ARGS=--no-open.
In development (make dev) the server listens only on 127.0.0.1.
By default the server listens only on 127.0.0.1 (this computer). To reach it from your phone, turn on Settings → Network access and restart the server: it then listens on 0.0.0.0 with the same port, and the card shows the address to open on the phone. The choice is stored in the database. The same card lets you change the server port (1024–65535, saved in the database and applied on the next start; a packaged server defaults to 9000). If --addr or IWORKFLOW_ADDR is set, it always wins and neither setting has an effect. The card shows a QR code and a selector with the machine's network interfaces (for example ethernet and Tailscale, each with its IP): pick the one your phone can reach and scan the code. With the token required, iworkflow opens already signed in; without it, the code just holds the plain address.
Whenever the server is reachable from the network (this setting, or a non-loopback --addr such as the 0.0.0.0:9000 of make dist / make run) it demands an access token by default:
- The QR code and the link on the card carry the token (
http://<ip>:<port>/?token=…) and follow the selected interface. Opening it stores the token in a cookie and redirects to a clean URL, so it does not stay in the address bar or the history. Without the cookie, the server shows a login page where you can paste the token. - At startup the server logs that link, which is how you get it on a machine without a browser.
- Generate a new token on the card invalidates the old one at once; devices using it must sign in again.
- The requirement is optional: untick Require the access token (applies on the next start) to serve the network without it. Then anyone who can reach the port can run commands as your user, so use it only on a network you fully trust.
- Loopback-only servers (the default) never ask for the token.
- The startup log line with the link contains the token: treat the server logs as sensitive.
- Writes (
POST, etc.) that a browser labels with anOrigindifferent from theHostit targeted are rejected with 403, so another website cannot change these settings through your browser. A reverse proxy must therefore forward the originalHostheader.
The token is a shared secret over plain HTTP: on an untrusted network it can be sniffed. Prefer a private network such as Tailscale (see below).
Prerequisites: Go, GNU make, templ, sqlc and the Tailwind CSS CLI (the same ones as make generate).
The Makefile builds and runs the app in release mode:
make dist # 1. compile in release and package into dist/
make run # 2. start what is already packaged
make prod # both steps in a row (dist + run)Typical flow: run make dist once (and again after every update), then make run whenever you want to start the server. ARGS is passed to the binary (e.g. ARGS=--no-open, see Executable arguments). The server listens on http://0.0.0.0:9000 by default; change it with SITE_ADDR:
make run SITE_ADDR=127.0.0.1:3000 ARGS=--no-open
make dist DIST_DIR=/opt/iworkflow # package somewhere elsemake dist regenerates templ, sqlc and CSS, builds a stripped iworkflow binary and copies
the static site/ folder into dist/. Both are required.
make run starts the binary with --addr and --site-root from the current directory, so the .env in that directory
is applied too. Make variables:
| Variable | Default |
|---|---|
DIST_DIR |
dist/ at the repo root |
SITE_ADDR |
0.0.0.0:9000 (all interfaces) |
ARGS |
none (flags for the binary) |
Security: the app launches agents that run commands as your user. Its only protection is the access token (see Access from other devices), which is demanded whenever the bind is not loopback unless you switch it off, and it travels over plain HTTP. Restrict access with a firewall, a private network (Tailscale/WireGuard) or a reverse proxy with TLS. To limit it to the local machine:
make run SITE_ADDR=127.0.0.1:3000.
To deploy on another machine, just copy dist/ and launch it with
./iworkflow --addr 0.0.0.0:9000
from inside that folder.
For people who do not want to deal with a terminal, electron/ wraps the same server in a desktop window and make electron-build builds an installer for the platform it runs on (Linux: AppImage, macOS: .dmg, Windows: NSIS .exe). There is no separate "mobile server" to build: the installer already contains the same iworkflow binary, so a phone connects to the server running inside the app (see Using it from your phone).
Prerequisites (once): the ones of Production, git and Node.js 22+ with npm. Cross-building is not supported: build each installer on its own platform.
make electron-buildIt runs, in order:
make dist: release build, leaving the binary andsite/indist/.- Copies both into
electron/resources/server/(outside the asar archive, so the binary stays executable). npm ciandelectron-builderfor the current platform.- Writes the installer to
dist/electron/:iworkflow-<version>-linux-x86_64.AppImage,iworkflow-<version>-mac-arm64.dmg(ormac-x64) oriworkflow-<version>-win-x64.exe.
Other forms:
make electron-build SKIP_SERVER_BUILD=1 # reuse the server already in dist/
make electron-build DIST_DIR=/some/dir # use another server package / output location
make electron-dev # open the window on dist/ without building an installerOn macOS the .dmg is built for the architecture of the Mac that builds it (Apple Silicon or Intel); it needs the Xcode Command Line Tools. On Windows run it from Git Bash (with GNU make installed).
chmod +x dist/electron/iworkflow-*.AppImage
./dist/electron/iworkflow-*.AppImage # add --appimage-extract-and-run if FUSE is missingOn macOS open the .dmg and drag iworkflow to Applications. On Windows run the installer and start iworkflow from the Start menu. The installer has a page to choose the data folder: if it is not the default %USERPROFILE%\.iworkflow, it is stored as the IWF_HOME user environment variable (the app also reads it from the registry, so it applies from the first launch). Silent installs and updates leave an existing IWF_HOME untouched.
The server inside the app is the regular one, so phone access works exactly as in Access from other devices:
- In the app open Settings → Network access and turn it on.
- Quit the app completely (tray icon → Quit and stop server, or File → Quit) and start it again. Closing only the window is not enough: the server keeps running and reads this setting only at startup. It now listens on
0.0.0.0:9000. - Scan the QR code on the same card with the phone (same local network, or the same Tailscale tailnet). It opens already signed in with the access token.
You can also pin the address before launching, e.g. IWORKFLOW_ADDR=0.0.0.0:9000 ./iworkflow-*.AppImage; then the saved setting is ignored.
- The app starts the bundled server (
--no-open) and shows it in a window. Nothing about the server changes: same data folder (IWF_HOME) and database, sameIWORKFLOW_ADDR, same access token. The window signs itself in when a token is required. - The server listens on
127.0.0.1:9000by default; change the port in Settings → Network access. The app reads the address the server announces at startup, so it follows the saved port. If the port is busy the app shows an error; free it or choose another one. - Closing the window only hides it, so a phone connected over the local network or Tailscale keeps working. Use the tray icon (Quit and stop server) or File → Quit to stop the server. On macOS the icon lives in the menu bar and clicking the Dock icon shows the window again (iworkflow → Quit or Cmd+Q stops the server). On a Linux desktop without tray support, closing the window quits the app.
- External links open in the system browser; the window itself only shows the local server.
git(and the agent CLIs you use) must be installed and on thePATH; the installer does not bundle them. With network access on, Windows and macOS ask once to allow the app through the firewall. On macOS the app adds/opt/homebrew/binand/usr/local/binto itsPATH, since apps opened from Finder do not inherit your shell's.- The installers are not code-signed, so Windows SmartScreen warns on first run. On macOS the app is only ad-hoc signed and not notarized: Gatekeeper blocks the first launch, so right-click the app → Open (or run
xattr -dr com.apple.quarantine /Applications/iworkflow.app).
The app checks the latest GitHub release 15 seconds after start and every 6 hours, and from the tray menu (Check for updates). Nothing is installed without asking:
- Windows (NSIS) and Linux (AppImage): it offers the new version, downloads it (only the changed blocks when possible), verifies the sha512 listed in
latest*.ymland, after you accept, stops the server, replaces the app and starts it again (electron-updater). A Linux build that is not an AppImage only notifies. - macOS: it only notifies and opens the release page, because macOS refuses to self-install an app without a valid code signature and the app is only signed ad hoc. Download the new
.dmgand replace the app. IWF_UPDATE_CHECK=offdisables the check. A private repository needsGH_TOKENin the environment; a public one needs nothing. Development runs (electron .) never check.
.github/workflows/release.yml builds the server archives and, on the same Linux, macOS (Apple Silicon and Intel) and Windows runners, the desktop installers: it runs the launcher tests (npm test in electron/), then make electron-build over the server it just compiled, and uploads iworkflow-desktop-<platform>. Pushing a v* tag publishes everything in a GitHub release; it can also be started by hand (Run workflow). The installer version comes from electron/package.json: change it with make bump-version VERSION=x.y.z. The workflow also uploads latest-linux.yml / latest.yml (and the .blockmap files) next to the installers: they are what electron-updater reads, so a release without them cannot update Windows or Linux installs. macOS has none.
Tailscale creates a private network (tailnet) between your devices, so your phone can reach the server running on your computer without opening ports to the internet. This matters because iworkflow is protected only by a shared token over plain HTTP and launches agents that run commands as your user.
-
Install Tailscale on the computer that runs iworkflow and on the phone (App Store / Google Play), and sign in to the same account on both.
-
Start the server listening on the Tailscale interface (or all interfaces, which is the default of
make dist/make run):make run # binds 0.0.0.0:9000 # or only reachable from this machine and the tailnet via Tailscale Serve (see below) make run SITE_ADDR=127.0.0.1:3000 ARGS=--no-open
-
Find the computer's tailnet name or IP:
tailscale ip -4 # e.g. 100.101.102.103 tailscale status # shows the MagicDNS name, e.g. mypc.tailnet-name.ts.net
-
On the phone, open
http://<tailnet-ip>:9000orhttp://mypc.tailnet-name.ts.net:9000.
Recommended: HTTPS with Tailscale Serve. Keep the bind on 127.0.0.1 and let Tailscale expose it only inside your tailnet, with a valid certificate:
tailscale serve --bg 3000
# https://mypc.tailnet-name.ts.net (only reachable by your tailnet devices)Terminal WebSockets are accepted when the Origin host matches the Host header, which holds when you use the same address in the browser.
Tips:
- Do not use Tailscale Funnel: it would publish the app to the whole internet.
- Restrict who can reach the machine with Tailscale ACLs if the tailnet is shared.
- The computer must stay awake and the server running; use
tmux/a systemd user service for long sessions.
Architecture and code guide in ARCHITECTURE.md; project rules in AGENTS.md. Checks:
make test # go test -p 2 -race ./...
make vet # go vet ./...
make lint # golangci-lint run
make e2e # full browser Playwright e2e suite (or FILTER=<name>)
make dist # release package (dist/)
make electron-build # package desktop app (dist/electron)
make bump-version VERSION=x.y.z # set the app version
make electron-dev # run Electron window in developmentCopyright 2026 Pablo López (plopezm). Licensed under the PolyForm Shield License 1.0.0.
You may use, modify and distribute iworkflow freely, including inside companies and for commercial work, but you may not use it to provide a product that competes with it (for example, reselling it or offering it as a hosted service). This is a source-available license, not an OSI-approved open-source one.
Bundled third-party assets keep their own licenses: xterm.js (MIT) and highlight.js (BSD-3-Clause) in public/vendor/, and the Instrument Serif and Martian Mono fonts (SIL OFL 1.1) in public/fonts/. Go dependencies are declared in go.mod; see NOTICE for attributions.
If iworkflow saves you time, you can buy me a coffee at buymeacoffee.com/plopezm. Thank you! ☕