supipowers 0.2.2 → 0.2.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "OMP-native workflow extension inspired by Superpowers.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -102,55 +102,57 @@ export function registerConfigCommand(pi: ExtensionAPI): void {
102
102
  return;
103
103
  }
104
104
 
105
- const settings = buildSettings(ctx.cwd);
105
+ ctx.ui.setEditorText("");
106
+ void (async () => {
107
+ const settings = buildSettings(ctx.cwd);
106
108
 
107
- // Main settings loop
108
- while (true) {
109
- const config = loadConfig(ctx.cwd);
109
+ while (true) {
110
+ const config = loadConfig(ctx.cwd);
110
111
 
111
- const options = settings.map(
112
- (s) => `${s.label}: ${s.get(config)}`
113
- );
114
- options.push("Done");
112
+ const options = settings.map(
113
+ (s) => `${s.label}: ${s.get(config)}`
114
+ );
115
+ options.push("Done");
115
116
 
116
- const choice = await ctx.ui.select(
117
- "Supipowers Settings",
118
- options,
119
- { helpText: "Select a setting to change · Esc to close" },
120
- );
117
+ const choice = await ctx.ui.select(
118
+ "Supipowers Settings",
119
+ options,
120
+ { helpText: "Select a setting to change · Esc to close" },
121
+ );
121
122
 
122
- if (choice === undefined || choice === "Done") break;
123
+ if (choice === undefined || choice === "Done") break;
123
124
 
124
- const index = options.indexOf(choice);
125
- const setting = settings[index];
126
- if (!setting) break;
125
+ const index = options.indexOf(choice);
126
+ const setting = settings[index];
127
+ if (!setting) break;
127
128
 
128
- if (setting.type === "select" && setting.options) {
129
- const value = await ctx.ui.select(
130
- setting.label,
131
- setting.options,
132
- { initialIndex: setting.options.indexOf(setting.get(config)) },
133
- );
134
- if (value !== undefined) {
135
- setting.set(ctx.cwd, value);
136
- ctx.ui.notify(`${setting.label} → ${value}`, "info");
137
- }
138
- } else if (setting.type === "toggle") {
139
- const current = setting.get(config);
140
- const newValue = current === "on" ? "off" : "on";
141
- setting.set(ctx.cwd, newValue);
142
- ctx.ui.notify(`${setting.label} → ${newValue}`, "info");
143
- } else if (setting.type === "text") {
144
- const value = await ctx.ui.input(
145
- setting.label,
146
- setting.get(config) === "not set" ? undefined : setting.get(config),
147
- );
148
- if (value !== undefined) {
149
- setting.set(ctx.cwd, value);
150
- ctx.ui.notify(`${setting.label} → ${value || "cleared"}`, "info");
129
+ if (setting.type === "select" && setting.options) {
130
+ const value = await ctx.ui.select(
131
+ setting.label,
132
+ setting.options,
133
+ { initialIndex: setting.options.indexOf(setting.get(config)) },
134
+ );
135
+ if (value !== undefined) {
136
+ setting.set(ctx.cwd, value);
137
+ ctx.ui.notify(`${setting.label} → ${value}`, "info");
138
+ }
139
+ } else if (setting.type === "toggle") {
140
+ const current = setting.get(config);
141
+ const newValue = current === "on" ? "off" : "on";
142
+ setting.set(ctx.cwd, newValue);
143
+ ctx.ui.notify(`${setting.label} → ${newValue}`, "info");
144
+ } else if (setting.type === "text") {
145
+ const value = await ctx.ui.input(
146
+ setting.label,
147
+ setting.get(config) === "not set" ? undefined : setting.get(config),
148
+ );
149
+ if (value !== undefined) {
150
+ setting.set(ctx.cwd, value);
151
+ ctx.ui.notify(`${setting.label} → ${value || "cleared"}`, "info");
152
+ }
151
153
  }
152
154
  }
153
- }
155
+ })();
154
156
  },
155
157
  });
156
158
  }
