supipowers 0.2.4 → 0.2.6

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.4",
3
+ "version": "0.2.6",
4
4
  "description": "OMP-native workflow extension inspired by Superpowers.",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,4 +1,4 @@
1
- import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
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";
@@ -93,66 +93,69 @@ function buildSettings(cwd: string): SettingDef[] {
93
93
  ];
94
94
  }
95
95
 
96
- export function registerConfigCommand(pi: ExtensionAPI): void {
97
- pi.registerCommand("supi:config", {
98
- description: "View and manage Supipowers configuration",
99
- async handler(_args, ctx) {
100
- if (!ctx.hasUI) {
101
- ctx.ui.notify("Config UI requires interactive mode", "warning");
102
- return;
103
- }
96
+ export function handleConfig(ctx: ExtensionContext): void {
97
+ if (!ctx.hasUI) {
98
+ ctx.ui.notify("Config UI requires interactive mode", "warning");
99
+ return;
100
+ }
104
101
 
105
- ctx.ui.setEditorText("");
106
- void (async () => {
107
- const settings = buildSettings(ctx.cwd);
102
+ void (async () => {
103
+ const settings = buildSettings(ctx.cwd);
108
104
 
109
- while (true) {
110
- const config = loadConfig(ctx.cwd);
105
+ while (true) {
106
+ const config = loadConfig(ctx.cwd);
111
107
 
112
- const options = settings.map(
113
- (s) => `${s.label}: ${s.get(config)}`
114
- );
115
- options.push("Done");
108
+ const options = settings.map(
109
+ (s) => `${s.label}: ${s.get(config)}`
110
+ );
111
+ options.push("Done");
116
112
 
117
- const choice = await ctx.ui.select(
118
- "Supipowers Settings",
119
- options,
120
- { helpText: "Select a setting to change · Esc to close" },
121
- );
113
+ const choice = await ctx.ui.select(
114
+ "Supipowers Settings",
115
+ options,
116
+ { helpText: "Select a setting to change · Esc to close" },
117
+ );
122
118
 
123
- if (choice === undefined || choice === "Done") break;
119
+ if (choice === undefined || choice === "Done") break;
124
120
 
125
- const index = options.indexOf(choice);
126
- const setting = settings[index];
127
- if (!setting) break;
121
+ const index = options.indexOf(choice);
122
+ const setting = settings[index];
123
+ if (!setting) break;
128
124
 
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
- }
153
- }
125
+ if (setting.type === "select" && setting.options) {
126
+ const value = await ctx.ui.select(
127
+ setting.label,
128
+ setting.options,
129
+ { initialIndex: setting.options.indexOf(setting.get(config)) },
130
+ );
131
+ if (value !== undefined) {
132
+ setting.set(ctx.cwd, value);
133
+ ctx.ui.notify(`${setting.label} → ${value}`, "info");
134
+ }
135
+ } else if (setting.type === "toggle") {
136
+ const current = setting.get(config);
137
+ const newValue = current === "on" ? "off" : "on";
138
+ setting.set(ctx.cwd, newValue);
139
+ ctx.ui.notify(`${setting.label} → ${newValue}`, "info");
140
+ } else if (setting.type === "text") {
141
+ const value = await ctx.ui.input(
142
+ setting.label,
143
+ setting.get(config) === "not set" ? undefined : setting.get(config),
144
+ );
145
+ if (value !== undefined) {
146
+ setting.set(ctx.cwd, value);
147
+ ctx.ui.notify(`${setting.label} → ${value || "cleared"}`, "info");
154
148
  }
155
- })();
149
+ }
150
+ }
151
+ })();
152
+ }
153
+
154
+ export function registerConfigCommand(pi: ExtensionAPI): void {
155
+ pi.registerCommand("supi:config", {
156
+ description: "View and manage Supipowers configuration",
157
+ async handler(_args, ctx) {
158
+ handleConfig(ctx);
156
159
  },
157
160
  });
158
161
  }
@@ -1,46 +1,48 @@
1
- import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
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 registerStatusCommand(pi: ExtensionAPI): void {
5
- pi.registerCommand("supi:status", {
6
- description: "Check on running sub-agents and task progress",
7
- async handler(_args, ctx) {
8
- ctx.ui.setEditorText("");
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
+ }
9
10
 
10
- const activeRun = findActiveRun(ctx.cwd);
11
- if (!activeRun) {
12
- ctx.ui.notify("No active runs — use /supi:run to execute a plan", "info");
13
- return;
14
- }
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");
15
21
 
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");
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
+ ];
26
34
 
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
- ];
35
+ await ctx.ui.select("Supipowers Status", options, {
36
+ helpText: "Esc to close",
37
+ });
38
+ })();
39
+ }
39
40
 
40
- await ctx.ui.select("Supipowers Status", options, {
41
- helpText: "Esc to close",
42
- });
43
- })();
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);
44
46
  },
