supipowers 0.2.1 → 0.2.2

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/bin/install.mjs CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  note,
12
12
  } from "@clack/prompts";
13
13
  import { spawnSync } from "node:child_process";
14
- import { readFileSync, existsSync, mkdirSync, cpSync, readdirSync } from "node:fs";
14
+ import { readFileSync, existsSync, mkdirSync, cpSync, rmSync, readdirSync } from "node:fs";
15
15
  import { resolve, dirname, join } from "node:path";
16
16
  import { fileURLToPath } from "node:url";
17
17
  import { homedir } from "node:os";
@@ -130,39 +130,65 @@ async function main() {
130
130
  ompSpinner.stop(`OMP ${ver} detected`);
131
131
  }
132
132
 
133
- // ── Step 2: Install supipowers into ~/.omp/agent/ ───────────
134
-
135
- const s = spinner();
136
- s.start("Installing supipowers...");
133
+ // ── Step 2: Install / update supipowers into ~/.omp/agent/ ──
137
134
 
138
135
  const packageRoot = resolve(__dirname, "..");
139
136
  const ompAgent = join(homedir(), ".omp", "agent");
137
+ const extDir = join(ompAgent, "extensions", "supipowers");
138
+ const installedPkgPath = join(extDir, "package.json");
139
+
140
+ // Check for existing installation
141
+ let installedVersion = null;
142
+ if (existsSync(installedPkgPath)) {
143
+ try {
144
+ const installed = JSON.parse(readFileSync(installedPkgPath, "utf8"));
145
+ installedVersion = installed.version;
146
+ } catch {
147
+ // corrupted package.json — treat as not installed
148
+ }
149
+ }
140
150
 
