pier-setup 0.3.0 → 0.4.1
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 +34 -31
- package/install.mjs +76 -106
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -19,18 +19,21 @@ pier ships as **two halves**, installed separately:
|
|
|
19
19
|
| Half | Package | Role |
|
|
20
20
|
|---|---|---|
|
|
21
21
|
| **pi extension** | `packages/pier-ext` (npm: [`pi-pier`](https://www.npmjs.com/package/pi-pier), [pi.dev gallery](https://pi.dev/packages/pi-pier)) | Injects `todo_write` / `subagent` / `terminal` / `ask_user_question` tools into a pi session, and reports pane state over the herdr socket API |
|
|
22
|
-
| **herdr plugin** | `packages/pier-workbench` (`pier.workbench`) |
|
|
22
|
+
| **herdr plugin** | `packages/pier-workbench` (`pier.workbench`) | Blocked human-gate notifications, focus heat layout (focused pane grows in place), ops dashboard + sidebar agent view |
|
|
23
23
|
|
|
24
24
|
### Highlights
|
|
25
25
|
|
|
26
|
-
- **Todo loop**: `todo_write` with full-replace semantics, session JSONL as source of truth, correct branch rollback; live pane-title projection `▶i ○p ■b ✓c (N/M) · current task`;
|
|
26
|
+
- **Todo loop**: `todo_write` with full-replace semantics, session JSONL as source of truth, correct branch rollback; live pane-title projection `▶i ○p ■b ✓c (N/M) · current task`; `/todos` command (including unblock)
|
|
27
27
|
- **Todo anti-freeze**: an all-completed list unchanged for ≥6 turns flips the read hook from recitation to a rewrite warning; after ≥1h it archives — first offering one rewrite window with old entries as reference, then (if ignored) a final notice that clears the list (rm persisted to JSONL; the empty guard takes over — multi-step work must re-track); `/todos` still shows history, session JSONL untouched
|
|
28
28
|
- **Interactive subagents**: every subagent = an isolated pi session in its own pane (separate context window); **a human can enter that pane and talk to it directly** (fix bugs, take over, answer ask_user_question)
|
|
29
|
+
- **Delegation identity** (D111): spawn hands the worker a `PI_HERDR_DELEGATION` context (parent pane/session, taskId, task, role), replayed env > worker marker > branch fold; the worker's system prompt gains a `pier-delegation` section (identity + one stable delivery contract), its transcript opens with an "⟵ delegated by master …" card, and its pane title carries an `↳` prefix; `pi-parent`/`pi-task` herdr tokens ride their own report batch, the dashboard groups workers under their master, and blocked alerts name the edge (`worker ← master`)
|
|
30
|
+
- **Task slugs + worker progress reports** (D112): spawn allocates a task slug (numbered on collision); tool outputs print `slug@paneId` and `agentId` accepts the bare slug, bare pane id, or the combined form (the slug part survives a revive that changed the pane); a worker-only `report_progress` tool pipes checkpoints into the master's context as a display-less custom message — no chat flood, no forced turn
|
|
31
|
+
- **Unified TUI status strip** (D113/D114): the resident widget is one line per agent — self as `▶ current item ✓done/total`, each running subagent as `↳ slug ▶ current item ✓d/n` (blocked shows the task + reason; settled rows never occupy the strip; a fully completed self list hides the widget instead of showing a bare `✓N/N`); workers push their full todo list over the pipe on every `todo_write` (event-driven, no polling), and `/pier-tree` opens the complete delegation tree (your todos + every worker's full list + settled nodes) in the shared Pier **popup** — the same singleton panel as `/dashboard`, opened on its `Tree` tab (inside the popup, `1`/`2`/`Tab` switch views; an explicit `/pier-tree overlay` keeps the independent full-window pane; non-Herdr falls back to the in-pane notify view); `/dashboard` opens the same panel on its `Dashboard` tab
|
|
29
32
|
- **Subagent output preview**: `subagent(action: "output", agentId)` returns what a running background subagent has printed since the previous call — an in-memory per-pane cursor computes the delta (`agent.read`, falling back to `pane.read` on older herdr), and the result carries status, revision, truncation and a `restart` flag for scrolled/cleared buffers, so the master can notice a stuck worker instead of waiting for settlement
|
|
30
33
|
- **Soft write-locks**: per-pane lock beacons on `write`/`edit` paths; cross-pane conflicts warn (default) or block (`PI_HERDR_WRITE_LOCK=1`). The warning lists every holder; `/locks` is the human view of the same table (`herdr agent list` exposes the raw tokens to agents)
|
|
31
34
|
- **Role profiles**: built-in `master` / `worker-default`; custom roles as `.pi-herdr/roles/<name>.json`; toolset converges per role (deny rules are not bypassable)
|
|
32
35
|
- **Human gate**: subagent `ask_user_question` → sidebar blocked marker + notification; manual takeover (ESC-interrupt then typing) is heuristically detected so the master pauses/returns management automatically
|
|
33
|
-
- **Ask picker**:
|
|
36
|
+
- **Ask picker**: every choice question renders through one pier-owned picker in the TUI — single-select (arrows + enter/space pick the highlighted row) and `multi: true` toggle list (checkboxes: space toggles, `a` toggles all, live selection count) share the same framed chrome, `recommended` seeds the cursor (and pre-checks in multi), and the free-text row stays last; `allowOther: false` gives pure choice. The host select dialog and typed-index prompts remain the fallbacks for RPC mode and older pi
|
|
34
37
|
- **Blocking dialogs mark the pane**: pi's `ui_prompt_start` / `ui_prompt_end` events cover the inherently blocking `ctx.ui` dialogs (`select` / `confirm` / `input` / `editor`), so a dialog from *any* extension marks the pane blocked and emits the `herdr:blocked` edge; nested gates coalesce, and the ask tool no longer needs its 5s refresh heartbeat. `custom` is deliberately excluded — pi routes resident overlays through it too (pier's own slim-frame overlay never calls `done()`), which used to leave a working pane marked blocked for the whole session
|
|
35
38
|
- **Role gate stops early**: a denied worker tool call returns `terminate`, so a batch whose results are all terminating ends without another model round trip
|
|
36
39
|
- **Transcript cards**: pier's own session entries (todo edits, subagent/terminal registries, role manifests, soft approvals) and its reminder messages render as compact cards instead of raw JSON — entry/message renderers, no pi-tui dependency
|
|
@@ -45,8 +48,8 @@ pier ships as **two halves**, installed separately:
|
|
|
45
48
|
### Requirements
|
|
46
49
|
|
|
47
50
|
- Node ≥ 22
|
|
48
|
-
- pi ≥
|
|
49
|
-
- herdr ≥ 0.9.
|
|
51
|
+
- pi ≥ 1.3.0 (`@earendil-works/pi-coding-agent`) — `before_agent_start.messageType` and resumed-session active tools
|
|
52
|
+
- herdr ≥ 0.9.5 (macOS / Linux / Windows; Windows is preview beta) — native `pane.focused`, self-reported resume, and shutdown-safe session saves
|
|
50
53
|
|
|
51
54
|
### One-shot install (recommended)
|
|
52
55
|
|
|
@@ -56,7 +59,7 @@ No clone needed — run the npm package directly:
|
|
|
56
59
|
npx pier-setup@latest # user mode: pi install npm:pi-pier + herdr plugin install
|
|
57
60
|
npx pier-setup@latest version # local vs npm latest (installer / pi-pier / herdr plugin)
|
|
58
61
|
npx pier-setup@latest update # refresh both halves in place (does not uninstall first)
|
|
59
|
-
npx pier-setup@latest uninstall
|
|
62
|
+
npx pier-setup@latest uninstall
|
|
60
63
|
|
|
61
64
|
npm i -g pier-setup # or install globally, then: pier-setup / version / update
|
|
62
65
|
```
|
|
@@ -70,15 +73,13 @@ Dev mode still requires cloning the repo:
|
|
|
70
73
|
git clone https://github.com/July24/pier && cd pier
|
|
71
74
|
node install.mjs install --dev # local-path pi install + herdr plugin link; code changes are live
|
|
72
75
|
node install.mjs version --dev
|
|
73
|
-
node install.mjs update --dev #
|
|
76
|
+
node install.mjs update --dev # re-check env + versions; pull the repo yourself
|
|
74
77
|
```
|
|
75
78
|
|
|
76
|
-
The script verifies the environment (node / pi / herdr versions)
|
|
77
|
-
and cli.js absolute paths, generates boot-config.json (user mode stores it in the
|
|
78
|
-
herdr plugin config dir, so reinstalls don't lose it), and registers both halves.
|
|
79
|
+
The script verifies the environment (node / pi / herdr versions) and registers both halves.
|
|
79
80
|
|
|
80
81
|
`update` re-runs `pi update npm:pi-pier` (falls back to `pi install`) and
|
|
81
|
-
`herdr plugin install … --yes
|
|
82
|
+
`herdr plugin install … --yes`. Override sources with
|
|
82
83
|
`--pi-spec=` / `--herdr-spec=` (npm publishing or forks). `pier-setup --help`
|
|
83
84
|
lists every command.
|
|
84
85
|
|
|
@@ -131,15 +132,7 @@ pi overwrites tools/commands by name; event listeners stack. Two todo or subagen
|
|
|
131
132
|
**Designed coexistence:** herdr’s official `herdr:pi` reporter. Keep it. pier emits `herdr:blocked` so that plugin remains lifecycle authority.
|
|
132
133
|
|
|
133
134
|
- **other agents inside herdr (claude code / codex etc.)**: tabs without a pi pane
|
|
134
|
-
are excluded from heat reflow; blocked notifications are pi-only
|
|
135
|
-
auto-bootstrap can be disabled with `autoBootstrap: false` in boot-config.
|
|
136
|
-
|
|
137
|
-
### Bootstrap config (workbench half)
|
|
138
|
-
|
|
139
|
-
Master-tab bootstrap needs local node / pi paths: `pier-setup` generates
|
|
140
|
-
them automatically; manually, copy `packages/pier-workbench/scripts/boot-config.example.json`
|
|
141
|
-
(placeholders for both macOS and Windows). User mode reads it from
|
|
142
|
-
`herdr plugin config-dir pier.workbench`; dev mode from `packages/pier-workbench/scripts/boot-config.json`.
|
|
135
|
+
are excluded from heat reflow; blocked notifications are pi-only.
|
|
143
136
|
|
|
144
137
|
### Usage
|
|
145
138
|
|
|
@@ -153,7 +146,7 @@ them automatically; manually, copy `packages/pier-workbench/scripts/boot-config.
|
|
|
153
146
|
```
|
|
154
147
|
packages/
|
|
155
148
|
pier-ext/ # pi extension (npm: pi-pier): todo/subagent tools, herdr client, vocab authority, skill
|
|
156
|
-
pier-workbench/ # herdr plugin (pier.workbench):
|
|
149
|
+
pier-workbench/ # herdr plugin (pier.workbench): blocked notify + heat layout + ops dashboard
|
|
157
150
|
docs/ # install guide, configuration overview, role profile docs, sidebar role config
|
|
158
151
|
```
|
|
159
152
|
|
|
@@ -161,7 +154,7 @@ docs/ # install guide, configuration overview, role profile docs, s
|
|
|
161
154
|
|
|
162
155
|
```sh
|
|
163
156
|
npm install --ignore-scripts
|
|
164
|
-
npm test # node --test,
|
|
157
|
+
npm test # node --test, 733 unit tests (planner / todo replay / anti-freeze staleness / session tail / GC / lifecycle / renderers / ask picker / subagent output / jev decision layer: diagnostic gate, notice ranking, excerpt windows)
|
|
165
158
|
```
|
|
166
159
|
|
|
167
160
|
## Configuration
|
|
@@ -179,9 +172,8 @@ Runtime policies and operational limits are centralized in `runtime-policy.ts` (
|
|
|
179
172
|
| `PIER_READY_TIMEOUT_MS` | `90000` | ms | Subagent pane pipe readiness wait (exponential backoff; a pane that exited fails immediately with its last output attached) |
|
|
180
173
|
| `PIER_SESSION_TTL_SECONDS` | `600` | s | Session retention TTL after subagent exit before GC cleanup |
|
|
181
174
|
| `PIER_GIT_TIMEOUT_MS` | `10000` | ms | Execution timeout for git operations (worktree creation, diff summary, cleanup) |
|
|
182
|
-
| `PIER_FOCUS_POLL_MS` | `1500` / `0` | ms | Focus sampling for heat layout (`0` disables). Default 1500ms on Herdr <0.9.1; 0 (event-first) on 0.9.1+ |
|
|
183
175
|
| `PIER_TERM_READ_MAX` | `8000` | chars | Maximum terminal buffer characters read per operation |
|
|
184
|
-
| `PIER_TERM_IDLE_MS` | `
|
|
176
|
+
| `PIER_TERM_IDLE_MS` | `300000` | ms | Idle time before the second terminal-cleanup nudge (one is sent at turn end for a terminal used that turn) |
|
|
185
177
|
| `PIER_TODO_GRACE_MS` | `30000` | ms | Settle grace before the unfinished-todo reminder |
|
|
186
178
|
| `PIER_TRACE` | – | flag | Write diagnostics (tool renderers, swallowed errors) to stderr |
|
|
187
179
|
| `PIER_JEV_ENABLE` | `0` | flag | Jev decision layer (TypeSafe System One classification) master switch; every call site fails open — see `docs/rfc-jev-integration.md` |
|
|
@@ -199,15 +191,26 @@ processes stay as they are (`PI_HERDR_SUBAGENT`, `PI_HERDR_ROLE_MANIFEST`, `PI_H
|
|
|
199
191
|
`/pier-config doctor` lists every option with its effective value and where it came from, plus the
|
|
200
192
|
errors pier deliberately swallowed this session.
|
|
201
193
|
|
|
202
|
-
**Focus heat:** Herdr 0.9.
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
194
|
+
**Focus heat:** Herdr 0.9.5 delivers `pane.focused` natively, so pier starts no per-pane sampler;
|
|
195
|
+
the workbench receives the event directly. `PIER_WORKBENCH_ROOT` points at a relocated plugin checkout.
|
|
196
|
+
|
|
197
|
+
**Upstream tracking:** pi 1.3 / herdr 0.9.5 impact, version-floor cleanup, and the next extension
|
|
198
|
+
directions (remote machines, native worktrees, plugin actions):
|
|
199
|
+
**[docs/rfc-upstream-absorption-2026-10.md](docs/rfc-upstream-absorption-2026-10.md)**.
|
|
206
200
|
|
|
207
201
|
### Efficiency mechanisms (D100–D103, opt-in)
|
|
208
202
|
|
|
209
203
|
Three token-saving mechanisms ship **disabled by default** and are safe to try one at a time:
|
|
210
204
|
|
|
205
|
+
**Origin — credit where due**: the mechanism ideas and names come from NVIDIA's
|
|
206
|
+
[SoL-Pi](https://github.com/NVlabs/SoL-Pi) ([paper](https://arxiv.org/abs/2609.20519) ·
|
|
207
|
+
[blog](https://nvlabs.github.io/SoL-Pi/)) — four harness-level efficiency mechanisms surfaced by
|
|
208
|
+
scaled auto-research loops. pier adopts three of them (ObservationPack / EPR / OCC; the fourth,
|
|
209
|
+
Action Fusion, is not adopted) and re-implements them on its own stack, extending the originals
|
|
210
|
+
with a jev decision layer (small System One classifications at the diagnostic gate, excerpt-window
|
|
211
|
+
pick, and settle attribution — [rfc-jev-integration.md](docs/rfc-jev-integration.md)), role gating,
|
|
212
|
+
and the 4-plane efficiency config with per-mechanism JSONL telemetry (D100–D103).
|
|
213
|
+
|
|
211
214
|
- **ObservationPack** — large tool outputs are projected as placeholders after the first `fullSends` requests, with paged retrieval via `obs_recall` (session JSONL stays untouched).
|
|
212
215
|
- **EPR** — long diagnostic (`bash` test/build) logs are reduced in-process to a byte-verified evidence receipt; the raw log is archived first and stays readable.
|
|
213
216
|
- **OCC** — `todo_write` boundaries drive pi's native compaction under a KV-cache cost model, carrying unfinished tasks across the compaction.
|
|
@@ -218,10 +221,10 @@ PI_HERDR_OBS_PACK_ENABLE=1 PI_HERDR_OBS_PACK_LOG=1 pi # start here
|
|
|
218
221
|
|
|
219
222
|
Full quick start, what to watch in `efficiency-logs/*.jsonl`, rollback steps and a feedback template: **[docs/efficiency-trial.md](docs/efficiency-trial.md)**.
|
|
220
223
|
|
|
221
|
-
Everything above (plus roles, pi settings
|
|
224
|
+
Everything above (plus roles, pi settings and the `PIER_*`/`PI_HERDR_*` env knobs) is also inspectable in-session:
|
|
222
225
|
|
|
223
226
|
```text
|
|
224
|
-
/pier-config #
|
|
227
|
+
/pier-config # 4-plane index; hands a guided change to the agent
|
|
225
228
|
/pier-config show all # effective value + source (env > workspace > user > default) per key
|
|
226
229
|
/pier-config check # validation across planes · /pier-config doc → written report
|
|
227
230
|
```
|
|
@@ -241,6 +244,6 @@ MIT
|
|
|
241
244
|
|
|
242
245
|
---
|
|
243
246
|
|
|
244
|
-
> 💡 **For contributors**: `.gitignore` is tracked in the repository.
|
|
247
|
+
> 💡 **For contributors**: `.gitignore` is tracked in the repository. `docs/research/` contains local research notes ignored by `.gitignore`.
|
|
245
248
|
>
|
|
246
249
|
> **Naming convention**: the brand is **pier** (repo/packages/plugin); runtime protocol identifiers keep the **`pi-herdr`** prefix (`.pi-herdr/roles/` dir, `pi-herdr.subs` session custom entries, `~/.pi/agent/herdr-pi/roles/` user dir) — they persist with user sessions/config files, renaming would break existing data, so they are the compatibility layer.
|
package/install.mjs
CHANGED
|
@@ -3,25 +3,23 @@
|
|
|
3
3
|
* pier one-shot installer / uninstaller / updater / version inspector (cross-platform: macOS / Linux / Windows).
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
* node install.mjs install [--dev]
|
|
7
|
-
* node install.mjs update [--dev]
|
|
8
|
-
* node install.mjs version [--json]
|
|
9
|
-
* node install.mjs uninstall [--dev]
|
|
6
|
+
* node install.mjs install [--dev] # Install (default command, 'install' can be omitted)
|
|
7
|
+
* node install.mjs update [--dev] # Refresh both halves in place to latest release (no prior uninstall)
|
|
8
|
+
* node install.mjs version [--json] # Local vs npm latest
|
|
9
|
+
* node install.mjs uninstall [--dev] # Uninstall both halves
|
|
10
10
|
* node install.mjs --prepare # npm prepare: hooksPath + repo root bin link
|
|
11
11
|
* node install.mjs --help
|
|
12
12
|
*
|
|
13
13
|
* Modes:
|
|
14
14
|
* User mode (default): pi install npm:pi-pier
|
|
15
15
|
* + herdr plugin install July24/pier/packages/pier-workbench --yes
|
|
16
|
-
* boot-config.json written to herdr plugin config-dir; extPath points to pi-installed
|
|
17
|
-
* pi-pier (~/.pi/agent/npm/...), not the repo path inside pier-setup package.
|
|
18
16
|
* --dev Development mode: pi install <repo>/packages/pier-ext + herdr plugin link <repo>/packages/pier-workbench
|
|
19
|
-
* (linked directory is live; code edits take effect immediately
|
|
17
|
+
* (linked directory is live; code edits take effect immediately).
|
|
20
18
|
* Distribution specs can be overridden via --pi-spec= / --herdr-spec= (for npm publish or fork scenarios).
|
|
21
19
|
* Failure semantics: Each step provides manual equivalent command; step failure does not abort reporting (exitCode=1).
|
|
22
20
|
*/
|
|
23
21
|
import { execFileSync } from 'node:child_process';
|
|
24
|
-
import { chmodSync, existsSync, mkdirSync, readFileSync,
|
|
22
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
25
23
|
import { homedir } from 'node:os';
|
|
26
24
|
import { dirname, join, resolve } from 'node:path';
|
|
27
25
|
import { fileURLToPath } from 'node:url';
|
|
@@ -29,7 +27,6 @@ import { fileURLToPath } from 'node:url';
|
|
|
29
27
|
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)));
|
|
30
28
|
const EXT_DIR = join(ROOT, 'packages', 'pier-ext');
|
|
31
29
|
const WB_DIR = join(ROOT, 'packages', 'pier-workbench');
|
|
32
|
-
const DEV_BOOT_CONFIG = join(WB_DIR, 'scripts', 'boot-config.json');
|
|
33
30
|
const EXT_PATH = join(EXT_DIR, 'src', 'index.ts');
|
|
34
31
|
const IS_WIN = process.platform === 'win32';
|
|
35
32
|
const COMMANDS = ['install', 'uninstall', 'update', 'version', 'help'];
|
|
@@ -103,6 +100,11 @@ function tryRun(cmd, args) {
|
|
|
103
100
|
try { run(cmd, args); return true; } catch (e) { log(` (${e.message.split('\n')[0]})`); return false; }
|
|
104
101
|
}
|
|
105
102
|
|
|
103
|
+
/** Same as tryRun without the diagnostic line, for feature probes whose failure is expected. */
|
|
104
|
+
function tryRunQuiet(cmd, args) {
|
|
105
|
+
try { run(cmd, args); return true; } catch { return false; }
|
|
106
|
+
}
|
|
107
|
+
|
|
106
108
|
function parseVersion(s) {
|
|
107
109
|
const m = String(s).match(/(\d+)\.(\d+)\.(\d+)/);
|
|
108
110
|
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
@@ -141,10 +143,6 @@ function npmLatest(name) {
|
|
|
141
143
|
}
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
/** Herdr plugin config directory (boot-config destination in user mode; semantic path exists once plugin is registered). */
|
|
145
|
-
function herdrConfigDir() {
|
|
146
|
-
try { return run('herdr', ['plugin', 'config-dir', 'pier.workbench']); } catch { return null; }
|
|
147
|
-
}
|
|
148
146
|
|
|
149
147
|
/* ── Environment check (pre-install) ────────────────────────────────── */
|
|
150
148
|
function checkEnv() {
|
|
@@ -152,13 +150,13 @@ function checkEnv() {
|
|
|
152
150
|
if (major < 22) die(`node ≥ 22 required, found ${process.versions.node}`);
|
|
153
151
|
log(`✓ node ${process.versions.node}`);
|
|
154
152
|
const piVersion = (() => { try { return parseVersion(run('pi', ['--version'])); } catch { return null; } })();
|
|
155
|
-
if (!piVersion || !geVersion(piVersion, [
|
|
156
|
-
die(`pi ≥
|
|
153
|
+
if (!piVersion || !geVersion(piVersion, [1, 3, 0])) {
|
|
154
|
+
die(`pi ≥ 1.3.0 required${piVersion ? `, found ${piVersion.join('.')}` : ' (pi not found or not runnable)'}. Install: npm i -g @earendil-works/pi-coding-agent`);
|
|
157
155
|
}
|
|
158
156
|
log(`✓ pi ${piVersion.join('.')}`);
|
|
159
157
|
const herdrVersion = (() => { try { return parseVersion(run('herdr', ['--version'])); } catch { return null; } })();
|
|
160
|
-
if (!herdrVersion || !geVersion(herdrVersion, [0, 9,
|
|
161
|
-
die(`herdr ≥ 0.9.
|
|
158
|
+
if (!herdrVersion || !geVersion(herdrVersion, [0, 9, 5])) {
|
|
159
|
+
die(`herdr ≥ 0.9.5 required${herdrVersion ? `, found ${herdrVersion.join('.')}` : ' (herdr not found)'}. Install: https://herdr.dev/`);
|
|
162
160
|
}
|
|
163
161
|
log(`✓ herdr ${herdrVersion.join('.')}`);
|
|
164
162
|
return { piVersion, herdrVersion };
|
|
@@ -167,26 +165,6 @@ function checkEnv() {
|
|
|
167
165
|
/** pi extension source: user mode uses git/npm spec, dev mode uses local directory. */
|
|
168
166
|
const piSource = () => (dev ? EXT_DIR : PI_SPEC);
|
|
169
167
|
|
|
170
|
-
/* ── boot-config detection and writing ────────────────────────────── */
|
|
171
|
-
function probePiRuntime() {
|
|
172
|
-
const piBin = which('pi');
|
|
173
|
-
// bin is a node shim: realpath resolves to .../pi-coding-agent/dist/cli.js (npm layout, isomorphic win/posix)
|
|
174
|
-
if (piBin) {
|
|
175
|
-
try {
|
|
176
|
-
const real = realpathSync(piBin);
|
|
177
|
-
if (real.endsWith('cli.js') && existsSync(real)) {
|
|
178
|
-
return { piCli: real, piNode: process.execPath };
|
|
179
|
-
}
|
|
180
|
-
} catch { /* fall back to fallback */ }
|
|
181
|
-
}
|
|
182
|
-
// npm root -g fallback: when pi is not in PATH (e.g. nvm shim), construct from global npm layout
|
|
183
|
-
try {
|
|
184
|
-
const gRoot = run('npm', ['root', '-g']);
|
|
185
|
-
const cand = join(gRoot, '@earendil-works', 'pi-coding-agent', 'dist', 'cli.js');
|
|
186
|
-
if (existsSync(cand)) return { piCli: cand, piNode: process.execPath };
|
|
187
|
-
} catch { /* npm unavailable */ }
|
|
188
|
-
return null;
|
|
189
|
-
}
|
|
190
168
|
|
|
191
169
|
function piAgentDir() {
|
|
192
170
|
return process.env.PI_CODING_AGENT_DIR || join(homedir(), '.pi', 'agent');
|
|
@@ -260,47 +238,6 @@ function resolveUserExtPath() {
|
|
|
260
238
|
return extPathFromPiList();
|
|
261
239
|
}
|
|
262
240
|
|
|
263
|
-
function writeBootConfig(hmrDev, force) {
|
|
264
|
-
const probed = probePiRuntime();
|
|
265
|
-
if (!probed) die('cannot locate pi cli.js (tried: pi shim realpath, npm root -g). Fill boot-config.json manually.');
|
|
266
|
-
let extPath;
|
|
267
|
-
if (dev) {
|
|
268
|
-
if (!existsSync(EXT_PATH)) die(`pier-ext entry missing: ${EXT_PATH} (repo layout broken?)`);
|
|
269
|
-
extPath = EXT_PATH;
|
|
270
|
-
} else {
|
|
271
|
-
extPath = resolveUserExtPath();
|
|
272
|
-
if (!extPath) die(`cannot locate installed pier-ext (npm:pi-pier / git clone). Did \`pi install ${piSource()}\` succeed?`);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
const config = {
|
|
276
|
-
mainTabLabel: 'main',
|
|
277
|
-
piNode: probed.piNode,
|
|
278
|
-
piCli: probed.piCli,
|
|
279
|
-
extPath,
|
|
280
|
-
workbenchPluginId: 'pier.workbench',
|
|
281
|
-
hmrDev,
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
const target = dev ? DEV_BOOT_CONFIG : join(herdrConfigDir() ?? die('cannot resolve herdr plugin config-dir (is pier.workbench installed?)'), 'boot-config.json');
|
|
285
|
-
if (existsSync(target) && !force) {
|
|
286
|
-
let existing = null;
|
|
287
|
-
try { existing = JSON.parse(readFileSync(target, 'utf8')); } catch { /* treated as corrupted */ }
|
|
288
|
-
if (existing) {
|
|
289
|
-
log(`• ${target} exists, diff (existing → new):`);
|
|
290
|
-
for (const k of Object.keys(config)) {
|
|
291
|
-
if (existing[k] !== config[k]) log(` ${k}: ${JSON.stringify(existing[k])} → ${JSON.stringify(config[k])}`);
|
|
292
|
-
}
|
|
293
|
-
log(' kept existing (use --force to overwrite)');
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
mkdirSync(dirname(target), { recursive: true });
|
|
298
|
-
writeFileSync(target, JSON.stringify(config, null, 2) + '\n');
|
|
299
|
-
log(`✓ boot-config.json ${force ? 'overwritten (--force)' : 'written'} → ${target}`);
|
|
300
|
-
log(` piNode = ${config.piNode}`);
|
|
301
|
-
log(` piCli = ${config.piCli}`);
|
|
302
|
-
log(` extPath = ${config.extPath}`);
|
|
303
|
-
}
|
|
304
241
|
|
|
305
242
|
function pkgDirFromExtPath(extPath) {
|
|
306
243
|
let d = dirname(extPath);
|
|
@@ -313,16 +250,57 @@ function pkgDirFromExtPath(extPath) {
|
|
|
313
250
|
return null;
|
|
314
251
|
}
|
|
315
252
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
253
|
+
const HERDR_PLUGIN_ID = 'pier.workbench';
|
|
254
|
+
const HERDR_PLUGIN_NAME = 'pier-workbench';
|
|
255
|
+
const NOT_INSTALLED = Object.freeze({ installed: false, spec: null, sha: null, version: null, source: null, enabled: false });
|
|
256
|
+
|
|
257
|
+
/** `plugin list --json` (herdr 0.9.5+): returns undefined for a malformed payload, null when the
|
|
258
|
+
* plugin is absent, so the caller can fall back to the text listing on older binaries. */
|
|
259
|
+
function parseHerdrPluginJson(out) {
|
|
260
|
+
let parsed;
|
|
261
|
+
try { parsed = JSON.parse(out); } catch { return undefined; }
|
|
262
|
+
const plugins = parsed?.result?.plugins ?? parsed?.plugins;
|
|
263
|
+
if (!Array.isArray(plugins)) return undefined;
|
|
264
|
+
const p = plugins.find((x) => x && (x.plugin_id === HERDR_PLUGIN_ID || x.name === HERDR_PLUGIN_NAME));
|
|
265
|
+
if (!p) return null;
|
|
266
|
+
const source = p.source && typeof p.source === 'object' ? p.source : {};
|
|
267
|
+
const kind = typeof source.kind === 'string' ? source.kind : null;
|
|
268
|
+
const root = typeof p.plugin_root === 'string' ? p.plugin_root : null;
|
|
269
|
+
const commit = typeof source.commit === 'string' ? source.commit : null;
|
|
270
|
+
const spec = kind === 'local'
|
|
271
|
+
? `local:${root ?? p.manifest_path ?? ''}`
|
|
272
|
+
: (typeof p.version === 'string' ? `v${p.version}` : (kind ?? 'installed'));
|
|
273
|
+
return {
|
|
274
|
+
installed: true,
|
|
275
|
+
spec,
|
|
276
|
+
sha: commit ? commit.slice(0, 7) : null,
|
|
277
|
+
version: typeof p.version === 'string' ? p.version : null,
|
|
278
|
+
source: kind,
|
|
279
|
+
enabled: p.enabled === true,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function parseHerdrPluginText(out) {
|
|
284
|
+
const line = out.split(/\r?\n/).find((l) => l.includes(HERDR_PLUGIN_ID));
|
|
285
|
+
if (!line) return NOT_INSTALLED;
|
|
321
286
|
const bracket = line.match(/\[([^\]]+)\]/);
|
|
322
287
|
const spec = bracket ? bracket[1] : line.trim();
|
|
323
288
|
const at = spec.lastIndexOf('@');
|
|
324
289
|
const fullSha = at >= 0 && !spec.startsWith('local:') ? spec.slice(at + 1) : null;
|
|
325
|
-
return { installed: true, spec, sha: fullSha ? fullSha.slice(0, 7) : null };
|
|
290
|
+
return { installed: true, spec, sha: fullSha ? fullSha.slice(0, 7) : null, version: null, source: spec.startsWith('local:') ? 'local' : null, enabled: false };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function herdrPluginInfo() {
|
|
294
|
+
try {
|
|
295
|
+
const out = run('herdr', ['plugin', 'list', '--json']);
|
|
296
|
+
const parsed = parseHerdrPluginJson(out);
|
|
297
|
+
if (parsed !== undefined) return parsed ?? NOT_INSTALLED;
|
|
298
|
+
} catch { /* older herdr or no server; fall through to the text listing */ }
|
|
299
|
+
try {
|
|
300
|
+
return parseHerdrPluginText(run('herdr', ['plugin', 'list']));
|
|
301
|
+
} catch {
|
|
302
|
+
return NOT_INSTALLED;
|
|
303
|
+
}
|
|
326
304
|
}
|
|
327
305
|
|
|
328
306
|
function collectStatus() {
|
|
@@ -344,17 +322,17 @@ function usage() {
|
|
|
344
322
|
log(`pier-setup — install / update / inspect pier (pi-pier + pier.workbench)
|
|
345
323
|
|
|
346
324
|
Usage:
|
|
347
|
-
pier-setup [install] [--dev]
|
|
348
|
-
pier-setup update [--dev]
|
|
325
|
+
pier-setup [install] [--dev]
|
|
326
|
+
pier-setup update [--dev]
|
|
349
327
|
pier-setup version [--json]
|
|
350
|
-
pier-setup uninstall [--dev]
|
|
328
|
+
pier-setup uninstall [--dev]
|
|
351
329
|
|
|
352
330
|
npx pier-setup@latest # recommended user install
|
|
353
331
|
npx pier-setup@latest update # refresh both halves to newest release
|
|
354
332
|
npx pier-setup@latest version # local vs npm latest
|
|
355
333
|
|
|
356
334
|
--pi-spec= / --herdr-spec= override distribution sources
|
|
357
|
-
--dev local link (clone);
|
|
335
|
+
--dev local link (clone); the linked directories are live`);
|
|
358
336
|
}
|
|
359
337
|
|
|
360
338
|
function version() {
|
|
@@ -390,25 +368,26 @@ function update() {
|
|
|
390
368
|
if (latestSetup && me && cmpSemver(me, latestSetup) < 0) {
|
|
391
369
|
log(`⚠ installer ${me} < latest ${latestSetup} — re-run: npx pier-setup@latest update`);
|
|
392
370
|
}
|
|
393
|
-
const hmrDev = flags.has('--hmr-dev');
|
|
394
371
|
if (dev) {
|
|
395
|
-
log('dev mode: local link is live; pull the repo yourself
|
|
396
|
-
writeBootConfig(hmrDev, true);
|
|
372
|
+
log('dev mode: local link is live; pull the repo yourself (no installer-managed files to refresh).');
|
|
397
373
|
} else {
|
|
398
374
|
if (tryRun('pi', ['update', piSource()])) log(`✓ pi extension updated (${piSource()})`);
|
|
399
375
|
else if (tryRun('pi', ['install', piSource()])) log(`✓ pi extension installed (${piSource()})`);
|
|
400
376
|
else { console.error('✗ pi update/install failed'); log(` manual: pi update ${piSource()}`); process.exitCode = 1; }
|
|
401
|
-
|
|
377
|
+
// 0.9.4+ updates the GitHub checkout in place without disturbing running plugin panes.
|
|
378
|
+
// Only try it for an installed, non-local plugin; otherwise install from the configured spec.
|
|
379
|
+
const existing = herdrPluginInfo();
|
|
380
|
+
const updated = (existing.installed && existing.source !== 'local'
|
|
381
|
+
&& tryRunQuiet('herdr', ['plugin', 'update', HERDR_PLUGIN_ID, '--yes']))
|
|
382
|
+
|| tryRunQuiet('herdr', ['plugin', 'install', HERDR_SPEC, '--yes']);
|
|
383
|
+
if (updated) log(`✓ herdr plugin updated (${HERDR_SPEC})`);
|
|
402
384
|
else { console.error('✗ herdr plugin update failed'); log(` manual: herdr plugin install ${HERDR_SPEC} --yes`); process.exitCode = 1; }
|
|
403
|
-
writeBootConfig(hmrDev, true);
|
|
404
385
|
}
|
|
405
386
|
log('');
|
|
406
387
|
version();
|
|
407
388
|
}
|
|
408
389
|
|
|
409
390
|
function install() {
|
|
410
|
-
const hmrDev = flags.has('--hmr-dev');
|
|
411
|
-
const force = flags.has('--force');
|
|
412
391
|
checkEnv();
|
|
413
392
|
removeLegacyGitRegistration();
|
|
414
393
|
|
|
@@ -417,40 +396,31 @@ function install() {
|
|
|
417
396
|
else { console.error('✗ pi install failed'); log(` manual: pi install ${EXT_DIR}`); process.exitCode = 1; }
|
|
418
397
|
if (tryRun('herdr', ['plugin', 'link', WB_DIR])) log('✓ herdr plugin linked (packages/pier-workbench, local)');
|
|
419
398
|
else { console.error('✗ herdr plugin link failed'); log(` manual: herdr plugin link ${WB_DIR}`); process.exitCode = 1; }
|
|
420
|
-
writeBootConfig(hmrDev, force);
|
|
421
399
|
} else {
|
|
422
400
|
if (tryRun('pi', ['install', piSource()])) log(`✓ pi extension installed (${PI_SPEC})`);
|
|
423
401
|
else { console.error('✗ pi install failed'); log(` manual: pi install ${PI_SPEC}`); process.exitCode = 1; }
|
|
424
402
|
if (tryRun('herdr', ['plugin', 'install', HERDR_SPEC, '--yes'])) log(`✓ herdr plugin installed (${HERDR_SPEC})`);
|
|
425
403
|
else { console.error('✗ herdr plugin install failed'); log(` manual: herdr plugin install ${HERDR_SPEC}`); process.exitCode = 1; }
|
|
426
|
-
writeBootConfig(hmrDev, force);
|
|
427
404
|
}
|
|
428
405
|
|
|
429
406
|
log(`
|
|
430
407
|
pier installed (${dev ? 'dev mode: local paths, code changes are live' : 'user mode: managed checkouts'}). Next:
|
|
431
|
-
1. start herdr, open/create a workspace
|
|
408
|
+
1. start herdr, open/create a workspace and run \`pi\` inside a pane
|
|
432
409
|
2. inside pi, ask the model to use todo_write / subagent tools`);
|
|
433
410
|
}
|
|
434
411
|
|
|
435
412
|
function uninstall() {
|
|
436
|
-
const purge = flags.has('--purge');
|
|
437
413
|
if (tryRun('pi', ['remove', piSource()])) log('✓ pi extension removed');
|
|
438
414
|
else { console.error('✗ pi remove failed'); log(` manual: pi remove ${piSource()}`); process.exitCode = 1; }
|
|
439
415
|
|
|
440
416
|
if (dev) {
|
|
441
417
|
if (tryRun('herdr', ['plugin', 'unlink', 'pier.workbench'])) log('✓ herdr plugin unlinked (files kept)');
|
|
442
418
|
else { console.error('✗ herdr plugin unlink failed'); log(' manual: herdr plugin unlink pier.workbench'); process.exitCode = 1; }
|
|
443
|
-
if (purge && existsSync(DEV_BOOT_CONFIG)) { rmSync(DEV_BOOT_CONFIG); log('✓ dev boot-config.json purged'); }
|
|
444
419
|
} else {
|
|
445
420
|
if (tryRun('herdr', ['plugin', 'uninstall', 'pier.workbench'])) log('✓ herdr plugin uninstalled (managed checkout removed)');
|
|
446
421
|
else { console.error('✗ herdr plugin uninstall failed'); log(' manual: herdr plugin uninstall pier.workbench'); process.exitCode = 1; }
|
|
447
|
-
if (purge) {
|
|
448
|
-
const dir = herdrConfigDir();
|
|
449
|
-
const f = dir ? join(dir, 'boot-config.json') : null;
|
|
450
|
-
if (f && existsSync(f)) { rmSync(f); log('✓ boot-config.json purged'); }
|
|
451
|
-
}
|
|
452
422
|
}
|
|
453
|
-
log(
|
|
423
|
+
log('\npier uninstalled.');
|
|
454
424
|
}
|
|
455
425
|
|
|
456
426
|
if (command === 'help' || flags.has('--help') || flags.has('-h')) usage();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pier-setup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "One-shot installer for pier: install / update / version / uninstall for pi-pier + pier.workbench",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -31,8 +31,9 @@
|
|
|
31
31
|
"packages/*"
|
|
32
32
|
],
|
|
33
33
|
"scripts": {
|
|
34
|
+
"build:workbench": "tsc -p packages/pier-workbench/tsconfig.build.json",
|
|
34
35
|
"typecheck": "tsc --noEmit -p packages/pier-ext/tsconfig.json",
|
|
35
|
-
"test": "npm run typecheck && node --test \"packages/*/test/*.test.ts\"",
|
|
36
|
+
"test": "npm run build:workbench && npm run typecheck && node --test \"packages/*/test/*.test.ts\"",
|
|
36
37
|
"test:mutation": "stryker run",
|
|
37
38
|
"prepare": "node ./install.mjs --prepare"
|
|
38
39
|
},
|
|
@@ -42,7 +43,6 @@
|
|
|
42
43
|
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
43
44
|
"@deepseek-ai/cordis-plugin-timer": "^1.1.3",
|
|
44
45
|
"@stryker-mutator/core": "^10.0.0",
|
|
45
|
-
"@stryker-mutator/typescript-checker": "^10.0.0",
|
|
46
46
|
"typescript": "~5.9.3"
|
|
47
47
|
},
|
|
48
48
|
"pi": {
|