Skip to content
nikcli
Docs
Documentation

Island

@nikcli-ai/island — NikcliIsland.app, the macOS Dynamic-Island-style notch HUD for live nikcli sessions.

Overview

@nikcli-ai/island (the Swift package under packages/nikcli-island/) ships NikcliIsland.app — a Dynamic-Island-style status HUD that draws over the macOS notch and shows the live state of every nikcli session running on the machine. It is modeled on Pookify (the equivalent for Claude Code), rebuilt around nikcli's own event bus instead of a one-way hook file.

Unlike every other package on the docs site, @nikcli-ai/island is a Swift package — it does not participate in the JS build graph. Its only dependency on nikcli itself is the file contract documented below.

macOS only
Island is a no-op on Windows and Linux. The native app is a macOS-only notch UI. The NIKCLI_ISLAND_TEST_FORCE_DARWIN=1 flag exists only for the bridge's own test suite and must never be set in production.

How it works

Three moving parts. The data flows from nikcli's process to the notch UI through a small JSON file contract that the Swift app polls.

  1. The bridge. @nikcli-ai/util/island-bridge is started unconditionally from src/index.ts's top-level middleware. It listens on GlobalBus (src/bus/global.ts), which every Bus.publish(...) call already forwards to regardless of mode — so the island reflects any nikcli process running on the machine, not just the TUI.
  2. The snapshot file. One JSON file per session under ~/Library/Application Support/NikcliIsland/state.d/, written atomically (temp file + rename). Each snapshot carries the session id, agent title, project, cwd, the writing process's pid, the local server port (when one is running), and a server URL.
  3. The Swift app. NikcliIsland.app reads the snapshot directory (port of Pookify's SessionAggregator.swift) and draws the notch (IslandView.swift, NotchWindow.swift, IslandShape.swift, NSScreen+Notch.swift).
Bidirectional — not just display
Because the snapshot also carries the writer's local server port and URL, the island can call back into POST /permission/:requestID/reply to approve or deny a permission request from the notch itself. Pookify can only display permission state; the nikcli island can actually answer it.

Install

Build the Swift package and drop the app into /Applications:

Terminal
bash
# Inside the nikcli monorepo
      ./packages/nikcli-island/scripts/install.sh

The install.sh script runs swift build -c release, assembles NikcliIsland.app, and installs it to /Applications. An uninstall.sh script is shipped alongside it.

To enable the bridge, start any nikcli command with the --island flag:

Terminal
bash
nikcli --island
      nikcli tui --island
      nikcli serve --island

The first session activity wakes the app via open -g -b com.nikcli.island; subsequent publishes are no-ops.

State file contract

Each *.json file under ~/Library/Application Support/NikcliIsland/state.d/ is one session's snapshot. The Swift app polls the directory and renders the union. The schema is owned by packages/nikcli-island/Sources/IslandCore/ on the read side and mirrored in src/plugin/island/bridge.ts on the write side.

Field Source Display
idle session.status → idle Empty pill on the notch.
thinking session.status → busy / retry "Thinking…" or "Retrying (N)…".
tool message.part.updated tool status Friendly label plus file basename. Lingers for ~1.9s after completion so the last action stays visible.
permission permission.asked "Awaiting permission" plus the request id for one-tap approval from the notch.
error session.error "Error" pill.
done (reserved) Used by the native app to mark a completed row before archival.

Beyond the state, each snapshot also carries the parent session id, agent title, project, cwd, the writing process pid, the local server port (when one is running), and a server URL — enough for the native app to invoke POST /permission/:requestID/reply directly when you approve a pending request from the notch.

Atomic writes
The bridge writes snapshots with the standard temp-file-then-rename idiom so a half- written file is never observable by the polling app. See IslandBridge.write in src/plugin/island/bridge.ts.

Permission reply from the notch

The whole reason nikcli's island is more useful than Pookify's is the bidirectional channel. Pookify's hook is one-way: it can render permission.asked, but it has no way to answer it. nikcli's island can, because the writing process is a live nikcli with a real HTTP API and a known server URL.

Terminal
bash
# What the island POSTs when you tap "Allow" on the notch
curl -X POST http://127.0.0.1:4096/permission/per_example/reply \
  -H "Content-Type: application/json" \
  -d '{"reply":"once"}'

The reply handler is the same one the TUI and mobile composer use; see Permissions for the full request / reply lifecycle.

Disable & rollback

The bridge is opt-in per invocation. To disable the bridge without uninstalling the native app, set the environment variable:

Terminal
bash
NIKCLI_ISLAND_DISABLE=1 nikcli

Toggling from inside the TUI uses the island feature plugin, which calls IslandBridge.setEnabled(). Turning it off clears the current snapshots and stops writing; turning it back on resumes through the already- registered listener without any race window.

To uninstall the native app entirely:

Terminal
bash
./packages/nikcli-island/scripts/uninstall.sh
Don't set NIKCLI_ISLAND_TEST_FORCE_DARWIN
The NIKCLI_ISLAND_TEST_FORCE_DARWIN=1 flag exists only for the bridge's own test suite. Never set it in production: the feature is Windows/Linux no-op by design and forcing it on a non-macOS host would silently write macOS-only paths.

Source anchors

Concern File
Swift package packages/nikcli-island/
Install / uninstall scripts packages/nikcli-island/scripts/install.sh, uninstall.sh
Snapshot schema (read side) packages/nikcli-island/Sources/IslandCore/
Notch rendering packages/nikcli-island/Sources/NikcliIsland/
Bridge (write side) @nikcli-ai/util/island-bridge
TUI plugin (toggle) packages/tui/src/feature-plugins/island/index.tsx
Worker shutdown hook packages/nikcli/src/cli/cmd/tui/worker.ts → shutdown RPC