@wbern/cc-ping 1.0.0 → 1.1.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 -1
- package/dist/cli.js +162 -97
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,7 +90,7 @@ Show all accounts with their quota window state — whether they have an active
|
|
|
90
90
|
|
|
91
91
|
### `cc-ping suggest`
|
|
92
92
|
|
|
93
|
-
Recommend which account to use next based on quota window state. Prefers accounts
|
|
93
|
+
Recommend which account to use next based on quota window state. Prefers accounts that need pinging or whose windows are about to reset.
|
|
94
94
|
|
|
95
95
|
### `cc-ping next-reset`
|
|
96
96
|
|
package/dist/cli.js
CHANGED
|
@@ -186,6 +186,26 @@ var init_bell = __esm({
|
|
|
186
186
|
}
|
|
187
187
|
});
|
|
188
188
|
|
|
189
|
+
// src/color.ts
|
|
190
|
+
function isColorEnabled() {
|
|
191
|
+
if (process.env.NO_COLOR) return false;
|
|
192
|
+
if (process.env.FORCE_COLOR === "1") return true;
|
|
193
|
+
if (process.env.FORCE_COLOR === "0") return false;
|
|
194
|
+
return process.stdout.isTTY ?? false;
|
|
195
|
+
}
|
|
196
|
+
function wrap(code, text) {
|
|
197
|
+
return isColorEnabled() ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
198
|
+
}
|
|
199
|
+
var green, red, yellow;
|
|
200
|
+
var init_color = __esm({
|
|
201
|
+
"src/color.ts"() {
|
|
202
|
+
"use strict";
|
|
203
|
+
green = (text) => wrap("32", text);
|
|
204
|
+
red = (text) => wrap("31", text);
|
|
205
|
+
yellow = (text) => wrap("33", text);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
189
209
|
// src/history.ts
|
|
190
210
|
import { appendFileSync, existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync4 } from "fs";
|
|
191
211
|
import { join as join5 } from "path";
|
|
@@ -463,13 +483,13 @@ async function runPing(accounts, options) {
|
|
|
463
483
|
parallel: options.parallel
|
|
464
484
|
});
|
|
465
485
|
}
|
|
466
|
-
|
|
467
|
-
|
|
486
|
+
const total = results.length;
|
|
487
|
+
for (let idx = 0; idx < results.length; idx++) {
|
|
488
|
+
const r = results[idx];
|
|
489
|
+
const status = r.success ? green("ok") : red("FAIL");
|
|
468
490
|
const detail = r.error ? ` (${r.error})` : "";
|
|
469
|
-
const cr = r.claudeResponse;
|
|
470
|
-
const costInfo = cr ? ` $${cr.total_cost_usd.toFixed(4)} ${cr.usage.input_tokens + cr.usage.output_tokens} tok` : "";
|
|
471
491
|
logger.log(
|
|
472
|
-
` ${r.handle}: ${status} ${r.durationMs}ms${detail}
|
|
492
|
+
` [${idx + 1}/${total}] ${r.handle}: ${status} ${r.durationMs}ms${detail}`
|
|
473
493
|
);
|
|
474
494
|
appendHistoryEntry({
|
|
475
495
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -480,13 +500,13 @@ async function runPing(accounts, options) {
|
|
|
480
500
|
});
|
|
481
501
|
if (r.success) {
|
|
482
502
|
let meta;
|
|
483
|
-
if (
|
|
503
|
+
if (r.claudeResponse) {
|
|
484
504
|
meta = {
|
|
485
|
-
costUsd:
|
|
486
|
-
inputTokens:
|
|
487
|
-
outputTokens:
|
|
488
|
-
model:
|
|
489
|
-
sessionId:
|
|
505
|
+
costUsd: r.claudeResponse.total_cost_usd,
|
|
506
|
+
inputTokens: r.claudeResponse.usage.input_tokens,
|
|
507
|
+
outputTokens: r.claudeResponse.usage.output_tokens,
|
|
508
|
+
model: r.claudeResponse.model,
|
|
509
|
+
sessionId: r.claudeResponse.session_id
|
|
490
510
|
};
|
|
491
511
|
}
|
|
492
512
|
recordPing(r.handle, /* @__PURE__ */ new Date(), meta);
|
|
@@ -544,6 +564,7 @@ var init_run_ping = __esm({
|
|
|
544
564
|
"src/run-ping.ts"() {
|
|
545
565
|
"use strict";
|
|
546
566
|
init_bell();
|
|
567
|
+
init_color();
|
|
547
568
|
init_history();
|
|
548
569
|
init_logger();
|
|
549
570
|
init_notify();
|
|
@@ -1402,27 +1423,8 @@ function generateCompletion(shell) {
|
|
|
1402
1423
|
init_config();
|
|
1403
1424
|
init_daemon();
|
|
1404
1425
|
|
|
1405
|
-
// src/
|
|
1406
|
-
|
|
1407
|
-
if (handles.length === 0) return accounts;
|
|
1408
|
-
const unknown = handles.filter((h) => !accounts.some((a) => a.handle === h));
|
|
1409
|
-
if (unknown.length > 0) {
|
|
1410
|
-
throw new Error(`Unknown account(s): ${unknown.join(", ")}`);
|
|
1411
|
-
}
|
|
1412
|
-
const set = new Set(handles);
|
|
1413
|
-
return accounts.filter((a) => set.has(a.handle));
|
|
1414
|
-
}
|
|
1415
|
-
function filterByGroup(accounts, group) {
|
|
1416
|
-
if (!group) return accounts;
|
|
1417
|
-
const filtered = accounts.filter((a) => a.group === group);
|
|
1418
|
-
if (filtered.length === 0) {
|
|
1419
|
-
throw new Error(`No accounts in group: ${group}`);
|
|
1420
|
-
}
|
|
1421
|
-
return filtered;
|
|
1422
|
-
}
|
|
1423
|
-
|
|
1424
|
-
// src/cli.ts
|
|
1425
|
-
init_history();
|
|
1426
|
+
// src/default-command.ts
|
|
1427
|
+
init_config();
|
|
1426
1428
|
|
|
1427
1429
|
// src/identity.ts
|
|
1428
1430
|
import { readFileSync as readFileSync6 } from "fs";
|
|
@@ -1471,76 +1473,25 @@ function findDuplicates(accounts) {
|
|
|
1471
1473
|
return result;
|
|
1472
1474
|
}
|
|
1473
1475
|
|
|
1474
|
-
// src/next-reset.ts
|
|
1475
|
-
init_state();
|
|
1476
|
-
function getNextReset(accounts, now = /* @__PURE__ */ new Date()) {
|
|
1477
|
-
let best = null;
|
|
1478
|
-
for (const account of accounts) {
|
|
1479
|
-
const window = getWindowReset(account.handle, now);
|
|
1480
|
-
if (!window) continue;
|
|
1481
|
-
if (best === null || window.remainingMs < best.remainingMs) {
|
|
1482
|
-
best = {
|
|
1483
|
-
handle: account.handle,
|
|
1484
|
-
configDir: account.configDir,
|
|
1485
|
-
remainingMs: window.remainingMs,
|
|
1486
|
-
resetAt: window.resetAt.toISOString(),
|
|
1487
|
-
timeUntilReset: formatTimeRemaining(window.remainingMs)
|
|
1488
|
-
};
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
return best;
|
|
1492
|
-
}
|
|
1493
|
-
|
|
1494
|
-
// src/cli.ts
|
|
1495
|
-
init_notify();
|
|
1496
|
-
init_paths();
|
|
1497
|
-
init_run_ping();
|
|
1498
|
-
|
|
1499
|
-
// src/scan.ts
|
|
1500
|
-
import { existsSync as existsSync6, readdirSync, statSync as statSync2 } from "fs";
|
|
1501
|
-
import { homedir as homedir2 } from "os";
|
|
1502
|
-
import { join as join8 } from "path";
|
|
1503
|
-
var ACCOUNTS_DIR = join8(homedir2(), ".claude-accounts");
|
|
1504
|
-
function scanAccounts() {
|
|
1505
|
-
if (!existsSync6(ACCOUNTS_DIR)) return [];
|
|
1506
|
-
return readdirSync(ACCOUNTS_DIR).filter((name) => {
|
|
1507
|
-
const full = join8(ACCOUNTS_DIR, name);
|
|
1508
|
-
return statSync2(full).isDirectory() && !name.startsWith(".");
|
|
1509
|
-
}).map((name) => ({
|
|
1510
|
-
handle: name,
|
|
1511
|
-
configDir: join8(ACCOUNTS_DIR, name)
|
|
1512
|
-
}));
|
|
1513
|
-
}
|
|
1514
|
-
|
|
1515
|
-
// src/stagger.ts
|
|
1516
|
-
init_state();
|
|
1517
|
-
function calculateStagger(accountCount, windowMs = QUOTA_WINDOW_MS) {
|
|
1518
|
-
if (accountCount <= 1) return 0;
|
|
1519
|
-
return Math.floor(windowMs / accountCount);
|
|
1520
|
-
}
|
|
1521
|
-
function parseStagger(value, accountCount) {
|
|
1522
|
-
if (value === "auto") {
|
|
1523
|
-
return calculateStagger(accountCount);
|
|
1524
|
-
}
|
|
1525
|
-
const minutes = Number(value);
|
|
1526
|
-
if (Number.isNaN(minutes)) {
|
|
1527
|
-
throw new Error(`Invalid stagger value: ${value}`);
|
|
1528
|
-
}
|
|
1529
|
-
if (minutes <= 0) {
|
|
1530
|
-
throw new Error("Stagger must be a positive number");
|
|
1531
|
-
}
|
|
1532
|
-
return minutes * 60 * 1e3;
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
1476
|
// src/status.ts
|
|
1477
|
+
init_color();
|
|
1536
1478
|
init_config();
|
|
1537
1479
|
init_state();
|
|
1480
|
+
function colorizeStatus(windowStatus) {
|
|
1481
|
+
switch (windowStatus) {
|
|
1482
|
+
case "active":
|
|
1483
|
+
return green(windowStatus);
|
|
1484
|
+
case "needs ping":
|
|
1485
|
+
return red(windowStatus);
|
|
1486
|
+
default:
|
|
1487
|
+
return yellow(windowStatus);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1538
1490
|
function formatStatusLine(status) {
|
|
1539
1491
|
const ping = status.lastPing === null ? "never" : status.lastPing.replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
1540
1492
|
const reset = status.timeUntilReset !== null ? ` (resets in ${status.timeUntilReset})` : "";
|
|
1541
|
-
const cost = status.lastCostUsd !== null && status.lastTokens !== null ? ` $${status.lastCostUsd.toFixed(4)} ${status.lastTokens} tok` : "";
|
|
1542
1493
|
const dup = status.duplicateOf ? ` [duplicate of ${status.duplicateOf}]` : "";
|
|
1543
|
-
return ` ${status.handle}: ${status.windowStatus} last ping: ${ping}${reset}${
|
|
1494
|
+
return ` ${status.handle}: ${colorizeStatus(status.windowStatus)} last ping: ${ping}${reset}${dup}`;
|
|
1544
1495
|
}
|
|
1545
1496
|
function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicates) {
|
|
1546
1497
|
const dupLookup = /* @__PURE__ */ new Map();
|
|
@@ -1575,7 +1526,7 @@ function getAccountStatuses(accounts, now = /* @__PURE__ */ new Date(), duplicat
|
|
|
1575
1526
|
handle: account.handle,
|
|
1576
1527
|
configDir: account.configDir,
|
|
1577
1528
|
lastPing: lastPing.toISOString(),
|
|
1578
|
-
windowStatus: window ? "active" : "
|
|
1529
|
+
windowStatus: window ? "active" : "needs ping",
|
|
1579
1530
|
timeUntilReset: window ? formatTimeRemaining(window.remainingMs) : null,
|
|
1580
1531
|
lastCostUsd,
|
|
1581
1532
|
lastTokens,
|
|
@@ -1596,6 +1547,118 @@ function printAccountTable(log = console.log, now = /* @__PURE__ */ new Date())
|
|
|
1596
1547
|
}
|
|
1597
1548
|
}
|
|
1598
1549
|
|
|
1550
|
+
// src/default-command.ts
|
|
1551
|
+
function showDefault(log = console.log, now = /* @__PURE__ */ new Date()) {
|
|
1552
|
+
const accounts = listAccounts();
|
|
1553
|
+
if (accounts.length === 0) {
|
|
1554
|
+
log("No accounts configured.");
|
|
1555
|
+
log("\nGet started:");
|
|
1556
|
+
log(" cc-ping scan Auto-discover accounts");
|
|
1557
|
+
log(" cc-ping add <h> <d> Add an account manually");
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
const dupes = findDuplicates(accounts);
|
|
1561
|
+
const statuses = getAccountStatuses(accounts, now, dupes);
|
|
1562
|
+
for (const s of statuses) {
|
|
1563
|
+
log(formatStatusLine(s));
|
|
1564
|
+
}
|
|
1565
|
+
const needsPing = statuses.filter((s) => s.windowStatus !== "active");
|
|
1566
|
+
if (needsPing.length > 0) {
|
|
1567
|
+
log("");
|
|
1568
|
+
log("Suggested next steps:");
|
|
1569
|
+
const handles = needsPing.map((s) => s.handle).join(" ");
|
|
1570
|
+
if (needsPing.length < statuses.length) {
|
|
1571
|
+
log(` cc-ping ping ${handles} Ping accounts that need it`);
|
|
1572
|
+
} else {
|
|
1573
|
+
log(" cc-ping ping Ping all accounts");
|
|
1574
|
+
}
|
|
1575
|
+
log(" cc-ping daemon start Auto-ping on a schedule");
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
// src/filter-accounts.ts
|
|
1580
|
+
function filterAccounts(accounts, handles) {
|
|
1581
|
+
if (handles.length === 0) return accounts;
|
|
1582
|
+
const unknown = handles.filter((h) => !accounts.some((a) => a.handle === h));
|
|
1583
|
+
if (unknown.length > 0) {
|
|
1584
|
+
throw new Error(`Unknown account(s): ${unknown.join(", ")}`);
|
|
1585
|
+
}
|
|
1586
|
+
const set = new Set(handles);
|
|
1587
|
+
return accounts.filter((a) => set.has(a.handle));
|
|
1588
|
+
}
|
|
1589
|
+
function filterByGroup(accounts, group) {
|
|
1590
|
+
if (!group) return accounts;
|
|
1591
|
+
const filtered = accounts.filter((a) => a.group === group);
|
|
1592
|
+
if (filtered.length === 0) {
|
|
1593
|
+
throw new Error(`No accounts in group: ${group}`);
|
|
1594
|
+
}
|
|
1595
|
+
return filtered;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// src/cli.ts
|
|
1599
|
+
init_history();
|
|
1600
|
+
|
|
1601
|
+
// src/next-reset.ts
|
|
1602
|
+
init_state();
|
|
1603
|
+
function getNextReset(accounts, now = /* @__PURE__ */ new Date()) {
|
|
1604
|
+
let best = null;
|
|
1605
|
+
for (const account of accounts) {
|
|
1606
|
+
const window = getWindowReset(account.handle, now);
|
|
1607
|
+
if (!window) continue;
|
|
1608
|
+
if (best === null || window.remainingMs < best.remainingMs) {
|
|
1609
|
+
best = {
|
|
1610
|
+
handle: account.handle,
|
|
1611
|
+
configDir: account.configDir,
|
|
1612
|
+
remainingMs: window.remainingMs,
|
|
1613
|
+
resetAt: window.resetAt.toISOString(),
|
|
1614
|
+
timeUntilReset: formatTimeRemaining(window.remainingMs)
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
return best;
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
// src/cli.ts
|
|
1622
|
+
init_notify();
|
|
1623
|
+
init_paths();
|
|
1624
|
+
init_run_ping();
|
|
1625
|
+
|
|
1626
|
+
// src/scan.ts
|
|
1627
|
+
import { existsSync as existsSync6, readdirSync, statSync as statSync2 } from "fs";
|
|
1628
|
+
import { homedir as homedir2 } from "os";
|
|
1629
|
+
import { join as join8 } from "path";
|
|
1630
|
+
var ACCOUNTS_DIR = join8(homedir2(), ".claude-accounts");
|
|
1631
|
+
function scanAccounts() {
|
|
1632
|
+
if (!existsSync6(ACCOUNTS_DIR)) return [];
|
|
1633
|
+
return readdirSync(ACCOUNTS_DIR).filter((name) => {
|
|
1634
|
+
const full = join8(ACCOUNTS_DIR, name);
|
|
1635
|
+
return statSync2(full).isDirectory() && !name.startsWith(".");
|
|
1636
|
+
}).map((name) => ({
|
|
1637
|
+
handle: name,
|
|
1638
|
+
configDir: join8(ACCOUNTS_DIR, name)
|
|
1639
|
+
}));
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
// src/stagger.ts
|
|
1643
|
+
init_state();
|
|
1644
|
+
function calculateStagger(accountCount, windowMs = QUOTA_WINDOW_MS) {
|
|
1645
|
+
if (accountCount <= 1) return 0;
|
|
1646
|
+
return Math.floor(windowMs / accountCount);
|
|
1647
|
+
}
|
|
1648
|
+
function parseStagger(value, accountCount) {
|
|
1649
|
+
if (value === "auto") {
|
|
1650
|
+
return calculateStagger(accountCount);
|
|
1651
|
+
}
|
|
1652
|
+
const minutes = Number(value);
|
|
1653
|
+
if (Number.isNaN(minutes)) {
|
|
1654
|
+
throw new Error(`Invalid stagger value: ${value}`);
|
|
1655
|
+
}
|
|
1656
|
+
if (minutes <= 0) {
|
|
1657
|
+
throw new Error("Stagger must be a positive number");
|
|
1658
|
+
}
|
|
1659
|
+
return minutes * 60 * 1e3;
|
|
1660
|
+
}
|
|
1661
|
+
|
|
1599
1662
|
// src/suggest.ts
|
|
1600
1663
|
init_state();
|
|
1601
1664
|
function suggestAccount(accounts, now = /* @__PURE__ */ new Date()) {
|
|
@@ -1627,7 +1690,7 @@ function suggestAccount(accounts, now = /* @__PURE__ */ new Date()) {
|
|
|
1627
1690
|
}
|
|
1628
1691
|
|
|
1629
1692
|
// src/cli.ts
|
|
1630
|
-
var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.
|
|
1693
|
+
var program = new Command().name("cc-ping").description("Ping Claude Code sessions to trigger quota windows early").version("1.1.0").option(
|
|
1631
1694
|
"--config <path>",
|
|
1632
1695
|
"Path to config directory (default: ~/.config/cc-ping, env: CC_PING_CONFIG)"
|
|
1633
1696
|
).hook("preAction", (thisCommand) => {
|
|
@@ -1635,6 +1698,8 @@ var program = new Command().name("cc-ping").description("Ping Claude Code sessio
|
|
|
1635
1698
|
if (opts.config) {
|
|
1636
1699
|
setConfigDir(opts.config);
|
|
1637
1700
|
}
|
|
1701
|
+
}).action(() => {
|
|
1702
|
+
showDefault();
|
|
1638
1703
|
});
|
|
1639
1704
|
program.command("ping").description("Ping configured accounts to start quota windows").argument("[handles...]", "Specific account handles to ping (default: all)").option("--parallel", "Ping all accounts in parallel", false).option("-q, --quiet", "Suppress all output except errors (for cron)", false).option("--json", "Output results as JSON", false).option("-g, --group <group>", "Ping only accounts in this group").option("--bell", "Ring terminal bell on ping failure", false).option("--notify", "Send desktop notification on ping failure", false).option(
|
|
1640
1705
|
"--stagger <minutes|auto>",
|