@narumitw/pi-caffeinate 0.14.1 → 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 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 mode
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
- Missing, invalid, or deleted settings default back to `display` mode on every supported OS.
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-settings.json`:
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-settings.json`, use an empty string to show the caffeinate status without an icon.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narumitw/pi-caffeinate",
3
- "version": "0.14.1",
3
+ "version": "0.15.0",
4
4
  "description": "Pi extension that keeps the computer awake while the agent is running.",
5
5
  "type": "module",
6
6
  "license": "MIT",
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) stopInhibitor(ctx, "agent finished");
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
- ctx.ui.notify(`Keeping computer awake (${statusModeLabel()}).`, "info");
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-settings.json (extensionStatusIcons.caffeinate).",
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 { kind: "invalid", reason: `${filePath}: expected { "mode": "sleep" | "display" }` };
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 {