@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 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 that artifact is included in the published CLI package. Its manifest entry is `{ "type": "skill", "source": "resources/skills/pi-sync", "target": "skills/pi-sync" }`. The CLI apply installs declared Pi packages from exact lockfile pins and reconciles managed resources; it does not install undeclared bundled content.
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.0.0",
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.