pi-sync-cli 0.5.0__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -60,3 +60,66 @@ uv run pi-sync --help
60
60
  `.backup` is kept, but don't be cavalier about it.
61
61
  - `~/.ssh/config` is protected: read it through code that extracts host aliases,
62
62
  and don't dump its contents into output or a commit.
63
+
64
+ Kept out of the README: these describe *using* pi-sync rather than changing
65
+ it, and are collected here so the README stays a short front page.
66
+
67
+ ## Hosts without pi
68
+
69
+ Each host is checked before syncing. If pi is missing, pi-sync offers to install
70
+ it — interactively, pi's installer takes over your terminal, and the sync
71
+ continues when it exits. `--install` does that unattended.
72
+
73
+ A host that still has no pi afterwards is skipped, and the run exits non-zero.
74
+
75
+ ## Updating
76
+
77
+ - `pi-sync update` upgrades pi-sync itself, through whichever installer owns it
78
+ (`--check` reports without changing anything).
79
+ - `pi-sync --update-pi <hosts>` updates pi on the hosts before syncing.
80
+
81
+ ## Uninstalling pi from a host
82
+
83
+ `pi-sync --uninstall <hosts>` removes pi and leaves your config in place. If it
84
+ fails, run `curl -fsSL https://pi.dev/install.sh | sh` on the host and choose
85
+ `u` instead.
86
+
87
+ ## Shell completions
88
+
89
+ Host arguments complete from `~/.ssh/config`. zsh and fish also show where each
90
+ alias points:
91
+
92
+ ```console
93
+ $ pi-sync t<TAB>
94
+ tinfoil tinfoil@tinfoil.sayan.page
95
+ tinfoil-proxy notdebian@100.98.241.11
96
+ ```
97
+
98
+ ```bash
99
+ # bash
100
+ _PI_SYNC_COMPLETE=bash_source pi-sync > ~/.pi-sync-complete.bash
101
+ echo 'source ~/.pi-sync-complete.bash' >> ~/.bashrc
102
+
103
+ # zsh
104
+ _PI_SYNC_COMPLETE=zsh_source pi-sync > ~/.pi-sync-complete.zsh
105
+ echo 'source ~/.pi-sync-complete.zsh' >> ~/.zshrc
106
+
107
+ # fish (config.fish)
108
+ _PI_SYNC_COMPLETE=fish_source pi-sync | source
109
+ ```
110
+
111
+ PowerShell uses `powershell_source` the same way.
112
+
113
+ ## Caveats
114
+
115
+ - `settings.json` is written by pi itself (`lastChangelogVersion` bumps, UI
116
+ toggles), so pushing it overwrites the host's local preferences — the previous
117
+ copy is kept as `settings.json.backup`. Sync it when you change `packages`.
118
+ - Extensions that keep runtime files in their own directory (logs, checkpoints)
119
+ get those files synced too, and whichever side pushes last wins. Exclude them
120
+ with `-x '*/logs/*'`.
121
+ - Extension versions are whatever each host has installed; pin them in
122
+ `settings.json` (`npm:pi-lens@1.2.3`) if you need hosts identical.
123
+ - `--auth` copies API keys in the clear. Prefer `OPENCODE_API_KEY` (and friends)
124
+ in the environment where you can.
125
+ - `--delete` disables backups for the mirrored directory.
@@ -25,6 +25,53 @@ Three design anchors worth understanding before editing:
25
25
  An older install can coexist under the pre-rename name `pi-sync`, and a
26
26
  hardcoded name would upgrade the wrong virtualenv — or nothing.
27
27
 
