pi-dev 0.1.7 → 0.1.8
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 +19 -6
- package/dist/cli.js +77 -50
- package/dist/install.js +106 -38
- package/dist/paths.js +12 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,21 +42,34 @@ You ask. It classifies. It executes. It reports.
|
|
|
42
42
|
Requires Node ≥ 20 and the [pi runtime](https://github.com/badlogic/pi).
|
|
43
43
|
|
|
44
44
|
```bash
|
|
45
|
-
#
|
|
45
|
+
# Interactive: pick global vs project-local, then install + seed preferences
|
|
46
46
|
npx pi-dev@latest install
|
|
47
47
|
|
|
48
|
-
#
|
|
48
|
+
# Or be explicit:
|
|
49
|
+
npx pi-dev@latest install --global # ~/.pi/agent/skills/ (every repo)
|
|
50
|
+
npx pi-dev@latest install --local # ./.pi/skills/ (this repo only)
|
|
51
|
+
|
|
52
|
+
# Refresh skills (auto-detects scope from disk; preferences are kept)
|
|
49
53
|
npx pi-dev@latest update
|
|
50
54
|
|
|
51
|
-
# See what's installed
|
|
55
|
+
# See what's installed under each scope
|
|
52
56
|
npx pi-dev list
|
|
53
57
|
|
|
54
|
-
# Verify
|
|
58
|
+
# Verify the active scope's layout
|
|
55
59
|
npx pi-dev doctor
|
|
56
60
|
```
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
**Scope choice cheat-sheet:**
|
|
63
|
+
|
|
64
|
+
| | global | local |
|
|
65
|
+
| --- | --- | --- |
|
|
66
|
+
| Skills | `~/.pi/agent/skills/` | `<repo>/.pi/skills/` |
|
|
67
|
+
| Preferences | `~/.pi/agent/preferences.md` | `<repo>/.pi/preferences.md` |
|
|
68
|
+
| Pi sessions see it from | every cwd | only this repo |
|
|
69
|
+
| Goes in the repo's git? | no | yes (commit `.pi/skills/`, gitignore `.pi/sessions/`) |
|
|
70
|
+
| Use when | the skills are part of *your* engineering taste | the skills are part of *the project's* contract |
|
|
71
|
+
|
|
72
|
+
Non-interactive runs (CI, piped input) default to `--global` silently. Pass `-y` to skip the prompt and accept the default.
|
|
60
73
|
|
|
61
74
|
## How `/do` actually flows
|
|
62
75
|
|
package/dist/cli.js
CHANGED
|
@@ -9,20 +9,30 @@ function help() {
|
|
|
9
9
|
console.log(`pi-dev — autonomous engineering skill framework for the pi runtime
|
|
10
10
|
|
|
11
11
|
Usage:
|
|
12
|
-
pi-dev install [--skip-prefs]
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
pi-dev
|
|
20
|
-
|
|
12
|
+
pi-dev install [scope] [--skip-prefs] [-y]
|
|
13
|
+
Install skills + seed preferences. Scope is one of:
|
|
14
|
+
--global ~/.pi/agent/skills/ (default, every pi session sees it)
|
|
15
|
+
--local .pi/skills/ in cwd (only this repo)
|
|
16
|
+
Without a flag, an interactive TTY is prompted; non-TTY defaults to global.
|
|
17
|
+
Pass -y to skip the prompt and accept the default.
|
|
18
|
+
|
|
19
|
+
pi-dev update [--include-prefs] [--global|--local]
|
|
20
|
+
Refresh skills in place. Scope is auto-detected from disk
|
|
21
|
+
(local wins if .pi/skills/ exists in cwd). Preferences are kept by default;
|
|
22
|
+
pass --include-prefs to re-seed.
|
|
23
|
+
|
|
24
|
+
pi-dev list Show installed skills under both scopes (if present).
|
|
25
|
+
pi-dev uninstall <skill> [--global|--local]
|
|
26
|
+
Soft-remove a skill (renamed to .removed-…).
|
|
27
|
+
pi-dev doctor Check the active scope's layout and external CLIs.
|
|
28
|
+
pi-dev version Print version.
|
|
29
|
+
pi-dev help This message.
|
|
21
30
|
|
|
22
31
|
After install, in any pi session, you primarily call:
|
|
23
|
-
/do
|
|
24
|
-
/taste
|
|
25
|
-
/where
|
|
32
|
+
/do — the one-shot engineering entry point
|
|
33
|
+
/taste — view or update preferences
|
|
34
|
+
/where — recall prior pi sessions for this cwd
|
|
35
|
+
/improve-skill-flow — audit pi sessions, propose evidence-based skill edits
|
|
26
36
|
|
|
27
37
|
All other skills are invoked automatically by /do.
|
|
28
38
|
`);
|
|
@@ -30,44 +40,61 @@ All other skills are invoked automatically by /do.
|
|
|
30
40
|
function getFlag(name) {
|
|
31
41
|
return args.includes(`--${name}`);
|
|
32
42
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
43
|
+
function getScope() {
|
|
44
|
+
if (getFlag("global"))
|
|
45
|
+
return "global";
|
|
46
|
+
if (getFlag("local"))
|
|
47
|
+
return "local";
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
async function main() {
|
|
51
|
+
switch (cmd) {
|
|
52
|
+
case "install":
|
|
53
|
+
await install({
|
|
54
|
+
skipPrefs: getFlag("skip-prefs"),
|
|
55
|
+
scope: getScope(),
|
|
56
|
+
yes: getFlag("yes") || args.includes("-y"),
|
|
57
|
+
});
|
|
58
|
+
break;
|
|
59
|
+
case "update":
|
|
60
|
+
await update({ skipPrefs: !getFlag("include-prefs"), scope: getScope() });
|
|
61
|
+
break;
|
|
62
|
+
case "list":
|
|
63
|
+
listInstalled();
|
|
64
|
+
break;
|
|
65
|
+
case "uninstall": {
|
|
66
|
+
const name = args[1];
|
|
67
|
+
if (!name || name.startsWith("--")) {
|
|
68
|
+
console.error("Usage: pi-dev uninstall <skill> [--global|--local]");
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
uninstallSkill(name, getScope());
|
|
72
|
+
break;
|
|
48
73
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
74
|
+
case "doctor":
|
|
75
|
+
doctor();
|
|
76
|
+
break;
|
|
77
|
+
case "version":
|
|
78
|
+
case "--version":
|
|
79
|
+
case "-v": {
|
|
80
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
81
|
+
const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
|
|
82
|
+
console.log(pkg.version);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "help":
|
|
86
|
+
case "--help":
|
|
87
|
+
case "-h":
|
|
88
|
+
case undefined:
|
|
89
|
+
help();
|
|
90
|
+
break;
|
|
91
|
+
default:
|
|
92
|
+
console.error(`Unknown command: ${cmd}\n`);
|
|
93
|
+
help();
|
|
94
|
+
process.exit(1);
|
|
62
95
|
}
|
|
63
|
-
case "help":
|
|
64
|
-
case "--help":
|
|
65
|
-
case "-h":
|
|
66
|
-
case undefined:
|
|
67
|
-
help();
|
|
68
|
-
break;
|
|
69
|
-
default:
|
|
70
|
-
console.error(`Unknown command: ${cmd}\n`);
|
|
71
|
-
help();
|
|
72
|
-
process.exit(1);
|
|
73
96
|
}
|
|
97
|
+
main().catch((err) => {
|
|
98
|
+
console.error(err);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|
package/dist/install.js
CHANGED
|
@@ -1,21 +1,55 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, cpSync, copyFileSync, renameSync } from "node:fs";
|
|
2
2
|
import { execSync } from "node:child_process";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { createInterface } from "node:readline";
|
|
4
5
|
import { SKILLS } from "./manifest.js";
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
import { PKG_SKILLS_DIR, PKG_GLOBAL_PREFS_PRESET, destFor, } from "./paths.js";
|
|
7
|
+
function ask(question) {
|
|
8
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9
|
+
return new Promise((res) => {
|
|
10
|
+
rl.question(question, (a) => {
|
|
11
|
+
rl.close();
|
|
12
|
+
res(a);
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
async function resolveScope(opts) {
|
|
17
|
+
if (opts.scope)
|
|
18
|
+
return opts.scope;
|
|
19
|
+
// Non-interactive (CI, piped) → default to global silently.
|
|
20
|
+
if (opts.yes || !process.stdin.isTTY)
|
|
21
|
+
return "global";
|
|
22
|
+
const cwd = process.cwd();
|
|
23
|
+
const { skillsDir: localSkills } = destFor("local", cwd);
|
|
24
|
+
const { skillsDir: globalSkills } = destFor("global");
|
|
25
|
+
console.log("");
|
|
26
|
+
console.log("pi-dev: where should the skills live?");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log(` [g] global → ${globalSkills}`);
|
|
29
|
+
console.log(` every pi session on this machine sees them.`);
|
|
30
|
+
console.log("");
|
|
31
|
+
console.log(` [l] local → ${localSkills}`);
|
|
32
|
+
console.log(` only pi sessions started from this repo see them.`);
|
|
33
|
+
console.log(` pi reads project-local config from .pi/ in the cwd.`);
|
|
34
|
+
console.log("");
|
|
35
|
+
const ans = (await ask("Choose [G/l] (default global): ")).trim().toLowerCase();
|
|
36
|
+
if (ans === "l" || ans === "local")
|
|
37
|
+
return "local";
|
|
38
|
+
return "global";
|
|
39
|
+
}
|
|
40
|
+
export async function install(opts = {}) {
|
|
41
|
+
const scope = await resolveScope(opts);
|
|
42
|
+
const { agentDir, skillsDir, prefsFile } = destFor(scope);
|
|
43
|
+
mkdirSync(skillsDir, { recursive: true });
|
|
8
44
|
let copied = 0;
|
|
9
45
|
for (const skill of SKILLS) {
|
|
10
46
|
const src = join(PKG_SKILLS_DIR, skill.name);
|
|
11
|
-
const dst = join(
|
|
47
|
+
const dst = join(skillsDir, skill.name);
|
|
12
48
|
if (!existsSync(src)) {
|
|
13
49
|
console.warn(` skip ${skill.name} (source not found in package)`);
|
|
14
50
|
continue;
|
|
15
51
|
}
|
|
16
52
|
if (existsSync(dst) && !opts.force) {
|
|
17
|
-
// Replace contents but keep the directory; safer than rmSync for skills
|
|
18
|
-
// the user may have customised lightly.
|
|
19
53
|
cpSync(src, dst, { recursive: true, force: true });
|
|
20
54
|
}
|
|
21
55
|
else {
|
|
@@ -23,63 +57,96 @@ export function install(opts = {}) {
|
|
|
23
57
|
}
|
|
24
58
|
copied++;
|
|
25
59
|
}
|
|
26
|
-
console.log(`Installed ${copied} skill(s) into ${
|
|
60
|
+
console.log(`Installed ${copied} skill(s) into ${skillsDir} [${scope}]`);
|
|
27
61
|
if (opts.skipPrefs) {
|
|
28
|
-
console.log("Skipped
|
|
62
|
+
console.log("Skipped preferences (pass --include-prefs on update to merge in new keys).");
|
|
29
63
|
return;
|
|
30
64
|
}
|
|
31
|
-
if (!existsSync(
|
|
65
|
+
if (!existsSync(prefsFile)) {
|
|
32
66
|
if (existsSync(PKG_GLOBAL_PREFS_PRESET)) {
|
|
33
|
-
copyFileSync(PKG_GLOBAL_PREFS_PRESET,
|
|
34
|
-
console.log(`Seeded
|
|
67
|
+
copyFileSync(PKG_GLOBAL_PREFS_PRESET, prefsFile);
|
|
68
|
+
console.log(`Seeded preferences at ${prefsFile} [${scope}]`);
|
|
35
69
|
}
|
|
36
70
|
}
|
|
37
71
|
else {
|
|
38
|
-
console.log(`Existing
|
|
72
|
+
console.log(`Existing preferences kept at ${prefsFile} (pass --include-prefs to merge new keys).`);
|
|
73
|
+
}
|
|
74
|
+
if (scope === "local") {
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log("Tip: add `.pi/sessions/` to .gitignore (sessions are local-only).");
|
|
77
|
+
console.log(" Skills under `.pi/skills/` are safe to commit.");
|
|
39
78
|
}
|
|
40
79
|
}
|
|
41
|
-
export function update(opts = {}) {
|
|
42
|
-
// Update is install with force
|
|
43
|
-
|
|
80
|
+
export async function update(opts = {}) {
|
|
81
|
+
// Update is install with force. If scope wasn't passed, infer it from what's
|
|
82
|
+
// already on disk: a local `.pi/skills/` in cwd wins over global.
|
|
83
|
+
let scope = opts.scope;
|
|
84
|
+
if (!scope) {
|
|
85
|
+
const local = destFor("local");
|
|
86
|
+
if (existsSync(local.skillsDir))
|
|
87
|
+
scope = "local";
|
|
88
|
+
else
|
|
89
|
+
scope = "global";
|
|
90
|
+
}
|
|
91
|
+
await install({
|
|
92
|
+
...opts,
|
|
93
|
+
scope,
|
|
94
|
+
force: true,
|
|
95
|
+
skipPrefs: !opts.skipPrefs ? true : opts.skipPrefs,
|
|
96
|
+
yes: true,
|
|
97
|
+
});
|
|
44
98
|
}
|
|
45
|
-
export function uninstallSkill(name) {
|
|
46
|
-
const
|
|
99
|
+
export function uninstallSkill(name, scope) {
|
|
100
|
+
const resolved = scope ?? (existsSync(destFor("local").skillsDir) ? "local" : "global");
|
|
101
|
+
const { skillsDir } = destFor(resolved);
|
|
102
|
+
const dst = join(skillsDir, name);
|
|
47
103
|
if (!existsSync(dst)) {
|
|
48
|
-
console.error(`Skill not installed: ${name}`);
|
|
104
|
+
console.error(`Skill not installed [${resolved}]: ${name}`);
|
|
49
105
|
process.exit(1);
|
|
50
106
|
}
|
|
51
|
-
// Soft delete: rename to .removed-<ts>
|
|
52
107
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
53
|
-
const moved = join(
|
|
108
|
+
const moved = join(skillsDir, `.removed-${stamp}-${name}`);
|
|
54
109
|
renameSync(dst, moved);
|
|
55
|
-
console.log(`Uninstalled ${name} (moved to ${moved}).`);
|
|
110
|
+
console.log(`Uninstalled ${name} from ${resolved} (moved to ${moved}).`);
|
|
56
111
|
}
|
|
57
112
|
export function listInstalled() {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
113
|
+
const local = destFor("local");
|
|
114
|
+
const global = destFor("global");
|
|
115
|
+
const targets = [];
|
|
116
|
+
if (existsSync(local.skillsDir))
|
|
117
|
+
targets.push({ label: "local", ...local });
|
|
118
|
+
targets.push({ label: "global", ...global });
|
|
119
|
+
for (const t of targets) {
|
|
120
|
+
console.log(`Skills directory [${t.label}]: ${t.skillsDir}`);
|
|
121
|
+
for (const skill of SKILLS) {
|
|
122
|
+
const path = join(t.skillsDir, skill.name, "SKILL.md");
|
|
123
|
+
const installed = existsSync(path);
|
|
124
|
+
const tag = skill.kind === "human" ? "[user] " : "[support]";
|
|
125
|
+
const status = installed ? "ok " : "missing";
|
|
126
|
+
console.log(` ${tag} /${skill.name.padEnd(34)} ${status} — ${skill.summary}`);
|
|
127
|
+
}
|
|
128
|
+
console.log(`Preferences: ${existsSync(t.prefsFile) ? t.prefsFile : "(missing)"}`);
|
|
129
|
+
console.log("");
|
|
65
130
|
}
|
|
66
|
-
console.log(`\nGlobal preferences: ${existsSync(PI_GLOBAL_PREFS) ? PI_GLOBAL_PREFS : "(missing)"}`);
|
|
67
131
|
}
|
|
68
132
|
export function doctor() {
|
|
69
133
|
const issues = [];
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
134
|
+
const local = destFor("local");
|
|
135
|
+
const hasLocal = existsSync(local.skillsDir);
|
|
136
|
+
const activeScope = hasLocal ? "local" : "global";
|
|
137
|
+
const { agentDir, skillsDir, prefsFile } = destFor(activeScope);
|
|
138
|
+
if (!existsSync(agentDir))
|
|
139
|
+
issues.push(`${agentDir} missing — run: pi-dev install`);
|
|
140
|
+
if (!existsSync(skillsDir))
|
|
141
|
+
issues.push(`${skillsDir} missing — run: pi-dev install`);
|
|
74
142
|
for (const skill of SKILLS.filter((s) => s.kind === "human")) {
|
|
75
|
-
if (!existsSync(join(
|
|
76
|
-
issues.push(`Human skill /${skill.name} not installed — run: pi-dev update`);
|
|
143
|
+
if (!existsSync(join(skillsDir, skill.name, "SKILL.md"))) {
|
|
144
|
+
issues.push(`Human skill /${skill.name} not installed [${activeScope}] — run: pi-dev update`);
|
|
77
145
|
}
|
|
78
146
|
}
|
|
79
|
-
if (!existsSync(
|
|
80
|
-
issues.push(`
|
|
147
|
+
if (!existsSync(prefsFile)) {
|
|
148
|
+
issues.push(`Preferences missing at ${prefsFile} — run: pi-dev install (or copy presets/preferences.md manually)`);
|
|
81
149
|
}
|
|
82
|
-
// Quick external check (best-effort)
|
|
83
150
|
const checkCmd = (cmd, label) => {
|
|
84
151
|
try {
|
|
85
152
|
execSync(`${cmd} --version`, { stdio: "ignore" });
|
|
@@ -90,6 +157,7 @@ export function doctor() {
|
|
|
90
157
|
};
|
|
91
158
|
checkCmd("git", "git");
|
|
92
159
|
checkCmd("gh", "gh CLI");
|
|
160
|
+
console.log(`Active scope: ${activeScope}`);
|
|
93
161
|
if (issues.length === 0) {
|
|
94
162
|
console.log("All checks passed.");
|
|
95
163
|
return;
|
package/dist/paths.js
CHANGED
|
@@ -12,3 +12,15 @@ export const PKG_ROOT = resolve(__dirname, "..");
|
|
|
12
12
|
export const PKG_SKILLS_DIR = join(PKG_ROOT, "skills");
|
|
13
13
|
export const PKG_PRESETS_DIR = join(PKG_ROOT, "presets");
|
|
14
14
|
export const PKG_GLOBAL_PREFS_PRESET = join(PKG_PRESETS_DIR, "preferences.md");
|
|
15
|
+
/** Compute install destinations for a given scope. `local` resolves against `cwd`. */
|
|
16
|
+
export function destFor(scope, cwd = process.cwd()) {
|
|
17
|
+
if (scope === "global") {
|
|
18
|
+
return { agentDir: PI_AGENT_DIR, skillsDir: PI_SKILLS_DIR, prefsFile: PI_GLOBAL_PREFS };
|
|
19
|
+
}
|
|
20
|
+
const agentDir = join(cwd, ".pi");
|
|
21
|
+
return {
|
|
22
|
+
agentDir,
|
|
23
|
+
skillsDir: join(agentDir, "skills"),
|
|
24
|
+
prefsFile: join(agentDir, "preferences.md"),
|
|
25
|
+
};
|
|
26
|
+
}
|