@wbern/cc-ping 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/cli.js +47 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -154,6 +154,7 @@ The daemon is smart about what it pings:
154
154
  - **Detects system sleep** — if the machine wakes from sleep and a ping cycle is overdue, the daemon notices and factors the delay into notifications
155
155
  - **Singleton enforcement** — only one daemon runs at a time, verified by PID and process name
156
156
  - **Graceful shutdown** — `daemon stop` writes a sentinel file and waits up to 60s for a clean exit before force-killing
157
+ - **Auto-restart on upgrade** — after upgrading cc-ping, the daemon detects the binary has changed and exits so the service manager can restart it with the new version. `daemon status` warns if the running daemon is outdated
157
158
 
158
159
  Logs are written to `~/.config/cc-ping/daemon.log`.
159
160
 
package/dist/cli.js CHANGED
@@ -717,6 +717,8 @@ import {
717
717
  openSync as fsOpenSync,
718
718
  mkdirSync as mkdirSync4,
719
719
  readFileSync as readFileSync6,
720
+ realpathSync,
721
+ statSync as statSync2,
720
722
  unlinkSync,
721
723
  writeFileSync as writeFileSync3
722
724
  } from "fs";
@@ -800,13 +802,17 @@ function getDaemonStatus(deps) {
800
802
  const nextPingMs = new Date(state.lastPingAt).getTime() + state.intervalMs - Date.now();
801
803
  nextPingIn = formatUptime(Math.max(0, nextPingMs));
802
804
  }
805
+ const currentVersion = deps?.currentVersion;
806
+ const versionMismatch = currentVersion != null && state.version != null ? state.version !== currentVersion : false;
803
807
  return {
804
808
  running: true,
805
809
  pid: state.pid,
806
810
  startedAt: state.startedAt,
807
811
  intervalMs: state.intervalMs,
808
812
  uptime,
809
- nextPingIn
813
+ nextPingIn,
814
+ versionMismatch,
815
+ daemonVersion: state.version
810
816
  };
811
817
  }