28
+ ## How it works
29
+
30
+ **One probe per host.** `probe_host` pipes a POSIX sh script through
31
+ `ssh host sh -s`, so reachability and pi's location cost a single round trip. It
32
+ checks `command -v pi` and then the usual install locations (`~/.local/bin`,
33
+ `~/.pi/bin`, `~/.pi/agent/bin`, linuxbrew, homebrew, `/usr/local/bin`) because a
34
+ non-interactive ssh session does not source the host's shell init — on a
35
+ linuxbrew host `command -v pi` alone finds nothing.
36
+
37
+ **Installing pi.** Interactively the terminal is handed to pi's own installer and
38
+ the sync resumes when it exits. Unattended (`--install` with no tty) the output is
39
+ captured and stdin is closed, so a prompt fails fast instead of hanging. The
40
+ installer's exit status is not trusted — its "do nothing" menu choice exits 0 —
41
+ so `install_pi` re-probes and reports honestly. A host that still lacks pi is
42
+ skipped, which makes the run exit non-zero. After a successful install,
43
+ `mkdir -p` creates the agent directory, because rsync will not create
44
+ intermediate directories itself.
45
+
46
+ **Uninstalling.** The official installer can only uninstall through its
47
+ interactive menu (its unattended mode always installs or reinstalls), so pi-sync
48
+ issues the npm command that menu would have: `npm uninstall -g --prefix <prefix>
49
+ @earendil-works/pi-coding-agent`, with the prefix derived from where pi actually
50
+ lives. It re-probes afterwards, since npm can exit 0 having removed nothing. Only
51
+ the CLI goes; the agent directory is never touched.
52
+
53
+ **Self-update.** `update` classifies the running environment and delegates:
54
+
55
+ | Detected | Action |
56
+ | --- | --- |
57
+ | pipx | `pipx upgrade <dist>` |
58
+ | uv tool | `uv tool upgrade <dist>` |
59
+ | editable checkout | prints `git -C <path> pull` |
60
+ | uvx ephemeral | nothing to do; suggests a durable install |
61
+ | otherwise | `python -m pip install --upgrade <dist>` |
62
+
63
+ It resolves the distribution from its own environment instead of hardcoding a
64
+ name, and refuses to downgrade when the local version is ahead of PyPI.
65
+
66
+ **Completions.** Read from `~/.ssh/config` at completion time, following
67
+ `Include` and skipping wildcard entries; `CompletionItem.help` carries
68
+ `user@hostname` for the shells that render it (bash cannot).
69
+
70
+ **rsync invocation.** `-az -i`, plus `--backup --suffix=.backup` and
71
+ `--exclude=*.backup` so replaced files are kept but backups never travel, and
72
+ `--delete-during` only for `extensions/` when `--delete` is given — which also
73
+ suppresses backups for that directory.
74
+
28
75
  ## Commands
29
76
 