141
- try {
142
- // Copy extension (src/ + package.json) ~/.omp/agent/extensions/supipowers/
143
- const extDir = join(ompAgent, "extensions", "supipowers");
144
- mkdirSync(extDir, { recursive: true });
145
- cpSync(join(packageRoot, "src"), join(extDir, "src"), { recursive: true });
146
- cpSync(join(packageRoot, "package.json"), join(extDir, "package.json"));
147
-
148
- // Copy skills → ~/.omp/agent/skills/<skillname>/SKILL.md
149
- const skillsSource = join(packageRoot, "skills");
150
- if (existsSync(skillsSource)) {
151
- const skillDirs = readdirSync(skillsSource, { withFileTypes: true });
152
- for (const entry of skillDirs) {
153
- if (!entry.isDirectory()) continue;
154
- const skillFile = join(skillsSource, entry.name, "SKILL.md");
155
- if (!existsSync(skillFile)) continue;
156
- const destDir = join(ompAgent, "skills", entry.name);
157
- mkdirSync(destDir, { recursive: true });
158
- cpSync(skillFile, join(destDir, "SKILL.md"));
159
- }
151
+ if (installedVersion === VERSION) {
152
+ note(`supipowers v${VERSION} is already installed and up to date.`, "Up to date");
153
+ } else {
154
+ const action = installedVersion ? "Updating" : "Installing";
155
+ if (installedVersion) {
156
+ note(`v${installedVersion} v${VERSION}`, "Updating supipowers");
160
157
  }
161
158
 
162
- s.stop("supipowers installed");
163
- } catch (err) {
164
- s.stop("Installation failed");
165
- bail(err.message || "Failed to copy files to ~/.omp/agent/");
159
+ const s = spinner();
160
+ s.start(`${action} supipowers...`);
161
+
162
+ try {
163
+ // Clean previous installation to remove stale files
164
+ if (existsSync(extDir)) {
165
+ rmSync(extDir, { recursive: true });
166
+ }
167
+
168
+ // Copy extension (src/ + package.json) → ~/.omp/agent/extensions/supipowers/
169
+ mkdirSync(extDir, { recursive: true });
170
+ cpSync(join(packageRoot, "src"), join(extDir, "src"), { recursive: true });
171
+ cpSync(join(packageRoot, "package.json"), join(extDir, "package.json"));
172
+
173
+ // Copy skills → ~/.omp/agent/skills/<skillname>/SKILL.md
174
+ const skillsSource = join(packageRoot, "skills");
175
+ if (existsSync(skillsSource)) {
176
+ const skillDirs = readdirSync(skillsSource, { withFileTypes: true });
177
+ for (const entry of skillDirs) {
178
+ if (!entry.isDirectory()) continue;
179
+ const skillFile = join(skillsSource, entry.name, "SKILL.md");
180
+ if (!existsSync(skillFile)) continue;
181
+ const destDir = join(ompAgent, "skills", entry.name);
182
+ mkdirSync(destDir, { recursive: true });
183
+ cpSync(skillFile, join(destDir, "SKILL.md"));
184
+ }
185
+ }
186
+
187
+ s.stop(installedVersion ? `supipowers updated to v${VERSION}` : `supipowers v${VERSION} installed`);
188
+ } catch (err) {
189
+ s.stop(`${action} failed`);
190
+ bail(err.message || "Failed to copy files to ~/.omp/agent/");
191
+ }
166
192
  }
167
193
 
168
194
  // ── Step 3: LSP setup (optional) ──────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "OMP-native workflow extension inspired by Superpowers.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -21,6 +21,7 @@ export function registerSupiCommand(pi: ExtensionAPI): void {
21
21
  "/supi:release — Release automation",
22
22
  "/supi:config — Manage configuration",
23
23
  "/supi:status — Check running tasks",
24
+ "/supi:update — Update to latest version",
24
25
  ];
25
26
 
26
27
  const status = [
@@ -0,0 +1,97 @@
1
+ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
2
+ import { readFileSync, existsSync, mkdirSync, cpSync, rmSync, readdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir, tmpdir } from "node:os";
5
+
6
+ export function registerUpdateCommand(pi: ExtensionAPI): void {
7
+ pi.registerCommand("supi:update", {
8
+ description: "Update supipowers to the latest version",
9
+ async handler(_args, ctx) {
10
+ const ompAgent = join(homedir(), ".omp", "agent");
11
+ const extDir = join(ompAgent, "extensions", "supipowers");
12
+ const installedPkgPath = join(extDir, "package.json");
13
+
14
+ // Get current installed version
15
+ let currentVersion = "unknown";
16
+ if (existsSync(installedPkgPath)) {
17
+ try {
18
+ const pkg = JSON.parse(readFileSync(installedPkgPath, "utf8"));
19
+ currentVersion = pkg.version;
20
+ } catch {
21
+ // corrupted — will update anyway
22
+ }
23
+ }
24
+
25
+ ctx.ui.notify(`Current version: v${currentVersion}`, "info");
26
+
27
+ // Check latest version on npm
28
+ const checkResult = await pi.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() });
29
+ if (checkResult.exitCode !== 0) {
30
+ ctx.ui.notify("Failed to check for updates — npm view failed", "error");
31
+ return;
32
+ }
33
+ const latestVersion = checkResult.stdout.trim();
34
+
35
+ if (latestVersion === currentVersion) {
36
+ ctx.ui.notify(`supipowers v${currentVersion} is already up to date`, "info");
37
+ return;
38
+ }
39
+
40
+ ctx.ui.notify(`Updating v${currentVersion} → v${latestVersion}...`, "info");
41
+
42
+ // Download latest to a temp directory
43
+ const tempDir = join(tmpdir(), `supipowers-update-${Date.now()}`);
44
+ mkdirSync(tempDir, { recursive: true });
45
+
46
+ try {
47
+ const installResult = await pi.exec(
48
+ "npm", ["install", "--prefix", tempDir, `supipowers@${latestVersion}`],
49
+ { cwd: tempDir },
50
+ );
51
+ if (installResult.exitCode !== 0) {
52
+ ctx.ui.notify("Failed to download latest version", "error");
53
+ return;
54
+ }
55
+
56
+ const downloadedRoot = join(tempDir, "node_modules", "supipowers");
57
+ if (!existsSync(downloadedRoot)) {
58
+ ctx.ui.notify("Downloaded package not found", "error");
59
+ return;
60
+ }
61
+
62
+ // Clean previous installation
63
+ if (existsSync(extDir)) {
64
+ rmSync(extDir, { recursive: true });
65
+ }
66
+
67
+ // Copy extension files
68
+ mkdirSync(extDir, { recursive: true });
69
+ cpSync(join(downloadedRoot, "src"), join(extDir, "src"), { recursive: true });
70
+ cpSync(join(downloadedRoot, "package.json"), join(extDir, "package.json"));
71
+
72
+ // Copy skills
73
+ const skillsSource = join(downloadedRoot, "skills");
74
+ if (existsSync(skillsSource)) {
75
+ const skillDirs = readdirSync(skillsSource, { withFileTypes: true });
76
+ for (const entry of skillDirs) {
77
+ if (!entry.isDirectory()) continue;
78
+ const skillFile = join(skillsSource, entry.name, "SKILL.md");
79
+ if (!existsSync(skillFile)) continue;
80
+ const destDir = join(ompAgent, "skills", entry.name);
81
+ mkdirSync(destDir, { recursive: true });
82
+ cpSync(skillFile, join(destDir, "SKILL.md"));
83
+ }
84
+ }
85
+
86
+ ctx.ui.notify(`supipowers updated to v${latestVersion}`, "info");
87
+ } finally {
88
+ // Clean up temp directory
89
+ try {
90
+ rmSync(tempDir, { recursive: true });
91
+ } catch {
92
+ // best effort cleanup
93
+ }
94
+ }
95
+ },
96
+ });
97
+ }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import { registerRunCommand } from "./commands/run.js";
7
7
  import { registerReviewCommand } from "./commands/review.js";
8
8
  import { registerQaCommand } from "./commands/qa.js";
9
9
  import { registerReleaseCommand } from "./commands/release.js";
10
+ import { registerUpdateCommand } from "./commands/update.js";
10
11
 
11
12
  export default function supipowers(pi: ExtensionAPI): void {
12
13
  // Register all commands
@@ -18,6 +19,7 @@ export default function supipowers(pi: ExtensionAPI): void {
18
19
  registerReviewCommand(pi);
19
20
  registerQaCommand(pi);
20
21
  registerReleaseCommand(pi);
22
+ registerUpdateCommand(pi);
21
23
 
22
24
  // Session start
23
25
  pi.on("session_start", async (_event, ctx) => {