office: say exactly what to do next; l opens lazygit and t opens the tool chooser everywhere; a remote run gets a terminal to ask in

This commit is contained in:
Sam
2026-11-13 07:21:23 +11:00
parent 4be72dae2e
commit cba45f82f8
12 changed files with 779 additions and 54 deletions

Binary file not shown.

View File

@@ -273,9 +273,9 @@ def run_on_remote(host, folder, opts):
for flag, value in (("--project", opts["project"]), ("--session", opts["session"]), ("--new", opts["new"])):
if value:
inner += [flag, value]
guard_machine(folder, opts)
ready = prepare_git(folder, opts)
for flag in ("--git-init", "--commit-all", "--anywhere"):
if opts.get(flag[2:].replace("-", "_")):
inner.append(flag)
if opts["dry"]:
inner.append("--dry")
# "$HOME" must stay unquoted so the other machine's shell expands it; every
@@ -283,9 +283,17 @@ def run_on_remote(host, folder, opts):
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)
sys.stdout.write(out.stdout)
# A question is asked on the other machine when the folder cannot hold a
# worktree, so give it a terminal when this one has one: without it, ssh
# hands it a pipe and it has to refuse instead of asking.
ssh_args = ["ssh", "-o", "StrictHostKeyChecking=accept-new"]
interactive = sys.stdin.isatty()
if interactive:
ssh_args.append("-t")
out = subprocess.run(ssh_args + [addr, cmd], timeout=600) if interactive else \
subprocess.run(ssh_args + [addr, cmd], capture_output=True, text=True, timeout=600)
if not interactive:
sys.stdout.write(out.stdout)
if out.returncode != 0:
raise h.Refused(3, "the other machine refused: %s" % (out.stderr or out.stdout).strip()[:300])
h.say("")
@@ -445,6 +453,20 @@ def guard_machine(folder, opts):
h.say(("warning: " if i == 0 else " ") + line)
def repeat_command(folder, opts):
"""The same command again, spelled out, with the arguments that were used."""
parts = ["office", folder]
if opts.get("new"):
parts += ["--new", '"%s"' % opts["new"]]
elif opts.get("project"):
parts += ["--project", '"%s"' % opts["project"]]
if opts.get("session"):
parts += ["--session", opts["session"]]
if opts.get("host"):
parts += ["--host", opts["host"]]
return " ".join(parts)
def prepare_git(folder, opts):
"""Get the folder as far as it can go toward a worktree per task.
@@ -483,9 +505,17 @@ def prepare_git(folder, opts):
h.say(" git add -A && git commit -m 'Initial commit'")
h.say("or pass --commit-all to let this command do all of it.")
raise h.Refused(2, "no repository and no terminal to ask in")
h.say(" Y make the repository, write .gitignore with .paperclip/,")
h.say(" stage everything, and stop. You commit it in lazygit, then")
h.say(" run this command again to create the project with worktrees.")
h.say(" Y make the repository now, then three steps:")
h.say("")
h.say(" 1. PUT INTO .gitignore EVERY FILE YOU DO NOT WANT COMMITTED.")
h.say(" A file there is invisible to the agent, which is usually what")
h.say(" you want for evidence folders, notes with credentials, and")
h.say(" .paperclip/, which is written there for you.")
h.say(" 2. commit what is left, in lazygit: stage with space, commit with c.")
h.say(" 3. run this same command again to create the project with worktrees:")
h.say("")
h.say(" %s" % repeat_command(folder, opts))
h.say("")
h.say(" N create the project in place instead: no branches, no diff,")
h.say(" nothing to merge.")
h.say("")
@@ -514,14 +544,18 @@ def prepare_git(folder, opts):
return "ready"
h.say("")
h.say(" Everything is staged and nothing is committed. Check what git is about")
h.say(" to take, and commit it:")
h.say(" Everything is staged and nothing is committed. What you commit is what")
h.say(" the agent will be able to see, so check it first:")
h.say("")
h.say(" cd %s && lazygit" % folder)
h.say(" space stage or unstage a file i ignore a file or folder")
h.say(" c commit ? every key")
h.say("")
h.say(" Then run this command again. It will create the project with worktrees.")
h.say(" i put the selected file or folder into .gitignore")
h.say(" space stage or unstage e edit a file, .gitignore included")
h.say(" c commit ? every key")
h.say("")
h.say(" Then repeat this command to create the project with worktrees:")
h.say("")
h.say(" %s" % repeat_command(folder, opts))
return "staged"
@@ -586,6 +620,8 @@ def main():
else:
return run_on_remote(opts["remote"], folder, opts)
# Only now: a folder on another machine has already been handed over, and
# the machine that owns it does these two checks itself.
guard_machine(folder, opts)
ready = prepare_git(folder, opts)
@@ -646,9 +682,6 @@ def main():
"watching": watching,
}
guard_machine(folder, opts)
ready = prepare_git(folder, opts)
if opts["dry"]:
h.say("would write %s" % pj.path(folder))
h.say("would build pages: shell, tree, reply, log, new")