@@ -5,39 +5,42 @@ export function registerStatusCommand(pi: ExtensionAPI): void {
5
5
  pi.registerCommand("supi:status", {
6
6
  description: "Check on running sub-agents and task progress",
7
7
  async handler(_args, ctx) {
8
- const activeRun = findActiveRun(ctx.cwd);
8
+ ctx.ui.setEditorText("");
9
9
 
10
+ const activeRun = findActiveRun(ctx.cwd);
10
11
  if (!activeRun) {
11
12
  ctx.ui.notify("No active runs — use /supi:run to execute a plan", "info");
12
13
  return;
13
14
  }
14
15
 
15
- const results = loadAllAgentResults(ctx.cwd, activeRun.id);
16
- const totalTasks = activeRun.batches.reduce(
17
- (sum, b) => sum + b.taskIds.length,
18
- 0
19
- );
20
- const doneCount = results.filter((r) => r.status === "done").length;
21
- const concernCount = results.filter((r) => r.status === "done_with_concerns").length;
22
- const blockedCount = results.filter((r) => r.status === "blocked").length;
23
- const currentBatch = activeRun.batches.find((b) => b.status !== "completed");
16
+ void (async () => {
17
+ const results = loadAllAgentResults(ctx.cwd, activeRun.id);
18
+ const totalTasks = activeRun.batches.reduce(
19
+ (sum, b) => sum + b.taskIds.length,
20
+ 0
21
+ );
22
+ const doneCount = results.filter((r) => r.status === "done").length;
23
+ const concernCount = results.filter((r) => r.status === "done_with_concerns").length;
24
+ const blockedCount = results.filter((r) => r.status === "blocked").length;
25
+ const currentBatch = activeRun.batches.find((b) => b.status !== "completed");
24
26
 
25
- const options = [
26
- `Run: ${activeRun.id}`,
27
- `Status: ${activeRun.status}`,
28
- `Plan: ${activeRun.planRef}`,
29
- `Profile: ${activeRun.profile}`,
30
- `Progress: ${results.length}/${totalTasks} tasks`,
31
- ` Done: ${doneCount}`,
32
- ` With concerns: ${concernCount}`,
33
- ` Blocked: ${blockedCount}`,
34
- `Batch: ${currentBatch ? `#${currentBatch.index} (${currentBatch.status})` : "all complete"}`,
35
- "Close",
36
- ];
27
+ const options = [
28
+ `Run: ${activeRun.id}`,
29
+ `Status: ${activeRun.status}`,
30
+ `Plan: ${activeRun.planRef}`,
31
+ `Profile: ${activeRun.profile}`,
32
+ `Progress: ${results.length}/${totalTasks} tasks`,
33
+ ` Done: ${doneCount}`,
34
+ ` With concerns: ${concernCount}`,
35
+ ` Blocked: ${blockedCount}`,
36
+ `Batch: ${currentBatch ? `#${currentBatch.index} (${currentBatch.status})` : "all complete"}`,
37
+ "Close",
38
+ ];
37
39
 
38
- await ctx.ui.select("Supipowers Status", options, {
39
- helpText: "Esc to close",
40
- });
40
+ await ctx.ui.select("Supipowers Status", options, {
41
+ helpText: "Esc to close",
42
+ });
43
+ })();
41
44
  },
42
45
  });
43
46
  }
