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 +46 -38
- package/package.json +1 -1
- package/src/commands/config.ts +43 -41
- package/src/commands/status.ts +28 -25
- package/src/commands/supi.ts +34 -31
- package/src/commands/update.ts +73 -70
- package/src/index.ts +31 -3
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
28
|
-
|
|
|
29
|
-
| `/supi`
|
|
30
|
-
| `/supi:plan`
|
|
31
|
-
| `/supi:run`
|
|
32
|
-
| `/supi:review`
|
|
33
|
-
| `/supi:qa`
|
|
34
|
-
| `/supi:release`
|
|
35
|
-
| `/supi:config`
|
|
36
|
-
| `/supi:status`
|
|
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 #
|
|
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 #
|
|
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
|
|
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
|
|
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,
|
|
119
|
-
"maxNestingDepth": 2,
|
|
120
|
-
"modelPreference": "auto"
|
|
121
|
+
"maxFixRetries": 2, // retry failed tasks
|
|
122
|
+
"maxNestingDepth": 2, // sub-agent nesting limit
|
|
123
|
+
"modelPreference": "auto"
|
|
121
124
|
},
|
|
122
125
|
"lsp": {
|
|
123
|
-
"
|
|
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.
|
|
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,
|
|
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,
|
|
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/
|
|
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
package/src/commands/config.ts
CHANGED
|
@@ -102,55 +102,57 @@ export function registerConfigCommand(pi: ExtensionAPI): void {
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
ctx.ui.setEditorText("");
|
|
106
|
+
void (async () => {
|
|
107
|
+
const settings = buildSettings(ctx.cwd);
|
|
106
108
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
const config = loadConfig(ctx.cwd);
|
|
109
|
+
while (true) {
|
|
110
|
+
const config = loadConfig(ctx.cwd);
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
112
|
+
const options = settings.map(
|
|
113
|
+
(s) => `${s.label}: ${s.get(config)}`
|
|
114
|
+
);
|
|
115
|
+
options.push("Done");
|
|
115
116
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
123
|
+
if (choice === undefined || choice === "Done") break;
|
|
123
124
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
const index = options.indexOf(choice);
|
|
126
|
+
const setting = settings[index];
|
|
127
|
+
if (!setting) break;
|
|
127
128
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
}
|
package/src/commands/status.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
await ctx.ui.select("Supipowers Status", options, {
|
|
41
|
+
helpText: "Esc to close",
|
|
42
|
+
});
|
|
43
|
+
})();
|
|
41
44
|
},
|
|
42
45
|
});
|
|
43
46
|
}
|
package/src/commands/supi.ts
CHANGED
|
@@ -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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
}
|
package/src/commands/update.ts
CHANGED
|
@@ -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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
27
|
+
ctx.ui.notify(`Current version: v${currentVersion}`, "info");
|
|
41
28
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
63
|
-
if (existsSync(extDir)) {
|
|
64
|
-
rmSync(extDir, { recursive: true });
|
|
65
|
-
}
|
|
42
|
+
ctx.ui.notify(`Updating v${currentVersion} → v${latestVersion}...`, "info");
|
|
66
43
|
|
|
67
|
-
//
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
//
|
|
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
|
-
|
|
27
|
-
|
|
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
|
}
|