@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 +8 -18
- package/dist/index.js +33 -78
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/Dockerfile
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
# autoclawd
|
|
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
|
|
5
|
-
# Config: docker: { image: autoclawd
|
|
3
|
+
# Build: docker build -t autoclawd .
|
|
4
|
+
# Config: docker: { image: autoclawd }
|
|
6
5
|
#
|
|
7
|
-
# Includes: Node.js 20, Python 3, Go,
|
|
8
|
-
#
|
|
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
|
|
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
|
|
57
|
-
LABEL org.opencontainers.image.description="Multi-runtime
|
|
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
|
|
538
|
+
import { homedir as homedir3 } from "os";
|
|
539
539
|
import { join as join3 } from "path";
|
|
540
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2
|
|
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
|
|
1024
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
1062
1025
|
import { join as join5 } from "path";
|
|
1063
|
-
import { tmpdir
|
|
1064
|
-
import { execSync
|
|
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 =
|
|
1197
|
+
const dir = mkdtempSync(join5(tmpdir(), "autoclawd-cred-"));
|
|
1235
1198
|
const path = join5(dir, "askpass.sh");
|
|
1236
|
-
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1236
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
1274
1237
|
throw err;
|
|
1275
1238
|
} finally {
|
|
1276
|
-
|
|
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
|
-
|
|
1375
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
1413
1376
|
workDir = await cloneToTemp(ticket.repoUrl, actualBase, config.github.token);
|
|
1414
1377
|
}
|
|
1415
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
2263
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
2287
|
-
import { execSync as
|
|
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 =
|
|
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
|
|
2723
|
-
console.log("
|
|
2724
|
-
console.log("
|
|
2725
|
-
console.log("
|
|
2726
|
-
const dockerImage = await ask("Docker image", "
|
|
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
|
-
|
|
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:
|