812
818
  function formatUptime(ms) {
@@ -821,6 +827,10 @@ function formatUptime(ms) {
821
827
  async function daemonLoop(intervalMs, options, deps) {
822
828
  let wakeDelayMs;
823
829
  while (!deps.shouldStop()) {
830
+ if (deps.hasUpgraded?.()) {
831
+ deps.log("Binary upgraded, exiting for restart...");
832
+ return "upgrade";
833
+ }
824
834
  const allAccounts = deps.listAccounts();
825
835
  let accounts = deps.isWindowActive ? allAccounts.filter((a) => !deps.isWindowActive(a.handle)) : allAccounts;
826
836
  const skipped = allAccounts.length - accounts.length;
@@ -883,6 +893,7 @@ async function daemonLoop(intervalMs, options, deps) {
883
893
  wakeDelayMs = void 0;
884
894
  }
885
895
  }
896
+ return "stop";
886
897
  }
887
898
  function startDaemon(options, deps) {
888
899
  const _getDaemonStatus = deps?.getDaemonStatus ?? getDaemonStatus;
@@ -928,7 +939,8 @@ function startDaemon(options, deps) {
928
939
  pid: child.pid,
929
940
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
930
941
  intervalMs,
931
- configDir
942
+ configDir,
943
+ version: options.version
932
944
  });
933
945
  return { success: true, pid: child.pid };
934
946
  }
@@ -999,14 +1011,18 @@ async function runDaemon(intervalMs, options, deps) {
999
1011
  deps.onSignal("SIGTERM", onSigterm);
1000
1012
  deps.onSignal("SIGINT", onSigint);
1001
1013
  deps.log(`Daemon started. Interval: ${Math.round(intervalMs / 6e4)}m`);
1014
+ let exitReason = "stop";
1002
1015
  try {
1003
- await daemonLoop(intervalMs, options, deps);
1016
+ exitReason = await daemonLoop(intervalMs, options, deps);
1004
1017
  } finally {
1005
1018
  deps.removeSignal("SIGTERM", onSigterm);
1006
1019
  deps.removeSignal("SIGINT", onSigint);
1007
1020
  deps.log("Daemon stopping...");
1008
1021
  cleanup();
1009
1022
  }
1023
+ if (exitReason === "upgrade") {
1024
+ deps.exit(75);
1025
+ }
1010
1026
  }
1011
1027
  async function runDaemonWithDefaults(intervalMs, options) {
1012
1028
  const stopPath = daemonStopPath();
@@ -1035,6 +1051,8 @@ async function runDaemonWithDefaults(intervalMs, options) {
1035
1051
  return shouldDefer2(/* @__PURE__ */ new Date(), schedule.optimalPingHour);
1036
1052
  };
1037
1053
  }
1054
+ const binaryPath = realpathSync(process.argv[1]);
1055
+ const startMtimeMs = statSync2(binaryPath).mtimeMs;
1038
1056
  await runDaemon(intervalMs, options, {
1039
1057
  runPing: runPing2,
1040
1058
  listAccounts: listAccounts2,
@@ -1043,6 +1061,13 @@ async function runDaemonWithDefaults(intervalMs, options) {
1043
1061
  log: (msg) => console.log(msg),
1044
1062
  isWindowActive: (handle) => getWindowReset2(handle) !== null,
1045
1063
  shouldDeferPing,
1064
+ hasUpgraded: () => {
1065
+ try {
1066
+ return statSync2(binaryPath).mtimeMs !== startMtimeMs;
1067
+ } catch {
1068
+ return false;
1069
+ }
1070
+ },
1046
1071
  updateState: (patch) => {
1047
1072
  const current = readDaemonState();
1048
1073
  if (current) writeDaemonState({ ...current, ...patch });
@@ -1799,7 +1824,7 @@ init_paths();
1799
1824
  init_run_ping();
1800
1825
 
1801
1826
  // src/scan.ts
1802
- import { existsSync as existsSync7, readdirSync, statSync as statSync2 } from "fs";
1827
+ import { existsSync as existsSync7, readdirSync, statSync as statSync3 } from "fs";
1803
1828
  import { homedir as homedir2 } from "os";
1804
1829
  import { join as join9 } from "path";
1805
1830
  function scanAccounts(dir) {
@@ -1807,7 +1832,7 @@ function scanAccounts(dir) {
1807
1832
  if (!existsSync7(accountsDir)) return [];
1808
1833
  return readdirSync(accountsDir).filter((name) => {
1809
1834
  const full = join9(accountsDir, name);
1810
- return statSync2(full).isDirectory() && !name.startsWith(".") && existsSync7(join9(full, ".claude.json"));
1835
+ return statSync3(full).isDirectory() && !name.startsWith(".") && existsSync7(join9(full, ".claude.json"));
1811
1836
  }).map((name) => ({
1812
1837
  handle: name,
1813
1838
  configDir: join9(accountsDir, name)
@@ -1868,7 +1893,7 @@ function suggestAccount(accounts, now = /* @__PURE__ */ new Date()) {
1868
1893
  }
1869
1894
 
1870
1895
  // src/cli.ts
1871
- var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.4.0").option(
1896
+ var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.5.0").option(
1872
1897
  "--config <path>",
1873
1898
  "Path to config directory (default: ~/.config/cc-ping, env: CC_PING_CONFIG)"
1874
1899
  ).hook("preAction", (thisCommand) => {
@@ -2088,7 +2113,8 @@ daemon.command("start").description("Start the daemon process").option(
2088
2113
  quiet: opts.quiet,
2089
2114
  bell: opts.bell,
2090
2115
  notify: opts.notify,
2091
- smartSchedule
2116
+ smartSchedule,
2117
+ version: "1.5.0"
2092
2118
  });
2093
2119
  if (!result.success) {
2094
2120
  console.error(result.error);
@@ -2122,7 +2148,7 @@ daemon.command("stop").description("Stop the daemon process").action(async () =>
2122
2148
  daemon.command("status").description("Show daemon status").option("--json", "Output as JSON", false).action(async (opts) => {
2123
2149
  const { getServiceStatus: getServiceStatus2 } = await Promise.resolve().then(() => (init_service(), service_exports));
2124
2150
  const svc = getServiceStatus2();
2125
- const status = getDaemonStatus();
2151
+ const status = getDaemonStatus({ currentVersion: "1.5.0" });
2126
2152
  if (opts.json) {
2127
2153
  const serviceInfo = svc.installed ? {
2128
2154
  service: {
@@ -2159,6 +2185,9 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
2159
2185
  return;
2160
2186
  }
2161
2187
  console.log(`Daemon is running (PID: ${status.pid})`);
2188
+ if (status.daemonVersion) {
2189
+ console.log(` Version: ${status.daemonVersion}`);
2190
+ }
2162
2191
  console.log(` Started: ${status.startedAt}`);
2163
2192
  console.log(
2164
2193
  ` Interval: ${Math.round((status.intervalMs ?? 0) / 6e4)}m`
@@ -2171,6 +2200,14 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
2171
2200
  const kind = svc.platform === "darwin" ? "launchd" : "systemd";
2172
2201
  console.log(` System service: installed (${kind})`);
2173
2202
  }
2203
+ if (status.versionMismatch) {
2204
+ console.log(
2205
+ ` Warning: daemon is running v${status.daemonVersion} but v${"1.5.0"} is installed.`
2206
+ );
2207
+ console.log(
2208
+ " Restart to pick up the new version: cc-ping daemon stop && cc-ping daemon start"
2209
+ );
2210
+ }
2174
2211
  console.log("");
2175
2212
  printAccountTable();
2176
2213
  });
@@ -2227,7 +2264,8 @@ daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping inte
2227
2264
  pid: process.pid,
2228
2265
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2229
2266
  intervalMs,
2230
- configDir: resolveConfigDir2()
2267
+ configDir: resolveConfigDir2(),
2268
+ version: "1.5.0"
2231
2269
  });
2232
2270
  }
2233
2271
  await runDaemonWithDefaults(intervalMs, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wbern/cc-ping",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "description": "Ping Claude Code sessions to trigger quota windows early across multiple accounts",
5
5
  "type": "module",
6
6
  "bin": {