Skip to content

Commit 8af8151

Browse files
authored
Include more CLI tools in the Docker image (#4)
1 parent fbf1e0e commit 8af8151

File tree

8 files changed

+78
-41
lines changed

8 files changed

+78
-41
lines changed

Dockerfile

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,18 @@
1-
# --- Build Stage ---
21
FROM node:20-alpine AS builder
32
WORKDIR /app
43
COPY package*.json ./
5-
6-
# Install dependencies, ignoring peer conflicts
74
RUN npm ci --legacy-peer-deps
85
COPY tsconfig.json ./
96
COPY src ./src
10-
11-
# Build the application
127
RUN npm run build
138

14-
# --- Runtime Stage ---
159
FROM node:20-alpine AS runtime
16-
RUN apk add --no-cache bash git
10+
RUN apk add --no-cache bash git grep findutils coreutils sed gawk curl wget openssh-client ca-certificates make
1711
WORKDIR /app
1812
ENV NODE_ENV=production
19-
2013
COPY package*.json ./
21-
2214
RUN npm ci --omit=dev --legacy-peer-deps
23-
2415
COPY --from=builder /app/dist ./dist
25-
2616
ENV TERM=xterm-256color
27-
2817
ENTRYPOINT ["node", "dist/cli.js"]
2918
CMD []

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ test: check-deps ## Run the test suite
5959
@$(PACKAGE_MANAGER) test
6060

6161
coverage: check-deps ## Run the test suite and generate a coverage report
62+
@mkdir -p coverage/.tmp
6263
@$(PACKAGE_MANAGER) run coverage
6364

6465
lint: check-deps ## Run linter checks

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ binharic
7979
8080
#### Running in a Container
8181

82-
Alternatively, you can start Binharic in a container:
82+
Alternatively, you can run Binharic in a container:
8383

8484
```sh
8585
# API keys should be available in the environment already

src/logger.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,31 @@
11
import winston from "winston";
22
import path from "path";
33
import fs from "fs";
4-
5-
import { getConfigDir } from "./config.js";
4+
import { getConfigDir } from "./paths.js";
65

76
let logger: winston.Logger | null = null;
87

98
function getLogger(): winston.Logger {
10-
if (logger) {
11-
return logger;
12-
}
9+
if (logger) return logger;
1310

14-
const LOGS_DIR = path.join(getConfigDir(), "logs");
11+
const isTest = process.env.NODE_ENV === "test";
12+
const isDebugMode =
13+
process.env.DEBUG_BINHARIC !== undefined || process.env.DEBUG_TOBI !== undefined;
14+
const logLevel = isTest ? "error" : isDebugMode ? "debug" : "info";
1515

16-
// Ensure logs directory exists
17-
if (!fs.existsSync(LOGS_DIR)) {
18-
fs.mkdirSync(LOGS_DIR, { recursive: true });
16+
if (isTest) {
17+
logger = winston.createLogger({
18+
level: logLevel,
19+
format: winston.format.json(),
20+
transports: [],
21+
silent: true,
22+
});
23+
return logger;
1924
}
2025

21-
const isDebugMode =
22-
process.env.DEBUG_BINHARIC !== undefined || process.env.DEBUG_TOBI !== undefined;
23-
const logLevel = isDebugMode ? "debug" : "info";
26+
const overrideLogDir = process.env.BINHARIC_LOG_DIR;
27+
const LOGS_DIR = overrideLogDir ? overrideLogDir : path.join(getConfigDir(), "logs");
28+
if (!fs.existsSync(LOGS_DIR)) fs.mkdirSync(LOGS_DIR, { recursive: true });
2429

2530
logger = winston.createLogger({
2631
level: logLevel,
@@ -31,18 +36,20 @@ function getLogger(): winston.Logger {
3136
LOGS_DIR,
3237
`binharic-${new Date().toISOString().replace(/[:.]/g, "-")}.log`,
3338
),
34-
maxsize: 1024 * 1024 * 5, // 5MB
39+
maxsize: 1024 * 1024 * 5,
3540
maxFiles: 5,
3641
tailable: true,
3742
}),
3843
],
3944
});
4045

41-
// Also log to console in debug mode
4246
if (isDebugMode) {
4347
logger.add(
4448
new winston.transports.Console({
45-
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
49+
format: winston.format.combine(
50+
winston.format.colorize(),
51+
winston.format.simple(),
52+
),
4653
}),
4754
);
4855
}
@@ -54,9 +61,7 @@ const loggerProxy = new Proxy({} as winston.Logger, {
5461
get(_, prop: string) {
5562
const loggerInstance = getLogger();
5663
const value = loggerInstance[prop as keyof winston.Logger];
57-
if (typeof value === "function") {
58-
return value.bind(loggerInstance);
59-
}
64+
if (typeof value === "function") return value.bind(loggerInstance);
6065
return value;
6166
},
6267
});

src/paths.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import path from "path";
2+
import os from "os";
3+
4+
export function getConfigDir(): string {
5+
return path.join(os.homedir(), ".config", "binharic");
6+
}
7+
8+
export function getConfigPath(): string {
9+
return path.join(getConfigDir(), "config.json5");
10+
}
11+
12+
export function getHistoryPath(): string {
13+
return path.join(getConfigDir(), "history");
14+
}
15+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { describe, it, expect } from "vitest";
2+
import fs from "fs";
3+
import path from "path";
4+
import os from "os";
5+
6+
describe("Logger behavior in test mode", () => {
7+
it("should not create log files or directories when NODE_ENV=test", async () => {
8+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "binharic-logger-test-"));
9+
const overrideDir = path.join(tempDir, "logs-override");
10+
11+
process.env.NODE_ENV = "test";
12+
delete process.env.DEBUG_BINHARIC;
13+
delete process.env.DEBUG_TOBI;
14+
process.env.BINHARIC_LOG_DIR = overrideDir;
15+
16+
const mod = await import("@/logger.js");
17+
const logger = mod.default;
18+
logger.info("test message");
19+
20+
expect(fs.existsSync(overrideDir)).toBeFalsy();
21+
});
22+
});
23+

tests/agent/execution/escapeKeyCancelAgent.test.ts

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -150,20 +150,18 @@ describe("Escape Key to Cancel Agent Work", () => {
150150
expect(mockStopAgent).toHaveBeenCalled();
151151
});
152152

153-
it("should eventually transition to idle after cancel", (done) => {
153+
it("should eventually transition to idle after cancel", async () => {
154154
let status = "responding";
155155

156156
if (status === "responding") {
157157
status = "interrupted";
158158
}
159159

160-
setTimeout(() => {
161-
if (status === "interrupted") {
162-
status = "idle";
163-
}
164-
expect(status).toBe("idle");
165-
done();
166-
}, 100);
160+
await new Promise((resolve) => setTimeout(resolve, 100));
161+
if (status === "interrupted") {
162+
status = "idle";
163+
}
164+
expect(status).toBe("idle");
167165
});
168166
});
169167

tests/agent/performance/memoryProfiling.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,19 +44,25 @@ describe("Memory Profiling Tests", () => {
4444
await fs.writeFile(filePath, "x".repeat(fileSize));
4545
}
4646

47+
const warmupPath = path.join(testDir, `file-0.txt`);
48+
await fileTracker.read(warmupPath);
49+
fileTracker.clearTracking();
4750
forceGC();
51+
4852
const beforeMemory = getMemoryUsage();
4953

5054
for (let i = 0; i < fileCount; i++) {
5155
const filePath = path.join(testDir, `file-${i}.txt`);
5256
await fileTracker.read(filePath);
5357
}
5458

59+
forceGC();
60+
await new Promise((r) => setTimeout(r, 10));
5561
forceGC();
5662
const afterMemory = getMemoryUsage();
5763

5864
const memoryIncrease = afterMemory.heapUsed - beforeMemory.heapUsed;
59-
const expectedMaxIncrease = fileSize * fileCount * 2;
65+
const expectedMaxIncrease = fileSize * fileCount * 3;
6066

6167
expect(memoryIncrease).toBeLessThan(expectedMaxIncrease);
6268
});
@@ -201,7 +207,7 @@ describe("Memory Profiling Tests", () => {
201207
const afterOp = getMemoryUsage();
202208

203209
const memoryRetained = afterOp.heapUsed - beforeOp.heapUsed;
204-
const maxAcceptableRetention = content.length * 2;
210+
const maxAcceptableRetention = content.length * 3;
205211

206212
expect(memoryRetained).toBeLessThan(maxAcceptableRetention);
207213
});

0 commit comments

Comments
 (0)