@codedealer/pi-sync 1.0.0 → 1.4.0
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.
- package/README.md +29 -2
- package/package.json +5 -2
- package/resources/extensions/pi-sync/README.md +100 -0
- package/resources/extensions/pi-sync/index.ts +564 -0
- package/resources/extensions/pi-sync/status.js +217 -0
- package/resources/skills/pi-sync/SKILL.md +21 -1
- package/src/cli.js +264 -39
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ Resources use per-type roots, and every source and target must stay inside its d
|
|
|
81
81
|
| `agent` | `resources/agents/NAME.md` | `agents/NAME.md` | single `.md` file |
|
|
82
82
|
| `role` | `resources/roles/NAME.md` | `roles/NAME.md` | single `.md` file |
|
|
83
83
|
|
|
84
|
-
For non-skill types the source and target must use the same name. Symbolic links are rejected, paths use relative POSIX syntax, and no path may traverse outside its root. `node_modules` directories are never imported or managed; only authored content and portable extension configuration belong in the setup repository. Package-installed code is never imported. The optional schema-v1 `settings` list supports explicitly selected JSON Pointer fields in `settings.json`, `models.json`, `keybindings.json`, or `mcp.json`; each entry has a `target` and `fields` array of `{ "path": "/nested/key", "value": ... }`. Keep paths narrow and never declare credentials or machine-local values; Pi Sync rejects credential-like field names as a defense-in-depth check. This is not a general secret scanner. Unselected keys and values are preserved. A pre-existing selected setting that differs from its declared value is reported as a collision on first apply rather than overwritten. Unsupported schema fields, resource types, malformed dependency declarations, and lockfile package entries that do not exactly match manifest dependencies are rejected before the local installation is changed.
|
|
84
|
+
For non-skill types the source and target must use the same name. Symbolic links are rejected, paths use relative POSIX syntax, and no path may traverse outside its root. `node_modules` directories are never imported or managed; only authored content and portable extension configuration belong in the setup repository. Package-installed code is never imported. The optional schema-v1 `settings` list supports explicitly selected JSON Pointer fields in `settings.json`, `models.json`, `keybindings.json`, or `mcp.json`; each entry has a `target` and `fields` array of `{ "path": "/nested/key", "value": ... }`. Keep paths narrow and never declare literal credentials or machine-local values; Pi Sync rejects credential-like field names in managed setting values as a defense-in-depth check, with two exceptions: a credential-like key may hold a strict `$NAME` or `${NAME}` environment reference (which Pi interpolates at request time), so the reference is synced while the actual value stays configured per machine, or the safe literal `__PLACEHOLDER__`, which is Pi's documented way to satisfy the mandatory `apiKey` field for local endpoints that require no real key. This is not a general secret scanner. Unselected keys and values are preserved. A pre-existing selected setting that differs from its declared value is reported as a collision on first apply rather than overwritten. Unsupported schema fields, resource types, malformed dependency declarations, and lockfile package entries that do not exactly match manifest dependencies are rejected before the local installation is changed.
|
|
85
85
|
|
|
86
86
|
Apply stores its last-applied content baseline and recoverable backups under `<agent-dir>/.pi-sync/`, outside the setup repository. The baseline enables file-level three-way resource reconciliation and per-field settings reconciliation: repository-only changes are applied independently, local-only changes are preserved and reported as `local-divergence`, and overlapping changes are reported as conflicts without overwriting the conflicted item. Non-conflicting files and settings fields can still be reconciled in the same apply. Apply operations are serialized with a local lock; if a lock's owner process has exited, apply fails closed and asks the user to remove `.pi-sync/apply.lock` only after confirming that no apply process is running. Local state and backups are not part of the setup repository.
|
|
87
87
|
|
|
@@ -112,6 +112,8 @@ pi-sync update --apply [--repo PATH] [--agent-dir PATH] [--approve]
|
|
|
112
112
|
pi-sync update --apply --push [--repo PATH] [--agent-dir PATH] [--approve]
|
|
113
113
|
pi-sync push [--repo PATH]
|
|
114
114
|
pi-sync push --publish [--repo PATH]
|
|
115
|
+
pi-sync sync [--repo PATH] [--agent-dir PATH]
|
|
116
|
+
pi-sync sync --apply [--repo PATH] [--agent-dir PATH]
|
|
115
117
|
pi-sync prune [--repo PATH] [--agent-dir PATH]
|
|
116
118
|
pi-sync prune --confirm [--repo PATH] [--agent-dir PATH]
|
|
117
119
|
```
|
|
@@ -124,11 +126,36 @@ Do not use the external `pi update --extensions` command for managed packages. I
|
|
|
124
126
|
|
|
125
127
|
`remove` is the explicit way to remove a managed resource or dependency from the repository. Resource removal takes the resource `--type` and the manifest `--target` (for example `skills/NAME` or `prompts/NAME.md`); dependency removal takes `--type dependency` and `--source npm:PACKAGE` or `github:OWNER/REPOSITORY`. Removal is non-destructive by design: it validates the entry and repository, then deletes only the manifest entry and repository content (or the manifest dependency and its exact lockfile pin). It never modifies the local Pi installation and never queues or schedules anything for deletion. A removed resource stays installed indefinitely and is reported as `stale` by `status`, `plan`, and `apply` until it is explicitly pruned or re-declared in the manifest; local removal still happens only through the explicit `pi-sync prune --confirm` flow. A removed dependency stays installed locally by default. Passing `--uninstall` additionally runs `pi uninstall` for the removed pin (for example `npm:PACKAGE@1.2.3`) and, on success, clears that package's local Pi Sync pin bookkeeping; a failed uninstall is reported with exit code `1` without rolling back or altering the repository change. Publishing a removal uses the ordinary explicit push flow, and push accepts commits that remove previously managed paths.
|
|
126
128
|
|
|
129
|
+
`sync` back-syncs local setting changes into the setup manifest, closing the loop for fields that Pi itself rewrites (model scope, default model, theme, thinking level, …). It uses the same three-way comparison as `plan`: a field is eligible only when the repository is unchanged and the local installation diverged from the last applied baseline. Eligible local values are portability-validated (credential-like keys may hold only strict `$NAME`/`${NAME}` environment references or the `__PLACEHOLDER__` placeholder), the resulting manifest is secret-scanned, and the write is atomic. Fields changed in both the repository and the local installation are reported as conflicts and never overwritten; fields changed locally without a baseline (collisions) or removed locally are reported for manual review and never written. `pi-sync sync` previews without writing; `pi-sync sync --apply` writes the manifest. Sync never commits or pushes; publishing still requires an explicit `pi-sync push`.
|
|
130
|
+
|
|
131
|
+
The first-party `pi-sync` extension (declared by bootstrap next to the skill, manifest entry `{ "type": "extension", "source": "resources/extensions/pi-sync", "target": "extensions/pi-sync" }`) automates this inside Pi: it watches the agent directory for changes to the files the manifest declares as settings targets and, after a short debounce, runs `pi-sync sync --apply`. A persistent `pi-sync` footer and a `/pi-sync` command expose the sync state. All sync logic stays in the CLI, which also works headless; the extension is a thin trigger and relay that stays silent on setups that are not managed by Pi Sync. It honors `PI_SYNC_REPO_DIR` and `PI_SYNC_BIN` overrides and resolves the agent directory through Pi itself (`PI_CODING_AGENT_DIR` or `~/.pi/agent`).
|
|
132
|
+
|
|
133
|
+
The footer renders the `summary` block of `pi-sync status` in at most one line; the CLI decides what counts as pending or review-worthy, and the extension never computes drift itself:
|
|
134
|
+
|
|
135
|
+
| Footer | Meaning |
|
|
136
|
+
| --- | --- |
|
|
137
|
+
| `pi-sync ✓` | Fully in sync: no local drift, repository clean, not behind upstream, nothing needs review. |
|
|
138
|
+
| `pi-sync ↑n` | n local change(s) not yet on the remote. |
|
|
139
|
+
| `pi-sync ↓n` | n remote change(s) not yet applied to the local installation. |
|
|
140
|
+
| `pi-sync !n` | n item(s) need manual review. |
|
|
141
|
+
| `pi-sync ✗` | The status check failed (CLI unavailable, repository error). |
|
|
142
|
+
| `pi-sync …` / `pi-sync ⟳` | Checking / auto-sync in flight. |
|
|
143
|
+
|
|
144
|
+
Non-zero terms combine in the order `↑ ↓ !`; zero terms are omitted; a fully clean state shows only `✓`. The footer refreshes on real changes (session start, sync completion, agent-directory and setup-repository watcher events) and on a 60-second tick that only re-reads file metadata (no network, no CLI spawn in steady state); a shared status cache under the agent directory deduplicates full refreshes across concurrent Pi sessions.
|
|
145
|
+
|
|
146
|
+
At session start the extension also makes remote commits visible without manual action: an async, fetch-only `git fetch` against the upstream that never pulls, merges, or applies anything. It is freshness-gated (skipped when the repository's `FETCH_HEAD` is younger than five minutes) and bounded by a 15-second timeout, so network access happens at most once per session and the periodic tick never touches the network. A fetch failure is silent — network or authentication trouble is not a sync-state failure, and the footer keeps its last known state. Applying remote changes remains the explicit human flow: pull, then `pi-sync apply`.
|
|
147
|
+
|
|
148
|
+
At Pi process startup on a managed setup the extension shows a one-line orientation notification (user-facing only — it never enters the transcript or the LLM context) naming the two main flows: update dependencies with `pi-sync update`, and manage resources by editing the setup repository and running `pi-sync apply`, with the bundled `pi-sync` skill documenting the full workflow. It is shown once per process start — not on session resume, fork, or new-session starts — and never on setups that are not managed by Pi Sync.
|
|
149
|
+
|
|
150
|
+
When the footer shows `✗`, the status check itself failed (CLI unavailable, timeout, or repository error): run `/pi-sync` (or `pi-sync status` in a terminal) to see the structured error. A failed check does not clear the last computed summary; the footer returns to it once the next check succeeds.
|
|
151
|
+
|
|
152
|
+
Multiple Pi sessions sharing one agent directory are supported: each session runs its own watcher and debounce with per-process state, and every `sync --apply` serializes on the same agent lock as `apply` (exclusive lockfile, up to 2.5 s wait). The footer's full refreshes are deduplicated across sessions through the shared status cache: after a real change the first session to refresh recomputes and publishes, the others read the cache. A concurrent sync that loses the race re-reads the fresh manifest and baseline and becomes a no-op instead of double-writing; if the lock is still held after the wait it fails with a readable error and retries on the next change. `sync --apply` never writes the settings files themselves (only the manifest and baseline state), so it cannot clobber a setting Pi is persisting, and settings reads retry once to ride out the brief window of Pi's in-place settings write. Genuine both-sides changes remain conflicts for manual review either way.
|
|
153
|
+
|
|
127
154
|
## Agent API and first-party skill
|
|
128
155
|
|
|
129
156
|
Every command emits JSON with `apiVersion: 1` and an `ok` boolean. Conflict responses include structured `conflicts`, `actions`, and/or `staleResources`; errors retain a readable `error` string and include `errorCode`, `details`, and `exitCode`. Exit `0` means the operation completed, `1` means an operational conflict, required first-install approval, package-manager failure, untrustworthy update metadata, a failed explicit `remove --uninstall`, or unsafe stale-resource operation, and `2` means invalid input or repository/manifest validation failed. In particular, apply leaves conflicted items untouched and returns `1`; inspect the JSON and re-plan rather than guessing or overwriting.
|
|
130
157
|
|
|
131
|
-
The agent workflow is documented in the first-party skill at `resources/skills/pi-sync/SKILL.md`, and
|
|
158
|
+
The agent workflow is documented in the first-party skill at `resources/skills/pi-sync/SKILL.md`, and both the skill and the settings-sync extension are included in the published CLI package and declared by bootstrap. The CLI apply installs declared Pi packages from exact lockfile pins and reconciles managed resources; it does not install undeclared bundled content.
|
|
132
159
|
|
|
133
160
|
Push is opt-in. `pi-sync push` previews the full patch for `pi-sync.json` (including policy), `pi-sync.lock.json`, and only resources explicitly registered by the manifest. It excludes unrelated staged/unstaged files, ignored files, and unregistered resources. Review the preview before `pi-sync push --publish`; the publishing flow re-displays the commit preview before creating an ordinary commit and fast-forward push. Git hooks are skipped for the Pi Sync commit so hooks cannot add paths outside the managed allowlist. Ahead commits are validated against the setup manifest as of that commit, the manifest before the commit, and the current managed set, so commits that add, edit, or remove managed paths — including a committed removal of a resource — are publishable. It stops on genuinely unrelated commits ahead of upstream or any remote advancement/divergence and never force-pushes. A rejected push leaves the managed commit local. `update --apply --push` is explicit authorization to publish after the approved package update and follows the same managed-only checks.
|
|
134
161
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codedealer/pi-sync",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "A manifest-driven CLI for applying explicitly managed Pi resources.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"src/cli.js",
|
|
12
12
|
"src/packages.js",
|
|
13
13
|
"src/prerequisites.js",
|
|
14
|
-
"resources/skills/pi-sync/SKILL.md"
|
|
14
|
+
"resources/skills/pi-sync/SKILL.md",
|
|
15
|
+
"resources/extensions/pi-sync/index.ts",
|
|
16
|
+
"resources/extensions/pi-sync/status.js",
|
|
17
|
+
"resources/extensions/pi-sync/README.md"
|
|
15
18
|
],
|
|
16
19
|
"scripts": {
|
|
17
20
|
"test": "node --test"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# pi-sync extension
|
|
2
|
+
|
|
3
|
+
Bundled first-party Pi extension that keeps local Pi setting changes in sync
|
|
4
|
+
with your pi-sync setup repository.
|
|
5
|
+
|
|
6
|
+
Pi writes `settings.json` (and any other file your setup manifest declares as
|
|
7
|
+
a settings target) when you change the model, theme, thinking level, model
|
|
8
|
+
scope, or any other setting in the TUI. This extension watches those files
|
|
9
|
+
and, after a short debounce, runs:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
pi-sync sync --apply --repo ~/.pi-sync/setup --agent-dir <agent dir>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`pi-sync sync` promotes local-only changes into the `settings` declarations of
|
|
16
|
+
`pi-sync.json` using the same three-way comparison as `apply` (managed fields
|
|
17
|
+
where the repository is unchanged and the local file diverged from the last
|
|
18
|
+
applied baseline). Values are portability-validated (credential-like keys may
|
|
19
|
+
only hold strict `$NAME`/`${NAME}` environment references or the
|
|
20
|
+
`__PLACEHOLDER__` placeholder), secret-scanned, and written atomically.
|
|
21
|
+
Conflicting fields (changed locally and in the repository) are never
|
|
22
|
+
overwritten; they are reported for manual review.
|
|
23
|
+
|
|
24
|
+
The extension is a thin trigger and relay: all sync logic lives in the
|
|
25
|
+
`pi-sync` CLI, which also works headless (`pi-sync sync` previews,
|
|
26
|
+
`pi-sync sync --apply` writes). Publishing synced changes still requires an
|
|
27
|
+
explicit `pi-sync push`.
|
|
28
|
+
|
|
29
|
+
## Footer status
|
|
30
|
+
|
|
31
|
+
The extension renders the `summary` block of `pi-sync status` as a persistent
|
|
32
|
+
footer. The CLI decides what counts as pending or review-worthy; the
|
|
33
|
+
extension never computes drift itself.
|
|
34
|
+
|
|
35
|
+
| Footer | Meaning |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `pi-sync ✓` | Fully in sync: no local drift, repository clean, not behind upstream, nothing needs review. |
|
|
38
|
+
| `pi-sync ↑n` | n local change(s) not yet on the remote. |
|
|
39
|
+
| `pi-sync ↓n` | n remote change(s) not yet applied to the local installation. |
|
|
40
|
+
| `pi-sync !n` | n item(s) need manual review. |
|
|
41
|
+
| `pi-sync ✗` | The status check failed (CLI unavailable, repository error). |
|
|
42
|
+
| `pi-sync …` / `pi-sync ⟳` | Checking / auto-sync in flight. |
|
|
43
|
+
|
|
44
|
+
Non-zero terms combine in the order `↑ ↓ !`; zero terms are omitted; a fully
|
|
45
|
+
clean state shows only `✓`. The footer refreshes on real changes (session
|
|
46
|
+
start, sync completion, agent-directory and setup-repository watcher events)
|
|
47
|
+
and on a 60-second tick that only re-reads file metadata — no network, no
|
|
48
|
+
CLI spawn in steady state. A shared status cache under the agent directory
|
|
49
|
+
deduplicates full refreshes across concurrent Pi sessions.
|
|
50
|
+
|
|
51
|
+
If the footer shows `✗`, the status check itself failed (CLI unavailable,
|
|
52
|
+
timeout, or repository error): run `/pi-sync` to see the structured error.
|
|
53
|
+
A failed check does not clear the last computed summary; the footer returns
|
|
54
|
+
to it once the next check succeeds.
|
|
55
|
+
|
|
56
|
+
## Session-start remote awareness
|
|
57
|
+
|
|
58
|
+
At session start the extension also makes remote commits visible without
|
|
59
|
+
manual action: an async, fetch-only `git fetch` against the upstream that
|
|
60
|
+
never pulls, merges, or applies anything (applying remote changes remains the
|
|
61
|
+
explicit human flow: pull, then `pi-sync apply`). The fetch is
|
|
62
|
+
freshness-gated (skipped when the repository's `FETCH_HEAD` is younger than
|
|
63
|
+
five minutes), single-flight, and bounded by a 15-second timeout, so network
|
|
64
|
+
access happens at most once per session and the periodic tick never touches
|
|
65
|
+
the network. A fetch failure is silent: network or authentication trouble is
|
|
66
|
+
not a sync-state failure, so the footer keeps its last known state and the
|
|
67
|
+
next session retries.
|
|
68
|
+
|
|
69
|
+
## Startup orientation
|
|
70
|
+
|
|
71
|
+
At Pi process startup — not on session resume, fork, or new-session starts —
|
|
72
|
+
a managed setup receives a one-line orientation notification naming the two
|
|
73
|
+
main flows: update dependencies with `pi-sync update`, and manage resources
|
|
74
|
+
by editing the setup repository and running `pi-sync apply`. It points at
|
|
75
|
+
the `pi-sync` skill for the full workflow. It is a user-facing info
|
|
76
|
+
notification only: it never enters the transcript or the LLM context.
|
|
77
|
+
|
|
78
|
+
## Command and environment
|
|
79
|
+
|
|
80
|
+
- Command: `/pi-sync` runs a manual sync and reports the result.
|
|
81
|
+
- Environment overrides: `PI_SYNC_REPO_DIR` (default `~/.pi-sync/setup`),
|
|
82
|
+
`PI_SYNC_BIN` (default `pi-sync` on PATH). The agent directory is resolved
|
|
83
|
+
through Pi itself (`PI_CODING_AGENT_DIR` or `~/.pi/agent`).
|
|
84
|
+
|
|
85
|
+
## Multiple sessions
|
|
86
|
+
|
|
87
|
+
Multiple Pi sessions sharing one agent directory are safe: each session runs
|
|
88
|
+
its own watcher with per-process state, every `sync --apply` serializes on
|
|
89
|
+
the same agent lock as `apply`, and the shared status cache ensures at most
|
|
90
|
+
one full footer refresh per real change. A concurrent sync that loses the
|
|
91
|
+
race re-reads the fresh manifest and baseline and becomes a no-op instead of
|
|
92
|
+
double-writing. `sync --apply` never writes the settings files themselves
|
|
93
|
+
(only the manifest and baseline state), so it cannot clobber a setting Pi is
|
|
94
|
+
persisting.
|
|
95
|
+
|
|
96
|
+
## Non-managed setups
|
|
97
|
+
|
|
98
|
+
The extension stays completely silent on setups that are not managed by
|
|
99
|
+
pi-sync (no settings targets declared in the setup manifest): no footer, no
|
|
100
|
+
orientation message, no status entry, and no network activity.
|