oh-my-claude-sisyphus 3.6.3 → 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/__tests__/delegation-enforcement-levels.test.d.ts +9 -0
- package/dist/__tests__/delegation-enforcement-levels.test.d.ts.map +1 -0
- package/dist/__tests__/delegation-enforcement-levels.test.js +550 -0
- package/dist/__tests__/delegation-enforcement-levels.test.js.map +1 -0
- package/dist/__tests__/installer.test.js +1 -1
- package/dist/__tests__/rate-limit-wait/daemon.test.d.ts +5 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.js +313 -0
- package/dist/__tests__/rate-limit-wait/daemon.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/integration.test.d.ts +8 -0
- package/dist/__tests__/rate-limit-wait/integration.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/integration.test.js +329 -0
- package/dist/__tests__/rate-limit-wait/integration.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.d.ts +5 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.js +167 -0
- package/dist/__tests__/rate-limit-wait/rate-limit-monitor.test.js.map +1 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.d.ts +5 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.js +295 -0
- package/dist/__tests__/rate-limit-wait/tmux-detector.test.js.map +1 -0
- package/dist/cli/commands/wait.d.ts +52 -0
- package/dist/cli/commands/wait.d.ts.map +1 -0
- package/dist/cli/commands/wait.js +229 -0
- package/dist/cli/commands/wait.js.map +1 -0
- package/dist/cli/index.js +54 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/features/rate-limit-wait/daemon.d.ts +52 -0
- package/dist/features/rate-limit-wait/daemon.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/daemon.js +545 -0
- package/dist/features/rate-limit-wait/daemon.js.map +1 -0
- package/dist/features/rate-limit-wait/index.d.ts +16 -0
- package/dist/features/rate-limit-wait/index.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/index.js +18 -0
- package/dist/features/rate-limit-wait/index.js.map +1 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.d.ts +22 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.js +99 -0
- package/dist/features/rate-limit-wait/rate-limit-monitor.js.map +1 -0
- package/dist/features/rate-limit-wait/tmux-detector.d.ts +59 -0
- package/dist/features/rate-limit-wait/tmux-detector.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/tmux-detector.js +304 -0
- package/dist/features/rate-limit-wait/tmux-detector.js.map +1 -0
- package/dist/features/rate-limit-wait/types.d.ts +121 -0
- package/dist/features/rate-limit-wait/types.d.ts.map +1 -0
- package/dist/features/rate-limit-wait/types.js +8 -0
- package/dist/features/rate-limit-wait/types.js.map +1 -0
- package/dist/hooks/bridge.d.ts +1 -1
- package/dist/hooks/bridge.d.ts.map +1 -1
- package/dist/hooks/bridge.js +50 -4
- package/dist/hooks/bridge.js.map +1 -1
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +15 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/omc-orchestrator/audit.d.ts +2 -1
- package/dist/hooks/omc-orchestrator/audit.d.ts.map +1 -1
- package/dist/hooks/omc-orchestrator/audit.js.map +1 -1
- package/dist/hooks/omc-orchestrator/index.d.ts +7 -0
- package/dist/hooks/omc-orchestrator/index.d.ts.map +1 -1
- package/dist/hooks/omc-orchestrator/index.js +95 -8
- package/dist/hooks/omc-orchestrator/index.js.map +1 -1
- package/dist/hooks/permission-handler/__tests__/index.test.d.ts +2 -0
- package/dist/hooks/permission-handler/__tests__/index.test.d.ts.map +1 -0
- package/dist/hooks/permission-handler/__tests__/index.test.js +244 -0
- package/dist/hooks/permission-handler/__tests__/index.test.js.map +1 -0
- package/dist/hooks/permission-handler/index.d.ts +42 -0
- package/dist/hooks/permission-handler/index.d.ts.map +1 -0
- package/dist/hooks/permission-handler/index.js +111 -0
- package/dist/hooks/permission-handler/index.js.map +1 -0
- package/dist/hooks/pre-compact/index.d.ts +82 -0
- package/dist/hooks/pre-compact/index.d.ts.map +1 -0
- package/dist/hooks/pre-compact/index.js +265 -0
- package/dist/hooks/pre-compact/index.js.map +1 -0
- package/dist/hooks/session-end/index.d.ts +50 -0
- package/dist/hooks/session-end/index.d.ts.map +1 -0
- package/dist/hooks/session-end/index.js +207 -0
- package/dist/hooks/session-end/index.js.map +1 -0
- package/dist/hooks/setup/index.d.ts +66 -0
- package/dist/hooks/setup/index.d.ts.map +1 -0
- package/dist/hooks/setup/index.js +299 -0
- package/dist/hooks/setup/index.js.map +1 -0
- package/dist/hooks/setup/types.d.ts +25 -0
- package/dist/hooks/setup/types.d.ts.map +1 -0
- package/dist/hooks/setup/types.js +5 -0
- package/dist/hooks/setup/types.js.map +1 -0
- package/dist/hooks/subagent-tracker/index.d.ts +68 -29
- package/dist/hooks/subagent-tracker/index.d.ts.map +1 -1
- package/dist/hooks/subagent-tracker/index.js +301 -131
- package/dist/hooks/subagent-tracker/index.js.map +1 -1
- package/dist/installer/index.d.ts +1 -1
- package/dist/installer/index.js +1 -1
- package/hooks/hooks.json +83 -1
- package/package.json +3 -1
- package/scripts/permission-handler.mjs +23 -0
- package/scripts/pre-compact.mjs +23 -0
- package/scripts/session-end.mjs +23 -0
- package/scripts/setup-init.mjs +23 -0
- package/scripts/setup-maintenance.mjs +23 -0
- package/scripts/subagent-tracker.mjs +35 -0
- package/templates/hooks/keyword-detector.mjs +198 -0
- package/templates/hooks/keyword-detector.sh +102 -0
- package/templates/hooks/persistent-mode.mjs +249 -0
- package/templates/hooks/persistent-mode.sh +187 -0
- package/templates/hooks/post-tool-use.mjs +133 -0
- package/templates/hooks/post-tool-use.sh +90 -0
- package/templates/hooks/pre-tool-use.mjs +145 -0
- package/templates/hooks/pre-tool-use.sh +113 -0
- package/templates/hooks/session-start.mjs +100 -0
- package/templates/hooks/session-start.sh +62 -0
- package/templates/hooks/stop-continuation.mjs +80 -0
- package/templates/hooks/stop-continuation.sh +40 -0
- package/templates/rules/README.md +40 -0
- package/templates/rules/coding-style.md +74 -0
- package/templates/rules/git-workflow.md +41 -0
- package/templates/rules/performance.md +40 -0
- package/templates/rules/security.md +41 -0
- package/templates/rules/testing.md +42 -0
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Monitor
|
|
3
|
+
*
|
|
4
|
+
* Wraps the existing usage-api.ts to provide rate limit status monitoring.
|
|
5
|
+
* Uses the OAuth API to check utilization percentages.
|
|
6
|
+
*/
|
|
7
|
+
import { getUsage } from '../../hud/usage-api.js';
|
|
8
|
+
/** Threshold percentage for considering rate limited */
|
|
9
|
+
const RATE_LIMIT_THRESHOLD = 100;
|
|
10
|
+
/**
|
|
11
|
+
* Check current rate limit status using the OAuth API
|
|
12
|
+
*
|
|
13
|
+
* @returns Rate limit status or null if API unavailable
|
|
14
|
+
*/
|
|
15
|
+
export async function checkRateLimitStatus() {
|
|
16
|
+
try {
|
|
17
|
+
const usage = await getUsage();
|
|
18
|
+
if (!usage) {
|
|
19
|
+
// No OAuth credentials or API unavailable
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
const fiveHourLimited = usage.fiveHourPercent >= RATE_LIMIT_THRESHOLD;
|
|
23
|
+
const weeklyLimited = usage.weeklyPercent >= RATE_LIMIT_THRESHOLD;
|
|
24
|
+
const isLimited = fiveHourLimited || weeklyLimited;
|
|
25
|
+
// Determine next reset time
|
|
26
|
+
let nextResetAt = null;
|
|
27
|
+
let timeUntilResetMs = null;
|
|
28
|
+
if (isLimited) {
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
const resets = [];
|
|
31
|
+
if (fiveHourLimited && usage.fiveHourResetsAt) {
|
|
32
|
+
resets.push(usage.fiveHourResetsAt);
|
|
33
|
+
}
|
|
34
|
+
if (weeklyLimited && usage.weeklyResetsAt) {
|
|
35
|
+
resets.push(usage.weeklyResetsAt);
|
|
36
|
+
}
|
|
37
|
+
if (resets.length > 0) {
|
|
38
|
+
// Find earliest reset
|
|
39
|
+
nextResetAt = resets.reduce((earliest, current) => current < earliest ? current : earliest);
|
|
40
|
+
timeUntilResetMs = Math.max(0, nextResetAt.getTime() - now);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return {
|
|
44
|
+
fiveHourLimited,
|
|
45
|
+
weeklyLimited,
|
|
46
|
+
isLimited,
|
|
47
|
+
fiveHourResetsAt: usage.fiveHourResetsAt ?? null,
|
|
48
|
+
weeklyResetsAt: usage.weeklyResetsAt ?? null,
|
|
49
|
+
nextResetAt,
|
|
50
|
+
timeUntilResetMs,
|
|
51
|
+
lastCheckedAt: new Date(),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
// Log error but don't throw - return null to indicate unavailable
|
|
56
|
+
console.error('[RateLimitMonitor] Error checking rate limit:', error);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format time until reset for display
|
|
62
|
+
*/
|
|
63
|
+
export function formatTimeUntilReset(ms) {
|
|
64
|
+
if (ms <= 0)
|
|
65
|
+
return 'now';
|
|
66
|
+
const seconds = Math.floor(ms / 1000);
|
|
67
|
+
const minutes = Math.floor(seconds / 60);
|
|
68
|
+
const hours = Math.floor(minutes / 60);
|
|
69
|
+
if (hours > 0) {
|
|
70
|
+
const remainingMinutes = minutes % 60;
|
|
71
|
+
return `${hours}h ${remainingMinutes}m`;
|
|
72
|
+
}
|
|
73
|
+
else if (minutes > 0) {
|
|
74
|
+
const remainingSeconds = seconds % 60;
|
|
75
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
76
|
+
}
|
|
77
|
+
return `${seconds}s`;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Get a human-readable rate limit status message
|
|
81
|
+
*/
|
|
82
|
+
export function formatRateLimitStatus(status) {
|
|
83
|
+
if (!status.isLimited) {
|
|
84
|
+
return 'Not rate limited';
|
|
85
|
+
}
|
|
86
|
+
const parts = [];
|
|
87
|
+
if (status.fiveHourLimited) {
|
|
88
|
+
parts.push('5-hour limit reached');
|
|
89
|
+
}
|
|
90
|
+
if (status.weeklyLimited) {
|
|
91
|
+
parts.push('Weekly limit reached');
|
|
92
|
+
}
|
|
93
|
+
let message = parts.join(' and ');
|
|
94
|
+
if (status.timeUntilResetMs !== null) {
|
|
95
|
+
message += ` (resets in ${formatTimeUntilReset(status.timeUntilResetMs)})`;
|
|
96
|
+
}
|
|
97
|
+
return message;
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=rate-limit-monitor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit-monitor.js","sourceRoot":"","sources":["../../../src/features/rate-limit-wait/rate-limit-monitor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects Claude Code sessions running in tmux panes and identifies
|
|
5
|
+
* those that are blocked due to rate limiting.
|
|
6
|
+
*
|
|
7
|
+
* Security considerations:
|
|
8
|
+
* - Pane IDs are validated before use in shell commands
|
|
9
|
+
* - Text inputs are sanitized to prevent command injection
|
|
10
|
+
*/
|
|
11
|
+
import type { TmuxPane, PaneAnalysisResult, BlockedPane } from './types.js';
|
|
12
|
+
/**
|
|
13
|
+
* Check if tmux is installed and available
|
|
14
|
+
*/
|
|
15
|
+
export declare function isTmuxAvailable(): boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Check if currently running inside a tmux session
|
|
18
|
+
*/
|
|
19
|
+
export declare function isInsideTmux(): boolean;
|
|
20
|
+
/**
|
|
21
|
+
* List all tmux panes across all sessions
|
|
22
|
+
*/
|
|
23
|
+
export declare function listTmuxPanes(): TmuxPane[];
|
|
24
|
+
/**
|
|
25
|
+
* Capture the content of a specific tmux pane
|
|
26
|
+
*
|
|
27
|
+
* @param paneId - The tmux pane ID (e.g., "%0")
|
|
28
|
+
* @param lines - Number of lines to capture (default: 15)
|
|
29
|
+
*/
|
|
30
|
+
export declare function capturePaneContent(paneId: string, lines?: number): string;
|
|
31
|
+
/**
|
|
32
|
+
* Analyze pane content to determine if it shows a rate-limited Claude Code session
|
|
33
|
+
*/
|
|
34
|
+
export declare function analyzePaneContent(content: string): PaneAnalysisResult;
|
|
35
|
+
/**
|
|
36
|
+
* Scan all tmux panes for blocked Claude Code sessions
|
|
37
|
+
*
|
|
38
|
+
* @param lines - Number of lines to capture from each pane
|
|
39
|
+
*/
|
|
40
|
+
export declare function scanForBlockedPanes(lines?: number): BlockedPane[];
|
|
41
|
+
/**
|
|
42
|
+
* Send resume sequence to a tmux pane
|
|
43
|
+
*
|
|
44
|
+
* This sends "1" followed by Enter to select the first option (usually "Continue"),
|
|
45
|
+
* then waits briefly and sends "continue" if needed.
|
|
46
|
+
*
|
|
47
|
+
* @param paneId - The tmux pane ID
|
|
48
|
+
* @returns Whether the command was sent successfully
|
|
49
|
+
*/
|
|
50
|
+
export declare function sendResumeSequence(paneId: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Send custom text to a tmux pane
|
|
53
|
+
*/
|
|
54
|
+
export declare function sendToPane(paneId: string, text: string, pressEnter?: boolean): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Get a summary of blocked panes for display
|
|
57
|
+
*/
|
|
58
|
+
export declare function formatBlockedPanesSummary(blockedPanes: BlockedPane[]): string;
|
|
59
|
+
//# sourceMappingURL=tmux-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux-detector.d.ts","sourceRoot":"","sources":["../../../src/features/rate-limit-wait/tmux-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAoD5E;;GAEG;AACH,wBAAgB,eAAe,IAAI,OAAO,CAUzC;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAEtC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,QAAQ,EAAE,CAyC1C;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,MAAM,CAyBrE;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,kBAAkB,CAqDtE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,SAAK,GAAG,WAAW,EAAE,CAmB7D;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAwB1D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAsBnF;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,YAAY,EAAE,WAAW,EAAE,GAAG,MAAM,CAwB7E"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tmux Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects Claude Code sessions running in tmux panes and identifies
|
|
5
|
+
* those that are blocked due to rate limiting.
|
|
6
|
+
*
|
|
7
|
+
* Security considerations:
|
|
8
|
+
* - Pane IDs are validated before use in shell commands
|
|
9
|
+
* - Text inputs are sanitized to prevent command injection
|
|
10
|
+
*/
|
|
11
|
+
import { execSync, spawnSync } from 'child_process';
|
|
12
|
+
/**
|
|
13
|
+
* Validate tmux pane ID format to prevent command injection
|
|
14
|
+
* Valid formats: %0, %1, %123, etc.
|
|
15
|
+
*/
|
|
16
|
+
function isValidPaneId(paneId) {
|
|
17
|
+
return /^%\d+$/.test(paneId);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Sanitize text for use in tmux send-keys command
|
|
21
|
+
* Escapes single quotes to prevent command injection
|
|
22
|
+
*/
|
|
23
|
+
function sanitizeForTmux(text) {
|
|
24
|
+
// Escape single quotes by ending the quote, adding escaped quote, and reopening
|
|
25
|
+
return text.replace(/'/g, "'\\''");
|
|
26
|
+
}
|
|
27
|
+
/** Rate limit message patterns to detect in pane content */
|
|
28
|
+
const RATE_LIMIT_PATTERNS = [
|
|
29
|
+
/rate limit/i,
|
|
30
|
+
/usage limit/i,
|
|
31
|
+
/quota exceeded/i,
|
|
32
|
+
/too many requests/i,
|
|
33
|
+
/please wait/i,
|
|
34
|
+
/try again later/i,
|
|
35
|
+
/limit reached/i,
|
|
36
|
+
/5[- ]?hour/i,
|
|
37
|
+
/weekly/i,
|
|
38
|
+
];
|
|
39
|
+
/** Patterns that indicate Claude Code is running */
|
|
40
|
+
const CLAUDE_CODE_PATTERNS = [
|
|
41
|
+
/claude/i,
|
|
42
|
+
/anthropic/i,
|
|
43
|
+
/\$ claude/,
|
|
44
|
+
/claude code/i,
|
|
45
|
+
/conversation/i,
|
|
46
|
+
/assistant/i,
|
|
47
|
+
];
|
|
48
|
+
/** Patterns that indicate the pane is waiting for user input */
|
|
49
|
+
const WAITING_PATTERNS = [
|
|
50
|
+
/\[\d+\]/, // Menu selection prompt like [1], [2], [3]
|
|
51
|
+
/continue\?/i, // Continue prompt
|
|
52
|
+
/press enter/i,
|
|
53
|
+
/waiting for/i,
|
|
54
|
+
/select an option/i,
|
|
55
|
+
/choice:/i,
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Check if tmux is installed and available
|
|
59
|
+
*/
|
|
60
|
+
export function isTmuxAvailable() {
|
|
61
|
+
try {
|
|
62
|
+
const result = spawnSync('which', ['tmux'], {
|
|
63
|
+
encoding: 'utf-8',
|
|
64
|
+
timeout: 2000,
|
|
65
|
+
});
|
|
66
|
+
return result.status === 0 && result.stdout.trim().length > 0;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if currently running inside a tmux session
|
|
74
|
+
*/
|
|
75
|
+
export function isInsideTmux() {
|
|
76
|
+
return !!process.env.TMUX;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* List all tmux panes across all sessions
|
|
80
|
+
*/
|
|
81
|
+
export function listTmuxPanes() {
|
|
82
|
+
if (!isTmuxAvailable()) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
// Format: session_name:window_index.pane_index pane_id pane_active window_name pane_title
|
|
87
|
+
const format = '#{session_name}:#{window_index}.#{pane_index} #{pane_id} #{pane_active} #{window_name} #{pane_title}';
|
|
88
|
+
const result = execSync(`tmux list-panes -a -F '${format}'`, {
|
|
89
|
+
encoding: 'utf-8',
|
|
90
|
+
timeout: 5000,
|
|
91
|
+
});
|
|
92
|
+
const panes = [];
|
|
93
|
+
for (const line of result.trim().split('\n')) {
|
|
94
|
+
if (!line.trim())
|
|
95
|
+
continue;
|
|
96
|
+
const parts = line.split(' ');
|
|
97
|
+
if (parts.length < 4)
|
|
98
|
+
continue;
|
|
99
|
+
const [location, paneId, activeStr, windowName, ...titleParts] = parts;
|
|
100
|
+
const [sessionWindow, paneIndexStr] = location.split('.');
|
|
101
|
+
const [session, windowIndexStr] = sessionWindow.split(':');
|
|
102
|
+
panes.push({
|
|
103
|
+
id: paneId,
|
|
104
|
+
session,
|
|
105
|
+
windowIndex: parseInt(windowIndexStr, 10),
|
|
106
|
+
windowName,
|
|
107
|
+
paneIndex: parseInt(paneIndexStr, 10),
|
|
108
|
+
title: titleParts.join(' ') || undefined,
|
|
109
|
+
isActive: activeStr === '1',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return panes;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
console.error('[TmuxDetector] Error listing panes:', error);
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Capture the content of a specific tmux pane
|
|
121
|
+
*
|
|
122
|
+
* @param paneId - The tmux pane ID (e.g., "%0")
|
|
123
|
+
* @param lines - Number of lines to capture (default: 15)
|
|
124
|
+
*/
|
|
125
|
+
export function capturePaneContent(paneId, lines = 15) {
|
|
126
|
+
if (!isTmuxAvailable()) {
|
|
127
|
+
return '';
|
|
128
|
+
}
|
|
129
|
+
// Validate pane ID to prevent command injection
|
|
130
|
+
if (!isValidPaneId(paneId)) {
|
|
131
|
+
console.error(`[TmuxDetector] Invalid pane ID format: ${paneId}`);
|
|
132
|
+
return '';
|
|
133
|
+
}
|
|
134
|
+
// Validate lines is a reasonable positive integer
|
|
135
|
+
const safeLines = Math.max(1, Math.min(100, Math.floor(lines)));
|
|
136
|
+
try {
|
|
137
|
+
// Capture the last N lines from the pane
|
|
138
|
+
const result = execSync(`tmux capture-pane -t '${paneId}' -p -S -${safeLines}`, {
|
|
139
|
+
encoding: 'utf-8',
|
|
140
|
+
timeout: 5000,
|
|
141
|
+
});
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(`[TmuxDetector] Error capturing pane ${paneId}:`, error);
|
|
146
|
+
return '';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Analyze pane content to determine if it shows a rate-limited Claude Code session
|
|
151
|
+
*/
|
|
152
|
+
export function analyzePaneContent(content) {
|
|
153
|
+
if (!content.trim()) {
|
|
154
|
+
return {
|
|
155
|
+
hasClaudeCode: false,
|
|
156
|
+
hasRateLimitMessage: false,
|
|
157
|
+
isBlocked: false,
|
|
158
|
+
confidence: 0,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
// Check for Claude Code indicators
|
|
162
|
+
const hasClaudeCode = CLAUDE_CODE_PATTERNS.some((pattern) => pattern.test(content));
|
|
163
|
+
// Check for rate limit messages
|
|
164
|
+
const rateLimitMatches = RATE_LIMIT_PATTERNS.filter((pattern) => pattern.test(content));
|
|
165
|
+
const hasRateLimitMessage = rateLimitMatches.length > 0;
|
|
166
|
+
// Check if waiting for user input
|
|
167
|
+
const isWaiting = WAITING_PATTERNS.some((pattern) => pattern.test(content));
|
|
168
|
+
// Determine rate limit type
|
|
169
|
+
let rateLimitType;
|
|
170
|
+
if (hasRateLimitMessage) {
|
|
171
|
+
if (/5[- ]?hour/i.test(content)) {
|
|
172
|
+
rateLimitType = 'five_hour';
|
|
173
|
+
}
|
|
174
|
+
else if (/weekly/i.test(content)) {
|
|
175
|
+
rateLimitType = 'weekly';
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
rateLimitType = 'unknown';
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Calculate confidence
|
|
182
|
+
let confidence = 0;
|
|
183
|
+
if (hasClaudeCode)
|
|
184
|
+
confidence += 0.4;
|
|
185
|
+
if (hasRateLimitMessage)
|
|
186
|
+
confidence += 0.4;
|
|
187
|
+
if (isWaiting)
|
|
188
|
+
confidence += 0.2;
|
|
189
|
+
if (rateLimitMatches.length > 1)
|
|
190
|
+
confidence += 0.1; // Multiple matches = higher confidence
|
|
191
|
+
// Determine if blocked
|
|
192
|
+
const isBlocked = hasClaudeCode && hasRateLimitMessage && confidence >= 0.6;
|
|
193
|
+
return {
|
|
194
|
+
hasClaudeCode,
|
|
195
|
+
hasRateLimitMessage,
|
|
196
|
+
isBlocked,
|
|
197
|
+
rateLimitType,
|
|
198
|
+
confidence: Math.min(1, confidence),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Scan all tmux panes for blocked Claude Code sessions
|
|
203
|
+
*
|
|
204
|
+
* @param lines - Number of lines to capture from each pane
|
|
205
|
+
*/
|
|
206
|
+
export function scanForBlockedPanes(lines = 15) {
|
|
207
|
+
const panes = listTmuxPanes();
|
|
208
|
+
const blocked = [];
|
|
209
|
+
for (const pane of panes) {
|
|
210
|
+
const content = capturePaneContent(pane.id, lines);
|
|
211
|
+
const analysis = analyzePaneContent(content);
|
|
212
|
+
if (analysis.isBlocked) {
|
|
213
|
+
blocked.push({
|
|
214
|
+
...pane,
|
|
215
|
+
analysis,
|
|
216
|
+
firstDetectedAt: new Date(),
|
|
217
|
+
resumeAttempted: false,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return blocked;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Send resume sequence to a tmux pane
|
|
225
|
+
*
|
|
226
|
+
* This sends "1" followed by Enter to select the first option (usually "Continue"),
|
|
227
|
+
* then waits briefly and sends "continue" if needed.
|
|
228
|
+
*
|
|
229
|
+
* @param paneId - The tmux pane ID
|
|
230
|
+
* @returns Whether the command was sent successfully
|
|
231
|
+
*/
|
|
232
|
+
export function sendResumeSequence(paneId) {
|
|
233
|
+
if (!isTmuxAvailable()) {
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
// Validate pane ID to prevent command injection
|
|
237
|
+
if (!isValidPaneId(paneId)) {
|
|
238
|
+
console.error(`[TmuxDetector] Invalid pane ID format: ${paneId}`);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
// Send "1" to select the first option (typically "Continue" or similar)
|
|
243
|
+
execSync(`tmux send-keys -t '${paneId}' '1' Enter`, {
|
|
244
|
+
timeout: 2000,
|
|
245
|
+
});
|
|
246
|
+
// Wait a moment for the response
|
|
247
|
+
// Note: In real usage, we should verify the pane state changed
|
|
248
|
+
return true;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
console.error(`[TmuxDetector] Error sending resume to pane ${paneId}:`, error);
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Send custom text to a tmux pane
|
|
257
|
+
*/
|
|
258
|
+
export function sendToPane(paneId, text, pressEnter = true) {
|
|
259
|
+
if (!isTmuxAvailable()) {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
// Validate pane ID to prevent command injection
|
|
263
|
+
if (!isValidPaneId(paneId)) {
|
|
264
|
+
console.error(`[TmuxDetector] Invalid pane ID format: ${paneId}`);
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
try {
|
|
268
|
+
const sanitizedText = sanitizeForTmux(text);
|
|
269
|
+
const enterSuffix = pressEnter ? ' Enter' : '';
|
|
270
|
+
execSync(`tmux send-keys -t '${paneId}' '${sanitizedText}'${enterSuffix}`, {
|
|
271
|
+
timeout: 2000,
|
|
272
|
+
});
|
|
273
|
+
return true;
|
|
274
|
+
}
|
|
275
|
+
catch (error) {
|
|
276
|
+
console.error(`[TmuxDetector] Error sending to pane ${paneId}:`, error);
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Get a summary of blocked panes for display
|
|
282
|
+
*/
|
|
283
|
+
export function formatBlockedPanesSummary(blockedPanes) {
|
|
284
|
+
if (blockedPanes.length === 0) {
|
|
285
|
+
return 'No blocked Claude Code sessions detected.';
|
|
286
|
+
}
|
|
287
|
+
const lines = [
|
|
288
|
+
`Found ${blockedPanes.length} blocked Claude Code session(s):`,
|
|
289
|
+
'',
|
|
290
|
+
];
|
|
291
|
+
for (const pane of blockedPanes) {
|
|
292
|
+
const location = `${pane.session}:${pane.windowIndex}.${pane.paneIndex}`;
|
|
293
|
+
const confidence = Math.round(pane.analysis.confidence * 100);
|
|
294
|
+
const limitType = pane.analysis.rateLimitType || 'unknown';
|
|
295
|
+
const status = pane.resumeAttempted
|
|
296
|
+
? pane.resumeSuccessful
|
|
297
|
+
? ' [RESUMED]'
|
|
298
|
+
: ' [RESUME FAILED]'
|
|
299
|
+
: '';
|
|
300
|
+
lines.push(` • ${location} (${pane.id}) - ${limitType} limit, ${confidence}% confidence${status}`);
|
|
301
|
+
}
|
|
302
|
+
return lines.join('\n');
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=tmux-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tmux-detector.js","sourceRoot":"","sources":["../../../src/features/rate-limit-wait/tmux-detector.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limit Wait - Type Definitions
|
|
3
|
+
*
|
|
4
|
+
* Types for the rate limit auto-resume daemon.
|
|
5
|
+
* Reference: https://github.com/EvanOman/cc-wait
|
|
6
|
+
*/
|
|
7
|
+
export interface RateLimitStatus {
|
|
8
|
+
/** Whether rate limited on 5-hour window */
|
|
9
|
+
fiveHourLimited: boolean;
|
|
10
|
+
/** Whether rate limited on weekly window */
|
|
11
|
+
weeklyLimited: boolean;
|
|
12
|
+
/** Combined: true if either limit is hit */
|
|
13
|
+
isLimited: boolean;
|
|
14
|
+
/** When 5-hour limit resets */
|
|
15
|
+
fiveHourResetsAt: Date | null;
|
|
16
|
+
/** When weekly limit resets */
|
|
17
|
+
weeklyResetsAt: Date | null;
|
|
18
|
+
/** Earliest reset time */
|
|
19
|
+
nextResetAt: Date | null;
|
|
20
|
+
/** Time until reset in milliseconds */
|
|
21
|
+
timeUntilResetMs: number | null;
|
|
22
|
+
/** Last check timestamp */
|
|
23
|
+
lastCheckedAt: Date;
|
|
24
|
+
}
|
|
25
|
+
export interface TmuxPane {
|
|
26
|
+
/** Pane ID (e.g., "%0") */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Session name */
|
|
29
|
+
session: string;
|
|
30
|
+
/** Window index */
|
|
31
|
+
windowIndex: number;
|
|
32
|
+
/** Window name */
|
|
33
|
+
windowName: string;
|
|
34
|
+
/** Pane index within window */
|
|
35
|
+
paneIndex: number;
|
|
36
|
+
/** Pane title (if set) */
|
|
37
|
+
title?: string;
|
|
38
|
+
/** Whether this pane is currently active */
|
|
39
|
+
isActive: boolean;
|
|
40
|
+
}
|
|
41
|
+
export interface PaneAnalysisResult {
|
|
42
|
+
/** Whether this pane appears to have Claude Code */
|
|
43
|
+
hasClaudeCode: boolean;
|
|
44
|
+
/** Whether rate limit message is visible */
|
|
45
|
+
hasRateLimitMessage: boolean;
|
|
46
|
+
/** Whether the pane appears blocked (waiting for input) */
|
|
47
|
+
isBlocked: boolean;
|
|
48
|
+
/** Detected rate limit type if any */
|
|
49
|
+
rateLimitType?: 'five_hour' | 'weekly' | 'unknown';
|
|
50
|
+
/** Confidence level (0-1) */
|
|
51
|
+
confidence: number;
|
|
52
|
+
}
|
|
53
|
+
export interface BlockedPane extends TmuxPane {
|
|
54
|
+
/** Analysis result for this pane */
|
|
55
|
+
analysis: PaneAnalysisResult;
|
|
56
|
+
/** When this pane was first detected as blocked */
|
|
57
|
+
firstDetectedAt: Date;
|
|
58
|
+
/** Whether resume has been attempted */
|
|
59
|
+
resumeAttempted: boolean;
|
|
60
|
+
/** Whether resume was successful */
|
|
61
|
+
resumeSuccessful?: boolean;
|
|
62
|
+
}
|
|
63
|
+
export interface DaemonState {
|
|
64
|
+
/** Whether daemon is running */
|
|
65
|
+
isRunning: boolean;
|
|
66
|
+
/** Process ID if running */
|
|
67
|
+
pid: number | null;
|
|
68
|
+
/** When daemon started */
|
|
69
|
+
startedAt: Date | null;
|
|
70
|
+
/** Last poll timestamp */
|
|
71
|
+
lastPollAt: Date | null;
|
|
72
|
+
/** Current rate limit status */
|
|
73
|
+
rateLimitStatus: RateLimitStatus | null;
|
|
74
|
+
/** Currently tracked blocked panes */
|
|
75
|
+
blockedPanes: BlockedPane[];
|
|
76
|
+
/** Panes that have been resumed (to avoid re-sending) */
|
|
77
|
+
resumedPaneIds: string[];
|
|
78
|
+
/** Total resume attempts */
|
|
79
|
+
totalResumeAttempts: number;
|
|
80
|
+
/** Successful resume count */
|
|
81
|
+
successfulResumes: number;
|
|
82
|
+
/** Error count */
|
|
83
|
+
errorCount: number;
|
|
84
|
+
/** Last error message */
|
|
85
|
+
lastError?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface DaemonConfig {
|
|
88
|
+
/** Polling interval in milliseconds (default: 60000 = 1 minute) */
|
|
89
|
+
pollIntervalMs?: number;
|
|
90
|
+
/** Number of pane lines to capture for analysis (default: 15) */
|
|
91
|
+
paneLinesToCapture?: number;
|
|
92
|
+
/** Whether to log verbose output (default: false) */
|
|
93
|
+
verbose?: boolean;
|
|
94
|
+
/** State file path (default: ~/.omc/state/rate-limit-daemon.json) */
|
|
95
|
+
stateFilePath?: string;
|
|
96
|
+
/** PID file path (default: ~/.omc/state/rate-limit-daemon.pid) */
|
|
97
|
+
pidFilePath?: string;
|
|
98
|
+
/** Log file path (default: ~/.omc/state/rate-limit-daemon.log) */
|
|
99
|
+
logFilePath?: string;
|
|
100
|
+
}
|
|
101
|
+
export interface ResumeResult {
|
|
102
|
+
/** Pane ID */
|
|
103
|
+
paneId: string;
|
|
104
|
+
/** Whether resume was successful */
|
|
105
|
+
success: boolean;
|
|
106
|
+
/** Error message if failed */
|
|
107
|
+
error?: string;
|
|
108
|
+
/** Timestamp */
|
|
109
|
+
timestamp: Date;
|
|
110
|
+
}
|
|
111
|
+
export interface DaemonCommand {
|
|
112
|
+
action: 'start' | 'stop' | 'status' | 'detect';
|
|
113
|
+
options?: DaemonConfig;
|
|
114
|
+
}
|
|
115
|
+
export interface DaemonResponse {
|
|
116
|
+
success: boolean;
|
|
117
|
+
message: string;
|
|
118
|
+
state?: DaemonState;
|
|
119
|
+
error?: string;
|
|
120
|
+
}
|
|
121
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/features/rate-limit-wait/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/features/rate-limit-wait/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
package/dist/hooks/bridge.d.ts
CHANGED
|
@@ -54,7 +54,7 @@ export interface HookOutput {
|
|
|
54
54
|
/**
|
|
55
55
|
* Hook types that can be processed
|
|
56
56
|
*/
|
|
57
|
-
export type HookType = 'keyword-detector' | 'stop-continuation' | 'ralph' | 'persistent-mode' | 'session-start' | 'pre-tool-use' | 'post-tool-use' | 'autopilot';
|
|
57
|
+
export type HookType = 'keyword-detector' | 'stop-continuation' | 'ralph' | 'persistent-mode' | 'session-start' | 'session-end' | 'pre-tool-use' | 'post-tool-use' | 'autopilot' | 'subagent-start' | 'subagent-stop' | 'pre-compact' | 'setup-init' | 'setup-maintenance' | 'permission-request';
|
|
58
58
|
/**
|
|
59
59
|
* Main hook processor
|
|
60
60
|
* Routes to specific hook handler based on type
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/hooks/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/hooks/bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAuDH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,OAAO,CAAC,EAAE;QACR,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,4CAA4C;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IACH,iCAAiC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,wBAAwB;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAC;IAClB,8CAA8C;IAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+CAA+C;IAC/C,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAChB,kBAAkB,GAClB,mBAAmB,GACnB,OAAO,GACP,iBAAiB,GACjB,eAAe,GACf,aAAa,GACb,cAAc,GACd,eAAe,GACf,WAAW,GACX,gBAAgB,GAChB,eAAe,GACf,aAAa,GACb,YAAY,GACZ,mBAAmB,GACnB,oBAAoB,CAAC;AAsazB;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,QAAQ,EAClB,KAAK,EAAE,SAAS,GACf,OAAO,CAAC,UAAU,CAAC,CAiErB;AAED;;;GAGG;AACH,wBAAsB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CA+B1C"}
|