supipowers 0.2.5 → 0.2.7
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 +1 -1
- package/src/commands/config.ts +99 -67
- package/src/commands/status.ts +40 -39
- package/src/commands/supi.ts +45 -42
- package/src/commands/update.ts +85 -83
- package/src/index.ts +30 -5
package/package.json
CHANGED
package/src/commands/config.ts
CHANGED
|
@@ -1,12 +1,31 @@
|
|
|
1
|
-
import type { ExtensionAPI,
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { loadConfig, updateConfig } from "../config/loader.js";
|
|
3
3
|
import { listProfiles } from "../config/profiles.js";
|
|
4
4
|
import type { SupipowersConfig } from "../types.js";
|
|
5
5
|
|
|
6
|
+
const FRAMEWORK_OPTIONS = [
|
|
7
|
+
{ value: "", label: "not set — auto-detect on first /supi:qa run", command: null },
|
|
8
|
+
{ value: "vitest", label: "vitest — npx vitest run", command: "npx vitest run" },
|
|
9
|
+
{ value: "jest", label: "jest — npx jest", command: "npx jest" },
|
|
10
|
+
{ value: "mocha", label: "mocha — npx mocha", command: "npx mocha" },
|
|
11
|
+
{ value: "pytest", label: "pytest — pytest", command: "pytest" },
|
|
12
|
+
{ value: "cargo-test", label: "cargo-test — cargo test", command: "cargo test" },
|
|
13
|
+
{ value: "go-test", label: "go-test — go test ./...", command: "go test ./..." },
|
|
14
|
+
{ value: "npm-test", label: "npm-test — npm test", command: "npm test" },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const PIPELINE_OPTIONS = [
|
|
18
|
+
{ value: "", label: "not set — choose on first /supi:release run" },
|
|
19
|
+
{ value: "npm", label: "npm — npm publish to registry" },
|
|
20
|
+
{ value: "github", label: "github — GitHub Release with gh CLI" },
|
|
21
|
+
{ value: "manual", label: "manual — I'll handle publishing myself" },
|
|
22
|
+
];
|
|
23
|
+
|
|
6
24
|
interface SettingDef {
|
|
7
25
|
label: string;
|
|
8
26
|
key: string;
|
|
9
|
-
|
|
27
|
+
helpText: string;
|
|
28
|
+
type: "select" | "toggle";
|
|
10
29
|
options?: string[];
|
|
11
30
|
get: (config: SupipowersConfig) => string;
|
|
12
31
|
set: (cwd: string, value: unknown) => void;
|
|
@@ -17,6 +36,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
17
36
|
{
|
|
18
37
|
label: "Default profile",
|
|
19
38
|
key: "defaultProfile",
|
|
39
|
+
helpText: "Review depth used when no flag is passed to /supi:review",
|
|
20
40
|
type: "select",
|
|
21
41
|
options: listProfiles(cwd),
|
|
22
42
|
get: (c) => c.defaultProfile,
|
|
@@ -25,6 +45,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
25
45
|
{
|
|
26
46
|
label: "Max parallel agents",
|
|
27
47
|
key: "orchestration.maxParallelAgents",
|
|
48
|
+
helpText: "Sub-agents running concurrently in each /supi:run batch",
|
|
28
49
|
type: "select",
|
|
29
50
|
options: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"],
|
|
30
51
|
get: (c) => String(c.orchestration.maxParallelAgents),
|
|
@@ -33,6 +54,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
33
54
|
{
|
|
34
55
|
label: "Max fix retries",
|
|
35
56
|
key: "orchestration.maxFixRetries",
|
|
57
|
+
helpText: "Times a failed task is retried before marking it blocked",
|
|
36
58
|
type: "select",
|
|
37
59
|
options: ["0", "1", "2", "3", "4", "5"],
|
|
38
60
|
get: (c) => String(c.orchestration.maxFixRetries),
|
|
@@ -41,6 +63,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
41
63
|
{
|
|
42
64
|
label: "Max nesting depth",
|
|
43
65
|
key: "orchestration.maxNestingDepth",
|
|
66
|
+
helpText: "How deep sub-agents can spawn other sub-agents",
|
|
44
67
|
type: "select",
|
|
45
68
|
options: ["0", "1", "2", "3", "4", "5"],
|
|
46
69
|
get: (c) => String(c.orchestration.maxNestingDepth),
|
|
@@ -49,6 +72,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
49
72
|
{
|
|
50
73
|
label: "Model preference",
|
|
51
74
|
key: "orchestration.modelPreference",
|
|
75
|
+
helpText: "Which model sub-agents use for code generation",
|
|
52
76
|
type: "select",
|
|
53
77
|
options: ["auto", "fast", "balanced", "quality"],
|
|
54
78
|
get: (c) => c.orchestration.modelPreference,
|
|
@@ -57,6 +81,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
57
81
|
{
|
|
58
82
|
label: "LSP setup guide",
|
|
59
83
|
key: "lsp.setupGuide",
|
|
84
|
+
helpText: "Show LSP setup tips when no language server is active",
|
|
60
85
|
type: "toggle",
|
|
61
86
|
get: (c) => c.lsp.setupGuide ? "on" : "off",
|
|
62
87
|
set: (d, v) => updateConfig(d, { lsp: { setupGuide: v === "on" } }),
|
|
@@ -64,6 +89,7 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
64
89
|
{
|
|
65
90
|
label: "Notification verbosity",
|
|
66
91
|
key: "notifications.verbosity",
|
|
92
|
+
helpText: "How much detail supipowers shows in notifications",
|
|
67
93
|
type: "select",
|
|
68
94
|
options: ["quiet", "normal", "verbose"],
|
|
69
95
|
get: (c) => c.notifications.verbosity,
|
|
@@ -72,88 +98,94 @@ function buildSettings(cwd: string): SettingDef[] {
|
|
|
72
98
|
{
|
|
73
99
|
label: "QA framework",
|
|
74
100
|
key: "qa.framework",
|
|
75
|
-
|
|
101
|
+
helpText: "Test runner used by /supi:qa",
|
|
102
|
+
type: "select",
|
|
103
|
+
options: FRAMEWORK_OPTIONS.map((f) => f.label),
|
|
76
104
|
get: (c) => c.qa.framework ?? "not set",
|
|
77
|
-
set: (d, v) =>
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
get: (c) => c.qa.command ?? "not set",
|
|
84
|
-
set: (d, v) => updateConfig(d, { qa: { command: v || null } }),
|
|
105
|
+
set: (d, v) => {
|
|
106
|
+
const chosen = FRAMEWORK_OPTIONS.find((f) => f.label === v);
|
|
107
|
+
if (chosen) {
|
|
108
|
+
updateConfig(d, { qa: { framework: chosen.value || null, command: chosen.command } });
|
|
109
|
+
}
|
|
110
|
+
},
|
|
85
111
|
},
|
|
86
112
|
{
|
|
87
113
|
label: "Release pipeline",
|
|
88
114
|
key: "release.pipeline",
|
|
89
|
-
|
|
115
|
+
helpText: "How /supi:release publishes your project",
|
|
116
|
+
type: "select",
|
|
117
|
+
options: PIPELINE_OPTIONS.map((p) => p.label),
|
|
90
118
|
get: (c) => c.release.pipeline ?? "not set",
|
|
91
|
-
set: (d, v) =>
|
|
119
|
+
set: (d, v) => {
|
|
120
|
+
const chosen = PIPELINE_OPTIONS.find((p) => p.label === v);
|
|
121
|
+
if (chosen) {
|
|
122
|
+
updateConfig(d, { release: { pipeline: chosen.value || null } });
|
|
123
|
+
}
|
|
124
|
+
},
|
|
92
125
|
},
|
|
93
126
|
];
|
|
94
127
|
}
|
|
95
128
|
|
|
96
|
-
export function
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (!ctx.hasUI) {
|
|
102
|
-
ctx.ui.notify("Config UI requires interactive mode", "warning");
|
|
103
|
-
return;
|
|
104
|
-
}
|
|
129
|
+
export function handleConfig(ctx: ExtensionContext): void {
|
|
130
|
+
if (!ctx.hasUI) {
|
|
131
|
+
ctx.ui.notify("Config UI requires interactive mode", "warning");
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
105
134
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const settings = buildSettings(ctx.cwd);
|
|
135
|
+
void (async () => {
|
|
136
|
+
const settings = buildSettings(ctx.cwd);
|
|
109
137
|
|
|
110
|
-
|
|
111
|
-
|
|
138
|
+
while (true) {
|
|
139
|
+
const config = loadConfig(ctx.cwd);
|
|
112
140
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
141
|
+
const options = settings.map(
|
|
142
|
+
(s) => `${s.label}: ${s.get(config)}`
|
|
143
|
+
);
|
|
144
|
+
options.push("Done");
|
|
117
145
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
146
|
+
const choice = await ctx.ui.select(
|
|
147
|
+
"Supipowers Settings",
|
|
148
|
+
options,
|
|
149
|
+
{ helpText: "Select a setting to change · Esc to close" },
|
|
150
|
+
);
|
|
123
151
|
|
|
124
|
-
|
|
152
|
+
if (choice === undefined || choice === "Done") break;
|
|
125
153
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
154
|
+
const index = options.indexOf(choice);
|
|
155
|
+
const setting = settings[index];
|
|
156
|
+
if (!setting) break;
|
|
129
157
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
} else if (setting.type === "text") {
|
|
146
|
-
const value = await ctx.ui.input(
|
|
147
|
-
setting.label,
|
|
148
|
-
setting.get(config) === "not set" ? undefined : setting.get(config),
|
|
149
|
-
);
|
|
150
|
-
if (value !== undefined) {
|
|
151
|
-
setting.set(ctx.cwd, value);
|
|
152
|
-
ctx.ui.notify(`${setting.label} → ${value || "cleared"}`, "info");
|
|
153
|
-
}
|
|
154
|
-
}
|
|
158
|
+
if (setting.type === "select" && setting.options) {
|
|
159
|
+
const currentValue = setting.get(config);
|
|
160
|
+
const currentIndex = setting.options.findIndex((o) => o.startsWith(currentValue));
|
|
161
|
+
const value = await ctx.ui.select(
|
|
162
|
+
setting.label,
|
|
163
|
+
setting.options,
|
|
164
|
+
{
|
|
165
|
+
initialIndex: Math.max(0, currentIndex),
|
|
166
|
+
helpText: setting.helpText,
|
|
167
|
+
},
|
|
168
|
+
);
|
|
169
|
+
if (value !== undefined) {
|
|
170
|
+
setting.set(ctx.cwd, value);
|
|
171
|
+
const display = value.split(" — ")[0];
|
|
172
|
+
ctx.ui.notify(`${setting.label} → ${display}`, "info");
|
|
155
173
|
}
|
|
156
|
-
}
|
|
157
|
-
|
|
174
|
+
} else if (setting.type === "toggle") {
|
|
175
|
+
const current = setting.get(config);
|
|
176
|
+
const newValue = current === "on" ? "off" : "on";
|
|
177
|
+
setting.set(ctx.cwd, newValue);
|
|
178
|
+
ctx.ui.notify(`${setting.label} → ${newValue}`, "info");
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
})();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export function registerConfigCommand(pi: ExtensionAPI): void {
|
|
185
|
+
pi.registerCommand("supi:config", {
|
|
186
|
+
description: "View and manage Supipowers configuration",
|
|
187
|
+
async handler(_args, ctx) {
|
|
188
|
+
handleConfig(ctx);
|
|
189
|
+
},
|
|
158
190
|
});
|
|
159
191
|
}
|
package/src/commands/status.ts
CHANGED
|
@@ -1,47 +1,48 @@
|
|
|
1
|
-
import type { ExtensionAPI,
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { findActiveRun, loadAllAgentResults } from "../storage/runs.js";
|
|
3
3
|
|
|
4
|
-
export function
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
export function handleStatus(ctx: ExtensionContext): void {
|
|
5
|
+
const activeRun = findActiveRun(ctx.cwd);
|
|
6
|
+
if (!activeRun) {
|
|
7
|
+
ctx.ui.notify("No active runs — use /supi:run to execute a plan", "info");
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
11
|
+
void (async () => {
|
|
12
|
+
const results = loadAllAgentResults(ctx.cwd, activeRun.id);
|
|
13
|
+
const totalTasks = activeRun.batches.reduce(
|
|
14
|
+
(sum, b) => sum + b.taskIds.length,
|
|
15
|
+
0
|
|
16
|
+
);
|
|
17
|
+
const doneCount = results.filter((r) => r.status === "done").length;
|
|
18
|
+
const concernCount = results.filter((r) => r.status === "done_with_concerns").length;
|
|
19
|
+
const blockedCount = results.filter((r) => r.status === "blocked").length;
|
|
20
|
+
const currentBatch = activeRun.batches.find((b) => b.status !== "completed");
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
const options = [
|
|
23
|
+
`Run: ${activeRun.id}`,
|
|
24
|
+
`Status: ${activeRun.status}`,
|
|
25
|
+
`Plan: ${activeRun.planRef}`,
|
|
26
|
+
`Profile: ${activeRun.profile}`,
|
|
27
|
+
`Progress: ${results.length}/${totalTasks} tasks`,
|
|
28
|
+
` Done: ${doneCount}`,
|
|
29
|
+
` With concerns: ${concernCount}`,
|
|
30
|
+
` Blocked: ${blockedCount}`,
|
|
31
|
+
`Batch: ${currentBatch ? `#${currentBatch.index} (${currentBatch.status})` : "all complete"}`,
|
|
32
|
+
"Close",
|
|
33
|
+
];
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
`Progress: ${results.length}/${totalTasks} tasks`,
|
|
34
|
-
` Done: ${doneCount}`,
|
|
35
|
-
` With concerns: ${concernCount}`,
|
|
36
|
-
` Blocked: ${blockedCount}`,
|
|
37
|
-
`Batch: ${currentBatch ? `#${currentBatch.index} (${currentBatch.status})` : "all complete"}`,
|
|
38
|
-
"Close",
|
|
39
|
-
];
|
|
35
|
+
await ctx.ui.select("Supipowers Status", options, {
|
|
36
|
+
helpText: "Esc to close",
|
|
37
|
+
});
|
|
38
|
+
})();
|
|
39
|
+
}
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
export function registerStatusCommand(pi: ExtensionAPI): void {
|
|
42
|
+
pi.registerCommand("supi:status", {
|
|
43
|
+
description: "Check on running sub-agents and task progress",
|
|
44
|
+
async handler(_args, ctx) {
|
|
45
|
+
handleStatus(ctx);
|
|
46
|
+
},
|
|
46
47
|
});
|
|
47
48
|
}
|
package/src/commands/supi.ts
CHANGED
|
@@ -1,53 +1,56 @@
|
|
|
1
|
-
import type { ExtensionAPI,
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { loadConfig } from "../config/loader.js";
|
|
3
3
|
import { findActiveRun } from "../storage/runs.js";
|
|
4
4
|
import { loadLatestReport } from "../storage/reports.js";
|
|
5
5
|
import { listPlans } from "../storage/plans.js";
|
|
6
6
|
|
|
7
|
-
export function
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
export function handleSupi(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
8
|
+
void (async () => {
|
|
9
|
+
const config = loadConfig(ctx.cwd);
|
|
10
|
+
const activeRun = findActiveRun(ctx.cwd);
|
|
11
|
+
const latestReport = loadLatestReport(ctx.cwd);
|
|
12
|
+
const plans = listPlans(ctx.cwd);
|
|
13
|
+
|
|
14
|
+
const commands = [
|
|
15
|
+
"/supi:plan — Start collaborative planning",
|
|
16
|
+
"/supi:run — Execute a plan with sub-agents",
|
|
17
|
+
"/supi:review — Run quality gates",
|
|
18
|
+
"/supi:qa — Run QA pipeline",
|
|
19
|
+
"/supi:release — Release automation",
|
|
20
|
+
"/supi:config — Manage configuration",
|
|
21
|
+
"/supi:status — Check running tasks",
|
|
22
|
+
"/supi:update — Update to latest version",
|
|
23
|
+
];
|
|
18
24
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"/supi:config — Manage configuration",
|
|
26
|
-
"/supi:status — Check running tasks",
|
|
27
|
-
"/supi:update — Update to latest version",
|
|
28
|
-
];
|
|
25
|
+
const status = [
|
|
26
|
+
`Profile: ${config.defaultProfile}`,
|
|
27
|
+
`Plans: ${plans.length}`,
|
|
28
|
+
`Active run: ${activeRun ? activeRun.id : "none"}`,
|
|
29
|
+
`Last review: ${latestReport ? `${latestReport.timestamp.slice(0, 10)} (${latestReport.passed ? "passed" : "failed"})` : "none"}`,
|
|
30
|
+
];
|
|
29
31
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
];
|
|
32
|
+
const choice = await ctx.ui.select(
|
|
33
|
+
"Supipowers",
|
|
34
|
+
[...commands, "", ...status, "", "Close"],
|
|
35
|
+
{ helpText: "Select a command to run · Esc to close" },
|
|
36
|
+
);
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
);
|
|
38
|
+
if (choice && choice.startsWith("/supi:")) {
|
|
39
|
+
const cmdName = choice.split(" ")[0].slice(1); // remove leading /
|
|
40
|
+
const cmd = pi.getCommands().find((c) => c.name === cmdName);
|
|
41
|
+
if (cmd) {
|
|
42
|
+
await cmd.handler("", ctx as any);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
})();
|
|
46
|
+
}
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})();
|
|
51
|
-
}) as any,
|
|
48
|
+
export function registerSupiCommand(pi: ExtensionAPI): void {
|
|
49
|
+
pi.registerCommand("supi", {
|
|
50
|
+
description: "Supipowers overview — show available commands and project status",
|
|
51
|
+
async handler(_args, ctx) {
|
|
52
|
+
// Handled via input event interception — this is a fallback for non-interactive contexts
|
|
53
|
+
handleSupi(pi, ctx);
|
|
54
|
+
},
|
|
52
55
|
});
|
|
53
56
|
}
|
package/src/commands/update.ts
CHANGED
|
@@ -1,101 +1,103 @@
|
|
|
1
|
-
import type { ExtensionAPI,
|
|
1
|
+
import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
2
2
|
import { readFileSync, existsSync, mkdirSync, cpSync, rmSync, readdirSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
5
|
|
|
6
|
-
export function
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ctx.ui.setEditorText("");
|
|
12
|
-
void (async () => {
|
|
13
|
-
const ompAgent = join(homedir(), ".omp", "agent");
|
|
14
|
-
const extDir = join(ompAgent, "extensions", "supipowers");
|
|
15
|
-
const installedPkgPath = join(extDir, "package.json");
|
|
6
|
+
export function handleUpdate(pi: ExtensionAPI, ctx: ExtensionContext): void {
|
|
7
|
+
void (async () => {
|
|
8
|
+
const ompAgent = join(homedir(), ".omp", "agent");
|
|
9
|
+
const extDir = join(ompAgent, "extensions", "supipowers");
|
|
10
|
+
const installedPkgPath = join(extDir, "package.json");
|
|
16
11
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
// Get current installed version
|
|
13
|
+
let currentVersion = "unknown";
|
|
14
|
+
if (existsSync(installedPkgPath)) {
|
|
15
|
+
try {
|
|
16
|
+
const pkg = JSON.parse(readFileSync(installedPkgPath, "utf8"));
|
|
17
|
+
currentVersion = pkg.version;
|
|
18
|
+
} catch {
|
|
19
|
+
// corrupted — will update anyway
|
|
20
|
+
}
|
|
21
|
+
}
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
ctx.ui.notify(`Current version: v${currentVersion}`, "info");
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (latestVersion === currentVersion) {
|
|
39
|
-
ctx.ui.notify(`supipowers v${currentVersion} is already up to date`, "info");
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
25
|
+
// Check latest version on npm
|
|
26
|
+
const checkResult = await pi.exec("npm", ["view", "supipowers", "version"], { cwd: tmpdir() });
|
|
27
|
+
if (checkResult.exitCode !== 0) {
|
|
28
|
+
ctx.ui.notify("Failed to check for updates — npm view failed", "error");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const latestVersion = checkResult.stdout.trim();
|
|
42
32
|
|
|
43
|
-
|
|
33
|
+
if (latestVersion === currentVersion) {
|
|
34
|
+
ctx.ui.notify(`supipowers v${currentVersion} is already up to date`, "info");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
const tempDir = join(tmpdir(), `supipowers-update-${Date.now()}`);
|
|
47
|
-
mkdirSync(tempDir, { recursive: true });
|
|
38
|
+
ctx.ui.notify(`Updating v${currentVersion} → v${latestVersion}...`, "info");
|
|
48
39
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
{ cwd: tempDir },
|
|
53
|
-
);
|
|
54
|
-
if (installResult.exitCode !== 0) {
|
|
55
|
-
ctx.ui.notify("Failed to download latest version", "error");
|
|
56
|
-
return;
|
|
57
|
-
}
|
|
40
|
+
// Download latest to a temp directory
|
|
41
|
+
const tempDir = join(tmpdir(), `supipowers-update-${Date.now()}`);
|
|
42
|
+
mkdirSync(tempDir, { recursive: true });
|
|
58
43
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
44
|
+
try {
|
|
45
|
+
const installResult = await pi.exec(
|
|
46
|
+
"npm", ["install", "--prefix", tempDir, `supipowers@${latestVersion}`],
|
|
47
|
+
{ cwd: tempDir },
|
|
48
|
+
);
|
|
49
|
+
if (installResult.exitCode !== 0) {
|
|
50
|
+
ctx.ui.notify("Failed to download latest version", "error");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
64
53
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
54
|
+
const downloadedRoot = join(tempDir, "node_modules", "supipowers");
|
|
55
|
+
if (!existsSync(downloadedRoot)) {
|
|
56
|
+
ctx.ui.notify("Downloaded package not found", "error");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
60
|
+
// Clean previous installation
|
|
61
|
+
if (existsSync(extDir)) {
|
|
62
|
+
rmSync(extDir, { recursive: true });
|
|
63
|
+
}
|
|
74
64
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
for (const entry of skillDirs) {
|
|
80
|
-
if (!entry.isDirectory()) continue;
|
|
81
|
-
const skillFile = join(skillsSource, entry.name, "SKILL.md");
|
|
82
|
-
if (!existsSync(skillFile)) continue;
|
|
83
|
-
const destDir = join(ompAgent, "skills", entry.name);
|
|
84
|
-
mkdirSync(destDir, { recursive: true });
|
|
85
|
-
cpSync(skillFile, join(destDir, "SKILL.md"));
|
|
86
|
-
}
|
|
87
|
-
}
|
|
65
|
+
// Copy extension files
|
|
66
|
+
mkdirSync(extDir, { recursive: true });
|
|
67
|
+
cpSync(join(downloadedRoot, "src"), join(extDir, "src"), { recursive: true });
|
|
68
|
+
cpSync(join(downloadedRoot, "package.json"), join(extDir, "package.json"));
|
|
88
69
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
// Copy skills
|
|
71
|
+
const skillsSource = join(downloadedRoot, "skills");
|
|
72
|
+
if (existsSync(skillsSource)) {
|
|
73
|
+
const skillDirs = readdirSync(skillsSource, { withFileTypes: true });
|
|
74
|
+
for (const entry of skillDirs) {
|
|
75
|
+
if (!entry.isDirectory()) continue;
|
|
76
|
+
const skillFile = join(skillsSource, entry.name, "SKILL.md");
|
|
77
|
+
if (!existsSync(skillFile)) continue;
|
|
78
|
+
const destDir = join(ompAgent, "skills", entry.name);
|
|
79
|
+
mkdirSync(destDir, { recursive: true });
|
|
80
|
+
cpSync(skillFile, join(destDir, "SKILL.md"));
|
|
97
81
|
}
|
|
98
|
-
}
|
|
99
|
-
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ctx.ui.notify(`supipowers updated to v${latestVersion}`, "info");
|
|
85
|
+
} finally {
|
|
86
|
+
// Clean up temp directory
|
|
87
|
+
try {
|
|
88
|
+
rmSync(tempDir, { recursive: true });
|
|
89
|
+
} catch {
|
|
90
|
+
// best effort cleanup
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
})();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function registerUpdateCommand(pi: ExtensionAPI): void {
|
|
97
|
+
pi.registerCommand("supi:update", {
|
|
98
|
+
description: "Update supipowers to the latest version",
|
|
99
|
+
async handler(_args, ctx) {
|
|
100
|
+
handleUpdate(pi, ctx);
|
|
101
|
+
},
|
|
100
102
|
});
|
|
101
103
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,15 +2,24 @@ import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
|
|
|
2
2
|
import { readFileSync, existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { homedir, tmpdir } from "node:os";
|
|
5
|
-
import { registerSupiCommand } from "./commands/supi.js";
|
|
6
|
-
import { registerConfigCommand } from "./commands/config.js";
|
|
7
|
-
import { registerStatusCommand } from "./commands/status.js";
|
|
5
|
+
import { registerSupiCommand, handleSupi } from "./commands/supi.js";
|
|
6
|
+
import { registerConfigCommand, handleConfig } from "./commands/config.js";
|
|
7
|
+
import { registerStatusCommand, handleStatus } from "./commands/status.js";
|
|
8
8
|
import { registerPlanCommand } from "./commands/plan.js";
|
|
9
9
|
import { registerRunCommand } from "./commands/run.js";
|
|
10
10
|
import { registerReviewCommand } from "./commands/review.js";
|
|
11
11
|
import { registerQaCommand } from "./commands/qa.js";
|
|
12
12
|
import { registerReleaseCommand } from "./commands/release.js";
|
|
13
|
-
import { registerUpdateCommand } from "./commands/update.js";
|
|
13
|
+
import { registerUpdateCommand, handleUpdate } from "./commands/update.js";
|
|
14
|
+
|
|
15
|
+
// TUI-only commands — intercepted at the input level to prevent
|
|
16
|
+
// message submission and "Working..." indicator
|
|
17
|
+
const TUI_COMMANDS: Record<string, (pi: ExtensionAPI, ctx: any) => void> = {
|
|
18
|
+
"supi": (pi, ctx) => handleSupi(pi, ctx),
|
|
19
|
+
"supi:config": (_pi, ctx) => handleConfig(ctx),
|
|
20
|
+
"supi:status": (_pi, ctx) => handleStatus(ctx),
|
|
21
|
+
"supi:update": (pi, ctx) => handleUpdate(pi, ctx),
|
|
22
|
+
};
|
|
14
23
|
|
|
15
24
|
function getInstalledVersion(): string | null {
|
|
16
25
|
const pkgPath = join(homedir(), ".omp", "agent", "extensions", "supipowers", "package.json");
|
|
@@ -23,7 +32,7 @@ function getInstalledVersion(): string | null {
|
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
export default function supipowers(pi: ExtensionAPI): void {
|
|
26
|
-
// Register all commands
|
|
35
|
+
// Register all commands (needed for autocomplete)
|
|
27
36
|
registerSupiCommand(pi);
|
|
28
37
|
registerConfigCommand(pi);
|
|
29
38
|
registerStatusCommand(pi);
|
|
@@ -34,6 +43,22 @@ export default function supipowers(pi: ExtensionAPI): void {
|
|
|
34
43
|
registerReleaseCommand(pi);
|
|
35
44
|
registerUpdateCommand(pi);
|
|
36
45
|
|
|
46
|
+
// Intercept TUI-only commands at the input level — this runs BEFORE
|
|
47
|
+
// message submission, so no chat message appears and no "Working..." indicator
|
|
48
|
+
pi.on("input", (event, ctx) => {
|
|
49
|
+
const text = event.text.trim();
|
|
50
|
+
if (!text.startsWith("/")) return;
|
|
51
|
+
|
|
52
|
+
const spaceIndex = text.indexOf(" ");
|
|
53
|
+
const commandName = spaceIndex === -1 ? text.slice(1) : text.slice(1, spaceIndex);
|
|
54
|
+
|
|
55
|
+
const handler = TUI_COMMANDS[commandName];
|
|
56
|
+
if (!handler) return;
|
|
57
|
+
|
|
58
|
+
handler(pi, ctx);
|
|
59
|
+
return { handled: true };
|
|
60
|
+
});
|
|
61
|
+
|
|
37
62
|
// Session start
|
|
38
63
|
pi.on("session_start", async (_event, ctx) => {
|
|
39
64
|
// Check for updates in the background
|