30
77
  ```bash
@@ -0,0 +1,89 @@
1
+ Metadata-Version: 2.5
2
+ Name: pi-sync-cli
3
+ Version: 0.5.1
4
+ Summary: Sync pi agent config and extensions between hosts over rsync
5
+ Project-URL: Repository, https://github.com/say4n/pi-sync
6
+ Project-URL: Issues, https://github.com/say4n/pi-sync/issues
7
+ Author: Sayan Goswami
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: agent-config,dotfiles,pi,rsync,sync
11
+ Classifier: Environment :: Console
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Topic :: System :: Archiving :: Mirroring
14
+ Requires-Python: >=3.10
15
+ Requires-Dist: click>=8.1
16
+ Description-Content-Type: text/markdown
17
+
18
+ # pi-sync
19
+
20
+ pi-sync keeps your pi agent config the same across hosts. It copies the
21
+ declarative parts of `~/.pi/agent` to another machine over rsync, and it reads
22
+ your ssh config, so the aliases in `~/.ssh/config` work as host names.
23
+
24
+ ```bash
25
+ pi-sync tinfoil # copy config and extensions to tinfoil
26
+ pi-sync --config laptop # copy only models.json and settings.json
27
+ pi-sync --pull --all tinfoil # copy tinfoil's config back to this machine
28
+ pi-sync --dry-run --all a b # report what would change, without copying
29
+ pi-sync update # update pi-sync itself
30
+ ```
31
+
32
+ ## install
33
+
34
+ ```bash
35
+ pipx install pi-sync-cli
36
+ ```
37
+
38
+ The PyPI package is `pi-sync-cli`, and it installs the `pi-sync` command.
39
+ `uv tool install pi-sync-cli` works the same way.
40
+
41
+ To install from source, run:
42
+
43
+ ```bash
44
+ pipx install git+ssh://git@github.com/say4n/pi-sync
45
+ ```
46
+
47
+ pi-sync requires Python 3.10 or later.
48
+
49
+ ## what syncs
50
+
51
+ pi-sync copies the declarative parts of the agent directory, and nothing else.
52
+
53
+ | group | what it copies |
54
+ | --- | --- |
55
+ | `--config` | `models.json` and `settings.json` |
56
+ | `--extensions` | the `extensions/` directory |
57
+ | `--auth` | `auth.json`, which holds API keys. This group is opt-in, and pi-sync warns you before it copies secrets. |
58
+
59
+ `--all` copies `--config` and `--extensions`. It is the default when you pass no
60
+ group flag.
61
+
62
+ pi-sync leaves host-local state alone: `sessions/`, `npm/`, `models-store.json`,
63
+ `ayu/`, `bin/`, and `trust.json` stay where they are.
64
+
65
+ ## flags
66
+
67
+ | flag | effect |
68
+ | --- | --- |
69
+ | `--all`, `--config`, `--extensions`, `--auth` | set what pi-sync copies |
70
+ | `--pull` | copy from the host to this machine instead of the other way |
71
+ | `--delete` | make `extensions/` match the source exactly, including deletions |
72
+ | `--dry-run` | report changes without copying anything |
73
+ | `-x, --exclude PATTERN` | skip files that match the pattern. Repeat the flag to add more patterns. |
74
+ | `--install` | install pi on hosts that do not have it, without prompting |
75
+ | `--uninstall` | remove pi from the host instead of copying config. Your config stays. |
76
+ | `--update-pi` | update pi on each host before copying config |
77
+ | `--local-dir` | set the local agent directory. The default is `$PI_CODING_AGENT_DIR`, or `~/.pi/agent`. |
78
+ | `--remote-dir` | set the agent directory on the host. The default is `~/.pi/agent`. |
79
+ | `-v, --verbose` | print each rsync command and its output |
80
+
81
+ To copy to more than one host, pass more than one name: `pi-sync a b c`. The
82
+ command exits with a non-zero status when it cannot copy to a host. Each file
83
+ pi-sync overwrites on the destination is kept beside the new one as
84
+ `<name>.backup`.
85
+
86
+ ## developing
87
+
88
+ For the layout, the tests, and the release process, see
89
+ [DEVELOPMENT.md](DEVELOPMENT.md).
@@ -0,0 +1,72 @@
1
+ # pi-sync
2
+
3
+ pi-sync keeps your pi agent config the same across hosts. It copies the
4
+ declarative parts of `~/.pi/agent` to another machine over rsync, and it reads
5
+ your ssh config, so the aliases in `~/.ssh/config` work as host names.
6
+
7
+ ```bash
8
+ pi-sync tinfoil # copy config and extensions to tinfoil
9
+ pi-sync --config laptop # copy only models.json and settings.json
10
+ pi-sync --pull --all tinfoil # copy tinfoil's config back to this machine
11
+ pi-sync --dry-run --all a b # report what would change, without copying
12
+ pi-sync update # update pi-sync itself
13
+ ```
14
+
15
+ ## install
16
+
17
+ ```bash
18
+ pipx install pi-sync-cli
19
+ ```
20
+
21
+ The PyPI package is `pi-sync-cli`, and it installs the `pi-sync` command.
22
+ `uv tool install pi-sync-cli` works the same way.
23
+
24
+ To install from source, run:
25
+
26
+ ```bash
27
+ pipx install git+ssh://git@github.com/say4n/pi-sync
28
+ ```
29
+
30
+ pi-sync requires Python 3.10 or later.
31
+
32
+ ## what syncs
33
+
34
+ pi-sync copies the declarative parts of the agent directory, and nothing else.
35
+
36
+ | group | what it copies |
37
+ | --- | --- |
38
+ | `--config` | `models.json` and `settings.json` |
39
+ | `--extensions` | the `extensions/` directory |
40
+ | `--auth` | `auth.json`, which holds API keys. This group is opt-in, and pi-sync warns you before it copies secrets. |
41
+
42
+ `--all` copies `--config` and `--extensions`. It is the default when you pass no
43
+ group flag.
44
+
45
+ pi-sync leaves host-local state alone: `sessions/`, `npm/`, `models-store.json`,
46
+ `ayu/`, `bin/`, and `trust.json` stay where they are.
47
+
48
+ ## flags
49
+
50
+ | flag | effect |
51
+ | --- | --- |
52
+ | `--all`, `--config`, `--extensions`, `--auth` | set what pi-sync copies |
53
+ | `--pull` | copy from the host to this machine instead of the other way |
54
+ | `--delete` | make `extensions/` match the source exactly, including deletions |
55
+ | `--dry-run` | report changes without copying anything |
56
+ | `-x, --exclude PATTERN` | skip files that match the pattern. Repeat the flag to add more patterns. |
57
+ | `--install` | install pi on hosts that do not have it, without prompting |
58
+ | `--uninstall` | remove pi from the host instead of copying config. Your config stays. |
59
+ | `--update-pi` | update pi on each host before copying config |
60
+ | `--local-dir` | set the local agent directory. The default is `$PI_CODING_AGENT_DIR`, or `~/.pi/agent`. |
61
+ | `--remote-dir` | set the agent directory on the host. The default is `~/.pi/agent`. |
62
+ | `-v, --verbose` | print each rsync command and its output |
63
+
64
+ To copy to more than one host, pass more than one name: `pi-sync a b c`. The
65
+ command exits with a non-zero status when it cannot copy to a host. Each file
66
+ pi-sync overwrites on the destination is kept beside the new one as
67
+ `<name>.backup`.
68
+
69
+ ## developing
70
+
71
+ For the layout, the tests, and the release process, see
72
+ [DEVELOPMENT.md](DEVELOPMENT.md).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "pi-sync-cli"
3
- version = "0.5.0"
3
+ version = "0.5.1"
4
4
  description = "Sync pi agent config and extensions between hosts over rsync"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -52,7 +52,7 @@ wheels = [
52
52
 
53
53
  [[package]]
54
54
  name = "pi-sync-cli"
55
- version = "0.5.0"
55
+ version = "0.5.1"
56
56
  source = { editable = "." }
57
57
  dependencies = [
58
58
  { name = "click" },
@@ -1,197 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: pi-sync-cli
3
- Version: 0.5.0
4
- Summary: Sync pi agent config and extensions between hosts over rsync
5
- Project-URL: Repository, https://github.com/say4n/pi-sync
6
- Project-URL: Issues, https://github.com/say4n/pi-sync/issues
7
- Author: Sayan Goswami
8
- License-Expression: MIT
9
- License-File: LICENSE
10
- Keywords: agent-config,dotfiles,pi,rsync,sync
11
- Classifier: Environment :: Console
12
- Classifier: Programming Language :: Python :: 3
13
- Classifier: Topic :: System :: Archiving :: Mirroring
14
- Requires-Python: >=3.10
15
- Requires-Dist: click>=8.1
16
- Description-Content-Type: text/markdown
17
-
18
- # pi-sync
19
-
20
- Sync pi agent config between hosts over rsync, using your existing ssh config
21
- for routing (so `~/.ssh/config` aliases just work).
22
-
23
- ```bash
24
- pi-sync tinfoil # push config + extensions
25
- pi-sync --config laptop # only models.json and settings.json
26
- pi-sync --pull --all tinfoil # fetch the host's config back
27
- pi-sync --dry-run --all a b # preview against two hosts
28
- pi-sync update # update pi-sync itself
29
- ```
30
-
31
- ## Install
32
-
33
- ```bash
34
- # the PyPI package is pi-sync-cli; it installs the `pi-sync` command
35
- pipx install pi-sync-cli
36
- pipx install git+ssh://git@github.com/say4n/pi-sync # from source (needs access)
37
- ```
38
-
39
- Requires Python 3.10+. `uv tool install` works in place of `pipx install`.
40
-
41
- ## What syncs
42
-
43
- Only the declarative parts of the agent dir:
44
-
45
- | Group | Files |
46
- | --- | --- |
47
- | `--config` | `models.json`, `settings.json` |
48
- | `--extensions` | `extensions/` |
49
- | `--auth` | `auth.json` — secrets, opt-in, warns on push |
50
-
51
- `--all` is `--config` + `--extensions` (also the default when no flag is given).
52
-
53
- Host-local state is deliberately never touched: `sessions/`, `npm/`,
54
- `models-store.json` (regenerated from the pi.dev catalog), `ayu/`, `bin/`,
55
- `trust.json`.
56
-
57
- ## Flags
58
-
59
- | Flag | Effect |
60
- | --- | --- |
61
- | `--all` / `--config` / `--extensions` / `--auth` | what to sync |
62
- | `--pull` | host → local instead of local → host |
63
- | `--delete` | mirror `extensions/` exactly (deletes extras on the destination) |
64
- | `--dry-run` | report changes, copy nothing |
65
- | `-x, --exclude PATTERN` | skip matching files (repeatable) |
66
- | `--install` | install pi on hosts that lack it, without prompting |
67
- | `--uninstall` | remove pi from the host instead of syncing (config is kept) |
68
- | `--update-pi` | update pi on each host before syncing |
69
- | `--local-dir` | default `$PI_CODING_AGENT_DIR` or `~/.pi/agent` |
70
- | `--remote-dir` | default `~/.pi/agent` |
71
- | `-v` / `--verbose` | print each rsync command and its output |
72
-
73
- Multiple hosts are accepted: `pi-sync a b c`. Exits non-zero if any host is
74
- unreachable or any transfer fails.
75
-
76
- Anything pi-sync overwrites on the destination is kept beside it as
77
- `<name>.backup`.
78
-
79
- ## Host preflight
80
-
81
- Each host gets one ssh probe that reports reachability and pi's location in the
82
- same round trip.
83
-
84
- If pi is missing, pi-sync hands the terminal to pi's own installer
85
- (`curl -fsSL https://pi.dev/install.sh | sh`), which keeps full control: its
86
- prompts (its install/uninstall/do-nothing menu, a Node.js install, a sudo
87
- password) work normally, and the sync continues once it exits. pi-sync does not
88
- add a confirmation of its own, because the installer already asks. Without a
89
- terminal it runs unattended under `--install`, stays quiet otherwise, and
90
- `--dry-run` never installs anything.
91
-
92
- The installer's exit status is not treated as proof: its "do nothing" choice
93
- exits 0, so the host is re-probed afterwards and reported honestly.
94
-
95
- **A host without pi is skipped** — copying into a host that has never run pi is
96
- not useful and usually fails anyway, since there is no agent directory to copy
97
- into. That covers an installer run that installed nothing, a failed install, and
98
- a non-interactive run without `--install`; a skipped host makes the run exit
99
- non-zero. After a successful install pi-sync creates the agent directory,
100
- because rsync will not create intermediate directories on its own.
101
-
102
- The probe checks `command -v pi` plus the usual install locations
103
- (`~/.local/bin`, `~/.pi/bin`, `~/.pi/agent/bin`, linuxbrew, homebrew,
104
- `/usr/local/bin`), because a non-interactive ssh session does not source the
105
- host's shell init — on a linuxbrew host `command -v pi` alone misses it.
106
-
107
- ## Updating
108
-
109
- `pi-sync update` upgrades this tool through whichever installer owns it —
110
- `pipx upgrade`, `uv tool upgrade`, or `pip install --upgrade` — and reports the
111
- version it moved from and to. `--check` reports without changing anything.
112
- Running from a source checkout it tells you to `git pull` instead, and from an
113
- ephemeral `uvx --from …` environment it explains that there is nothing to
114
- upgrade.
115
-
116
- `pi-sync --update-pi <hosts>` runs pi's own updater (`pi update --self`) on each
117
- host before syncing, so the fleet does not drift:
118
-
119
- ```console
120
- $ pi-sync --update-pi tinfoil
121
- → tinfoil
122
- pi 0.85.1 → 0.86.0
123
- models.json already in sync
124
- settings.json already in sync
125
- extensions already in sync
126
- ```
127
-
128
- `update` is a reserved word — a host with that alias is still reachable as
129
- `user@update`.
130
-
131
- ## Uninstalling
132
-
133
- `--uninstall` removes pi from the host instead of syncing. It runs
134
- `npm uninstall -g --prefix <prefix> @earendil-works/pi-coding-agent`, deriving the
135
- prefix from where pi actually lives (as the official installer does), then
136
- re-probes to confirm the binary is really gone — npm can exit 0 having removed
137
- nothing. `~/.pi/agent` is deliberately left untouched, since that is your config
138
- rather than the CLI.
139
-
140
- This exists because the official installer can only uninstall through its
141
- interactive menu: its unattended mode always installs or reinstalls. If the
142
- uninstall fails, that is usually a managed install
143
- (`PI_EXPERIMENTAL=1`, under `<agent dir>/install`) — run
144
- `curl -fsSL https://pi.dev/install.sh | sh` on the host and choose `u`.
145
-
146
- ## Shell completions
147
-
148
- Host arguments complete from `~/.ssh/config`, following `Include` directives and
149
- skipping wildcard entries. zsh and fish also show where each alias points:
150
-
151
- ```console
152
- $ pi-sync t<TAB>
153
- tinfoil tinfoil@tinfoil.sayan.page
154
- tinfoil-proxy notdebian@100.98.241.11
155
- ```
156
-
157
- ```bash
158
- # bash
159
- _PI_SYNC_COMPLETE=bash_source pi-sync > ~/.pi-sync-complete.bash
160
- echo 'source ~/.pi-sync-complete.bash' >> ~/.bashrc
161
-
162
- # zsh
163
- _PI_SYNC_COMPLETE=zsh_source pi-sync > ~/.pi-sync-complete.zsh
164
- echo 'source ~/.pi-sync-complete.zsh' >> ~/.zshrc
165
-
166
- # fish (config.fish)
167
- _PI_SYNC_COMPLETE=fish_source pi-sync | source
168
- ```
169
-
170
- Writing the script out (rather than `eval "$(_PI_SYNC_COMPLETE=bash_source pi-sync)"`
171
- on every shell start) keeps shell startup fast, since the eval form runs the
172
- binary each time. PowerShell works the same way via `powershell_source`.
173
-
174
- Completions are read from the config at completion time, so new hosts appear
175
- without regenerating anything.
176
-
177
- ## Caveats
178
-
179
- - `settings.json` is machine-written by pi (`lastChangelogVersion` bumps, UI
180
- toggles), so two hosts pushing it will overwrite each other's local
181
- preferences. Sync it when you change `packages`, not reflexively — and note the
182
- overwritten copy is kept as `settings.json.backup` on the receiving host.
183
- - `*.backup` files are never synced, so those copies stay host-local and never
184
- trampoline between hosts. `--delete` suppresses backups for the mirrored
185
- directory, because mirroring means "match exactly".
186
- - Extensions that write runtime files inside their own directory (logs,
187
- checkpoints) get those files synced too, and each host's copy is overwritten by
188
- whichever side pushed last — exclude them with `-x '*/logs/*'`.
189
- - Extension versions are whatever each host has installed; pin them in
190
- `settings.json` (`npm:pi-lens@1.2.3`) if you need hosts identical.
191
- - `--auth` copies API keys in the clear. Prefer `OPENCODE_API_KEY` (and friends)
192
- in the environment where you can.
193
- - Remote paths go through the host's shell, so `~` expands there as usual.
194
-
195
- ## Developing
196
-
197
- See [DEVELOPMENT.md](DEVELOPMENT.md) for the layout, tests and release process.
@@ -1,180 +0,0 @@
1
- # pi-sync
2
-
3
- Sync pi agent config between hosts over rsync, using your existing ssh config
4
- for routing (so `~/.ssh/config` aliases just work).
5
-
6
- ```bash
7
- pi-sync tinfoil # push config + extensions
8
- pi-sync --config laptop # only models.json and settings.json
9
- pi-sync --pull --all tinfoil # fetch the host's config back
10
- pi-sync --dry-run --all a b # preview against two hosts
11
- pi-sync update # update pi-sync itself
12
- ```
13
-
14
- ## Install
15
-
16
- ```bash
17
- # the PyPI package is pi-sync-cli; it installs the `pi-sync` command
18
- pipx install pi-sync-cli
19
- pipx install git+ssh://git@github.com/say4n/pi-sync # from source (needs access)
20
- ```
21
-
22
- Requires Python 3.10+. `uv tool install` works in place of `pipx install`.
23
-
24
- ## What syncs
25
-
26
- Only the declarative parts of the agent dir:
27
-
28
- | Group | Files |
29
- | --- | --- |
30
- | `--config` | `models.json`, `settings.json` |
31
- | `--extensions` | `extensions/` |
32
- | `--auth` | `auth.json` — secrets, opt-in, warns on push |
33
-
34
- `--all` is `--config` + `--extensions` (also the default when no flag is given).
35
-
36
- Host-local state is deliberately never touched: `sessions/`, `npm/`,
37
- `models-store.json` (regenerated from the pi.dev catalog), `ayu/`, `bin/`,
38
- `trust.json`.
39
-
40
- ## Flags
41
-
42
- | Flag | Effect |
43
- | --- | --- |
44
- | `--all` / `--config` / `--extensions` / `--auth` | what to sync |
45
- | `--pull` | host → local instead of local → host |
46
- | `--delete` | mirror `extensions/` exactly (deletes extras on the destination) |
47
- | `--dry-run` | report changes, copy nothing |
48
- | `-x, --exclude PATTERN` | skip matching files (repeatable) |
49
- | `--install` | install pi on hosts that lack it, without prompting |
50
- | `--uninstall` | remove pi from the host instead of syncing (config is kept) |
51
- | `--update-pi` | update pi on each host before syncing |
52
- | `--local-dir` | default `$PI_CODING_AGENT_DIR` or `~/.pi/agent` |
53
- | `--remote-dir` | default `~/.pi/agent` |
54
- | `-v` / `--verbose` | print each rsync command and its output |
55
-
56
- Multiple hosts are accepted: `pi-sync a b c`. Exits non-zero if any host is
57
- unreachable or any transfer fails.
58
-
59
- Anything pi-sync overwrites on the destination is kept beside it as
60
- `<name>.backup`.
61
-
62
- ## Host preflight
63
-
64
- Each host gets one ssh probe that reports reachability and pi's location in the
65
- same round trip.
66
-
67
- If pi is missing, pi-sync hands the terminal to pi's own installer
68
- (`curl -fsSL https://pi.dev/install.sh | sh`), which keeps full control: its
69
- prompts (its install/uninstall/do-nothing menu, a Node.js install, a sudo
70
- password) work normally, and the sync continues once it exits. pi-sync does not
71
- add a confirmation of its own, because the installer already asks. Without a
72
- terminal it runs unattended under `--install`, stays quiet otherwise, and
73
- `--dry-run` never installs anything.
74
-
75
- The installer's exit status is not treated as proof: its "do nothing" choice
76
- exits 0, so the host is re-probed afterwards and reported honestly.
77
-
78
- **A host without pi is skipped** — copying into a host that has never run pi is
79
- not useful and usually fails anyway, since there is no agent directory to copy
80
- into. That covers an installer run that installed nothing, a failed install, and
81
- a non-interactive run without `--install`; a skipped host makes the run exit
82
- non-zero. After a successful install pi-sync creates the agent directory,
83
- because rsync will not create intermediate directories on its own.
84
-
85
- The probe checks `command -v pi` plus the usual install locations
86
- (`~/.local/bin`, `~/.pi/bin`, `~/.pi/agent/bin`, linuxbrew, homebrew,
87
- `/usr/local/bin`), because a non-interactive ssh session does not source the
88
- host's shell init — on a linuxbrew host `command -v pi` alone misses it.
89
-
90
- ## Updating
91
-
92
- `pi-sync update` upgrades this tool through whichever installer owns it —
93
- `pipx upgrade`, `uv tool upgrade`, or `pip install --upgrade` — and reports the
94
- version it moved from and to. `--check` reports without changing anything.
95
- Running from a source checkout it tells you to `git pull` instead, and from an
96
- ephemeral `uvx --from …` environment it explains that there is nothing to
97
- upgrade.
98
-
99
- `pi-sync --update-pi <hosts>` runs pi's own updater (`pi update --self`) on each
100
- host before syncing, so the fleet does not drift:
101
-
102
- ```console
103
- $ pi-sync --update-pi tinfoil
104
- → tinfoil
105
- pi 0.85.1 → 0.86.0
106
- models.json already in sync
107
- settings.json already in sync
108
- extensions already in sync
109
- ```
110
-
111
- `update` is a reserved word — a host with that alias is still reachable as
112
- `user@update`.
113
-
114
- ## Uninstalling
115
-
116
- `--uninstall` removes pi from the host instead of syncing. It runs
117
- `npm uninstall -g --prefix <prefix> @earendil-works/pi-coding-agent`, deriving the
118
- prefix from where pi actually lives (as the official installer does), then
119
- re-probes to confirm the binary is really gone — npm can exit 0 having removed
120
- nothing. `~/.pi/agent` is deliberately left untouched, since that is your config
121
- rather than the CLI.
122
-
123
- This exists because the official installer can only uninstall through its
124
- interactive menu: its unattended mode always installs or reinstalls. If the
125
- uninstall fails, that is usually a managed install
126
- (`PI_EXPERIMENTAL=1`, under `<agent dir>/install`) — run
127
- `curl -fsSL https://pi.dev/install.sh | sh` on the host and choose `u`.
128
-
129
- ## Shell completions
130
-
131
- Host arguments complete from `~/.ssh/config`, following `Include` directives and
132
- skipping wildcard entries. zsh and fish also show where each alias points:
133
-
134
- ```console
135
- $ pi-sync t<TAB>
136
- tinfoil tinfoil@tinfoil.sayan.page
137
- tinfoil-proxy notdebian@100.98.241.11
138
- ```
139
-
140
- ```bash
141
- # bash
142
- _PI_SYNC_COMPLETE=bash_source pi-sync > ~/.pi-sync-complete.bash
143
- echo 'source ~/.pi-sync-complete.bash' >> ~/.bashrc
144
-
145
- # zsh
146
- _PI_SYNC_COMPLETE=zsh_source pi-sync > ~/.pi-sync-complete.zsh
147
- echo 'source ~/.pi-sync-complete.zsh' >> ~/.zshrc
148
-
149
- # fish (config.fish)
150
- _PI_SYNC_COMPLETE=fish_source pi-sync | source
151
- ```
152
-
153
- Writing the script out (rather than `eval "$(_PI_SYNC_COMPLETE=bash_source pi-sync)"`
154
- on every shell start) keeps shell startup fast, since the eval form runs the
155
- binary each time. PowerShell works the same way via `powershell_source`.
156
-
157
- Completions are read from the config at completion time, so new hosts appear
158
- without regenerating anything.
159
-
160
- ## Caveats
161
-
162
- - `settings.json` is machine-written by pi (`lastChangelogVersion` bumps, UI
163
- toggles), so two hosts pushing it will overwrite each other's local
164
- preferences. Sync it when you change `packages`, not reflexively — and note the
165
- overwritten copy is kept as `settings.json.backup` on the receiving host.
166
- - `*.backup` files are never synced, so those copies stay host-local and never
167
- trampoline between hosts. `--delete` suppresses backups for the mirrored
168
- directory, because mirroring means "match exactly".
169
- - Extensions that write runtime files inside their own directory (logs,
170
- checkpoints) get those files synced too, and each host's copy is overwritten by
171
- whichever side pushed last — exclude them with `-x '*/logs/*'`.
172
- - Extension versions are whatever each host has installed; pin them in
173
- `settings.json` (`npm:pi-lens@1.2.3`) if you need hosts identical.
174
- - `--auth` copies API keys in the clear. Prefer `OPENCODE_API_KEY` (and friends)
175
- in the environment where you can.
176
- - Remote paths go through the host's shell, so `~` expands there as usual.
177
-
178
- ## Developing
179
-
180
- See [DEVELOPMENT.md](DEVELOPMENT.md) for the layout, tests and release process.
File without changes
File without changes