@wbern/cc-ping 1.5.0 → 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.
- package/dist/cli.js +104 -29
- 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
|
|
|
@@ -701,6 +702,7 @@ __export(daemon_exports, {
|
|
|
701
702
|
daemonStopPath: () => daemonStopPath,
|
|
702
703
|
getDaemonStatus: () => getDaemonStatus,
|
|
703
704
|
isProcessRunning: () => isProcessRunning,
|
|
705
|
+
msUntilUtcHour: () => msUntilUtcHour,
|
|
704
706
|
parseInterval: () => parseInterval,
|
|
705
707
|
readDaemonState: () => readDaemonState,
|
|
706
708
|
removeDaemonState: () => removeDaemonState,
|
|
@@ -824,6 +826,12 @@ function formatUptime(ms) {
|
|
|
824
826
|
if (minutes > 0) return `${minutes}m ${seconds}s`;
|
|
825
827
|
return `${seconds}s`;
|
|
826
828
|
}
|
|
829
|
+
function msUntilUtcHour(targetHour, now) {
|
|
830
|
+
const currentMs = now.getUTCHours() * 36e5 + now.getUTCMinutes() * 6e4 + now.getUTCSeconds() * 1e3 + now.getUTCMilliseconds();
|
|
831
|
+
const targetMs = targetHour * 36e5;
|
|
832
|
+
const diff = targetMs - currentMs;
|
|
833
|
+
return diff > 0 ? diff : diff + 24 * 36e5;
|
|
834
|
+
}
|
|
827
835
|
async function daemonLoop(intervalMs, options, deps) {
|
|
828
836
|
let wakeDelayMs;
|
|
829
837
|
while (!deps.shouldStop()) {
|
|
@@ -837,23 +845,28 @@ async function daemonLoop(intervalMs, options, deps) {
|
|
|
837
845
|
if (skipped > 0) {
|
|
838
846
|
deps.log(`Skipping ${skipped} account(s) with active window`);
|
|
839
847
|
}
|
|
848
|
+
let soonestDeferHour;
|
|
840
849
|
if (deps.shouldDeferPing) {
|
|
841
850
|
const deferResults = /* @__PURE__ */ new Map();
|
|
842
851
|
for (const a of accounts) {
|
|
843
|
-
deferResults.set(
|
|
844
|
-
a.handle,
|
|
845
|
-
deps.shouldDeferPing(a.handle, a.configDir).defer
|
|
846
|
-
);
|
|
852
|
+
deferResults.set(a.handle, deps.shouldDeferPing(a.handle, a.configDir));
|
|
847
853
|
}
|
|
848
|
-
const
|
|
849
|
-
if (
|
|
850
|
-
accounts = accounts.filter((a) => !deferResults.get(a.handle));
|
|
851
|
-
deps.log(`Deferring ${
|
|
854
|
+
const deferred = [...deferResults.entries()].filter(([, r]) => r.defer);
|
|
855
|
+
if (deferred.length > 0) {
|
|
856
|
+
accounts = accounts.filter((a) => !deferResults.get(a.handle)?.defer);
|
|
857
|
+
deps.log(`Deferring ${deferred.length} account(s) (smart scheduling)`);
|
|
858
|
+
for (const [, r] of deferred) {
|
|
859
|
+
if (r.deferUntilUtcHour !== void 0 && (soonestDeferHour === void 0 || /* c8 ignore next -- production default */
|
|
860
|
+
msUntilUtcHour(r.deferUntilUtcHour, deps.now?.() ?? /* @__PURE__ */ new Date()) < /* c8 ignore next -- production default */
|
|
861
|
+
msUntilUtcHour(soonestDeferHour, deps.now?.() ?? /* @__PURE__ */ new Date()))) {
|
|
862
|
+
soonestDeferHour = r.deferUntilUtcHour;
|
|
863
|
+
}
|
|
864
|
+
}
|
|
852
865
|
}
|
|
853
866
|
}
|
|
854
867
|
if (accounts.length === 0) {
|
|
855
868
|
deps.log(
|
|
856
|
-
allAccounts.length === 0 ? "No accounts configured, waiting..." : "All accounts have active windows, waiting..."
|
|
869
|
+
allAccounts.length === 0 ? "No accounts configured, waiting..." : soonestDeferHour !== void 0 ? "All accounts deferred (smart scheduling), waiting..." : "All accounts have active windows, waiting..."
|
|
857
870
|
);
|
|
858
871
|
} else {
|
|
859
872
|
deps.log(
|
|
@@ -882,9 +895,20 @@ async function daemonLoop(intervalMs, options, deps) {
|
|
|
882
895
|
deps.updateState?.({ lastPingAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
883
896
|
}
|
|
884
897
|
if (deps.shouldStop()) break;
|
|
885
|
-
|
|
898
|
+
let sleepMs = intervalMs;
|
|
899
|
+
if (soonestDeferHour !== void 0) {
|
|
900
|
+
const msUntilDefer = msUntilUtcHour(
|
|
901
|
+
soonestDeferHour,
|
|
902
|
+
/* c8 ignore next -- production default */
|
|
903
|
+
deps.now?.() ?? /* @__PURE__ */ new Date()
|
|
904
|
+
);
|
|
905
|
+
if (msUntilDefer > 0 && msUntilDefer < intervalMs) {
|
|
906
|
+
sleepMs = msUntilDefer;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
deps.log(`Sleeping ${Math.round(sleepMs / 6e4)}m until next ping...`);
|
|
886
910
|
const sleepStart = Date.now();
|
|
887
|
-
await deps.sleep(
|
|
911
|
+
await deps.sleep(sleepMs);
|
|
888
912
|
const overshootMs = Date.now() - sleepStart - intervalMs;
|
|
889
913
|
if (overshootMs > 6e4) {
|
|
890
914
|
wakeDelayMs = overshootMs;
|
|
@@ -968,12 +992,22 @@ async function stopDaemon(deps) {
|
|
|
968
992
|
process.kill(pid2, "SIGTERM");
|
|
969
993
|
}
|
|
970
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));
|
|
971
1004
|
const status = _getDaemonStatus();
|
|
972
1005
|
if (!status.running || !status.pid) {
|
|
973
1006
|
return { success: false, error: "Daemon is not running" };
|
|
974
1007
|
}
|
|
975
1008
|
const pid = status.pid;
|
|
976
1009
|
_writeStopFile();
|
|
1010
|
+
_log(`Waiting for daemon to stop (PID: ${pid})...`);
|
|
977
1011
|
for (let i = 0; i < GRACEFUL_POLL_ATTEMPTS; i++) {
|
|
978
1012
|
await _sleep(GRACEFUL_POLL_MS);
|
|
979
1013
|
if (!_isProcessRunning(pid)) {
|
|
@@ -982,13 +1016,28 @@ async function stopDaemon(deps) {
|
|
|
982
1016
|
return { success: true, pid };
|
|
983
1017
|
}
|
|
984
1018
|
}
|
|
1019
|
+
_log("Force-killing daemon...");
|
|
985
1020
|
try {
|
|
986
1021
|
_kill(pid);
|
|
987
1022
|
} catch {
|
|
988
1023
|
}
|
|
989
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
|
+
}
|
|
990
1032
|
_removeDaemonState();
|
|
991
1033
|
_removeStopFile();
|
|
1034
|
+
if (_isProcessRunning(pid)) {
|
|
1035
|
+
return {
|
|
1036
|
+
success: false,
|
|
1037
|
+
pid,
|
|
1038
|
+
error: `Failed to stop daemon (PID: ${pid})`
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
992
1041
|
return { success: true, pid };
|
|
993
1042
|
}
|
|
994
1043
|
async function runDaemon(intervalMs, options, deps) {
|
|
@@ -1684,6 +1733,8 @@ function colorizeStatus(windowStatus) {
|
|
|
1684
1733
|
return green(windowStatus);
|
|
1685
1734
|
case "needs ping":
|
|
1686
1735
|
return red(windowStatus);
|
|
1736
|
+
case "deferred":
|
|
1737
|
+
return blue(windowStatus);
|
|
1687
1738
|
default:
|
|
1688
1739
|
return yellow(windowStatus);
|
|
1689
1740
|
}
|
|
@@ -1694,7 +1745,7 @@ function formatStatusLine(status) {
|
|
|
1694
1745
|
const dup = status.duplicateOf ? ` [duplicate of ${status.duplicateOf}]` : "";
|
|
1695
1746
|
return ` ${status.handle}: ${colorizeStatus(status.windowStatus)} last ping: ${ping}${reset}${dup}`;
|
|
1696
1747
|
}
|
|
1697
|
-
function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicates) {
|
|
1748
|
+
function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicates, deferredHandles) {
|
|
1698
1749
|
const dupLookup = /* @__PURE__ */ new Map();
|
|
1699
1750
|
if (duplicates) {
|
|
1700
1751
|
for (const group of duplicates.values()) {
|
|
@@ -1723,11 +1774,12 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
|
|
|
1723
1774
|
};
|
|
1724
1775
|
}
|
|
1725
1776
|
const window = getWindowReset(account.handle, now);
|
|
1777
|
+
const isDeferred = !window && deferredHandles?.has(account.handle);
|
|
1726
1778
|
return {
|
|
1727
1779
|
handle: account.handle,
|
|
1728
1780
|
configDir: account.configDir,
|
|
1729
1781
|
lastPing: lastPing.toISOString(),
|
|
1730
|
-
windowStatus: window ? "active" : "needs ping",
|
|
1782
|
+
windowStatus: window ? "active" : isDeferred ? "deferred" : "needs ping",
|
|
1731
1783
|
timeUntilReset: window ? formatTimeRemaining(window.remainingMs) : null,
|
|
1732
1784
|
lastCostUsd,
|
|
1733
1785
|
lastTokens,
|
|
@@ -1735,21 +1787,21 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
|
|
|
1735
1787
|
};
|
|
1736
1788
|
});
|
|
1737
1789
|
}
|
|
1738
|
-
function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date()) {
|
|
1790
|
+
function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles) {
|
|
1739
1791
|
const accounts = listAccounts();
|
|
1740
1792
|
if (accounts.length === 0) {
|
|
1741
1793
|
log("No accounts configured");
|
|
1742
1794
|
return;
|
|
1743
1795
|
}
|
|
1744
1796
|
const dupes = findDuplicates(accounts);
|
|
1745
|
-
const statuses = getAccountStatuses(accounts, now, dupes);
|
|
1797
|
+
const statuses = getAccountStatuses(accounts, now, dupes, deferredHandles);
|
|
1746
1798
|
for (const s of statuses) {
|
|
1747
1799
|
log(formatStatusLine(s));
|
|
1748
1800
|
}
|
|
1749
1801
|
}
|
|
1750
1802
|
|
|
1751
1803
|
// src/default-command.ts
|
|
1752
|
-
function showDefault(log = console.log, now = /* @__PURE__ */ new Date()) {
|
|
1804
|
+
function showDefault(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles) {
|
|
1753
1805
|
const accounts = listAccounts();
|
|
1754
1806
|
if (accounts.length === 0) {
|
|
1755
1807
|
log("No accounts configured.");
|
|
@@ -1759,7 +1811,7 @@ function showDefault(log = console.log, now = /* @__PURE__ */ new Date()) {
|
|
|
1759
1811
|
return;
|
|
1760
1812
|
}
|
|
1761
1813
|
const dupes = findDuplicates(accounts);
|
|
1762
|
-
const statuses = getAccountStatuses(accounts, now, dupes);
|
|
1814
|
+
const statuses = getAccountStatuses(accounts, now, dupes, deferredHandles);
|
|
1763
1815
|
for (const s of statuses) {
|
|
1764
1816
|
log(formatStatusLine(s));
|
|
1765
1817
|
}
|
|
@@ -1893,7 +1945,18 @@ function suggestAccount(accounts, now = /* @__PURE__ */ new Date()) {
|
|
|
1893
1945
|
}
|
|
1894
1946
|
|
|
1895
1947
|
// src/cli.ts
|
|
1896
|
-
|
|
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(
|
|
1897
1960
|
"--config <path>",
|
|
1898
1961
|
"Path to config directory (default: ~/.config/cc-ping, env: CC_PING_CONFIG)"
|
|
1899
1962
|
).hook("preAction", (thisCommand) => {
|
|
@@ -1902,7 +1965,7 @@ var program = new Command().name("cc-ping").description("Ping Claude Code sessio
|
|
|
1902
1965
|
setConfigDir(opts.config);
|
|
1903
1966
|
}
|
|
1904
1967
|
}).action(() => {
|
|
1905
|
-
showDefault();
|
|
1968
|
+
showDefault(console.log, /* @__PURE__ */ new Date(), getDeferredHandles());
|
|
1906
1969
|
});
|
|
1907
1970
|
program.command("ping").description("Ping configured accounts to start quota windows").argument(
|
|
1908
1971
|
"[handles...]",
|
|
@@ -2016,14 +2079,20 @@ program.command("list").description("List configured accounts").option("--json",
|
|
|
2016
2079
|
}
|
|
2017
2080
|
});
|
|
2018
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();
|
|
2019
2083
|
if (opts.json) {
|
|
2020
2084
|
const accounts = listAccounts();
|
|
2021
2085
|
const dupes = findDuplicates(accounts);
|
|
2022
|
-
const statuses = getAccountStatuses(
|
|
2086
|
+
const statuses = getAccountStatuses(
|
|
2087
|
+
accounts,
|
|
2088
|
+
/* @__PURE__ */ new Date(),
|
|
2089
|
+
dupes,
|
|
2090
|
+
deferred
|
|
2091
|
+
);
|
|
2023
2092
|
console.log(JSON.stringify(statuses, null, 2));
|
|
2024
2093
|
return;
|
|
2025
2094
|
}
|
|
2026
|
-
printAccountTable();
|
|
2095
|
+
printAccountTable(console.log, /* @__PURE__ */ new Date(), deferred);
|
|
2027
2096
|
});
|
|
2028
2097
|
program.command("next-reset").description("Show which account has its quota window resetting soonest").option("--json", "Output as JSON", false).action((opts) => {
|
|
2029
2098
|
const accounts = listAccounts();
|
|
@@ -2114,7 +2183,7 @@ daemon.command("start").description("Start the daemon process").option(
|
|
|
2114
2183
|
bell: opts.bell,
|
|
2115
2184
|
notify: opts.notify,
|
|
2116
2185
|
smartSchedule,
|
|
2117
|
-
version: "1.
|
|
2186
|
+
version: "1.6.0"
|
|
2118
2187
|
});
|
|
2119
2188
|
if (!result.success) {
|
|
2120
2189
|
console.error(result.error);
|
|
@@ -2128,7 +2197,7 @@ daemon.command("start").description("Start the daemon process").option(
|
|
|
2128
2197
|
"Hint: won't survive a reboot. Use `cc-ping daemon install` for a persistent service."
|
|
2129
2198
|
);
|
|
2130
2199
|
}
|
|
2131
|
-
printAccountTable();
|
|
2200
|
+
printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles());
|
|
2132
2201
|
});
|
|
2133
2202
|
daemon.command("stop").description("Stop the daemon process").action(async () => {
|
|
2134
2203
|
const result = await stopDaemon();
|
|
@@ -2148,7 +2217,7 @@ daemon.command("stop").description("Stop the daemon process").action(async () =>
|
|
|
2148
2217
|
daemon.command("status").description("Show daemon status").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
2149
2218
|
const { getServiceStatus: getServiceStatus2 } = await Promise.resolve().then(() => (init_service(), service_exports));
|
|
2150
2219
|
const svc = getServiceStatus2();
|
|
2151
|
-
const status = getDaemonStatus({ currentVersion: "1.
|
|
2220
|
+
const status = getDaemonStatus({ currentVersion: "1.6.0" });
|
|
2152
2221
|
if (opts.json) {
|
|
2153
2222
|
const serviceInfo = svc.installed ? {
|
|
2154
2223
|
service: {
|
|
@@ -2163,7 +2232,13 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
|
|
|
2163
2232
|
}
|
|
2164
2233
|
const accounts = listAccounts();
|
|
2165
2234
|
const dupes = findDuplicates(accounts);
|
|
2166
|
-
const
|
|
2235
|
+
const deferred = getDeferredHandles();
|
|
2236
|
+
const accountStatuses = getAccountStatuses(
|
|
2237
|
+
accounts,
|
|
2238
|
+
/* @__PURE__ */ new Date(),
|
|
2239
|
+
dupes,
|
|
2240
|
+
deferred
|
|
2241
|
+
);
|
|
2167
2242
|
console.log(
|
|
2168
2243
|
JSON.stringify(
|
|
2169
2244
|
{ ...status, ...serviceInfo, accounts: accountStatuses },
|
|
@@ -2202,14 +2277,14 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
|
|
|
2202
2277
|
}
|
|
2203
2278
|
if (status.versionMismatch) {
|
|
2204
2279
|
console.log(
|
|
2205
|
-
` Warning: daemon is running v${status.daemonVersion} but v${"1.
|
|
2280
|
+
` Warning: daemon is running v${status.daemonVersion} but v${"1.6.0"} is installed.`
|
|
2206
2281
|
);
|
|
2207
2282
|
console.log(
|
|
2208
2283
|
" Restart to pick up the new version: cc-ping daemon stop && cc-ping daemon start"
|
|
2209
2284
|
);
|
|
2210
2285
|
}
|
|
2211
2286
|
console.log("");
|
|
2212
|
-
printAccountTable();
|
|
2287
|
+
printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles());
|
|
2213
2288
|
});
|
|
2214
2289
|
daemon.command("install").description("Install daemon as a system service (launchd/systemd)").option(
|
|
2215
2290
|
"--interval <minutes>",
|
|
@@ -2265,7 +2340,7 @@ daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping inte
|
|
|
2265
2340
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2266
2341
|
intervalMs,
|
|
2267
2342
|
configDir: resolveConfigDir2(),
|
|
2268
|
-
version: "1.
|
|
2343
|
+
version: "1.6.0"
|
|
2269
2344
|
});
|
|
2270
2345
|
}
|
|
2271
2346
|
await runDaemonWithDefaults(intervalMs, {
|