claude-auto-continue 1.0.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/LICENSE +21 -0
- package/README.md +62 -0
- package/bin/claude-auto-continue.js +2 -0
- package/dist/CountdownCard.d.ts +27 -0
- package/dist/CountdownCard.js +90 -0
- package/dist/CountdownCard.js.map +1 -0
- package/dist/PatternDetector.d.ts +33 -0
- package/dist/PatternDetector.js +128 -0
- package/dist/PatternDetector.js.map +1 -0
- package/dist/ProcessSupervisor.d.ts +61 -0
- package/dist/ProcessSupervisor.js +184 -0
- package/dist/ProcessSupervisor.js.map +1 -0
- package/dist/Scheduler.d.ts +18 -0
- package/dist/Scheduler.js +48 -0
- package/dist/Scheduler.js.map +1 -0
- package/dist/StatusBar.d.ts +41 -0
- package/dist/StatusBar.js +99 -0
- package/dist/StatusBar.js.map +1 -0
- package/dist/StdinWriter.d.ts +8 -0
- package/dist/StdinWriter.js +32 -0
- package/dist/StdinWriter.js.map +1 -0
- package/dist/ansi.d.ts +18 -0
- package/dist/ansi.js +51 -0
- package/dist/ansi.js.map +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +166 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +5 -0
- package/dist/config.js +23 -0
- package/dist/config.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dakmor
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# claude-auto-continue
|
|
2
|
+
|
|
3
|
+
Automatically resumes Claude Code sessions after usage limits reset — no manual babysitting.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/claude-auto-continue)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
|
|
9
|
+
Claude Code pauses with a "usage limit reached" message and a reset timestamp. Instead of watching and waiting, `claude-auto-continue` wraps Claude Code in a pseudo-terminal, monitors all output for rate-limit messages, parses the exact reset timestamp, shows a live status bar and countdown timer, then sends `continue` automatically at reset time. Leave it running overnight; it resumes by itself.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g claude-auto-continue
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
claude-auto-continue # start — launches 'claude' automatically
|
|
21
|
+
clac # short alias
|
|
22
|
+
clac -- --continue # pass flags through to Claude Code
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
All arguments after `--` are forwarded directly to `claude`. For example, `clac -- --resume` passes `--resume` to Claude Code.
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
`claude-auto-continue` depends on `node-pty`, a native addon that compiles during install. Build tools must be present.
|
|
30
|
+
|
|
31
|
+
**Linux (Debian/Ubuntu):**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
sudo apt-get install build-essential python3 make g++
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**macOS:** Xcode Command Line Tools are usually pre-installed. If not:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
xcode-select --install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Windows:** `windows-build-tools` may be required. Install via an elevated PowerShell:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install --global windows-build-tools
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
**Node.js >= 18 is required.**
|
|
50
|
+
|
|
51
|
+
## How It Works
|
|
52
|
+
|
|
53
|
+
1. Wraps `claude` in a pseudo-terminal (PTY) so it receives a real TTY — required for interactive operation
|
|
54
|
+
2. Monitors all output with a regex-based pattern detector that strips ANSI codes and uses a rolling buffer
|
|
55
|
+
3. Parses the reset timestamp from the rate-limit message when a usage limit is detected
|
|
56
|
+
4. Displays a status bar and centered countdown card in the terminal while waiting
|
|
57
|
+
5. Sends `continue` to the PTY at reset time, automatically resuming the session
|
|
58
|
+
6. Returns to monitoring — handles multiple rate limits in a single session
|
|
59
|
+
|
|
60
|
+
## License
|
|
61
|
+
|
|
62
|
+
ISC
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface CountdownCardOptions {
|
|
2
|
+
cols?: number;
|
|
3
|
+
rows?: number;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* CountdownCard — renders a centered countdown box during WAITING state.
|
|
7
|
+
*
|
|
8
|
+
* Pure renderer: produces strings, does NOT write to stdout directly.
|
|
9
|
+
* The caller is responsible for writing the returned strings to the terminal.
|
|
10
|
+
*/
|
|
11
|
+
export declare class CountdownCard {
|
|
12
|
+
cols: number;
|
|
13
|
+
rows: number;
|
|
14
|
+
constructor(options?: CountdownCardOptions);
|
|
15
|
+
/**
|
|
16
|
+
* Render the countdown card as a complete ANSI string.
|
|
17
|
+
* Shows a centered box with session name, countdown, and absolute reset time.
|
|
18
|
+
*/
|
|
19
|
+
render(opts: {
|
|
20
|
+
resetTime: Date | null;
|
|
21
|
+
cwd: string;
|
|
22
|
+
}): string;
|
|
23
|
+
/**
|
|
24
|
+
* Clear the card area by writing clearLine to each row the card occupied.
|
|
25
|
+
*/
|
|
26
|
+
clear(): string;
|
|
27
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CountdownCard = void 0;
|
|
4
|
+
const ansi_js_1 = require("./ansi.js");
|
|
5
|
+
const StatusBar_js_1 = require("./StatusBar.js");
|
|
6
|
+
/** Number of lines the card occupies (border + content + border) */
|
|
7
|
+
const CARD_HEIGHT = 9;
|
|
8
|
+
/**
|
|
9
|
+
* CountdownCard — renders a centered countdown box during WAITING state.
|
|
10
|
+
*
|
|
11
|
+
* Pure renderer: produces strings, does NOT write to stdout directly.
|
|
12
|
+
* The caller is responsible for writing the returned strings to the terminal.
|
|
13
|
+
*/
|
|
14
|
+
class CountdownCard {
|
|
15
|
+
cols;
|
|
16
|
+
rows;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.cols = options.cols ?? 80;
|
|
19
|
+
this.rows = options.rows ?? 24;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Render the countdown card as a complete ANSI string.
|
|
23
|
+
* Shows a centered box with session name, countdown, and absolute reset time.
|
|
24
|
+
*/
|
|
25
|
+
render(opts) {
|
|
26
|
+
const { resetTime, cwd } = opts;
|
|
27
|
+
const countdown = resetTime ? (0, StatusBar_js_1.formatCountdown)(resetTime) : 'Unknown';
|
|
28
|
+
const resetAt = resetTime ? (0, StatusBar_js_1.formatResetTime)(resetTime) : 'Unknown';
|
|
29
|
+
// Build card content lines (without borders)
|
|
30
|
+
const header = 'Waiting for rate limit reset';
|
|
31
|
+
const countdownLine = `${countdown} remaining`;
|
|
32
|
+
const resetLine = `Resets at ${resetAt}`;
|
|
33
|
+
const sessionLine = `Session: ${cwd}`;
|
|
34
|
+
// Calculate box width — fit the longest content line + padding
|
|
35
|
+
const contentLines = [header, countdownLine, resetLine, sessionLine];
|
|
36
|
+
const maxContentWidth = Math.max(...contentLines.map((l) => l.length));
|
|
37
|
+
const boxInnerWidth = Math.min(Math.max(maxContentWidth + 4, 40), this.cols - 4);
|
|
38
|
+
const boxOuterWidth = boxInnerWidth + 2; // +2 for left/right border chars
|
|
39
|
+
// Calculate centering positions
|
|
40
|
+
const startCol = Math.max(1, Math.floor((this.cols - boxOuterWidth) / 2) + 1);
|
|
41
|
+
// Vertical center: account for row 1 (status bar), so usable area is rows 2..this.rows
|
|
42
|
+
const usableRows = this.rows - 1;
|
|
43
|
+
const startRow = Math.max(2, Math.floor((usableRows - CARD_HEIGHT) / 2) + 2);
|
|
44
|
+
// Build the box
|
|
45
|
+
const topBorder = '+' + '-'.repeat(boxInnerWidth) + '+';
|
|
46
|
+
const bottomBorder = topBorder;
|
|
47
|
+
const emptyLine = '|' + ' '.repeat(boxInnerWidth) + '|';
|
|
48
|
+
const centerInBox = (text, visibleLength) => {
|
|
49
|
+
const len = visibleLength ?? text.length;
|
|
50
|
+
const padding = Math.max(0, boxInnerWidth - len);
|
|
51
|
+
const leftPad = Math.floor(padding / 2);
|
|
52
|
+
const rightPad = padding - leftPad;
|
|
53
|
+
return '|' + ' '.repeat(leftPad) + text + ' '.repeat(rightPad) + '|';
|
|
54
|
+
};
|
|
55
|
+
const lines = [
|
|
56
|
+
topBorder,
|
|
57
|
+
emptyLine,
|
|
58
|
+
centerInBox((0, ansi_js_1.bold)(header), header.length),
|
|
59
|
+
emptyLine,
|
|
60
|
+
centerInBox((0, ansi_js_1.yellow)(countdownLine), countdownLine.length),
|
|
61
|
+
centerInBox(resetLine),
|
|
62
|
+
emptyLine,
|
|
63
|
+
centerInBox(sessionLine),
|
|
64
|
+
bottomBorder,
|
|
65
|
+
];
|
|
66
|
+
// Render each line at the calculated position
|
|
67
|
+
let output = '';
|
|
68
|
+
for (let i = 0; i < lines.length; i++) {
|
|
69
|
+
output += (0, ansi_js_1.moveTo)(startRow + i, startCol) + lines[i];
|
|
70
|
+
}
|
|
71
|
+
return output;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Clear the card area by writing clearLine to each row the card occupied.
|
|
75
|
+
*/
|
|
76
|
+
clear() {
|
|
77
|
+
const boxInnerWidth = Math.min(44, this.cols - 4);
|
|
78
|
+
const boxOuterWidth = boxInnerWidth + 2;
|
|
79
|
+
const startCol = Math.max(1, Math.floor((this.cols - boxOuterWidth) / 2) + 1);
|
|
80
|
+
const usableRows = this.rows - 1;
|
|
81
|
+
const startRow = Math.max(2, Math.floor((usableRows - CARD_HEIGHT) / 2) + 2);
|
|
82
|
+
let output = '';
|
|
83
|
+
for (let i = 0; i < CARD_HEIGHT; i++) {
|
|
84
|
+
output += (0, ansi_js_1.moveTo)(startRow + i, startCol) + ansi_js_1.clearLine;
|
|
85
|
+
}
|
|
86
|
+
return output;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
exports.CountdownCard = CountdownCard;
|
|
90
|
+
//# sourceMappingURL=CountdownCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CountdownCard.js","sourceRoot":"","sources":["../src/CountdownCard.ts"],"names":[],"mappings":";;;AAAA,uCAA4D;AAC5D,iDAAkE;AAOlE,oEAAoE;AACpE,MAAM,WAAW,GAAG,CAAC,CAAC;AAEtB;;;;;GAKG;AACH,MAAa,aAAa;IACxB,IAAI,CAAS;IACb,IAAI,CAAS;IAEb,YAAY,UAAgC,EAAE;QAC5C,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;IACjC,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,IAA6C;QAClD,MAAM,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAEhC,MAAM,SAAS,GAAG,SAAS,CAAC,CAAC,CAAC,IAAA,8BAAe,EAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QACrE,MAAM,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,IAAA,8BAAe,EAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;QAEnE,6CAA6C;QAC7C,MAAM,MAAM,GAAG,8BAA8B,CAAC;QAC9C,MAAM,aAAa,GAAG,GAAG,SAAS,YAAY,CAAC;QAC/C,MAAM,SAAS,GAAG,aAAa,OAAO,EAAE,CAAC;QACzC,MAAM,WAAW,GAAG,YAAY,GAAG,EAAE,CAAC;QAEtC,+DAA+D;QAC/D,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QACrE,MAAM,eAAe,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC;QACvE,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QACjF,MAAM,aAAa,GAAG,aAAa,GAAG,CAAC,CAAC,CAAC,iCAAiC;QAE1E,gCAAgC;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9E,uFAAuF;QACvF,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE7E,gBAAgB;QAChB,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;QACxD,MAAM,YAAY,GAAG,SAAS,CAAC;QAC/B,MAAM,SAAS,GAAG,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC,GAAG,GAAG,CAAC;QAExD,MAAM,WAAW,GAAG,CAAC,IAAY,EAAE,aAAsB,EAAU,EAAE;YACnE,MAAM,GAAG,GAAG,aAAa,IAAI,IAAI,CAAC,MAAM,CAAC;YACzC,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,aAAa,GAAG,GAAG,CAAC,CAAC;YACjD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC;YACxC,MAAM,QAAQ,GAAG,OAAO,GAAG,OAAO,CAAC;YACnC,OAAO,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,IAAI,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,GAAG,GAAG,CAAC;QACvE,CAAC,CAAC;QAEF,MAAM,KAAK,GAAa;YACtB,SAAS;YACT,SAAS;YACT,WAAW,CAAC,IAAA,cAAI,EAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,CAAC;YACxC,SAAS;YACT,WAAW,CAAC,IAAA,gBAAM,EAAC,aAAa,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC;YACxD,WAAW,CAAC,SAAS,CAAC;YACtB,SAAS;YACT,WAAW,CAAC,WAAW,CAAC;YACxB,YAAY;SACb,CAAC;QAEF,8CAA8C;QAC9C,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACtC,MAAM,IAAI,IAAA,gBAAM,EAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtD,CAAC;QAED,OAAO,MAAM,CAAC;IAChB,CAAC;IAED;;OAEG;IACH,KAAK;QACH,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC;QAClD,MAAM,aAAa,GAAG,aAAa,GAAG,CAAC,CAAC;QACxC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9E,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC;QACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,UAAU,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QAE7E,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,EAAE,CAAC;YACrC,MAAM,IAAI,IAAA,gBAAM,EAAC,QAAQ,GAAG,CAAC,EAAE,QAAQ,CAAC,GAAG,mBAAS,CAAC;QACvD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAvFD,sCAuFC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
/**
|
|
3
|
+
* Payload emitted with the 'limit' event.
|
|
4
|
+
*/
|
|
5
|
+
export interface LimitEvent {
|
|
6
|
+
/** Parsed reset time as a Date, or null when unparseable */
|
|
7
|
+
resetTime: Date | null;
|
|
8
|
+
/** Last ~500 chars of the buffer at the time of detection */
|
|
9
|
+
rawMatch: string;
|
|
10
|
+
}
|
|
11
|
+
export interface PatternDetectorOptions {
|
|
12
|
+
/** Override default detection pattern */
|
|
13
|
+
pattern?: RegExp;
|
|
14
|
+
/** If true, write buffer snapshots to stderr on each feed() call */
|
|
15
|
+
debug?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* EventEmitter subclass that detects Claude rate-limit messages in a stream of
|
|
19
|
+
* PTY output chunks. Emits a 'limit' event with a LimitEvent payload.
|
|
20
|
+
*/
|
|
21
|
+
export declare class PatternDetector extends EventEmitter {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options?: PatternDetectorOptions);
|
|
24
|
+
/**
|
|
25
|
+
* Feed a raw PTY output chunk into the detector.
|
|
26
|
+
* Strips ANSI codes, appends to rolling buffer, trims if >4KB, then checks pattern.
|
|
27
|
+
*/
|
|
28
|
+
feed(rawChunk: string): void;
|
|
29
|
+
/**
|
|
30
|
+
* Clear the buffer and re-arm detection (allow the next match to fire again).
|
|
31
|
+
*/
|
|
32
|
+
reset(): void;
|
|
33
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.PatternDetector = void 0;
|
|
7
|
+
const events_1 = require("events");
|
|
8
|
+
const strip_ansi_1 = __importDefault(require("strip-ansi"));
|
|
9
|
+
const config_js_1 = require("./config.js");
|
|
10
|
+
/**
|
|
11
|
+
* Default regex that matches all three known Claude rate-limit message formats:
|
|
12
|
+
* Format A (legacy): "Claude AI usage limit reached|1760000400"
|
|
13
|
+
* Format B (mid-era): "Claude usage limit reached. Your limit will reset at 3pm (America/Santiago)."
|
|
14
|
+
* Format C (current): "You've hit your limit · resets 4pm (Europe/Berlin)"
|
|
15
|
+
*/
|
|
16
|
+
const DEFAULT_PATTERN = /(?:Claude(?:\s+AI)?\s+usage\s+limit\s+reached[|.]|you(?:'ve| have)\s+hit\s+your\s+limit)/i;
|
|
17
|
+
/** Matches a 10+ digit unix timestamp after a pipe: |1760000400 */
|
|
18
|
+
const UNIX_TS_PATTERN = /\|(\d{10,})/;
|
|
19
|
+
/**
|
|
20
|
+
* Matches human-readable reset time in multiple forms:
|
|
21
|
+
* "resets Feb 20, 5pm (Africa/Libreville)"
|
|
22
|
+
* "reset at 3pm (America/Santiago)"
|
|
23
|
+
* "resets 4pm (Europe/Berlin)"
|
|
24
|
+
*/
|
|
25
|
+
const HUMAN_TIME_PATTERN = /reset(?:s)?\s+(?:at\s+)?(?:\w+\s+\d{1,2},?\s*)?(\d{1,2}(?::\d{2})?\s*[ap]m)/i;
|
|
26
|
+
/** Maximum rolling buffer size in characters */
|
|
27
|
+
const MAX_BUFFER = 4096;
|
|
28
|
+
/**
|
|
29
|
+
* EventEmitter subclass that detects Claude rate-limit messages in a stream of
|
|
30
|
+
* PTY output chunks. Emits a 'limit' event with a LimitEvent payload.
|
|
31
|
+
*/
|
|
32
|
+
class PatternDetector extends events_1.EventEmitter {
|
|
33
|
+
#pattern;
|
|
34
|
+
#debug;
|
|
35
|
+
#buffer = '';
|
|
36
|
+
#detected = false;
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
super();
|
|
39
|
+
// Priority: constructor option > config file > default
|
|
40
|
+
const config = (0, config_js_1.loadConfig)();
|
|
41
|
+
this.#pattern = options.pattern ?? config.pattern ?? DEFAULT_PATTERN;
|
|
42
|
+
this.#debug = options.debug ?? false;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Feed a raw PTY output chunk into the detector.
|
|
46
|
+
* Strips ANSI codes, appends to rolling buffer, trims if >4KB, then checks pattern.
|
|
47
|
+
*/
|
|
48
|
+
feed(rawChunk) {
|
|
49
|
+
// Already detected — ignore further input until reset()
|
|
50
|
+
if (this.#detected)
|
|
51
|
+
return;
|
|
52
|
+
// Strip ANSI escape codes and append to rolling buffer
|
|
53
|
+
const clean = (0, strip_ansi_1.default)(rawChunk);
|
|
54
|
+
this.#buffer += clean;
|
|
55
|
+
// Trim buffer to last MAX_BUFFER characters to avoid unbounded growth
|
|
56
|
+
if (this.#buffer.length > MAX_BUFFER) {
|
|
57
|
+
this.#buffer = this.#buffer.slice(this.#buffer.length - MAX_BUFFER);
|
|
58
|
+
}
|
|
59
|
+
if (this.#debug) {
|
|
60
|
+
process.stderr.write(`[PatternDetector] buffer(${this.#buffer.length}): ${this.#buffer}\n`);
|
|
61
|
+
}
|
|
62
|
+
// Test the pattern against the current buffer
|
|
63
|
+
if (this.#pattern.test(this.#buffer)) {
|
|
64
|
+
this.#detected = true;
|
|
65
|
+
const rawMatch = this.#buffer.slice(-500);
|
|
66
|
+
const resetTime = this.#parseResetTime(this.#buffer);
|
|
67
|
+
this.emit('limit', { resetTime, rawMatch });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Clear the buffer and re-arm detection (allow the next match to fire again).
|
|
72
|
+
*/
|
|
73
|
+
reset() {
|
|
74
|
+
this.#buffer = '';
|
|
75
|
+
this.#detected = false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse a reset timestamp from the buffer text.
|
|
79
|
+
* Tries unix epoch first, then human-readable time expression.
|
|
80
|
+
* Returns null if neither can be parsed.
|
|
81
|
+
*/
|
|
82
|
+
#parseResetTime(text) {
|
|
83
|
+
// Try unix timestamp (Format A): |1760000400
|
|
84
|
+
const unixMatch = UNIX_TS_PATTERN.exec(text);
|
|
85
|
+
if (unixMatch) {
|
|
86
|
+
const seconds = parseInt(unixMatch[1], 10);
|
|
87
|
+
return new Date(seconds * 1000);
|
|
88
|
+
}
|
|
89
|
+
// Try human-readable time (Formats B & C)
|
|
90
|
+
return this.#parseHumanTime(text);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Parse a human-readable time expression like "3pm", "4pm", "5:30am"
|
|
94
|
+
* into an absolute Date. Rolls to tomorrow if the time is already past.
|
|
95
|
+
* Returns null if the pattern doesn't match.
|
|
96
|
+
*
|
|
97
|
+
* Note: Does not perform IANA timezone lookup — uses local clock per user decision.
|
|
98
|
+
*/
|
|
99
|
+
#parseHumanTime(text) {
|
|
100
|
+
const match = HUMAN_TIME_PATTERN.exec(text);
|
|
101
|
+
if (!match)
|
|
102
|
+
return null;
|
|
103
|
+
const timeStr = match[1].trim(); // e.g. "3pm", "4pm", "5:30am"
|
|
104
|
+
const timeMatch = /^(\d{1,2})(?::(\d{2}))?\s*([ap]m)$/i.exec(timeStr);
|
|
105
|
+
if (!timeMatch)
|
|
106
|
+
return null;
|
|
107
|
+
let hours = parseInt(timeMatch[1], 10);
|
|
108
|
+
const minutes = timeMatch[2] ? parseInt(timeMatch[2], 10) : 0;
|
|
109
|
+
const meridiem = timeMatch[3].toLowerCase();
|
|
110
|
+
if (meridiem === 'am') {
|
|
111
|
+
if (hours === 12)
|
|
112
|
+
hours = 0;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
if (hours !== 12)
|
|
116
|
+
hours += 12;
|
|
117
|
+
}
|
|
118
|
+
const now = new Date();
|
|
119
|
+
const candidate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), hours, minutes, 0, 0);
|
|
120
|
+
// If the time is in the past (or right now), roll to tomorrow
|
|
121
|
+
if (candidate.getTime() <= now.getTime()) {
|
|
122
|
+
candidate.setDate(candidate.getDate() + 1);
|
|
123
|
+
}
|
|
124
|
+
return candidate;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
exports.PatternDetector = PatternDetector;
|
|
128
|
+
//# sourceMappingURL=PatternDetector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PatternDetector.js","sourceRoot":"","sources":["../src/PatternDetector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import * as pty from 'node-pty';
|
|
3
|
+
/**
|
|
4
|
+
* Four-state machine for the PTY session lifecycle:
|
|
5
|
+
*
|
|
6
|
+
* RUNNING — normal operation, forwarding I/O and feeding PatternDetector
|
|
7
|
+
* LIMIT_DETECTED — transient: rate limit seen, calling scheduler.scheduleAt()
|
|
8
|
+
* WAITING — blocked: scheduler is counting down to reset time
|
|
9
|
+
* RESUMING — transient: sending resume sequence to PTY, resetting detector
|
|
10
|
+
*
|
|
11
|
+
* Transitions: RUNNING -> LIMIT_DETECTED -> WAITING -> RESUMING -> RUNNING
|
|
12
|
+
*/
|
|
13
|
+
export declare const enum SessionState {
|
|
14
|
+
RUNNING = "RUNNING",
|
|
15
|
+
LIMIT_DETECTED = "LIMIT_DETECTED",
|
|
16
|
+
WAITING = "WAITING",
|
|
17
|
+
RESUMING = "RESUMING"
|
|
18
|
+
}
|
|
19
|
+
/** Payload emitted with the 'stateChange' event */
|
|
20
|
+
export interface StateChangeEvent {
|
|
21
|
+
state: string;
|
|
22
|
+
resetTime: Date | null;
|
|
23
|
+
}
|
|
24
|
+
export interface ProcessSupervisorOptions {
|
|
25
|
+
/** Override node-pty spawn function for testing (dependency injection) */
|
|
26
|
+
spawnFn?: typeof pty.spawn;
|
|
27
|
+
/** Cooldown after resume — suppresses false-positive re-detections. Default: 30000ms */
|
|
28
|
+
cooldownMs?: number;
|
|
29
|
+
/** Safety buffer added to reset time before resuming. Default: 5000ms */
|
|
30
|
+
safetyMs?: number;
|
|
31
|
+
/** Override process.exit for testing. Default: process.exit */
|
|
32
|
+
onExit?: (code: number) => void;
|
|
33
|
+
/** Override PTY output handler. Default: process.stdout.write */
|
|
34
|
+
onOutput?: (data: string) => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* ProcessSupervisor — the core PTY orchestrator.
|
|
38
|
+
*
|
|
39
|
+
* Spawns Claude Code in a real PTY, passes all I/O transparently, detects
|
|
40
|
+
* usage-limit messages via PatternDetector, waits via Scheduler, and
|
|
41
|
+
* auto-resumes via StdinWriter.
|
|
42
|
+
*
|
|
43
|
+
* Extends EventEmitter to broadcast 'stateChange' events for the display layer.
|
|
44
|
+
*/
|
|
45
|
+
export declare class ProcessSupervisor extends EventEmitter {
|
|
46
|
+
#private;
|
|
47
|
+
constructor(options?: ProcessSupervisorOptions);
|
|
48
|
+
/** Current session state — exposed for Phase 3 status display and testing */
|
|
49
|
+
get state(): SessionState;
|
|
50
|
+
/**
|
|
51
|
+
* Spawn the given command in a PTY and wire up all I/O.
|
|
52
|
+
*
|
|
53
|
+
* @param command - executable to run (e.g. 'claude')
|
|
54
|
+
* @param args - CLI arguments (e.g. ['--continue'])
|
|
55
|
+
*/
|
|
56
|
+
spawn(command: string, args: string[]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Gracefully shut down: cancel pending timer, mark writer dead, kill PTY.
|
|
59
|
+
*/
|
|
60
|
+
shutdown(): void;
|
|
61
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ProcessSupervisor = void 0;
|
|
37
|
+
const events_1 = require("events");
|
|
38
|
+
const pty = __importStar(require("node-pty"));
|
|
39
|
+
const PatternDetector_js_1 = require("./PatternDetector.js");
|
|
40
|
+
const Scheduler_js_1 = require("./Scheduler.js");
|
|
41
|
+
const StdinWriter_js_1 = require("./StdinWriter.js");
|
|
42
|
+
/**
|
|
43
|
+
* ProcessSupervisor — the core PTY orchestrator.
|
|
44
|
+
*
|
|
45
|
+
* Spawns Claude Code in a real PTY, passes all I/O transparently, detects
|
|
46
|
+
* usage-limit messages via PatternDetector, waits via Scheduler, and
|
|
47
|
+
* auto-resumes via StdinWriter.
|
|
48
|
+
*
|
|
49
|
+
* Extends EventEmitter to broadcast 'stateChange' events for the display layer.
|
|
50
|
+
*/
|
|
51
|
+
class ProcessSupervisor extends events_1.EventEmitter {
|
|
52
|
+
#spawnFn;
|
|
53
|
+
#cooldownMs;
|
|
54
|
+
#detector;
|
|
55
|
+
#scheduler;
|
|
56
|
+
#onExitCallback;
|
|
57
|
+
#onOutput;
|
|
58
|
+
#state = "RUNNING" /* SessionState.RUNNING */;
|
|
59
|
+
#writer = null;
|
|
60
|
+
#cooldownUntil = 0;
|
|
61
|
+
#resetTime = null;
|
|
62
|
+
constructor(options = {}) {
|
|
63
|
+
super();
|
|
64
|
+
this.#spawnFn = options.spawnFn ?? pty.spawn;
|
|
65
|
+
this.#cooldownMs = options.cooldownMs ?? 30_000;
|
|
66
|
+
this.#detector = new PatternDetector_js_1.PatternDetector();
|
|
67
|
+
this.#scheduler = new Scheduler_js_1.Scheduler(options.safetyMs ?? 5_000);
|
|
68
|
+
this.#onExitCallback = options.onExit ?? ((code) => process.exit(code));
|
|
69
|
+
this.#onOutput = options.onOutput ?? ((data) => { process.stdout.write(data); });
|
|
70
|
+
// Listen for rate-limit detections
|
|
71
|
+
this.#detector.on('limit', (event) => this.#onLimitDetected(event));
|
|
72
|
+
}
|
|
73
|
+
/** Current session state — exposed for Phase 3 status display and testing */
|
|
74
|
+
get state() {
|
|
75
|
+
return this.#state;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Set state and emit a 'stateChange' event.
|
|
79
|
+
*/
|
|
80
|
+
#setState(newState) {
|
|
81
|
+
if (newState !== 'DEAD') {
|
|
82
|
+
this.#state = newState;
|
|
83
|
+
}
|
|
84
|
+
this.emit('stateChange', {
|
|
85
|
+
state: newState,
|
|
86
|
+
resetTime: this.#resetTime,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Spawn the given command in a PTY and wire up all I/O.
|
|
91
|
+
*
|
|
92
|
+
* @param command - executable to run (e.g. 'claude')
|
|
93
|
+
* @param args - CLI arguments (e.g. ['--continue'])
|
|
94
|
+
*/
|
|
95
|
+
spawn(command, args) {
|
|
96
|
+
const ptyProcess = this.#spawnFn(command, args, {
|
|
97
|
+
name: process.env['TERM'] ?? 'xterm-256color',
|
|
98
|
+
cols: process.stdout.columns ?? 80,
|
|
99
|
+
rows: process.stdout.rows ?? 24,
|
|
100
|
+
cwd: process.cwd(),
|
|
101
|
+
env: process.env,
|
|
102
|
+
});
|
|
103
|
+
this.#writer = new StdinWriter_js_1.StdinWriter(ptyProcess);
|
|
104
|
+
// --- PTY output: pass to output handler, optionally feed detector ---
|
|
105
|
+
ptyProcess.onData((data) => {
|
|
106
|
+
this.#onOutput(data);
|
|
107
|
+
// Only feed the detector when RUNNING — ignore output in all other states
|
|
108
|
+
if (this.#state === "RUNNING" /* SessionState.RUNNING */) {
|
|
109
|
+
this.#detector.feed(data);
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
// --- PTY exit: clean up and exit ---
|
|
113
|
+
ptyProcess.onExit((e) => {
|
|
114
|
+
this.#writer.markDead();
|
|
115
|
+
this.#scheduler.cancel();
|
|
116
|
+
this.#setState('DEAD');
|
|
117
|
+
// Allow Node.js event loop to drain (stdin will no longer hold the process open)
|
|
118
|
+
if (process.stdin.isTTY) {
|
|
119
|
+
process.stdin.unref();
|
|
120
|
+
}
|
|
121
|
+
this.#onExitCallback(e.exitCode ?? 0);
|
|
122
|
+
});
|
|
123
|
+
// --- Stdin forwarding (only in a real TTY environment) ---
|
|
124
|
+
if (process.stdin.isTTY) {
|
|
125
|
+
process.stdin.setRawMode(true);
|
|
126
|
+
process.stdin.resume();
|
|
127
|
+
process.stdin.on('data', (chunk) => {
|
|
128
|
+
// Forward keystrokes to PTY only during RUNNING state
|
|
129
|
+
if (this.#state === "RUNNING" /* SessionState.RUNNING */) {
|
|
130
|
+
this.#writer.write(chunk.toString('binary'));
|
|
131
|
+
}
|
|
132
|
+
// Silently discard during WAITING / RESUMING / LIMIT_DETECTED
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// --- Terminal resize forwarding ---
|
|
136
|
+
process.stdout.on('resize', () => {
|
|
137
|
+
ptyProcess.resize(process.stdout.columns ?? 80, process.stdout.rows ?? 24);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Called when PatternDetector emits a 'limit' event.
|
|
142
|
+
* Transitions: RUNNING -> LIMIT_DETECTED -> WAITING
|
|
143
|
+
*/
|
|
144
|
+
#onLimitDetected(event) {
|
|
145
|
+
// Suppress false positives during post-resume cooldown
|
|
146
|
+
if (Date.now() < this.#cooldownUntil) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
this.#resetTime = event.resetTime;
|
|
150
|
+
this.#setState("LIMIT_DETECTED" /* SessionState.LIMIT_DETECTED */);
|
|
151
|
+
// Schedule the resume callback
|
|
152
|
+
this.#scheduler.scheduleAt(event.resetTime, () => this.#onResumeReady());
|
|
153
|
+
this.#setState("WAITING" /* SessionState.WAITING */);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Called by Scheduler when the reset time has elapsed.
|
|
157
|
+
* Transitions: WAITING -> RESUMING -> RUNNING
|
|
158
|
+
*/
|
|
159
|
+
#onResumeReady() {
|
|
160
|
+
this.#setState("RESUMING" /* SessionState.RESUMING */);
|
|
161
|
+
// Send Escape to dismiss any rate-limit UI overlay
|
|
162
|
+
this.#writer.write('\x1b');
|
|
163
|
+
// Type "continue" and press Enter to resume the session
|
|
164
|
+
this.#writer.write('continue\r');
|
|
165
|
+
// Arm cooldown to suppress the false-positive re-detection that can occur
|
|
166
|
+
// immediately after resume (Claude Code issue #14129)
|
|
167
|
+
this.#cooldownUntil = Date.now() + this.#cooldownMs;
|
|
168
|
+
// Re-arm the detector for the next potential rate-limit cycle
|
|
169
|
+
this.#detector.reset();
|
|
170
|
+
this.#resetTime = null;
|
|
171
|
+
this.#setState("RUNNING" /* SessionState.RUNNING */);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Gracefully shut down: cancel pending timer, mark writer dead, kill PTY.
|
|
175
|
+
*/
|
|
176
|
+
shutdown() {
|
|
177
|
+
this.#scheduler.cancel();
|
|
178
|
+
if (this.#writer) {
|
|
179
|
+
this.#writer.markDead();
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
exports.ProcessSupervisor = ProcessSupervisor;
|
|
184
|
+
//# sourceMappingURL=ProcessSupervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ProcessSupervisor.js","sourceRoot":"","sources":["../src/ProcessSupervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare class Scheduler {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(safetyMs?: number);
|
|
4
|
+
/**
|
|
5
|
+
* Schedule callback to fire at resetTime + safetyBuffer milliseconds from now.
|
|
6
|
+
* If resetTime is null → fire immediately (0ms delay) as a fallback.
|
|
7
|
+
* If resetTime is in the past → waitMs clamps to 0 (plus safety buffer).
|
|
8
|
+
* Calling scheduleAt() while a timer is pending cancels the previous timer.
|
|
9
|
+
* Calling scheduleAt() after cancel() is a no-op (cancelled flag stays true).
|
|
10
|
+
*/
|
|
11
|
+
scheduleAt(resetTime: Date | null, callback: () => void): void;
|
|
12
|
+
/**
|
|
13
|
+
* Cancel any pending timer. After cancel(), the scheduler is inert —
|
|
14
|
+
* subsequent scheduleAt() calls are ignored.
|
|
15
|
+
* Safe to call when no timer is pending (no-op, no error).
|
|
16
|
+
*/
|
|
17
|
+
cancel(): void;
|
|
18
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Scheduler = void 0;
|
|
4
|
+
class Scheduler {
|
|
5
|
+
#timer = null;
|
|
6
|
+
#cancelled = false;
|
|
7
|
+
#safetyMs;
|
|
8
|
+
constructor(safetyMs = 5000) {
|
|
9
|
+
this.#safetyMs = safetyMs;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Schedule callback to fire at resetTime + safetyBuffer milliseconds from now.
|
|
13
|
+
* If resetTime is null → fire immediately (0ms delay) as a fallback.
|
|
14
|
+
* If resetTime is in the past → waitMs clamps to 0 (plus safety buffer).
|
|
15
|
+
* Calling scheduleAt() while a timer is pending cancels the previous timer.
|
|
16
|
+
* Calling scheduleAt() after cancel() is a no-op (cancelled flag stays true).
|
|
17
|
+
*/
|
|
18
|
+
scheduleAt(resetTime, callback) {
|
|
19
|
+
if (this.#cancelled)
|
|
20
|
+
return;
|
|
21
|
+
// Clear any existing timer before setting a new one
|
|
22
|
+
if (this.#timer) {
|
|
23
|
+
clearTimeout(this.#timer);
|
|
24
|
+
this.#timer = null;
|
|
25
|
+
}
|
|
26
|
+
const waitMs = resetTime
|
|
27
|
+
? Math.max(0, resetTime.getTime() - Date.now() + this.#safetyMs)
|
|
28
|
+
: 0;
|
|
29
|
+
this.#timer = setTimeout(() => {
|
|
30
|
+
if (!this.#cancelled)
|
|
31
|
+
callback();
|
|
32
|
+
}, waitMs);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Cancel any pending timer. After cancel(), the scheduler is inert —
|
|
36
|
+
* subsequent scheduleAt() calls are ignored.
|
|
37
|
+
* Safe to call when no timer is pending (no-op, no error).
|
|
38
|
+
*/
|
|
39
|
+
cancel() {
|
|
40
|
+
this.#cancelled = true;
|
|
41
|
+
if (this.#timer) {
|
|
42
|
+
clearTimeout(this.#timer);
|
|
43
|
+
this.#timer = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
exports.Scheduler = Scheduler;
|
|
48
|
+
//# sourceMappingURL=Scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Scheduler.js","sourceRoot":"","sources":["../src/Scheduler.ts"],"names":[],"mappings":";;;AAAA,MAAa,SAAS;IACpB,MAAM,GAAyC,IAAI,CAAC;IACpD,UAAU,GAAG,KAAK,CAAC;IACV,SAAS,CAAS;IAE3B,YAAY,QAAQ,GAAG,IAAI;QACzB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;IAC5B,CAAC;IAED;;;;;;OAMG;IACH,UAAU,CAAC,SAAsB,EAAE,QAAoB;QACrD,IAAI,IAAI,CAAC,UAAU;YAAE,OAAO;QAE5B,oDAAoD;QACpD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;QAED,MAAM,MAAM,GAAG,SAAS;YACtB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC;YAChE,CAAC,CAAC,CAAC,CAAC;QAEN,IAAI,CAAC,MAAM,GAAG,UAAU,CAAC,GAAG,EAAE;YAC5B,IAAI,CAAC,IAAI,CAAC,UAAU;gBAAE,QAAQ,EAAE,CAAC;QACnC,CAAC,EAAE,MAAM,CAAC,CAAC;IACb,CAAC;IAED;;;;OAIG;IACH,MAAM;QACJ,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC;QACvB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YAC1B,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACrB,CAAC;IACH,CAAC;CACF;AA9CD,8BA8CC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface StatusBarOptions {
|
|
2
|
+
cols?: number;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Format a countdown from now until resetTime as a human-readable string.
|
|
6
|
+
* Returns "Xh Xm Xs" for durations over an hour, "Xm Xs" otherwise.
|
|
7
|
+
* Returns "0m 00s" for past or null dates.
|
|
8
|
+
*/
|
|
9
|
+
export declare function formatCountdown(resetTime: Date): string;
|
|
10
|
+
/**
|
|
11
|
+
* Format a Date as a human-readable absolute time (e.g., "2:45 PM").
|
|
12
|
+
*/
|
|
13
|
+
export declare function formatResetTime(resetTime: Date): string;
|
|
14
|
+
/**
|
|
15
|
+
* StatusBar — renders a fixed top-row status bar with color-coded session state.
|
|
16
|
+
*
|
|
17
|
+
* Pure renderer: produces strings, does NOT write to stdout directly.
|
|
18
|
+
* The caller is responsible for writing the returned strings to the terminal.
|
|
19
|
+
*/
|
|
20
|
+
export declare class StatusBar {
|
|
21
|
+
cols: number;
|
|
22
|
+
constructor(options?: StatusBarOptions);
|
|
23
|
+
/**
|
|
24
|
+
* Render the status bar string for the given state.
|
|
25
|
+
* Returns a complete ANSI sequence: save cursor, move to row 1, render bar, restore cursor.
|
|
26
|
+
*/
|
|
27
|
+
render(state: string, opts?: {
|
|
28
|
+
resetTime?: Date;
|
|
29
|
+
cwd?: string;
|
|
30
|
+
}): string;
|
|
31
|
+
/**
|
|
32
|
+
* Produce the ANSI sequence to initialize the scroll region.
|
|
33
|
+
* Confines scrolling to rows 2..termRows, leaving row 1 for the status bar.
|
|
34
|
+
*/
|
|
35
|
+
initScrollRegion(rows: number): string;
|
|
36
|
+
/**
|
|
37
|
+
* Produce the ANSI cleanup sequence.
|
|
38
|
+
* Resets scroll region, shows cursor, and resets all attributes.
|
|
39
|
+
*/
|
|
40
|
+
cleanup(): string;
|
|
41
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatusBar = void 0;
|
|
4
|
+
exports.formatCountdown = formatCountdown;
|
|
5
|
+
exports.formatResetTime = formatResetTime;
|
|
6
|
+
const ansi_js_1 = require("./ansi.js");
|
|
7
|
+
/**
|
|
8
|
+
* Format a countdown from now until resetTime as a human-readable string.
|
|
9
|
+
* Returns "Xh Xm Xs" for durations over an hour, "Xm Xs" otherwise.
|
|
10
|
+
* Returns "0m 00s" for past or null dates.
|
|
11
|
+
*/
|
|
12
|
+
function formatCountdown(resetTime) {
|
|
13
|
+
const remaining = Math.max(0, resetTime.getTime() - Date.now());
|
|
14
|
+
const totalSec = Math.ceil(remaining / 1000);
|
|
15
|
+
const hours = Math.floor(totalSec / 3600);
|
|
16
|
+
const minutes = Math.floor((totalSec % 3600) / 60);
|
|
17
|
+
const seconds = totalSec % 60;
|
|
18
|
+
if (hours > 0) {
|
|
19
|
+
return `${hours}h ${String(minutes).padStart(2, '0')}m ${String(seconds).padStart(2, '0')}s`;
|
|
20
|
+
}
|
|
21
|
+
return `${minutes}m ${String(seconds).padStart(2, '0')}s`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Format a Date as a human-readable absolute time (e.g., "2:45 PM").
|
|
25
|
+
*/
|
|
26
|
+
function formatResetTime(resetTime) {
|
|
27
|
+
return resetTime.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* StatusBar — renders a fixed top-row status bar with color-coded session state.
|
|
31
|
+
*
|
|
32
|
+
* Pure renderer: produces strings, does NOT write to stdout directly.
|
|
33
|
+
* The caller is responsible for writing the returned strings to the terminal.
|
|
34
|
+
*/
|
|
35
|
+
class StatusBar {
|
|
36
|
+
cols;
|
|
37
|
+
constructor(options = {}) {
|
|
38
|
+
this.cols = options.cols ?? 80;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Render the status bar string for the given state.
|
|
42
|
+
* Returns a complete ANSI sequence: save cursor, move to row 1, render bar, restore cursor.
|
|
43
|
+
*/
|
|
44
|
+
render(state, opts = {}) {
|
|
45
|
+
const { resetTime, cwd = '' } = opts;
|
|
46
|
+
let stateText;
|
|
47
|
+
let colorFn;
|
|
48
|
+
switch (state) {
|
|
49
|
+
case 'RUNNING':
|
|
50
|
+
stateText = 'Running';
|
|
51
|
+
colorFn = ansi_js_1.green;
|
|
52
|
+
break;
|
|
53
|
+
case 'WAITING':
|
|
54
|
+
stateText = 'Waiting';
|
|
55
|
+
colorFn = ansi_js_1.yellow;
|
|
56
|
+
break;
|
|
57
|
+
case 'RESUMING':
|
|
58
|
+
stateText = 'Resuming';
|
|
59
|
+
colorFn = ansi_js_1.green;
|
|
60
|
+
break;
|
|
61
|
+
case 'DEAD':
|
|
62
|
+
stateText = 'Dead';
|
|
63
|
+
colorFn = ansi_js_1.red;
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
stateText = state;
|
|
67
|
+
colorFn = ansi_js_1.green;
|
|
68
|
+
}
|
|
69
|
+
// Build bar content
|
|
70
|
+
const parts = [colorFn(stateText)];
|
|
71
|
+
if (state === 'WAITING' && resetTime) {
|
|
72
|
+
const countdown = formatCountdown(resetTime);
|
|
73
|
+
const resetAt = formatResetTime(resetTime);
|
|
74
|
+
parts.push(`${countdown} (resets ${resetAt})`);
|
|
75
|
+
}
|
|
76
|
+
if (cwd) {
|
|
77
|
+
parts.push(cwd);
|
|
78
|
+
}
|
|
79
|
+
const content = ` ${parts.join(' | ')} `;
|
|
80
|
+
const barContent = (0, ansi_js_1.inverse)(content);
|
|
81
|
+
return `${ansi_js_1.saveCursor}${(0, ansi_js_1.moveTo)(1, 1)}${ansi_js_1.clearLine}${barContent}${ansi_js_1.restoreCursor}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Produce the ANSI sequence to initialize the scroll region.
|
|
85
|
+
* Confines scrolling to rows 2..termRows, leaving row 1 for the status bar.
|
|
86
|
+
*/
|
|
87
|
+
initScrollRegion(rows) {
|
|
88
|
+
return `${(0, ansi_js_1.setScrollRegion)(2, rows)}${(0, ansi_js_1.moveTo)(2, 1)}`;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Produce the ANSI cleanup sequence.
|
|
92
|
+
* Resets scroll region, shows cursor, and resets all attributes.
|
|
93
|
+
*/
|
|
94
|
+
cleanup() {
|
|
95
|
+
return `${ansi_js_1.resetScrollRegion}${ansi_js_1.showCursor}${ansi_js_1.resetAttributes}`;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.StatusBar = StatusBar;
|
|
99
|
+
//# sourceMappingURL=StatusBar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StatusBar.js","sourceRoot":"","sources":["../src/StatusBar.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StdinWriter = void 0;
|
|
4
|
+
class StdinWriter {
|
|
5
|
+
#pty;
|
|
6
|
+
#dead = false;
|
|
7
|
+
constructor(ptyProcess) {
|
|
8
|
+
this.#pty = ptyProcess;
|
|
9
|
+
}
|
|
10
|
+
write(data) {
|
|
11
|
+
if (this.#dead)
|
|
12
|
+
return;
|
|
13
|
+
try {
|
|
14
|
+
this.#pty.write(data);
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const code = err.code;
|
|
18
|
+
if (code !== 'EPIPE') {
|
|
19
|
+
process.stderr.write(`[StdinWriter] write error: ${code ?? String(err)}\n`);
|
|
20
|
+
}
|
|
21
|
+
// EPIPE = PTY process exited between dead-check and write — silently ignore
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
markDead() {
|
|
25
|
+
this.#dead = true;
|
|
26
|
+
}
|
|
27
|
+
get isDead() {
|
|
28
|
+
return this.#dead;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.StdinWriter = StdinWriter;
|
|
32
|
+
//# sourceMappingURL=StdinWriter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StdinWriter.js","sourceRoot":"","sources":["../src/StdinWriter.ts"],"names":[],"mappings":";;;AAEA,MAAa,WAAW;IACb,IAAI,CAAW;IACxB,KAAK,GAAG,KAAK,CAAC;IAEd,YAAY,UAAoB;QAC9B,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,IAAY;QAChB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,GAAY,EAAE,CAAC;YACtB,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;YACjD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;gBACrB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,8BAA8B,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;YAC9E,CAAC;YACD,4EAA4E;QAC9E,CAAC;IACH,CAAC;IAED,QAAQ;QACN,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;IACpB,CAAC;IAED,IAAI,MAAM;QACR,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;CACF;AA5BD,kCA4BC"}
|
package/dist/ansi.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ANSI escape code helpers for terminal rendering.
|
|
3
|
+
* Pure string functions — no I/O, no dependencies.
|
|
4
|
+
*/
|
|
5
|
+
export declare function moveTo(row: number, col: number): string;
|
|
6
|
+
export declare const saveCursor = "\u001B7";
|
|
7
|
+
export declare const restoreCursor = "\u001B8";
|
|
8
|
+
export declare function setScrollRegion(top: number, bottom: number): string;
|
|
9
|
+
export declare const resetScrollRegion = "\u001B[r";
|
|
10
|
+
export declare const clearLine = "\u001B[2K";
|
|
11
|
+
export declare function green(text: string): string;
|
|
12
|
+
export declare function yellow(text: string): string;
|
|
13
|
+
export declare function red(text: string): string;
|
|
14
|
+
export declare function bold(text: string): string;
|
|
15
|
+
export declare function inverse(text: string): string;
|
|
16
|
+
export declare const showCursor = "\u001B[?25h";
|
|
17
|
+
export declare const hideCursor = "\u001B[?25l";
|
|
18
|
+
export declare const resetAttributes = "\u001B[0m";
|
package/dist/ansi.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* ANSI escape code helpers for terminal rendering.
|
|
4
|
+
* Pure string functions — no I/O, no dependencies.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.resetAttributes = exports.hideCursor = exports.showCursor = exports.clearLine = exports.resetScrollRegion = exports.restoreCursor = exports.saveCursor = void 0;
|
|
8
|
+
exports.moveTo = moveTo;
|
|
9
|
+
exports.setScrollRegion = setScrollRegion;
|
|
10
|
+
exports.green = green;
|
|
11
|
+
exports.yellow = yellow;
|
|
12
|
+
exports.red = red;
|
|
13
|
+
exports.bold = bold;
|
|
14
|
+
exports.inverse = inverse;
|
|
15
|
+
const CSI = '\x1b[';
|
|
16
|
+
// --- Cursor positioning ---
|
|
17
|
+
function moveTo(row, col) {
|
|
18
|
+
return `${CSI}${row};${col}H`;
|
|
19
|
+
}
|
|
20
|
+
exports.saveCursor = '\x1b7';
|
|
21
|
+
exports.restoreCursor = '\x1b8';
|
|
22
|
+
// --- Scroll region ---
|
|
23
|
+
function setScrollRegion(top, bottom) {
|
|
24
|
+
return `${CSI}${top};${bottom}r`;
|
|
25
|
+
}
|
|
26
|
+
exports.resetScrollRegion = `${CSI}r`;
|
|
27
|
+
// --- Line operations ---
|
|
28
|
+
exports.clearLine = `${CSI}2K`;
|
|
29
|
+
// --- Colors (foreground) ---
|
|
30
|
+
function green(text) {
|
|
31
|
+
return `${CSI}32m${text}${CSI}0m`;
|
|
32
|
+
}
|
|
33
|
+
function yellow(text) {
|
|
34
|
+
return `${CSI}33m${text}${CSI}0m`;
|
|
35
|
+
}
|
|
36
|
+
function red(text) {
|
|
37
|
+
return `${CSI}31m${text}${CSI}0m`;
|
|
38
|
+
}
|
|
39
|
+
// --- Text attributes ---
|
|
40
|
+
function bold(text) {
|
|
41
|
+
return `${CSI}1m${text}${CSI}0m`;
|
|
42
|
+
}
|
|
43
|
+
function inverse(text) {
|
|
44
|
+
return `${CSI}7m${text}${CSI}0m`;
|
|
45
|
+
}
|
|
46
|
+
// --- Cursor visibility ---
|
|
47
|
+
exports.showCursor = `${CSI}?25h`;
|
|
48
|
+
exports.hideCursor = `${CSI}?25l`;
|
|
49
|
+
// --- Reset ---
|
|
50
|
+
exports.resetAttributes = `${CSI}0m`;
|
|
51
|
+
//# sourceMappingURL=ansi.js.map
|
package/dist/ansi.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ansi.js","sourceRoot":"","sources":["../src/ansi.ts"],"names":[],"mappings":";AAAA;;;GAGG;;;AAMH,wBAEC;AAOD,0CAEC;AAUD,sBAEC;AAED,wBAEC;AAED,kBAEC;AAID,oBAEC;AAED,0BAEC;AA7CD,MAAM,GAAG,GAAG,OAAO,CAAC;AAEpB,6BAA6B;AAE7B,SAAgB,MAAM,CAAC,GAAW,EAAE,GAAW;IAC7C,OAAO,GAAG,GAAG,GAAG,GAAG,IAAI,GAAG,GAAG,CAAC;AAChC,CAAC;AAEY,QAAA,UAAU,GAAG,OAAO,CAAC;AACrB,QAAA,aAAa,GAAG,OAAO,CAAC;AAErC,wBAAwB;AAExB,SAAgB,eAAe,CAAC,GAAW,EAAE,MAAc;IACzD,OAAO,GAAG,GAAG,GAAG,GAAG,IAAI,MAAM,GAAG,CAAC;AACnC,CAAC;AAEY,QAAA,iBAAiB,GAAG,GAAG,GAAG,GAAG,CAAC;AAE3C,0BAA0B;AAEb,QAAA,SAAS,GAAG,GAAG,GAAG,IAAI,CAAC;AAEpC,8BAA8B;AAE9B,SAAgB,KAAK,CAAC,IAAY;IAChC,OAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC;AACpC,CAAC;AAED,SAAgB,MAAM,CAAC,IAAY;IACjC,OAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC;AACpC,CAAC;AAED,SAAgB,GAAG,CAAC,IAAY;IAC9B,OAAO,GAAG,GAAG,MAAM,IAAI,GAAG,GAAG,IAAI,CAAC;AACpC,CAAC;AAED,0BAA0B;AAE1B,SAAgB,IAAI,CAAC,IAAY;IAC/B,OAAO,GAAG,GAAG,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC;AACnC,CAAC;AAED,SAAgB,OAAO,CAAC,IAAY;IAClC,OAAO,GAAG,GAAG,KAAK,IAAI,GAAG,GAAG,IAAI,CAAC;AACnC,CAAC;AAED,4BAA4B;AAEf,QAAA,UAAU,GAAG,GAAG,GAAG,MAAM,CAAC;AAC1B,QAAA,UAAU,GAAG,GAAG,GAAG,MAAM,CAAC;AAEvC,gBAAgB;AAEH,QAAA,eAAe,GAAG,GAAG,GAAG,IAAI,CAAC"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ProcessSupervisor_js_1 = require("./ProcessSupervisor.js");
|
|
4
|
+
const StatusBar_js_1 = require("./StatusBar.js");
|
|
5
|
+
const CountdownCard_js_1 = require("./CountdownCard.js");
|
|
6
|
+
const fs_1 = require("fs");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
/**
|
|
9
|
+
* Read the version string from package.json.
|
|
10
|
+
*/
|
|
11
|
+
function getVersion() {
|
|
12
|
+
try {
|
|
13
|
+
const pkgPath = (0, path_1.join)(__dirname, '..', 'package.json');
|
|
14
|
+
const pkg = JSON.parse((0, fs_1.readFileSync)(pkgPath, 'utf8'));
|
|
15
|
+
return pkg.version ?? 'unknown';
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return 'unknown';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Print usage instructions to stdout.
|
|
23
|
+
*/
|
|
24
|
+
function showHelp() {
|
|
25
|
+
const version = getVersion();
|
|
26
|
+
console.log(`claude-auto-continue v${version}
|
|
27
|
+
|
|
28
|
+
Automatically resumes Claude Code sessions after usage limits reset.
|
|
29
|
+
|
|
30
|
+
USAGE:
|
|
31
|
+
claude-auto-continue [options] [-- claude-code-args...]
|
|
32
|
+
clac [options] [-- claude-code-args...]
|
|
33
|
+
|
|
34
|
+
OPTIONS:
|
|
35
|
+
--help, -h Show this help message
|
|
36
|
+
--version, -v Show version number
|
|
37
|
+
|
|
38
|
+
EXAMPLES:
|
|
39
|
+
claude-auto-continue Start with defaults
|
|
40
|
+
claude-auto-continue -- --continue Pass --continue to Claude Code
|
|
41
|
+
clac -- --resume Short alias, pass --resume to Claude Code
|
|
42
|
+
|
|
43
|
+
WHAT IT DOES:
|
|
44
|
+
Wraps Claude Code in a PTY, monitors for rate-limit messages, waits
|
|
45
|
+
until the reset time, then sends "continue" to resume automatically.
|
|
46
|
+
A status bar and countdown timer show progress while waiting.`);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Parse CLI arguments, extracting Claude Code args after the -- separator.
|
|
50
|
+
*/
|
|
51
|
+
function parseArgs(argv) {
|
|
52
|
+
const userArgs = argv.slice(2); // strip 'node' and script path
|
|
53
|
+
const separatorIdx = userArgs.indexOf('--');
|
|
54
|
+
if (separatorIdx === -1) {
|
|
55
|
+
return { claudeArgs: [] };
|
|
56
|
+
}
|
|
57
|
+
return { claudeArgs: userArgs.slice(separatorIdx + 1) };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Main entry point — wires ProcessSupervisor to the status display.
|
|
61
|
+
*/
|
|
62
|
+
function main() {
|
|
63
|
+
const args = process.argv.slice(2);
|
|
64
|
+
// Handle --help and --version before anything else
|
|
65
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
66
|
+
showHelp();
|
|
67
|
+
process.exit(0);
|
|
68
|
+
}
|
|
69
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
70
|
+
console.log(getVersion());
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
const { claudeArgs } = parseArgs(process.argv);
|
|
74
|
+
const cols = () => process.stdout.columns ?? 80;
|
|
75
|
+
const rows = () => process.stdout.rows ?? 24;
|
|
76
|
+
const cwd = process.cwd();
|
|
77
|
+
// Create display components
|
|
78
|
+
const statusBar = new StatusBar_js_1.StatusBar({ cols: cols() });
|
|
79
|
+
const countdownCard = new CountdownCard_js_1.CountdownCard({ cols: cols(), rows: rows() });
|
|
80
|
+
// Set up terminal: scroll region leaves row 1 for status bar
|
|
81
|
+
process.stdout.write(statusBar.initScrollRegion(rows()));
|
|
82
|
+
// Initial status bar render
|
|
83
|
+
process.stdout.write(statusBar.render('RUNNING', { cwd }));
|
|
84
|
+
// Countdown timer handle
|
|
85
|
+
let countdownInterval = null;
|
|
86
|
+
// Create supervisor with output routed through the standard handler
|
|
87
|
+
const supervisor = new ProcessSupervisor_js_1.ProcessSupervisor({
|
|
88
|
+
onOutput: (data) => {
|
|
89
|
+
process.stdout.write(data);
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
// Handle state changes from ProcessSupervisor
|
|
93
|
+
supervisor.on('stateChange', (event) => {
|
|
94
|
+
const { state, resetTime } = event;
|
|
95
|
+
// Clear any existing countdown timer
|
|
96
|
+
if (countdownInterval) {
|
|
97
|
+
clearInterval(countdownInterval);
|
|
98
|
+
countdownInterval = null;
|
|
99
|
+
}
|
|
100
|
+
if (state === 'DEAD') {
|
|
101
|
+
// Show dead state in status bar, clear countdown card
|
|
102
|
+
process.stdout.write(statusBar.render('DEAD', { cwd }));
|
|
103
|
+
process.stdout.write(countdownCard.clear());
|
|
104
|
+
// Wait 5 seconds to show "Dead" status, then cleanup and let exit handler run
|
|
105
|
+
setTimeout(() => {
|
|
106
|
+
process.stdout.write(statusBar.cleanup());
|
|
107
|
+
}, 5000);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
// Re-render status bar for the current state
|
|
111
|
+
process.stdout.write(statusBar.render(state, { resetTime: resetTime ?? undefined, cwd }));
|
|
112
|
+
if (state === 'WAITING' && resetTime) {
|
|
113
|
+
// Show centered countdown card
|
|
114
|
+
process.stdout.write(countdownCard.render({ resetTime, cwd }));
|
|
115
|
+
// Start 1-second countdown tick
|
|
116
|
+
countdownInterval = setInterval(() => {
|
|
117
|
+
// Race protection: if state has moved on, stop ticking
|
|
118
|
+
if (supervisor.state !== 'WAITING') {
|
|
119
|
+
clearInterval(countdownInterval);
|
|
120
|
+
countdownInterval = null;
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Recalculate from resetTime - Date.now() each tick (no drift)
|
|
124
|
+
process.stdout.write(statusBar.render('WAITING', { resetTime, cwd }));
|
|
125
|
+
process.stdout.write(countdownCard.render({ resetTime, cwd }));
|
|
126
|
+
}, 1000);
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Clear countdown card if it was showing
|
|
130
|
+
process.stdout.write(countdownCard.clear());
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
// Handle terminal resize
|
|
134
|
+
process.stdout.on('resize', () => {
|
|
135
|
+
// Update display component dimensions
|
|
136
|
+
statusBar.cols = cols();
|
|
137
|
+
countdownCard.cols = cols();
|
|
138
|
+
countdownCard.rows = rows();
|
|
139
|
+
// Re-initialize scroll region with new dimensions
|
|
140
|
+
process.stdout.write(statusBar.initScrollRegion(rows()));
|
|
141
|
+
// Re-render current state
|
|
142
|
+
const currentState = supervisor.state;
|
|
143
|
+
process.stdout.write(statusBar.render(currentState, { cwd }));
|
|
144
|
+
});
|
|
145
|
+
// Terminal cleanup on all exit paths
|
|
146
|
+
const cleanup = () => {
|
|
147
|
+
if (countdownInterval) {
|
|
148
|
+
clearInterval(countdownInterval);
|
|
149
|
+
countdownInterval = null;
|
|
150
|
+
}
|
|
151
|
+
process.stdout.write(statusBar.cleanup());
|
|
152
|
+
};
|
|
153
|
+
process.on('exit', cleanup);
|
|
154
|
+
process.on('SIGINT', () => {
|
|
155
|
+
cleanup();
|
|
156
|
+
process.exit(130);
|
|
157
|
+
});
|
|
158
|
+
process.on('SIGTERM', () => {
|
|
159
|
+
cleanup();
|
|
160
|
+
process.exit(143);
|
|
161
|
+
});
|
|
162
|
+
// Spawn Claude Code with passed-through arguments
|
|
163
|
+
supervisor.spawn('claude', claudeArgs);
|
|
164
|
+
}
|
|
165
|
+
main();
|
|
166
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
package/dist/config.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CONFIG_PATH = void 0;
|
|
4
|
+
exports.loadConfig = loadConfig;
|
|
5
|
+
const fs_1 = require("fs");
|
|
6
|
+
const os_1 = require("os");
|
|
7
|
+
const path_1 = require("path");
|
|
8
|
+
exports.CONFIG_PATH = (0, path_1.join)((0, os_1.homedir)(), '.config', 'claude-auto-continue', 'config.json');
|
|
9
|
+
function loadConfig() {
|
|
10
|
+
try {
|
|
11
|
+
const raw = (0, fs_1.readFileSync)(exports.CONFIG_PATH, 'utf8');
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
const config = {};
|
|
14
|
+
if (typeof parsed.pattern === 'string') {
|
|
15
|
+
config.pattern = new RegExp(parsed.pattern, 'i');
|
|
16
|
+
}
|
|
17
|
+
return config;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":";;;AAUA,gCAYC;AAtBD,2BAAkC;AAClC,2BAA6B;AAC7B,+BAA4B;AAMf,QAAA,WAAW,GAAG,IAAA,WAAI,EAAC,IAAA,YAAO,GAAE,EAAE,SAAS,EAAE,sBAAsB,EAAE,aAAa,CAAC,CAAC;AAE7F,SAAgB,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAA,iBAAY,EAAC,mBAAW,EAAE,MAAM,CAAC,CAAC;QAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,MAAM,GAAe,EAAE,CAAC;QAC9B,IAAI,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,CAAC,OAAO,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;QACnD,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "claude-auto-continue",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Automatically resumes Claude Code sessions after usage limits reset",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-auto-continue": "bin/claude-auto-continue.js",
|
|
8
|
+
"clac": "bin/claude-auto-continue.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"bin/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"claude",
|
|
25
|
+
"claude-code",
|
|
26
|
+
"auto-continue",
|
|
27
|
+
"rate-limit",
|
|
28
|
+
"cli"
|
|
29
|
+
],
|
|
30
|
+
"author": "dakmor",
|
|
31
|
+
"license": "MIT",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "https://github.com/oguztecimer/claude-auto-continue.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/oguztecimer/claude-auto-continue#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/oguztecimer/claude-auto-continue/issues"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"node-pty": "^1.1.0",
|
|
42
|
+
"strip-ansi": "^6.0.1"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@types/node": "^25.3.2",
|
|
46
|
+
"tsx": "^4.21.0",
|
|
47
|
+
"typescript": "^5.9.3",
|
|
48
|
+
"vitest": "^2.1.9"
|
|
49
|
+
}
|
|
50
|
+
}
|