diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1ab84bd..68a2cde 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,6 +31,24 @@ IKE_DATA_FILE=$(mktemp -d)/t.json go run . list It must be an absolute path whose parent directory exists. +To build a binary that reports a real version rather than `dev`, stamp it the +way the release workflow does: + +```sh +go build -ldflags "-X github.com/jonascript/ike/internal/cli.version=$(git describe --tags --always)" -o ike . +``` + +The demo GIF in the README is generated from a checked-in script, so it can be +regenerated when the UI changes rather than re-recorded by hand: + +```sh +brew install vhs # https://github.com/charmbracelet/vhs +vhs assets/demo.tape +``` + +It builds ike from the working tree and seeds a throwaway matrix in a fresh +`mktemp -d`, so recording never touches your own data file. + ## How the pieces fit together Dependency direction is `cli` → `tui`/`mcpserver` → `store` → `task`. diff --git a/README.md b/README.md index 5642418..b64df00 100644 --- a/README.md +++ b/README.md @@ -13,83 +13,28 @@ Minimum Go version

---- - -An Eisenhower matrix task manager, named for the president who popularized the -method ("Ike" was his campaign-era nickname). One binary, three ways in: - -- **TUI** — run `ike` for an interactive 4-quadrant matrix -- **CLI** — `ike add`, `ike done`, … for quick capture and scripting -- **MCP** — `ike mcp` serves the Model Context Protocol so AI agents can manage your matrix - -All three share a single JSON data file, safe against concurrent writes. - -## The matrix - -| | Urgent | Not urgent | -|---|---|---| -| **Important** | 1 · Do It First | 2 · Schedule It | -| **Not important** | 3 · Delegate It | 4 · Consider Eliminating It | - -Those headings are defaults, not fixed: rename any quadrant with `t` in the TUI -or `ike label`. The quadrant *numbers* never change, so scripts and the MCP -tools keep working whatever you call them. - -## Spaces - -One data file holds several independent matrices, called **spaces** — work and -personal, say. Each has its own tasks, archive, quadrant headings, ID numbering, -and undo history, so `ike undo` in one can never reach into another. A fresh -install has a single space named `default`, and nothing changes until you make -a second. - -```sh -ike space # list spaces, marking the current one -ike space new work # create it (does not switch) -ike space use work # switch; every later command follows -ike space rename work job -ike space rm work # refuses a non-empty space without --force -``` - -Every command takes `-s/--space NAME` to act on one space just once, without -switching: - -```sh -ike add "Fix prod bug" -s work -q 1 -ike list -s work -``` - -Two concepts worth keeping apart: a **file** is a document and contains spaces; -a **space** is one matrix inside it. `--file` picks the document, `--space` picks -the matrix. In the TUI, `s` opens a space picker and `]`/`[` move between spaces. - -**Deleting a space cannot be undone.** History lives inside the space, so there -is no stack left to revert from — which is why `ike space rm` names the counts it -is about to destroy and needs `--force` if the space still holds anything. The -previous file contents remain in `tasks.json.bak` until the next change. - -## Moving a matrix between machines +

+ Capturing a task from the shell with ike add, then the four-quadrant TUI: moving between tasks, opening an attached plan, adding a task, reclassifying it into another quadrant, and undoing that with an explanation of what was undone +

