@controlvector/cv-agent 1.3.0 → 1.4.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/dist/bundle.cjs +262 -21
- package/dist/bundle.cjs.map +4 -4
- package/dist/commands/agent.d.ts +29 -0
- package/dist/commands/agent.d.ts.map +1 -1
- package/dist/commands/agent.js +235 -10
- package/dist/commands/agent.js.map +1 -1
- package/dist/commands/auth.d.ts.map +1 -1
- package/dist/commands/auth.js +32 -0
- package/dist/commands/auth.js.map +1 -1
- package/dist/utils/api.d.ts +1 -1
- package/dist/utils/api.d.ts.map +1 -1
- package/dist/utils/api.js +5 -4
- package/dist/utils/api.js.map +1 -1
- package/dist/utils/config.d.ts +1 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js.map +1 -1
- package/package.json +1 -1
package/dist/bundle.cjs
CHANGED
|
@@ -960,7 +960,7 @@ var require_command = __commonJS({
|
|
|
960
960
|
var EventEmitter = require("node:events").EventEmitter;
|
|
961
961
|
var childProcess = require("node:child_process");
|
|
962
962
|
var path = require("node:path");
|
|
963
|
-
var
|
|
963
|
+
var fs3 = require("node:fs");
|
|
964
964
|
var process3 = require("node:process");
|
|
965
965
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
966
966
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1893,10 +1893,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1893
1893
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1894
1894
|
function findFile(baseDir, baseName) {
|
|
1895
1895
|
const localBin = path.resolve(baseDir, baseName);
|
|
1896
|
-
if (
|
|
1896
|
+
if (fs3.existsSync(localBin)) return localBin;
|
|
1897
1897
|
if (sourceExt.includes(path.extname(baseName))) return void 0;
|
|
1898
1898
|
const foundExt = sourceExt.find(
|
|
1899
|
-
(ext) =>
|
|
1899
|
+
(ext) => fs3.existsSync(`${localBin}${ext}`)
|
|
1900
1900
|
);
|
|
1901
1901
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1902
1902
|
return void 0;
|
|
@@ -1908,7 +1908,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1908
1908
|
if (this._scriptPath) {
|
|
1909
1909
|
let resolvedScriptPath;
|
|
1910
1910
|
try {
|
|
1911
|
-
resolvedScriptPath =
|
|
1911
|
+
resolvedScriptPath = fs3.realpathSync(this._scriptPath);
|
|
1912
1912
|
} catch (err) {
|
|
1913
1913
|
resolvedScriptPath = this._scriptPath;
|
|
1914
1914
|
}
|
|
@@ -3697,12 +3697,13 @@ async function failTask(creds, executorId, taskId, error) {
|
|
|
3697
3697
|
const res = await apiCall(creds, "POST", `/api/v1/executors/${executorId}/tasks/${taskId}/fail`, { error });
|
|
3698
3698
|
if (!res.ok) throw new Error(`Fail failed: ${res.status}`);
|
|
3699
3699
|
}
|
|
3700
|
-
async function sendHeartbeat(creds, executorId, taskId, message) {
|
|
3701
|
-
|
|
3700
|
+
async function sendHeartbeat(creds, executorId, taskId, message, authStatus2) {
|
|
3701
|
+
const body = authStatus2 ? { auth_status: authStatus2 } : void 0;
|
|
3702
|
+
await apiCall(creds, "POST", `/api/v1/executors/${executorId}/heartbeat`, body).catch(() => {
|
|
3702
3703
|
});
|
|
3703
3704
|
if (taskId) {
|
|
3704
|
-
const
|
|
3705
|
-
await apiCall(creds, "POST", `/api/v1/executors/${executorId}/tasks/${taskId}/heartbeat`,
|
|
3705
|
+
const taskBody = message ? { message, log_type: "heartbeat" } : void 0;
|
|
3706
|
+
await apiCall(creds, "POST", `/api/v1/executors/${executorId}/tasks/${taskId}/heartbeat`, taskBody).catch(() => {
|
|
3706
3707
|
});
|
|
3707
3708
|
}
|
|
3708
3709
|
}
|
|
@@ -4067,7 +4068,95 @@ ${source_default.yellow("\u26A0")} ${label} failed, retrying in ${delay}s... (${
|
|
|
4067
4068
|
throw new Error("unreachable");
|
|
4068
4069
|
}
|
|
4069
4070
|
|
|
4071
|
+
// src/utils/config.ts
|
|
4072
|
+
var import_fs2 = require("fs");
|
|
4073
|
+
var import_os2 = require("os");
|
|
4074
|
+
var import_path2 = require("path");
|
|
4075
|
+
function getConfigPath() {
|
|
4076
|
+
return (0, import_path2.join)((0, import_os2.homedir)(), ".config", "cva", "config.json");
|
|
4077
|
+
}
|
|
4078
|
+
async function readConfig() {
|
|
4079
|
+
try {
|
|
4080
|
+
const content = await import_fs2.promises.readFile(getConfigPath(), "utf-8");
|
|
4081
|
+
return JSON.parse(content);
|
|
4082
|
+
} catch {
|
|
4083
|
+
return {};
|
|
4084
|
+
}
|
|
4085
|
+
}
|
|
4086
|
+
async function writeConfig(config) {
|
|
4087
|
+
const configPath = getConfigPath();
|
|
4088
|
+
await import_fs2.promises.mkdir((0, import_path2.dirname)(configPath), { recursive: true });
|
|
4089
|
+
await import_fs2.promises.writeFile(configPath, JSON.stringify(config, null, 2) + "\n", { mode: 384 });
|
|
4090
|
+
}
|
|
4091
|
+
|
|
4070
4092
|
// src/commands/agent.ts
|
|
4093
|
+
var AUTH_ERROR_PATTERNS = [
|
|
4094
|
+
"Not logged in",
|
|
4095
|
+
"Please run /login",
|
|
4096
|
+
"authentication required",
|
|
4097
|
+
"unauthorized",
|
|
4098
|
+
"expired token",
|
|
4099
|
+
"not authenticated",
|
|
4100
|
+
"login required"
|
|
4101
|
+
];
|
|
4102
|
+
function containsAuthError(text) {
|
|
4103
|
+
const lower = text.toLowerCase();
|
|
4104
|
+
for (const pattern of AUTH_ERROR_PATTERNS) {
|
|
4105
|
+
if (lower.includes(pattern.toLowerCase())) {
|
|
4106
|
+
return pattern;
|
|
4107
|
+
}
|
|
4108
|
+
}
|
|
4109
|
+
return null;
|
|
4110
|
+
}
|
|
4111
|
+
async function checkClaudeAuth() {
|
|
4112
|
+
try {
|
|
4113
|
+
const output = (0, import_node_child_process2.execSync)("claude --version 2>&1", {
|
|
4114
|
+
encoding: "utf8",
|
|
4115
|
+
timeout: 1e4,
|
|
4116
|
+
env: { ...process.env }
|
|
4117
|
+
});
|
|
4118
|
+
const authError = containsAuthError(output);
|
|
4119
|
+
if (authError) {
|
|
4120
|
+
return { status: "expired", error: authError };
|
|
4121
|
+
}
|
|
4122
|
+
return { status: "authenticated" };
|
|
4123
|
+
} catch (err) {
|
|
4124
|
+
const output = (err.stdout || "") + (err.stderr || "") + (err.message || "");
|
|
4125
|
+
const authError = containsAuthError(output);
|
|
4126
|
+
if (authError) {
|
|
4127
|
+
return { status: "expired", error: authError };
|
|
4128
|
+
}
|
|
4129
|
+
return { status: "not_configured", error: output.slice(0, 500) };
|
|
4130
|
+
}
|
|
4131
|
+
}
|
|
4132
|
+
async function getClaudeEnv() {
|
|
4133
|
+
const env2 = { ...process.env };
|
|
4134
|
+
let usingApiKey = false;
|
|
4135
|
+
if (!env2.ANTHROPIC_API_KEY) {
|
|
4136
|
+
try {
|
|
4137
|
+
const config = await readConfig();
|
|
4138
|
+
if (config.anthropic_api_key) {
|
|
4139
|
+
env2.ANTHROPIC_API_KEY = config.anthropic_api_key;
|
|
4140
|
+
usingApiKey = true;
|
|
4141
|
+
}
|
|
4142
|
+
} catch {
|
|
4143
|
+
}
|
|
4144
|
+
}
|
|
4145
|
+
return { env: env2, usingApiKey };
|
|
4146
|
+
}
|
|
4147
|
+
function buildAuthFailureMessage(errorString, machineName, hasApiKeyFallback) {
|
|
4148
|
+
let msg = `CLAUDE_AUTH_REQUIRED: ${errorString}
|
|
4149
|
+
`;
|
|
4150
|
+
msg += `Machine: ${machineName}
|
|
4151
|
+
`;
|
|
4152
|
+
msg += `Fix: SSH into ${machineName} and run: claude /login
|
|
4153
|
+
`;
|
|
4154
|
+
if (!hasApiKeyFallback) {
|
|
4155
|
+
msg += `Alternative: Set an API key fallback with: cva auth set-api-key sk-ant-...
|
|
4156
|
+
`;
|
|
4157
|
+
}
|
|
4158
|
+
return msg;
|
|
4159
|
+
}
|
|
4071
4160
|
function buildClaudePrompt(task) {
|
|
4072
4161
|
let prompt = "";
|
|
4073
4162
|
prompt += `You are executing a task dispatched via CV-Hub.
|
|
@@ -4235,11 +4324,13 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
4235
4324
|
const child = (0, import_node_child_process2.spawn)("claude", args, {
|
|
4236
4325
|
cwd: options.cwd,
|
|
4237
4326
|
stdio: ["inherit", "pipe", "pipe"],
|
|
4238
|
-
env: { ...process.env }
|
|
4327
|
+
env: options.spawnEnv || { ...process.env }
|
|
4239
4328
|
});
|
|
4240
4329
|
_activeChild = child;
|
|
4241
4330
|
let stderr = "";
|
|
4242
4331
|
let lineBuffer = "";
|
|
4332
|
+
let authFailure = false;
|
|
4333
|
+
const spawnTime = Date.now();
|
|
4243
4334
|
child.stdout?.on("data", (data) => {
|
|
4244
4335
|
const text = data.toString();
|
|
4245
4336
|
process.stdout.write(data);
|
|
@@ -4247,6 +4338,23 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
4247
4338
|
if (fullOutput.length > MAX_OUTPUT_BYTES) {
|
|
4248
4339
|
fullOutput = fullOutput.slice(-MAX_OUTPUT_BYTES);
|
|
4249
4340
|
}
|
|
4341
|
+
if (Date.now() - spawnTime < 1e4) {
|
|
4342
|
+
const authError = containsAuthError(fullOutput + stderr);
|
|
4343
|
+
if (authError) {
|
|
4344
|
+
authFailure = true;
|
|
4345
|
+
console.log(`
|
|
4346
|
+
${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
|
|
4347
|
+
console.log(source_default.yellow(` Killing process \u2014 it won't recover without re-authentication.`));
|
|
4348
|
+
if (options.machineName) {
|
|
4349
|
+
console.log(source_default.cyan(` Fix: SSH into ${options.machineName} and run: claude /login`));
|
|
4350
|
+
}
|
|
4351
|
+
try {
|
|
4352
|
+
child.kill("SIGTERM");
|
|
4353
|
+
} catch {
|
|
4354
|
+
}
|
|
4355
|
+
return;
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4250
4358
|
if (options.creds && options.taskId) {
|
|
4251
4359
|
lineBuffer += text;
|
|
4252
4360
|
const lines = lineBuffer.split("\n");
|
|
@@ -4289,12 +4397,30 @@ async function launchAutoApproveMode(prompt, options) {
|
|
|
4289
4397
|
}
|
|
4290
4398
|
});
|
|
4291
4399
|
child.stderr?.on("data", (data) => {
|
|
4292
|
-
|
|
4400
|
+
const text = data.toString();
|
|
4401
|
+
stderr += text;
|
|
4293
4402
|
process.stderr.write(data);
|
|
4403
|
+
if (Date.now() - spawnTime < 1e4 && !authFailure) {
|
|
4404
|
+
const authError = containsAuthError(text);
|
|
4405
|
+
if (authError) {
|
|
4406
|
+
authFailure = true;
|
|
4407
|
+
console.log(`
|
|
4408
|
+
${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
4409
|
+
try {
|
|
4410
|
+
child.kill("SIGTERM");
|
|
4411
|
+
} catch {
|
|
4412
|
+
}
|
|
4413
|
+
}
|
|
4414
|
+
}
|
|
4294
4415
|
});
|
|
4295
4416
|
child.on("close", (code, signal) => {
|
|
4296
4417
|
_activeChild = null;
|
|
4297
|
-
resolve({
|
|
4418
|
+
resolve({
|
|
4419
|
+
exitCode: signal === "SIGKILL" ? 137 : code ?? 1,
|
|
4420
|
+
stderr,
|
|
4421
|
+
output: fullOutput,
|
|
4422
|
+
authFailure
|
|
4423
|
+
});
|
|
4298
4424
|
});
|
|
4299
4425
|
child.on("error", (err) => {
|
|
4300
4426
|
_activeChild = null;
|
|
@@ -4332,13 +4458,15 @@ async function launchRelayMode(prompt, options) {
|
|
|
4332
4458
|
const child = (0, import_node_child_process2.spawn)("claude", [], {
|
|
4333
4459
|
cwd: options.cwd,
|
|
4334
4460
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4335
|
-
env: { ...process.env }
|
|
4461
|
+
env: options.spawnEnv || { ...process.env }
|
|
4336
4462
|
});
|
|
4337
4463
|
_activeChild = child;
|
|
4338
4464
|
let stderr = "";
|
|
4339
4465
|
let stdoutBuffer = "";
|
|
4340
4466
|
let fullOutput = "";
|
|
4341
4467
|
let lastProgressBytes = 0;
|
|
4468
|
+
let authFailure = false;
|
|
4469
|
+
const spawnTime = Date.now();
|
|
4342
4470
|
child.stdin?.write(prompt + "\n");
|
|
4343
4471
|
let lastRedirectCheck = Date.now();
|
|
4344
4472
|
let lineBuffer = "";
|
|
@@ -4350,6 +4478,19 @@ async function launchRelayMode(prompt, options) {
|
|
|
4350
4478
|
if (fullOutput.length > MAX_OUTPUT_BYTES) {
|
|
4351
4479
|
fullOutput = fullOutput.slice(-MAX_OUTPUT_BYTES);
|
|
4352
4480
|
}
|
|
4481
|
+
if (Date.now() - spawnTime < 1e4 && !authFailure) {
|
|
4482
|
+
const authError = containsAuthError(fullOutput + stderr);
|
|
4483
|
+
if (authError) {
|
|
4484
|
+
authFailure = true;
|
|
4485
|
+
console.log(`
|
|
4486
|
+
${source_default.red("!")} Claude Code auth failure detected: "${authError}"`);
|
|
4487
|
+
try {
|
|
4488
|
+
child.kill("SIGTERM");
|
|
4489
|
+
} catch {
|
|
4490
|
+
}
|
|
4491
|
+
return;
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4353
4494
|
lineBuffer += text;
|
|
4354
4495
|
const lines = lineBuffer.split("\n");
|
|
4355
4496
|
lineBuffer = lines.pop() ?? "";
|
|
@@ -4490,13 +4631,25 @@ async function launchRelayMode(prompt, options) {
|
|
|
4490
4631
|
const text = data.toString();
|
|
4491
4632
|
stderr += text;
|
|
4492
4633
|
process.stderr.write(data);
|
|
4634
|
+
if (Date.now() - spawnTime < 1e4 && !authFailure) {
|
|
4635
|
+
const authError = containsAuthError(text);
|
|
4636
|
+
if (authError) {
|
|
4637
|
+
authFailure = true;
|
|
4638
|
+
console.log(`
|
|
4639
|
+
${source_default.red("!")} Claude Code auth failure (stderr): "${authError}"`);
|
|
4640
|
+
try {
|
|
4641
|
+
child.kill("SIGTERM");
|
|
4642
|
+
} catch {
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4493
4646
|
});
|
|
4494
4647
|
child.on("close", (code, signal) => {
|
|
4495
4648
|
_activeChild = null;
|
|
4496
4649
|
if (signal === "SIGKILL") {
|
|
4497
|
-
resolve({ exitCode: 137, stderr, output: fullOutput });
|
|
4650
|
+
resolve({ exitCode: 137, stderr, output: fullOutput, authFailure });
|
|
4498
4651
|
} else {
|
|
4499
|
-
resolve({ exitCode: code ?? 1, stderr, output: fullOutput });
|
|
4652
|
+
resolve({ exitCode: code ?? 1, stderr, output: fullOutput, authFailure });
|
|
4500
4653
|
}
|
|
4501
4654
|
});
|
|
4502
4655
|
child.on("error", (err) => {
|
|
@@ -4606,6 +4759,26 @@ async function runAgent(options) {
|
|
|
4606
4759
|
console.log();
|
|
4607
4760
|
process.exit(1);
|
|
4608
4761
|
}
|
|
4762
|
+
const { env: claudeEnv, usingApiKey } = await getClaudeEnv();
|
|
4763
|
+
const authCheck = await checkClaudeAuth();
|
|
4764
|
+
let currentAuthStatus = authCheck.status;
|
|
4765
|
+
if (authCheck.status === "expired") {
|
|
4766
|
+
if (usingApiKey) {
|
|
4767
|
+
console.log(source_default.yellow("!") + " Claude Code OAuth expired, but API key fallback is configured.");
|
|
4768
|
+
currentAuthStatus = "api_key_fallback";
|
|
4769
|
+
} else {
|
|
4770
|
+
console.log();
|
|
4771
|
+
console.log(source_default.red("Claude Code auth expired: ") + source_default.yellow(authCheck.error || "unknown"));
|
|
4772
|
+
console.log(` Fix: Run ${source_default.cyan("claude /login")} to re-authenticate.`);
|
|
4773
|
+
console.log(` Alternative: ${source_default.cyan("cva auth set-api-key sk-ant-...")} for API key fallback.`);
|
|
4774
|
+
console.log();
|
|
4775
|
+
console.log(source_default.gray("Agent will start but pause task claims until auth is resolved."));
|
|
4776
|
+
console.log();
|
|
4777
|
+
}
|
|
4778
|
+
} else if (usingApiKey) {
|
|
4779
|
+
console.log(source_default.gray(" Using Anthropic API key from config as fallback."));
|
|
4780
|
+
currentAuthStatus = "api_key_fallback";
|
|
4781
|
+
}
|
|
4609
4782
|
try {
|
|
4610
4783
|
(0, import_node_child_process2.execSync)("cv --version", { stdio: "pipe", timeout: 5e3 });
|
|
4611
4784
|
} catch {
|
|
@@ -4672,7 +4845,9 @@ async function runAgent(options) {
|
|
|
4672
4845
|
failedCount: 0,
|
|
4673
4846
|
lastPoll: Date.now(),
|
|
4674
4847
|
lastTaskEnd: Date.now(),
|
|
4675
|
-
running: true
|
|
4848
|
+
running: true,
|
|
4849
|
+
authStatus: currentAuthStatus,
|
|
4850
|
+
machineName
|
|
4676
4851
|
};
|
|
4677
4852
|
installSignalHandlers(
|
|
4678
4853
|
() => state,
|
|
@@ -4683,13 +4858,26 @@ async function runAgent(options) {
|
|
|
4683
4858
|
);
|
|
4684
4859
|
while (state.running) {
|
|
4685
4860
|
try {
|
|
4861
|
+
if (state.authStatus === "expired") {
|
|
4862
|
+
const recheck = await checkClaudeAuth();
|
|
4863
|
+
if (recheck.status === "authenticated") {
|
|
4864
|
+
state.authStatus = "authenticated";
|
|
4865
|
+
console.log(`
|
|
4866
|
+
${source_default.green("\u2713")} Claude Code auth restored. Resuming task claims.`);
|
|
4867
|
+
} else {
|
|
4868
|
+
sendHeartbeat(creds, state.executorId, void 0, void 0, state.authStatus).catch(() => {
|
|
4869
|
+
});
|
|
4870
|
+
await new Promise((r) => setTimeout(r, pollInterval));
|
|
4871
|
+
continue;
|
|
4872
|
+
}
|
|
4873
|
+
}
|
|
4686
4874
|
const task = await withRetry(
|
|
4687
4875
|
() => pollForTask(creds, state.executorId),
|
|
4688
4876
|
"Task poll"
|
|
4689
4877
|
);
|
|
4690
4878
|
state.lastPoll = Date.now();
|
|
4691
4879
|
if (task) {
|
|
4692
|
-
await executeTask(task, state, creds, options);
|
|
4880
|
+
await executeTask(task, state, creds, options, claudeEnv);
|
|
4693
4881
|
} else {
|
|
4694
4882
|
updateStatusLine(
|
|
4695
4883
|
formatDuration(Date.now() - state.lastTaskEnd),
|
|
@@ -4705,7 +4893,7 @@ ${source_default.red("!")} Error: ${err.message}`);
|
|
|
4705
4893
|
await new Promise((r) => setTimeout(r, pollInterval));
|
|
4706
4894
|
}
|
|
4707
4895
|
}
|
|
4708
|
-
async function executeTask(task, state, creds, options) {
|
|
4896
|
+
async function executeTask(task, state, creds, options, claudeEnv) {
|
|
4709
4897
|
const startTime = Date.now();
|
|
4710
4898
|
state.currentTaskId = task.id;
|
|
4711
4899
|
if (task.task_type === "_system_update") {
|
|
@@ -4733,7 +4921,7 @@ async function executeTask(task, state, creds, options) {
|
|
|
4733
4921
|
try {
|
|
4734
4922
|
const elapsed = formatDuration(Date.now() - startTime);
|
|
4735
4923
|
setTerminalTitle(`cva: ${task.title} (${elapsed})`);
|
|
4736
|
-
await sendHeartbeat(creds, state.executorId, task.id, `Claude Code running (${elapsed} elapsed)
|
|
4924
|
+
await sendHeartbeat(creds, state.executorId, task.id, `Claude Code running (${elapsed} elapsed)`, state.authStatus);
|
|
4737
4925
|
} catch {
|
|
4738
4926
|
}
|
|
4739
4927
|
}, 3e4);
|
|
@@ -4767,14 +4955,18 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
4767
4955
|
cwd: options.workingDir,
|
|
4768
4956
|
creds,
|
|
4769
4957
|
taskId: task.id,
|
|
4770
|
-
executorId: state.executorId
|
|
4958
|
+
executorId: state.executorId,
|
|
4959
|
+
spawnEnv: claudeEnv,
|
|
4960
|
+
machineName: state.machineName
|
|
4771
4961
|
});
|
|
4772
4962
|
} else {
|
|
4773
4963
|
result = await launchRelayMode(prompt, {
|
|
4774
4964
|
cwd: options.workingDir,
|
|
4775
4965
|
creds,
|
|
4776
4966
|
executorId: state.executorId,
|
|
4777
|
-
taskId: task.id
|
|
4967
|
+
taskId: task.id,
|
|
4968
|
+
spawnEnv: claudeEnv,
|
|
4969
|
+
machineName: state.machineName
|
|
4778
4970
|
});
|
|
4779
4971
|
}
|
|
4780
4972
|
console.log(source_default.gray("\n" + "-".repeat(60)));
|
|
@@ -4849,6 +5041,31 @@ ${source_default.red("Timeout")} Task timed out after ${formatDuration(timeoutMs
|
|
|
4849
5041
|
} catch {
|
|
4850
5042
|
}
|
|
4851
5043
|
state.failedCount++;
|
|
5044
|
+
} else if (result.authFailure) {
|
|
5045
|
+
const authErrorStr = containsAuthError(result.output + result.stderr) || "auth failure";
|
|
5046
|
+
const hasApiKey = !!claudeEnv?.ANTHROPIC_API_KEY;
|
|
5047
|
+
const authMsg = buildAuthFailureMessage(authErrorStr, state.machineName, hasApiKey);
|
|
5048
|
+
sendTaskLog(creds, state.executorId, task.id, "error", authMsg);
|
|
5049
|
+
console.log();
|
|
5050
|
+
console.log(`\u{1F511} ${source_default.bold.red("AUTH FAILED")} \u2014 Claude Code is not authenticated`);
|
|
5051
|
+
console.log(source_default.yellow(` ${authMsg.replace(/\n/g, "\n ")}`));
|
|
5052
|
+
postTaskEvent(creds, task.id, {
|
|
5053
|
+
event_type: "auth_failure",
|
|
5054
|
+
content: {
|
|
5055
|
+
error: authErrorStr,
|
|
5056
|
+
machine: state.machineName,
|
|
5057
|
+
fix_command: `claude /login`,
|
|
5058
|
+
api_key_configured: hasApiKey
|
|
5059
|
+
}
|
|
5060
|
+
}).catch(() => {
|
|
5061
|
+
});
|
|
5062
|
+
await withRetry(
|
|
5063
|
+
() => failTask(creds, state.executorId, task.id, authMsg),
|
|
5064
|
+
"Report auth failure"
|
|
5065
|
+
);
|
|
5066
|
+
state.failedCount++;
|
|
5067
|
+
state.authStatus = "expired";
|
|
5068
|
+
console.log(source_default.yellow(" Pausing task claims until auth is restored."));
|
|
4852
5069
|
} else {
|
|
4853
5070
|
const stderrTail = result.stderr.trim().slice(-500);
|
|
4854
5071
|
sendTaskLog(
|
|
@@ -4978,11 +5195,35 @@ async function authStatus() {
|
|
|
4978
5195
|
console.log(`Status: ${source_default.red("unreachable")} (${err.message})`);
|
|
4979
5196
|
}
|
|
4980
5197
|
}
|
|
5198
|
+
async function authSetApiKey(key) {
|
|
5199
|
+
if (!key.startsWith("sk-ant-")) {
|
|
5200
|
+
console.log(source_default.red("Invalid API key.") + " Anthropic API keys start with sk-ant-");
|
|
5201
|
+
process.exit(1);
|
|
5202
|
+
}
|
|
5203
|
+
const config = await readConfig();
|
|
5204
|
+
config.anthropic_api_key = key;
|
|
5205
|
+
await writeConfig(config);
|
|
5206
|
+
const masked = key.substring(0, 10) + "..." + key.slice(-4);
|
|
5207
|
+
console.log(source_default.green("API key saved") + ` (${masked})`);
|
|
5208
|
+
console.log(source_default.gray("This key will be used as fallback when Claude Code OAuth expires."));
|
|
5209
|
+
}
|
|
5210
|
+
async function authRemoveApiKey() {
|
|
5211
|
+
const config = await readConfig();
|
|
5212
|
+
if (!config.anthropic_api_key) {
|
|
5213
|
+
console.log(source_default.yellow("No API key configured."));
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
delete config.anthropic_api_key;
|
|
5217
|
+
await writeConfig(config);
|
|
5218
|
+
console.log(source_default.green("API key removed."));
|
|
5219
|
+
}
|
|
4981
5220
|
function authCommand() {
|
|
4982
5221
|
const cmd = new Command("auth");
|
|
4983
5222
|
cmd.description("Manage CV-Hub authentication");
|
|
4984
5223
|
cmd.command("login").description("Authenticate with CV-Hub using a PAT token").option("--token <token>", "PAT token (or enter interactively)").option("--api-url <url>", "CV-Hub API URL", "https://api.hub.controlvector.io").action(authLogin);
|
|
4985
5224
|
cmd.command("status").description("Show current authentication status").action(authStatus);
|
|
5225
|
+
cmd.command("set-api-key").description("Set Anthropic API key as fallback for Claude Code OAuth").argument("<key>", "Anthropic API key (sk-ant-...)").action(authSetApiKey);
|
|
5226
|
+
cmd.command("remove-api-key").description("Remove stored Anthropic API key").action(authRemoveApiKey);
|
|
4986
5227
|
return cmd;
|
|
4987
5228
|
}
|
|
4988
5229
|
|
|
@@ -5203,7 +5444,7 @@ function statusCommand() {
|
|
|
5203
5444
|
|
|
5204
5445
|
// src/index.ts
|
|
5205
5446
|
var program2 = new Command();
|
|
5206
|
-
program2.name("cva").description("CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch").version(true ? "1.
|
|
5447
|
+
program2.name("cva").description("CV-Hub Agent \u2014 bridges Claude Code with CV-Hub task dispatch").version(true ? "1.4.0" : "1.1.0");
|
|
5207
5448
|
program2.addCommand(agentCommand());
|
|
5208
5449
|
program2.addCommand(authCommand());
|
|
5209
5450
|
program2.addCommand(remoteCommand());
|