@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.
- package/README.md +1 -0
- package/dist/cli.js +47 -9
- 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
|
|
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
|
|
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.
|
|
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, {
|