office: one word for everything — forward board/reset/delete/merge/review/triage, and a help page that lists all of it

This commit is contained in:
Sam
2026-11-13 17:26:58 +11:00
parent 6dcdcbc818
commit 0717697f03
7 changed files with 150 additions and 96 deletions

View File

@@ -371,70 +371,65 @@ def run_on_remote(host, folder, opts):
return 0
USAGE = """office — open a folder as a project, and say what else can be run
USAGE = """office — everything for one project, under one word
office <folder> … open a folder as a project
office help this page
office board … build or repair the panes
office reset <folder> … clear panes and worktrees, keep the history
office delete <folder> … remove sessions, the Paperclip project, .git, dot files
office merge <task> … rebase, check, review, merge one task
office review <task> … run ocr over one task's diff
office triage <task> … ask Mouse-1 one question about a task
Every one of these is also on your PATH under its own name (board, merge,
review, triage, project-reset, project-delete). Both spellings do the same
thing; office is the one to remember.
THE RULE
The folder and the session live on the same machine. If you are typing inside
the session, on the machine that owns the folder, it is just: office <folder>
Use --host only when the folder is on a different machine from your shell.
WHAT IT DOES
1. reads the folder
2. if a machine is named, checks whether that is this machine, and runs locally if so
3. if it really is another machine, checks the login and runs the same command there
4. adopts the Paperclip project, or creates it with --new
5. writes what is missing in <folder>/.harness/ and never overwrites
6. records the project in ~/.config/paperclip-harness/projects.toml
7. creates the session on that machine's daemon, then builds eight panes
OPENING A FOLDER
office <folder> --project "<Name>" adopt a project Paperclip already has
office <folder> --new "<Name>" create the Paperclip project as well
office . adopt the folder you are standing in
office <folder> --session <name> name the session instead of the folder
office <folder> … --host <machine> the folder is on that machine
office <folder> … --dry say what would happen, write nothing
COMMANDS
What it does, in order: reads the folder; runs locally if the named machine is
this one; otherwise checks the login and runs the same command there; adopts or
creates the Paperclip project; writes what is missing in <folder>/.harness/ and
never overwrites; records the project in ~/.config/paperclip-harness/projects.toml;
creates the session on that machine's daemon; builds eight panes.
Each of these is its own command on your PATH, not a subcommand of office:
type `board`, not `office board`.
PAGES: office board [--folder <f>] [--replace] [--reset]
board create any pane that is missing, close nothing
board --replace close this project's panes, then build all eight again
board --reset close every pane in the session, wait, then build
board --folder <folder> do it for another project
office <folder> --project "<Name>" adopt a project Paperclip already has
office <folder> --new "<Name>" create the Paperclip project as well
office . adopt the folder you are standing in
office <folder> --session <name> name the session instead of the folder
office <folder> --project "<Name>" --host <machine> the folder is on that machine
office <folder> --new "<Name>" --dry say what would happen, write nothing
office <folder> --new "<Name>" (with no repository it asks Y or N)
office <folder> --new "<Name>" --commit-all make the repository and commit everything
office <folder> --new "<Name>" --anywhere allow it although agents cannot see it
Inside a pane, R rebuilds everything and r re-reads Paperclip. After a reboot
TUIOS restores a session with a bare shell in each pane and keeps the pane
names, so run board once: it now checks what each pane is really running and
replaces any pane that is only a prompt.
board create any missing pane in this project's session
board --folder <folder> the same, for another project
board --replace close this project's panes, then build all eight again
board --reset close every pane in the session, then build
project-reset <folder> clear the panes and the worktrees, keep the history
project-delete <folder> remove the sessions, the Paperclip project, .git and the dot files
(says what would go; add --yes to do it)
merge <task> rebase, check, review and merge one task (asks first)
review <task> [--post] run ocr over one task's diff, and record the verdict
triage <task> [--ask next|failure] ask Mouse-1 one question about a task
STARTING OVER
office reset <folder> clear the panes and the worktrees; keeps the
folder, .harness, the git history and every
Paperclip record
office reset <folder> --dry-run say what would go
office reset <folder> --yes do it
office reset <folder> --yes --delete-folder remove the folder too
MACHINES (name, or the address ssh reaches)
nixos-desktop .13 192.168.20.13 hosts Paperclip, the daemon, the main session
sam-4screen-desktop .27 192.168.20.27 your working desktop
sam-thinkpad .51 192.168.20.51 the laptop
EVERY MACHINE, FROM THE TERMINAL
On .13, a folder that is on .13:
office ~/paseo/projects/test-alpha --project "test-alpha"
On .27, adopting a folder that lives on .13:
office /home/sam/paseo/projects/email_agent --project "Email Agent" --host nixos-desktop
On .27, a folder that is on .27 (stays local, no --host):
office ~/src/thing --new "thing"
On .51, the same folder on .51:
office ~/src/thing --project "thing" --host sam-thinkpad
Then attach. Nothing may move your client from inside a pane, so the last step is yours:
tuios attach <session> on this machine
tuios attach --host nixos-desktop <session> to a session on another machine
office delete <folder> remove the TUIOS sessions, every Paperclip
project that claims the folder, the registry
lines, and .git / .gitignore / .harness /
.paperclip / .pi. Keeps ordinary files.
office delete <folder> --yes do it
office delete <folder> --yes --delete-folder remove the folder as well
WORKTREES, AND WHY GIT MATTERS
A new project gets a worktree policy: Paperclip makes a branch and a separate
@@ -445,52 +440,75 @@ WORKTREES, AND WHY GIT MATTERS
When the folder cannot do that yet, office asks, and never writes history:
Y make the repository, write .gitignore containing .paperclip/, stage
everything, and stop. Commit in lazygit, then run this again.
Y make the repository, write .gitignore containing .paperclip/, and stop.
Nothing is staged. Ignore first, then stage, then commit, in lazygit.
N create the project in place (local_path): a task edits the folder
directly, there is no diff and nothing to merge.
Y leaves you in lazygit's hands on purpose: what you commit is what the agent
will see, and .gitignore is how you keep a file out of its view.
--commit-all does the whole thing in one go, for anyone who does not care.
Y opens a session with lazygit already in the folder, on the machine that owns
it, and tells you how to attach. Ignore with i, stage all with a, commit with c.
Then run the same office command again and it creates the project.
The base branch is read from the folder, not assumed to be main.
.paperclip/ is written into .gitignore every time and is not optional: it is
where Paperclip puts the worktrees, inside the project folder.
Ignoring a file does NOT untrack one that is already committed: office warns
and gives you the git rm --cached line when it finds that.
WHERE THINGS RUN
Panes run on the machine that owns the folder. Agents run on the machine that
serves Paperclip, because Paperclip starts them itself. If those differ, the
panes work and the agents cannot see the files, so --new refuses unless
panes work and the agents cannot see the files, so office --new refuses unless
--anywhere is given.
ON ANOTHER MACHINE
--host copies this folder of commands to the other machine, at
~/.local/share/paperclip-harness/bin/
and leaves a short ~/.local/bin/office there, so it can be run from that
machine too. Nothing has to be installed by hand. On .27 and .51 ~/.local/bin
is not on PATH yet, so there you call it by its full path:
~/.local/share/paperclip-harness/bin/office <folder>
MACHINES (name, or the address ssh reaches)
nixos-desktop .13 192.168.20.13 hosts Paperclip, the daemon, the main session
sam-4screen-desktop .27 192.168.20.27 your working desktop
sam-thinkpad .51 192.168.20.51 the laptop
WHEN THE FILES ARE ON ANOTHER MACHINE
The git work has to happen where the files are, because that is where the
commit, the worktree and the agent are. So when a folder cannot hold a
worktree yet, office makes a session on THAT machine with lazygit already
open in the folder, and tells you how to attach to it. No ssh, no cd.
FROM A TERMINAL, ON ANY MACHINE
a folder on .13, typed on .13:
office ~/paseo/projects/test-alpha --project "test-alpha"
a folder on .13, typed on .27:
office /home/sam/TEST-BETA --new "TEST-BETA" --host nixos-desktop
a folder on .27, typed on .27:
office ~/src/thing --new "thing"
then attach; nothing may move your client from inside a pane:
tuios attach <session>
tuios attach --host nixos-desktop <session>
AFTER A REBOOT
TUIOS restores a session with a fresh shell in every pane and keeps the pane
names. Run board once for that project: it checks what each pane is actually
running and replaces any pane that is only a prompt.
--host copies these commands to the other machine at
~/.local/share/paperclip-harness/bin/ and leaves ~/.local/bin/office there, so
a machine that has never seen this repository still works.
MERGE AND REVIEW
office merge <task> rebase onto the base, run the project's check, run ocr,
merge, re-run the check, roll back if it fails. It asks
for confirmation before touching anything.
office review <task> ocr over one task's diff; --post records the verdict
office triage <task> one four-option Mouse-1 question, posted as a comment
KEYS INSIDE THE PANES
( ) change tab tab change panel enter open / choose
r re-read Paperclip R rebuild all panes ? list the keys
t open lazygit in the project folder (stage, ignore, commit)
t tools (lazygit, yazi, nvim, hunk, fzf, ocr, shell)
g lazygit, straight away, in the project folder
h j k l z n and digits belong to TUIOS and never reach the program.
THE PROJECT PANE
overview · git · facts · costs · settings · projects
The git tab reads the folder and Paperclip together: repository, branch,
commits, remote, what is staged, what .gitignore holds, the worktree policy,
and Paperclip's own scan of every task worktree (dirty, ahead, behind, merged).
WHILE A FOLDER CANNOT HOLD A WORKTREE
Every pane carries one line at the top, naming what is wrong and where to look.
It is read when the pane starts; fix the folder and press R to clear it.
MORE
docs/STARTUP.md these commands, the rule, and the three rebuild levels
docs/WORKSPACES.md what each of the eight panes is for
docs/CLEAN-TESTS.md the two end-to-end tests and what they measured
docs/STARTUP.md these commands, the rule, the rebuild levels
docs/WORKSPACES.md what each of the eight panes is for
docs/RUNS-AND-MERGE.md how a task becomes merged work
docs/CLEAN-TESTS.md the two end-to-end tests and what they measured
"""
@@ -680,6 +698,38 @@ def note_ignore(folder, opts):
h.say(" take Paperclip's task worktrees with it. Add it before you commit.")
# The commands that have their own script next to this one. `office board` and
# `board` must do the same thing: a person should only have to remember office.
SIBLINGS = {
"board": "board",
"pages": "board",
"build": "board",
"reset": "project-reset",
"project-reset": "project-reset",
"delete": "project-delete",
"project-delete": "project-delete",
"forget": "project-delete",
"merge": "merge",
"review": "review",
"triage": "triage",
}
def forward(argv):
"""Run a sibling command with the rest of the arguments, or return None."""
if not argv:
return None
name = SIBLINGS.get(argv[0])
if name is None:
return None
script = os.path.join(HERE, name)
if not os.path.isfile(script):
raise h.Refused(2, "no %s next to office; the repository looks incomplete" % name)
# exec, not a subprocess: this program is a router for this one call and has
# nothing left to do, so the sibling should be the process the person sees.
os.execv(sys.executable, [sys.executable, script] + argv[1:])
def main():
if os.geteuid() == 0:
h.say("office: run this as your own user, not with sudo.")
@@ -687,6 +737,10 @@ def main():
h.say(" neither of those exists for root, and files it wrote would belong to root.")
return 2
argv = sys.argv[1:]
if argv and argv[0] in ("help", "-h", "--help"):
h.say(USAGE.strip())
return 0
forward(argv)
if not argv or "-h" in argv or "--help" in argv:
h.say(USAGE.strip())
return 0