@wbern/cc-ping 1.11.0 → 1.12.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 +82 -24
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -60,6 +60,7 @@ __export(config_exports, {
60
60
  listAccounts: () => listAccounts,
61
61
  loadConfig: () => loadConfig,
62
62
  removeAccount: () => removeAccount,
63
+ resetSchedule: () => resetSchedule,
63
64
  saveConfig: () => saveConfig
64
65
  });
65
66
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
@@ -109,6 +110,21 @@ function removeAccount(handle) {
109
110
  function listAccounts() {
110
111
  return loadConfig().accounts;
111
112
  }
113
+ function resetSchedule(handle, now = /* @__PURE__ */ new Date()) {
114
+ const config = loadConfig();
115
+ if (handle) {
116
+ const account = config.accounts.find((a) => a.handle === handle);
117
+ if (!account) return false;
118
+ account.scheduleResetAt = now.toISOString();
119
+ } else {
120
+ if (config.accounts.length === 0) return false;
121
+ for (const account of config.accounts) {
122
+ account.scheduleResetAt = now.toISOString();
123
+ }
124
+ }
125
+ saveConfig(config);
126
+ return true;
127
+ }
112
128
  var init_config = __esm({
113
129
  "src/config.ts"() {
114
130
  "use strict";
@@ -644,8 +660,11 @@ function shouldDefer(now, optimalPingHour) {
644
660
  }
645
661
  return { defer: false };
646
662
  }
647
- function getAccountSchedule(historyLines, now = /* @__PURE__ */ new Date()) {
648
- const cutoff = now.getTime() - HISTORY_WINDOW_MS;
663
+ function getAccountSchedule(historyLines, now = /* @__PURE__ */ new Date(), resetAt) {
664
+ const cutoff = Math.max(
665
+ now.getTime() - HISTORY_WINDOW_MS,
666
+ resetAt?.getTime() ?? 0
667
+ );
649
668
  const timestamps = [];
650
669
  const daysSeen = /* @__PURE__ */ new Set();
651
670
  for (const line of historyLines) {
@@ -667,14 +686,16 @@ function getAccountSchedule(historyLines, now = /* @__PURE__ */ new Date()) {
667
686
  if (max <= avg * 1.5) return null;
668
687
  const optimalPingHour = findOptimalPingHour(histogram);
669
688
  if (optimalPingHour === -1) return null;
670
- return { optimalPingHour, histogram };
689
+ const peakStart = (optimalPingHour + Math.floor(QUOTA_WINDOW_HOURS / 2) + 1) % 24;
690
+ const peakEnd = (peakStart + QUOTA_WINDOW_HOURS) % 24;
691
+ return { optimalPingHour, peakStart, peakEnd, histogram };
671
692
  }
672
- function readAccountSchedule(configDir, now = /* @__PURE__ */ new Date()) {
693
+ function readAccountSchedule(configDir, now = /* @__PURE__ */ new Date(), resetAt) {
673
694
  const historyPath = join6(configDir, "history.jsonl");
674
695
  if (!existsSync5(historyPath)) return null;
675
696
  const content = readFileSync5(historyPath, "utf-8");
676
697
  const lines = content.split("\n").filter((l) => l.trim());
677
- return getAccountSchedule(lines, now);
698
+ return getAccountSchedule(lines, now, resetAt);
678
699
  }
679
700
  function parseSmartSchedule(value) {
680
701
  const lower = value.toLowerCase();
@@ -1093,10 +1114,15 @@ async function runDaemonWithDefaults(intervalMs, options) {
1093
1114
  if (smartScheduleEnabled) {
1094
1115
  const { readAccountSchedule: readAccountSchedule2, shouldDefer: shouldDefer2 } = await Promise.resolve().then(() => (init_schedule(), schedule_exports));
1095
1116
  for (const account of listAccounts2()) {
1096
- const schedule = readAccountSchedule2(account.configDir);
1097
- if (schedule) {
1117
+ const resetAt = account.scheduleResetAt ? new Date(account.scheduleResetAt) : void 0;
1118
+ const schedule2 = readAccountSchedule2(
1119
+ account.configDir,
1120
+ /* @__PURE__ */ new Date(),
1121
+ resetAt
1122
+ );
1123
+ if (schedule2) {
1098
1124
  console.log(
1099
- `Smart schedule: ${account.handle} \u2192 optimal ping at ${schedule.optimalPingHour}:00 UTC`
1125
+ `Smart schedule: ${account.handle} \u2192 optimal ping at ${schedule2.optimalPingHour}:00 UTC`
1100
1126
  );
1101
1127
  } else {
1102
1128
  console.log(
@@ -1104,10 +1130,13 @@ async function runDaemonWithDefaults(intervalMs, options) {
1104
1130
  );
1105
1131
  }
1106
1132
  }
1107
- shouldDeferPing = (_handle, configDir) => {
1108
- const schedule = readAccountSchedule2(configDir);
1109
- if (!schedule) return { defer: false };
1110
- return shouldDefer2(/* @__PURE__ */ new Date(), schedule.optimalPingHour);
1133
+ shouldDeferPing = (handle, configDir) => {
1134
+ const accounts = listAccounts2();
1135
+ const account = accounts.find((a) => a.handle === handle);
1136
+ const resetAt = account?.scheduleResetAt ? new Date(account.scheduleResetAt) : void 0;
1137
+ const schedule2 = readAccountSchedule2(configDir, /* @__PURE__ */ new Date(), resetAt);
1138
+ if (!schedule2) return { defer: false };
1139
+ return shouldDefer2(/* @__PURE__ */ new Date(), schedule2.optimalPingHour);
1111
1140
  };
1112
1141
  }
1113
1142
  let hasUpgraded;
@@ -1806,7 +1835,10 @@ function formatStatusLine(status, options) {
1806
1835
  lines.push(` - resets in ${status.timeUntilReset}`);
1807
1836
  }
1808
1837
  if (status.deferUntilUtcHour !== void 0) {
1809
- lines.push(` - scheduled ping at ${status.deferUntilUtcHour}:00 UTC`);
1838
+ const peak = status.peakWindowUtc ? ` (peak: ${status.peakWindowUtc} UTC)` : "";
1839
+ lines.push(
1840
+ ` - scheduled ping at ${status.deferUntilUtcHour}:00 UTC${peak}`
1841
+ );
1810
1842
  }
1811
1843
  return lines.join("\n");
1812
1844
  }
@@ -1839,8 +1871,10 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
1839
1871
  };
1840
1872
  }
1841
1873
  const window = getWindowReset(account.handle, now);
1842
- const isDeferred = !window && deferredHandles?.has(account.handle);
1843
- const deferUntilUtcHour = isDeferred ? deferredHandles?.get(account.handle) : void 0;
1874
+ const deferInfo = deferredHandles?.get(account.handle);
1875
+ const isDeferred = !window && deferInfo !== void 0;
1876
+ const deferUntilUtcHour = isDeferred ? deferInfo.optimalPingHour : void 0;
1877
+ const peakWindowUtc = isDeferred ? `${deferInfo.peakStart}-${deferInfo.peakEnd}` : void 0;
1844
1878
  return {
1845
1879
  handle: account.handle,
1846
1880
  configDir: account.configDir,
@@ -1850,7 +1884,8 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
1850
1884
  lastCostUsd,
1851
1885
  lastTokens,
1852
1886
  duplicateOf,
1853
- deferUntilUtcHour
1887
+ deferUntilUtcHour,
1888
+ peakWindowUtc
1854
1889
  };
1855
1890
  });
1856
1891
  }
@@ -2016,14 +2051,19 @@ function getDeferredHandles() {
2016
2051
  const deferred = /* @__PURE__ */ new Map();
2017
2052
  const now = /* @__PURE__ */ new Date();
2018
2053
  for (const account of listAccounts()) {
2019
- const schedule = readAccountSchedule(account.configDir);
2020
- if (schedule && shouldDefer(now, schedule.optimalPingHour).defer) {
2021
- deferred.set(account.handle, schedule.optimalPingHour);
2054
+ const resetAt = account.scheduleResetAt ? new Date(account.scheduleResetAt) : void 0;
2055
+ const schedule2 = readAccountSchedule(account.configDir, now, resetAt);
2056
+ if (schedule2 && shouldDefer(now, schedule2.optimalPingHour).defer) {
2057
+ deferred.set(account.handle, {
2058
+ optimalPingHour: schedule2.optimalPingHour,
2059
+ peakStart: schedule2.peakStart,
2060
+ peakEnd: schedule2.peakEnd
2061
+ });
2022
2062
  }
2023
2063
  }
2024
2064
  return deferred;
2025
2065
  }
2026
- var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.11.0").option(
2066
+ var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.12.0").option(
2027
2067
  "--config <path>",
2028
2068
  "Path to config directory (default: ~/.config/cc-ping, env: CC_PING_CONFIG)"
2029
2069
  ).hook("preAction", (thisCommand) => {
@@ -2252,7 +2292,7 @@ daemon.command("start").description("Start the daemon process").option(
2252
2292
  bell: opts.bell,
2253
2293
  notify: opts.notify,
2254
2294
  smartSchedule,
2255
- version: "1.11.0"
2295
+ version: "1.12.0"
2256
2296
  });
2257
2297
  if (!result.success) {
2258
2298
  console.error(result.error);
@@ -2288,7 +2328,7 @@ daemon.command("stop").description("Stop the daemon process").action(async () =>
2288
2328
  daemon.command("status").description("Show daemon status").option("--json", "Output as JSON", false).option("--censor", "Mask account handles in output (for screenshots)").action(async (opts) => {
2289
2329
  const { getServiceStatus: getServiceStatus2 } = await Promise.resolve().then(() => (init_service(), service_exports));
2290
2330
  const svc = getServiceStatus2();
2291
- const status = getDaemonStatus({ currentVersion: "1.11.0" });
2331
+ const status = getDaemonStatus({ currentVersion: "1.12.0" });
2292
2332
  if (opts.json) {
2293
2333
  const serviceInfo = svc.installed ? {
2294
2334
  service: {
@@ -2349,7 +2389,7 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
2349
2389
  if (status.versionMismatch) {
2350
2390
  console.log(
2351
2391
  yellow(
2352
- ` Warning: daemon is running v${status.daemonVersion} but v${"1.11.0"} is installed.`
2392
+ ` Warning: daemon is running v${status.daemonVersion} but v${"1.12.0"} is installed.`
2353
2393
  )
2354
2394
  );
2355
2395
  console.log(
@@ -2417,7 +2457,7 @@ daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping inte
2417
2457
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
2418
2458
  intervalMs,
2419
2459
  configDir: resolveConfigDir2(),
2420
- version: "1.11.0"
2460
+ version: "1.12.0"
2421
2461
  });
2422
2462
  }
2423
2463
  await runDaemonWithDefaults(intervalMs, {
@@ -2428,4 +2468,22 @@ daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping inte
2428
2468
  autoUpdate: opts.autoUpdate
2429
2469
  });
2430
2470
  });
2471
+ var schedule = program.command("schedule").description("Manage smart scheduling");
2472
+ schedule.command("reset").description("Reset smart scheduling data to recompute optimal ping times").argument("[handle]", "Specific account handle (default: all accounts)").action((handle) => {
2473
+ if (resetSchedule(handle)) {
2474
+ if (handle) {
2475
+ console.log(`Schedule reset for: ${handle}`);
2476
+ } else {
2477
+ console.log("Schedule reset for all accounts");
2478
+ }
2479
+ } else {
2480
+ if (handle) {
2481
+ console.error(`Account not found: ${handle}`);
2482
+ process.exit(1);
2483
+ } else {
2484
+ console.error("No accounts configured");
2485
+ process.exit(1);
2486
+ }
2487
+ }
2488
+ });
2431
2489
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wbern/cc-ping",
3
- "version": "1.11.0",
3
+ "version": "1.12.0",
4
4
  "description": "Ping Claude Code sessions to trigger quota windows early across multiple accounts",
5
5
  "type": "module",
6
6
  "bin": {