@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 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
- const { data: authUser } = await octokit.rest.users.getAuthenticated();
486
- if (authUser.login === opts.owner) {
487
- await octokit.rest.repos.createForAuthenticatedUser({
488
- name: opts.repo,
489
- description: opts.description,
490
- auto_init: false
491
- });
492
- } else {
493
- await octokit.rest.repos.createInOrg({
494
- org: opts.owner,
495
- name: opts.repo,
496
- description: opts.description,
497
- auto_init: false
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: ["HOME=/root"]
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
- const content = readFileSync2(credFile, "utf-8");
917
- const b64 = Buffer.from(content).toString("base64");
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
- log.debug("Copied .credentials.json");
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 execSync2 } from "child_process";
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 = execSync2(
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
- execSync2("git init", { cwd: scaffoldDir, stdio: "pipe", env });
1415
- execSync2("git checkout -b main", { cwd: scaffoldDir, stdio: "pipe", env });
1416
- execSync2('git config user.email "autoclawd@users.noreply.github.com"', { cwd: scaffoldDir, stdio: "pipe", env });
1417
- execSync2('git config user.name "autoclawd"', { cwd: scaffoldDir, stdio: "pipe", env });
1418
- execSync2("git add .", { cwd: scaffoldDir, stdio: "pipe", env });
1419
- execSync2('git commit -m "chore: initial scaffold"', { cwd: scaffoldDir, stdio: "pipe", env });
1420
- execSync2(`git remote add origin ${authedUrl}`, { cwd: scaffoldDir, stdio: "pipe", env });
1421
- execSync2("git push -u origin main", { cwd: scaffoldDir, stdio: "pipe", timeout: 6e4, env });
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
- execSync2(`git clone --depth=50 -b ${baseBranch} -- ${authedUrl} ${workDir}`, {
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
- workDir = await cloneToTemp(ticket.repoUrl, detectedBase, config.github.token);
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
- execSync2(`git checkout -B ${branchName} --`, { cwd: workDir, stdio: "pipe" });
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 execSync3 } from "child_process";
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: "Run: claude (and complete the login flow)"
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
- const credPaths = [
2431
- join8(homedir5(), ".claude", ".credentials.json"),
2432
- join8(homedir5(), ".claude", "credentials.json")
2433
- ];
2434
- if (credPaths.some((p) => existsSync6(p))) return true;
2435
- try {
2436
- execSync3("claude --version", { stdio: "pipe", timeout: 5e3 });
2437
- return existsSync6(join8(homedir5(), ".claude"));
2438
- } catch {
2439
- return false;
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
- execSync3(`which ${cmd}`, { stdio: "pipe" });
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
- execSync3("docker info", { stdio: "pipe", timeout: 1e4 });
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 execSync4, spawn as spawn2 } from "child_process";
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
- "Claude Code credentials not found (~/.claude/.credentials.json)\nRun: claude (and complete login) to create credentials"
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
- "Claude Code credentials not found (~/.claude/.credentials.json)\nRun: claude (and complete login) to create credentials"
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
- "Claude Code credentials not found (~/.claude/.credentials.json)\nRun: claude (and complete login) to create credentials"
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
- "Claude Code credentials not found (~/.claude/.credentials.json)\nRun: claude (and complete login) to create credentials"
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
- "Claude Code credentials not found (~/.claude/.credentials.json)\nRun: claude (and complete login) to create credentials"
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 (existsSync7(join9(home, ".claude")) && commandExistsSync("claude")) return true;
2868
- return false;
2869
- }
2870
- function commandExistsSync(cmd) {
2871
- try {
2872
- execSync4(`which ${cmd}`, { stdio: "pipe" });
2873
- return true;
2874
- } catch {
2875
- return false;
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 = execSync4("gh auth token", { encoding: "utf-8" }).trim();
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");