@wbern/cc-ping 1.5.1 → 1.6.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 (2) hide show
  1. package/dist/cli.js +70 -18
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -196,13 +196,14 @@ function isColorEnabled() {
196
196
  function wrap(code, text) {
197
197
  return isColorEnabled() ? `\x1B[${code}m${text}\x1B[0m` : text;
198
198
  }
199
- var green, red, yellow;
199
+ var green, red, yellow, blue;
200
200
  var init_color = __esm({
201
201
  "src/color.ts"() {
202
202
  "use strict";
203
203
  green = (text) => wrap("32", text);
204
204
  red = (text) => wrap("31", text);
205
205
  yellow = (text) => wrap("33", text);
206
+ blue = (text) => wrap("34", text);
206
207
  }
207
208
  });
208
209
 
@@ -991,12 +992,22 @@ async function stopDaemon(deps) {
991
992
  process.kill(pid2, "SIGTERM");
992
993
  }
993
994
  });
995
+ const _forceKill = deps?.forceKill ?? /* c8 ignore next 7 -- production default */
996
+ ((pid2) => {
997
+ if (process.platform === "win32") {
998
+ execSync(`taskkill /F /PID ${pid2}`);
999
+ } else {
1000
+ process.kill(pid2, "SIGKILL");
1001
+ }
1002
+ });
1003
+ const _log = deps?.log ?? ((msg) => console.log(msg));
994
1004
  const status = _getDaemonStatus();
995
1005
  if (!status.running || !status.pid) {
996
1006
  return { success: false, error: "Daemon is not running" };
997
1007
  }
998
1008
  const pid = status.pid;
999
1009
  _writeStopFile();
1010
+ _log(`Waiting for daemon to stop (PID: ${pid})...`);
1000
1011
  for (let i = 0; i < GRACEFUL_POLL_ATTEMPTS; i++) {
1001
1012
  await _sleep(GRACEFUL_POLL_MS);
1002
1013
  if (!_isProcessRunning(pid)) {
@@ -1005,13 +1016,28 @@ async function stopDaemon(deps) {
1005
1016
  return { success: true, pid };
1006
1017
  }
1007
1018
  }
1019
+ _log("Force-killing daemon...");
1008
1020
  try {
1009
1021
  _kill(pid);
1010
1022
  } catch {
1011
1023
  }
1012
1024
  await _sleep(POST_KILL_DELAY_MS);
1025
+ if (_isProcessRunning(pid)) {
1026
+ try {
1027
+ _forceKill(pid);
1028
+ } catch {
1029
+ }
1030
+ await _sleep(POST_KILL_DELAY_MS);
1031
+ }
1013
1032
  _removeDaemonState();
1014
1033
  _removeStopFile();
1034
+ if (_isProcessRunning(pid)) {
1035
+ return {
1036
+ success: false,
1037
+ pid,
1038
+ error: `Failed to stop daemon (PID: ${pid})`
1039
+ };
1040
+ }
1015
1041
  return { success: true, pid };
1016
1042
  }
1017
1043
  async function runDaemon(intervalMs, options, deps) {
@@ -1707,6 +1733,8 @@ function colorizeStatus(windowStatus) {
1707
1733
  return green(windowStatus);
1708
1734
  case "needs ping":
1709
1735
  return red(windowStatus);
1736
+ case "deferred":
1737
+ return blue(windowStatus);
1710
1738
  default:
1711
1739
  return yellow(windowStatus);
1712
1740
  }
@@ -1717,7 +1745,7 @@ function formatStatusLine(status) {
1717
1745
  const dup = status.duplicateOf ? ` [duplicate of ${status.duplicateOf}]` : "";
1718
1746
  return ` ${status.handle}: ${colorizeStatus(status.windowStatus)} last ping: ${ping}${reset}${dup}`;
1719
1747
  }
1720
- function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicates) {
1748
+ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicates, deferredHandles) {
1721
1749
  const dupLookup = /* @__PURE__ */ new Map();
1722
1750
  if (duplicates) {
1723
1751
  for (const group of duplicates.values()) {
@@ -1746,11 +1774,12 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
1746
1774
  };
1747
1775
  }
1748
1776
  const window = getWindowReset(account.handle, now);
1777
+ const isDeferred = !window && deferredHandles?.has(account.handle);
1749
1778
  return {
1750
1779
  handle: account.handle,
1751
1780
  configDir: account.configDir,
1752
1781
  lastPing: lastPing.toISOString(),
1753
- windowStatus: window ? "active" : "needs ping",
1782
+ windowStatus: window ? "active" : isDeferred ? "deferred" : "needs ping",
1754
1783
  timeUntilReset: window ? formatTimeRemaining(window.remainingMs) : null,
1755
1784
  lastCostUsd,
1756
1785
  lastTokens,
@@ -1758,21 +1787,21 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
1758
1787
  };
1759
1788
  });
