Skip to content
Merged
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ jobs:
uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: https://registry.npmjs.org
cache: npm

- name: Install dependencies
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Carbon Code is aimed at personal developer workflows: open a repository, let the
agent read and search the codebase, review planned edits, approve shell commands,
run validation, and keep a concise session trail.

Simplified Chinese documentation: [README.zh-CN.md](README.zh-CN.md)

## Install

Requires Node.js 22 or newer.
Expand All @@ -17,6 +19,13 @@ cd path/to/project
carboncode
```

On Windows PowerShell, if `npm` fails with a script execution policy error, use
`npm.cmd` instead:

```powershell
npm.cmd install -g @carboncode/cli
```

Short command:

```bash
Expand Down
10 changes: 10 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
Carbon Code 面向个人开发者工作流:进入一个仓库,让智能体读取和搜索代码、规划
修改、展示 diff、在执行 shell 命令前请求确认、按需运行验证,并输出简洁结果。

英文文档:[README.md](README.md)

## 安装

要求 Node.js 22 或更新版本。
Expand All @@ -15,6 +17,14 @@ cd path/to/project
carboncode
```

Windows PowerShell 运行 `npm` 命令时,如果提示 `npm.ps1` 被禁止执行,请改用 `npm.cmd`,例如:

```powershell
npm.cmd install
npm.cmd run dev
npm.cmd run verify
```

短命令:

```bash
Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@carboncode/cli",
"version": "0.1.2",
"version": "0.1.5",
"description": "Chinese-first DeepSeek-powered terminal coding agent for personal developer workflows.",
"type": "module",
"bin": {
Expand Down
22 changes: 14 additions & 8 deletions src/server/api/health.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { existsSync, readdirSync, statSync } from "node:fs";
import { homedir } from "node:os";
import { join } from "node:path";
import { listSessions } from "../../memory/session.js";
import { VERSION } from "../../version.js";
import type { DashboardContext } from "../context.js";
import { type DashboardContext, resolveCarboncodeHome } from "../context.js";
import type { ApiResult } from "../router.js";

interface DirStat {
Expand Down Expand Up @@ -56,6 +54,17 @@ function dirSize(path: string): DirStat {
return { path, exists: true, fileCount, totalBytes };
}

function countSessionFiles(path: string): number {
if (!existsSync(path)) return 0;
try {
return readdirSync(path).filter(
(file) => file.endsWith(".jsonl") && !file.endsWith(".events.jsonl"),
).length;
} catch {
return 0;
}
}

export async function handleHealth(
method: string,
_rest: string[],
Expand All @@ -65,8 +74,7 @@ export async function handleHealth(
if (method !== "GET") {
return { status: 405, body: { error: "GET only" } };
}
const home = homedir();
const carboncodeHome = join(home, ".carboncode");
const carboncodeHome = resolveCarboncodeHome(ctx.configPath);

const sessionsStat = dirSize(join(carboncodeHome, "sessions"));
const memoryStat = dirSize(join(carboncodeHome, "memory"));
Expand All @@ -81,8 +89,6 @@ export async function handleHealth(
}
}

const sessions = listSessions();

return {
status: 200,
body: {
Expand All @@ -91,7 +97,7 @@ export async function handleHealth(
carboncodeHome,
sessions: {
path: sessionsStat.path,
count: sessions.length,
count: countSessionFiles(sessionsStat.path),
totalBytes: sessionsStat.totalBytes,
},
memory: {
Expand Down
16 changes: 8 additions & 8 deletions src/server/api/memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,25 @@ import {
unlinkSync,
writeFileSync,
} from "node:fs";
import { homedir } from "node:os";
import { basename, dirname, join, resolve as resolvePath } from "node:path";
import {
PROJECT_MEMORY_FILE,
findProjectMemoryPath,
resolveProjectMemoryWritePath,
} from "../../memory/project.js";
import type { DashboardContext } from "../context.js";
import { type DashboardContext, resolveCarboncodeHome } from "../context.js";
import type { ApiResult } from "../router.js";

function projectHash(rootDir: string): string {
return createHash("sha1").update(resolvePath(rootDir)).digest("hex").slice(0, 16);
}

function globalMemoryDir(): string {
return join(homedir(), ".carboncode", "memory", "global");
function globalMemoryDir(carboncodeHome: string): string {
return join(carboncodeHome, "memory", "global");
}

function projectMemoryDir(rootDir: string): string {
return join(homedir(), ".carboncode", "memory", projectHash(rootDir));
function projectMemoryDir(carboncodeHome: string, rootDir: string): string {
return join(carboncodeHome, "memory", projectHash(rootDir));
}

interface WriteBody {
Expand Down Expand Up @@ -74,8 +73,9 @@ export async function handleMemory(
ctx: DashboardContext,
): Promise<ApiResult> {
const cwd = ctx.getCurrentCwd?.();
const globalDir = globalMemoryDir();
const projectMemDir = cwd ? projectMemoryDir(cwd) : "";
const carboncodeHome = resolveCarboncodeHome(ctx.configPath);
const globalDir = globalMemoryDir(carboncodeHome);
const projectMemDir = cwd ? projectMemoryDir(carboncodeHome, cwd) : "";

if (method === "GET" && rest.length === 0) {
const existingProjectMemory = cwd ? findProjectMemoryPath(cwd) : null;
Expand Down
9 changes: 9 additions & 0 deletions src/server/context.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
/** Callbacks (not refs) so endpoints read live loop state per request, not a frozen closure. */

import { homedir } from "node:os";
import { basename, dirname, join } from "node:path";
import type { McpServerSummary } from "../cli/ui/slash/types.js";
import type { EditMode } from "../config.js";
import type { CacheFirstLoop } from "../loop.js";
import type { ToolRegistry } from "../tools.js";
import type { JobRegistry } from "../tools/jobs.js";

export function resolveCarboncodeHome(configPath: string): string {
if (!configPath.trim()) return join(homedir(), ".carboncode");
const configDir = dirname(configPath);
if (basename(configDir).toLowerCase() === ".carboncode") return configDir;
return join(configDir, ".carboncode");
}

export interface DashboardContext {
/** Caller resolves via `defaultConfigPath()`; module deliberately avoids `homedir()` so tests can redirect. */
configPath: string;
Expand Down
8 changes: 8 additions & 0 deletions tests/cli-bare-routing.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { writeConfig } from "../src/config.js";

const HEAP_REEXEC_ENV = "REASONIX_HEAP_REEXEC";
const codeCommand = vi.fn(async () => {});
const chatCommand = vi.fn(async () => {});
const setupCommand = vi.fn(async () => {});
Expand All @@ -25,6 +26,7 @@ describe("bare CLI routing", () => {
let cwd: string;
const origHome = process.env.HOME;
const origUserProfile = process.env.USERPROFILE;
const origHeapReexec = process.env[HEAP_REEXEC_ENV];
const origArgv = process.argv;
const origCwd = process.cwd();
let stderr: ReturnType<typeof vi.spyOn>;
Expand All @@ -38,6 +40,7 @@ describe("bare CLI routing", () => {
cwd = realpathSync(mkdtempSync(join(tmpdir(), "carboncode-cli-cwd-")));
process.env.HOME = home;
process.env.USERPROFILE = home;
process.env[HEAP_REEXEC_ENV] = "1";
process.chdir(cwd);
codeCommand.mockClear();
chatCommand.mockClear();
Expand All @@ -63,6 +66,11 @@ describe("bare CLI routing", () => {
} else {
process.env.USERPROFILE = origUserProfile;
}
if (origHeapReexec === undefined) {
delete process.env[HEAP_REEXEC_ENV];
} else {
process.env[HEAP_REEXEC_ENV] = origHeapReexec;
}
});

it("routes bare carboncode to code mode rooted at cwd", async () => {
Expand Down
1 change: 1 addition & 0 deletions tests/helpers/codex-parity-harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ function runCommand(
const child = spawn(command, [...args], {
cwd,
env: { ...process.env, CI: "1" },
shell: process.platform === "win32",
stdio: ["ignore", "pipe", "pipe"],
});
const chunks: Buffer[] = [];
Expand Down