|
|
|
|
@@ -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
|
|
|
|
|
|