1760
1789
  }
1761
- function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date()) {
1790
+ function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles) {
1762
1791
  const accounts = listAccounts();
1763
1792
  if (accounts.length === 0) {
1764
1793
  log("No accounts configured");
1765
1794
  return;
1766
1795
  }
1767
1796
  const dupes = findDuplicates(accounts);
1768
- const statuses = getAccountStatuses(accounts, now, dupes);
1797
+ const statuses = getAccountStatuses(accounts, now, dupes, deferredHandles);
1769
1798
  for (const s of statuses) {
1770
1799
  log(formatStatusLine(s));
1771
1800
  }
1772
1801
  }
1773
1802
 
1774
1803
  // src/default-command.ts
1775
- function showDefault(log = console.log, now = /* @__PURE__ */ new Date()) {
1804
+ function showDefault(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles) {
1776
1805
  const accounts = listAccounts();
1777
1806
  if (accounts.length === 0) {
1778
1807
  log("No accounts configured.");
@@ -1782,7 +1811,7 @@ function showDefault(log = console.log, now = /* @__PURE__ */ new Date()) {
1782
1811
  return;
1783
1812
  }
1784
1813
  const dupes = findDuplicates(accounts);
1785
- const statuses = getAccountStatuses(accounts, now, dupes);
1814
+ const statuses = getAccountStatuses(accounts, now, dupes, deferredHandles);
1786
1815
  for (const s of statuses) {
1787
1816
  log(formatStatusLine(s));
1788
1817
  }
@@ -1916,7 +1945,18 @@ function suggestAccount(accounts, now = /* @__PURE__ */ new Date()) {
1916
1945
  }
1917
1946
 
1918
1947
  // src/cli.ts
1919
- var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.5.1").option(
1948
+ function getDeferredHandles() {
1949
+ const deferred = /* @__PURE__ */ new Set();
1950
+ const now = /* @__PURE__ */ new Date();
1951
+ for (const account of listAccounts()) {
1952
+ const schedule = readAccountSchedule(account.configDir);
1953
+ if (schedule && shouldDefer(now, schedule.optimalPingHour).defer) {
1954
+ deferred.add(account.handle);
1955
+ }
1956
+ }
1957
+ return deferred;
1958
+ }
1959
+ var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.6.0").option(
1920
1960
  "--config <path>",
1921
1961
  "Path to config directory (default: ~/.config/cc-ping, env: CC_PING_CONFIG)"
1922
1962
  ).hook("preAction", (thisCommand) => {
@@ -1925,7 +1965,7 @@ var program = new Command().name("cc-ping").description("Ping Claude Code sessio
1925
1965
  setConfigDir(opts.config);
1926
1966
  }
1927
1967
  }).action(() => {
1928
- showDefault();
1968
+ showDefault(console.log, /* @__PURE__ */ new Date(), getDeferredHandles());
1929
1969
  });
1930
1970
  program.command("ping").description("Ping configured accounts to start quota windows").argument(
1931
1971
  "[handles...]",
@@ -2039,14 +2079,20 @@ program.command("list").description("List configured accounts").option("--json",
2039
2079
  }
2040
2080
  });
2041
2081
  program.command("status").description("Show status of all accounts with window information").option("--json", "Output as JSON", false).action((opts) => {
2082
+ const deferred = getDeferredHandles();
2042
2083
  if (opts.json) {
2043
2084
  const accounts = listAccounts();
2044
2085
  const dupes = findDuplicates(accounts);
2045
- const statuses = getAccountStatuses(accounts, /* @__PURE__ */ new Date(), dupes);
2086
+ const statuses = getAccountStatuses(
2087
+ accounts,
2088
+ /* @__PURE__ */ new Date(),
2089
+ dupes,
2090
+ deferred
2091
+ );
2046
2092
  console.log(JSON.stringify(statuses, null, 2));
2047
2093
  return;
2048
2094
  }
2049
- printAccountTable();
2095
+ printAccountTable(console.log, /* @__PURE__ */ new Date(), deferred);
2050
2096
  });
2051
2097
  program.command("next-reset").description("Show which account has its quota window resetting soonest").option("--json", "Output as JSON", false).action((opts) => {
2052
2098
  const accounts = listAccounts();
@@ -2137,7 +2183,7 @@ daemon.command("start").description("Start the daemon process").option(
2137
2183
  bell: opts.bell,
2138
2184
  notify: opts.notify,
2139
2185
  smartSchedule,
2140
- version: "1.5.1"
2186
+ version: "1.6.0"
2141
2187
  });
2142
2188
  if (!result.success) {
2143
2189
  console.error(result.error);
@@ -2151,7 +2197,7 @@ daemon.command("start").description("Start the daemon process").option(
2151
2197
  "Hint: won't survive a reboot. Use `cc-ping daemon install` for a persistent service."
2152
2198
  );
2153
2199
  }
2154
- printAccountTable();
2200
+ printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles());
2155
2201
  });
