@tigorhutasuhut/claude-retry 0.1.10 → 0.1.11
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 +2 -1
- package/dist/format.d.ts +15 -0
- package/dist/format.js +36 -0
- package/dist/monitor.js +8 -8
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import { capturePane, inject, listPaneTargets, captureTarget, injectTarget, } fr
|
|
|
3
3
|
import { runMonitor, runMultiMonitor } from "./monitor.js";
|
|
4
4
|
import { readAccessToken, fetchUsage } from "./usage.js";
|
|
5
5
|
import { discoverAccountDirs, resolvePaneConfigDir } from "./accounts.js";
|
|
6
|
+
import { formatClock } from "./format.js";
|
|
6
7
|
const USAGE = `claude-retry — Auto-inject 'continue' when Claude hits a rate limit in zellij
|
|
7
8
|
|
|
8
9
|
Usage:
|
|
@@ -19,7 +20,7 @@ automatically; closed ones are dropped. Logs go to stderr.
|
|
|
19
20
|
'monitor <pane-id>' is the legacy single-pane mode (current session only).`;
|
|
20
21
|
/** Timestamped stderr logger — chatty so the daemon shows clear signs of life. */
|
|
21
22
|
function log(msg) {
|
|
22
|
-
const ts =
|
|
23
|
+
const ts = formatClock(); // local HH:MM:SS
|
|
23
24
|
process.stderr.write(`[${ts}] ${msg}\n`);
|
|
24
25
|
}
|
|
25
26
|
const now = () => Date.now();
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-time formatting helpers.
|
|
3
|
+
* All output uses the machine's local timezone (not UTC).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Local wall-clock "HH:MM:SS" (24h).
|
|
7
|
+
* @param ms - epoch ms; defaults to Date.now() when omitted.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatClock(ms?: number): string;
|
|
10
|
+
/**
|
|
11
|
+
* Local "YYYY-MM-DD HH:MM:SS TZ" (e.g. "2026-06-07 14:05:09 GMT+7").
|
|
12
|
+
* The TZ suffix is derived from Intl.DateTimeFormat; omitted gracefully if unavailable.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatLocalDateTime(ms: number): string;
|
|
15
|
+
//# sourceMappingURL=format.d.ts.map
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local-time formatting helpers.
|
|
3
|
+
* All output uses the machine's local timezone (not UTC).
|
|
4
|
+
*/
|
|
5
|
+
function pad2(n) {
|
|
6
|
+
return String(n).padStart(2, '0');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Local wall-clock "HH:MM:SS" (24h).
|
|
10
|
+
* @param ms - epoch ms; defaults to Date.now() when omitted.
|
|
11
|
+
*/
|
|
12
|
+
export function formatClock(ms) {
|
|
13
|
+
const d = new Date(ms ?? Date.now());
|
|
14
|
+
return `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Local "YYYY-MM-DD HH:MM:SS TZ" (e.g. "2026-06-07 14:05:09 GMT+7").
|
|
18
|
+
* The TZ suffix is derived from Intl.DateTimeFormat; omitted gracefully if unavailable.
|
|
19
|
+
*/
|
|
20
|
+
export function formatLocalDateTime(ms) {
|
|
21
|
+
const d = new Date(ms);
|
|
22
|
+
const date = `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}`;
|
|
23
|
+
const time = `${pad2(d.getHours())}:${pad2(d.getMinutes())}:${pad2(d.getSeconds())}`;
|
|
24
|
+
let tz = '';
|
|
25
|
+
try {
|
|
26
|
+
const parts = Intl.DateTimeFormat(undefined, { timeZoneName: 'short' }).formatToParts(d);
|
|
27
|
+
const tzPart = parts.find((p) => p.type === 'timeZoneName');
|
|
28
|
+
if (tzPart?.value)
|
|
29
|
+
tz = ` ${tzPart.value}`;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Intl unavailable or failed — omit TZ suffix
|
|
33
|
+
}
|
|
34
|
+
return `${date} ${time}${tz}`;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=format.js.map
|
package/dist/monitor.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { match, isBlockedAtBanner } from "./patterns.js";
|
|
2
2
|
import { parseResetTime, calculateWaitMs } from "./time-parser.js";
|
|
3
|
+
import { formatLocalDateTime } from "./format.js";
|
|
3
4
|
const MAX_MISSES = 3;
|
|
4
5
|
export function createState() {
|
|
5
6
|
return { status: 'monitoring', waitUntil: 0, missCount: 0 };
|
|
@@ -65,7 +66,7 @@ async function stepState(state, screenText, now, injectContinue, marginSeconds,
|
|
|
65
66
|
await injectContinue();
|
|
66
67
|
state.status = 'monitoring';
|
|
67
68
|
state.waitUntil = 0;
|
|
68
|
-
logger(`${label} reset reached
|
|
69
|
+
logger(`${label} — reset reached, sent 'continue'`);
|
|
69
70
|
return 'retried';
|
|
70
71
|
}
|
|
71
72
|
// Still limited, before reset → keep waiting.
|
|
@@ -83,7 +84,7 @@ async function stepState(state, screenText, now, injectContinue, marginSeconds,
|
|
|
83
84
|
// limit banner whose quota just reset (restart-after-reset / reopened-idle).
|
|
84
85
|
if (isBlockedAtBanner(screenText)) {
|
|
85
86
|
await injectContinue();
|
|
86
|
-
logger(`${label} cleared-limit banner at bottom —
|
|
87
|
+
logger(`${label} cleared-limit banner at bottom — sent 'continue'`);
|
|
87
88
|
return 'retried';
|
|
88
89
|
}
|
|
89
90
|
logger(`${label} stale banner ignored (account not limited)`);
|
|
@@ -94,7 +95,7 @@ async function stepState(state, screenText, now, injectContinue, marginSeconds,
|
|
|
94
95
|
if (usage.resetsAtMs !== null) {
|
|
95
96
|
state.waitUntil = usage.resetsAtMs + marginMs;
|
|
96
97
|
state.status = 'waiting';
|
|
97
|
-
logger(`${label} account ${accountDir} limited,
|
|
98
|
+
logger(`${label} account ${accountDir} limited, 'continue' at ${formatLocalDateTime(state.waitUntil)}`);
|
|
98
99
|
return 'rate-limited';
|
|
99
100
|
}
|
|
100
101
|
// resetsAtMs null — fall through to text parse for the time, but
|
|
@@ -111,7 +112,7 @@ async function stepState(state, screenText, now, injectContinue, marginSeconds,
|
|
|
111
112
|
// Reset time already passed → limit is over (no roll-to-tomorrow).
|
|
112
113
|
if (isBlockedAtBanner(screenText)) {
|
|
113
114
|
await injectContinue();
|
|
114
|
-
logger(`${label} reset already passed —
|
|
115
|
+
logger(`${label} reset already passed — sent 'continue'`);
|
|
115
116
|
return 'retried';
|
|
116
117
|
}
|
|
117
118
|
logger(`${label} stale banner ignored (reset already passed)`);
|
|
@@ -233,14 +234,13 @@ export async function multiTick(states, deps, marginSeconds, fallbackHours) {
|
|
|
233
234
|
}
|
|
234
235
|
function logPaneStatus(log, label, before, state, status) {
|
|
235
236
|
if (status === 'rate-limited' && before === 'monitoring') {
|
|
236
|
-
|
|
237
|
-
log(`${label} — RATE LIMITED, waiting until ${until}`);
|
|
237
|
+
log(`${label} — rate limited, will send 'continue' at ${formatLocalDateTime(state.waitUntil)}`);
|
|
238
238
|
}
|
|
239
239
|
else if (status === 'rate-limited') {
|
|
240
|
-
log(`${label} —
|
|
240
|
+
log(`${label} — waiting, 'continue' at ${formatLocalDateTime(state.waitUntil)}`);
|
|
241
241
|
}
|
|
242
242
|
else if (status === 'retried') {
|
|
243
|
-
log(`${label} — reset reached,
|
|
243
|
+
log(`${label} — reset reached, sent 'continue'`);
|
|
244
244
|
}
|
|
245
245
|
else {
|
|
246
246
|
log(`${label} — ok`);
|