pier-setup 0.1.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/LICENSE +21 -0
- package/README.en.md +134 -0
- package/README.md +130 -0
- package/install.mjs +220 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 July24
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.en.md
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# pier · pi × herdr workspace fusion
|
|
4
|
+
|
|
5
|
+
**pier** = **pi** × h**erdr** (a pier/dock — where things moor, dock, and fan out).
|
|
6
|
+
|
|
7
|
+
[pi](https://pi.dev/) (`@earendil-works/pi-coding-agent`) is the coding-agent carrier; [herdr](https://herdr.dev/) is a terminal workspace manager. pier adds the two capabilities pi deliberately leaves out — a **todo list loop** and **interactive subagents** — and gives them the herdr pane/tab layer as their visual and interactive substrate.
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## What it is
|
|
14
|
+
|
|
15
|
+
pier ships as **two halves**, installed separately:
|
|
16
|
+
|
|
17
|
+
| Half | Package | Role |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **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` / `list_agents` / `send_message` / `interrupt_agent` tools into a pi session, and reports pane state over the herdr socket API |
|
|
20
|
+
| **herdr plugin** | `packages/pier-workbench` (`pier.workbench`) | Workspace bootstrap, blocked human-gate notifications, focus heat layout (focused pane grows in place) |
|
|
21
|
+
|
|
22
|
+
### Highlights
|
|
23
|
+
|
|
24
|
+
- **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)
|
|
25
|
+
- **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)
|
|
26
|
+
- **Soft write-locks**: per-pane lock tokens on write paths; cross-pane conflicts warn or block
|
|
27
|
+
- **Role profiles**: built-in `master` / `worker-default`; custom roles as `.pi-herdr/roles/<name>.json`; toolset converges per role (deny rules are not bypassable)
|
|
28
|
+
- **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
|
|
29
|
+
- **Focus heat layout**: focused pane grows in place (0.72 target); blocked / ask / working / idle sized by weight; surplus panes collapse to title strips
|
|
30
|
+
- **Settlement notice folding**: subagent settlements no longer flood back when a long main run ends — they inject at turn gaps, up to 3 shown, the rest folded with pointers
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
### Requirements
|
|
35
|
+
|
|
36
|
+
- Node ≥ 22
|
|
37
|
+
- pi ≥ 0.84 (`@earendil-works/pi-coding-agent`)
|
|
38
|
+
- herdr ≥ 0.8.0 (macOS / Linux / Windows; Windows is preview beta)
|
|
39
|
+
|
|
40
|
+
### One-shot install (recommended)
|
|
41
|
+
|
|
42
|
+
No clone needed — run the npm package directly:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npx pier-setup # user mode: pi install npm:pi-pier + herdr plugin install + auto-generated bootstrap config
|
|
46
|
+
npm i -g pier-setup # or install globally, then just run pier-setup
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Dev mode still requires cloning the repo:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
git clone https://github.com/July24/pier && cd pier
|
|
53
|
+
node install.mjs install --dev # local-path pi install + herdr plugin link; code changes are live
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The script verifies the environment (node / pi / herdr versions), probes pi's node
|
|
57
|
+
and cli.js absolute paths, generates boot-config.json (user mode stores it in the
|
|
58
|
+
herdr plugin config dir, so reinstalls don't lose it), and registers both halves.
|
|
59
|
+
|
|
60
|
+
Other commands: `pier-setup update` (uninstall + reinstall, re-probes paths),
|
|
61
|
+
`pier-setup uninstall --purge`. Override the distribution specs with
|
|
62
|
+
`--pi-spec=` / `--herdr-spec=` (npm publishing or forks).
|
|
63
|
+
|
|
64
|
+
### Manual install (equivalent steps)
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
# pi extension (any one; npm source = recommended user mode, git source = latest main, local path = dev mode)
|
|
68
|
+
pi install npm:pi-pier # user mode (npm release, auto-listed on the pi.dev/packages gallery)
|
|
69
|
+
pi install git:github.com/July24/pier # user mode (tracks main)
|
|
70
|
+
pi install ./packages/pier-ext # development
|
|
71
|
+
|
|
72
|
+
# herdr plugin
|
|
73
|
+
herdr plugin install July24/pier/packages/pier-workbench --yes # user mode (reinstall = update)
|
|
74
|
+
herdr plugin link ./packages/pier-workbench # development
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
The extension degrades gracefully outside a herdr environment (see Scope below).
|
|
78
|
+
|
|
79
|
+
### Scope (only the herdr × pi intersection is affected)
|
|
80
|
+
|
|
81
|
+
- **pi outside herdr**: the extension degrades — reporting is free, workbench tools
|
|
82
|
+
(`subagent`, terminal family) return a clear "requires a herdr-managed pane" error;
|
|
83
|
+
`todo_write` / `ask_user_question` stay available (session JSONL is the authority,
|
|
84
|
+
no herdr dependency).
|
|
85
|
+
- **other agents inside herdr (claude code / codex etc.)**: tabs without a pi pane
|
|
86
|
+
are excluded from heat reflow; blocked notifications are pi-only; master-tab
|
|
87
|
+
auto-bootstrap can be disabled with `autoBootstrap: false` in boot-config.
|
|
88
|
+
|
|
89
|
+
### Bootstrap config (workbench half)
|
|
90
|
+
|
|
91
|
+
Master-tab bootstrap needs local node / pi paths: `pier-setup` generates
|
|
92
|
+
them automatically; manually, copy `packages/pier-workbench/scripts/boot-config.example.json`
|
|
93
|
+
(placeholders for both macOS and Windows). User mode reads it from
|
|
94
|
+
`herdr plugin config-dir pier.workbench`; dev mode from `packages/pier-workbench/scripts/boot-config.json`.
|
|
95
|
+
|
|
96
|
+
### Usage
|
|
97
|
+
|
|
98
|
+
1. Run pi inside a herdr workspace pane (`pi` in the pane); the extension detects `HERDR_ENV` and starts reporting
|
|
99
|
+
2. The model can call `todo_write` (live pane-title projection), `subagent` (foreground / parallel / background), `list_agents` / `send_message` / `interrupt_agent`
|
|
100
|
+
3. Every subagent = its own pane (observable, enterable, blocked notifies)
|
|
101
|
+
4. Restart recovery: herdr session restore rebuilds panes automatically (subagent rpc session files are reported); resume the parent pi session with `/resume`
|
|
102
|
+
|
|
103
|
+
## Repository layout
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
packages/
|
|
107
|
+
pier-ext/ # pi extension (npm: pi-pier): todo/subagent tools, herdr client, vocab authority, skill
|
|
108
|
+
pier-workbench/ # herdr plugin (pier.workbench): workspace bootstrap + blocked notify + heat layout
|
|
109
|
+
docs/ # install guide, role profile docs, sidebar role config
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Testing
|
|
113
|
+
|
|
114
|
+
```sh
|
|
115
|
+
npm install --ignore-scripts
|
|
116
|
+
npm test # node --test, 244 unit tests (planner / todo replay / session tail / GC / lifecycle)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Design principles
|
|
120
|
+
|
|
121
|
+
- **Session JSONL is the single source of truth**: todos, delegations, and registries replay from session branches — restart and branch switches stay correct
|
|
122
|
+
- **Event-driven, never poll**: state arrives via herdr event subscriptions (`pane.agent_status_changed` / `pane.closed`); one snapshot is taken only when an event fires
|
|
123
|
+
- **Zero position migration**: the herdr BSP layout topology is fixed at creation; pier only adjusts split ratios — panes never move, they size by priority
|
|
124
|
+
- **Best-effort projection layer**: reporting failures are silent and never block the pi main flow
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
MIT
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
> 💡 **For contributors**: `.gitignore` is deliberately **not committed** to this repository (ignore rules are per-environment). Maintain your own rules based on `README.md` / `README.en.md`; `packages/pier-workbench/scripts/boot-config.json` is also machine-local (see the `.example.json` template).
|
|
133
|
+
>
|
|
134
|
+
> **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/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# pier · pi × herdr 工作台融合
|
|
4
|
+
|
|
5
|
+
**pier** = **pi** × h**erdr** 的融合体(码头/桥,对接、停泊、分发)。
|
|
6
|
+
|
|
7
|
+
[pi](https://pi.dev/)(`@earendil-works/pi-coding-agent`)是 coding agent 载体,[herdr](https://herdr.dev/) 是终端工作区管理器。pier 补上 pi 刻意不内置的两项能力——**todo list 闭环**与**交互式子代理**——并让它们以 herdr 的 pane/tab 为视觉与交互底座。
|
|
8
|
+
|
|
9
|
+
</div>
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## 是什么
|
|
14
|
+
|
|
15
|
+
pier 由**两个半区**组成,各装一处:
|
|
16
|
+
|
|
17
|
+
| 半区 | 包 | 作用 |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| **pi 扩展** | `packages/pier-ext`(npm: [`pi-pier`](https://www.npmjs.com/package/pi-pier),[pi.dev 市场](https://pi.dev/packages/pi-pier)) | 在 pi 会话里注入 `todo_write` / `subagent` / `list_agents` / `send_message` / `interrupt_agent` 工具 + 经 herdr socket API 上报 pane 状态 |
|
|
20
|
+
| **herdr 插件** | `packages/pier-workbench`(`pier.workbench`) | 主 tab 引导、blocked 人类闸门通知、焦点热力布局(聚焦 pane 原地放大) |
|
|
21
|
+
|
|
22
|
+
### 核心能力
|
|
23
|
+
|
|
24
|
+
- **todo 闭环**:`todo_write` 全量替换语义、会话 JSONL 权威、分支正确回滚;窗格标题实时投影 `▶i ○p ■b ✓c (N/M) · 当前任务`;`/todos` 命令(含 unblock)可查看/编辑
|
|
25
|
+
- **交互式子代理**:每个子代理 = 独立 pane 的隔离 pi 会话(独立上下文窗口);**人类可随时进入该 pane 直接对话**(修 bug、接管、回答 ask_user_question)
|
|
26
|
+
- **软锁防争抢**(写路径按 pane 粒度锁 token,冲突时警告/阻止)
|
|
27
|
+
- **角色档案**:`master` / `worker-default` 内置,自定义 role 按 `.pi-herdr/roles/<name>.json` 挂载;工具集按角色收敛(deny 规则不可绕过)
|
|
28
|
+
- **人类闸门**:子代理 `ask_user_question` → 侧边栏 blocked 标记 + 通知;用户手动接管(ESC 打断后输入)→ 启发式检测自动暂停/归还 master 管理
|
|
29
|
+
- **焦点热力布局**:聚焦 pane 原地放大(0.72 目标),blocked / ask / working / idle 按权重分大小,多余 pane 自动压成 title 条
|
|
30
|
+
- **结算通知折叠**:后台子代理结算不再攒到 run 结束洪水回填——turn 间隙注入,最多 3 条逐条展示、其余折叠指路
|
|
31
|
+
|
|
32
|
+
## 安装
|
|
33
|
+
|
|
34
|
+
### 环境要求
|
|
35
|
+
|
|
36
|
+
- Node ≥ 22
|
|
37
|
+
- pi ≥ 0.84(`@earendil-works/pi-coding-agent`)
|
|
38
|
+
- herdr ≥ 0.8.0(macOS / Linux / Windows;Windows 为 preview beta)
|
|
39
|
+
|
|
40
|
+
### 一键安装(推荐)
|
|
41
|
+
|
|
42
|
+
无需克隆仓库,直接用 npm 包运行:
|
|
43
|
+
|
|
44
|
+
```sh
|
|
45
|
+
npx pier-setup # 用户模式:pi install npm:pi-pier + herdr plugin install + 自动生成引导配置
|
|
46
|
+
npm i -g pier-setup # 或全局安装后直接跑 pier-setup
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
开发模式仍需克隆仓库:
|
|
50
|
+
|
|
51
|
+
```sh
|
|
52
|
+
git clone https://github.com/July24/pier && cd pier
|
|
53
|
+
node install.mjs install --dev # 本地路径 pi install + herdr plugin link,改码即生效
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
脚本自动校验环境(node / pi / herdr 版本)、探测 pi 的 node 与 cli.js 绝对路径、
|
|
57
|
+
生成 boot-config.json(用户模式落 herdr 插件配置目录,重装不丢),并注册两半区。
|
|
58
|
+
|
|
59
|
+
其他命令:`pier-setup update`(卸载+重装,重探测路径)、`pier-setup uninstall --purge`。
|
|
60
|
+
发行规格可用 `--pi-spec=` / `--herdr-spec=` 覆盖(npm 发布或 fork 场景)。
|
|
61
|
+
|
|
62
|
+
### 手动安装(等价步骤)
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
# pi 侧扩展(任选其一;npm 源 = 用户模式推荐,git 源 = 跟随仓库最新,本地路径 = 开发模式)
|
|
66
|
+
pi install npm:pi-pier # 用户模式(npm 发布版,自动出现在 pi.dev/packages 市场)
|
|
67
|
+
pi install git:github.com/July24/pier # 用户模式(跟随 main 最新)
|
|
68
|
+
pi install ./packages/pier-ext # 开发期
|
|
69
|
+
|
|
70
|
+
# herdr 侧插件
|
|
71
|
+
herdr plugin install July24/pier/packages/pier-workbench --yes # 用户模式(重装即更新)
|
|
72
|
+
herdr plugin link ./packages/pier-workbench # 开发期
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
扩展在非 herdr 环境下自动降级(详见下节"作用域")。
|
|
76
|
+
|
|
77
|
+
### 作用域(只影响 herdr × pi 交叉场景)
|
|
78
|
+
|
|
79
|
+
- **非 herdr 环境用 pi**:扩展自动降级——herdr 上报零成本,`subagent` / 终端族
|
|
80
|
+
工具返回"requires a herdr-managed pane"提示;`todo_write` / `ask_user_question`
|
|
81
|
+
照常可用(以会话 JSONL 为权威,无 herdr 依赖)。
|
|
82
|
+
- **herdr 中用其他 agent(claude code / codex 等)**:不含 pi pane 的 tab 不参与
|
|
83
|
+
热力布局;blocked 通知只发给 pi;主 tab 自动引导可用 boot-config 的
|
|
84
|
+
`autoBootstrap: false` 关闭。
|
|
85
|
+
|
|
86
|
+
### 引导配置(workbench 半区)
|
|
87
|
+
|
|
88
|
+
master 主 tab 引导需要本机 node / pi 路径:`node install.mjs` 自动生成;手动可复制模板
|
|
89
|
+
`packages/pier-workbench/scripts/boot-config.example.json`(含 macOS / Windows 双平台占位)。
|
|
90
|
+
用户模式配置在 `herdr plugin config-dir pier.workbench`;开发模式在 `packages/pier-workbench/scripts/boot-config.json`。
|
|
91
|
+
|
|
92
|
+
### 使用
|
|
93
|
+
|
|
94
|
+
1. 在 herdr workspace 里开 pane 跑 pi(pane 内 `pi`),扩展检测 `HERDR_ENV` 自动开始上报
|
|
95
|
+
2. 模型可调用 `todo_write`(窗格头实时投影)、`subagent`(前台/并行/后台)、`list_agents` / `send_message` / `interrupt_agent`
|
|
96
|
+
3. 每个子代理 = 独立 pane(可观察、可进入交互、blocked 时触发通知)
|
|
97
|
+
4. 重启恢复:herdr session 恢复自动重建 pane(子代理 rpc 会话文件已上报);父 pi 会话用 `/resume` 恢复
|
|
98
|
+
|
|
99
|
+
## 仓库结构
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
packages/
|
|
103
|
+
pier-ext/ # pi 扩展(npm: pi-pier):todo/subagent 工具、herdr 客户端、vocab 权威、skill
|
|
104
|
+
pier-workbench/ # herdr 插件(pier.workbench):主 tab 引导 + blocked 通知 + 热力布局
|
|
105
|
+
docs/ # 安装手册、role 档案说明、侧边栏 role 配置
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## 测试
|
|
109
|
+
|
|
110
|
+
```sh
|
|
111
|
+
npm install --ignore-scripts
|
|
112
|
+
npm test # node --test,244 项单测(规划器 / todo 重放 / 会话尾 / GC / 生命周期)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## 设计原则
|
|
116
|
+
|
|
117
|
+
- **会话 JSONL 是唯一权威**:todo / 委派 / 注册表全部回放自会话分支,重启与分支切换正确
|
|
118
|
+
- **事件驱动,不轮询**:状态经 herdr 事件订阅(`pane.agent_status_changed` / `pane.closed`)推送,仅事件触发时拉一次快照
|
|
119
|
+
- **零位置迁移**:herdr BSP 布局拓扑创建时定死,pier 只调 split ratio——pane 位置永不动,大小按优先级重算
|
|
120
|
+
- **尽力而为的投影层**:上报失败静默,绝不影响 pi 主流程
|
|
121
|
+
|
|
122
|
+
## License
|
|
123
|
+
|
|
124
|
+
MIT
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
> 💡 **开发说明**:`.gitignore` 刻意**不提交**到本仓库(本地资产忽略规则属个人环境)。贡献者请基于 `README.md` / `README.en.md` 自行维护忽略规则;`packages/pier-workbench/scripts/boot-config.json` 亦为本机配置(模板见 `.example.json`)。
|
|
129
|
+
>
|
|
130
|
+
> **命名约定**:品牌名 **pier**(仓库/包/插件),运行时协议标识保留 **`pi-herdr`** 前缀(`.pi-herdr/roles/` 目录、`pi-herdr.subs` 等会话 custom 条目、`~/.pi/agent/herdr-pi/roles/` 用户目录)——它们随用户会话/配置文件持久化,改动会破坏既有数据,属兼容层。
|
package/install.mjs
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* pier 一键安装/卸载/更新(跨平台:macOS / Linux / Windows)。
|
|
4
|
+
*
|
|
5
|
+
* 用法:
|
|
6
|
+
* node install.mjs install [--dev] [--hmr-dev] [--force] # 安装(默认命令,可省略 install)
|
|
7
|
+
* node install.mjs uninstall [--dev] [--purge] # 卸载;--purge 连 boot-config.json 一起删
|
|
8
|
+
* node install.mjs update [--dev] [--hmr-dev] [--force] # 更新 = 卸载注册 + 重新安装(重探测路径)
|
|
9
|
+
*
|
|
10
|
+
* 模式:
|
|
11
|
+
* 用户模式(默认):pi install git:github.com/July24/pier(仓库根 package.json 的 pi 字段)
|
|
12
|
+
* + herdr plugin install July24/pier/packages/pier-workbench --yes
|
|
13
|
+
* boot-config.json 写 herdr plugin config-dir(reinstall 不丢)。
|
|
14
|
+
* --dev 开发模式:pi install <repo>/packages/pier-ext + herdr plugin link <repo>/packages/pier-workbench
|
|
15
|
+
* (link 目录是活的,改码即生效;update 只重写 boot-config)。
|
|
16
|
+
*
|
|
17
|
+
* 发行规格可用 --pi-spec= / --herdr-spec= 覆盖(npm 发布或 fork 场景)。
|
|
18
|
+
* 失败语义:每步给出手动等价命令;步骤失败不阻断报告(exitCode=1)。
|
|
19
|
+
*/
|
|
20
|
+
import { execFileSync } from 'node:child_process';
|
|
21
|
+
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from 'node:fs';
|
|
22
|
+
import { dirname, join, resolve } from 'node:path';
|
|
23
|
+
import { fileURLToPath } from 'node:url';
|
|
24
|
+
|
|
25
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)));
|
|
26
|
+
const EXT_DIR = join(ROOT, 'packages', 'pier-ext');
|
|
27
|
+
const WB_DIR = join(ROOT, 'packages', 'pier-workbench');
|
|
28
|
+
const DEV_BOOT_CONFIG = join(WB_DIR, 'scripts', 'boot-config.json');
|
|
29
|
+
const EXT_PATH = join(EXT_DIR, 'src', 'index.ts');
|
|
30
|
+
const IS_WIN = process.platform === 'win32';
|
|
31
|
+
|
|
32
|
+
const argv = process.argv.slice(2);
|
|
33
|
+
const command = ['install', 'uninstall', 'update'].includes(argv[0]) ? argv.shift() : 'install';
|
|
34
|
+
const flags = new Set(argv.filter((a) => a.startsWith('--') && !a.includes('=')));
|
|
35
|
+
const optValue = (name) => argv.find((a) => a.startsWith(`--${name}=`))?.split('=').slice(1).join('=') ?? null;
|
|
36
|
+
const dev = flags.has('--dev');
|
|
37
|
+
const PI_SPEC = optValue('pi-spec') ?? 'npm:pi-pier'; // 用户模式默认 npm 发行版;--pi-spec=git:github.com/July24/pier 可跟随仓库 main
|
|
38
|
+
const HERDR_SPEC = optValue('herdr-spec') ?? 'July24/pier/packages/pier-workbench';
|
|
39
|
+
|
|
40
|
+
const log = (m) => console.log(m);
|
|
41
|
+
const die = (m) => { console.error(`✗ ${m}`); process.exit(1); };
|
|
42
|
+
|
|
43
|
+
/* ── 工具函数 ────────────────────────────────────────────────────── */
|
|
44
|
+
function which(bin) {
|
|
45
|
+
try {
|
|
46
|
+
const out = execFileSync(IS_WIN ? 'where' : 'which', [bin], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
47
|
+
// where 可能多行(PATH 逐条命中),取第一条存在的
|
|
48
|
+
for (const line of out.split('\n').map((l) => l.trim()).filter(Boolean)) {
|
|
49
|
+
if (existsSync(line)) return line;
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function run(cmd, args) {
|
|
58
|
+
return execFileSync(cmd, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function tryRun(cmd, args) {
|
|
62
|
+
try { run(cmd, args); return true; } catch (e) { log(` (${e.message.split('\n')[0]})`); return false; }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseVersion(s) {
|
|
66
|
+
const m = String(s).match(/(\d+)\.(\d+)\.(\d+)/);
|
|
67
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function geVersion(v, min) {
|
|
71
|
+
for (let i = 0; i < 3; i++) {
|
|
72
|
+
if (v[i] !== min[i]) return v[i] > min[i];
|
|
73
|
+
}
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** herdr 插件配置目录(用户模式 boot-config 落点;插件已注册才存在路径语义)。 */
|
|
78
|
+
function herdrConfigDir() {
|
|
79
|
+
try { return run('herdr', ['plugin', 'config-dir', 'pier.workbench']); } catch { return null; }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* ── 环境校验(install 前置) ────────────────────────────────────── */
|
|
83
|
+
function checkEnv() {
|
|
84
|
+
const [major] = process.versions.node.split('.').map(Number);
|
|
85
|
+
if (major < 22) die(`node ≥ 22 required, found ${process.versions.node}`);
|
|
86
|
+
log(`✓ node ${process.versions.node}`);
|
|
87
|
+
const piVersion = (() => { try { return parseVersion(run('pi', ['--version'])); } catch { return null; } })();
|
|
88
|
+
if (!piVersion || !geVersion(piVersion, [0, 84, 0])) {
|
|
89
|
+
die(`pi ≥ 0.84 required${piVersion ? `, found ${piVersion.join('.')}` : ' (pi not found or not runnable)'}. Install: npm i -g @earendil-works/pi-coding-agent`);
|
|
90
|
+
}
|
|
91
|
+
log(`✓ pi ${piVersion.join('.')}`);
|
|
92
|
+
const herdrVersion = (() => { try { return parseVersion(run('herdr', ['--version'])); } catch { return null; } })();
|
|
93
|
+
if (!herdrVersion || !geVersion(herdrVersion, [0, 8, 0])) {
|
|
94
|
+
die(`herdr ≥ 0.8.0 required${herdrVersion ? `, found ${herdrVersion.join('.')}` : ' (herdr not found)'}. Install: https://herdr.dev/`);
|
|
95
|
+
}
|
|
96
|
+
log(`✓ herdr ${herdrVersion.join('.')}`);
|
|
97
|
+
return { piVersion, herdrVersion };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** pi 半区源:用户模式 git 规格,dev 模式本地目录。 */
|
|
101
|
+
const piSource = () => (dev ? EXT_DIR : PI_SPEC);
|
|
102
|
+
|
|
103
|
+
/* ── boot-config 探测与写入 ──────────────────────────────────────── */
|
|
104
|
+
function probePiRuntime() {
|
|
105
|
+
const piBin = which('pi');
|
|
106
|
+
// bin 是 node shim:realpath 落到 .../pi-coding-agent/dist/cli.js(npm 布局,win/posix 同构)
|
|
107
|
+
if (piBin) {
|
|
108
|
+
try {
|
|
109
|
+
const real = realpathSync(piBin);
|
|
110
|
+
if (real.endsWith('cli.js') && existsSync(real)) {
|
|
111
|
+
return { piCli: real, piNode: process.execPath };
|
|
112
|
+
}
|
|
113
|
+
} catch { /* 落到兜底 */ }
|
|
114
|
+
}
|
|
115
|
+
// npm root -g 兜底:pi 不在 PATH(nvm shim 等)时按全局布局拼
|
|
116
|
+
try {
|
|
117
|
+
const gRoot = run('npm', ['root', '-g']);
|
|
118
|
+
const cand = join(gRoot, '@earendil-works', 'pi-coding-agent', 'dist', 'cli.js');
|
|
119
|
+
if (existsSync(cand)) return { piCli: cand, piNode: process.execPath };
|
|
120
|
+
} catch { /* npm 不可用 */ }
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function writeBootConfig(hmrDev, force) {
|
|
125
|
+
const probed = probePiRuntime();
|
|
126
|
+
if (!probed) die('cannot locate pi cli.js (tried: pi shim realpath, npm root -g). Fill boot-config.json manually.');
|
|
127
|
+
if (!existsSync(EXT_PATH)) die(`pier-ext entry missing: ${EXT_PATH} (repo layout broken?)`);
|
|
128
|
+
// 用户模式 extPath 必须指向 herdr 管理的 checkout 内的扩展(安装 herdr 半区后才知道);
|
|
129
|
+
// git 安装布局与本地同构:packages/pier-ext/src/index.ts。
|
|
130
|
+
const extPath = dev ? EXT_PATH
|
|
131
|
+
: join(herdrManagedRootGuess(), 'packages', 'pier-ext', 'src', 'index.ts');
|
|
132
|
+
|
|
133
|
+
const config = {
|
|
134
|
+
mainTabLabel: 'main',
|
|
135
|
+
piNode: probed.piNode,
|
|
136
|
+
piCli: probed.piCli,
|
|
137
|
+
extPath,
|
|
138
|
+
workbenchPluginId: 'pier.workbench',
|
|
139
|
+
hmrDev,
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const target = dev ? DEV_BOOT_CONFIG : join(herdrConfigDir() ?? die('cannot resolve herdr plugin config-dir (is pier.workbench installed?)'), 'boot-config.json');
|
|
143
|
+
if (existsSync(target) && !force) {
|
|
144
|
+
let existing = null;
|
|
145
|
+
try { existing = JSON.parse(readFileSync(target, 'utf8')); } catch { /* 视为损坏 */ }
|
|
146
|
+
if (existing) {
|
|
147
|
+
log(`• ${target} exists, diff (existing → new):`);
|
|
148
|
+
for (const k of Object.keys(config)) {
|
|
149
|
+
if (existing[k] !== config[k]) log(` ${k}: ${JSON.stringify(existing[k])} → ${JSON.stringify(config[k])}`);
|
|
150
|
+
}
|
|
151
|
+
log(' kept existing (use --force to overwrite)');
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
156
|
+
writeFileSync(target, JSON.stringify(config, null, 2) + '\n');
|
|
157
|
+
log(`✓ boot-config.json ${force ? 'overwritten (--force)' : 'written'} → ${target}`);
|
|
158
|
+
log(` piNode = ${config.piNode}`);
|
|
159
|
+
log(` piCli = ${config.piCli}`);
|
|
160
|
+
log(` extPath = ${config.extPath}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** herdr 管理的插件 checkout 根(GitHub 安装布局:…/plugins/<owner>-<repo>/…)。 */
|
|
164
|
+
function herdrManagedRootGuess() {
|
|
165
|
+
const dir = herdrConfigDir();
|
|
166
|
+
if (!dir) return '';
|
|
167
|
+
// config-dir 形如 <plugins-root>/<plugin-id>/config → checkout 在 <plugins-root>/<plugin-id>/
|
|
168
|
+
return resolve(dir, '..');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/* ── 三命令 ──────────────────────────────────────────────────────── */
|
|
172
|
+
function install() {
|
|
173
|
+
const hmrDev = flags.has('--hmr-dev');
|
|
174
|
+
const force = flags.has('--force');
|
|
175
|
+
checkEnv();
|
|
176
|
+
|
|
177
|
+
if (dev) {
|
|
178
|
+
if (tryRun('pi', ['install', piSource()])) log('✓ pi extension installed (packages/pier-ext, local)');
|
|
179
|
+
else { console.error('✗ pi install failed'); log(` manual: pi install ${EXT_DIR}`); process.exitCode = 1; }
|
|
180
|
+
if (tryRun('herdr', ['plugin', 'link', WB_DIR])) log('✓ herdr plugin linked (packages/pier-workbench, local)');
|
|
181
|
+
else { console.error('✗ herdr plugin link failed'); log(` manual: herdr plugin link ${WB_DIR}`); process.exitCode = 1; }
|
|
182
|
+
writeBootConfig(hmrDev, force);
|
|
183
|
+
} else {
|
|
184
|
+
if (tryRun('pi', ['install', piSource()])) log(`✓ pi extension installed (${PI_SPEC})`);
|
|
185
|
+
else { console.error('✗ pi install failed'); log(` manual: pi install ${PI_SPEC}`); process.exitCode = 1; }
|
|
186
|
+
if (tryRun('herdr', ['plugin', 'install', HERDR_SPEC, '--yes'])) log(`✓ herdr plugin installed (${HERDR_SPEC})`);
|
|
187
|
+
else { console.error('✗ herdr plugin install failed'); log(` manual: herdr plugin install ${HERDR_SPEC}`); process.exitCode = 1; }
|
|
188
|
+
writeBootConfig(hmrDev, force);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
log(`
|
|
192
|
+
pier installed (${dev ? 'dev mode: local paths, code changes are live' : 'user mode: managed checkouts'}). Next:
|
|
193
|
+
1. start herdr, open/create a workspace — the main tab auto-bootstraps a pi session
|
|
194
|
+
2. inside pi, ask the model to use todo_write / subagent tools`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function uninstall() {
|
|
198
|
+
const purge = flags.has('--purge');
|
|
199
|
+
if (tryRun('pi', ['remove', piSource()])) log('✓ pi extension removed');
|
|
200
|
+
else { console.error('✗ pi remove failed'); log(` manual: pi remove ${piSource()}`); process.exitCode = 1; }
|
|
201
|
+
|
|
202
|
+
if (dev) {
|
|
203
|
+
if (tryRun('herdr', ['plugin', 'unlink', 'pier.workbench'])) log('✓ herdr plugin unlinked (files kept)');
|
|
204
|
+
else { console.error('✗ herdr plugin unlink failed'); log(' manual: herdr plugin unlink pier.workbench'); process.exitCode = 1; }
|
|
205
|
+
if (purge && existsSync(DEV_BOOT_CONFIG)) { rmSync(DEV_BOOT_CONFIG); log('✓ dev boot-config.json purged'); }
|
|
206
|
+
} else {
|
|
207
|
+
if (tryRun('herdr', ['plugin', 'uninstall', 'pier.workbench'])) log('✓ herdr plugin uninstalled (managed checkout removed)');
|
|
208
|
+
else { console.error('✗ herdr plugin uninstall failed'); log(' manual: herdr plugin uninstall pier.workbench'); process.exitCode = 1; }
|
|
209
|
+
if (purge) {
|
|
210
|
+
const dir = herdrConfigDir();
|
|
211
|
+
const f = dir ? join(dir, 'boot-config.json') : null;
|
|
212
|
+
if (f && existsSync(f)) { rmSync(f); log('✓ boot-config.json purged'); }
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
log(`\npier uninstalled.${purge ? '' : ' (boot-config.json kept; use --purge to remove)'}`);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (command === 'uninstall') uninstall();
|
|
219
|
+
else if (command === 'update') { log('== uninstall =='); uninstall(); log('\n== install =='); install(); }
|
|
220
|
+
else install();
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pier-setup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "One-shot installer for pier: registers the pi-pier extension with pi and the pier.workbench plugin with herdr (install / uninstall / update)",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pier-setup": "./install.mjs"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"install.mjs"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/July24/pier.git"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/July24/pier#readme",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/July24/pier/issues"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"pier",
|
|
26
|
+
"installer",
|
|
27
|
+
"herdr",
|
|
28
|
+
"coding-agent"
|
|
29
|
+
],
|
|
30
|
+
"workspaces": [
|
|
31
|
+
"packages/*"
|
|
32
|
+
],
|
|
33
|
+
"scripts": {
|
|
34
|
+
"test": "node --test \"packages/*/test/*.test.ts\""
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@deepseek-ai/cordis-plugin-group": "^1.0.1",
|
|
38
|
+
"@deepseek-ai/cordis-plugin-hmr": "^1.0.16",
|
|
39
|
+
"@deepseek-ai/cordis-plugin-loader": "^1.0.2",
|
|
40
|
+
"@deepseek-ai/cordis-plugin-timer": "^1.1.3"
|
|
41
|
+
},
|
|
42
|
+
"pi": {
|
|
43
|
+
"extensions": [
|
|
44
|
+
"./packages/pier-ext/src/index.ts"
|
|
45
|
+
],
|
|
46
|
+
"skills": [
|
|
47
|
+
"./packages/pier-ext/skills/pi-herdr-workflow"
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
}
|