@narumitw/pi-caffeinate 0.14.0 → 0.15.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 +14 -6
- package/package.json +1 -1
- package/src/caffeinate.ts +18 -7
- package/src/settings.ts +8 -3
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ It is designed for long-running coding, refactoring, debugging, web research, an
|
|
|
14
14
|
- Supports macOS, Windows, WSL, and Linux.
|
|
15
15
|
- Defaults to display-awake mode on every supported OS: prevent system sleep and keep the screen/display awake.
|
|
16
16
|
- Provides a single `/caffeinate` command with menu-based controls and direct subcommands.
|
|
17
|
-
- Persists the selected keep-awake mode in a small JSON settings file.
|
|
17
|
+
- Persists the selected keep-awake mode and optional quiet mode in a small JSON settings file.
|
|
18
18
|
- Allows a custom inhibitor command through environment configuration.
|
|
19
19
|
- Emits plain status text; `@narumitw/pi-statusline` can add or suppress the status icon from JSON config.
|
|
20
20
|
- Fails safely when no supported inhibitor is available.
|
|
@@ -77,7 +77,7 @@ Keeps the system awake while allowing normal display sleep. If an inhibitor is c
|
|
|
77
77
|
/caffeinate status
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
Shows whether an inhibitor is active, unavailable, disabled, or idle. The status includes the current mode and settings file path.
|
|
80
|
+
Shows whether an inhibitor is active, unavailable, disabled, or idle. The status includes the current mode, quiet mode, and settings file path.
|
|
81
81
|
|
|
82
82
|
```text
|
|
83
83
|
/caffeinate mode
|
|
@@ -93,7 +93,7 @@ Releases any active inhibitor until Pi starts another agent run.
|
|
|
93
93
|
|
|
94
94
|
## ⚙️ Configuration
|
|
95
95
|
|
|
96
|
-
### Persisted
|
|
96
|
+
### Persisted settings
|
|
97
97
|
|
|
98
98
|
`/caffeinate sleep` and `/caffeinate display` save the selected mode to:
|
|
99
99
|
|
|
@@ -106,11 +106,19 @@ Example:
|
|
|
106
106
|
```json
|
|
107
107
|
{
|
|
108
108
|
"mode": "display",
|
|
109
|
+
"quiet": true,
|
|
109
110
|
"updatedAt": 1791763200000
|
|
110
111
|
}
|
|
111
112
|
```
|
|
112
113
|
|
|
113
|
-
|
|
114
|
+
Set `"quiet": true` to hide the routine `Keeping computer awake (...)` and
|
|
115
|
+
`Released pi-caffeinate (agent finished)` lifecycle notifications. Quiet mode does not hide
|
|
116
|
+
warnings, status updates, or feedback from `/caffeinate` commands such as `status`, mode changes,
|
|
117
|
+
help, and manual stop. It defaults to `false` when omitted. The file is read at startup and on
|
|
118
|
+
`/reload`; run `/reload` after editing it in a running Pi session before using mode commands.
|
|
119
|
+
|
|
120
|
+
Missing, invalid, or deleted settings default back to `display` mode with quiet mode disabled on
|
|
121
|
+
every supported OS.
|
|
114
122
|
|
|
115
123
|
Compatibility: older versions used `pi-caffeinate-settings.json`. During the migration window, a
|
|
116
124
|
legacy-only file is automatically migrated to `pi-caffeinate.json` with a warning. If both files
|
|
@@ -133,7 +141,7 @@ PI_CAFFEINATE_COMMAND='systemd-inhibit --what=idle:sleep --why="pi running" --mo
|
|
|
133
141
|
|
|
134
142
|
The custom command is parsed with shell-like quoting and is run directly without a shell. `PI_CAFFEINATE_COMMAND` takes precedence over the saved mode; `/caffeinate status` reports when a custom command is active.
|
|
135
143
|
|
|
136
|
-
Deprecated: `PI_CAFFEINATE_ICON` still works for now. If you use `@narumitw/pi-statusline`, move the icon to `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-statusline
|
|
144
|
+
Deprecated: `PI_CAFFEINATE_ICON` still works for now. If you use `@narumitw/pi-statusline`, move the icon to `${PI_CODING_AGENT_DIR:-~/.pi/agent}/pi-statusline.json`:
|
|
137
145
|
|
|
138
146
|
```json
|
|
139
147
|
{
|
|
@@ -143,7 +151,7 @@ Deprecated: `PI_CAFFEINATE_ICON` still works for now. If you use `@narumitw/pi-s
|
|
|
143
151
|
}
|
|
144
152
|
```
|
|
145
153
|
|
|
146
|
-
Without `@narumitw/pi-statusline`, keep using `PI_CAFFEINATE_ICON` during the compatibility window. In `pi-statusline
|
|
154
|
+
Without `@narumitw/pi-statusline`, keep using `PI_CAFFEINATE_ICON` during the compatibility window. In `pi-statusline.json`, use an empty string to show the caffeinate status without an icon.
|
|
147
155
|
|
|
148
156
|
## 🧠 Why use pi-caffeinate?
|
|
149
157
|
|
package/package.json
CHANGED
package/src/caffeinate.ts
CHANGED
|
@@ -56,6 +56,7 @@ interface CaffeinateState {
|
|
|
56
56
|
available: boolean;
|
|
57
57
|
disabled: boolean;
|
|
58
58
|
mode: CaffeinateMode;
|
|
59
|
+
quiet: boolean;
|
|
59
60
|
settingsLoaded: boolean;
|
|
60
61
|
settingsError?: string;
|
|
61
62
|
settingsNotice?: string;
|
|
@@ -67,6 +68,7 @@ const state: CaffeinateState = {
|
|
|
67
68
|
available: true,
|
|
68
69
|
disabled: isDisabled(),
|
|
69
70
|
mode: DEFAULT_MODE,
|
|
71
|
+
quiet: false,
|
|
70
72
|
settingsLoaded: false,
|
|
71
73
|
iconWarningShown: false,
|
|
72
74
|
};
|
|
@@ -83,12 +85,14 @@ export default function caffeinate(pi: ExtensionAPI) {
|
|
|
83
85
|
pi.on("agent_start", async (_event, ctx) => {
|
|
84
86
|
await ensureSettingsLoaded(ctx);
|
|
85
87
|
state.activeTurns += 1;
|
|
86
|
-
startInhibitor(ctx);
|
|
88
|
+
startInhibitor(ctx, { notify: !state.quiet });
|
|
87
89
|
});
|
|
88
90
|
|
|
89
91
|
pi.on("agent_end", (_event, ctx) => {
|
|
90
92
|
state.activeTurns = Math.max(0, state.activeTurns - 1);
|
|
91
|
-
if (state.activeTurns === 0)
|
|
93
|
+
if (state.activeTurns === 0) {
|
|
94
|
+
stopInhibitor(ctx, "agent finished", { notify: !state.quiet });
|
|
95
|
+
}
|
|
92
96
|
updateStatus(ctx);
|
|
93
97
|
});
|
|
94
98
|
|
|
@@ -195,7 +199,7 @@ async function setMode(ctx: ExtensionContext, mode: CaffeinateMode) {
|
|
|
195
199
|
|
|
196
200
|
let saved = true;
|
|
197
201
|
try {
|
|
198
|
-
await saveSettings({ mode, updatedAt: Date.now() });
|
|
202
|
+
await saveSettings({ mode, quiet: state.quiet, updatedAt: Date.now() });
|
|
199
203
|
} catch (error) {
|
|
200
204
|
saved = false;
|
|
201
205
|
state.settingsError = `settings save failed: ${formatError(error)}`;
|
|
@@ -204,7 +208,7 @@ async function setMode(ctx: ExtensionContext, mode: CaffeinateMode) {
|
|
|
204
208
|
|
|
205
209
|
if (state.process && previousMode !== mode && !state.command?.custom) {
|
|
206
210
|
stopInhibitor(ctx, "mode changed", { notify: false });
|
|
207
|
-
startInhibitor(ctx);
|
|
211
|
+
startInhibitor(ctx, { notify: !state.quiet });
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
ctx.ui.notify(
|
|
@@ -261,7 +265,7 @@ function buildCommandGuide() {
|
|
|
261
265
|
].join("\n");
|
|
262
266
|
}
|
|
263
267
|
|
|
264
|
-
function startInhibitor(ctx: ExtensionContext) {
|
|
268
|
+
function startInhibitor(ctx: ExtensionContext, options: { notify?: boolean } = {}) {
|
|
265
269
|
if (state.disabled || state.process) {
|
|
266
270
|
updateStatus(ctx);
|
|
267
271
|
return;
|
|
@@ -293,6 +297,7 @@ function startInhibitor(ctx: ExtensionContext) {
|
|
|
293
297
|
if (state.process !== child) return;
|
|
294
298
|
state.process = undefined;
|
|
295
299
|
state.startedAt = undefined;
|
|
300
|
+
state.available = false;
|
|
296
301
|
state.lastError = `${command.description} exited unexpectedly (${exit}).`;
|
|
297
302
|
ctx.ui.notify(state.lastError, "warning");
|
|
298
303
|
updateStatus(ctx);
|
|
@@ -303,7 +308,9 @@ function startInhibitor(ctx: ExtensionContext) {
|
|
|
303
308
|
state.command = command;
|
|
304
309
|
state.available = true;
|
|
305
310
|
state.lastError = undefined;
|
|
306
|
-
|
|
311
|
+
if (options.notify !== false) {
|
|
312
|
+
ctx.ui.notify(`Keeping computer awake (${statusModeLabel()}).`, "info");
|
|
313
|
+
}
|
|
307
314
|
updateStatus(ctx);
|
|
308
315
|
} catch (error) {
|
|
309
316
|
state.process = undefined;
|
|
@@ -348,6 +355,7 @@ function describeState() {
|
|
|
348
355
|
const customCommand = hasCustomCommand();
|
|
349
356
|
const lines = [
|
|
350
357
|
`Mode: ${formatMode(state.mode)}${customCommand ? " (overridden by custom command)" : ""}`,
|
|
358
|
+
`Quiet mode: ${state.quiet ? "enabled" : "disabled"}`,
|
|
351
359
|
`Settings: ${settingsFilePath()}`,
|
|
352
360
|
];
|
|
353
361
|
|
|
@@ -394,6 +402,7 @@ async function loadSettingsIntoState(ctx: ExtensionContext) {
|
|
|
394
402
|
if (state.disabled) {
|
|
395
403
|
state.settingsLoaded = true;
|
|
396
404
|
state.settingsError = undefined;
|
|
405
|
+
state.quiet = false;
|
|
397
406
|
return;
|
|
398
407
|
}
|
|
399
408
|
|
|
@@ -407,10 +416,12 @@ async function loadSettingsIntoState(ctx: ExtensionContext) {
|
|
|
407
416
|
|
|
408
417
|
if (settings.kind === "loaded") {
|
|
409
418
|
state.mode = settings.settings.mode;
|
|
419
|
+
state.quiet = settings.settings.quiet;
|
|
410
420
|
return;
|
|
411
421
|
}
|
|
412
422
|
|
|
413
423
|
state.mode = DEFAULT_MODE;
|
|
424
|
+
state.quiet = false;
|
|
414
425
|
if (settings.kind === "invalid") {
|
|
415
426
|
state.settingsError = settings.reason;
|
|
416
427
|
ctx.ui.notify(
|
|
@@ -442,7 +453,7 @@ function warnDeprecatedIcon(ctx: ExtensionContext) {
|
|
|
442
453
|
if (state.iconWarningShown || !process.env.PI_CAFFEINATE_ICON?.trim()) return;
|
|
443
454
|
state.iconWarningShown = true;
|
|
444
455
|
ctx.ui.notify(
|
|
445
|
-
"PI_CAFFEINATE_ICON is deprecated but still works for now. If you use @narumitw/pi-statusline, move it to pi-statusline
|
|
456
|
+
"PI_CAFFEINATE_ICON is deprecated but still works for now. If you use @narumitw/pi-statusline, move it to pi-statusline.json (extensionStatusIcons.caffeinate).",
|
|
446
457
|
"warning",
|
|
447
458
|
);
|
|
448
459
|
}
|
package/src/settings.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type CaffeinateMode = "sleep" | "display";
|
|
|
11
11
|
|
|
12
12
|
export interface CaffeinateSettings {
|
|
13
13
|
mode: CaffeinateMode;
|
|
14
|
+
quiet: boolean;
|
|
14
15
|
updatedAt: number;
|
|
15
16
|
}
|
|
16
17
|
|
|
@@ -59,7 +60,10 @@ async function readSettingsFile(filePath: string): Promise<SettingsLoadResult> {
|
|
|
59
60
|
try {
|
|
60
61
|
const settings = normalizeCaffeinateSettings(JSON.parse(text) as unknown);
|
|
61
62
|
if (settings) return { kind: "loaded", settings };
|
|
62
|
-
return {
|
|
63
|
+
return {
|
|
64
|
+
kind: "invalid",
|
|
65
|
+
reason: `${filePath}: expected { "mode": "sleep" | "display", optional "quiet": boolean }`,
|
|
66
|
+
};
|
|
63
67
|
} catch (error) {
|
|
64
68
|
return { kind: "invalid", reason: `${filePath}: ${formatError(error)}` };
|
|
65
69
|
}
|
|
@@ -108,10 +112,11 @@ async function fileExists(filePath: string) {
|
|
|
108
112
|
|
|
109
113
|
export function normalizeCaffeinateSettings(value: unknown): CaffeinateSettings | undefined {
|
|
110
114
|
if (!value || typeof value !== "object") return undefined;
|
|
111
|
-
const settings = value as { mode?: unknown; updatedAt?: unknown };
|
|
115
|
+
const settings = value as { mode?: unknown; quiet?: unknown; updatedAt?: unknown };
|
|
112
116
|
if (!isCaffeinateMode(settings.mode)) return undefined;
|
|
117
|
+
if (settings.quiet !== undefined && typeof settings.quiet !== "boolean") return undefined;
|
|
113
118
|
if (settings.updatedAt !== undefined && typeof settings.updatedAt !== "number") return undefined;
|
|
114
|
-
return { mode: settings.mode, updatedAt: settings.updatedAt ?? 0 };
|
|
119
|
+
return { mode: settings.mode, quiet: settings.quiet ?? false, updatedAt: settings.updatedAt ?? 0 };
|
|
115
120
|
}
|
|
116
121
|
|
|
117
122
|
function isCaffeinateMode(value: unknown): value is CaffeinateMode {
|