cc-resilient 0.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/LICENSE +21 -0
- package/README.md +160 -0
- package/dist/src/cli.d.ts +2 -0
- package/dist/src/cli.js +74 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/config.d.ts +4 -0
- package/dist/src/config.js +62 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +8 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger.d.ts +10 -0
- package/dist/src/logger.js +48 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/network-monitor.d.ts +18 -0
- package/dist/src/network-monitor.js +110 -0
- package/dist/src/network-monitor.js.map +1 -0
- package/dist/src/process-monitor.d.ts +21 -0
- package/dist/src/process-monitor.js +105 -0
- package/dist/src/process-monitor.js.map +1 -0
- package/dist/src/recovery-manager.d.ts +33 -0
- package/dist/src/recovery-manager.js +206 -0
- package/dist/src/recovery-manager.js.map +1 -0
- package/dist/src/session-tracker.d.ts +14 -0
- package/dist/src/session-tracker.js +88 -0
- package/dist/src/session-tracker.js.map +1 -0
- package/dist/src/status-display.d.ts +16 -0
- package/dist/src/status-display.js +74 -0
- package/dist/src/status-display.js.map +1 -0
- package/dist/src/types.d.ts +52 -0
- package/dist/src/types.js +18 -0
- package/dist/src/types.js.map +1 -0
- package/dist/src/wrapper.d.ts +13 -0
- package/dist/src/wrapper.js +86 -0
- package/dist/src/wrapper.js.map +1 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SaravananJaichandar
|
|
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,160 @@
|
|
|
1
|
+
# cc-resilient
|
|
2
|
+
|
|
3
|
+
Network-resilient wrapper for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) CLI.
|
|
4
|
+
|
|
5
|
+
Built as a working prototype for [anthropics/claude-code#26729](https://github.com/anthropics/claude-code/issues/26729) -- a proposal for native streaming resilience in Claude Code.
|
|
6
|
+
|
|
7
|
+
## Problem
|
|
8
|
+
|
|
9
|
+
When using Claude Code over unstable connections (Wi-Fi drops, power cuts, VPN disconnects, mobile hotspot switching), active sessions hang silently with no timeout, no recovery, and no graceful handling. The only option is to kill the process and manually restart.
|
|
10
|
+
|
|
11
|
+
## What cc-resilient does
|
|
12
|
+
|
|
13
|
+
`cc-resilient` wraps the `claude` CLI and adds three capabilities:
|
|
14
|
+
|
|
15
|
+
1. **Network monitoring** -- pings `api.anthropic.com` every 5 seconds to detect connectivity loss
|
|
16
|
+
2. **Hang detection** -- identifies stalled processes with no output for a configurable timeout
|
|
17
|
+
3. **Automatic recovery** -- kills hung processes, saves session metadata, and resumes with context when connectivity returns
|
|
18
|
+
|
|
19
|
+
## Install
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm install -g cc-resilient
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**Prerequisite**: Claude Code must be installed and authenticated (`claude` command available in PATH).
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Use exactly like claude, but with network resilience
|
|
31
|
+
cc-resilient -- "explain this project"
|
|
32
|
+
|
|
33
|
+
# Print mode
|
|
34
|
+
cc-resilient -- -p "refactor the auth module"
|
|
35
|
+
|
|
36
|
+
# Continue a session with resilience
|
|
37
|
+
cc-resilient -- --continue
|
|
38
|
+
|
|
39
|
+
# Interactive mode (stdin passed through)
|
|
40
|
+
cc-resilient
|
|
41
|
+
|
|
42
|
+
# Disable auto-resume (ask before resuming)
|
|
43
|
+
cc-resilient --no-auto-resume -- -p "build feature X"
|
|
44
|
+
|
|
45
|
+
# Verbose mode (see network status on stderr)
|
|
46
|
+
cc-resilient --verbose -- -p "hello"
|
|
47
|
+
|
|
48
|
+
# Check last recovery state
|
|
49
|
+
cc-resilient --status
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How it works
|
|
53
|
+
|
|
54
|
+
```
|
|
55
|
+
cc-resilient
|
|
56
|
+
|
|
|
57
|
+
+-------------+-------------+
|
|
58
|
+
| | |
|
|
59
|
+
Network Monitor Process Monitor Session Tracker
|
|
60
|
+
(ping every 5s) (track stdout) (find session ID)
|
|
61
|
+
| | |
|
|
62
|
+
+------+------+ |
|
|
63
|
+
| |
|
|
64
|
+
Recovery Manager <--------+
|
|
65
|
+
(orchestrate disconnect/reconnect/resume)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
1. `cc-resilient` spawns `claude` as a child process
|
|
69
|
+
2. In parallel, it pings `api.anthropic.com` every 5 seconds (HTTPS HEAD)
|
|
70
|
+
3. If 3 consecutive pings fail, it declares the connection offline
|
|
71
|
+
4. It gracefully kills the hung claude process (SIGTERM, then SIGKILL after 5s)
|
|
72
|
+
5. It saves recovery metadata to `~/.cc-resilient/recovery.json`
|
|
73
|
+
6. When 2 consecutive pings succeed, it declares the connection restored
|
|
74
|
+
7. It resumes the session: `claude --continue -p "You were interrupted by a network disconnection..."`
|
|
75
|
+
|
|
76
|
+
## Configuration
|
|
77
|
+
|
|
78
|
+
### CLI flags
|
|
79
|
+
|
|
80
|
+
| Flag | Default | Description |
|
|
81
|
+
|------|---------|-------------|
|
|
82
|
+
| `--health-interval <ms>` | 5000 | How often to check connectivity |
|
|
83
|
+
| `--health-timeout <ms>` | 3000 | Timeout per health check |
|
|
84
|
+
| `--hang-timeout <ms>` | 300000 | No-output duration before declaring a hang (5 min) |
|
|
85
|
+
| `--no-auto-resume` | false | Ask before resuming instead of auto-resume |
|
|
86
|
+
| `--max-resumes <n>` | 3 | Give up after N consecutive resume failures |
|
|
87
|
+
| `--resume-prompt <text>` | (see below) | Custom text for the resume context message |
|
|
88
|
+
| `--log-file <path>` | null | Write structured logs to a file |
|
|
89
|
+
| `--verbose` | false | Show detailed status on stderr |
|
|
90
|
+
| `--config <path>` | `~/.cc-resilient.json` | Path to config file |
|
|
91
|
+
| `--status` | - | Print last recovery state and exit |
|
|
92
|
+
|
|
93
|
+
### Config file
|
|
94
|
+
|
|
95
|
+
Create `~/.cc-resilient.json` with any of these options:
|
|
96
|
+
|
|
97
|
+
```json
|
|
98
|
+
{
|
|
99
|
+
"healthCheckIntervalMs": 5000,
|
|
100
|
+
"healthCheckTimeoutMs": 3000,
|
|
101
|
+
"healthCheckEndpoint": "https://api.anthropic.com",
|
|
102
|
+
"offlineThreshold": 3,
|
|
103
|
+
"reconnectStabilityCount": 2,
|
|
104
|
+
"processHangTimeoutMs": 300000,
|
|
105
|
+
"gracefulShutdownTimeoutMs": 5000,
|
|
106
|
+
"autoResume": true,
|
|
107
|
+
"autoResumeDelayMs": 2000,
|
|
108
|
+
"maxResumeAttempts": 3,
|
|
109
|
+
"verbose": false
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
All fields are optional. Missing fields use defaults.
|
|
114
|
+
|
|
115
|
+
## Limitations
|
|
116
|
+
|
|
117
|
+
This is an external wrapper, not a native feature. It can solve some problems but not all:
|
|
118
|
+
|
|
119
|
+
| What it can do | What it cannot do |
|
|
120
|
+
|----------------|-------------------|
|
|
121
|
+
| Detect network loss (via periodic pings) | See the SSE stream directly (instant detection) |
|
|
122
|
+
| Kill hung processes | Save partial streaming responses |
|
|
123
|
+
| Auto-resume sessions with context | Know exactly which tool was mid-execution |
|
|
124
|
+
| Save recovery metadata to disk | Repair corrupted conversation state (orphaned tool_use blocks) |
|
|
125
|
+
| Work with both interactive and print modes | Selectively retry safe vs unsafe tools |
|
|
126
|
+
|
|
127
|
+
The remaining ~60% of the proposed functionality requires native support inside Claude Code. See the [full feature request](https://github.com/anthropics/claude-code/issues/26729) for the complete design.
|
|
128
|
+
|
|
129
|
+
## Recovery metadata
|
|
130
|
+
|
|
131
|
+
On disconnect, `cc-resilient` saves state to `~/.cc-resilient/recovery.json`:
|
|
132
|
+
|
|
133
|
+
```json
|
|
134
|
+
{
|
|
135
|
+
"sessionId": "fdbee89f-40d1-483c-b33b-b4305d924d84",
|
|
136
|
+
"workingDirectory": "/home/user/project",
|
|
137
|
+
"timestamp": "2026-02-23T13:45:00.000Z",
|
|
138
|
+
"lastActivityTimestamp": "2026-02-23T13:44:55.000Z",
|
|
139
|
+
"disconnectReason": "network",
|
|
140
|
+
"claudeArgs": ["-p", "build the auth module"],
|
|
141
|
+
"pid": 12345,
|
|
142
|
+
"resumeCount": 0
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
This persists even if `cc-resilient` itself crashes, so you can inspect what happened and manually resume.
|
|
147
|
+
|
|
148
|
+
## Development
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
git clone https://github.com/SaravananJaichandar/cc-resilient.git
|
|
152
|
+
cd cc-resilient
|
|
153
|
+
npm install
|
|
154
|
+
npm run build
|
|
155
|
+
npm test
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## License
|
|
159
|
+
|
|
160
|
+
MIT
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { loadConfig } from './config.js';
|
|
3
|
+
import { CcResilientWrapper } from './wrapper.js';
|
|
4
|
+
import { RecoveryManager } from './recovery-manager.js';
|
|
5
|
+
export function createCli() {
|
|
6
|
+
const program = new Command();
|
|
7
|
+
program
|
|
8
|
+
.name('cc-resilient')
|
|
9
|
+
.description('Network-resilient wrapper for Claude Code CLI')
|
|
10
|
+
.version('0.1.0')
|
|
11
|
+
.option('--health-interval <ms>', 'Health check interval in ms', '5000')
|
|
12
|
+
.option('--health-timeout <ms>', 'Health check timeout in ms', '3000')
|
|
13
|
+
.option('--hang-timeout <ms>', 'Process hang timeout in ms', '300000')
|
|
14
|
+
.option('--no-auto-resume', 'Disable automatic resume on reconnect')
|
|
15
|
+
.option('--max-resumes <n>', 'Maximum resume attempts', '3')
|
|
16
|
+
.option('--resume-prompt <text>', 'Custom prompt for resume context')
|
|
17
|
+
.option('--log-file <path>', 'Write logs to file')
|
|
18
|
+
.option('--verbose', 'Enable verbose output', false)
|
|
19
|
+
.option('--config <path>', 'Path to config file')
|
|
20
|
+
.option('--status', 'Show last recovery state and exit')
|
|
21
|
+
.allowUnknownOption(true)
|
|
22
|
+
.argument('[claude-args...]', 'Arguments to pass through to claude')
|
|
23
|
+
.action(async (claudeArgs, options) => {
|
|
24
|
+
if (options.status) {
|
|
25
|
+
showStatus();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const cliOverrides = {};
|
|
29
|
+
if (options.healthInterval !== '5000') {
|
|
30
|
+
cliOverrides.healthCheckIntervalMs = parseInt(options.healthInterval, 10);
|
|
31
|
+
}
|
|
32
|
+
if (options.healthTimeout !== '3000') {
|
|
33
|
+
cliOverrides.healthCheckTimeoutMs = parseInt(options.healthTimeout, 10);
|
|
34
|
+
}
|
|
35
|
+
if (options.hangTimeout !== '300000') {
|
|
36
|
+
cliOverrides.processHangTimeoutMs = parseInt(options.hangTimeout, 10);
|
|
37
|
+
}
|
|
38
|
+
if (options.autoResume === false) {
|
|
39
|
+
cliOverrides.autoResume = false;
|
|
40
|
+
}
|
|
41
|
+
if (options.maxResumes !== '3') {
|
|
42
|
+
cliOverrides.maxResumeAttempts = parseInt(options.maxResumes, 10);
|
|
43
|
+
}
|
|
44
|
+
if (options.resumePrompt) {
|
|
45
|
+
cliOverrides.resumePrompt = options.resumePrompt;
|
|
46
|
+
}
|
|
47
|
+
if (options.logFile) {
|
|
48
|
+
cliOverrides.logFile = options.logFile;
|
|
49
|
+
}
|
|
50
|
+
if (options.verbose) {
|
|
51
|
+
cliOverrides.verbose = true;
|
|
52
|
+
}
|
|
53
|
+
const config = loadConfig(cliOverrides, options.config);
|
|
54
|
+
const wrapper = new CcResilientWrapper(claudeArgs, config);
|
|
55
|
+
const exitCode = await wrapper.run();
|
|
56
|
+
process.exit(exitCode);
|
|
57
|
+
});
|
|
58
|
+
return program;
|
|
59
|
+
}
|
|
60
|
+
function showStatus() {
|
|
61
|
+
const metadata = RecoveryManager.loadRecoveryMetadata();
|
|
62
|
+
if (!metadata) {
|
|
63
|
+
process.stderr.write('[cc-resilient] No recovery data found.\n');
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
process.stderr.write('[cc-resilient] Last recovery state:\n');
|
|
67
|
+
process.stderr.write(` Session ID: ${metadata.sessionId ?? 'unknown'}\n`);
|
|
68
|
+
process.stderr.write(` Directory: ${metadata.workingDirectory}\n`);
|
|
69
|
+
process.stderr.write(` Disconnected: ${metadata.timestamp}\n`);
|
|
70
|
+
process.stderr.write(` Reason: ${metadata.disconnectReason}\n`);
|
|
71
|
+
process.stderr.write(` Resumes: ${metadata.resumeCount}\n`);
|
|
72
|
+
process.stderr.write(` Args: ${metadata.claudeArgs.join(' ')}\n`);
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=cli.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const DEFAULT_CONFIG = {
|
|
5
|
+
healthCheckIntervalMs: 5000,
|
|
6
|
+
healthCheckTimeoutMs: 3000,
|
|
7
|
+
healthCheckEndpoint: 'https://api.anthropic.com',
|
|
8
|
+
offlineThreshold: 3,
|
|
9
|
+
reconnectStabilityCount: 2,
|
|
10
|
+
processHangTimeoutMs: 300000,
|
|
11
|
+
gracefulShutdownTimeoutMs: 5000,
|
|
12
|
+
autoResume: true,
|
|
13
|
+
autoResumeDelayMs: 2000,
|
|
14
|
+
maxResumeAttempts: 3,
|
|
15
|
+
resumePrompt: 'You were interrupted by a network disconnection at {timestamp}. Your session has been automatically resumed. Please continue from where you left off. If you were in the middle of a multi-step task, summarize what you have completed so far and continue.',
|
|
16
|
+
logFile: null,
|
|
17
|
+
verbose: false,
|
|
18
|
+
};
|
|
19
|
+
function loadConfigFile(configPath) {
|
|
20
|
+
const filePath = configPath ?? join(homedir(), '.cc-resilient.json');
|
|
21
|
+
try {
|
|
22
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
23
|
+
return JSON.parse(raw);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function validateConfig(config) {
|
|
30
|
+
if (config.healthCheckIntervalMs <= 0)
|
|
31
|
+
throw new Error('healthCheckIntervalMs must be positive');
|
|
32
|
+
if (config.healthCheckTimeoutMs <= 0)
|
|
33
|
+
throw new Error('healthCheckTimeoutMs must be positive');
|
|
34
|
+
if (config.offlineThreshold < 1)
|
|
35
|
+
throw new Error('offlineThreshold must be at least 1');
|
|
36
|
+
if (config.reconnectStabilityCount < 1)
|
|
37
|
+
throw new Error('reconnectStabilityCount must be at least 1');
|
|
38
|
+
if (config.processHangTimeoutMs <= 0)
|
|
39
|
+
throw new Error('processHangTimeoutMs must be positive');
|
|
40
|
+
if (config.gracefulShutdownTimeoutMs <= 0)
|
|
41
|
+
throw new Error('gracefulShutdownTimeoutMs must be positive');
|
|
42
|
+
if (config.maxResumeAttempts < 1)
|
|
43
|
+
throw new Error('maxResumeAttempts must be at least 1');
|
|
44
|
+
try {
|
|
45
|
+
new URL(config.healthCheckEndpoint);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
throw new Error('healthCheckEndpoint must be a valid URL');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function loadConfig(cliOverrides = {}, configPath) {
|
|
52
|
+
const fileConfig = loadConfigFile(configPath);
|
|
53
|
+
const merged = {
|
|
54
|
+
...DEFAULT_CONFIG,
|
|
55
|
+
...fileConfig,
|
|
56
|
+
...cliOverrides,
|
|
57
|
+
};
|
|
58
|
+
validateConfig(merged);
|
|
59
|
+
return merged;
|
|
60
|
+
}
|
|
61
|
+
export { DEFAULT_CONFIG };
|
|
62
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAGlC,MAAM,cAAc,GAAsB;IACxC,qBAAqB,EAAE,IAAI;IAC3B,oBAAoB,EAAE,IAAI;IAC1B,mBAAmB,EAAE,2BAA2B;IAChD,gBAAgB,EAAE,CAAC;IACnB,uBAAuB,EAAE,CAAC;IAC1B,oBAAoB,EAAE,MAAM;IAC5B,yBAAyB,EAAE,IAAI;IAC/B,UAAU,EAAE,IAAI;IAChB,iBAAiB,EAAE,IAAI;IACvB,iBAAiB,EAAE,CAAC;IACpB,YAAY,EACV,8PAA8P;IAChQ,OAAO,EAAE,IAAI;IACb,OAAO,EAAE,KAAK;CACf,CAAC;AAEF,SAAS,cAAc,CAAC,UAAmB;IACzC,MAAM,QAAQ,GAAG,UAAU,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,oBAAoB,CAAC,CAAC;IACrE,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC5C,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAA+B,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,SAAS,cAAc,CAAC,MAAyB;IAC/C,IAAI,MAAM,CAAC,qBAAqB,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IACjG,IAAI,MAAM,CAAC,oBAAoB,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC/F,IAAI,MAAM,CAAC,gBAAgB,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IACxF,IAAI,MAAM,CAAC,uBAAuB,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACtG,IAAI,MAAM,CAAC,oBAAoB,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;IAC/F,IAAI,MAAM,CAAC,yBAAyB,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IACzG,IAAI,MAAM,CAAC,iBAAiB,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1F,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CACxB,eAA2C,EAAE,EAC7C,UAAmB;IAEnB,MAAM,UAAU,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAsB;QAChC,GAAG,cAAc;QACjB,GAAG,UAAU;QACb,GAAG,YAAY;KAChB,CAAC;IACF,cAAc,CAAC,MAAM,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createCli } from './cli.js';
|
|
3
|
+
const program = createCli();
|
|
4
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
5
|
+
process.stderr.write(`[cc-resilient] Fatal error: ${err.message}\n`);
|
|
6
|
+
process.exit(1);
|
|
7
|
+
});
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAErC,MAAM,OAAO,GAAG,SAAS,EAAE,CAAC;AAC5B,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IACpD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,+BAA+B,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare class Logger {
|
|
2
|
+
private logFile;
|
|
3
|
+
private verbose;
|
|
4
|
+
constructor(logFile: string | null, verbose: boolean);
|
|
5
|
+
info(message: string, data?: Record<string, unknown>): void;
|
|
6
|
+
warn(message: string, data?: Record<string, unknown>): void;
|
|
7
|
+
error(message: string, data?: Record<string, unknown>): void;
|
|
8
|
+
debug(message: string, data?: Record<string, unknown>): void;
|
|
9
|
+
private write;
|
|
10
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
export class Logger {
|
|
4
|
+
logFile;
|
|
5
|
+
verbose;
|
|
6
|
+
constructor(logFile, verbose) {
|
|
7
|
+
this.logFile = logFile;
|
|
8
|
+
this.verbose = verbose;
|
|
9
|
+
if (this.logFile) {
|
|
10
|
+
mkdirSync(dirname(this.logFile), { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
info(message, data) {
|
|
14
|
+
this.write('INFO', message, data);
|
|
15
|
+
}
|
|
16
|
+
warn(message, data) {
|
|
17
|
+
this.write('WARN', message, data);
|
|
18
|
+
}
|
|
19
|
+
error(message, data) {
|
|
20
|
+
this.write('ERROR', message, data);
|
|
21
|
+
}
|
|
22
|
+
debug(message, data) {
|
|
23
|
+
if (this.verbose) {
|
|
24
|
+
this.write('DEBUG', message, data);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
write(level, message, data) {
|
|
28
|
+
const entry = {
|
|
29
|
+
timestamp: new Date().toISOString(),
|
|
30
|
+
level,
|
|
31
|
+
message,
|
|
32
|
+
...data,
|
|
33
|
+
};
|
|
34
|
+
if (this.logFile) {
|
|
35
|
+
appendFileSync(this.logFile, JSON.stringify(entry) + '\n');
|
|
36
|
+
}
|
|
37
|
+
if (this.verbose || level === 'ERROR' || level === 'WARN') {
|
|
38
|
+
const prefix = level === 'ERROR' ? '\x1b[31m' : level === 'WARN' ? '\x1b[33m' : '\x1b[90m';
|
|
39
|
+
const reset = '\x1b[0m';
|
|
40
|
+
const isTTY = process.stderr.isTTY;
|
|
41
|
+
const line = isTTY
|
|
42
|
+
? `${prefix}[cc-resilient] ${level}: ${message}${reset}\n`
|
|
43
|
+
: `[cc-resilient] ${level}: ${message}\n`;
|
|
44
|
+
process.stderr.write(line);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,OAAO,MAAM;IACT,OAAO,CAAgB;IACvB,OAAO,CAAU;IAEzB,YAAY,OAAsB,EAAE,OAAgB;QAClD,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC;IACH,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,IAAI,CAAC,OAAe,EAAE,IAA8B;QAClD,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,OAAe,EAAE,IAA8B;QACnD,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;QACrC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,KAAa,EAAE,OAAe,EAAE,IAA8B;QAC1E,MAAM,KAAK,GAAG;YACZ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,KAAK;YACL,OAAO;YACP,GAAG,IAAI;SACR,CAAC;QAEF,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACjB,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC;QAC7D,CAAC;QAED,IAAI,IAAI,CAAC,OAAO,IAAI,KAAK,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,EAAE,CAAC;YAC1D,MAAM,MAAM,GAAG,KAAK,KAAK,OAAO,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;YAC3F,MAAM,KAAK,GAAG,SAAS,CAAC;YACxB,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC;YACnC,MAAM,IAAI,GAAG,KAAK;gBAChB,CAAC,CAAC,GAAG,MAAM,kBAAkB,KAAK,KAAK,OAAO,GAAG,KAAK,IAAI;gBAC1D,CAAC,CAAC,kBAAkB,KAAK,KAAK,OAAO,IAAI,CAAC;YAC5C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7B,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { ConnectionState } from './types.js';
|
|
3
|
+
import type { CcResilientConfig, NetworkCheckResult } from './types.js';
|
|
4
|
+
import type { Logger } from './logger.js';
|
|
5
|
+
export declare class NetworkMonitor extends EventEmitter {
|
|
6
|
+
private config;
|
|
7
|
+
private logger;
|
|
8
|
+
private intervalHandle;
|
|
9
|
+
private consecutiveFailures;
|
|
10
|
+
private consecutiveSuccesses;
|
|
11
|
+
private state;
|
|
12
|
+
constructor(config: Pick<CcResilientConfig, 'healthCheckIntervalMs' | 'healthCheckTimeoutMs' | 'healthCheckEndpoint' | 'offlineThreshold' | 'reconnectStabilityCount'>, logger: Logger);
|
|
13
|
+
start(): void;
|
|
14
|
+
stop(): void;
|
|
15
|
+
getState(): ConnectionState;
|
|
16
|
+
checkOnce(): Promise<NetworkCheckResult>;
|
|
17
|
+
private updateState;
|
|
18
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import https from 'node:https';
|
|
3
|
+
import { ConnectionState } from './types.js';
|
|
4
|
+
export class NetworkMonitor extends EventEmitter {
|
|
5
|
+
config;
|
|
6
|
+
logger;
|
|
7
|
+
intervalHandle = null;
|
|
8
|
+
consecutiveFailures = 0;
|
|
9
|
+
consecutiveSuccesses = 0;
|
|
10
|
+
state = ConnectionState.ONLINE;
|
|
11
|
+
constructor(config, logger) {
|
|
12
|
+
super();
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.logger = logger;
|
|
15
|
+
}
|
|
16
|
+
start() {
|
|
17
|
+
if (this.intervalHandle)
|
|
18
|
+
return;
|
|
19
|
+
this.logger.debug('Network monitor started', {
|
|
20
|
+
interval: this.config.healthCheckIntervalMs,
|
|
21
|
+
endpoint: this.config.healthCheckEndpoint,
|
|
22
|
+
});
|
|
23
|
+
this.intervalHandle = setInterval(() => {
|
|
24
|
+
this.checkOnce().catch((err) => {
|
|
25
|
+
this.logger.error('Health check error', { error: String(err) });
|
|
26
|
+
});
|
|
27
|
+
}, this.config.healthCheckIntervalMs);
|
|
28
|
+
}
|
|
29
|
+
stop() {
|
|
30
|
+
if (this.intervalHandle) {
|
|
31
|
+
clearInterval(this.intervalHandle);
|
|
32
|
+
this.intervalHandle = null;
|
|
33
|
+
this.logger.debug('Network monitor stopped');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
getState() {
|
|
37
|
+
return this.state;
|
|
38
|
+
}
|
|
39
|
+
async checkOnce() {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
const url = new URL(this.config.healthCheckEndpoint);
|
|
42
|
+
const result = await new Promise((resolve) => {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const timeout = setTimeout(() => controller.abort(), this.config.healthCheckTimeoutMs);
|
|
45
|
+
const req = https.request({
|
|
46
|
+
hostname: url.hostname,
|
|
47
|
+
port: 443,
|
|
48
|
+
path: '/',
|
|
49
|
+
method: 'HEAD',
|
|
50
|
+
signal: controller.signal,
|
|
51
|
+
}, () => {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
resolve({
|
|
54
|
+
ok: true,
|
|
55
|
+
latencyMs: Date.now() - start,
|
|
56
|
+
timestamp: new Date().toISOString(),
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
req.on('error', (err) => {
|
|
60
|
+
clearTimeout(timeout);
|
|
61
|
+
resolve({
|
|
62
|
+
ok: false,
|
|
63
|
+
latencyMs: Date.now() - start,
|
|
64
|
+
error: err.message,
|
|
65
|
+
timestamp: new Date().toISOString(),
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
req.end();
|
|
69
|
+
});
|
|
70
|
+
this.emit('check', result);
|
|
71
|
+
this.updateState(result);
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
updateState(result) {
|
|
75
|
+
const oldState = this.state;
|
|
76
|
+
if (result.ok) {
|
|
77
|
+
this.consecutiveFailures = 0;
|
|
78
|
+
this.consecutiveSuccesses++;
|
|
79
|
+
if (this.state === ConnectionState.OFFLINE || this.state === ConnectionState.RECONNECTING) {
|
|
80
|
+
if (this.consecutiveSuccesses >= this.config.reconnectStabilityCount) {
|
|
81
|
+
this.state = ConnectionState.ONLINE;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
this.state = ConnectionState.RECONNECTING;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
this.state = ConnectionState.ONLINE;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.consecutiveSuccesses = 0;
|
|
93
|
+
this.consecutiveFailures++;
|
|
94
|
+
if (this.consecutiveFailures >= this.config.offlineThreshold) {
|
|
95
|
+
this.state = ConnectionState.OFFLINE;
|
|
96
|
+
}
|
|
97
|
+
else if (this.consecutiveFailures > 0) {
|
|
98
|
+
this.state = ConnectionState.DEGRADED;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (oldState !== this.state) {
|
|
102
|
+
this.logger.info(`Network state: ${oldState} -> ${this.state}`, {
|
|
103
|
+
consecutiveFailures: this.consecutiveFailures,
|
|
104
|
+
consecutiveSuccesses: this.consecutiveSuccesses,
|
|
105
|
+
});
|
|
106
|
+
this.emit('stateChange', oldState, this.state);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=network-monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-monitor.js","sourceRoot":"","sources":["../../src/network-monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { EventEmitter } from 'node:events';
|
|
2
|
+
import { type ChildProcess } from 'node:child_process';
|
|
3
|
+
import type { CcResilientConfig } from './types.js';
|
|
4
|
+
import type { Logger } from './logger.js';
|
|
5
|
+
export declare class ProcessMonitor extends EventEmitter {
|
|
6
|
+
private config;
|
|
7
|
+
private logger;
|
|
8
|
+
private childProcess;
|
|
9
|
+
private lastActivityTimestamp;
|
|
10
|
+
private hangCheckInterval;
|
|
11
|
+
private isInteractive;
|
|
12
|
+
constructor(config: Pick<CcResilientConfig, 'processHangTimeoutMs' | 'gracefulShutdownTimeoutMs'>, logger: Logger);
|
|
13
|
+
spawnClaude(args: string[]): ChildProcess;
|
|
14
|
+
gracefulKill(): Promise<void>;
|
|
15
|
+
isAlive(): boolean;
|
|
16
|
+
getLastActivity(): number;
|
|
17
|
+
getPid(): number | undefined;
|
|
18
|
+
getIsInteractive(): boolean;
|
|
19
|
+
private startHangDetection;
|
|
20
|
+
private stopHangDetection;
|
|
21
|
+
}
|