Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Auto-detect text files, normalize to LF on commit
* text=auto

# Source code — always LF
*.ts text eol=lf
*.tsx text eol=lf
*.js text eol=lf
*.mjs text eol=lf
*.cjs text eol=lf
*.json text eol=lf
*.toml text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.md text eol=lf
*.css text eol=lf
*.html text eol=lf
*.svg text eol=lf
*.astro text eol=lf
*.jsonc text eol=lf
*.plist text eol=lf
*.td text eol=lf

# Shell scripts — always LF (won't run with CRLF)
*.sh text eol=lf
*.bash text eol=lf

# Binary files — don't touch line endings
*.png binary
*.jpg binary
*.jpeg binary
*.gif binary
*.ico binary
*.icns binary
*.woff binary
*.woff2 binary
*.eot binary
*.ttf binary
*.lockb binary
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,25 @@ bun run dev

> **Requirements:** [Codex CLI](https://github.com/openai/codex), Node.js 24+ or Bun, Git 2.30+, modern browser.

#### From Source - Windows

On Windows, the server must run on Node.js because Bun does not yet implement ConPTY. The `dev` command handles this automatically. After cloning:

```powershell
# Install dependencies (use public registry if you have a private npm registry configured)
bun install --registry https://registry.npmjs.org

# Build the server (one-time)
bun run build

# Start the dev environment
bun run dev
```

Then open **`http://localhost:5733`** in your browser.

> `bun run dev` auto-detects Windows and runs the Node.js server alongside the Vite frontend — same command as macOS/Linux.

## Features

### Multi-Agent, One Interface
Expand Down Expand Up @@ -148,6 +167,22 @@ bun run dist:desktop:linux # Linux AppImage
bun run dist:desktop:win # Windows installer
```

### Windows Development

`bun run dev` auto-detects Windows and runs the server on Node.js instead of Bun. The workflow is the same as macOS/Linux:

```powershell
# First-time: build the server
bun run build

# Start both frontend and backend
bun run dev
```

Open **`http://localhost:5733`**. After editing server source, rebuild with `bun run build` and restart.

> On Windows, `bun run dev` spawns Vite (via Bun) and the Node.js server as two coordinated processes — no manual terminal juggling or environment variables required.

### Isolated Development

Run alongside an existing Peak Code instance without port conflicts:
Expand Down
35 changes: 35 additions & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,25 @@ bun run dev

> **要求:** [Codex CLI](https://github.com/openai/codex)、Node.js 24+ 或 Bun、Git 2.30+、现代浏览器。

#### 从源码构建 - Windows

Windows 上服务器需用 Node.js 运行(Bun 尚未实现 ConPTY)。`dev` 命令已自动处理此问题。克隆项目后:

```powershell
# 安装依赖(如果配了私有 npm 仓库,需指定公网 registry)
bun install --registry https://registry.npmjs.org

# 构建服务器(一次性)
bun run build

# 启动开发环境
bun run dev
```

然后浏览器打开 **`http://localhost:5733`**。

> `bun run dev` 自动检测 Windows 并用 Node.js 运行服务器,同时启动 Vite 前端——和 macOS/Linux 同一个命令。

## 功能特性

### 多代理,统一界面
Expand Down Expand Up @@ -147,6 +166,22 @@ bun run dist:desktop:linux # Linux AppImage
bun run dist:desktop:win # Windows 安装包
```

### Windows 开发

`bun run dev` 自动检测 Windows 并用 Node.js 运行服务器,工作流与 macOS/Linux 一致:

```powershell
# 首次:构建服务器
bun run build

# 一键启动前后端
bun run dev
```

打开 **`http://localhost:5733`**。修改服务器源码后,运行 `bun run build` 重新构建并重启。

> Windows 上 `bun run dev` 会将 Vite(Bun)和 Node.js 服务器作为两个协调进程启动——无需手动开终端或配环境变量。

### 隔离开发

与现有 Peak Code 实例并行运行,避免端口冲突:
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"dev:web": "node scripts/dev-runner.ts dev:web",
"dev:marketing": "turbo run dev --filter=@peakcode/marketing",
"dev:desktop": "node scripts/dev-runner.ts dev:desktop",
"dev:win": "node scripts/dev-runner.ts dev:win",
"electron": "bun run start:desktop",
"electron:dev": "node scripts/dev-runner.ts dev:desktop --home-dir ./.peakcode/electron-dev",
"start": "turbo run start --filter=peakcode",
Expand Down
103 changes: 80 additions & 23 deletions scripts/dev-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ const MODE_ARGS = {
"--filter=@peakcode/web",
"--parallel",
],
"dev:win": [
"run",
"dev",
"--ui=tui",
"--filter=@peakcode/contracts",
"--filter=@peakcode/web",
"--parallel",
],
} as const satisfies Record<string, ReadonlyArray<string>>;

type DevMode = keyof typeof MODE_ARGS;
Expand Down Expand Up @@ -192,7 +200,7 @@ export function createDevRunnerEnv({
delete output.PEAKCODE_LOG_WS_EVENTS;
}

if (mode === "dev") {
if (mode === "dev" || mode === "dev:win") {
output.PEAKCODE_MODE = "web";
delete output.PEAKCODE_DESKTOP_WS_URL;
}
Expand Down Expand Up @@ -451,30 +459,79 @@ export function runDevRunnerWithInput(input: DevRunnerCliInput) {
return;
}

const child = yield* ChildProcess.make(
"turbo",
[...MODE_ARGS[input.mode], ...input.turboArgs],
{
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
// On Windows the server must run on Node.js because Bun does not
// implement ConPTY. When the user runs `dev` on Windows — or explicitly
// runs `dev:win` — we split into two processes: turbo drives contracts +
// Vite, and Node.js runs the pre-built server directly.
const useWinSplit =
input.mode === "dev:win" ||
(input.mode === "dev" && process.platform === "win32");

if (useWinSplit) {
const pathService = yield* Path.Path;
const serverCwd = pathService.join(process.cwd(), "apps", "server");
const serverScript = pathService.join(serverCwd, "dist", "index.mjs");

const common = {
stdin: "inherit" as const,
stdout: "inherit" as const,
stderr: "inherit" as const,
env,
extendEnv: false,
// Windows needs shell mode to resolve .cmd shims (e.g. bun.cmd).
shell: process.platform === "win32",
// Keep turbo in the same process group so terminal signals (Ctrl+C)
// reach it directly. Effect defaults to detached: true on non-Windows,
// which would put turbo in a new group and require manual forwarding.
detached: false,
forceKillAfter: "1500 millis",
},
);

const exitCode = yield* child.exitCode;
if (exitCode !== 0) {
return yield* new DevRunnerError({
message: `turbo exited with code ${exitCode}`,
extendEnv: false as const,
detached: false as const,
forceKillAfter: "1500 millis" as const,
};

const turboChild = yield* ChildProcess.make(
"turbo",
[...MODE_ARGS["dev:win"], ...input.turboArgs],
{ ...common, shell: process.platform === "win32" },
);

const serverChild = yield* ChildProcess.make("node", [serverScript], {
...common,
cwd: serverCwd,
});

const [turboExit, serverExit] = yield* Effect.all(
[turboChild.exitCode, serverChild.exitCode],
{ concurrency: "unbounded" },
);

if (turboExit !== 0 || serverExit !== 0) {
const parts: Array<string> = [];
if (turboExit !== 0) parts.push(`turbo exited with code ${turboExit}`);
if (serverExit !== 0) parts.push(`server exited with code ${serverExit}`);
return yield* new DevRunnerError({
message: parts.join("; "),
});
}
} else {
const child = yield* ChildProcess.make(
"turbo",
[...MODE_ARGS[input.mode], ...input.turboArgs],
{
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
env,
extendEnv: false,
// Windows needs shell mode to resolve .cmd shims (e.g. bun.cmd).
shell: process.platform === "win32",
// Keep turbo in the same process group so terminal signals (Ctrl+C)
// reach it directly. Effect defaults to detached: true on non-Windows,
// which would put turbo in a new group and require manual forwarding.
detached: false,
forceKillAfter: "1500 millis",
},
);

const exitCode = yield* child.exitCode;
if (exitCode !== 0) {
return yield* new DevRunnerError({
message: `turbo exited with code ${exitCode}`,
});
}
}
}).pipe(
Effect.mapError((cause) =>
Expand Down