@wbern/cc-ping 1.8.0 → 1.10.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 -69
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -701,6 +701,7 @@ __export(daemon_exports, {
|
|
|
701
701
|
daemonPidPath: () => daemonPidPath,
|
|
702
702
|
daemonStopPath: () => daemonStopPath,
|
|
703
703
|
getDaemonStatus: () => getDaemonStatus,
|
|
704
|
+
hasVersionChanged: () => hasVersionChanged,
|
|
704
705
|
isProcessRunning: () => isProcessRunning,
|
|
705
706
|
msUntilUtcHour: () => msUntilUtcHour,
|
|
706
707
|
parseInterval: () => parseInterval,
|
|
@@ -712,15 +713,13 @@ __export(daemon_exports, {
|
|
|
712
713
|
stopDaemon: () => stopDaemon,
|
|
713
714
|
writeDaemonState: () => writeDaemonState
|
|
714
715
|
});
|
|
715
|
-
import { execSync, spawn } from "child_process";
|
|
716
|
+
import { execFileSync, execSync, spawn } from "child_process";
|
|
716
717
|
import {
|
|
717
718
|
existsSync as existsSync6,
|
|
718
719
|
closeSync as fsCloseSync,
|
|
719
720
|
openSync as fsOpenSync,
|
|
720
721
|
mkdirSync as mkdirSync4,
|
|
721
722
|
readFileSync as readFileSync6,
|
|
722
|
-
realpathSync,
|
|
723
|
-
statSync as statSync2,
|
|
724
723
|
unlinkSync,
|
|
725
724
|
writeFileSync as writeFileSync3
|
|
726
725
|
} from "fs";
|
|
@@ -832,6 +831,14 @@ function msUntilUtcHour(targetHour, now) {
|
|
|
832
831
|
const diff = targetMs - currentMs;
|
|
833
832
|
return diff > 0 ? diff : diff + 24 * 36e5;
|
|
834
833
|
}
|
|
834
|
+
function hasVersionChanged(runningVersion, getInstalledVersion) {
|
|
835
|
+
if (!runningVersion) return false;
|
|
836
|
+
try {
|
|
837
|
+
return getInstalledVersion() !== runningVersion;
|
|
838
|
+
} catch {
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
835
842
|
async function daemonLoop(intervalMs, options, deps) {
|
|
836
843
|
let wakeDelayMs;
|
|
837
844
|
while (!deps.shouldStop()) {
|
|
@@ -1070,15 +1077,7 @@ async function runDaemon(intervalMs, options, deps) {
|
|
|
1070
1077
|
cleanup();
|
|
1071
1078
|
}
|
|
1072
1079
|
if (exitReason === "upgrade") {
|
|
1073
|
-
|
|
1074
|
-
try {
|
|
1075
|
-
deps.restart();
|
|
1076
|
-
} catch {
|
|
1077
|
-
deps.exit(75);
|
|
1078
|
-
}
|
|
1079
|
-
} else {
|
|
1080
|
-
deps.exit(75);
|
|
1081
|
-
}
|
|
1080
|
+
deps.exit(75);
|
|
1082
1081
|
}
|
|
1083
1082
|
}
|
|
1084
1083
|
async function runDaemonWithDefaults(intervalMs, options) {
|
|
@@ -1108,8 +1107,28 @@ async function runDaemonWithDefaults(intervalMs, options) {
|
|
|
1108
1107
|
return shouldDefer2(/* @__PURE__ */ new Date(), schedule.optimalPingHour);
|
|
1109
1108
|
};
|
|
1110
1109
|
}
|
|
1111
|
-
|
|
1112
|
-
|
|
1110
|
+
let hasUpgraded;
|
|
1111
|
+
if (options.autoUpdate) {
|
|
1112
|
+
let ccPingBin = process.env.CC_PING_BIN;
|
|
1113
|
+
if (!ccPingBin) {
|
|
1114
|
+
try {
|
|
1115
|
+
ccPingBin = execSync("which cc-ping", {
|
|
1116
|
+
encoding: "utf-8",
|
|
1117
|
+
timeout: 5e3
|
|
1118
|
+
}).trim();
|
|
1119
|
+
} catch {
|
|
1120
|
+
console.log(
|
|
1121
|
+
"Auto-update: cc-ping not found in PATH, version checks disabled"
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
if (ccPingBin) {
|
|
1126
|
+
hasUpgraded = () => hasVersionChanged(
|
|
1127
|
+
readDaemonState()?.version,
|
|
1128
|
+
() => execFileSync(ccPingBin, ["--version"], { timeout: 5e3 }).toString().trim()
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1113
1132
|
await runDaemon(intervalMs, options, {
|
|
1114
1133
|
runPing: runPing2,
|
|
1115
1134
|
listAccounts: listAccounts2,
|
|
@@ -1118,36 +1137,14 @@ async function runDaemonWithDefaults(intervalMs, options) {
|
|
|
1118
1137
|
log: (msg) => console.log(msg),
|
|
1119
1138
|
isWindowActive: (handle) => getWindowReset2(handle) !== null,
|
|
1120
1139
|
shouldDeferPing,
|
|
1121
|
-
hasUpgraded
|
|
1122
|
-
try {
|
|
1123
|
-
return statSync2(binaryPath).mtimeMs !== startMtimeMs;
|
|
1124
|
-
} catch {
|
|
1125
|
-
return false;
|
|
1126
|
-
}
|
|
1127
|
-
},
|
|
1140
|
+
hasUpgraded,
|
|
1128
1141
|
updateState: (patch) => {
|
|
1129
1142
|
const current = readDaemonState();
|
|
1130
1143
|
if (current) writeDaemonState({ ...current, ...patch });
|
|
1131
1144
|
},
|
|
1132
1145
|
onSignal: (signal, handler) => process.on(signal, handler),
|
|
1133
1146
|
removeSignal: (signal, handler) => process.removeListener(signal, handler),
|
|
1134
|
-
exit: (code) => process.exit(code)
|
|
1135
|
-
restart: () => {
|
|
1136
|
-
console.log("Restarting daemon with updated binary...");
|
|
1137
|
-
const result = startDaemon({
|
|
1138
|
-
interval: `${intervalMs / 6e4}m`,
|
|
1139
|
-
quiet: options.quiet,
|
|
1140
|
-
bell: options.bell,
|
|
1141
|
-
notify: options.notify,
|
|
1142
|
-
smartSchedule: options.smartSchedule
|
|
1143
|
-
});
|
|
1144
|
-
if (result.success) {
|
|
1145
|
-
console.log(`Daemon restarted (PID: ${result.pid})`);
|
|
1146
|
-
} else {
|
|
1147
|
-
console.log(`Failed to restart: ${result.error}`);
|
|
1148
|
-
process.exit(75);
|
|
1149
|
-
}
|
|
1150
|
-
}
|
|
1147
|
+
exit: (code) => process.exit(code)
|
|
1151
1148
|
});
|
|
1152
1149
|
}
|
|
1153
1150
|
var GRACEFUL_POLL_MS, GRACEFUL_POLL_ATTEMPTS, POST_KILL_DELAY_MS;
|
|
@@ -1212,19 +1209,26 @@ function generateLaunchdPlist(options, execInfo, configDir) {
|
|
|
1212
1209
|
if (options.notify) programArgs.push("--notify");
|
|
1213
1210
|
if (options.smartSchedule === false)
|
|
1214
1211
|
programArgs.push("--smart-schedule", "off");
|
|
1212
|
+
programArgs.push("--auto-update");
|
|
1215
1213
|
const allArgs = [execInfo.executable, ...programArgs];
|
|
1216
1214
|
const argsXml = allArgs.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
1217
1215
|
const logPath = join10(
|
|
1218
1216
|
configDir || join10(nodeHomedir(), ".config", "cc-ping"),
|
|
1219
1217
|
"daemon.log"
|
|
1220
1218
|
);
|
|
1219
|
+
const envVars = {};
|
|
1220
|
+
if (configDir) envVars.CC_PING_CONFIG = configDir;
|
|
1221
|
+
if (execInfo.args.length === 0) envVars.CC_PING_BIN = execInfo.executable;
|
|
1221
1222
|
let envSection = "";
|
|
1222
|
-
if (
|
|
1223
|
+
if (Object.keys(envVars).length > 0) {
|
|
1224
|
+
const entries = Object.entries(envVars).map(
|
|
1225
|
+
([k, v]) => ` <key>${escapeXml(k)}</key>
|
|
1226
|
+
<string>${escapeXml(v)}</string>`
|
|
1227
|
+
).join("\n");
|
|
1223
1228
|
envSection = `
|
|
1224
1229
|
<key>EnvironmentVariables</key>
|
|
1225
1230
|
<dict>
|
|
1226
|
-
|
|
1227
|
-
<string>${escapeXml(configDir)}</string>
|
|
1231
|
+
${entries}
|
|
1228
1232
|
</dict>`;
|
|
1229
1233
|
}
|
|
1230
1234
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -1266,12 +1270,14 @@ function generateSystemdUnit(options, execInfo, configDir) {
|
|
|
1266
1270
|
if (options.notify) programArgs.push("--notify");
|
|
1267
1271
|
if (options.smartSchedule === false)
|
|
1268
1272
|
programArgs.push("--smart-schedule", "off");
|
|
1273
|
+
programArgs.push("--auto-update");
|
|
1269
1274
|
const execStart = [execInfo.executable, ...programArgs].map((a) => a.includes(" ") ? `"${a}"` : a).join(" ");
|
|
1270
|
-
|
|
1271
|
-
if (configDir) {
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
+
const envPairs = [];
|
|
1276
|
+
if (configDir) envPairs.push(`CC_PING_CONFIG=${configDir}`);
|
|
1277
|
+
if (execInfo.args.length === 0)
|
|
1278
|
+
envPairs.push(`CC_PING_BIN=${execInfo.executable}`);
|
|
1279
|
+
const envLine = envPairs.map((p) => `
|
|
1280
|
+
Environment=${p}`).join("");
|
|
1275
1281
|
return `[Unit]
|
|
1276
1282
|
Description=cc-ping daemon - auto-ping Claude Code sessions
|
|
1277
1283
|
|
|
@@ -1766,12 +1772,31 @@ function colorizeStatus(windowStatus) {
|
|
|
1766
1772
|
return yellow(windowStatus);
|
|
1767
1773
|
}
|
|
1768
1774
|
}
|
|
1769
|
-
function
|
|
1775
|
+
function censorHandle(handle) {
|
|
1776
|
+
const atIdx = handle.indexOf("@");
|
|
1777
|
+
if (atIdx !== -1) {
|
|
1778
|
+
const local = handle.slice(0, atIdx);
|
|
1779
|
+
const domain = handle.slice(atIdx + 1);
|
|
1780
|
+
return censorPart(local) + "@" + censorDomain(domain);
|
|
1781
|
+
}
|
|
1782
|
+
return censorDomain(handle);
|
|
1783
|
+
}
|
|
1784
|
+
function censorPart(part) {
|
|
1785
|
+
if (part.length <= 1) return part;
|
|
1786
|
+
return part[0] + "*".repeat(part.length - 1);
|
|
1787
|
+
}
|
|
1788
|
+
function censorDomain(domain) {
|
|
1789
|
+
const lastDot = domain.lastIndexOf(".");
|
|
1790
|
+
if (lastDot === -1) return censorPart(domain);
|
|
1791
|
+
const name = domain.slice(0, lastDot);
|
|
1792
|
+
const tld = domain.slice(lastDot);
|
|
1793
|
+
return censorPart(name) + tld;
|
|
1794
|
+
}
|
|
1795
|
+
function formatStatusLine(status, options) {
|
|
1770
1796
|
const lines = [];
|
|
1771
|
-
const
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
);
|
|
1797
|
+
const handle = options?.censor ? censorHandle(status.handle) : status.handle;
|
|
1798
|
+
const dup = status.duplicateOf ? ` [duplicate of ${options?.censor ? censorHandle(status.duplicateOf) : status.duplicateOf}]` : "";
|
|
1799
|
+
lines.push(` ${handle}: ${colorizeStatus(status.windowStatus)}${dup}`);
|
|
1775
1800
|
const ping = status.lastPing === null ? "never" : status.lastPing.replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
1776
1801
|
lines.push(` - last ping: ${ping}`);
|
|
1777
1802
|
if (status.timeUntilReset !== null) {
|
|
@@ -1826,7 +1851,7 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
|
|
|
1826
1851
|
};
|
|
1827
1852
|
});
|
|
1828
1853
|
}
|
|
1829
|
-
function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles) {
|
|
1854
|
+
function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles, options) {
|
|
1830
1855
|
const accounts = listAccounts();
|
|
1831
1856
|
if (accounts.length === 0) {
|
|
1832
1857
|
log("No accounts configured");
|
|
@@ -1835,12 +1860,12 @@ function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date(),
|
|
|
1835
1860
|
const dupes = findDuplicates(accounts);
|
|
1836
1861
|
const statuses = getAccountStatuses(accounts, now, dupes, deferredHandles);
|
|
1837
1862
|
for (const s of statuses) {
|
|
1838
|
-
log(formatStatusLine(s));
|
|
1863
|
+
log(formatStatusLine(s, options));
|
|
1839
1864
|
}
|
|
1840
1865
|
}
|
|
1841
1866
|
|
|
1842
1867
|
// src/default-command.ts
|
|
1843
|
-
function showDefault(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles) {
|
|
1868
|
+
function showDefault(log = console.log, now = /* @__PURE__ */ new Date(), deferredHandles, options) {
|
|
1844
1869
|
const accounts = listAccounts();
|
|
1845
1870
|
if (accounts.length === 0) {
|
|
1846
1871
|
log("No accounts configured.");
|
|
@@ -1852,7 +1877,7 @@ function showDefault(log = console.log, now = /* @__PURE__ */ new Date(), deferr
|
|
|
1852
1877
|
const dupes = findDuplicates(accounts);
|
|
1853
1878
|
const statuses = getAccountStatuses(accounts, now, dupes, deferredHandles);
|
|
1854
1879
|
for (const s of statuses) {
|
|
1855
|
-
log(formatStatusLine(s));
|
|
1880
|
+
log(formatStatusLine(s, options));
|
|
1856
1881
|
}
|
|
1857
1882
|
const needsPing = statuses.filter((s) => s.windowStatus !== "active");
|
|
1858
1883
|
if (needsPing.length > 0) {
|
|
@@ -1915,7 +1940,7 @@ init_paths();
|
|
|
1915
1940
|
init_run_ping();
|
|
1916
1941
|
|
|
1917
1942
|
// src/scan.ts
|
|
1918
|
-
import { existsSync as existsSync7, readdirSync, statSync as
|
|
1943
|
+
import { existsSync as existsSync7, readdirSync, statSync as statSync2 } from "fs";
|
|
1919
1944
|
import { homedir as homedir2 } from "os";
|
|
1920
1945
|
import { join as join9 } from "path";
|
|
1921
1946
|
function scanAccounts(dir) {
|
|
@@ -1923,7 +1948,7 @@ function scanAccounts(dir) {
|
|
|
1923
1948
|
if (!existsSync7(accountsDir)) return [];
|
|
1924
1949
|
return readdirSync(accountsDir).filter((name) => {
|
|
1925
1950
|
const full = join9(accountsDir, name);
|
|
1926
|
-
return
|
|
1951
|
+
return statSync2(full).isDirectory() && !name.startsWith(".") && existsSync7(join9(full, ".claude.json"));
|
|
1927
1952
|
}).map((name) => ({
|
|
1928
1953
|
handle: name,
|
|
1929
1954
|
configDir: join9(accountsDir, name)
|
|
@@ -1995,16 +2020,19 @@ function getDeferredHandles() {
|
|
|
1995
2020
|
}
|
|
1996
2021
|
return deferred;
|
|
1997
2022
|
}
|
|
1998
|
-
var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.
|
|
2023
|
+
var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.10.0").option(
|
|
1999
2024
|
"--config <path>",
|
|
2000
2025
|
"Path to config directory (default: ~/.config/cc-ping, env: CC_PING_CONFIG)"
|
|
2001
|
-
).hook("preAction", (thisCommand) => {
|
|
2026
|
+
).option("--censor", "Mask account handles in output (for screenshots)").hook("preAction", (thisCommand) => {
|
|
2002
2027
|
const opts = thisCommand.opts();
|
|
2003
2028
|
if (opts.config) {
|
|
2004
2029
|
setConfigDir(opts.config);
|
|
2005
2030
|
}
|
|
2006
2031
|
}).action(() => {
|
|
2007
|
-
|
|
2032
|
+
const opts = program.opts();
|
|
2033
|
+
showDefault(console.log, /* @__PURE__ */ new Date(), getDeferredHandles(), {
|
|
2034
|
+
censor: opts.censor
|
|
2035
|
+
});
|
|
2008
2036
|
});
|
|
2009
2037
|
program.command("ping").description("Ping configured accounts to start quota windows").argument(
|
|
2010
2038
|
"[handles...]",
|
|
@@ -2131,7 +2159,9 @@ program.command("status").description("Show status of all accounts with window i
|
|
|
2131
2159
|
console.log(JSON.stringify(statuses, null, 2));
|
|
2132
2160
|
return;
|
|
2133
2161
|
}
|
|
2134
|
-
printAccountTable(console.log, /* @__PURE__ */ new Date(), deferred
|
|
2162
|
+
printAccountTable(console.log, /* @__PURE__ */ new Date(), deferred, {
|
|
2163
|
+
censor: program.opts().censor
|
|
2164
|
+
});
|
|
2135
2165
|
});
|
|
2136
2166
|
program.command("next-reset").description("Show which account has its quota window resetting soonest").option("--json", "Output as JSON", false).action((opts) => {
|
|
2137
2167
|
const accounts = listAccounts();
|
|
@@ -2222,7 +2252,7 @@ daemon.command("start").description("Start the daemon process").option(
|
|
|
2222
2252
|
bell: opts.bell,
|
|
2223
2253
|
notify: opts.notify,
|
|
2224
2254
|
smartSchedule,
|
|
2225
|
-
version: "1.
|
|
2255
|
+
version: "1.10.0"
|
|
2226
2256
|
});
|
|
2227
2257
|
if (!result.success) {
|
|
2228
2258
|
console.error(result.error);
|
|
@@ -2236,7 +2266,9 @@ daemon.command("start").description("Start the daemon process").option(
|
|
|
2236
2266
|
"Hint: won't survive a reboot. Use `cc-ping daemon install` for a persistent service."
|
|
2237
2267
|
);
|
|
2238
2268
|
}
|
|
2239
|
-
printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles()
|
|
2269
|
+
printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles(), {
|
|
2270
|
+
censor: program.opts().censor
|
|
2271
|
+
});
|
|
2240
2272
|
});
|
|
2241
2273
|
daemon.command("stop").description("Stop the daemon process").action(async () => {
|
|
2242
2274
|
const result = await stopDaemon();
|
|
@@ -2256,7 +2288,7 @@ daemon.command("stop").description("Stop the daemon process").action(async () =>
|
|
|
2256
2288
|
daemon.command("status").description("Show daemon status").option("--json", "Output as JSON", false).action(async (opts) => {
|
|
2257
2289
|
const { getServiceStatus: getServiceStatus2 } = await Promise.resolve().then(() => (init_service(), service_exports));
|
|
2258
2290
|
const svc = getServiceStatus2();
|
|
2259
|
-
const status = getDaemonStatus({ currentVersion: "1.
|
|
2291
|
+
const status = getDaemonStatus({ currentVersion: "1.10.0" });
|
|
2260
2292
|
if (opts.json) {
|
|
2261
2293
|
const serviceInfo = svc.installed ? {
|
|
2262
2294
|
service: {
|
|
@@ -2317,7 +2349,7 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
|
|
|
2317
2349
|
if (status.versionMismatch) {
|
|
2318
2350
|
console.log(
|
|
2319
2351
|
yellow(
|
|
2320
|
-
` Warning: daemon is running v${status.daemonVersion} but v${"1.
|
|
2352
|
+
` Warning: daemon is running v${status.daemonVersion} but v${"1.10.0"} is installed.`
|
|
2321
2353
|
)
|
|
2322
2354
|
);
|
|
2323
2355
|
console.log(
|
|
@@ -2327,7 +2359,9 @@ daemon.command("status").description("Show daemon status").option("--json", "Out
|
|
|
2327
2359
|
);
|
|
2328
2360
|
}
|
|
2329
2361
|
console.log("");
|
|
2330
|
-
printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles()
|
|
2362
|
+
printAccountTable(console.log, /* @__PURE__ */ new Date(), getDeferredHandles(), {
|
|
2363
|
+
censor: program.opts().censor
|
|
2364
|
+
});
|
|
2331
2365
|
});
|
|
2332
2366
|
daemon.command("install").description("Install daemon as a system service (launchd/systemd)").option(
|
|
2333
2367
|
"--interval <minutes>",
|
|
@@ -2366,7 +2400,7 @@ daemon.command("uninstall").description("Remove daemon system service").action(a
|
|
|
2366
2400
|
}
|
|
2367
2401
|
console.log(`Service removed: ${result.servicePath}`);
|
|
2368
2402
|
});
|
|
2369
|
-
daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping interval in milliseconds").option("-q, --quiet", "Suppress ping output", false).option("--bell", "Ring terminal bell on ping failure", false).option("--notify", "Send desktop notification on ping failure", false).option("--smart-schedule <on|off>", "Smart scheduling (default: on)").action(async (opts) => {
|
|
2403
|
+
daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping interval in milliseconds").option("-q, --quiet", "Suppress ping output", false).option("--bell", "Ring terminal bell on ping failure", false).option("--notify", "Send desktop notification on ping failure", false).option("--smart-schedule <on|off>", "Smart scheduling (default: on)").option("--auto-update", "Auto-restart on upgrade (for service installs)").action(async (opts) => {
|
|
2370
2404
|
const intervalMs = Number(opts.intervalMs);
|
|
2371
2405
|
if (!intervalMs || intervalMs <= 0) {
|
|
2372
2406
|
console.error("Invalid --interval-ms");
|
|
@@ -2383,14 +2417,15 @@ daemon.command("_run", { hidden: true }).option("--interval-ms <ms>", "Ping inte
|
|
|
2383
2417
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2384
2418
|
intervalMs,
|
|
2385
2419
|
configDir: resolveConfigDir2(),
|
|
2386
|
-
version: "1.
|
|
2420
|
+
version: "1.10.0"
|
|
2387
2421
|
});
|
|
2388
2422
|
}
|
|
2389
2423
|
await runDaemonWithDefaults(intervalMs, {
|
|
2390
2424
|
quiet: opts.quiet,
|
|
2391
2425
|
bell: opts.bell,
|
|
2392
2426
|
notify: opts.notify,
|
|
2393
|
-
smartSchedule
|
|
2427
|
+
smartSchedule,
|
|
2428
|
+
autoUpdate: opts.autoUpdate
|
|
2394
2429
|
});
|
|
2395
2430
|
});
|
|
2396
2431
|
program.parse();
|