office: full --help, and --host copies the commands to the other machine and works from any folder on it

This commit is contained in:
Sam
2026-11-12 16:26:34 +11:00
parent 6c6bacc647
commit 3043e9dec1
7 changed files with 757 additions and 10 deletions

View File

@@ -169,6 +169,9 @@ exit 0
# Where the commands live on a machine that is driven over ssh.
REMOTE_BIN = "~/.local/share/paperclip-harness/bin"
HOSTS = {
"nixos-desktop": "192.168.20.13", ".13": "192.168.20.13", "13": "192.168.20.13",
"sam-4screen-desktop": "192.168.20.27", "sam-4screen-desktop-1": "192.168.20.27",
@@ -204,6 +207,16 @@ def is_this_machine(name):
return addr in local_addresses()
def install_wrapper(addr, remote_bin):
"""Leave a short `office` on the other machine's PATH, pointing at the copy."""
script = ("#!/bin/sh\n"
"# Written by office on another machine. Runs the copy it keeps here.\n"
'exec "$HOME/.local/share/paperclip-harness/bin/office" "$@"\n')
subprocess.run(["ssh", "-o", "BatchMode=yes", addr,
"mkdir -p \"$HOME/.local/bin\" && cat > \"$HOME/.local/bin/office\" && chmod +x \"$HOME/.local/bin/office\""],
input=script, text=True, capture_output=True, timeout=60)
def run_on_remote(host, folder, opts):
"""Adopt a folder that lives on another machine, from where you are sitting.
@@ -218,14 +231,34 @@ def run_on_remote(host, folder, opts):
if probe.returncode != 0:
raise h.Refused(3, "cannot log in to %s (%s). Set up a key first: ssh-copy-id %s"
% (host, addr, addr))
# The other machine needs the commands. They are copied to one fixed place
# there rather than assumed to be in the same folder: a machine that has
# never seen this repository still gets a working `office`. Copying every
# time is what keeps the two in step, and it is one small folder.
here = os.path.dirname(os.path.realpath(__file__))
inner = [os.path.join(here, "office"), folder, "--no-switch"]
remote_bin = REMOTE_BIN
# rsync will not make two levels of directory, so make the parent first.
subprocess.run(["ssh", "-o", "BatchMode=yes", addr,
"mkdir -p \"$HOME/.local/share/paperclip-harness\""],
capture_output=True, text=True, timeout=60)
copy = subprocess.run(
["rsync", "-a", "--delete", "--exclude", "__pycache__", "--exclude", "*.pyc",
here + "/", "%s:%s/" % (addr, remote_bin)],
capture_output=True, text=True, timeout=300)
if copy.returncode != 0:
raise h.Refused(3, "could not copy the commands to %s: %s"
% (host, (copy.stderr or copy.stdout).strip()[:300]))
install_wrapper(addr, remote_bin)
inner = [folder, "--no-switch"]
for flag, value in (("--project", opts["project"]), ("--session", opts["session"]), ("--new", opts["new"])):
if value:
inner += [flag, value]
if opts["dry"]:
inner.append("--dry")
cmd = " ".join(shlex.quote(a) for a in inner)
# "$HOME" must stay unquoted so the other machine's shell expands it; every
# argument after it is quoted, so a folder with a space still arrives whole.
cmd = 'exec "$HOME/.local/share/paperclip-harness/bin/office" ' + \
" ".join(shlex.quote(a) for a in inner)
h.say("on %s: %s" % (host, cmd))
out = subprocess.run(["ssh", "-o", "StrictHostKeyChecking=accept-new", addr, cmd],
capture_output=True, text=True, timeout=600)
@@ -237,15 +270,85 @@ def run_on_remote(host, folder, opts):
return 0
USAGE = """office — open a folder as a project
USAGE = """office — open a folder as a project, and say what else can be run
office <folder> --project "<Name>" adopt a project Paperclip already has
office <folder> --new "<Name>" create the Paperclip project too
office <folder> --project "<Name>" --host nixos-desktop the folder is on that machine
office <folder> --dry say what would happen, write nothing
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.
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>.
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
COMMANDS
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
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> forget the session and start this project again
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
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
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>
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.
KEYS INSIDE THE PANES
( ) change tab tab change panel enter open / choose
r re-read Paperclip R rebuild all panes ? list the keys
h j k l z n and digits belong to TUIOS and never reach the program.
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
"""
@@ -260,7 +363,11 @@ def main():
# are standing in. Only a missing argument is an error.
raise h.Refused(2, "give the folder: office <folder> --project \"<Name>\" (office --help)")
folder = os.path.abspath(os.path.expanduser(opts["folder"]))
if not os.path.isdir(folder):
# The folder is only checked here when it is this machine's folder. A folder
# on another machine is checked there: asking this machine about it says "no
# folder", which is true and useless.
wanted = opts["host"] or opts["remote"]
if not (wanted and not is_this_machine(wanted)) and not os.path.isdir(folder):
raise h.Refused(2, "no folder at %s" % folder)
host = opts["host"] or pj.this_machine()