@narumitw/pi-caffeinate 0.35.0 → 0.38.0
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 +3 -2
- package/package.json +4 -1
- package/src/caffeinate.ts +73 -42
package/README.md
CHANGED
|
@@ -59,7 +59,8 @@ If no supported inhibitor is available, the extension stays loaded and reports t
|
|
|
59
59
|
/caffeinate
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
Opens keep-awake controls
|
|
62
|
+
Opens standard keep-awake controls in TUI or RPC mode. Print and JSON modes reject the interactive
|
|
63
|
+
menu observably; use the direct `status`, `sleep`, `display`, `stop`, or `help` routes instead.
|
|
63
64
|
|
|
64
65
|
```text
|
|
65
66
|
/caffeinate display
|
|
@@ -83,7 +84,7 @@ Shows whether an inhibitor is active, unavailable, disabled, or idle. The status
|
|
|
83
84
|
/caffeinate mode
|
|
84
85
|
```
|
|
85
86
|
|
|
86
|
-
Opens
|
|
87
|
+
Opens the standard keep-awake mode selector in TUI or RPC mode. Escape closes the selector.
|
|
87
88
|
|
|
88
89
|
```text
|
|
89
90
|
/caffeinate stop
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@narumitw/pi-caffeinate",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.38.0",
|
|
4
4
|
"description": "Pi extension that keeps the computer awake while the agent is running.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,5 +38,8 @@
|
|
|
38
38
|
"type": "git",
|
|
39
39
|
"url": "https://github.com/narumiruna/pi-extensions",
|
|
40
40
|
"directory": "extensions/pi-caffeinate"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@narumitw/pi-tui-kit": "<1"
|
|
41
44
|
}
|
|
42
45
|
}
|
package/src/caffeinate.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
ExtensionCommandContext,
|
|
6
6
|
ExtensionContext,
|
|
7
7
|
} from "@earendil-works/pi-coding-agent";
|
|
8
|
+
import { defineMenu, runMenu } from "@narumitw/pi-tui-kit";
|
|
8
9
|
import { startInhibitorProcess, stopInhibitorProcess } from "./inhibitor-process.js";
|
|
9
10
|
import { formatMode, getInhibitorCommand, type InhibitorCommand } from "./inhibitors.js";
|
|
10
11
|
import {
|
|
@@ -56,6 +57,7 @@ interface CaffeinateState {
|
|
|
56
57
|
settingsNotice?: string;
|
|
57
58
|
iconWarningShown: boolean;
|
|
58
59
|
sessionGeneration: number;
|
|
60
|
+
menuController: AbortController;
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
const state: CaffeinateState = {
|
|
@@ -67,11 +69,13 @@ const state: CaffeinateState = {
|
|
|
67
69
|
settingsLoaded: false,
|
|
68
70
|
iconWarningShown: false,
|
|
69
71
|
sessionGeneration: 0,
|
|
72
|
+
menuController: new AbortController(),
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
export default function caffeinate(pi: ExtensionAPI) {
|
|
73
76
|
pi.on("session_start", async (_event, ctx) => {
|
|
74
77
|
const generation = ++state.sessionGeneration;
|
|
78
|
+
replaceMenuController("Caffeinate session replaced");
|
|
75
79
|
state.iconWarningShown = false;
|
|
76
80
|
state.settingsNotice = undefined;
|
|
77
81
|
warnDeprecatedIcon(ctx);
|
|
@@ -98,6 +102,7 @@ export default function caffeinate(pi: ExtensionAPI) {
|
|
|
98
102
|
|
|
99
103
|
pi.on("session_shutdown", async (_event, ctx) => {
|
|
100
104
|
state.sessionGeneration += 1;
|
|
105
|
+
state.menuController.abort(new DOMException("Caffeinate session shut down", "AbortError"));
|
|
101
106
|
state.activeTurns = 0;
|
|
102
107
|
stopInhibitor(ctx, "session shutdown", { notify: false });
|
|
103
108
|
ctx.ui.setStatus(STATUS_KEY, undefined);
|
|
@@ -151,55 +156,81 @@ async function handleCaffeinateCommand(args: string, ctx: CommandContext, genera
|
|
|
151
156
|
}
|
|
152
157
|
|
|
153
158
|
async function showMenu(ctx: CommandContext, generation: number) {
|
|
154
|
-
|
|
155
|
-
ctx.ui.notify(`${buildCommandGuide()}\n\n${describeState()}`, statusLevel());
|
|
156
|
-
updateStatus(ctx);
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const choice = await ctx.ui.select("pi-caffeinate controls", Object.values(MENU_OPTIONS));
|
|
161
|
-
if (generation !== state.sessionGeneration) return;
|
|
162
|
-
switch (choice) {
|
|
163
|
-
case MENU_OPTIONS.status:
|
|
164
|
-
showStatus(ctx);
|
|
165
|
-
return;
|
|
166
|
-
case MENU_OPTIONS.sleep:
|
|
167
|
-
await setMode(ctx, "sleep", generation);
|
|
168
|
-
return;
|
|
169
|
-
case MENU_OPTIONS.display:
|
|
170
|
-
await setMode(ctx, "display", generation);
|
|
171
|
-
return;
|
|
172
|
-
case MENU_OPTIONS.stop:
|
|
173
|
-
stopCaffeinate(ctx, "manual stop");
|
|
174
|
-
return;
|
|
175
|
-
case MENU_OPTIONS.help:
|
|
176
|
-
ctx.ui.notify(buildCommandGuide(), "info");
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
159
|
+
await runCaffeinateMenu(ctx, generation, "main");
|
|
179
160
|
}
|
|
180
161
|
|
|
181
162
|
async function showModeSelector(ctx: CommandContext, generation: number) {
|
|
163
|
+
await runCaffeinateMenu(ctx, generation, "mode");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function runCaffeinateMenu(ctx: CommandContext, generation: number, start: "main" | "mode") {
|
|
182
167
|
if (!ctx.hasUI) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
168
|
+
throw new Error(
|
|
169
|
+
start === "mode"
|
|
170
|
+
? "Mode selection requires TUI or RPC mode. Use /caffeinate sleep or /caffeinate display."
|
|
171
|
+
: `The pi-caffeinate menu requires TUI or RPC mode.\n\n${buildCommandGuide()}\n\n${describeState()}`,
|
|
186
172
|
);
|
|
187
|
-
updateStatus(ctx);
|
|
188
|
-
return;
|
|
189
173
|
}
|
|
174
|
+
type Screen = "main" | "mode";
|
|
175
|
+
type Action = "display" | "sleep" | "status" | "stop" | "help";
|
|
176
|
+
const menu = defineMenu<undefined, Screen, Action>({
|
|
177
|
+
start,
|
|
178
|
+
screens: {
|
|
179
|
+
main: () => ({
|
|
180
|
+
kind: "actions",
|
|
181
|
+
title: "pi-caffeinate controls",
|
|
182
|
+
lines: describeState().split("\n"),
|
|
183
|
+
items: Object.entries(MENU_OPTIONS).map(([id, label]) => ({
|
|
184
|
+
id,
|
|
185
|
+
label,
|
|
186
|
+
action: id as Action,
|
|
187
|
+
})),
|
|
188
|
+
hint: "close",
|
|
189
|
+
}),
|
|
190
|
+
mode: () => ({
|
|
191
|
+
kind: "actions",
|
|
192
|
+
title: `pi-caffeinate mode (current: ${formatMode(state.mode)})`,
|
|
193
|
+
items: Object.entries(MODE_OPTIONS).map(([id, label]) => ({
|
|
194
|
+
id,
|
|
195
|
+
label,
|
|
196
|
+
action: id as Action,
|
|
197
|
+
})),
|
|
198
|
+
hint: "close",
|
|
199
|
+
}),
|
|
200
|
+
},
|
|
201
|
+
actions: {
|
|
202
|
+
display: async () => {
|
|
203
|
+
await setMode(ctx, "display", generation);
|
|
204
|
+
return { kind: "close" };
|
|
205
|
+
},
|
|
206
|
+
sleep: async () => {
|
|
207
|
+
await setMode(ctx, "sleep", generation);
|
|
208
|
+
return { kind: "close" };
|
|
209
|
+
},
|
|
210
|
+
status: async () => {
|
|
211
|
+
showStatus(ctx);
|
|
212
|
+
return { kind: "close" };
|
|
213
|
+
},
|
|
214
|
+
stop: async () => {
|
|
215
|
+
stopCaffeinate(ctx, "manual stop");
|
|
216
|
+
return { kind: "close" };
|
|
217
|
+
},
|
|
218
|
+
help: async () => {
|
|
219
|
+
ctx.ui.notify(buildCommandGuide(), "info");
|
|
220
|
+
return { kind: "close" };
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
});
|
|
224
|
+
await runMenu(ctx, menu, {
|
|
225
|
+
getState: () => undefined,
|
|
226
|
+
signal: state.menuController.signal,
|
|
227
|
+
isCurrent: () => generation === state.sessionGeneration,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
190
230
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
if (generation !== state.sessionGeneration) return;
|
|
196
|
-
if (choice === MODE_OPTIONS.sleep) {
|
|
197
|
-
await setMode(ctx, "sleep", generation);
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
if (choice === MODE_OPTIONS.display) {
|
|
201
|
-
await setMode(ctx, "display", generation);
|
|
202
|
-
}
|
|
231
|
+
function replaceMenuController(reason: string) {
|
|
232
|
+
state.menuController.abort(new DOMException(reason, "AbortError"));
|
|
233
|
+
state.menuController = new AbortController();
|
|
203
234
|
}
|
|
204
235
|
|
|
205
236
|
let modeOperationQueue = Promise.resolve();
|