@event4u/agent-switch 2.3.0 → 2.3.1

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/dist/daemon.js CHANGED
@@ -46,12 +46,29 @@ import { execFileSync } from "node:child_process";
46
46
  * daemon-side OS notifications, fire a desktop notification too — marking the
47
47
  * record so the GUI does not show it a second time. Deduped events (append
48
48
  * returns null) never OS-notify. */
49
- function notify(event) {
49
+ function notify(event, opts = {}) {
50
50
  const created = appendNotification(event);
51
- if (created && readOsNotifications() && osNotify(event.title, event.message)) {
51
+ if (created && opts.os !== false && readOsNotifications() && osNotify(event.title, event.message)) {
52
52
  markOsNotified(created.id);
53
53
  }
54
54
  }
55
+ /** How long reads must fail before it counts as an outage. A single failed read
56
+ * is routine — a rate-limited endpoint answers again a minute later — and the
57
+ * last known values keep being served meanwhile. */
58
+ export const FETCH_OUTAGE_MS = 15 * 60_000;
59
+ /** The notice for a profile whose reads have failed since `sinceMs`, or null
60
+ * while it is still a blip. Kept off the desktop: a fetch failure is a
61
+ * diagnostic for the app's log, not something to interrupt anyone for. */
62
+ export function fetchFailureNotice(key, sinceMs, now) {
63
+ if (now - sinceMs < FETCH_OUTAGE_MS)
64
+ return null;
65
+ return {
66
+ kind: "warning",
67
+ title: "Usage fetch failed",
68
+ message: `Could not fetch usage limits for ${key} for ${Math.round((now - sinceMs) / 60_000)} min. Keeping the last known values.`,
69
+ os: false,
70
+ };
71
+ }
55
72
  // Providers the daemon may suggest a switch for on threshold (they expose a
56
73
  // usage readout to base a headroom suggestion on). Antigravity has none.
57
74
  const AUTO_PROVIDERS = ["claude", "codex"];
@@ -319,24 +336,23 @@ async function pollProvider(provider, state, thresholds, notifiedReset, log) {
319
336
  let failures = 0;
320
337
  const polled = [];
321
338
  state.notifiedFetchFail = state.notifiedFetchFail ?? {};
339
+ state.fetchFailingSince = state.fetchFailingSince ?? {};
322
340
  for (const name of targets) {
323
341
  const key = `${provider}/${name}`;
324
342
  const snapshot = await snapshotFor(provider, name);
325
343
  if (!snapshot) {
326
344
  failures++;
327
345
  // Fail-safe: the last-known snapshot in state.profiles[key] is left intact
328
- // (never overwritten with null), and we warn ONCE per outage rather than
329
- // every cycle — the loop keeps serving the last good numbers meanwhile.
330
- if (!state.notifiedFetchFail[key]) {
346
+ // (never overwritten with null). Only an outage is reported, once.
347
+ const since = state.fetchFailingSince[key] ?? (state.fetchFailingSince[key] = new Date().toISOString());
348
+ const notice = state.notifiedFetchFail[key] ? null : fetchFailureNotice(key, Date.parse(since), Date.now());
349
+ if (notice) {
331
350
  state.notifiedFetchFail[key] = true;
332
- notify({
333
- kind: "warning",
334
- title: "Usage fetch failed",
335
- message: `Could not fetch usage limits for ${provider}/${name}. Keeping the last known values.`,
336
- });
351
+ notify(notice, { os: notice.os });
337
352
  }
338
353
  continue;
339
354
  }
355
+ delete state.fetchFailingSince[key];
340
356
  if (state.notifiedFetchFail[key]) {
341
357
  delete state.notifiedFetchFail[key];
342
358
  log(`recovered: ${provider}/${name} usage fetch succeeded again`);
package/dist/index.js CHANGED
@@ -2058,8 +2058,11 @@ function cmdOsNotify(sub, json = false) {
2058
2058
  const enabled = readOsNotifications();
2059
2059
  if (json)
2060
2060
  console.log(JSON.stringify({ enabled }));
2061
- else
2061
+ else {
2062
2062
  console.log(`Daemon OS notifications ${enabled ? "ON" : "OFF"}.`);
2063
+ if (enabled && process.platform === "darwin")
2064
+ console.log("On macOS the daemon shows none itself — the Agent Switch app shows them while it runs.");
2065
+ }
2063
2066
  }
2064
2067
  /** `agent-switch notifications [clear] [--json]` — list the recent
2065
2068
  * notifications (newest first) or clear the log. */
@@ -2521,7 +2524,7 @@ Run with no command to launch the tray/menubar GUI (single-instance).
2521
2524
  agent-switch service run|start|stop|status|install|uninstall background usage daemon
2522
2525
  agent-switch notifications [clear] [--json] recent notifications (usage-limit suggestions, fetch failures)
2523
2526
  agent-switch notify --kind K --title T --message M [--json] record a notification event
2524
- agent-switch os-notify [on|off|status] [--json] daemon-side OS desktop notifications (default off)
2527
+ agent-switch os-notify [on|off|status] [--json] daemon-side OS desktop notifications, Linux/Windows (default off; macOS: the app shows them)
2525
2528
  agent-switch uninstall [--force] remove all agent-switch data + daemon
2526
2529
  agent-switch usage history [--provider P] [--profile <name>] [--json] daemon-recorded usage samples per profile
2527
2530
  agent-switch tooling [--json] ecosystem tool readout (agent-config, rtk, provider CLIs)
package/dist/os-notify.js CHANGED
@@ -2,7 +2,9 @@
2
2
  * Best-effort OS desktop notifications for the background daemon, so an
3
3
  * auto-switch is visible even when the GUI is closed. Per-platform native
4
4
  * command, no external dependency:
5
- * - macOS → `osascript -e 'display notification …'`
5
+ * - macOS → nothing: an `osascript` notification is attributed to Script
6
+ * Editor. The Agent Switch app shows the notice itself when it
7
+ * runs (records not marked OS-notified), otherwise no one does.
6
8
  * - Linux → `notify-send <title> <body>`
7
9
  * - Windows → PowerShell balloon tip via System.Windows.Forms.NotifyIcon
8
10
  *
@@ -10,10 +12,6 @@
10
12
  * testable; `osNotify` wraps it with a fire-and-forget spawn that never throws.
11
13
  */
12
14
  import { spawnSync } from "node:child_process";
13
- /** AppleScript string literal escaping: backslash and double-quote only. */
14
- function escAppleScript(s) {
15
- return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
16
- }
17
15
  /** PowerShell single-quoted string literal escaping: double the single quote. */
18
16
  function escPowerShellSingle(s) {
19
17
  return s.replace(/'/g, "''");
@@ -23,10 +21,6 @@ function escPowerShellSingle(s) {
23
21
  * `platform` matches `process.platform` values (`darwin` / `linux` / `win32`).
24
22
  */
25
23
  export function buildOsNotifyCommand(platform, title, body) {
26
- if (platform === "darwin") {
27
- const script = `display notification "${escAppleScript(body)}" with title "${escAppleScript(title)}"`;
28
- return { program: "osascript", args: ["-e", script] };
29
- }
30
24
  if (platform === "linux") {
31
25
  // notify-send treats the first arg as summary, the second as body.
32
26
  return { program: "notify-send", args: [title, body] };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@event4u/agent-switch",
3
- "version": "2.3.0",
3
+ "version": "2.3.1",
4
4
  "description": "Switch between multiple Claude Code accounts on macOS, Linux, and Windows via isolated CLAUDE_CONFIG_DIR profiles",
5
5
  "repository": {
6
6
  "type": "git",