@autoclawd/autoclawd 1.0.0 → 1.0.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/Dockerfile CHANGED
@@ -1,18 +1,15 @@
1
- # autoclawd base image — all common runtimes for Claude Code
2
- # Use this as docker.image in your config for zero-setup multi-language support
1
+ # autoclawd Docker image — multi-runtime environment for Claude Code
3
2
  #
4
- # Build: docker build -t autoclawd-base .
5
- # Config: docker: { image: autoclawd-base }
3
+ # Build: docker build -t autoclawd .
4
+ # Config: docker: { image: autoclawd }
6
5
  #
7
- # Includes: Node.js 20, Python 3, Go, Rust, Ruby, Git, common build tools
8
- # Size: ~1.5 GB (trades disk for zero-config developer experience)
6
+ # Includes: Node.js 20, Python 3, Go, Ruby, Git, common build tools
7
+ # Claude Code is auto-installed by autoclawd at runtime — not baked in
9
8
 
10
9
  FROM node:20-bookworm
11
10
 
12
- # Avoid interactive prompts during package installation
13
11
  ENV DEBIAN_FRONTEND=noninteractive
14
12
 
15
- # Common build tools + git (already in node:20 but be explicit)
16
13
  RUN apt-get update && apt-get install -y --no-install-recommends \
17
14
  build-essential \
18
15
  git \
@@ -22,15 +19,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
22
19
  openssh-client \
23
20
  jq \
24
21
  unzip \
25
- # Python
26
22
  python3 \
27
23
  python3-pip \
28
24
  python3-venv \
29
25
  python3-dev \
30
- # Ruby
31
26
  ruby \
32
27
  ruby-dev \
