@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 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 = new Date().toISOString().slice(11, 19); // HH:MM:SS
23
+ const ts = formatClock(); // local HH:MM:SS
23
24
  process.stderr.write(`[${ts}] ${msg}\n`);
24
25
  }
25
26
  const now = () => Date.now();
@@ -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 injected continue`);
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 — injected continue`);
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, reset ${new Date(usage.resetsAtMs).toISOString()}`);
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 — injected continue`);
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
- const until = new Date(state.waitUntil).toISOString();
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} — still waiting for reset`);
240
+ log(`${label} — waiting, 'continue' at ${formatLocalDateTime(state.waitUntil)}`);
241
241
  }
242
242
  else if (status === 'retried') {
243
- log(`${label} — reset reached, cleared input + injected 'continue'`);
243
+ log(`${label} — reset reached, sent 'continue'`);
244
244
  }
245
245
  else {
246
246
  log(`${label} — ok`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tigorhutasuhut/claude-retry",
3
- "version": "0.1.10",
3
+ "version": "0.1.11",
4
4
  "description": "Monitor Claude CLI in a zellij pane, auto-inject continue on rate-limit",
5
5
  "type": "module",
6
6
  "license": "MIT",