supipowers 0.2.2 → 0.2.4

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
@@ -10,30 +10,33 @@ Agentic workflows for [OMP](https://github.com/can1357/oh-my-pi). Plan features,
10
10
  bunx supipowers@latest
11
11
  ```
12
12
 
13
- The installer checks for OMP, helps you install it if needed, sets up supipowers, and optionally configures LSP servers for your project.
13
+ The installer checks for OMP, helps you install it if needed, copies supipowers into `~/.omp/agent/`, and optionally installs LSP servers for better code intelligence.
14
14
 
15
- ### Manual install
15
+ Re-running the installer updates supipowers if a newer version is available. Already up-to-date installs are detected and skipped.
16
16
 
17
- ```bash
18
- # Global (all projects)
19
- omp install npm:supipowers
17
+ ### Update from inside OMP
20
18
 
21
- # Project-local
22
- omp install npm:supipowers -l
23
19
  ```
20
+ /supi:update
21
+ ```
22
+
23
+ Checks npm for the latest version, downloads and installs it — no prompts, no restart needed.
24
24
 
25
25
  ## Commands
26
26
 
27
- | Command | What it does |
28
- | --------------- | ------------------------------------------------ |
29
- | `/supi` | Overviewavailable commands and project status |
30
- | `/supi:plan` | Collaborative planning with task breakdown |
31
- | `/supi:run` | Execute a plan with parallel sub-agents |
32
- | `/supi:review` | Quality gates at chosen depth |
33
- | `/supi:qa` | Run test suite and E2E pipeline |
34
- | `/supi:release` | Version bump, release notes, publish |
35
- | `/supi:config` | View and edit configuration |
36
- | `/supi:status` | Check running sub-agents and progress |
27
+ | Command | What it does |
28
+ | ---------------- | ------------------------------------------------ |
29
+ | `/supi` | Interactive menu — commands and project status |
30
+ | `/supi:plan` | Collaborative planning with task breakdown |
31
+ | `/supi:run` | Execute a plan with parallel sub-agents |
32
+ | `/supi:review` | Quality gates at chosen depth |
33
+ | `/supi:qa` | Run test suite and E2E pipeline |
34
+ | `/supi:release` | Version bump, release notes, publish |
35
+ | `/supi:config` | Interactive settings (TUI) |
36
+ | `/supi:status` | Check running sub-agents and progress |
37
+ | `/supi:update` | Update supipowers to latest version |
38
+
39
+ Commands like `/supi`, `/supi:config`, `/supi:status`, and `/supi:update` open native OMP TUI dialogs — they don't send chat messages or trigger the AI.
37
40
 
38
41
  ### Planning
39
42
 
@@ -62,21 +65,23 @@ The orchestration loop: dispatch batch → collect results → detect conflicts
62
65
  ### Quality review
63
66
 
64
67
  ```
65
- /supi:review # uses default profile
68
+ /supi:review # opens profile picker
66
69
  /supi:review --quick # fast: LSP diagnostics + AI scan
67
70
  /supi:review --thorough # deep: full AI review + code quality
68
71
  /supi:review --full # everything: tests + E2E + all gates
69
72
  ```
70
73
 
74
+ When no flag is provided, a TUI picker lets you choose the review profile interactively.
75
+
71
76
  ### QA
72
77
 
73
78
  ```
74
- /supi:qa # full test suite
79
+ /supi:qa # opens scope picker
75
80
  /supi:qa --changed # tests for changed files only
76
81
  /supi:qa --e2e # Playwright / E2E only
77
82
  ```
78
83
 
79
- Detects your test framework on first run (vitest, jest, pytest, cargo test, go test) and caches it.
84
+ Detects your test framework on first run (vitest, jest, pytest, cargo test, go test) and caches it. When no flag is provided, a TUI picker lets you choose the scope.
80
85
 
81
86
  ### Release
82
87
 
@@ -84,18 +89,16 @@ Detects your test framework on first run (vitest, jest, pytest, cargo test, go t
84
89
  /supi:release
85
90
  ```
86
91
 
87
- Analyzes commits since last tag, suggests a version bump, generates release notes, and publishes (npm, GitHub release, or manual — configured on first run).
92
+ Analyzes commits since last tag, suggests a version bump, generates release notes, and publishes. On first run, a TUI picker lets you choose your pipeline (npm, GitHub release, or manual).
88
93
 
89
94
  ## Configuration
90
95
 
91
- Layered config: project (`.omp/supipowers/config.json`) overrides global (`~/.omp/supipowers/config.json`).
92
-
93
96
  ```
94
- /supi:config # view current config
95
- /supi:config set orchestration.maxParallelAgents 5
96
- /supi:config set defaultProfile thorough
97
+ /supi:config
97
98
  ```
98
99
 
100
+ Opens an interactive settings screen with all configuration options. Select a setting to change its value — toggles flip instantly, selects open a picker, text fields open an input dialog.
101
+
99
102
  ### Profiles
100
103
 
101
104
  Three built-in profiles control quality gate depth:
@@ -115,21 +118,22 @@ Create custom profiles in `.omp/supipowers/profiles/`.
115
118
  "defaultProfile": "thorough",
116
119
  "orchestration": {
117
120
  "maxParallelAgents": 3, // concurrent sub-agents per batch
118
- "maxFixRetries": 2, // retry failed tasks
119
- "maxNestingDepth": 2, // sub-agent nesting limit
120
- "modelPreference": "auto", // "auto" | "fast" | "capable" | "<model-id>"
121
+ "maxFixRetries": 2, // retry failed tasks
122
+ "maxNestingDepth": 2, // sub-agent nesting limit
123
+ "modelPreference": "auto"
121
124
  },
122
125
  "lsp": {
123
- "autoDetect": true,
124
- "setupGuide": true,
126
+ "setupGuide": true
125
127
  },
126
128
  "qa": {
127
129
  "framework": null, // auto-detected and cached
128
- "command": null,
129
- },
130
+ "command": null
131
+ }
130
132
  }
131
133
  ```
132
134
 
135
+ Config is stored in `~/.omp/agent/extensions/supipowers/` and managed entirely through `/supi:config`.
136
+
133
137
  ## How it works
134
138
 
135
139
  Supipowers is built on OMP's extension API. Every command is an immediate action — no state machine, no workflow phases.
@@ -138,17 +142,21 @@ Supipowers is built on OMP's extension API. Every command is an immediate action
138
142
 
139
143
  **Quality gates**: Composable checks selected by profile. LSP diagnostics feed real type errors. AI review catches logic issues. Test gates run your actual test suite. Gates report issues with severity levels (error/warning/info).
140
144
 
141
- **LSP integration**: Sub-agents query LSP before making changes (find references, check diagnostics). If no LSP is active, everything still works — just better with it. Run `/supi:config` for setup guidance.
145
+ **LSP integration**: Sub-agents query LSP before making changes (find references, check diagnostics). If no LSP is active, everything still works — just better with it. The installer offers to set up LSP servers during installation.
146
+
147
+ **Update checking**: On session start, supipowers checks npm for a newer version in the background. If one is available, a notification tells you to run `/supi:update`.
142
148
 
143
149
  ## Project structure
144
150
 
145
151
  ```
146
152
  src/
147
- index.ts # extension entry point
153
+ index.ts # extension entry point + update checker
148
154
  commands/ # slash command handlers
149
- supi.ts, plan.ts, run.ts, review.ts, qa.ts, release.ts, config.ts, status.ts
155
+ supi.ts, plan.ts, run.ts, review.ts, qa.ts, release.ts,
156
+ config.ts, status.ts, update.ts
150
157
  orchestrator/ # sub-agent dispatch & coordination
151
- batch-scheduler.ts, dispatcher.ts, result-collector.ts, conflict-resolver.ts, prompts.ts
158
+ batch-scheduler.ts, dispatcher.ts, result-collector.ts,
159
+ conflict-resolver.ts, prompts.ts
152
160
  quality/ # composable quality gates
153
161
  gate-runner.ts, lsp-gate.ts, ai-review-gate.ts, test-gate.ts
154
162
  qa/ # QA pipeline
@@ -176,7 +184,7 @@ bin/
176
184
  ## Development
177
185
 
178
186
  ```bash
179
- git clone https://github.com/pedromendes/supipowers.git
187
+ git clone https://github.com/ogrodev/supipowers.git
180
188
  cd supipowers
181
189
  bun install
182
190
  bun run test # run tests
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supipowers",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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
  }