-The data file is self-contained and fully portable: nothing in it refers to a -path or a machine, and timestamps are stored in UTC. Copy it to another computer -and every space comes with it. The sidecar `.lock` and `.bak` files do not need -copying. +--- -To move one space rather than the whole file: +A task manager that sorts work by urgency against importance, into four +quadrants — the [method popularized +by](https://en.wikipedia.org/wiki/Time_management#The_Eisenhower_Method) the +president whose campaign-era nickname it borrows. What makes it unusual is that +an agent can work with it in both directions: -```sh -ike space export work ~/work-matrix.json # a standalone ike data file -# copy that one file to the other machine, then: -ike space import ~/work-matrix.json -ike space import ~/old.json --as archive-2025 -ike --file ~/work-matrix.json list # or just open it in place -``` +- **An agent can manage your matrix.** `ike mcp` serves the Model Context + Protocol, so Claude Code or any MCP client can capture, complete, and + reprioritise tasks while you work. Off until you allow it. +- **You can hand a task to an agent.** `ike plan 3` drafts a plan you read and + argue with; `ike delegate 3` carries it out in the task's own directory and + streams what it does. Also off until you allow it — separately. -An export is an ordinary data file, so `--file` opens it directly. **MCP access -is always off in an exported file**, whatever it was in the original: agent -access is a decision about a file on a machine, and an export is made to travel. -Importing a name that is already in use is an error rather than a merge — use -`--as` to bring it in under a different name. +One Go binary, three ways in (TUI, CLI, MCP), over a single JSON file that is +yours: mode `0600`, on your disk, no account and no server. All three can run at +once without losing writes. ## Install @@ -97,38 +42,40 @@ Importing a name that is already in use is an error rather than a merge — use brew install jonascript/tap/ike ``` -The formula builds from source, so Homebrew fetches Go for the build. Linux and -macOS, Intel and Apple silicon. - Or with Go 1.25+: ```sh go install github.com/jonascript/ike@latest ``` -Or from a checkout, with a stamped version: +Linux and macOS, Intel and Apple silicon. Prebuilt archives are attached to +every [release](https://github.com/jonascript/ike/releases) — they are not +code-signed, so verify what you download: ```sh -go build -ldflags "-X github.com/jonascript/ike/internal/cli.version=$(git describe --tags --always)" -o ike . +shasum -a 256 -c checksums.txt --ignore-missing +gh attestation verify ike_0.2.0_darwin_arm64.tar.gz --repo jonascript/ike ``` -### Prebuilt archives +That second command checks ike's published build provenance, which proves the +archive was built by this repository's release workflow from the tag it claims. -Every [release](https://github.com/jonascript/ike/releases) attaches archives -for macOS and Linux on both architectures. They are **not** code-signed, so -verify what you download — against `checksums.txt`: +## Quickstart ```sh -shasum -a 256 -c checksums.txt --ignore-missing +ike add "Fix prod bug" -q 1 # quadrant 1: urgent and important +ike add "Write the release notes" # defaults to quadrant 2 +ike # open the matrix ``` -and, if you have the GitHub CLI, against the build provenance ike publishes, -which proves the archive was built by this repository's release workflow from -the tag it claims: +| | Urgent | Not urgent | +|---|---|---| +| **Important** | 1 · Do It First | 2 · Schedule It | +| **Not important** | 3 · Delegate It | 4 · Consider Eliminating It | -```sh -gh attestation verify ike_0.1.0_darwin_arm64.tar.gz --repo jonascript/ike -``` +Those headings are defaults, not fixed — rename any of them with `t` in the TUI +or `ike label`. The quadrant *numbers* never change, so scripts and the MCP +tools keep working whatever you call them. ## TUI @@ -161,11 +108,10 @@ Run `ike` with no arguments. Changes made by the CLI or MCP server while the TUI is open appear within ~2 seconds. -A dim `◆ mcp` marker sits in the footer while AI agent access is enabled; see -[MCP](#mcp). No marker means nothing but you can reach the matrix. After a title, +A dim `◆ mcp` marker sits in the footer while [agent access](#agents) is +enabled. No marker means nothing but you can reach the matrix. After a title, `✎` means the task has a plan attached, `⌁` that it has a conversation you can -pick back up, and `⣾` that an agent is working on it right now (see -[Delegating a task](#delegating-a-task)). +pick back up, and `⣾` that an agent is working on it right now. ## CLI @@ -181,290 +127,73 @@ ike archive # completed tasks, newest first ike restore 3 # un-archive task 3, back to its quadrant ike undo # revert the last change, from any frontend ike redo # re-apply the last undone change -ike label # show the four quadrant headings -ike label 1 "Firefighting" # rename a quadrant -ike label 1 --reset # restore its default name +ike label 1 "Firefighting" # rename a quadrant (--reset restores the default) -ike plan 3 # draft a plan for task 3: see "Delegating a task" +ike plan 3 # draft a plan for task 3 ike delegate 3 # hand task 3 to an agent ike agent status # whether ike may run an agent -ike space # spaces: see "Spaces" below +ike space # list spaces ike list -s work # act on one space just this once ike --file /path/to.json list # act on a different data file ``` -## Renaming the quadrants +## Agents -The default headings are *Do It First*, *Schedule It*, *Delegate It*, and -*Consider Eliminating It*. Rename any of them with `t` in the TUI or `ike label` -— up to 40 characters. Custom names are stored per data file and shared by all -three frontends; a rename is undoable like any other change. - -Only the display name changes. Quadrant numbers 1–4 and their urgent/important -meanings are fixed, so `ike add -q 1`, the matrix layout, and the MCP tools are -unaffected by whatever you call them. Clearing a name (empty input, or -`--reset`) drops the override and restores the default, which also means a -future release's defaults reach you rather than being frozen at rename time. - -## Undo and redo - -Every change records a snapshot, so `ike undo` (or `u` in the TUI) reverts the -last one — including a delete, and including changes made from a different -frontend. Run it repeatedly to walk further back; the last 20 changes are kept -in the data file, so history survives restarts. Undo does not recycle task IDs. -History belongs to the space it was made in, so `ike undo` never reverts a change -made in a different one. - -`ike redo` (or `U` / `ctrl+r`) re-applies what you just undid, and is itself -undoable. **Any new change discards the redo history** — that includes a change -made from another frontend, so an MCP agent writing while your TUI is open will -clear a redo you were holding. The alternative is replaying a snapshot from a -branch you've diverged from, which would silently clobber the newer change. The -TUI only shows the redo hint while a redo is actually available. - -## MCP - -**Agent access is off by default.** ike will not serve your tasks over MCP -until you say so, and registering ike with an MCP client is not enough on its -own — `ike mcp` refuses to start while access is off: +Both directions are off by default, and they are separate decisions: letting an +agent edit your task list and letting ike start a process that edits your +*files* have very different blast radii. ```sh -ike mcp enable # allow AI agents to read and manage this matrix -ike mcp disable # revoke it, including for a session already connected -ike mcp status # show the current setting and which data file it applies to +ike mcp enable # let an MCP client read and manage this matrix +ike agent enable # let ike run an agent on a task's working directory ``` -The setting is remembered in the data file, so it survives restarts and -upgrades, and it is scoped to that **file** — which means every space in it. -Enabling access for one space enables it for all of them; to keep a matrix out -of reach, put it in a separate file (see [Spaces](#spaces)) or pin the server to -one space with `ike -s work mcp`. Access never travels: a space you export -always lands with it switched off. It is not -part of undo history — no sequence of `ike undo` can re-open access you closed. -While access is on, the TUI shows a `◆ mcp` marker in its footer, and `?` -explains the setting either way. - -`ike mcp disable` takes effect immediately, including on a client that is -already connected: the setting is re-checked on every read and every write, so -the next thing an agent tries fails. It does not wait for the session to end. - -> **What this is, and isn't.** The gate is a **consent mechanism, not a security -> boundary.** It controls whether ike's MCP server will serve your matrix. It -> cannot restrain an agent that already has shell access on your machine — such -> an agent can read `~/.local/share/ike/tasks.json` directly, run `ike list`, or -> simply run `ike mcp enable` itself. Treat it as "I have decided to let my -> agent manage my tasks", not as a sandbox. - -Once enabled, `ike mcp` runs an MCP server on stdio with tools `list_tasks`, -`add_task`, `complete_task`, `move_task`, `reorder_task`, `update_task`, -`delete_task`, `list_archive`, `restore_task`, `undo`, `redo`, `list_quadrants`, -`set_quadrant_label`, and `list_spaces`. - -Every tool takes an optional `space` argument and defaults to whichever space -you are on, so an agent can work in one matrix while you work in another. It is -read-only about spaces: no tool can create, rename, delete, or switch one, since -switching would change what your own TUI and a bare `ike list` show. Launching -the server as `ike -s work mcp` pins it to that space — a request naming another -is refused, and `list_spaces` then reports only the one it was launched for. - -Register with Claude Code: +**Serving your matrix over MCP.** `ike mcp` runs a stdio server with tools for +listing, adding, completing, moving, reordering, archiving, and undoing — +scoped to one space if you launch it that way. `ike mcp disable` takes effect +immediately, even on a client already connected. ```sh ike mcp enable claude mcp add ike -- ike mcp ``` -Or in any MCP client config: +**Handing a task over.** `ike plan 3` asks an agent to explore the task's +directory and draft a plan, which is attached to the task for you to read and +edit — that run is read-only and needs no permission. `ike delegate 3` then +carries the plan out and streams what it does. `-i` on either hands you the +terminal for a real Claude Code session instead, resuming the same conversation +each time you come back to the task. -```json -{ "mcpServers": { "ike": { "command": "ike", "args": ["mcp"] } } } -``` - -## Delegating a task +A delegated run never completes the task. You read what it did and decide. -MCP lets an agent manage your matrix. This is the other direction: handing a -task *to* an agent. It runs the [Claude Code](https://claude.com/claude-code) -CLI — your own installation, so your authentication, settings, and per-project -`CLAUDE.md` all apply. +→ **[docs/agents.md](docs/agents.md)** covers the consent gates, what a +delegated run is actually allowed to do (with measurements — the permission +modes are not a safety ladder), how ike picks an effort level per run, and where +plans are stored. -There are two steps, and you can stop after the first. +## Spaces and data -**Draft a plan.** `ike plan 3` (or `P` in the TUI) asks an agent to explore the -task's working directory and write a plan, which is then attached to the task. -This is read-only — it runs in Claude Code's plan mode and cannot change -anything — so it needs no permission. +One file holds several independent matrices, called **spaces** — work and +personal, say. Each has its own tasks, archive, headings, ID numbering, and undo +history, so `ike undo` in one can never reach into another. ```sh -ike plan 3 # draft one, streaming as it works -ike plan 3 --show # print it -ike plan 3 --edit # open it in $EDITOR -ike plan 3 --from-file notes.md # attach one you wrote yourself -ike plan 3 --clear # remove it +ike space new work +ike space use work # every later command follows +ike add "Fix prod bug" -s work # or act on one space just once ``` -The plan is yours. Read it, argue with it, edit it — then either do the work -yourself, or hand it on. - -**Or talk it through.** `ike plan 3 -i` (or `c` in the TUI) hands the terminal to -a real Claude Code session, opened in the task's directory and already briefed on -it. Exit the session and you're back in ike exactly where you were. - -The conversation belongs to the task. Run it again next week and you resume the -same session — full history, nothing re-explained: - -```sh -ike plan 3 -i # first time: a new conversation, briefed on the task -ike plan 3 -i # later: picks up where you left off -ike plan 3 -i --new-session # start over deliberately -``` - -When you agree on a plan, the agent writes it to a path ike gave it, and ike -attaches it to the task as you come back — so `ike plan 3 --show` reflects what -you actually decided together. A conversation that ends without a plan attaches -nothing, which is most of them. - -`⌁` marks a task with a conversation waiting to be picked up. - -**Hand it on.** `ike delegate 3` (or `D`) runs an agent that carries the task -out, following the attached plan if there is one. - -```sh -ike agent enable # required before any delegated run -ike delegate 3 # follow the attached plan, streaming -ike delegate 3 -i # supervise it in a real session instead -ike delegate 3 --plan-first # draft a plan, then carry it out -ike delegate 3 --dir ~/dev/thing # set the working directory -ike delegate 3 --model opus # choose a model -ike delegate 3 --effort max # choose how hard it works -``` - -`-i` works here too, and resumes the same per-task conversation: the agent does -the work while you watch and answer its questions, rather than reporting back -afterwards. Being present doesn't remove the gate — `ike agent enable` is still -required, because it's still ike starting an agent that edits your files. - -**Delegation is off by default**, separately from MCP access. Letting an agent -edit your task list and letting ike start a process that edits your *files* are -different decisions, so agreeing to one is not agreeing to the other. Like the -MCP gate, the setting is per data file, survives restarts, is never carried into -an export, and is not part of undo history — no sequence of `ike undo` can -re-open it. - -**A delegated run never completes the task** — read what it did and decide. - -### How hard the agent works - -Effort controls how deeply the agent thinks, how many tools it reaches for, and -how much it says on the way. It usually matters more than the model: a run's wall -clock is dominated by how many turns it takes, and effort is what moves that. - -ike picks a level per run rather than using one setting for everything, because -the two things it delegates are not the same shape of work — and it prints what -it chose, and why, in the run header: - -| run | effort | why | -|---|---|---| -| `ike plan 3` | `high` | drafting a plan — the thinking *is* the product | -| `ike delegate 3` with a plan attached | `medium` | following an attached plan; the approach is already decided and reviewed | -| `ike delegate 3` with no plan | `high` | no plan to follow, so it has to work the approach out as well as do it | - -``` -$ ike delegate 3 -delegating 3 Fix the flaky reorder test - in /Users/you/dev/thing - · effort medium — following an attached plan -``` - -`--effort low|medium|high|xhigh|max` overrides it, on both `ike plan` and -`ike delegate`, and the header then just states the level. A mistyped level fails -before any process starts. Attaching a plan is therefore also the cheapest way to -make a delegated run cheaper: the run stops paying to re-derive decisions the -plan already records. - -The TUI has no flag for this and always uses the recommendation, shown in the -same line at the top of the run view. - -### What a delegated run is allowed to do - -Runs are unattended, so nothing can prompt you part-way through. The default is -`--permission-mode auto`, which lets the agent change files *and run commands* in -the working directory. - -Do not read the mode names as a safety ladder — they are not. Measured against a -real run, asking an agent to `rm` a file: - -| `--permission-mode` | result | -|---|---| -| `manual` | denied — the file survived | -| `acceptEdits` | allowed — the file was deleted | -| `auto` (default) | allowed — the file was deleted | -| `bypassPermissions` | allowed, by definition | - -So `acceptEdits` is **not** a middle setting that withholds the shell. -`manual` is the one mode that meaningfully restrains a delegated run: with nobody -to ask, it denies anything needing approval, and the denials come back in the -transcript. Use it when you want the agent to work and then stop at the first -thing you would have wanted to be asked about. - -```sh -ike delegate 3 --permission-mode manual -``` +Everything lives in `~/.local/share/ike/tasks.json` (mode `0600`, in a `0700` +directory). Writes go through a lock file and an atomic rename with the previous +contents kept as `.bak`, so three frontends can run at once and an interrupted +write costs one change rather than the matrix. The file is self-contained and +portable — copy it to another machine and every space comes with it. -One trap worth knowing if you test this yourself: commands the harness -classifies as safe — `echo`, `ls` — run under *every* mode including `manual`, -so a harmless command shows no difference between any of them. - -The practical control is therefore the gate — whether ike starts an agent at all -— rather than the mode it starts it in. - -Each task remembers a **working directory**. The first run stores the one you -give it, or the directory you ran from, so `cd ~/dev/thing && ike delegate 3` -does the obvious thing and later runs go back to the same project wherever you -start them. - -In the TUI a run takes over the screen and streams. `esc` detaches and the run -keeps going — the task shows `⣾` in the matrix and `D` on it reattaches. `ctrl+c` -in the run view stops the run. Quitting ike stops it too: the agent is a child -process, and ike will not leave one running with nothing reading it. - -> **The same caveat as MCP.** The gate is a consent mechanism, not a security -> boundary. It decides whether *ike* starts an agent; it does nothing about what -> that agent then does, which is governed by Claude Code's own permissions. And -> anyone who can run commands as you can run `claude` directly. - -Plans are stored beside the data file, one file per task, in -`tasks.json.plans//.md` — not inside `tasks.json`, so a few KB of -markdown per task is not copied into every undo snapshot. Deleting a task leaves -its plan, so undoing the delete brings both back; `ike plan --prune` sweeps the -ones left behind. Note that plans do **not** travel with `ike space export` yet. - -Delegation needs `claude` on your `PATH`; `ike agent status` says whether it -found it. Set `IKE_AGENT_CMD` to point at a different binary or a wrapper. - -## Data - -Tasks live in `$XDG_DATA_HOME/ike/tasks.json` (default -`~/.local/share/ike/tasks.json`), created mode `0600` in a `0700` directory — -your matrix is not readable by other users on the machine. - -Writes are serialized through a sidecar lock file and land via an atomic -rename, so the TUI, CLI, and MCP server can run at the same time without losing -updates. Each write is flushed to disk before the rename, and the previous -contents are kept as `tasks.json.bak`, so an interrupted write costs at most -one change rather than the whole matrix. If the file is ever unreadable, ike -refuses to overwrite it rather than starting fresh over the top. - -Override the location with `--file` or `IKE_DATA_FILE` — highest precedence -first: `--file`, then `IKE_DATA_FILE`, then the default above. Either must be an -**absolute** path whose parent directory already exists (a relative path would resolve against -whatever directory the process happened to start in — not something you can -predict when an MCP client launches ike for you). - -One caveat on the concurrency guarantee: it relies on advisory `flock`, which is -unreliable on NFS and some FUSE mounts — so pointing the data file at a Dropbox, -iCloud, or network-mounted folder and writing from two machines at once is not -covered. A single machine writing to a synced folder is fine. +→ **[docs/data.md](docs/data.md)** covers spaces in full, exporting and +importing a single matrix, the file's durability guarantees and their one +caveat (`flock` on network mounts), undo/redo semantics, and renaming quadrants. ## Platform support diff --git a/assets/demo-seed.sh b/assets/demo-seed.sh new file mode 100644 index 0000000..992f5ef --- /dev/null +++ b/assets/demo-seed.sh @@ -0,0 +1,47 @@ +# Seeds a throwaway matrix for the demo recording. Meant to be sourced, not run: +# it exports PATH and IKE_DATA_FILE into the calling shell, which is what lets +# demo.tape drive a real `ike` rather than a mock. +# +# The data file is a fresh mktemp directory every time, so recording a demo can +# never touch the matrix you actually use. Everything else in here is chosen to +# look like a real week of work on this repo. + +set -e + +demo_dir="$(mktemp -d)" +demo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# Built from the working tree, so the GIF always shows the code it ships with. +go build -o "$demo_dir/ike" "$demo_root" + +export PATH="$demo_dir:$PATH" +export IKE_DATA_FILE="$demo_dir/demo.json" + +ike add "Fix the flaky reorder test" -q 1 >/dev/null +ike add "Reply to the security report" -q 1 >/dev/null +ike add "Write the v0.3 release notes" -q 2 >/dev/null +ike add "Add plan export to space export" -q 2 >/dev/null +ike add "Bump the Homebrew formula" -q 3 >/dev/null +ike add "Triage this week's new issues" -q 3 >/dev/null +ike add "Hand-tune the logo SVG again" -q 4 >/dev/null + +# One task carries a plan, so the ✎ mark and the plan view have something real +# to show without the demo having to run (and pay for) an agent. +cat > "$demo_dir/plan.md" <<'PLAN' +## Goal + +Make reorder deterministic under -race. + +## Steps + +1. Reproduce with `go test -run TestReorder -race -count=50` +2. Renormalise ranks inside the flock, not after it +3. Add a regression test that interleaves two writers + +## Checking it worked + +The loop above passes 50/50, and `ike list` order is unchanged. +PLAN +ike plan 1 --from-file "$demo_dir/plan.md" >/dev/null + +set +e diff --git a/assets/demo.gif b/assets/demo.gif new file mode 100644 index 0000000..107c903 Binary files /dev/null and b/assets/demo.gif differ diff --git a/assets/demo.tape b/assets/demo.tape new file mode 100644 index 0000000..7193645 --- /dev/null +++ b/assets/demo.tape @@ -0,0 +1,75 @@ +# Demo recording for the README, driven by VHS (https://github.com/charmbracelet/vhs). +# +# brew install vhs +# vhs assets/demo.tape +# +# Checked in so the demo can be regenerated when the UI changes, rather than +# being a hand-recorded artifact nobody can reproduce. It drives the real binary +# built from the working tree against a throwaway data file — see demo-seed.sh. + +Output assets/demo.gif + +Set Shell bash +Set FontSize 15 +Set Width 1180 +Set Height 700 +Set Padding 24 +Set Theme "Catppuccin Mocha" +Set TypingSpeed 55ms + +# Build and seed off camera. +Hide +Type "source assets/demo-seed.sh" +Enter +Sleep 8s +Type "clear" +Enter +Sleep 1s +Show + +# Capture from the shell, the way most tasks actually arrive. +Type "ike add 'Post the v0.2 announcement' -q 2" +Sleep 700ms +Enter +Sleep 1800ms + +# Then open the matrix. +Type "ike" +Sleep 500ms +Enter +Sleep 2500ms + +# Move around quadrant 1. +Type "j" +Sleep 900ms +Type "k" +Sleep 1200ms + +# The task carries a plan — open it. +Type "p" +Sleep 3000ms +Escape +Sleep 1200ms + +# Capture straight into the matrix. +Type "a" +Sleep 600ms +Type "Reply to the packaging thread" +Sleep 700ms +Enter +Sleep 1800ms + +# Reclassify it: not important, just urgent. +Type "m" +Sleep 900ms +Type "3" +Sleep 2000ms + +# Undo history spans every frontend and survives restarts, and says what it +# undid rather than just moving something back. +Type "u" +Sleep 3000ms + +# Ends on the matrix rather than back at a bare prompt, so the loop reads as a +# tool rather than as a session that finished. +Sleep 500ms diff --git a/docs/agents.md b/docs/agents.md new file mode 100644 index 0000000..3dfeb1f --- /dev/null +++ b/docs/agents.md @@ -0,0 +1,237 @@ +# Agents + +ike works with agents in both directions: an agent can manage your matrix, and +you can hand a task *to* an agent. They are separate features with separate +permissions, and both are off until you say otherwise. + +- [MCP: letting an agent manage your matrix](#mcp) +- [Delegating a task to an agent](#delegating-a-task) + +--- + +## MCP + +**Agent access is off by default.** ike will not serve your tasks over MCP +until you say so, and registering ike with an MCP client is not enough on its +own — `ike mcp` refuses to start while access is off: + +```sh +ike mcp enable # allow AI agents to read and manage this matrix +ike mcp disable # revoke it, including for a session already connected +ike mcp status # show the current setting and which data file it applies to +``` + +The setting is remembered in the data file, so it survives restarts and +upgrades, and it is scoped to that **file** — which means every space in it. +Enabling access for one space enables it for all of them; to keep a matrix out +of reach, put it in a separate file (see [Spaces](data.md#spaces)) or pin the +server to one space with `ike -s work mcp`. Access never travels: a space you +export always lands with it switched off. It is not part of undo history — no +sequence of `ike undo` can re-open access you closed. While access is on, the +TUI shows a `◆ mcp` marker in its footer, and `?` explains the setting either +way. + +`ike mcp disable` takes effect immediately, including on a client that is +already connected: the setting is re-checked on every read and every write, so +the next thing an agent tries fails. It does not wait for the session to end. + +> **What this is, and isn't.** The gate is a **consent mechanism, not a security +> boundary.** It controls whether ike's MCP server will serve your matrix. It +> cannot restrain an agent that already has shell access on your machine — such +> an agent can read `~/.local/share/ike/tasks.json` directly, run `ike list`, or +> simply run `ike mcp enable` itself. Treat it as "I have decided to let my +> agent manage my tasks", not as a sandbox. + +Once enabled, `ike mcp` runs an MCP server on stdio with tools `list_tasks`, +`add_task`, `complete_task`, `move_task`, `reorder_task`, `update_task`, +`delete_task`, `list_archive`, `restore_task`, `undo`, `redo`, `list_quadrants`, +`set_quadrant_label`, and `list_spaces`. + +Every tool takes an optional `space` argument and defaults to whichever space +you are on, so an agent can work in one matrix while you work in another. It is +read-only about spaces: no tool can create, rename, delete, or switch one, since +switching would change what your own TUI and a bare `ike list` show. Launching +the server as `ike -s work mcp` pins it to that space — a request naming another +is refused, and `list_spaces` then reports only the one it was launched for. + +Register with Claude Code: + +```sh +ike mcp enable +claude mcp add ike -- ike mcp +``` + +Or in any MCP client config: + +```json +{ "mcpServers": { "ike": { "command": "ike", "args": ["mcp"] } } } +``` + +--- + +## Delegating a task + +MCP lets an agent manage your matrix. This is the other direction: handing a +task *to* an agent. It runs the [Claude Code](https://claude.com/claude-code) +CLI — your own installation, so your authentication, settings, and per-project +`CLAUDE.md` all apply. + +There are two steps, and you can stop after the first. + +### Draft a plan + +`ike plan 3` (or `P` in the TUI) asks an agent to explore the task's working +directory and write a plan, which is then attached to the task. This is +read-only — it runs in Claude Code's plan mode and cannot change anything — so +it needs no permission. + +```sh +ike plan 3 # draft one, streaming as it works +ike plan 3 --show # print it +ike plan 3 --edit # open it in $EDITOR +ike plan 3 --from-file notes.md # attach one you wrote yourself +ike plan 3 --clear # remove it +``` + +The plan is yours. Read it, argue with it, edit it — then either do the work +yourself, or hand it on. + +### Or talk it through + +`ike plan 3 -i` (or `c` in the TUI) hands the terminal to a real Claude Code +session, opened in the task's directory and already briefed on it. Exit the +session and you're back in ike exactly where you were. + +The conversation belongs to the task. Run it again next week and you resume the +same session — full history, nothing re-explained: + +```sh +ike plan 3 -i # first time: a new conversation, briefed on the task +ike plan 3 -i # later: picks up where you left off +ike plan 3 -i --new-session # start over deliberately +``` + +When you agree on a plan, the agent writes it to a path ike gave it, and ike +attaches it to the task as you come back — so `ike plan 3 --show` reflects what +you actually decided together. A conversation that ends without a plan attaches +nothing, which is most of them. + +`⌁` marks a task with a conversation waiting to be picked up. + +### Hand it on + +`ike delegate 3` (or `D`) runs an agent that carries the task out, following the +attached plan if there is one. + +```sh +ike agent enable # required before any delegated run +ike delegate 3 # follow the attached plan, streaming +ike delegate 3 -i # supervise it in a real session instead +ike delegate 3 --plan-first # draft a plan, then carry it out +ike delegate 3 --dir ~/dev/thing # set the working directory +ike delegate 3 --model opus # choose a model +ike delegate 3 --effort max # choose how hard it works +``` + +`-i` works here too, and resumes the same per-task conversation: the agent does +the work while you watch and answer its questions, rather than reporting back +afterwards. Being present doesn't remove the gate — `ike agent enable` is still +required, because it's still ike starting an agent that edits your files. + +**Delegation is off by default**, separately from MCP access. Letting an agent +edit your task list and letting ike start a process that edits your *files* are +different decisions, so agreeing to one is not agreeing to the other. Like the +MCP gate, the setting is per data file, survives restarts, is never carried into +an export, and is not part of undo history — no sequence of `ike undo` can +re-open it. + +**A delegated run never completes the task** — read what it did and decide. + +### How hard the agent works + +Effort controls how deeply the agent thinks, how many tools it reaches for, and +how much it says on the way. It usually matters more than the model: a run's wall +clock is dominated by how many turns it takes, and effort is what moves that. + +ike picks a level per run rather than using one setting for everything, because +the two things it delegates are not the same shape of work — and it prints what +it chose, and why, in the run header: + +| run | effort | why | +|---|---|---| +| `ike plan 3` | `high` | drafting a plan — the thinking *is* the product | +| `ike delegate 3` with a plan attached | `medium` | following an attached plan; the approach is already decided and reviewed | +| `ike delegate 3` with no plan | `high` | no plan to follow, so it has to work the approach out as well as do it | + +``` +$ ike delegate 3 +delegating 3 Fix the flaky reorder test + in /Users/you/dev/thing + · effort medium — following an attached plan +``` + +`--effort low|medium|high|xhigh|max` overrides it, on both `ike plan` and +`ike delegate`, and the header then just states the level. A mistyped level fails +before any process starts. Attaching a plan is therefore also the cheapest way to +make a delegated run cheaper: the run stops paying to re-derive decisions the +plan already records. + +The TUI has no flag for this and always uses the recommendation, shown in the +same line at the top of the run view. + +### What a delegated run is allowed to do + +Runs are unattended, so nothing can prompt you part-way through. The default is +`--permission-mode auto`, which lets the agent change files *and run commands* in +the working directory. + +Do not read the mode names as a safety ladder — they are not. Measured against a +real run, asking an agent to `rm` a file: + +| `--permission-mode` | result | +|---|---| +| `manual` | denied — the file survived | +| `acceptEdits` | allowed — the file was deleted | +| `auto` (default) | allowed — the file was deleted | +| `bypassPermissions` | allowed, by definition | + +So `acceptEdits` is **not** a middle setting that withholds the shell. +`manual` is the one mode that meaningfully restrains a delegated run: with nobody +to ask, it denies anything needing approval, and the denials come back in the +transcript. Use it when you want the agent to work and then stop at the first +thing you would have wanted to be asked about. + +```sh +ike delegate 3 --permission-mode manual +``` + +One trap worth knowing if you test this yourself: commands the harness +classifies as safe — `echo`, `ls` — run under *every* mode including `manual`, +so a harmless command shows no difference between any of them. + +The practical control is therefore the gate — whether ike starts an agent at all +— rather than the mode it starts it in. + +Each task remembers a **working directory**. The first run stores the one you +give it, or the directory you ran from, so `cd ~/dev/thing && ike delegate 3` +does the obvious thing and later runs go back to the same project wherever you +start them. + +In the TUI a run takes over the screen and streams. `esc` detaches and the run +keeps going — the task shows `⣾` in the matrix and `D` on it reattaches. `ctrl+c` +in the run view stops the run. Quitting ike stops it too: the agent is a child +process, and ike will not leave one running with nothing reading it. + +> **The same caveat as MCP.** The gate is a consent mechanism, not a security +> boundary. It decides whether *ike* starts an agent; it does nothing about what +> that agent then does, which is governed by Claude Code's own permissions. And +> anyone who can run commands as you can run `claude` directly. + +Plans are stored beside the data file, one file per task, in +`tasks.json.plans//.md` — not inside `tasks.json`, so a few KB of +markdown per task is not copied into every undo snapshot. Deleting a task leaves +its plan, so undoing the delete brings both back; `ike plan --prune` sweeps the +ones left behind. Note that plans do **not** travel with `ike space export` yet. + +Delegation needs `claude` on your `PATH`; `ike agent status` says whether it +found it. Set `IKE_AGENT_CMD` to point at a different binary or a wrapper. diff --git a/docs/data.md b/docs/data.md new file mode 100644 index 0000000..4306dcb --- /dev/null +++ b/docs/data.md @@ -0,0 +1,124 @@ +# Spaces, files, and your data + +Everything ike knows lives in one JSON file you own. This page covers how that +file is organised, where it lives, how it survives being written to by three +frontends at once, and how to move it around. + +- [Spaces](#spaces) +- [Moving a matrix between machines](#moving-a-matrix-between-machines) +- [Where your data lives](#where-your-data-lives) +- [Undo and redo](#undo-and-redo) +- [Renaming the quadrants](#renaming-the-quadrants) + +--- + +## Spaces + +One data file holds several independent matrices, called **spaces** — work and +personal, say. Each has its own tasks, archive, quadrant headings, ID numbering, +and undo history, so `ike undo` in one can never reach into another. A fresh +install has a single space named `default`, and nothing changes until you make +a second. + +```sh +ike space # list spaces, marking the current one +ike space new work # create it (does not switch) +ike space use work # switch; every later command follows +ike space rename work job +ike space rm work # refuses a non-empty space without --force +``` + +Every command takes `-s/--space NAME` to act on one space just once, without +switching: + +```sh +ike add "Fix prod bug" -s work -q 1 +ike list -s work +``` + +Two concepts worth keeping apart: a **file** is a document and contains spaces; +a **space** is one matrix inside it. `--file` picks the document, `--space` picks +the matrix. In the TUI, `s` opens a space picker and `]`/`[` move between spaces. + +**Deleting a space cannot be undone.** History lives inside the space, so there +is no stack left to revert from — which is why `ike space rm` names the counts it +is about to destroy and needs `--force` if the space still holds anything. The +previous file contents remain in `tasks.json.bak` until the next change. + +## Moving a matrix between machines + +The data file is self-contained and fully portable: nothing in it refers to a +path or a machine, and timestamps are stored in UTC. Copy it to another computer +and every space comes with it. The sidecar `.lock` and `.bak` files do not need +copying. + +To move one space rather than the whole file: + +```sh +ike space export work ~/work-matrix.json # a standalone ike data file +# copy that one file to the other machine, then: +ike space import ~/work-matrix.json +ike space import ~/old.json --as archive-2025 +ike --file ~/work-matrix.json list # or just open it in place +``` + +An export is an ordinary data file, so `--file` opens it directly. **MCP access +is always off in an exported file**, whatever it was in the original: agent +access is a decision about a file on a machine, and an export is made to travel. +Importing a name that is already in use is an error rather than a merge — use +`--as` to bring it in under a different name. + +Note that attached plans do **not** yet travel with `ike space export`. + +## Where your data lives + +Tasks live in `$XDG_DATA_HOME/ike/tasks.json` (default +`~/.local/share/ike/tasks.json`), created mode `0600` in a `0700` directory — +your matrix is not readable by other users on the machine. + +Writes are serialized through a sidecar lock file and land via an atomic +rename, so the TUI, CLI, and MCP server can run at the same time without losing +updates. Each write is flushed to disk before the rename, and the previous +contents are kept as `tasks.json.bak`, so an interrupted write costs at most +one change rather than the whole matrix. If the file is ever unreadable, ike +refuses to overwrite it rather than starting fresh over the top. + +Override the location with `--file` or `IKE_DATA_FILE` — highest precedence +first: `--file`, then `IKE_DATA_FILE`, then the default above. Either must be an +**absolute** path whose parent directory already exists (a relative path would +resolve against whatever directory the process happened to start in — not +something you can predict when an MCP client launches ike for you). + +One caveat on the concurrency guarantee: it relies on advisory `flock`, which is +unreliable on NFS and some FUSE mounts — so pointing the data file at a Dropbox, +iCloud, or network-mounted folder and writing from two machines at once is not +covered. A single machine writing to a synced folder is fine. + +## Undo and redo + +Every change records a snapshot, so `ike undo` (or `u` in the TUI) reverts the +last one — including a delete, and including changes made from a different +frontend. Run it repeatedly to walk further back; the last 20 changes are kept +in the data file, so history survives restarts. Undo does not recycle task IDs. +History belongs to the space it was made in, so `ike undo` never reverts a change +made in a different one. + +`ike redo` (or `U` / `ctrl+r`) re-applies what you just undid, and is itself +undoable. **Any new change discards the redo history** — that includes a change +made from another frontend, so an MCP agent writing while your TUI is open will +clear a redo you were holding. The alternative is replaying a snapshot from a +branch you've diverged from, which would silently clobber the newer change. The +TUI only shows the redo hint while a redo is actually available. + +## Renaming the quadrants + +The default headings are *Do It First*, *Schedule It*, *Delegate It*, and +*Consider Eliminating It*. Rename any of them with `t` in the TUI or `ike label` +— up to 40 characters. Custom names are stored per data file and shared by all +three frontends; a rename is undoable like any other change. + +Only the display name changes. Quadrant numbers 1–4 and their urgent/important +meanings are fixed, so `ike add -q 1`, the matrix layout, and the MCP tools are +unaffected by whatever you call them. Clearing a name (empty input, or +`--reset`) drops the override and restores the default, which also means a +future release's defaults reach you rather than being frozen at rename time.