@autoclawd/autoclawd 1.1.4 → 1.1.6
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/index.js +113 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -482,20 +482,29 @@ async function ensureRepoExists(octokit, opts) {
|
|
|
482
482
|
if (status !== 404) throw err;
|
|
483
483
|
}
|
|
484
484
|
log.info(`Repo ${opts.owner}/${opts.repo} does not exist, creating...`);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
485
|
+
try {
|
|
486
|
+
const { data: authUser } = await octokit.rest.users.getAuthenticated();
|
|
487
|
+
if (authUser.login === opts.owner) {
|
|
488
|
+
await octokit.rest.repos.createForAuthenticatedUser({
|
|
489
|
+
name: opts.repo,
|
|
490
|
+
description: opts.description,
|
|
491
|
+
auto_init: false
|
|
492
|
+
});
|
|
493
|
+
} else {
|
|
494
|
+
await octokit.rest.repos.createInOrg({
|
|
495
|
+
org: opts.owner,
|
|
496
|
+
name: opts.repo,
|
|
497
|
+
description: opts.description,
|
|
498
|
+
auto_init: false
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
} catch (err) {
|
|
502
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
503
|
+
if (msg.includes("name already exists")) {
|
|
504
|
+
log.info(`Repo ${opts.owner}/${opts.repo} was created by another ticket, continuing`);
|
|
505
|
+
return { created: false };
|
|
506
|
+
}
|
|
507
|
+
throw err;
|
|
499
508
|
}
|
|
500
509
|
return { created: true };
|
|
501
510
|
}
|
|
@@ -712,6 +721,7 @@ import { PassThrough } from "stream";
|
|
|
712
721
|
import { homedir as homedir3, platform } from "os";
|
|
713
722
|
import { join as join4 } from "path";
|
|
714
723
|
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
724
|
+
import { execSync as execSync2 } from "child_process";
|
|
715
725
|
var docker = new Docker();
|
|
716
726
|
async function checkDockerAvailable() {
|
|
717
727
|
try {
|
|
@@ -770,7 +780,10 @@ async function createContainer(opts) {
|
|
|
770
780
|
NanoCpus: dockerConfig.cpus ? dockerConfig.cpus * 1e9 : void 0,
|
|
771
781
|
NetworkMode: dockerConfig.network ?? "bridge"
|
|
772
782
|
},
|
|
773
|
-
Env: [
|
|
783
|
+
Env: [
|
|
784
|
+
"HOME=/root",
|
|
785
|
+
...process.env.ANTHROPIC_API_KEY ? [`ANTHROPIC_API_KEY=${process.env.ANTHROPIC_API_KEY}`] : []
|
|
786
|
+
]
|
|
774
787
|
});
|
|
775
788
|
await container.start();
|
|
776
789
|
log.info(`Container ${name} started (${container.id.slice(0, 12)})`);
|
|
@@ -911,16 +924,33 @@ async function copyClaudeCredentials(container) {
|
|
|
911
924
|
"-c",
|
|
912
925
|
"mkdir -p /home/autoclawd/.claude && chown autoclawd /home/autoclawd/.claude"
|
|
913
926
|
]);
|
|
927
|
+
let credJson;
|
|
914
928
|
const credFile = join4(home, ".claude", ".credentials.json");
|
|
915
929
|
if (existsSync3(credFile)) {
|
|
916
|
-
|
|
917
|
-
|
|
930
|
+
credJson = readFileSync2(credFile, "utf-8");
|
|
931
|
+
log.debug("Read credentials from file");
|
|
932
|
+
} else if (platform() === "darwin") {
|
|
933
|
+
try {
|
|
934
|
+
credJson = execSync2('security find-generic-password -s "Claude Code-credentials" -w', {
|
|
935
|
+
stdio: "pipe",
|
|
936
|
+
encoding: "utf-8",
|
|
937
|
+
timeout: 5e3
|
|
938
|
+
}).trim();
|
|
939
|
+
log.debug("Read credentials from macOS Keychain");
|
|
940
|
+
} catch {
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
if (credJson) {
|
|
944
|
+
const b64 = Buffer.from(credJson).toString("base64");
|
|
918
945
|
await exec(container, [
|
|
919
946
|
"sh",
|
|
920
947
|
"-c",
|
|
921
948
|
`echo '${b64}' | base64 -d > /home/autoclawd/.claude/.credentials.json && chown autoclawd /home/autoclawd/.claude/.credentials.json`
|
|
922
949
|
]);
|
|
923
|
-
|
|
950
|
+
} else if (process.env.ANTHROPIC_API_KEY) {
|
|
951
|
+
log.debug("Using ANTHROPIC_API_KEY env var for container auth");
|
|
952
|
+
} else {
|
|
953
|
+
log.warn("No Claude credentials found. Run: claude (to log in)");
|
|
924
954
|
}
|
|
925
955
|
const settingsFile = join4(home, ".claude", "settings.json");
|
|
926
956
|
if (existsSync3(settingsFile)) {
|
|
@@ -1199,7 +1229,7 @@ async function commitAndPush(container, opts) {
|
|
|
1199
1229
|
import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1200
1230
|
import { join as join6 } from "path";
|
|
1201
1231
|
import { tmpdir as tmpdir2 } from "os";
|
|
1202
|
-
import { execSync as
|
|
1232
|
+
import { execSync as execSync3 } from "child_process";
|
|
1203
1233
|
|
|
1204
1234
|
// src/db.ts
|
|
1205
1235
|
import Database from "better-sqlite3";
|
|
@@ -1387,7 +1417,7 @@ function detectDefaultBranch2(repoUrl, githubToken) {
|
|
|
1387
1417
|
const askpass = createAskpass2(githubToken);
|
|
1388
1418
|
const authedUrl = repoUrl.replace("https://", "https://x-access-token@");
|
|
1389
1419
|
try {
|
|
1390
|
-
const output =
|
|
1420
|
+
const output = execSync3(
|
|
1391
1421
|
`git ls-remote --symref -- ${authedUrl} HEAD`,
|
|
1392
1422
|
{ stdio: "pipe", timeout: 3e4, env: gitEnv2(askpass.path), encoding: "utf-8" }
|
|
1393
1423
|
);
|
|
@@ -1411,14 +1441,14 @@ async function pushScaffold(opts) {
|
|
|
1411
1441
|
}
|
|
1412
1442
|
writeFileSync2(join6(scaffoldDir, "README.md"), readmeLines.join("\n") + "\n");
|
|
1413
1443
|
writeFileSync2(join6(scaffoldDir, ".autoclawd.yaml"), "base: main\n");
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1444
|
+
execSync3("git init", { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1445
|
+
execSync3("git checkout -b main", { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1446
|
+
execSync3('git config user.email "autoclawd@users.noreply.github.com"', { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1447
|
+
execSync3('git config user.name "autoclawd"', { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1448
|
+
execSync3("git add .", { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1449
|
+
execSync3('git commit -m "chore: initial scaffold"', { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1450
|
+
execSync3(`git remote add origin ${authedUrl}`, { cwd: scaffoldDir, stdio: "pipe", env });
|
|
1451
|
+
execSync3("git push -u origin main", { cwd: scaffoldDir, stdio: "pipe", timeout: 6e4, env });
|
|
1422
1452
|
} finally {
|
|
1423
1453
|
rmSync2(scaffoldDir, { recursive: true, force: true });
|
|
1424
1454
|
rmSync2(askpass.dir, { recursive: true, force: true });
|
|
@@ -1431,7 +1461,7 @@ async function cloneToTemp(repoUrl, baseBranch, githubToken) {
|
|
|
1431
1461
|
const askpass = createAskpass2(githubToken);
|
|
1432
1462
|
const authedUrl = repoUrl.replace("https://", "https://x-access-token@");
|
|
1433
1463
|
try {
|
|
1434
|
-
|
|
1464
|
+
execSync3(`git clone --depth=50 -b ${baseBranch} -- ${authedUrl} ${workDir}`, {
|
|
1435
1465
|
stdio: "pipe",
|
|
1436
1466
|
timeout: 12e4,
|
|
1437
1467
|
env: gitEnv2(askpass.path)
|
|
@@ -1579,7 +1609,21 @@ async function executeTicket(opts) {
|
|
|
1579
1609
|
}
|
|
1580
1610
|
const detectedBase = repoCreated ? "main" : detectDefaultBranch2(ticket.repoUrl, config.github.token);
|
|
1581
1611
|
log.ticket(ticket.identifier, `Default branch: ${detectedBase}`);
|
|
1582
|
-
|
|
1612
|
+
try {
|
|
1613
|
+
workDir = await cloneToTemp(ticket.repoUrl, detectedBase, config.github.token);
|
|
1614
|
+
} catch (err) {
|
|
1615
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1616
|
+
if (msg.includes("not found in upstream") || msg.includes("Could not find remote branch") || msg.includes("Remote branch")) {
|
|
1617
|
+
log.ticket(ticket.identifier, `Branch "${detectedBase}" not ready \u2014 will retry later`);
|
|
1618
|
+
return {
|
|
1619
|
+
ticketId: ticket.identifier,
|
|
1620
|
+
success: false,
|
|
1621
|
+
error: `REQUEUE: branch "${detectedBase}" does not exist yet`,
|
|
1622
|
+
iterations: 0
|
|
1623
|
+
};
|
|
1624
|
+
}
|
|
1625
|
+
throw err;
|
|
1626
|
+
}
|
|
1583
1627
|
log.ticket(ticket.identifier, "Cloned repo");
|
|
1584
1628
|
const repoLocal = loadRepoLocalConfig(workDir);
|
|
1585
1629
|
const actualBase = ticket.baseBranch ?? repoLocal?.base ?? detectedBase;
|
|
@@ -1608,7 +1652,7 @@ async function executeTicket(opts) {
|
|
|
1608
1652
|
throw err;
|
|
1609
1653
|
}
|
|
1610
1654
|
}
|
|
1611
|
-
|
|
1655
|
+
execSync3(`git checkout -B ${branchName} --`, { cwd: workDir, stdio: "pipe" });
|
|
1612
1656
|
container = await createContainer({
|
|
1613
1657
|
dockerConfig: docker2,
|
|
1614
1658
|
workspacePath: workDir,
|
|
@@ -2397,7 +2441,7 @@ function printHistoryTable(records) {
|
|
|
2397
2441
|
}
|
|
2398
2442
|
|
|
2399
2443
|
// src/deps.ts
|
|
2400
|
-
import { execSync as
|
|
2444
|
+
import { execSync as execSync4 } from "child_process";
|
|
2401
2445
|
import { existsSync as existsSync6 } from "fs";
|
|
2402
2446
|
import { join as join8 } from "path";
|
|
2403
2447
|
import { homedir as homedir5 } from "os";
|
|
@@ -2417,7 +2461,7 @@ function checkDeps() {
|
|
|
2417
2461
|
{
|
|
2418
2462
|
name: "Claude credentials",
|
|
2419
2463
|
installed: hasClaudeAuth(),
|
|
2420
|
-
instructions: "
|
|
2464
|
+
instructions: 'export ANTHROPIC_API_KEY="sk-ant-..." (from console.anthropic.com)\n Or run: claude (and complete the login flow)'
|
|
2421
2465
|
},
|
|
2422
2466
|
{
|
|
2423
2467
|
name: "cloudflared",
|
|
@@ -2427,21 +2471,26 @@ function checkDeps() {
|
|
|
2427
2471
|
];
|
|
2428
2472
|
}
|
|
2429
2473
|
function hasClaudeAuth() {
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
if (
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2474
|
+
if (process.env.ANTHROPIC_API_KEY) return true;
|
|
2475
|
+
const home = homedir5();
|
|
2476
|
+
if (existsSync6(join8(home, ".claude", ".credentials.json"))) return true;
|
|
2477
|
+
if (existsSync6(join8(home, ".claude", "credentials.json"))) return true;
|
|
2478
|
+
if (process.platform === "darwin") {
|
|
2479
|
+
try {
|
|
2480
|
+
const result = execSync4('security find-generic-password -s "Claude Code-credentials" -w', {
|
|
2481
|
+
stdio: "pipe",
|
|
2482
|
+
encoding: "utf-8",
|
|
2483
|
+
timeout: 5e3
|
|
2484
|
+
});
|
|
2485
|
+
if (result.trim()) return true;
|
|
2486
|
+
} catch {
|
|
2487
|
+
}
|
|
2440
2488
|
}
|
|
2489
|
+
return false;
|
|
2441
2490
|
}
|
|
2442
2491
|
function commandExists(cmd) {
|
|
2443
2492
|
try {
|
|
2444
|
-
|
|
2493
|
+
execSync4(`which ${cmd}`, { stdio: "pipe" });
|
|
2445
2494
|
return true;
|
|
2446
2495
|
} catch {
|
|
2447
2496
|
return false;
|
|
@@ -2449,7 +2498,7 @@ function commandExists(cmd) {
|
|
|
2449
2498
|
}
|
|
2450
2499
|
function isDockerRunning() {
|
|
2451
2500
|
try {
|
|
2452
|
-
|
|
2501
|
+
execSync4("docker info", { stdio: "pipe", timeout: 1e4 });
|
|
2453
2502
|
return true;
|
|
2454
2503
|
} catch {
|
|
2455
2504
|
return false;
|
|
@@ -2458,7 +2507,7 @@ function isDockerRunning() {
|
|
|
2458
2507
|
|
|
2459
2508
|
// src/index.ts
|
|
2460
2509
|
import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
2461
|
-
import { execSync as
|
|
2510
|
+
import { execSync as execSync5, spawn as spawn2 } from "child_process";
|
|
2462
2511
|
import { createInterface } from "readline";
|
|
2463
2512
|
import { join as join9 } from "path";
|
|
2464
2513
|
import { homedir as homedir6 } from "os";
|
|
@@ -2516,7 +2565,7 @@ program.command("serve").description("Start webhook server with auto-tunnel").op
|
|
|
2516
2565
|
await checkDockerAvailable();
|
|
2517
2566
|
if (!checkClaudeCredentials()) {
|
|
2518
2567
|
throw new Error(
|
|
2519
|
-
|
|
2568
|
+
'Claude Code credentials not found.\nEither: export ANTHROPIC_API_KEY="sk-ant-..." (recommended for macOS)\nOr: run claude (and complete login) to create ~/.claude/.credentials.json'
|
|
2520
2569
|
);
|
|
2521
2570
|
}
|
|
2522
2571
|
const orphans = await cleanupOrphanedContainers();
|
|
@@ -2582,7 +2631,7 @@ program.command("watch").description("Poll Linear for tickets (no webhook/tunnel
|
|
|
2582
2631
|
await checkDockerAvailable();
|
|
2583
2632
|
if (!checkClaudeCredentials()) {
|
|
2584
2633
|
throw new Error(
|
|
2585
|
-
|
|
2634
|
+
'Claude Code credentials not found.\nEither: export ANTHROPIC_API_KEY="sk-ant-..." (recommended for macOS)\nOr: run claude (and complete login) to create ~/.claude/.credentials.json'
|
|
2586
2635
|
);
|
|
2587
2636
|
}
|
|
2588
2637
|
const orphans = await cleanupOrphanedContainers();
|
|
@@ -2655,7 +2704,7 @@ program.command("run <ticket>").description("Run a single ticket (e.g. autoclawd
|
|
|
2655
2704
|
await checkDockerAvailable();
|
|
2656
2705
|
if (!checkClaudeCredentials()) {
|
|
2657
2706
|
throw new Error(
|
|
2658
|
-
|
|
2707
|
+
'Claude Code credentials not found.\nEither: export ANTHROPIC_API_KEY="sk-ant-..." (recommended for macOS)\nOr: run claude (and complete login) to create ~/.claude/.credentials.json'
|
|
2659
2708
|
);
|
|
2660
2709
|
}
|
|
2661
2710
|
const octokit = createOctokit(config);
|
|
@@ -2689,7 +2738,7 @@ Expected: https://github.com/owner/repo/pull/123`);
|
|
|
2689
2738
|
await checkDockerAvailable();
|
|
2690
2739
|
if (!checkClaudeCredentials()) {
|
|
2691
2740
|
throw new Error(
|
|
2692
|
-
|
|
2741
|
+
'Claude Code credentials not found.\nEither: export ANTHROPIC_API_KEY="sk-ant-..." (recommended for macOS)\nOr: run claude (and complete login) to create ~/.claude/.credentials.json'
|
|
2693
2742
|
);
|
|
2694
2743
|
}
|
|
2695
2744
|
const octokit = createOctokit(config);
|
|
@@ -2710,7 +2759,7 @@ program.command("retry [ticket]").description("Retry a failed ticket (e.g. autoc
|
|
|
2710
2759
|
await checkDockerAvailable();
|
|
2711
2760
|
if (!checkClaudeCredentials()) {
|
|
2712
2761
|
throw new Error(
|
|
2713
|
-
|
|
2762
|
+
'Claude Code credentials not found.\nEither: export ANTHROPIC_API_KEY="sk-ant-..." (recommended for macOS)\nOr: run claude (and complete login) to create ~/.claude/.credentials.json'
|
|
2714
2763
|
);
|
|
2715
2764
|
}
|
|
2716
2765
|
const linearClient = createLinearClient(config);
|
|
@@ -2861,19 +2910,22 @@ program.command("history").description("Show run history").option("-n, --limit <
|
|
|
2861
2910
|
}
|
|
2862
2911
|
});
|
|
2863
2912
|
function checkClaudeCredentials() {
|
|
2913
|
+
if (process.env.ANTHROPIC_API_KEY) return true;
|
|
2864
2914
|
const home = homedir6();
|
|
2865
2915
|
if (existsSync7(join9(home, ".claude", ".credentials.json"))) return true;
|
|
2866
2916
|
if (existsSync7(join9(home, ".claude", "credentials.json"))) return true;
|
|
2867
|
-
if (
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2917
|
+
if (process.platform === "darwin") {
|
|
2918
|
+
try {
|
|
2919
|
+
const result = execSync5('security find-generic-password -s "Claude Code-credentials" -w', {
|
|
2920
|
+
stdio: "pipe",
|
|
2921
|
+
encoding: "utf-8",
|
|
2922
|
+
timeout: 5e3
|
|
2923
|
+
});
|
|
2924
|
+
if (result.trim()) return true;
|
|
2925
|
+
} catch {
|
|
2926
|
+
}
|
|
2876
2927
|
}
|
|
2928
|
+
return false;
|
|
2877
2929
|
}
|
|
2878
2930
|
async function validateGitHubToken(token) {
|
|
2879
2931
|
try {
|
|
@@ -2947,7 +2999,7 @@ program.command("init").description("Set up autoclawd interactively").action(asy
|
|
|
2947
2999
|
}
|
|
2948
3000
|
let ghToken = "";
|
|
2949
3001
|
try {
|
|
2950
|
-
ghToken =
|
|
3002
|
+
ghToken = execSync5("gh auth token", { encoding: "utf-8" }).trim();
|
|
2951
3003
|
log.success(`GitHub token detected from gh CLI`);
|
|
2952
3004
|
} catch {
|
|
2953
3005
|
ghToken = await ask("GitHub personal access token");
|