@autoclawd/autoclawd 1.0.0 → 1.0.2
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 +112 -79
- 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
|
@@ -184,6 +184,10 @@ function enableFileLogging() {
|
|
|
184
184
|
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
185
185
|
logFilePath = join2(logDir, `autoclawd-${date}.log`);
|
|
186
186
|
}
|
|
187
|
+
function getLogFilePath() {
|
|
188
|
+
const date = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
189
|
+
return join2(homedir2(), ".autoclawd", "logs", `autoclawd-${date}.log`);
|
|
190
|
+
}
|
|
187
191
|
function shouldLog(level) {
|
|
188
192
|
return levels[level] >= levels[currentLevel];
|
|
189
193
|
}
|
|
@@ -535,10 +539,9 @@ import { createHmac, timingSafeEqual } from "crypto";
|
|
|
535
539
|
// src/docker.ts
|
|
536
540
|
import Docker from "dockerode";
|
|
537
541
|
import { PassThrough } from "stream";
|
|
538
|
-
import { homedir as homedir3
|
|
542
|
+
import { homedir as homedir3 } from "os";
|
|
539
543
|
import { join as join3 } from "path";
|
|
540
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2
|
|
541
|
-
import { execSync } from "child_process";
|
|
544
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
542
545
|
var docker = new Docker();
|
|
543
546
|
async function checkDockerAvailable() {
|
|
544
547
|
try {
|
|
@@ -549,20 +552,6 @@ async function checkDockerAvailable() {
|
|
|
549
552
|
);
|
|
550
553
|
}
|
|
551
554
|
}
|
|
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
555
|
async function ensureImage(image) {
|
|
567
556
|
try {
|
|
568
557
|
await docker.getImage(image).inspect();
|
|
@@ -570,28 +559,6 @@ async function ensureImage(image) {
|
|
|
570
559
|
return;
|
|
571
560
|
} catch {
|
|
572
561
|
}
|
|
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
562
|
log.info(`Pulling image ${image} (this may take a moment)...`);
|
|
596
563
|
await retry(() => new Promise((resolve, reject) => {
|
|
597
564
|
docker.pull(image, (err, stream) => {
|
|
@@ -1058,10 +1025,10 @@ async function commitAndPush(container, opts) {
|
|
|
1058
1025
|
}
|
|
1059
1026
|
|
|
1060
1027
|
// src/worker.ts
|
|
1061
|
-
import { mkdtempSync
|
|
1028
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
1062
1029
|
import { join as join5 } from "path";
|
|
1063
|
-
import { tmpdir
|
|
1064
|
-
import { execSync
|
|
1030
|
+
import { tmpdir } from "os";
|
|
1031
|
+
import { execSync } from "child_process";
|
|
1065
1032
|
|
|
1066
1033
|
// src/db.ts
|
|
1067
1034
|
import Database from "better-sqlite3";
|
|
@@ -1231,9 +1198,9 @@ function assertSafeRef(name, label) {
|
|
|
1231
1198
|
}
|
|
1232
1199
|
}
|
|
1233
1200
|
function createAskpass(githubToken) {
|
|
1234
|
-
const dir =
|
|
1201
|
+
const dir = mkdtempSync(join5(tmpdir(), "autoclawd-cred-"));
|
|
1235
1202
|
const path = join5(dir, "askpass.sh");
|
|
1236
|
-
|
|
1203
|
+
writeFileSync(path, `#!/bin/sh
|
|
1237
1204
|
echo "${githubToken}"
|
|
1238
1205
|
`, { mode: 448 });
|
|
1239
1206
|
return { dir, path };
|
|
@@ -1245,7 +1212,7 @@ function detectDefaultBranch(repoUrl, githubToken) {
|
|
|
1245
1212
|
const askpass = createAskpass(githubToken);
|
|
1246
1213
|
const authedUrl = repoUrl.replace("https://", "https://x-access-token@");
|
|
1247
1214
|
try {
|
|
1248
|
-
const output =
|
|
1215
|
+
const output = execSync(
|
|
1249
1216
|
`git ls-remote --symref -- ${authedUrl} HEAD`,
|
|
1250
1217
|
{ stdio: "pipe", timeout: 3e4, env: gitEnv(askpass.path), encoding: "utf-8" }
|
|
1251
1218
|
);
|
|
@@ -1253,27 +1220,27 @@ function detectDefaultBranch(repoUrl, githubToken) {
|
|
|
1253
1220
|
if (match) return match[1];
|
|
1254
1221
|
} catch {
|
|
1255
1222
|
} finally {
|
|
1256
|
-
|
|
1223
|
+
rmSync(askpass.dir, { recursive: true, force: true });
|
|
1257
1224
|
}
|
|
1258
1225
|
return "main";
|
|
1259
1226
|
}
|
|
1260
1227
|
async function cloneToTemp(repoUrl, baseBranch, githubToken) {
|
|
1261
1228
|
assertSafeRef(baseBranch, "base branch");
|
|
1262
1229
|
return retrySync(() => {
|
|
1263
|
-
const workDir =
|
|
1230
|
+
const workDir = mkdtempSync(join5(tmpdir(), "autoclawd-"));
|
|
1264
1231
|
const askpass = createAskpass(githubToken);
|
|
1265
1232
|
const authedUrl = repoUrl.replace("https://", "https://x-access-token@");
|
|
1266
1233
|
try {
|
|
1267
|
-
|
|
1234
|
+
execSync(`git clone --depth=50 -b ${baseBranch} -- ${authedUrl} ${workDir}`, {
|
|
1268
1235
|
stdio: "pipe",
|
|
1269
1236
|
timeout: 12e4,
|
|
1270
1237
|
env: gitEnv(askpass.path)
|
|
1271
1238
|
});
|
|
1272
1239
|
} catch (err) {
|
|
1273
|
-
|
|
1240
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
1274
1241
|
throw err;
|
|
1275
1242
|
} finally {
|
|
1276
|
-
|
|
1243
|
+
rmSync(askpass.dir, { recursive: true, force: true });
|
|
1277
1244
|
}
|
|
1278
1245
|
return workDir;
|
|
1279
1246
|
}, { label: "git clone", retryIf: isTransientError });
|
|
@@ -1409,10 +1376,10 @@ async function executeTicket(opts) {
|
|
|
1409
1376
|
log.ticket(ticket.identifier, `Stacked on branch: ${ticket.baseBranch}`);
|
|
1410
1377
|
}
|
|
1411
1378
|
if (actualBase !== detectedBase) {
|
|
1412
|
-
|
|
1379
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
1413
1380
|
workDir = await cloneToTemp(ticket.repoUrl, actualBase, config.github.token);
|
|
1414
1381
|
}
|
|
1415
|
-
|
|
1382
|
+
execSync(`git checkout -B ${branchName} --`, { cwd: workDir, stdio: "pipe" });
|
|
1416
1383
|
container = await createContainer({
|
|
1417
1384
|
dockerConfig: docker2,
|
|
1418
1385
|
workspacePath: workDir,
|
|
@@ -1579,7 +1546,7 @@ async function executeTicket(opts) {
|
|
|
1579
1546
|
}
|
|
1580
1547
|
if (workDir) {
|
|
1581
1548
|
try {
|
|
1582
|
-
|
|
1549
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
1583
1550
|
} catch {
|
|
1584
1551
|
}
|
|
1585
1552
|
}
|
|
@@ -1668,7 +1635,7 @@ async function fixPR(opts) {
|
|
|
1668
1635
|
if (container) await destroyContainer(container);
|
|
1669
1636
|
if (workDir) {
|
|
1670
1637
|
try {
|
|
1671
|
-
|
|
1638
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
1672
1639
|
} catch {
|
|
1673
1640
|
}
|
|
1674
1641
|
}
|
|
@@ -2143,7 +2110,7 @@ function printHistoryTable(records) {
|
|
|
2143
2110
|
}
|
|
2144
2111
|
|
|
2145
2112
|
// src/deps.ts
|
|
2146
|
-
import { execSync as
|
|
2113
|
+
import { execSync as execSync2 } from "child_process";
|
|
2147
2114
|
import { existsSync as existsSync6 } from "fs";
|
|
2148
2115
|
import { join as join7 } from "path";
|
|
2149
2116
|
import { homedir as homedir5 } from "os";
|
|
@@ -2158,7 +2125,7 @@ function detectPackageManager() {
|
|
|
2158
2125
|
];
|
|
2159
2126
|
for (const [name, cmd] of checks) {
|
|
2160
2127
|
try {
|
|
2161
|
-
|
|
2128
|
+
execSync2(`which ${cmd}`, { stdio: "pipe" });
|
|
2162
2129
|
return name;
|
|
2163
2130
|
} catch {
|
|
2164
2131
|
}
|
|
@@ -2249,7 +2216,7 @@ function installDep(dep) {
|
|
|
2249
2216
|
if (dep.installCommands.length === 0) return false;
|
|
2250
2217
|
for (const cmd of dep.installCommands) {
|
|
2251
2218
|
try {
|
|
2252
|
-
|
|
2219
|
+
execSync2(cmd, { stdio: "inherit", timeout: 3e5 });
|
|
2253
2220
|
} catch {
|
|
2254
2221
|
return false;
|
|
2255
2222
|
}
|
|
@@ -2259,15 +2226,15 @@ function installDep(dep) {
|
|
|
2259
2226
|
function addUserToDockerGroup() {
|
|
2260
2227
|
if (process.platform !== "linux") return;
|
|
2261
2228
|
try {
|
|
2262
|
-
const user =
|
|
2263
|
-
|
|
2229
|
+
const user = execSync2("whoami", { encoding: "utf-8" }).trim();
|
|
2230
|
+
execSync2(`sudo usermod -aG docker ${user}`, { stdio: "pipe" });
|
|
2264
2231
|
log.info(`Added ${user} to docker group \u2014 log out and back in to apply`);
|
|
2265
2232
|
} catch {
|
|
2266
2233
|
}
|
|
2267
2234
|
}
|
|
2268
2235
|
function commandExists(cmd) {
|
|
2269
2236
|
try {
|
|
2270
|
-
|
|
2237
|
+
execSync2(`which ${cmd}`, { stdio: "pipe" });
|
|
2271
2238
|
return true;
|
|
2272
2239
|
} catch {
|
|
2273
2240
|
return false;
|
|
@@ -2275,7 +2242,7 @@ function commandExists(cmd) {
|
|
|
2275
2242
|
}
|
|
2276
2243
|
function isDockerRunning() {
|
|
2277
2244
|
try {
|
|
2278
|
-
|
|
2245
|
+
execSync2("docker info", { stdio: "pipe", timeout: 1e4 });
|
|
2279
2246
|
return true;
|
|
2280
2247
|
} catch {
|
|
2281
2248
|
return false;
|
|
@@ -2283,8 +2250,8 @@ function isDockerRunning() {
|
|
|
2283
2250
|
}
|
|
2284
2251
|
|
|
2285
2252
|
// src/index.ts
|
|
2286
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as
|
|
2287
|
-
import { execSync as
|
|
2253
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, writeFileSync as writeFileSync2, readFileSync as readFileSync4, unlinkSync } from "fs";
|
|
2254
|
+
import { execSync as execSync3, spawn as spawn2 } from "child_process";
|
|
2288
2255
|
import { createInterface } from "readline";
|
|
2289
2256
|
import { join as join8 } from "path";
|
|
2290
2257
|
import { homedir as homedir6 } from "os";
|
|
@@ -2294,6 +2261,36 @@ import { createRequire } from "module";
|
|
|
2294
2261
|
var require2 = createRequire(import.meta.url);
|
|
2295
2262
|
var pkg = require2("../package.json");
|
|
2296
2263
|
var program = new Command();
|
|
2264
|
+
var WATCHER_PID_FILE = join8(homedir6(), ".autoclawd", "watcher.pid");
|
|
2265
|
+
function readWatcherPid() {
|
|
2266
|
+
try {
|
|
2267
|
+
const content = readFileSync4(WATCHER_PID_FILE, "utf-8").trim();
|
|
2268
|
+
const pid = parseInt(content, 10);
|
|
2269
|
+
return isNaN(pid) ? null : pid;
|
|
2270
|
+
} catch {
|
|
2271
|
+
return null;
|
|
2272
|
+
}
|
|
2273
|
+
}
|
|
2274
|
+
function writeWatcherPid(pid) {
|
|
2275
|
+
mkdirSync3(join8(homedir6(), ".autoclawd"), { recursive: true });
|
|
2276
|
+
writeFileSync2(WATCHER_PID_FILE, String(pid), { mode: 384 });
|
|
2277
|
+
}
|
|
2278
|
+
function removeWatcherPid() {
|
|
2279
|
+
try {
|
|
2280
|
+
unlinkSync(WATCHER_PID_FILE);
|
|
2281
|
+
} catch {
|
|
2282
|
+
}
|
|
2283
|
+
}
|
|
2284
|
+
function getRunningWatcherPid() {
|
|
2285
|
+
const pid = readWatcherPid();
|
|
2286
|
+
if (pid === null) return null;
|
|
2287
|
+
try {
|
|
2288
|
+
process.kill(pid, 0);
|
|
2289
|
+
return pid;
|
|
2290
|
+
} catch {
|
|
2291
|
+
return null;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2297
2294
|
program.name("autoclawd").description("Linear webhooks \u2192 Claude Code in Docker \u2192 PRs").version(pkg.version);
|
|
2298
2295
|
function ask(question, defaultVal) {
|
|
2299
2296
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -2367,7 +2364,29 @@ program.command("serve").description("Start webhook server with auto-tunnel").op
|
|
|
2367
2364
|
process.on("SIGINT", shutdown);
|
|
2368
2365
|
process.on("SIGTERM", shutdown);
|
|
2369
2366
|
});
|
|
2370
|
-
program.command("watch").description("Poll Linear for tickets (no webhook/tunnel needed)").option("-c, --config <path>", "Config file path").option("-i, --interval <seconds>", "Poll interval in seconds", "30").option("--once", "Poll once and exit (useful for cron)").option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
2367
|
+
program.command("watch").description("Poll Linear for tickets (no webhook/tunnel needed)").option("-c, --config <path>", "Config file path").option("-i, --interval <seconds>", "Poll interval in seconds", "30").option("--once", "Poll once and exit (useful for cron)").option("--foreground", "Run in foreground instead of daemonizing").option("-v, --verbose", "Verbose logging").action(async (opts) => {
|
|
2368
|
+
if (!opts.foreground && !opts.once) {
|
|
2369
|
+
const existingPid = getRunningWatcherPid();
|
|
2370
|
+
if (existingPid !== null) {
|
|
2371
|
+
console.error(`autoclawd watcher is already running (PID ${existingPid})`);
|
|
2372
|
+
console.error(`Run 'autoclawd stop' to stop it, or use '--foreground' to run in terminal`);
|
|
2373
|
+
process.exit(1);
|
|
2374
|
+
}
|
|
2375
|
+
const child = spawn2(process.argv[0], [...process.argv.slice(1), "--foreground"], {
|
|
2376
|
+
detached: true,
|
|
2377
|
+
stdio: "ignore"
|
|
2378
|
+
});
|
|
2379
|
+
await new Promise((resolve, reject) => {
|
|
2380
|
+
child.once("spawn", resolve);
|
|
2381
|
+
child.once("error", reject);
|
|
2382
|
+
});
|
|
2383
|
+
writeWatcherPid(child.pid);
|
|
2384
|
+
const logPath = getLogFilePath();
|
|
2385
|
+
console.log(`autoclawd watching in background (PID ${child.pid}, log: ${logPath})`);
|
|
2386
|
+
child.unref();
|
|
2387
|
+
process.exit(0);
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2371
2390
|
if (opts.verbose) setLogLevel("debug");
|
|
2372
2391
|
enableFileLogging();
|
|
2373
2392
|
log.info(`autoclawd v${pkg.version}`);
|
|
@@ -2390,12 +2409,34 @@ program.command("watch").description("Poll Linear for tickets (no webhook/tunnel
|
|
|
2390
2409
|
const shutdown = () => {
|
|
2391
2410
|
log.info("Shutting down...");
|
|
2392
2411
|
watcher.stop();
|
|
2412
|
+
removeWatcherPid();
|
|
2393
2413
|
process.exit(0);
|
|
2394
2414
|
};
|
|
2395
2415
|
process.on("SIGINT", shutdown);
|
|
2396
2416
|
process.on("SIGTERM", shutdown);
|
|
2397
2417
|
await watcher.start(intervalSeconds, opts.once ?? false);
|
|
2398
2418
|
});
|
|
2419
|
+
program.command("stop").description("Stop the background watcher").action(() => {
|
|
2420
|
+
const pid = readWatcherPid();
|
|
2421
|
+
if (pid === null) {
|
|
2422
|
+
log.info("No watcher PID file found \u2014 watcher is not running");
|
|
2423
|
+
return;
|
|
2424
|
+
}
|
|
2425
|
+
const running = getRunningWatcherPid();
|
|
2426
|
+
if (running === null) {
|
|
2427
|
+
removeWatcherPid();
|
|
2428
|
+
log.info(`Removed stale PID file (process ${pid} is not running)`);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
try {
|
|
2432
|
+
process.kill(pid, "SIGTERM");
|
|
2433
|
+
removeWatcherPid();
|
|
2434
|
+
log.success(`Watcher stopped (PID ${pid})`);
|
|
2435
|
+
} catch (err) {
|
|
2436
|
+
log.error(`Failed to stop watcher: ${err instanceof Error ? err.message : err}`);
|
|
2437
|
+
process.exit(1);
|
|
2438
|
+
}
|
|
2439
|
+
});
|
|
2399
2440
|
program.command("run <ticket>").description("Run a single ticket (e.g. autoclawd run RAH-123)").option("-c, --config <path>", "Config file path").option("-v, --verbose", "Verbose logging").option("--dry-run", "Show what would happen without executing").option("--force", "Bypass completed-ticket check and re-run").action(async (ticketId, opts) => {
|
|
2400
2441
|
if (opts.verbose) setLogLevel("debug");
|
|
2401
2442
|
enableFileLogging();
|
|
@@ -2703,7 +2744,7 @@ program.command("init").description("Set up autoclawd interactively").action(asy
|
|
|
2703
2744
|
}
|
|
2704
2745
|
let ghToken = "";
|
|
2705
2746
|
try {
|
|
2706
|
-
ghToken =
|
|
2747
|
+
ghToken = execSync3("gh auth token", { encoding: "utf-8" }).trim();
|
|
2707
2748
|
log.success(`GitHub token detected from gh CLI`);
|
|
2708
2749
|
} catch {
|
|
2709
2750
|
ghToken = await ask("GitHub personal access token");
|
|
@@ -2719,19 +2760,11 @@ program.command("init").description("Set up autoclawd interactively").action(asy
|
|
|
2719
2760
|
log.error("GitHub token is invalid or expired");
|
|
2720
2761
|
process.exit(1);
|
|
2721
2762
|
}
|
|
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
|
-
}
|
|
2763
|
+
console.log("\n Docker image (any image works \u2014 autoclawd auto-installs git + Claude Code):");
|
|
2764
|
+
console.log(" node:20 \u2014 Node.js (default, recommended)");
|
|
2765
|
+
console.log(" python:3.12 \u2014 Python");
|
|
2766
|
+
console.log(" ubuntu:24.04 \u2014 General purpose");
|
|
2767
|
+
const dockerImage = await ask("Docker image", "node:20");
|
|
2735
2768
|
const model = await ask("Claude model", "claude-sonnet-4-6");
|
|
2736
2769
|
const maxIter = await ask("Max iterations per ticket", "10");
|
|
2737
2770
|
const config = `# autoclawd config \u2014 generated by autoclawd init
|
|
@@ -2762,7 +2795,7 @@ agent:
|
|
|
2762
2795
|
|
|
2763
2796
|
maxConcurrent: 1
|
|
2764
2797
|
`;
|
|
2765
|
-
|
|
2798
|
+
writeFileSync2(CONFIG_FILE, config, { mode: 384 });
|
|
2766
2799
|
log.success(`Config saved to ${CONFIG_FILE}`);
|
|
2767
2800
|
console.log(`
|
|
2768
2801
|
Setup complete! Next steps:
|