45
47
  });
46
48
  }
@@ -1,52 +1,56 @@
1
- import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
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 registerSupiCommand(pi: ExtensionAPI): void {
8
- pi.registerCommand("supi", {
9
- description: "Supipowers overview — show available commands and project status",
10
- async handler(_args, ctx) {
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);
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
+ ];
17
24
 
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
- ];
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
+ ];
28
31
 
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
- ];
32
+ const choice = await ctx.ui.select(
33
+ "Supipowers",
34
+ [...commands, "", ...status, "", "Close"],
35
+ { helpText: "Select a command to run · Esc to close" },
36
+ );
35
37
 
36
- const choice = await ctx.ui.select(
37
- "Supipowers",
38
- [...commands, "", ...status, "", "Close"],
39
- { helpText: "Select a command to run · Esc to close" },
40
- );
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
+ }
41
47
 
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
- }
48
- }
49
- })();
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);
50
54
  },
51
55
  });
52
56
  }
@@ -1,100 +1,103 @@
1
- import type { ExtensionAPI } from "@oh-my-pi/pi-coding-agent";
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 registerUpdateCommand(pi: ExtensionAPI): void {
7
- pi.registerCommand("supi:update", {
8
- description: "Update supipowers to the latest version",
9
- async handler(_args, ctx) {
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");
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");
15
11
 
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
- }
25
- }
26
-
27
- ctx.ui.notify(`Current version: v${currentVersion}`, "info");
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
+ }
28
22
 
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");
33
- return;
34
- }
35
- const latestVersion = checkResult.stdout.trim();
23
+ ctx.ui.notify(`Current version: v${currentVersion}`, "info");
36
24
 
37
- if (latestVersion === currentVersion) {
38
- ctx.ui.notify(`supipowers v${currentVersion} is already up to date`, "info");
39
- return;
40
- }
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();
41
32
 
42
- ctx.ui.notify(`Updating v${currentVersion} → v${latestVersion}...`, "info");
33
+ if (latestVersion === currentVersion) {
34
+ ctx.ui.notify(`supipowers v${currentVersion} is already up to date`, "info");
35
+ return;
36
+ }
43
37
 
44
- // Download latest to a temp directory
45
- const tempDir = join(tmpdir(), `supipowers-update-${Date.now()}`);
46
- mkdirSync(tempDir, { recursive: true });
38
+ ctx.ui.notify(`Updating v${currentVersion} v${latestVersion}...`, "info");
47
39
 
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;
56
- }
40
+ // Download latest to a temp directory
41
+ const tempDir = join(tmpdir(), `supipowers-update-${Date.now()}`);
42
+ mkdirSync(tempDir, { recursive: true });
57
43
 
58
- const downloadedRoot = join(tempDir, "node_modules", "supipowers");
59
- if (!existsSync(downloadedRoot)) {
60
- ctx.ui.notify("Downloaded package not found", "error");
61
- return;
62
- }
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
+ }
63
53
 
64
- // Clean previous installation
65
- if (existsSync(extDir)) {
66
- rmSync(extDir, { recursive: true });
67
- }
54
+ const downloadedRoot = join(tempDir, "node_modules", "supipowers");
55
+ if (!existsSync(downloadedRoot)) {
56
+ ctx.ui.notify("Downloaded package not found", "error");
57
+ return;
58
+ }
68
59
 
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"));
60
+ // Clean previous installation
61
+ if (existsSync(extDir)) {
62
+ rmSync(extDir, { recursive: true });
63
+ }
73
64
 
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
- }
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"));
87
69
 
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
- }
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"));
96
81
  }
97
- })();
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);
98
101
  },
99
102
  });
100
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