@@ -8,42 +8,45 @@ export function registerSupiCommand(pi: ExtensionAPI): void {
8
8
  pi.registerCommand("supi", {
9
9
  description: "Supipowers overview — show available commands and project status",
10
10
  async handler(_args, ctx) {
11
- const config = loadConfig(ctx.cwd);
12
- const activeRun = findActiveRun(ctx.cwd);
13
- const latestReport = loadLatestReport(ctx.cwd);
14
- const plans = listPlans(ctx.cwd);
11
+ ctx.ui.setEditorText("");
12
+ void (async () => {
13
+ const config = loadConfig(ctx.cwd);
14
+ const activeRun = findActiveRun(ctx.cwd);
15
+ const latestReport = loadLatestReport(ctx.cwd);
16
+ const plans = listPlans(ctx.cwd);
15
17
 
16
- const commands = [
17
- "/supi:plan — Start collaborative planning",
18
- "/supi:run — Execute a plan with sub-agents",
19
- "/supi:review — Run quality gates",
20
- "/supi:qa — Run QA pipeline",
21
- "/supi:release — Release automation",
22
- "/supi:config — Manage configuration",
23
- "/supi:status — Check running tasks",
24
- "/supi:update — Update to latest version",
25
- ];
18
+ const commands = [
19
+ "/supi:plan — Start collaborative planning",
20
+ "/supi:run — Execute a plan with sub-agents",
21
+ "/supi:review — Run quality gates",
22
+ "/supi:qa — Run QA pipeline",
23
+ "/supi:release — Release automation",
24
+ "/supi:config — Manage configuration",
25
+ "/supi:status — Check running tasks",
26
+ "/supi:update — Update to latest version",
27
+ ];
26
28
 
27
- const status = [
28
- `Profile: ${config.defaultProfile}`,
29
- `Plans: ${plans.length}`,
30
- `Active run: ${activeRun ? activeRun.id : "none"}`,
31
- `Last review: ${latestReport ? `${latestReport.timestamp.slice(0, 10)} (${latestReport.passed ? "passed" : "failed"})` : "none"}`,
32
- ];
29
+ const status = [
30
+ `Profile: ${config.defaultProfile}`,
31
+ `Plans: ${plans.length}`,
32
+ `Active run: ${activeRun ? activeRun.id : "none"}`,
33
+ `Last review: ${latestReport ? `${latestReport.timestamp.slice(0, 10)} (${latestReport.passed ? "passed" : "failed"})` : "none"}`,
34
+ ];
33
35
 
34
- const choice = await ctx.ui.select(
35
- "Supipowers",
36
- [...commands, "", ...status, "", "Close"],
37
- { helpText: "Select a command to run · Esc to close" },
38
- );
36
+ const choice = await ctx.ui.select(
37
+ "Supipowers",
38
+ [...commands, "", ...status, "", "Close"],
39
+ { helpText: "Select a command to run · Esc to close" },
40
+ );
39
41
 
40
- if (choice && choice.startsWith("/supi:")) {
41
- const cmdName = choice.split(" ")[0].slice(1); // remove leading /
42
- const handler = pi.getCommands().find((c) => c.name === cmdName);
43
- if (handler) {
44
- await handler.handler("", ctx);
42
+ if (choice && choice.startsWith("/supi:")) {
43
+ const cmdName = choice.split(" ")[0].slice(1); // remove leading /
44
+ const cmd = pi.getCommands().find((c) => c.name === cmdName);
45
+ if (cmd) {
46
+ await cmd.handler("", ctx);
47
+ }
45
48
  }
46
- }
49
+ })();
47
50
  },
48
51
  });
49
52
  }
@@ -7,91 +7,94 @@ export function registerUpdateCommand(pi: ExtensionAPI): void {
7
7
  pi.registerCommand("supi:update", {
8
8
  description: "Update supipowers to the latest version",
9
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");
10
+ ctx.ui.setEditorText("");
11
+ void (async () => {
12
+ const ompAgent = join(homedir(), ".omp", "agent");
13
+ const extDir = join(ompAgent, "extensions", "supipowers");
14
+ const installedPkgPath = join(extDir, "package.json");
13
15
 
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
16
+ // Get current installed version
17
+ let currentVersion = "unknown";
18
+ if (existsSync(installedPkgPath)) {
19
+ try {
20
+ const pkg = JSON.parse(readFileSync(installedPkgPath, "utf8"));
21
+ currentVersion = pkg.version;
22
+ } catch {
23
+ // corrupted — will update anyway
24
+ }
22
25
  }
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
26
 
40
- ctx.ui.notify(`Updating v${currentVersion} → v${latestVersion}...`, "info");
27
+ ctx.ui.notify(`Current version: v${currentVersion}`, "info");
41
28
 
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");
29
+ // Check latest version on npm
30
+ const checkResult = await pi.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() });
31
+ if (checkResult.exitCode !== 0) {
32
+ ctx.ui.notify("Failed to check for updates — npm view failed", "error");
53
33
  return;
54
34
  }
35
+ const latestVersion = checkResult.stdout.trim();
55
36
 
56
- const downloadedRoot = join(tempDir, "node_modules", "supipowers");
57
- if (!existsSync(downloadedRoot)) {
58
- ctx.ui.notify("Downloaded package not found", "error");
37
+ if (latestVersion === currentVersion) {
38
+ ctx.ui.notify(`supipowers v${currentVersion} is already up to date`, "info");
59
39
  return;
60
40
  }
61
41
 
62
- // Clean previous installation
63
- if (existsSync(extDir)) {
64
- rmSync(extDir, { recursive: true });
65
- }
42
+ ctx.ui.notify(`Updating v${currentVersion} v${latestVersion}...`, "info");
66
43
 
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"));
44
+ // Download latest to a temp directory
45
+ const tempDir = join(tmpdir(), `supipowers-update-${Date.now()}`);
46
+ mkdirSync(tempDir, { recursive: true });
71
47
 
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"));
48
+ try {
49
+ const installResult = await pi.exec(
50
+ "npm", ["install", "--prefix", tempDir, `supipowers@${latestVersion}`],
51
+ { cwd: tempDir },
52
+ );
53
+ if (installResult.exitCode !== 0) {
54
+ ctx.ui.notify("Failed to download latest version", "error");
55
+ return;
83
56
  }
84
- }
85
57
 
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
58
+ const downloadedRoot = join(tempDir, "node_modules", "supipowers");
59
+ if (!existsSync(downloadedRoot)) {
60
+ ctx.ui.notify("Downloaded package not found", "error");
61
+ return;
62
+ }
63
+
64
+ // Clean previous installation
65
+ if (existsSync(extDir)) {
66
+ rmSync(extDir, { recursive: true });
67
+ }
68
+
69
+ // Copy extension files
70
+ mkdirSync(extDir, { recursive: true });
71
+ cpSync(join(downloadedRoot, "src"), join(extDir, "src"), { recursive: true });
72
+ cpSync(join(downloadedRoot, "package.json"), join(extDir, "package.json"));
73
+
74
+ // Copy skills
75
+ const skillsSource = join(downloadedRoot, "skills");
76
+ if (existsSync(skillsSource)) {
77
+ const skillDirs = readdirSync(skillsSource, { withFileTypes: true });
78
+ for (const entry of skillDirs) {
79
+ if (!entry.isDirectory()) continue;
80
+ const skillFile = join(skillsSource, entry.name, "SKILL.md");
81
+ if (!existsSync(skillFile)) continue;
82
+ const destDir = join(ompAgent, "skills", entry.name);
83
+ mkdirSync(destDir, { recursive: true });
84
+ cpSync(skillFile, join(destDir, "SKILL.md"));
85
+ }
86
+ }
87
+
88
+ ctx.ui.notify(`supipowers updated to v${latestVersion}`, "info");
89
+ } finally {
90
+ // Clean up temp directory
91
+ try {
92
+ rmSync(tempDir, { recursive: true });
93
+ } catch {
94
+ // best effort cleanup
95
+ }
93
96
  }
94
- }
97
+ })();
95
98
  },