33
- # Go (via official tarball for latest stable)
34
28
  && rm -rf /var/lib/apt/lists/*
35
29
 
36
30
  # Install Go (bookworm packages are often outdated)
@@ -39,19 +33,15 @@ RUN curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-$(dpkg --print-architect
39
33
  | tar -xz -C /usr/local
40
34
  ENV PATH="/usr/local/go/bin:${PATH}"
41
35
 
42
- # Rust is omitted by default (~1.5GB). For Rust repos, use docker.setup:
36
+ # Rust is omitted (~1.5GB). For Rust repos, use docker.setup:
43
37
  # docker:
44
38
  # setup:
45
39
  # - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
46
40
 
47
- # Pre-install Claude Code globally
48
- RUN npm install -g @anthropic-ai/claude-code@latest
49
-
50
41
  # Verify installations
51
42
  RUN node --version && python3 --version && go version && ruby --version && git --version
52
43
 
53
- # Workspace directory
54
44
  WORKDIR /workspace
55
45
 
56
- LABEL org.opencontainers.image.title="autoclawd-base"
57
- LABEL org.opencontainers.image.description="Multi-runtime base image for autoclawd (Linear → Claude Code → PRs)"
46
+ LABEL org.opencontainers.image.title="autoclawd"
47
+ LABEL org.opencontainers.image.description="Multi-runtime environment for autoclawd (Linear → Claude Code → PRs)"
package/dist/index.js CHANGED
@@ -535,10 +535,9 @@ import { createHmac, timingSafeEqual } from "crypto";
535
535
  // src/docker.ts
536
536
  import Docker from "dockerode";
537
537
  import { PassThrough } from "stream";
538
- import { homedir as homedir3, tmpdir } from "os";
538
+ import { homedir as homedir3 } from "os";
539
539
  import { join as join3 } from "path";
540
- import { existsSync as existsSync3, readFileSync as readFileSync2, writeFileSync, mkdtempSync, rmSync } from "fs";
541
- import { execSync } from "child_process";
540
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
542
541
  var docker = new Docker();
543
542
  async function checkDockerAvailable() {
544
543
  try {
@@ -549,20 +548,6 @@ async function checkDockerAvailable() {
549
548
  );
550
549
  }
551
550
  }
552
- var AUTOCODE_BASE_DOCKERFILE = `FROM node:20-bookworm
553
- ENV DEBIAN_FRONTEND=noninteractive
554
- RUN apt-get update && apt-get install -y --no-install-recommends \\
555
- build-essential git curl wget ca-certificates openssh-client jq unzip \\
556
- python3 python3-pip python3-venv python3-dev \\
557
- ruby ruby-dev \\
558
- && rm -rf /var/lib/apt/lists/*
559
- ARG GO_VERSION=1.22.2
560
- RUN curl -fsSL "https://go.dev/dl/go\${GO_VERSION}.linux-$(dpkg --print-architecture).tar.gz" \\
561
- | tar -xz -C /usr/local
562
- ENV PATH="/usr/local/go/bin:\${PATH}"
563
- RUN npm install -g @anthropic-ai/claude-code@latest
564
- WORKDIR /workspace
565
- `;
566
551
  async function ensureImage(image) {
567
552
  try {
568
553
  await docker.getImage(image).inspect();
@@ -570,28 +555,6 @@ async function ensureImage(image) {
570
555
  return;
571
556
  } catch {
572
557
  }
573
- if (image === "autoclawd-base") {
574
- log.info("Building autoclawd-base image (first time only, takes a few minutes)...");
575
- const buildDir = mkdtempSync(join3(tmpdir(), "autoclawd-build-"));
576
- try {
577
- writeFileSync(join3(buildDir, "Dockerfile"), AUTOCODE_BASE_DOCKERFILE);
578
- execSync(`docker build -t autoclawd-base ${buildDir}`, {
579
- stdio: "inherit",
580
- timeout: 6e5
581
- });
582
- log.success("autoclawd-base image built");
583
- return;
584
- } catch (err) {
585
- throw new Error(
586
- `Failed to build autoclawd-base image.
587
- You can build manually: docker build -t autoclawd-base .
588
- Or use a different image in your config (e.g. node:20).
589
- Error: ${err instanceof Error ? err.message : err}`
590
- );
591
- } finally {
592
- rmSync(buildDir, { recursive: true, force: true });
593
- }
594
- }
595
558
  log.info(`Pulling image ${image} (this may take a moment)...`);
596
559
  await retry(() => new Promise((resolve, reject) => {
597
560
  docker.pull(image, (err, stream) => {
@@ -1058,10 +1021,10 @@ async function commitAndPush(container, opts) {
1058
1021
  }
1059
1022
 
1060
1023
  // src/worker.ts
1061
- import { mkdtempSync as mkdtempSync2, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
1024
+ import { mkdtempSync, rmSync, writeFileSync } from "fs";
1062
1025
  import { join as join5 } from "path";
1063
- import { tmpdir as tmpdir2 } from "os";
1064
- import { execSync as execSync2 } from "child_process";
1026
+ import { tmpdir } from "os";
1027
+ import { execSync } from "child_process";
1065
1028
 
1066
1029
  // src/db.ts
1067
1030
  import Database from "better-sqlite3";
@@ -1231,9 +1194,9 @@ function assertSafeRef(name, label) {
1231
1194
  }
1232
1195
  }
1233
1196
  function createAskpass(githubToken) {
1234
- const dir = mkdtempSync2(join5(tmpdir2(), "autoclawd-cred-"));
1197
+ const dir = mkdtempSync(join5(tmpdir(), "autoclawd-cred-"));
1235
1198
  const path = join5(dir, "askpass.sh");
1236
- writeFileSync2(path, `#!/bin/sh
1199
+ writeFileSync(path, `#!/bin/sh
1237
1200
  echo "${githubToken}"
1238
1201
  `, { mode: 448 });
1239
1202
  return { dir, path };
@@ -1245,7 +1208,7 @@ function detectDefaultBranch(repoUrl, githubToken) {
1245
1208
  const askpass = createAskpass(githubToken);
1246
1209
  const authedUrl = repoUrl.replace("https://", "https://x-access-token@");
1247
1210
  try {
1248
- const output = execSync2(
1211
+ const output = execSync(
1249
1212
  `git ls-remote --symref -- ${authedUrl} HEAD`,
1250
1213
  { stdio: "pipe", timeout: 3e4, env: gitEnv(askpass.path), encoding: "utf-8" }
1251
1214
  );
@@ -1253,27 +1216,27 @@ function detectDefaultBranch(repoUrl, githubToken) {
1253
1216
  if (match) return match[1];
1254
1217
  } catch {
1255
1218
  } finally {
1256
- rmSync2(askpass.dir, { recursive: true, force: true });
1219
+ rmSync(askpass.dir, { recursive: true, force: true });
1257
1220
  }
1258
1221
  return "main";
1259
1222
  }
1260
1223
  async function cloneToTemp(repoUrl, baseBranch, githubToken) {
1261
1224
  assertSafeRef(baseBranch, "base branch");
1262
1225
  return retrySync(() => {
1263
- const workDir = mkdtempSync2(join5(tmpdir2(), "autoclawd-"));
1226
+ const workDir = mkdtempSync(join5(tmpdir(), "autoclawd-"));
1264
1227
  const askpass = createAskpass(githubToken);
1265
1228
  const authedUrl = repoUrl.replace("https://", "https://x-access-token@");
1266
1229
  try {
1267
- execSync2(`git clone --depth=50 -b ${baseBranch} -- ${authedUrl} ${workDir}`, {
1230
+ execSync(`git clone --depth=50 -b ${baseBranch} -- ${authedUrl} ${workDir}`, {
1268
1231
  stdio: "pipe",
1269
1232
  timeout: 12e4,
1270
1233
  env: gitEnv(askpass.path)
1271
1234
  });
1272
1235
  } catch (err) {
1273
- rmSync2(workDir, { recursive: true, force: true });
1236
+ rmSync(workDir, { recursive: true, force: true });
1274
1237
  throw err;
1275
1238
  } finally {
1276
- rmSync2(askpass.dir, { recursive: true, force: true });
1239
+ rmSync(askpass.dir, { recursive: true, force: true });
1277
1240
  }
1278
1241
  return workDir;
1279
1242
  }, { label: "git clone", retryIf: isTransientError });
@@ -1409,10 +1372,10 @@ async function executeTicket(opts) {
1409
1372
  log.ticket(ticket.identifier, `Stacked on branch: ${ticket.baseBranch}`);
1410
1373
  }
1411
1374
  if (actualBase !== detectedBase) {
1412
- rmSync2(workDir, { recursive: true, force: true });
1375
+ rmSync(workDir, { recursive: true, force: true });
1413
1376
  workDir = await cloneToTemp(ticket.repoUrl, actualBase, config.github.token);
1414
1377
  }
1415
- execSync2(`git checkout -B ${branchName} --`, { cwd: workDir, stdio: "pipe" });
1378
+ execSync(`git checkout -B ${branchName} --`, { cwd: workDir, stdio: "pipe" });
1416
1379
  container = await createContainer({
1417
1380
  dockerConfig: docker2,
1418
1381
  workspacePath: workDir,
@@ -1579,7 +1542,7 @@ async function executeTicket(opts) {
1579
1542
  }
1580
1543
  if (workDir) {
1581
1544
  try {
1582
- rmSync2(workDir, { recursive: true, force: true });
1545
+ rmSync(workDir, { recursive: true, force: true });
1583
1546
  } catch {
1584
1547
  }
1585
1548
  }
@@ -1668,7 +1631,7 @@ async function fixPR(opts) {
1668
1631
  if (container) await destroyContainer(container);
1669
1632
  if (workDir) {
1670
1633
  try {
1671
- rmSync2(workDir, { recursive: true, force: true });
1634
+ rmSync(workDir, { recursive: true, force: true });
1672
1635
  } catch {
1673
1636
  }
1674
1637
  }
@@ -2143,7 +2106,7 @@ function printHistoryTable(records) {
2143
2106
  }
2144
2107
 
2145
2108
  // src/deps.ts
2146
- import { execSync as execSync3 } from "child_process";
2109
+ import { execSync as execSync2 } from "child_process";
2147
2110
  import { existsSync as existsSync6 } from "fs";
2148
2111
  import { join as join7 } from "path";
2149
2112
  import { homedir as homedir5 } from "os";
@@ -2158,7 +2121,7 @@ function detectPackageManager() {
2158
2121
  ];
2159
2122
  for (const [name, cmd] of checks) {
2160
2123
  try {
2161
- execSync3(`which ${cmd}`, { stdio: "pipe" });
2124
+ execSync2(`which ${cmd}`, { stdio: "pipe" });
2162
2125
  return name;
2163
2126
  } catch {
2164
2127
  }
@@ -2249,7 +2212,7 @@ function installDep(dep) {
2249
2212
  if (dep.installCommands.length === 0) return false;
2250
2213
  for (const cmd of dep.installCommands) {
2251
2214
  try {
2252
- execSync3(cmd, { stdio: "inherit", timeout: 3e5 });
2215
+ execSync2(cmd, { stdio: "inherit", timeout: 3e5 });
2253
2216
  } catch {
2254
2217
  return false;
2255
2218
  }
@@ -2259,15 +2222,15 @@ function installDep(dep) {
2259
2222
  function addUserToDockerGroup() {
2260
2223
  if (process.platform !== "linux") return;
2261
2224
  try {
2262
- const user = execSync3("whoami", { encoding: "utf-8" }).trim();
2263
- execSync3(`sudo usermod -aG docker ${user}`, { stdio: "pipe" });
2225
+ const user = execSync2("whoami", { encoding: "utf-8" }).trim();
2226
+ execSync2(`sudo usermod -aG docker ${user}`, { stdio: "pipe" });
2264
2227
  log.info(`Added ${user} to docker group \u2014 log out and back in to apply`);
2265
2228
  } catch {
2266
2229
  }
2267
2230
  }
2268
2231
  function commandExists(cmd) {
2269
2232
  try {
2270
- execSync3(`which ${cmd}`, { stdio: "pipe" });
2233
+ execSync2(`which ${cmd}`, { stdio: "pipe" });
2271
2234
  return true;
2272
2235
  } catch {
2273
2236
  return false;
@@ -2275,7 +2238,7 @@ function commandExists(cmd) {
2275
2238
  }
2276
2239
  function isDockerRunning() {
2277
2240
  try {
2278
- execSync3("docker info", { stdio: "pipe", timeout: 1e4 });
2241
+ execSync2("docker info", { stdio: "pipe", timeout: 1e4 });
2279
2242
  return true;
2280
2243
  } catch {
2281
2244
  return false;
@@ -2283,8 +2246,8 @@ function isDockerRunning() {
2283
2246
  }
2284
2247
 
2285
2248
  // src/index.ts
2286
- import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync3 } from "fs";
2287
- import { execSync as execSync4 } from "child_process";
2249
+ import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2 } from "fs";
2250
+ import { execSync as execSync3 } from "child_process";
2288
2251
  import { createInterface } from "readline";
2289
2252
  import { join as join8 } from "path";
2290
2253
  import { homedir as homedir6 } from "os";
@@ -2703,7 +2666,7 @@ program.command("init").description("Set up autoclawd interactively").action(asy
2703
2666
  }
2704
2667
  let ghToken = "";
2705
2668
  try {
2706
- ghToken = execSync4("gh auth token", { encoding: "utf-8" }).trim();
2669
+ ghToken = execSync3("gh auth token", { encoding: "utf-8" }).trim();
2707
2670
  log.success(`GitHub token detected from gh CLI`);
2708
2671
  } catch {
2709
2672
  ghToken = await ask("GitHub personal access token");
@@ -2719,19 +2682,11 @@ program.command("init").description("Set up autoclawd interactively").action(asy
2719
2682
  log.error("GitHub token is invalid or expired");
2720
2683
  process.exit(1);
2721
2684
  }
2722
- console.log("\n Docker image options:");
2723
- console.log(" autoclawd-base \u2014 All runtimes: Node, Python, Go, Rust, Ruby (~1.5GB)");
2724
- console.log(" Works with any repo, no per-repo config needed");
2725
- console.log(" node:20 \u2014 Node.js only (fast, ~300MB)");
2726
- const dockerImage = await ask("Docker image", "autoclawd-base");
2727
- if (dockerImage === "autoclawd-base") {
2728
- try {
2729
- execSync4("docker image inspect autoclawd-base", { stdio: "pipe" });
2730
- log.success("autoclawd-base image found");
2731
- } catch {
2732
- log.info("autoclawd-base will be built automatically on first run (~3 min)");
2733
- }
2734
- }
2685
+ console.log("\n Docker image (any image works \u2014 autoclawd auto-installs git + Claude Code):");
2686
+ console.log(" node:20 \u2014 Node.js (default, recommended)");
2687
+ console.log(" python:3.12 \u2014 Python");
2688
+ console.log(" ubuntu:24.04 \u2014 General purpose");
2689
+ const dockerImage = await ask("Docker image", "node:20");
2735
2690
  const model = await ask("Claude model", "claude-sonnet-4-6");
2736
2691
  const maxIter = await ask("Max iterations per ticket", "10");
2737
2692
  const config = `# autoclawd config \u2014 generated by autoclawd init
@@ -2762,7 +2717,7 @@ agent:
2762
2717
 
2763
2718
  maxConcurrent: 1
2764
2719
  `;
2765
- writeFileSync3(CONFIG_FILE, config, { mode: 384 });
2720
+ writeFileSync2(CONFIG_FILE, config, { mode: 384 });
2766
2721
  log.success(`Config saved to ${CONFIG_FILE}`);
2767
2722
  console.log(`
2768
2723
  Setup complete! Next steps: