pi-sync-cli 0.4.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.
- pi_sync_cli-0.5.1/AGENTS.md +125 -0
- pi_sync_cli-0.5.1/DEVELOPMENT.md +166 -0
- pi_sync_cli-0.5.1/PKG-INFO +89 -0
- pi_sync_cli-0.5.1/README.md +72 -0
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/pyproject.toml +2 -2
- pi_sync_cli-0.5.1/src/pi_sync/__init__.py +5 -0
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/src/pi_sync/cli.py +213 -2
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/tests/test_cli.py +265 -1
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/uv.lock +1 -1
- pi_sync_cli-0.4.0/PKG-INFO +0 -169
- pi_sync_cli-0.4.0/README.md +0 -152
- pi_sync_cli-0.4.0/src/pi_sync/__init__.py +0 -3
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/.github/workflows/publish.yml +0 -0
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/.gitignore +0 -0
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/LICENSE +0 -0
- {pi_sync_cli-0.4.0 → pi_sync_cli-0.5.1}/pyrightconfig.json +0 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Working notes for AI agents (and humans) changing this repo. User-facing
|
|
4
|
+
documentation lives in [README.md](README.md); layout, tooling and the release
|
|
5
|
+
process live in [DEVELOPMENT.md](DEVELOPMENT.md).
|
|
6
|
+
|
|
7
|
+
## What this is
|
|
8
|
+
|
|
9
|
+
pi-sync: a single click module that rsyncs pi agent config between hosts.
|
|
10
|
+
`src/pi_sync/cli.py` is the whole program and `tests/test_cli.py` the whole
|
|
11
|
+
suite, so a change is one file plus its tests.
|
|
12
|
+
|
|
13
|
+
## Commands
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
uv sync # create/refresh .venv and uv.lock
|
|
17
|
+
uv run pytest # the suite (fast, offline)
|
|
18
|
+
uv run pi-sync --help
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Invariants — please don't "fix" these
|
|
22
|
+
|
|
23
|
+
- **Entry point is `pi_sync.cli:app`**, a click group with a default-command
|
|
24
|
+
fallback: `DefaultGroup.parse_args` prepends `sync` when the first token is not
|
|
25
|
+
a subcommand, and `SyncCommand.format_usage` keeps help reading
|
|
26
|
+
`pi-sync [OPTIONS] ...`. The sync function stays named `main` because the tests
|
|
27
|
+
invoke `cli.main` directly.
|
|
28
|
+
- **No `__version__`.** The version comes from distribution metadata; adding one
|
|
29
|
+
back reintroduces the drift that removal fixed.
|
|
30
|
+
- **Tests never spawn anything.** `FakeRun` records argv and replays output;
|
|
31
|
+
extend it instead of calling `subprocess`. That guarantee is deliberate.
|
|
32
|
+
- **Host probes are POSIX sh piped to `ssh host sh -s`**, because some hosts run
|
|
33
|
+
fish and `for …; do … done` is a syntax error there.
|
|
34
|
+
- **Never trust pi's installer exit status.** Its "do nothing" menu choice exits
|
|
35
|
+
0, so `install_pi` re-probes the host. Keep new install/uninstall paths
|
|
36
|
+
verification-based.
|
|
37
|
+
- **`update` resolves the distribution from its own environment**; do not
|
|
38
|
+
hardcode `pi-sync-cli`, since an older install can coexist under `pi-sync` and
|
|
39
|
+
a hardcoded name would upgrade the wrong virtualenv.
|
|
40
|
+
- **`--dry-run` must not mutate anything**, including installing or uninstalling
|
|
41
|
+
pi on a host.
|
|
42
|
+
|
|
43
|
+
## Conventions
|
|
44
|
+
|
|
45
|
+
- Commit messages: all lowercase, conventional prefix (`fix:`, `feat:`, `chore:`,
|
|
46
|
+
`ci:`, `docs:`).
|
|
47
|
+
- Run `uv run pytest` and `lens_diagnostics mode=all` before declaring work done;
|
|
48
|
+
fix blockers rather than reporting around them.
|
|
49
|
+
- pi-lens reformats files on write and sometimes *after* a commit. Check
|
|
50
|
+
`git status` before finishing and commit formatter churn separately.
|
|
51
|
+
- `.pi/tasks/` is gitignored local task output — never commit it.
|
|
52
|
+
- Never commit secrets. `auth.json` is host-local and only travels with an
|
|
53
|
+
explicit `--auth`.
|
|
54
|
+
|
|
55
|
+
## Working against real hosts
|
|
56
|
+
|
|
57
|
+
- This Mac drives everything; `tinfoil` and the Raspberry Pi (`pi`) are real
|
|
58
|
+
hosts and can be offline.
|
|
59
|
+
- Prefer `--dry-run` when demonstrating. A real push overwrites host config — a
|
|
60
|
+
`.backup` is kept, but don't be cavalier about it.
|
|
61
|
+
- `~/.ssh/config` is protected: read it through code that extracts host aliases,
|
|
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.
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Developing pi-sync
|
|
2
|
+
|
|
3
|
+
## Layout
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
src/pi_sync/cli.py the whole CLI: probing, rsync, completions, self-update
|
|
7
|
+
tests/test_cli.py unit tests (no ssh or rsync process is ever executed)
|
|
8
|
+
pyproject.toml click dependency, console script, uv dev group
|
|
9
|
+
pyrightconfig.json points pyright at .venv (see "Tooling" below)
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Three design anchors worth understanding before editing:
|
|
13
|
+
|
|
14
|
+
- **The console script points at `pi_sync.cli:app`**, a click *group*.
|
|
15
|
+
`DefaultGroup.parse_args` prepends `sync` when the first argument is not a
|
|
16
|
+
subcommand, which is what lets `pi-sync tinfoil`, `pi-sync --config tinfoil`
|
|
17
|
+
and `pi-sync update` all work. `SyncCommand.format_usage` exists so help reads
|
|
18
|
+
`pi-sync [OPTIONS] [USER@]HOST...` instead of `pi-sync sync [OPTIONS] ...`.
|
|
19
|
+
`main` is the sync subcommand and keeps that name because the tests drive it
|
|
20
|
+
directly.
|
|
21
|
+
- **The version comes from distribution metadata** (`@click.version_option`), so
|
|
22
|
+
there is no `__version__` to fall out of step with the release.
|
|
23
|
+
- **`update` resolves the distribution from its own environment**
|
|
24
|
+
(`importlib.metadata.packages_distributions()`) rather than hardcoding a name.
|
|
25
|
+
An older install can coexist under the pre-rename name `pi-sync`, and a
|
|
26
|
+
hardcoded name would upgrade the wrong virtualenv — or nothing.
|
|
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
|
+
|
|
75
|
+
## Commands
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
uv sync # create/refresh .venv and uv.lock
|
|
79
|
+
uv run pytest # the suite
|
|
80
|
+
uv run pi-sync --help
|
|
81
|
+
uv build # wheel + sdist into dist/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Tests
|
|
85
|
+
|
|
86
|
+
`FakeRun` records every command and replays canned output, so the suite needs no
|
|
87
|
+
network, no hosts, and no real transfers. When you add a command, extend
|
|
88
|
+
`FakeRun` rather than reaching for `subprocess` — the guarantee that nothing is
|
|
89
|
+
executed is what keeps the suite fast and safe.
|
|
90
|
+
|
|
91
|
+
The one path that cannot be unit-tested is the interactive installer handover,
|
|
92
|
+
because it depends on a real terminal. It was verified out-of-band with a PTY
|
|
93
|
+
harness that attaches a pseudo-terminal, feeds a keystroke, and asserts the
|
|
94
|
+
installer's `/dev/tty` menu reached the terminal and that the keypress reached
|
|
95
|
+
the remote host.
|
|
96
|
+
|
|
97
|
+
## Tooling
|
|
98
|
+
|
|
99
|
+
`pi-lens` runs ruff format/lint as files are written, and sometimes reformats
|
|
100
|
+
*after* a commit — check `git status` before finishing, and commit formatter
|
|
101
|
+
churn on its own.
|
|
102
|
+
|
|
103
|
+
`pyrightconfig.json` sets `venvPath`/`venv` because pyright does not pick up a
|
|
104
|
+
uv-created venv on its own here. Measured: without it, `Import "pytest" could
|
|
105
|
+
not be resolved`; with it, zero errors. A long-lived pyright language server
|
|
106
|
+
that started before the venv existed will keep reporting the stale result until
|
|
107
|
+
it is restarted.
|
|
108
|
+
|
|
109
|
+
## Releasing
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
uv version --bump minor # or patch; also updates uv.lock
|
|
113
|
+
git commit -am "chore: release X.Y.Z"
|
|
114
|
+
git push origin main
|
|
115
|
+
git tag -a vX.Y.Z -m vX.Y.Z
|
|
116
|
+
git push origin vX.Y.Z # publishing happens on the tag
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`.github/workflows/publish.yml` then builds, smoke-tests both artifacts *through
|
|
120
|
+
the console script*, and publishes with OIDC trusted publishing — no token is
|
|
121
|
+
stored anywhere. It refuses to publish when the tag does not match the version in
|
|
122
|
+
`pyproject.toml`.
|
|
123
|
+
|
|
124
|
+
Verify a release:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gh run view <run-id> --log | grep "Uploading pi_"
|
|
128
|
+
curl -s https://pypi.org/pypi/pi-sync-cli/X.Y.Z/json # per-version: immediate
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Expect **CDN lag** on the aggregate endpoints. Measured on 0.4.0: the
|
|
132
|
+
per-version endpoint answered immediately, `/pypi/pi-sync-cli/json` took ~45s,
|
|
133
|
+
and the simple index took ~40s. Until the simple index flips,
|
|
134
|
+
`uvx --from pi-sync-cli==X.Y.Z` fails with "no version … requirements are
|
|
135
|
+
unsatisfiable", which looks like a broken release but is not. Re-tagging never
|
|
136
|
+
helps (PyPI rejects duplicate versions); `workflow_dispatch` re-runs the job
|
|
137
|
+
without a new tag.
|
|
138
|
+
|
|
139
|
+
## PyPI notes
|
|
140
|
+
|
|
141
|
+
The distribution is `pi-sync-cli`; the command is `pi-sync`. Plain `pi-sync` is
|
|
142
|
+
permanently unavailable: PyPI compares names with punctuation stripped, and
|
|
143
|
+
`pisync` already exists (an unrelated rsync backup script).
|
|
144
|
+
|
|
145
|
+
Trusted publisher fields: project `pi-sync-cli`, owner `say4n`, repository
|
|
146
|
+
`pi-sync`, workflow `publish.yml`, environment `pypi`. A pending publisher
|
|
147
|
+
reserves nothing until first use — it is invalidated if someone else registers
|
|
148
|
+
the name in the meantime.
|
|
149
|
+
|
|
150
|
+
## Platform gotchas found the hard way
|
|
151
|
+
|
|
152
|
+
- macOS ships **openrsync**, which rejects rsync 3 flags. Only flags verified
|
|
153
|
+
against it are used; `--ignore-missing-args`, `--human-readable` and
|
|
154
|
+
`--mkpath` are not available.
|
|
155
|
+
- `--backup` with `--delete` fails on openrsync acting as *receiver*
|
|
156
|
+
(`fchownat: Operation not permitted`, exit 23) but works fine with rsync 3.x,
|
|
157
|
+
so backups are suppressed whenever `--delete` is in play.
|
|
158
|
+
- rsync's default check compares size and mtime at 1-second granularity: two
|
|
159
|
+
files created in the same second with equal sizes look "already in sync". Use
|
|
160
|
+
distinct mtimes when testing backups, or the test proves nothing.
|
|
161
|
+
- Remote shells are **fish** on some hosts, so probes are POSIX sh piped over
|
|
162
|
+
stdin (`ssh host sh -s`), never a shell loop in the command string
|
|
163
|
+
(`for …; do … done` is a syntax error there).
|
|
164
|
+
- pi's installer reads `/dev/tty`, not stdin, so capturing its output makes its
|
|
165
|
+
prompts invisible while it waits for a keypress. Interactive installs must
|
|
166
|
+
stream; unattended ones must close stdin so a prompt fails fast.
|
|
@@ -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.
|
|
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"
|
|
@@ -20,7 +20,7 @@ Repository = "https://github.com/say4n/pi-sync"
|
|
|
20
20
|
Issues = "https://github.com/say4n/pi-sync/issues"
|
|
21
21
|
|
|
22
22
|
[project.scripts]
|
|
23
|
-
pi-sync = "pi_sync.cli:
|
|
23
|
+
pi-sync = "pi_sync.cli:app"
|
|
24
24
|
|
|
25
25
|
[dependency-groups]
|
|
26
26
|
dev = ["pytest>=8"]
|