claude-threads 1.6.3 → 1.7.1
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/CHANGELOG.md +15 -0
- package/README.md +1 -0
- package/dist/index.js +598 -312
- package/dist/mcp/permission-server.js +144 -37
- package/docs/CONFIGURATION.md +27 -0
- package/package.json +1 -1
|
@@ -53500,6 +53500,69 @@ import { fileURLToPath as fileURLToPath3 } from "url";
|
|
|
53500
53500
|
import { existsSync as existsSync4, readFileSync as readFileSync3, watchFile, unwatchFile, unlinkSync, statSync, readdirSync } from "fs";
|
|
53501
53501
|
import { tmpdir } from "os";
|
|
53502
53502
|
import { join as join3 } from "path";
|
|
53503
|
+
|
|
53504
|
+
// src/claude/rate-limit-detector.ts
|
|
53505
|
+
var RATE_LIMIT_PHRASES = [
|
|
53506
|
+
/usage limit reached/i,
|
|
53507
|
+
/rate[_\s-]?limit[_\s-]?error/i,
|
|
53508
|
+
/you have hit the rate limit/i,
|
|
53509
|
+
/quota (has been )?exceeded/i,
|
|
53510
|
+
/\b429\b.*(rate|limit|quota)/i
|
|
53511
|
+
];
|
|
53512
|
+
var DEFAULT_COOLDOWN_MS = 60 * 60 * 1000;
|
|
53513
|
+
function detectRateLimit(text, now = Date.now()) {
|
|
53514
|
+
if (!text)
|
|
53515
|
+
return { detected: false };
|
|
53516
|
+
let matched;
|
|
53517
|
+
for (const phrase of RATE_LIMIT_PHRASES) {
|
|
53518
|
+
const m = text.match(phrase);
|
|
53519
|
+
if (m) {
|
|
53520
|
+
matched = m[0];
|
|
53521
|
+
break;
|
|
53522
|
+
}
|
|
53523
|
+
}
|
|
53524
|
+
if (!matched)
|
|
53525
|
+
return { detected: false };
|
|
53526
|
+
const resetAtEpochMs = extractResetAt(text, now);
|
|
53527
|
+
return { detected: true, matched, resetAtEpochMs };
|
|
53528
|
+
}
|
|
53529
|
+
function cooldownDeadline(hit, now = Date.now()) {
|
|
53530
|
+
if (!hit.detected)
|
|
53531
|
+
return now;
|
|
53532
|
+
return hit.resetAtEpochMs ?? now + DEFAULT_COOLDOWN_MS;
|
|
53533
|
+
}
|
|
53534
|
+
function extractResetAt(text, now) {
|
|
53535
|
+
const relative = text.match(/(?:retry[_\s-]?after|resets?\s+in)\s+(\d+)\s*(second|minute|hour|day)s?/i);
|
|
53536
|
+
if (relative) {
|
|
53537
|
+
const value = parseInt(relative[1], 10);
|
|
53538
|
+
const unit = relative[2].toLowerCase();
|
|
53539
|
+
const unitMs = {
|
|
53540
|
+
second: 1000,
|
|
53541
|
+
minute: 60000,
|
|
53542
|
+
hour: 3600000,
|
|
53543
|
+
day: 86400000
|
|
53544
|
+
};
|
|
53545
|
+
return now + value * unitMs[unit];
|
|
53546
|
+
}
|
|
53547
|
+
const unix = text.match(/\breset(?:_at)?\b\s*["']?\s*[:=]\s*(\d{10,13})/);
|
|
53548
|
+
if (unix) {
|
|
53549
|
+
const raw = parseInt(unix[1], 10);
|
|
53550
|
+
return unix[1].length === 13 ? raw : raw * 1000;
|
|
53551
|
+
}
|
|
53552
|
+
const clock = text.match(/resets?\s+at\s+(\d{1,2}):(\d{2})\s*(utc|gmt)?/i);
|
|
53553
|
+
if (clock) {
|
|
53554
|
+
const hh = parseInt(clock[1], 10);
|
|
53555
|
+
const mm = parseInt(clock[2], 10);
|
|
53556
|
+
if (hh < 24 && mm < 60) {
|
|
53557
|
+
const reference = new Date(now);
|
|
53558
|
+
const target = new Date(Date.UTC(reference.getUTCFullYear(), reference.getUTCMonth(), reference.getUTCDate(), hh, mm)).getTime();
|
|
53559
|
+
return target > now ? target : target + 86400000;
|
|
53560
|
+
}
|
|
53561
|
+
}
|
|
53562
|
+
return;
|
|
53563
|
+
}
|
|
53564
|
+
|
|
53565
|
+
// src/claude/cli.ts
|
|
53503
53566
|
var log7 = createLogger("claude");
|
|
53504
53567
|
function cleanupBrowserBridgeSockets() {
|
|
53505
53568
|
try {
|
|
@@ -53521,6 +53584,14 @@ function cleanupBrowserBridgeSockets() {
|
|
|
53521
53584
|
log7.debug(`Browser bridge cleanup failed: ${err}`);
|
|
53522
53585
|
}
|
|
53523
53586
|
}
|
|
53587
|
+
function isErrorResultEvent(event) {
|
|
53588
|
+
const ev = event;
|
|
53589
|
+
if (typeof ev.subtype === "string" && ev.subtype.startsWith("error"))
|
|
53590
|
+
return true;
|
|
53591
|
+
if (ev.is_error === true)
|
|
53592
|
+
return true;
|
|
53593
|
+
return false;
|
|
53594
|
+
}
|
|
53524
53595
|
|
|
53525
53596
|
class ClaudeCli extends EventEmitter2 {
|
|
53526
53597
|
process = null;
|
|
@@ -53530,6 +53601,7 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53530
53601
|
statusFilePath = null;
|
|
53531
53602
|
lastStatusData = null;
|
|
53532
53603
|
stderrBuffer = "";
|
|
53604
|
+
lastEmittedRateLimitDeadline = 0;
|
|
53533
53605
|
log;
|
|
53534
53606
|
constructor(options2) {
|
|
53535
53607
|
super();
|
|
@@ -53581,6 +53653,7 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53581
53653
|
if (this.process)
|
|
53582
53654
|
throw new Error("Already running");
|
|
53583
53655
|
this.stderrBuffer = "";
|
|
53656
|
+
this.lastEmittedRateLimitDeadline = 0;
|
|
53584
53657
|
cleanupBrowserBridgeSockets();
|
|
53585
53658
|
const claudePath = getClaudePath();
|
|
53586
53659
|
const args = [
|
|
@@ -53650,9 +53723,13 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53650
53723
|
args.push("--settings", JSON.stringify(statusLineSettings));
|
|
53651
53724
|
}
|
|
53652
53725
|
this.log.debug(`Starting: ${claudePath} ${args.slice(0, 5).join(" ")}...`);
|
|
53726
|
+
const childEnv = this.buildChildEnv();
|
|
53727
|
+
if (this.options.account) {
|
|
53728
|
+
this.log.debug(`Spawning under Claude account "${this.options.account.id}"`);
|
|
53729
|
+
}
|
|
53653
53730
|
this.process = crossSpawn(claudePath, args, {
|
|
53654
53731
|
cwd: this.options.workingDir,
|
|
53655
|
-
env:
|
|
53732
|
+
env: childEnv,
|
|
53656
53733
|
stdio: ["pipe", "pipe", "pipe"]
|
|
53657
53734
|
});
|
|
53658
53735
|
this.log.debug(`Claude process spawned: pid=${this.process.pid}`);
|
|
@@ -53666,6 +53743,7 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53666
53743
|
this.stderrBuffer = this.stderrBuffer.slice(-10240);
|
|
53667
53744
|
}
|
|
53668
53745
|
this.log.debug(`stderr: ${text.trim()}`);
|
|
53746
|
+
this.maybeEmitRateLimit(text);
|
|
53669
53747
|
});
|
|
53670
53748
|
this.process.on("error", (err) => {
|
|
53671
53749
|
this.log.error(`Claude error: ${err}`);
|
|
@@ -53720,9 +53798,24 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53720
53798
|
try {
|
|
53721
53799
|
const event = JSON.parse(trimmed);
|
|
53722
53800
|
this.emit("event", event);
|
|
53801
|
+
if (event.type === "result" && isErrorResultEvent(event)) {
|
|
53802
|
+
this.maybeEmitRateLimit(trimmed);
|
|
53803
|
+
}
|
|
53723
53804
|
} catch {}
|
|
53724
53805
|
}
|
|
53725
53806
|
}
|
|
53807
|
+
maybeEmitRateLimit(text) {
|
|
53808
|
+
const hit = detectRateLimit(text);
|
|
53809
|
+
if (!hit.detected)
|
|
53810
|
+
return;
|
|
53811
|
+
const newDeadline = cooldownDeadline(hit);
|
|
53812
|
+
const MIN_ADVANCE_MS = 60000;
|
|
53813
|
+
if (newDeadline - this.lastEmittedRateLimitDeadline < MIN_ADVANCE_MS)
|
|
53814
|
+
return;
|
|
53815
|
+
this.lastEmittedRateLimitDeadline = newDeadline;
|
|
53816
|
+
this.log.warn(`Rate limit detected: ${hit.matched ?? "(no match text)"}`);
|
|
53817
|
+
this.emit("rate-limit", hit);
|
|
53818
|
+
}
|
|
53726
53819
|
isRunning() {
|
|
53727
53820
|
return this.process !== null;
|
|
53728
53821
|
}
|
|
@@ -53791,6 +53884,22 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53791
53884
|
this.process.kill("SIGINT");
|
|
53792
53885
|
return true;
|
|
53793
53886
|
}
|
|
53887
|
+
buildChildEnv() {
|
|
53888
|
+
const account = this.options.account;
|
|
53889
|
+
if (!account)
|
|
53890
|
+
return process.env;
|
|
53891
|
+
const env = { ...process.env };
|
|
53892
|
+
if (account.home) {
|
|
53893
|
+
env.HOME = account.home;
|
|
53894
|
+
env.USERPROFILE = account.home;
|
|
53895
|
+
delete env.ANTHROPIC_API_KEY;
|
|
53896
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
53897
|
+
} else if (account.apiKey) {
|
|
53898
|
+
env.ANTHROPIC_API_KEY = account.apiKey;
|
|
53899
|
+
delete env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
53900
|
+
}
|
|
53901
|
+
return env;
|
|
53902
|
+
}
|
|
53794
53903
|
getMcpServerPath() {
|
|
53795
53904
|
const __filename2 = fileURLToPath3(import.meta.url);
|
|
53796
53905
|
const __dirname4 = dirname4(__filename2);
|
|
@@ -53811,42 +53920,6 @@ class ClaudeCli extends EventEmitter2 {
|
|
|
53811
53920
|
}
|
|
53812
53921
|
}
|
|
53813
53922
|
|
|
53814
|
-
// src/update-notifier.ts
|
|
53815
|
-
var import_semver2 = __toESM(require_semver2(), 1);
|
|
53816
|
-
|
|
53817
|
-
// src/operations/commands/handler.ts
|
|
53818
|
-
init_emoji();
|
|
53819
|
-
|
|
53820
|
-
// src/utils/error-handler/index.ts
|
|
53821
|
-
var log8 = createLogger("error");
|
|
53822
|
-
|
|
53823
|
-
// src/utils/session-log.ts
|
|
53824
|
-
function createSessionLog(baseLog) {
|
|
53825
|
-
return (session) => {
|
|
53826
|
-
if (session?.sessionId) {
|
|
53827
|
-
return baseLog.forSession(session.sessionId);
|
|
53828
|
-
}
|
|
53829
|
-
return baseLog;
|
|
53830
|
-
};
|
|
53831
|
-
}
|
|
53832
|
-
|
|
53833
|
-
// src/operations/post-helpers/index.ts
|
|
53834
|
-
init_emoji();
|
|
53835
|
-
|
|
53836
|
-
// src/git/worktree.ts
|
|
53837
|
-
import * as path from "path";
|
|
53838
|
-
import { homedir as homedir2 } from "os";
|
|
53839
|
-
var log9 = createLogger("git-wt");
|
|
53840
|
-
var WORKTREES_DIR = path.join(homedir2(), ".claude-threads", "worktrees");
|
|
53841
|
-
var METADATA_STORE_PATH = path.join(homedir2(), ".claude-threads", "worktree-metadata.json");
|
|
53842
|
-
|
|
53843
|
-
// src/operations/post-helpers/index.ts
|
|
53844
|
-
var log10 = createLogger("helpers");
|
|
53845
|
-
var sessionLog = createSessionLog(log10);
|
|
53846
|
-
|
|
53847
|
-
// src/claude/quick-query.ts
|
|
53848
|
-
var log11 = createLogger("query");
|
|
53849
|
-
|
|
53850
53923
|
// src/commands/registry.ts
|
|
53851
53924
|
var COMMAND_REGISTRY = [
|
|
53852
53925
|
{
|
|
@@ -54399,6 +54472,36 @@ ${avoidCommands.map((c) => `- \`!${c.command}\` - ${c.reason}`).join(`
|
|
|
54399
54472
|
`)}
|
|
54400
54473
|
`.trim();
|
|
54401
54474
|
}
|
|
54475
|
+
// src/utils/error-handler/index.ts
|
|
54476
|
+
var log8 = createLogger("error");
|
|
54477
|
+
|
|
54478
|
+
// src/utils/session-log.ts
|
|
54479
|
+
function createSessionLog(baseLog) {
|
|
54480
|
+
return (session) => {
|
|
54481
|
+
if (session?.sessionId) {
|
|
54482
|
+
return baseLog.forSession(session.sessionId);
|
|
54483
|
+
}
|
|
54484
|
+
return baseLog;
|
|
54485
|
+
};
|
|
54486
|
+
}
|
|
54487
|
+
|
|
54488
|
+
// src/operations/post-helpers/index.ts
|
|
54489
|
+
init_emoji();
|
|
54490
|
+
|
|
54491
|
+
// src/git/worktree.ts
|
|
54492
|
+
import * as path from "path";
|
|
54493
|
+
import { homedir as homedir2 } from "os";
|
|
54494
|
+
var log9 = createLogger("git-wt");
|
|
54495
|
+
var WORKTREES_DIR = path.join(homedir2(), ".claude-threads", "worktrees");
|
|
54496
|
+
var METADATA_STORE_PATH = path.join(homedir2(), ".claude-threads", "worktree-metadata.json");
|
|
54497
|
+
|
|
54498
|
+
// src/operations/post-helpers/index.ts
|
|
54499
|
+
var log10 = createLogger("helpers");
|
|
54500
|
+
var sessionLog = createSessionLog(log10);
|
|
54501
|
+
|
|
54502
|
+
// src/claude/quick-query.ts
|
|
54503
|
+
var log11 = createLogger("query");
|
|
54504
|
+
|
|
54402
54505
|
// src/operations/suggestions/title.ts
|
|
54403
54506
|
var log12 = createLogger("title");
|
|
54404
54507
|
|
|
@@ -54416,7 +54519,11 @@ var log15 = createLogger("lifecycle");
|
|
|
54416
54519
|
var sessionLog3 = createSessionLog(log15);
|
|
54417
54520
|
var CHAT_PLATFORM_PROMPT = generateChatPlatformPrompt();
|
|
54418
54521
|
|
|
54522
|
+
// src/update-notifier.ts
|
|
54523
|
+
var import_semver2 = __toESM(require_semver2(), 1);
|
|
54524
|
+
|
|
54419
54525
|
// src/operations/commands/handler.ts
|
|
54526
|
+
init_emoji();
|
|
54420
54527
|
var log16 = createLogger("commands");
|
|
54421
54528
|
var sessionLog4 = createSessionLog(log16);
|
|
54422
54529
|
// src/operations/suggestions/branch.ts
|
package/docs/CONFIGURATION.md
CHANGED
|
@@ -72,6 +72,33 @@ platforms:
|
|
|
72
72
|
| `allowedUsers` | No | List of Slack usernames |
|
|
73
73
|
| `skipPermissions` | No | Auto-approve actions (default: `false`) |
|
|
74
74
|
|
|
75
|
+
## Claude Accounts (optional, multi-account mode)
|
|
76
|
+
|
|
77
|
+
By default every session spawns `claude` with the bot's own `process.env`, so they all share one subscription's token budget. Add a `claudeAccounts` block to spread load across multiple accounts — the bot round-robins new sessions across the pool and automatically skips accounts in rate-limit cooldown. Omit the block entirely to stay in single-account mode (unchanged behavior).
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
claudeAccounts:
|
|
81
|
+
# OAuth accounts — prepare each HOME first with `HOME=<path> claude login`
|
|
82
|
+
- id: primary
|
|
83
|
+
home: /home/bot/.claude-accounts/primary
|
|
84
|
+
- id: backup
|
|
85
|
+
displayName: Backup (Pro)
|
|
86
|
+
home: /home/bot/.claude-accounts/backup
|
|
87
|
+
|
|
88
|
+
# API-key billed
|
|
89
|
+
- id: shared-api
|
|
90
|
+
apiKey: sk-ant-api03-xxxxxxxx...
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
| Setting | Required | Description |
|
|
94
|
+
|---------|----------|-------------|
|
|
95
|
+
| `id` | Yes | Stable identifier used in logs, UI, and persisted session state |
|
|
96
|
+
| `home` | One of | Alternate `$HOME` containing `.claude/.credentials.json` from a prior `HOME=<path> claude login`. For OAuth Pro/Max subscriptions. Session history also lives here, so resumed sessions pick the same account. |
|
|
97
|
+
| `apiKey` | One of | Anthropic API key. Billed against that key; session history stays under the bot's default `HOME`. |
|
|
98
|
+
| `displayName` | No | Human-readable label in UI (defaults to `id`) |
|
|
99
|
+
|
|
100
|
+
Exactly one of `home` or `apiKey` should be set per account. Persisted sessions record which account they ran under and resume on the same one.
|
|
101
|
+
|
|
75
102
|
## Environment Variables
|
|
76
103
|
|
|
77
104
|
| Variable | Description | Default |
|