diff --git a/README.md b/README.md
index ccb727c..f8bd885 100644
--- a/README.md
+++ b/README.md
@@ -443,11 +443,15 @@ Useful commands:
npm run sync-tmux
npm run sync-tmux -- --reload
npm run sync-tmux -- --bootstrap --reload
+npm run restore-tmux
```
- `sync-tmux` copies this checkout into `~/.tmux/plugins/coding-agents-tmux`
- `--reload` runs `tmux source-file ~/.tmux.conf` after syncing
- `--bootstrap` reinstalls production dependencies in the synced plugin copy when `package.json` changed
+- `restore-tmux` removes the synced development files, preserves `node_modules`, and returns the TPM checkout to a clean state so `prefix + U` can update it normally
+
+Run `npm run restore-tmux` when you finish local development or before asking TPM to update all plugins.
## CLI
diff --git a/package.json b/package.json
index 51bff6a..7829b92 100644
--- a/package.json
+++ b/package.json
@@ -29,6 +29,7 @@
"check": "./bin/coding-agents-tmux --help",
"typecheck": "tsc --project tsconfig.json",
"sync-tmux": "./scripts/sync-tmux-plugin.sh",
+ "restore-tmux": "./scripts/sync-tmux-plugin.sh --restore",
"fmt": "oxfmt .",
"fmt:check": "oxfmt --check ."
},
diff --git a/scripts/sync-tmux-plugin.sh b/scripts/sync-tmux-plugin.sh
index fee3923..bd357b7 100755
--- a/scripts/sync-tmux-plugin.sh
+++ b/scripts/sync-tmux-plugin.sh
@@ -7,9 +7,10 @@ TARGET_DIR="${CODING_AGENTS_TMUX_PLUGIN_DIR:-$HOME/.tmux/plugins/coding-agents-t
TMUX_CONF="${CODING_AGENTS_TMUX_TMUX_CONF:-$HOME/.tmux.conf}"
RELOAD=0
BOOTSTRAP=0
+RESTORE=0
usage() {
- printf 'Usage: %s [--target
] [--reload] [--bootstrap]\n' "${0##*/}"
+ printf 'Usage: %s [--target ] [--reload] [--bootstrap] [--restore]\n' "${0##*/}"
printf '\n'
printf 'Sync this checkout into the tmux plugin install used by TPM.\n'
printf '\n'
@@ -17,6 +18,7 @@ usage() {
printf ' --target Override target plugin directory\n'
printf ' --reload Reload tmux after syncing\n'
printf ' --bootstrap Reinstall production npm deps in the target dir\n'
+ printf ' --restore Discard synced files and restore the TPM checkout\n'
printf ' -h, --help Show this help\n'
}
@@ -76,6 +78,9 @@ while [ "$#" -gt 0 ]; do
--bootstrap)
BOOTSTRAP=1
;;
+ --restore)
+ RESTORE=1
+ ;;
-h | --help)
usage
exit 0
@@ -89,6 +94,22 @@ while [ "$#" -gt 0 ]; do
shift
done
+if [ "$RESTORE" -eq 1 ]; then
+ if [ ! -d "$TARGET_DIR/.git" ]; then
+ printf 'coding-agents-tmux: cannot restore non-git target: %s\n' "$TARGET_DIR" >&2
+ exit 1
+ fi
+
+ git -C "$TARGET_DIR" reset --hard HEAD
+ git -C "$TARGET_DIR" clean -fd -e node_modules/
+ printf 'Restored clean TPM checkout at %s\n' "$TARGET_DIR"
+
+ if [ "$RELOAD" -eq 1 ]; then
+ reload_tmux
+ fi
+ exit 0
+fi
+
if ! command -v rsync >/dev/null 2>&1; then
printf 'coding-agents-tmux: rsync is required to sync the plugin checkout\n' >&2
exit 1
diff --git a/test/sync-tmux-plugin.test.ts b/test/sync-tmux-plugin.test.ts
new file mode 100644
index 0000000..a47c480
--- /dev/null
+++ b/test/sync-tmux-plugin.test.ts
@@ -0,0 +1,44 @@
+import assert from "node:assert/strict";
+import { execFileSync } from "node:child_process";
+import { mkdtempSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
+import { tmpdir } from "node:os";
+import { join } from "node:path";
+import test from "node:test";
+
+const repoRoot = join(import.meta.dirname, "..");
+const syncScript = join(repoRoot, "scripts", "sync-tmux-plugin.sh");
+
+function git(cwd: string, ...args: string[]) {
+ execFileSync("git", args, { cwd, stdio: "pipe" });
+}
+
+test("--restore returns a synced TPM checkout to a clean install", () => {
+ const tempRoot = mkdtempSync(join(tmpdir(), "coding-agents-tmux-sync-"));
+ const target = join(tempRoot, "plugin");
+
+ mkdirSync(join(target, "node_modules"), { recursive: true });
+ writeFileSync(join(target, "README.md"), "installed version\n");
+ writeFileSync(join(target, ".gitignore"), "node_modules/\n");
+ writeFileSync(join(target, "node_modules", "keep.txt"), "keep\n");
+
+ git(target, "init", "-q");
+ git(target, "config", "user.name", "Test User");
+ git(target, "config", "user.email", "test@example.com");
+ git(target, "add", "README.md", ".gitignore");
+ git(target, "commit", "-qm", "installed plugin");
+
+ execFileSync(syncScript, ["--target", target], { cwd: repoRoot, stdio: "pipe" });
+ assert.notEqual(readFileSync(join(target, "README.md"), "utf8"), "installed version\n");
+
+ execFileSync(syncScript, ["--target", target, "--restore"], {
+ cwd: repoRoot,
+ stdio: "pipe",
+ });
+
+ assert.equal(readFileSync(join(target, "README.md"), "utf8"), "installed version\n");
+ assert.equal(readFileSync(join(target, "node_modules", "keep.txt"), "utf8"), "keep\n");
+ assert.equal(
+ execFileSync("git", ["status", "--porcelain"], { cwd: target, encoding: "utf8" }),
+ "",
+ );
+});