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 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
- # Install the skills + seed your global preferences
45
+ # Interactive: pick global vs project-local, then install + seed preferences
46
46
  npx pi-dev@latest install
47
47
 
48
- # Refresh skills only (keeps your preferences as-is)
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 your environment
58
+ # Verify the active scope's layout
55
59
  npx pi-dev doctor
56
60
  ```
57
61
 
58
- `install` copies the skill folders to `~/.pi/agent/skills/` (where the pi runtime looks) and seeds `~/.pi/agent/preferences.md` only if you don't already have one.
59
- `update` refreshes skill folders but leaves your preferences alone unless you pass `--include-prefs`.
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] Copy skills into ~/.pi/agent/skills and seed
13
- ~/.pi/agent/preferences.md if missing.
14
- pi-dev update [--include-prefs] Refresh skills. By default keeps your global
15
- preferences. Pass --include-prefs to overwrite.
16
- pi-dev list Show installed skills + global prefs path.
17
- pi-dev uninstall <skill> Soft-remove a skill (renamed to .removed-…).
18
- pi-dev doctor Check ~/.pi layout and external CLIs.
19
- pi-dev version Print version.
20
- pi-dev help This message.
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 — the one-shot engineering entry point
24
- /taste — view or update preferences
25
- /where — recall prior pi sessions for this cwd
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
- switch (cmd) {
34
- case "install":
35
- install({ skipPrefs: getFlag("skip-prefs") });
36
- break;
37
- case "update":
38
- update({ skipPrefs: !getFlag("include-prefs") });
39
- break;
40
- case "list":
41
- listInstalled();
42
- break;
43
- case "uninstall": {
44
- const name = args[1];
45
- if (!name) {
46
- console.error("Usage: pi-dev uninstall <skill>");
47
- process.exit(1);
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
- uninstallSkill(name);
50
- break;
51
- }
52
- case "doctor":
53
- doctor();
54
- break;
55
- case "version":
56
- case "--version":
57
- case "-v": {
58
- const here = dirname(fileURLToPath(import.meta.url));
59
- const pkg = JSON.parse(readFileSync(join(here, "..", "package.json"), "utf8"));
60
- console.log(pkg.version);
61
- break;
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 { PI_AGENT_DIR, PI_SKILLS_DIR, PI_GLOBAL_PREFS, PKG_SKILLS_DIR, PKG_GLOBAL_PREFS_PRESET, } from "./paths.js";
6
- export function install(opts = {}) {
7
- mkdirSync(PI_SKILLS_DIR, { recursive: true });
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(PI_SKILLS_DIR, skill.name);
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 ${PI_SKILLS_DIR}`);
60
+ console.log(`Installed ${copied} skill(s) into ${skillsDir} [${scope}]`);
27
61
  if (opts.skipPrefs) {
28
- console.log("Skipped global preferences (use --include-prefs on update to merge in new keys).");
62
+ console.log("Skipped preferences (pass --include-prefs on update to merge in new keys).");
29
63
  return;
30
64
  }
31
- if (!existsSync(PI_GLOBAL_PREFS)) {
65
+ if (!existsSync(prefsFile)) {
32
66
  if (existsSync(PKG_GLOBAL_PREFS_PRESET)) {
33
- copyFileSync(PKG_GLOBAL_PREFS_PRESET, PI_GLOBAL_PREFS);
34
- console.log(`Seeded global preferences at ${PI_GLOBAL_PREFS}`);
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 global preferences kept at ${PI_GLOBAL_PREFS} (use --include-prefs to merge new keys).`);
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, but defaults to NOT overwriting global prefs.
43
- install({ ...opts, force: true, skipPrefs: !opts.skipPrefs ? true : opts.skipPrefs });
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 dst = join(PI_SKILLS_DIR, name);
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(PI_SKILLS_DIR, `.removed-${stamp}-${name}`);
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
- console.log(`Skills directory: ${PI_SKILLS_DIR}\n`);
59
- for (const skill of SKILLS) {
60
- const path = join(PI_SKILLS_DIR, skill.name, "SKILL.md");
61
- const installed = existsSync(path);
62
- const tag = skill.kind === "human" ? "[user] " : "[support]";
63
- const status = installed ? "ok" : "missing";
64
- console.log(` ${tag} /${skill.name.padEnd(34)} ${status} — ${skill.summary}`);
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
- if (!existsSync(PI_AGENT_DIR))
71
- issues.push(`~/.pi/agent missing run: pi-dev install`);
72
- if (!existsSync(PI_SKILLS_DIR))
73
- issues.push(`~/.pi/agent/skills missing run: pi-dev install`);
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(PI_SKILLS_DIR, skill.name, "SKILL.md"))) {
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(PI_GLOBAL_PREFS)) {
80
- issues.push(`Global preferences missing — run: pi-dev install (or copy presets/preferences.md manually)`);
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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-dev",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "An autonomous engineering skill framework for the pi runtime — built on Matt Pocock's skills.",
5
5
  "type": "module",
6
6
  "bin": {