pi-lazy-setup 2.0.0 → 3.0.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 +7 -4
- package/bin/pi-setup.mjs +5 -1
- package/configs/pi/extensions/pi-setup.js +228 -0
- package/package.json +1 -1
- package/src/agents/pi.mjs +6 -9
- package/src/agents/types.mjs +1 -32
- package/src/commands/init.mjs +25 -123
- package/src/agents/packages.mjs +0 -28
- package/src/agents/skills.mjs +0 -19
- package/src/lib/exec.mjs +0 -40
package/README.md
CHANGED
|
@@ -8,10 +8,13 @@ npx pi-lazy-setup
|
|
|
8
8
|
|
|
9
9
|
## What it does
|
|
10
10
|
|
|
11
|
-
1. **
|
|
12
|
-
2. **Deploys
|
|
11
|
+
1. **Deploys configs** — overwrites `~/.pi/agent` from bundled `configs/pi/` (binary install is out of scope: `pi` must already be on PATH).
|
|
12
|
+
2. **Deploys the `/pi-setup` extension** — copies `configs/pi/extensions/pi-setup.js` into `~/.pi/agent/extensions/` so Pi auto-discovers it on the next start.
|
|
13
13
|
3. **Deploys reference docs** — copies into `~/.pi/agent/pi-setup/`.
|
|
14
|
-
|
|
14
|
+
|
|
15
|
+
Then, inside Pi, run `/pi-setup`: it interactively installs **packages** (`pi install <source>`), **skills** (`npx skills add`, https://skills.sh/), and **CLI tools** (officecli: skill file from <https://officecli.ai/SKILL.md> + binary via `curl -fsSL https://d.officecli.ai/install.sh | bash`). Every item is confirmed individually, and already-installed items are skipped. Pattern follows the `/rpiv-setup` command of `@juicesharp/rpiv-pi`.
|
|
16
|
+
|
|
17
|
+
The candidate lists (packages, skills, CLI tools) live as snapshot data inside the extension file itself.
|
|
15
18
|
|
|
16
19
|
## Flags
|
|
17
20
|
|
|
@@ -27,7 +30,7 @@ This is a **snapshot**, not a generic installer. Configs ship exactly as the aut
|
|
|
27
30
|
|
|
28
31
|
## Scope
|
|
29
32
|
|
|
30
|
-
**Pi** (<https://github.com/mariozechner/pi-coding-agent>).
|
|
33
|
+
**Pi** (<https://github.com/mariozechner/pi-coding-agent>). The CLI installs nothing: it only deploys the declaration file (`mcp.json`) and config files (`settings.json`, `models.json`, `AGENTS.md`, `prompts/`, `extensions/pi-setup.js`). MCP server binaries are not installed either. Everything interactive (packages, skills, CLI tools) happens via `/pi-setup` inside Pi.
|
|
31
34
|
|
|
32
35
|
For skill management, see [@kkmila/isc](https://github.com/machuqiji/isc).
|
|
33
36
|
|
package/bin/pi-setup.mjs
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
// pi-setup — initialize the Pi agent environment from a snapshot.
|
|
4
4
|
//
|
|
5
|
+
// Deploy-only: no binaries, packages, or skills are installed here. Instead
|
|
6
|
+
// the /pi-setup extension lands in ~/.pi/agent/extensions/ — run `/pi-setup`
|
|
7
|
+
// inside Pi to install packages, skills, and CLI tools interactively
|
|
8
|
+
// (each item confirmed individually).
|
|
9
|
+
//
|
|
5
10
|
// Usage:
|
|
6
11
|
// pi-setup run directly (all = pi)
|
|
7
12
|
// pi-setup pi explicit
|
|
@@ -27,7 +32,6 @@ program
|
|
|
27
32
|
const code = await initialize({
|
|
28
33
|
dryRun: !!opts.dryRun,
|
|
29
34
|
backup: !!opts.backup,
|
|
30
|
-
tty: !!process.stdin.isTTY,
|
|
31
35
|
agents: chosen,
|
|
32
36
|
});
|
|
33
37
|
process.exit(code);
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
// /pi-setup — interactive installer for Pi packages, skills, and CLI tools.
|
|
2
|
+
//
|
|
3
|
+
// Deployed by the pi-setup CLI (npx pi-setup) into ~/.pi/agent/extensions/pi-setup.js.
|
|
4
|
+
// Run `/pi-setup` inside Pi: every candidate is confirmed individually, and
|
|
5
|
+
// already-installed items are skipped automatically. Pattern follows the
|
|
6
|
+
// /rpiv-setup command of @juicesharp/rpiv-pi (serial installs, per-item
|
|
7
|
+
// confirm, succeeded/failed report, restart hint).
|
|
8
|
+
//
|
|
9
|
+
// Candidates (snapshot data, kept in sync with the owner's setup):
|
|
10
|
+
// packages → `pi install <source>` (writes into ~/.pi/agent/settings.json)
|
|
11
|
+
// skills → `npx --yes skills add <repo> --skill <name> -g --agent pi -y`
|
|
12
|
+
// clis → officecli: skill file from https://officecli.ai/SKILL.md plus
|
|
13
|
+
// binary via `curl -fsSL https://d.officecli.ai/install.sh | bash`
|
|
14
|
+
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
16
|
+
import { homedir } from "node:os";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
|
|
19
|
+
// Resolved lazily (not at module load) so tests can redirect HOME.
|
|
20
|
+
function piSettingsPath() {
|
|
21
|
+
return join(homedir(), ".pi", "agent", "settings.json");
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function globalSkillsDir() {
|
|
25
|
+
return join(homedir(), ".agents", "skills");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const INSTALL_TIMEOUT_MS = 300_000;
|
|
29
|
+
const BIN_CHECK_TIMEOUT_MS = 15_000;
|
|
30
|
+
const STDERR_SNIPPET_CHARS = 300;
|
|
31
|
+
|
|
32
|
+
/** Pi extension packages offered via `pi install <source>`. */
|
|
33
|
+
const PACKAGES = [
|
|
34
|
+
{ source: "npm:pi-footer", description: "Footer status line (token usage, model, cost)." },
|
|
35
|
+
{ source: "npm:pi-tps-meter", description: "Tokens-per-second meter for streaming responses." },
|
|
36
|
+
{ source: "npm:@juicesharp/rpiv-pi", description: "RPIV pipeline skills (code review, commit, handoff, diagrams)." },
|
|
37
|
+
{ source: "https://github.com/cathrynlavery/diagram-design", description: "Branded diagram/chart generation skills." },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
/** Skills offered via `npx skills add` (https://skills.sh/). */
|
|
41
|
+
const SKILLS = [
|
|
42
|
+
{
|
|
43
|
+
repo: "mattpocock/skills",
|
|
44
|
+
name: "grill-me",
|
|
45
|
+
description: "Matt Pocock — a relentless interview to sharpen a plan or design.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
repo: "microsoft/playwright-cli",
|
|
49
|
+
name: "playwright-cli",
|
|
50
|
+
description: "Microsoft — browser automation and Playwright test driving.",
|
|
51
|
+
},
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
/** CLI tools. Each entry installs two parts: a skill file and the binary itself. */
|
|
55
|
+
const CLIS = [
|
|
56
|
+
{
|
|
57
|
+
id: "officecli",
|
|
58
|
+
bin: "officecli",
|
|
59
|
+
description: "AI-friendly CLI for Office documents (.docx/.xlsx/.pptx).",
|
|
60
|
+
skillFile: { name: "officecli", url: "https://officecli.ai/SKILL.md" },
|
|
61
|
+
installCommand: "curl -fsSL https://d.officecli.ai/install.sh | bash",
|
|
62
|
+
},
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
export default function piSetupExtension(pi) {
|
|
66
|
+
pi.registerCommand("pi-setup", {
|
|
67
|
+
description: "Install packages, skills, and CLI tools (each confirmed individually)",
|
|
68
|
+
handler: async (_args, ctx) => {
|
|
69
|
+
if (!ctx.hasUI) {
|
|
70
|
+
ctx.ui.notify("/pi-setup requires interactive mode", "error");
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const report = { installed: [], skipped: [], failed: [] };
|
|
74
|
+
await installPackages(pi, ctx, report);
|
|
75
|
+
await installSkills(pi, ctx, report);
|
|
76
|
+
await installClis(pi, ctx, report);
|
|
77
|
+
ctx.ui.notify(buildReport(report), report.failed.length > 0 ? "warning" : "info");
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Packages section: offer each undeclared package via confirm, then `pi install`. */
|
|
83
|
+
async function installPackages(pi, ctx, report) {
|
|
84
|
+
const declared = declaredPackageSources();
|
|
85
|
+
for (const p of PACKAGES) {
|
|
86
|
+
if (declared.has(p.source)) {
|
|
87
|
+
report.skipped.push(p.source);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const confirmed = await ctx.ui.confirm("Install Pi package?", `${p.source}\n${p.description}`);
|
|
91
|
+
if (!confirmed) continue;
|
|
92
|
+
ctx.ui.notify(`Installing ${p.source}…`, "info");
|
|
93
|
+
const result = await runExec(pi, "pi", ["install", p.source]);
|
|
94
|
+
settle(result, p.source, report);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Skills section: offer each missing skill via confirm, then `npx skills add`. */
|
|
99
|
+
async function installSkills(pi, ctx, report) {
|
|
100
|
+
for (const s of SKILLS) {
|
|
101
|
+
if (skillInstalled(s.name)) {
|
|
102
|
+
report.skipped.push(`skill:${s.name}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const confirmed = await ctx.ui.confirm("Install skill?", `${s.repo}@${s.name}\n${s.description}`);
|
|
106
|
+
if (!confirmed) continue;
|
|
107
|
+
ctx.ui.notify(`Installing skill ${s.name}…`, "info");
|
|
108
|
+
const result = await runExec(pi, "npx", [
|
|
109
|
+
"--yes",
|
|
110
|
+
"skills",
|
|
111
|
+
"add",
|
|
112
|
+
s.repo,
|
|
113
|
+
"--skill",
|
|
114
|
+
s.name,
|
|
115
|
+
"-g",
|
|
116
|
+
"--agent",
|
|
117
|
+
"pi",
|
|
118
|
+
"-y",
|
|
119
|
+
]);
|
|
120
|
+
settle(result, `skill:${s.name}`, report);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* CLI tools section. Each entry installs two parts — the skill file and the
|
|
126
|
+
* binary — and each part is confirmed individually.
|
|
127
|
+
*/
|
|
128
|
+
async function installClis(pi, ctx, report) {
|
|
129
|
+
for (const c of CLIS) {
|
|
130
|
+
if (c.skillFile) {
|
|
131
|
+
const { name, url } = c.skillFile;
|
|
132
|
+
if (skillInstalled(name)) {
|
|
133
|
+
report.skipped.push(`skill:${name}`);
|
|
134
|
+
} else {
|
|
135
|
+
const confirmed = await ctx.ui.confirm(
|
|
136
|
+
"Install skill file?",
|
|
137
|
+
`${url} → ~/.agents/skills/${name}/SKILL.md\n${c.description}`,
|
|
138
|
+
);
|
|
139
|
+
if (confirmed) {
|
|
140
|
+
ctx.ui.notify(`Downloading ${url}…`, "info");
|
|
141
|
+
try {
|
|
142
|
+
await downloadSkillFile(name, url);
|
|
143
|
+
report.installed.push(`skill:${name}`);
|
|
144
|
+
} catch (err) {
|
|
145
|
+
report.failed.push({ item: `skill:${name}`, error: toMessage(err) });
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (await binaryAvailable(pi, c.bin)) {
|
|
151
|
+
report.skipped.push(`cli:${c.bin}`);
|
|
152
|
+
} else {
|
|
153
|
+
const confirmed = await ctx.ui.confirm("Install CLI tool?", `${c.id} — ${c.description}\n${c.installCommand}`);
|
|
154
|
+
if (confirmed) {
|
|
155
|
+
ctx.ui.notify(`Installing ${c.id}…`, "info");
|
|
156
|
+
const result = await runExec(pi, "bash", ["-c", c.installCommand]);
|
|
157
|
+
settle(result, `cli:${c.bin}`, report);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** Run a command via pi.exec and normalize into { ok, error }. */
|
|
164
|
+
async function runExec(pi, command, args, timeoutMs = INSTALL_TIMEOUT_MS) {
|
|
165
|
+
try {
|
|
166
|
+
const result = await pi.exec(command, args, { timeout: timeoutMs });
|
|
167
|
+
if (result.code === 0) return { ok: true };
|
|
168
|
+
const error = (result.stderr || result.stdout || `exit ${result.code}`).trim().slice(0, STDERR_SNIPPET_CHARS);
|
|
169
|
+
return { ok: false, error };
|
|
170
|
+
} catch (err) {
|
|
171
|
+
return { ok: false, error: toMessage(err) };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function settle(result, item, report) {
|
|
176
|
+
if (result.ok) report.installed.push(item);
|
|
177
|
+
else report.failed.push({ item, error: result.error });
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function declaredPackageSources() {
|
|
181
|
+
try {
|
|
182
|
+
const settings = JSON.parse(readFileSync(piSettingsPath(), "utf8"));
|
|
183
|
+
return new Set((settings.packages ?? []).map((p) => (typeof p === "string" ? p : p.source)));
|
|
184
|
+
} catch {
|
|
185
|
+
return new Set();
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Whether a skill directory with SKILL.md exists in ~/.agents/skills/. */
|
|
190
|
+
function skillInstalled(name) {
|
|
191
|
+
return existsSync(join(globalSkillsDir(), name, "SKILL.md"));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Whether a binary responds to --version. */
|
|
195
|
+
async function binaryAvailable(pi, name) {
|
|
196
|
+
const result = await runExec(pi, name, ["--version"], BIN_CHECK_TIMEOUT_MS);
|
|
197
|
+
return result.ok;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Download a SKILL.md into ~/.agents/skills/<name>/SKILL.md. */
|
|
201
|
+
async function downloadSkillFile(name, url) {
|
|
202
|
+
const res = await fetch(url);
|
|
203
|
+
if (!res.ok) throw new Error(`HTTP ${res.status} fetching ${url}`);
|
|
204
|
+
const text = await res.text();
|
|
205
|
+
const dir = join(globalSkillsDir(), name);
|
|
206
|
+
mkdirSync(dir, { recursive: true });
|
|
207
|
+
writeFileSync(join(dir, "SKILL.md"), text);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toMessage(err) {
|
|
211
|
+
return String(err?.message ?? err);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function buildReport(report) {
|
|
215
|
+
const lines = [];
|
|
216
|
+
if (report.installed.length > 0) lines.push(`✓ Installed: ${report.installed.join(", ")}`);
|
|
217
|
+
if (report.skipped.length > 0) lines.push(`• Already installed (skipped): ${report.skipped.join(", ")}`);
|
|
218
|
+
if (report.failed.length > 0) {
|
|
219
|
+
lines.push("✗ Failed:");
|
|
220
|
+
for (const f of report.failed) lines.push(` ${f.item}: ${f.error}`);
|
|
221
|
+
}
|
|
222
|
+
if (report.installed.length > 0) {
|
|
223
|
+
lines.push("");
|
|
224
|
+
lines.push("Restart Pi (or run /reload) to load the newly installed packages and skills.");
|
|
225
|
+
}
|
|
226
|
+
if (lines.length === 0) lines.push("Nothing to install.");
|
|
227
|
+
return lines.join("\n");
|
|
228
|
+
}
|
package/package.json
CHANGED
package/src/agents/pi.mjs
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
// pi.mjs — Pi agent descriptor.
|
|
2
2
|
// Pure data; the initializer in src/commands/init.mjs loops over a list of
|
|
3
3
|
// these. Paths with ~ are expanded at runtime by the initializer.
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
//
|
|
5
|
+
// The CLI only deploys the config snapshot and the /pi-setup extension
|
|
6
|
+
// (deploy.extensionFiles). Installing the pi binary, packages, skills, and
|
|
7
|
+
// CLI tools is out of scope — `/pi-setup` handles those interactively inside
|
|
8
|
+
// Pi, each item confirmed individually.
|
|
6
9
|
|
|
7
10
|
/** @type {import('./types.mjs').AgentDescriptor} */
|
|
8
11
|
export const pi = {
|
|
@@ -11,17 +14,11 @@ export const pi = {
|
|
|
11
14
|
home: "~/.pi/agent",
|
|
12
15
|
configSource: "configs/pi",
|
|
13
16
|
referenceDest: "~/.pi/agent/pi-setup",
|
|
14
|
-
install: {
|
|
15
|
-
kind: "npm",
|
|
16
|
-
pkg: "@mariozechner/pi-coding-agent",
|
|
17
|
-
bin: "pi",
|
|
18
|
-
},
|
|
19
17
|
deploy: {
|
|
20
18
|
overwriteFiles: ["settings.json", "mcp.json", "models.json", "AGENTS.md"],
|
|
21
19
|
replaceDirs: ["prompts"],
|
|
20
|
+
extensionFiles: ["pi-setup.js"],
|
|
22
21
|
overwriteExtra: [{ file: "web-config.json", dest: "~/.config/rpiv-web-tools/config.json" }],
|
|
23
22
|
referenceSource: "configs/reference",
|
|
24
23
|
},
|
|
25
|
-
skills: { specs: DEFAULT_SKILLS, cliAgent: "pi" },
|
|
26
|
-
packages: { specs: DEFAULT_PACKAGES },
|
|
27
24
|
};
|
package/src/agents/types.mjs
CHANGED
|
@@ -1,43 +1,13 @@
|
|
|
1
1
|
// types.mjs — JSDoc type shapes for agent descriptors.
|
|
2
2
|
// No runtime exports; used only for type hints in editors.
|
|
3
3
|
|
|
4
|
-
/**
|
|
5
|
-
* @typedef {Object} InstallSpec
|
|
6
|
-
* @property {"npm"} kind
|
|
7
|
-
* @property {string} [pkg] — npm package name
|
|
8
|
-
* @property {string} bin — binary name to detect on PATH
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
4
|
/**
|
|
12
5
|
* @typedef {Object} DeploySpec
|
|
13
6
|
* @property {string[]} overwriteFiles — files copied flat into home
|
|
14
7
|
* @property {string[]} replaceDirs — subdirs rm-rf'd then fully replaced
|
|
8
|
+
* @property {string[]} [extensionFiles] — extension files copied flat into `<home>/extensions/`
|
|
15
9
|
* @property {{ file: string, dest: string }[]} [overwriteExtra] — files copied to a ~-prefixed dest outside home
|
|
16
10
|
* @property {string} referenceSource — package-relative path to reference docs
|
|
17
|
-
* @property {SkillsSpec} [skills] — default skills to install from remote
|
|
18
|
-
* @property {PackagesSpec} [packages] — extension packages offered via `pi install`
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @typedef {Object} SkillSpec
|
|
23
|
-
* @property {string} spec — "owner/repo@skill" identifier
|
|
24
|
-
* @property {string} [description] — shown in the interactive skill picker
|
|
25
|
-
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @typedef {Object} SkillsSpec
|
|
29
|
-
* @property {SkillSpec[]} specs — skills offered for install (user selects)
|
|
30
|
-
* @property {string} [cliAgent] — lowercase skills-CLI agent name; install via `npx skills add`
|
|
31
|
-
*/
|
|
32
|
-
/**
|
|
33
|
-
* @typedef {Object} PackageSpec
|
|
34
|
-
* @property {string} source — package source passed to `pi install`
|
|
35
|
-
* @property {string} [description] — shown in the interactive package picker
|
|
36
|
-
*/
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @typedef {Object} PackagesSpec
|
|
40
|
-
* @property {PackageSpec[]} specs — packages offered for install (user selects)
|
|
41
11
|
*/
|
|
42
12
|
|
|
43
13
|
/**
|
|
@@ -47,7 +17,6 @@
|
|
|
47
17
|
* @property {string} home — ~-prefixed agent home
|
|
48
18
|
* @property {string} configSource — package-relative path to agent configs
|
|
49
19
|
* @property {string} referenceDest — ~-prefixed destination for reference docs
|
|
50
|
-
* @property {InstallSpec} install
|
|
51
20
|
* @property {DeploySpec} deploy
|
|
52
21
|
*/
|
|
53
22
|
|
package/src/commands/init.mjs
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
// init.mjs — the initializer.
|
|
2
|
-
// Loops over the agent descriptors and runs detect →
|
|
3
|
-
// → deploy-configs → deploy-reference
|
|
4
|
-
// → select-skills → install-skills.
|
|
2
|
+
// Loops over the agent descriptors and runs detect → (backup)
|
|
3
|
+
// → deploy-configs → deploy-reference.
|
|
5
4
|
// Each step honors { dryRun }.
|
|
5
|
+
//
|
|
6
|
+
// Binaries, packages, skills, and CLI tools are NOT installed here. Instead
|
|
7
|
+
// the /pi-setup extension is deployed (see deploy.extensions) — run `/pi-setup`
|
|
8
|
+
// inside Pi to install those interactively, each item confirmed individually.
|
|
6
9
|
|
|
7
10
|
import { homedir } from "node:os";
|
|
8
11
|
import { resolve } from "node:path";
|
|
9
|
-
import { checkbox } from "@inquirer/prompts";
|
|
10
12
|
import { pi } from "../agents/pi.mjs";
|
|
11
|
-
import { detectBinary, runCommand } from "../lib/exec.mjs";
|
|
12
13
|
import { backupToTar, copyDir, copyDirFiles, copyFile, ensureDir } from "../lib/fs.mjs";
|
|
13
14
|
import { log } from "../lib/log.mjs";
|
|
14
15
|
import { expandHome, packageRoot, pkgPath } from "../lib/paths.mjs";
|
|
@@ -24,32 +25,9 @@ function backupDirFor() {
|
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
|
|
30
|
-
async function installStep(agent, { dryRun }) {
|
|
31
|
-
const present = dryRun ? false : detectBinary(agent.install.bin);
|
|
32
|
-
if (present) {
|
|
33
|
-
if (!dryRun) log.skip(agent.name);
|
|
34
|
-
else log.dry(`detect ${agent.install.bin} (assume present → skip)`);
|
|
35
|
-
return 0;
|
|
36
|
-
}
|
|
37
|
-
// install
|
|
38
|
-
const cmdline = installCmdline(agent.install);
|
|
39
|
-
if (dryRun) {
|
|
40
|
-
log.dry(`detect ${agent.install.bin} → missing → install`);
|
|
41
|
-
}
|
|
42
|
-
log.install(agent.name);
|
|
43
|
-
return runCommand(cmdline, { dryRun });
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function installCmdline(spec) {
|
|
47
|
-
if (spec.kind === "npm") return `npm install -g ${spec.pkg}`;
|
|
48
|
-
throw new Error(`unknown install kind: ${spec.kind}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Deploy agent configs: overwrite flat files, replace subdirs wholesale.
|
|
28
|
+
* Deploy step: copy the agent snapshot into its home. Flat files are
|
|
29
|
+
* overwritten, subdirs replaced wholesale, extension files deployed so they
|
|
30
|
+
* auto-load on the next Pi start. No binary installs happen here.
|
|
53
31
|
*/
|
|
54
32
|
function deployConfigs(agent, pkgRoot, { dryRun }) {
|
|
55
33
|
const home = expandHome(agent.home);
|
|
@@ -64,6 +42,12 @@ function deployConfigs(agent, pkgRoot, { dryRun }) {
|
|
|
64
42
|
for (const extra of agent.deploy.overwriteExtra ?? []) {
|
|
65
43
|
copyFile(resolve(srcDir, extra.file), expandHome(extra.dest), { dryRun });
|
|
66
44
|
}
|
|
45
|
+
// extensions: deploy each file flat into ~/.pi/agent/extensions/ so Pi
|
|
46
|
+
// auto-discovers it on the next start. Existing files with the same name
|
|
47
|
+
// are overwritten (snapshot semantics).
|
|
48
|
+
for (const file of agent.deploy.extensionFiles ?? []) {
|
|
49
|
+
copyFile(resolve(srcDir, "extensions", file), resolve(home, "extensions", file), { dryRun });
|
|
50
|
+
}
|
|
67
51
|
}
|
|
68
52
|
|
|
69
53
|
function deployReference(agent, pkgRoot, { dryRun }) {
|
|
@@ -73,89 +57,6 @@ function deployReference(agent, pkgRoot, { dryRun }) {
|
|
|
73
57
|
copyDirFiles(src, dest, { dryRun });
|
|
74
58
|
}
|
|
75
59
|
|
|
76
|
-
/**
|
|
77
|
-
* Skills step: let the user pick which skills to install (interactive TTY),
|
|
78
|
-
* then install each selected one from remote via `npx skills add`
|
|
79
|
-
* (https://skills.sh/). Non-interactive runs skip skill install entirely —
|
|
80
|
-
* nothing is auto-installed.
|
|
81
|
-
*/
|
|
82
|
-
async function installSkillsStep(agent, { dryRun, tty }) {
|
|
83
|
-
const spec = agent.skills;
|
|
84
|
-
if (!spec) return 0;
|
|
85
|
-
let selected = [];
|
|
86
|
-
if (tty) {
|
|
87
|
-
selected = await checkbox({
|
|
88
|
-
message: "Select skills to install:",
|
|
89
|
-
choices: spec.specs.map((s) => ({
|
|
90
|
-
name: s.spec,
|
|
91
|
-
value: s.spec,
|
|
92
|
-
description: s.description,
|
|
93
|
-
checked: true,
|
|
94
|
-
})),
|
|
95
|
-
});
|
|
96
|
-
if (selected.length === 0) {
|
|
97
|
-
log.info("No skills selected — skipping skill install");
|
|
98
|
-
return 0;
|
|
99
|
-
}
|
|
100
|
-
} else {
|
|
101
|
-
log.info(`Non-interactive mode — skipping skill install (available: ${spec.specs.map((s) => s.spec).join(", ")})`);
|
|
102
|
-
return 0;
|
|
103
|
-
}
|
|
104
|
-
for (const skill of selected) {
|
|
105
|
-
const { repo, name } = parseSkillSpec(skill);
|
|
106
|
-
const cmdline = `npx --yes skills add ${repo} --skill ${name} -g --agent ${spec.cliAgent} -y`;
|
|
107
|
-
log.install(`${skill} → ${spec.cliAgent}`);
|
|
108
|
-
const code = await runCommand(cmdline, { dryRun });
|
|
109
|
-
if (code !== 0) return code;
|
|
110
|
-
}
|
|
111
|
-
return 0;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Packages step: let the user pick which Pi extension packages to install
|
|
116
|
-
* (interactive TTY), then install each via `pi install <source>`, which
|
|
117
|
-
* writes the declaration into ~/.pi/agent/settings.json. Non-interactive
|
|
118
|
-
* runs skip package install entirely — nothing is auto-installed.
|
|
119
|
-
*/
|
|
120
|
-
async function installPackagesStep(agent, { dryRun, tty }) {
|
|
121
|
-
const spec = agent.packages;
|
|
122
|
-
if (!spec) return 0;
|
|
123
|
-
let selected = [];
|
|
124
|
-
if (tty) {
|
|
125
|
-
selected = await checkbox({
|
|
126
|
-
message: "Select Pi extension packages to install:",
|
|
127
|
-
choices: spec.specs.map((p) => ({
|
|
128
|
-
name: p.source,
|
|
129
|
-
value: p.source,
|
|
130
|
-
description: p.description,
|
|
131
|
-
checked: false,
|
|
132
|
-
})),
|
|
133
|
-
});
|
|
134
|
-
if (selected.length === 0) {
|
|
135
|
-
log.info("No packages selected — skipping package install");
|
|
136
|
-
return 0;
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
const available = spec.specs.map((p) => p.source).join(", ");
|
|
140
|
-
log.info(`Non-interactive mode — skipping package install (available: ${available})`);
|
|
141
|
-
return 0;
|
|
142
|
-
}
|
|
143
|
-
for (const source of selected) {
|
|
144
|
-
const cmdline = `pi install ${source}`;
|
|
145
|
-
log.install(`${source} → ${agent.name}`);
|
|
146
|
-
const code = await runCommand(cmdline, { dryRun });
|
|
147
|
-
if (code !== 0) return code;
|
|
148
|
-
}
|
|
149
|
-
return 0;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Parse "owner/repo@skill" into { repo, name }. */
|
|
153
|
-
function parseSkillSpec(spec) {
|
|
154
|
-
const at = spec.lastIndexOf("@");
|
|
155
|
-
if (at < 0) throw new Error(`skill spec missing @skill: ${spec}`);
|
|
156
|
-
return { repo: spec.slice(0, at), name: spec.slice(at + 1) };
|
|
157
|
-
}
|
|
158
|
-
|
|
159
60
|
/**
|
|
160
61
|
* Back up the agent's existing home to a tar (no-op if absent).
|
|
161
62
|
*/
|
|
@@ -164,26 +65,27 @@ function backupAgentHome(agent, backupDir, { dryRun }) {
|
|
|
164
65
|
}
|
|
165
66
|
|
|
166
67
|
/**
|
|
167
|
-
* Initialize the configured agents
|
|
68
|
+
* Initialize the configured agents: (backup) → deploy configs (including the
|
|
69
|
+
* /pi-setup extension) → deploy reference docs. Returns 0 on success.
|
|
70
|
+
*
|
|
71
|
+
* Installing binaries, packages, skills, and CLI tools is deferred to the
|
|
72
|
+
* `/pi-setup` command inside Pi (see configs/pi/extensions/pi-setup.js).
|
|
168
73
|
*
|
|
169
|
-
* @param {{ dryRun?: boolean, backup?: boolean,
|
|
74
|
+
* @param {{ dryRun?: boolean, backup?: boolean, agents?: Array }} opts
|
|
170
75
|
* @returns {Promise<number>}
|
|
171
76
|
*/
|
|
172
|
-
export async function initialize({ dryRun = false, backup = false,
|
|
77
|
+
export async function initialize({ dryRun = false, backup = false, agents = AGENTS } = {}) {
|
|
173
78
|
const pkgRoot = packageRoot(import.meta.url);
|
|
174
79
|
const backupDir = backup ? backupDirFor() : null;
|
|
175
80
|
log.info(dryRun ? "Dry-run mode — no changes will be made" : "Initializing agent environments");
|
|
176
81
|
if (backup) log.info(`Backing up existing agent homes to ${backupDir}`);
|
|
177
|
-
let rc = 0;
|
|
178
82
|
for (const agent of agents) {
|
|
179
83
|
log.step(`Configure ${agent.name}`);
|
|
180
|
-
rc ||= await installStep(agent, { dryRun });
|
|
181
84
|
if (backup) backupAgentHome(agent, backupDir, { dryRun });
|
|
182
85
|
deployConfigs(agent, pkgRoot, { dryRun });
|
|
183
86
|
deployReference(agent, pkgRoot, { dryRun });
|
|
184
|
-
rc ||= await installPackagesStep(agent, { dryRun, tty });
|
|
185
|
-
rc ||= await installSkillsStep(agent, { dryRun, tty });
|
|
186
87
|
}
|
|
187
|
-
|
|
188
|
-
|
|
88
|
+
log.success("Setup complete");
|
|
89
|
+
log.info("Run /pi-setup inside Pi to install packages, skills, and CLI tools (each confirmed individually)");
|
|
90
|
+
return 0;
|
|
189
91
|
}
|
package/src/agents/packages.mjs
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
// packages.mjs — Pi extension packages offered for install via `pi install`.
|
|
2
|
-
// The settings.json snapshot ships WITHOUT a "packages" key; the initializer
|
|
3
|
-
// prompts the user to pick which ones to install (interactive TTY only;
|
|
4
|
-
// non-interactive runs skip package install entirely — nothing is
|
|
5
|
-
// auto-installed).
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Packages offered for installation. `source` is passed verbatim to
|
|
9
|
-
* `pi install`, which writes the declaration into ~/.pi/agent/settings.json.
|
|
10
|
-
*/
|
|
11
|
-
export const DEFAULT_PACKAGES = [
|
|
12
|
-
{
|
|
13
|
-
source: "npm:pi-footer",
|
|
14
|
-
description: "Footer status line (token usage, model, cost).",
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
source: "npm:pi-tps-meter",
|
|
18
|
-
description: "Tokens-per-second meter for streaming responses.",
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
source: "npm:@juicesharp/rpiv-pi",
|
|
22
|
-
description: "RPIV pipeline skills (code review, commit, handoff, diagrams).",
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
source: "https://github.com/cathrynlavery/diagram-design",
|
|
26
|
-
description: "Branded diagram/chart generation skills.",
|
|
27
|
-
},
|
|
28
|
-
];
|
package/src/agents/skills.mjs
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// skills.mjs — skill specs available to install from remote via `npx skills add`.
|
|
2
|
-
// Each spec is "owner/repo@skill" (see https://skills.sh/).
|
|
3
|
-
// The initializer prompts the user to pick which ones to install
|
|
4
|
-
// (interactive TTY only; non-interactive runs skip skill install).
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Skills offered for installation. Sourced from remote via
|
|
8
|
-
* `npx skills add` (https://skills.sh/).
|
|
9
|
-
*/
|
|
10
|
-
export const DEFAULT_SKILLS = [
|
|
11
|
-
{
|
|
12
|
-
spec: "mattpocock/skills@grill-me",
|
|
13
|
-
description: "Matt Pocock — a relentless interview to sharpen a plan or design.",
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
spec: "microsoft/playwright-cli@playwright-cli",
|
|
17
|
-
description: "Microsoft — browser automation and Playwright test driving.",
|
|
18
|
-
},
|
|
19
|
-
];
|
package/src/lib/exec.mjs
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
// exec.mjs — exec + detect helpers that honor --dry-run.
|
|
2
|
-
//
|
|
3
|
-
// runCommand takes a single shell command string. In dry-run mode it prints
|
|
4
|
-
// "[dry] <cmd>" instead of spawning. In real mode it spawns with shell:true
|
|
5
|
-
// and inherited stdio, returning the exit code.
|
|
6
|
-
|
|
7
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Run a shell command. If dryRun is true, only print the command.
|
|
11
|
-
* Returns 0 on success, non-zero on failure. Resolves a Promise.
|
|
12
|
-
*/
|
|
13
|
-
export function runCommand(cmdline, { dryRun = false, cwd } = {}) {
|
|
14
|
-
if (dryRun) {
|
|
15
|
-
console.log(`[dry] ${cmdline}`);
|
|
16
|
-
return Promise.resolve(0);
|
|
17
|
-
}
|
|
18
|
-
return new Promise((resolve) => {
|
|
19
|
-
const child = spawn(cmdline, { cwd, stdio: "inherit", shell: true });
|
|
20
|
-
child.on("close", (code) => resolve(code ?? 1));
|
|
21
|
-
child.on("error", () => resolve(1));
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Detect whether a binary is on PATH. Synchronous — used by the detect step
|
|
27
|
-
* to decide install-vs-skip. Returns boolean.
|
|
28
|
-
*
|
|
29
|
-
* The command is passed as a single string, NOT as an args array: args +
|
|
30
|
-
* shell:true triggers Node's DEP0190 DeprecationWarning, which is printed
|
|
31
|
-
* asynchronously — it lands inside the first interactive prompt that follows
|
|
32
|
-
* and shifts the cursor, breaking @inquirer's screen clearing (stale frames).
|
|
33
|
-
*/
|
|
34
|
-
export function detectBinary(name) {
|
|
35
|
-
const result = spawnSync(`command -v ${name}`, {
|
|
36
|
-
shell: true,
|
|
37
|
-
stdio: "ignore",
|
|
38
|
-
});
|
|
39
|
-
return result.status === 0;
|
|
40
|
-
}
|