2156
2202
  daemon.command("stop").description("Stop the daemon process").action(async () => {
2157
2203
  const result = await stopDaemon();
@@ -2171,7 +2217,7 @@ daemon.command("stop").description("Stop the daemon process").action(async () =>
2171
2217
  daemon.command("status").description("Show daemon status").option("--json", "Output as JSON", false).action(async (opts) => {
2172
2218
  const { getServiceStatus: getServiceStatus2 } = await Promise.resolve().then(() => (init_service(), service_exports));
2173
2219
  const svc = getServiceStatus2();
2174
- const status = getDaemonStatus({ currentVersion: "1.5.1" });
2220
+ const status = getDaemonStatus({ currentVersion: "1.6.0" });
2175
2221
  if (opts.json) {
2176
2222
  const serviceInfo = svc.installed ? {
2177
2223
  service: {
@@ -2186,7 +2232,13 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
2186
2232
  }
2187
2233
  const accounts = listAccounts();
2188
2234
  const dupes = findDuplicates(accounts);
2189
- const accountStatuses = getAccountStatuses(accounts, /* @__PURE__ */ new Date(), dupes);
2235
+ const deferred = getDeferredHandles();
2236
+ const accountStatuses = getAccountStatuses(
2237
+ accounts,
2238
+ /* @__PURE__ */ new Date(),
2239
+ dupes,
2240
+ deferred
2241
+ );
2190
2242
  console.log(
2191
2243
  JSON.stringify(
2192
2244
  { ...status, ...serviceInfo, accounts: accountStatuses },
@@ -2225,14 +2277,14 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
2225
2277
  }
2226
2278
  if (status.versionMismatch) {
2227
2279
  console.log(
2228
- ` Warning: daemon is running v${status.daemonVersion} but v${"1.5.1"} is installed.`
2280
+ ` Warning: daemon is running v${status.daemonVersion} but v${"1.6.0"} is installed.`
2229
2281
  );
2230
2282
  console.log(
2231
2283
  " Restart to pick up the new version: cc-ping daemon stop && cc-ping daemon start"
2232
2284
  );
2233
2285
  }
2234
2286
  console.log("");
2235
- printAccountTable();
2287
+ printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles());
2236
2288
  });
2237
2289
  daemon.command("install").description("Install daemon as a system service (launchd/systemd)").option(
2238
2290
  "--interval <minutes>",
@@ -2288,7 +2340,7 @@ daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping inte
2288
2340
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2289
2341
  intervalMs,
2290
2342
  configDir: resolveConfigDir2(),
2291
- version: "1.5.1"
2343
+ version: "1.6.0"
2292
2344
  });
2293
2345
  }
2294
2346
  await runDaemonWithDefaults(intervalMs, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wbern/cc-ping",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "Ping Claude Code sessions to trigger quota windows early across multiple accounts",
5
5
  "type": "module",
6
6
  "bin": {