diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d7f15f..9e24466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 diff --git a/README.md b/README.md index c8be058..969458e 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/README.zh-CN.md b/README.zh-CN.md index d6d9999..6d7c4c1 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -5,6 +5,8 @@ Carbon Code 面向个人开发者工作流:进入一个仓库,让智能体读取和搜索代码、规划 修改、展示 diff、在执行 shell 命令前请求确认、按需运行验证,并输出简洁结果。 +英文文档:[README.md](README.md) + ## 安装 要求 Node.js 22 或更新版本。 @@ -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 diff --git a/package-lock.json b/package-lock.json index ce39a1d..182f6dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@carboncode/cli", - "version": "0.1.2", + "version": "0.1.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@carboncode/cli", - "version": "0.1.2", + "version": "0.1.5", "license": "MIT", "dependencies": { "cli-highlight": "^2.1.11", @@ -2225,7 +2225,7 @@ }, "node_modules/@types/ws": { "version": "8.18.1", - "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "dev": true, "license": "MIT", @@ -6891,7 +6891,7 @@ }, "node_modules/ws": { "version": "8.20.1", - "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.1.tgz", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "license": "MIT", "engines": { diff --git a/package.json b/package.json index 4aeaec7..7be76a3 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/server/api/health.ts b/src/server/api/health.ts index 6583d11..d43fcea 100644 --- a/src/server/api/health.ts +++ b/src/server/api/health.ts @@ -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 { @@ -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[], @@ -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")); @@ -81,8 +89,6 @@ export async function handleHealth( } } - const sessions = listSessions(); - return { status: 200, body: { @@ -91,7 +97,7 @@ export async function handleHealth( carboncodeHome, sessions: { path: sessionsStat.path, - count: sessions.length, + count: countSessionFiles(sessionsStat.path), totalBytes: sessionsStat.totalBytes, }, memory: { diff --git a/src/server/api/memory.ts b/src/server/api/memory.ts index e244ce5..28871d0 100644 --- a/src/server/api/memory.ts +++ b/src/server/api/memory.ts @@ -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 { @@ -74,8 +73,9 @@ export async function handleMemory( ctx: DashboardContext, ): Promise { 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; diff --git a/src/server/context.ts b/src/server/context.ts index 83fa7c8..50faefa 100644 --- a/src/server/context.ts +++ b/src/server/context.ts @@ -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; diff --git a/tests/cli-bare-routing.test.ts b/tests/cli-bare-routing.test.ts index 8c99a98..9556d7f 100644 --- a/tests/cli-bare-routing.test.ts +++ b/tests/cli-bare-routing.test.ts @@ -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 () => {}); @@ -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; @@ -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(); @@ -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 () => { diff --git a/tests/helpers/codex-parity-harness.ts b/tests/helpers/codex-parity-harness.ts index 67e127c..2bb03c6 100644 --- a/tests/helpers/codex-parity-harness.ts +++ b/tests/helpers/codex-parity-harness.ts @@ -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[] = [];