96
99
  });
97
100
  }
package/src/index.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
2
+ import { readFileSync, existsSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { homedir, tmpdir } from "node:os";
2
5
  import { registerSupiCommand } from "./commands/supi.js";
3
6
  import { registerConfigCommand } from "./commands/config.js";
4
7
  import { registerStatusCommand } from "./commands/status.js";
@@ -9,6 +12,16 @@ import { registerQaCommand } from "./commands/qa.js";
9
12
  import { registerReleaseCommand } from "./commands/release.js";
10
13
  import { registerUpdateCommand } from "./commands/update.js";
11
14
 
15
+ function getInstalledVersion(): string | null {
16
+ const pkgPath = join(homedir(), ".omp", "agent", "extensions", "supipowers", "package.json");
17
+ if (!existsSync(pkgPath)) return null;
18
+ try {
19
+ return JSON.parse(readFileSync(pkgPath, "utf8")).version;
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
12
25
  export default function supipowers(pi: ExtensionAPI): void {
13
26
  // Register all commands
14
27
  registerSupiCommand(pi);
@@ -23,8 +36,23 @@ export default function supipowers(pi: ExtensionAPI): void {
23
36
 
24
37
  // Session start
25
38
  pi.on("session_start", async (_event, ctx) => {
26
- if (ctx.hasUI) {
27
- ctx.ui.setStatus("supipowers", "supi ready");
28
- }
39
+ // Check for updates in the background
40
+ const currentVersion = getInstalledVersion();
41
+ if (!currentVersion) return;
42
+
43
+ pi.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() })
44
+ .then((result) => {
45
+ if (result.exitCode !== 0) return;
46
+ const latest = result.stdout.trim();
47
+ if (latest && latest !== currentVersion) {
48
+ ctx.ui.notify(
49
+ `supipowers v${latest} available (current: v${currentVersion}). Run /supi:update`,
50
+ "info",
51
+ );
52
+ }
53
+ })
54
+ .catch(() => {
55
+ // Network error — silently ignore
56
+ });
29
57
  });
30
58
  }