Skip to content

Commit a9a2dc0

Browse files
authored
Merge pull request #8 from dakl/feat/terminal-emulator
feat: replace message UI with PTY terminal emulator
2 parents 4bbffb0 + 76e9fd0 commit a9a2dc0

72 files changed

Lines changed: 1444 additions & 5751 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

electron-builder.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ asarUnpack:
1111
- "**/node_modules/better-sqlite3/**"
1212
- "**/node_modules/bindings/**"
1313
- "**/node_modules/file-uri-to-path/**"
14+
- "**/node_modules/node-pty/**"
1415
publish:
1516
provider: github
1617
owner: dakl

package-lock.json

Lines changed: 38 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "codez",
3-
"version": "0.2.1",
3+
"version": "2.1.0",
44
"productName": "Codez",
55
"description": "A macOS desktop app for managing AI coding agent sessions across git worktrees.",
66
"author": "Daniel Klevebring",
@@ -27,6 +27,7 @@
2727
},
2828
"dependencies": {
2929
"better-sqlite3": "^12.6.2",
30+
"node-pty": "^1.0.0",
3031
"react-markdown": "^10.1.0",
3132
"rehype-highlight": "^7.0.2",
3233
"remark-gfm": "^4.0.1"
@@ -40,6 +41,8 @@
4041
"@types/react": "^19.2.14",
4142
"@types/react-dom": "^19.2.3",
4243
"@vitejs/plugin-react": "^5.1.4",
44+
"@xterm/addon-fit": "^0.10.0",
45+
"@xterm/xterm": "^5.5.0",
4346
"concurrently": "^8.2.2",
4447
"electron": "^40.4.1",
4548
"electron-builder": "^26.7.0",

resources/icon.png

741 KB
Loading

src/__fixtures__/claude-stream-permission.ndjson

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { describe, expect, it } from "vitest";
22
import { createAdapter } from "./agent-registry";
33
import { ClaudeAdapter } from "./claude-adapter";
4-
import { MistralAdapter } from "./mistral-adapter";
54

65
describe("createAdapter", () => {
76
it("returns a ClaudeAdapter for agent type 'claude'", () => {
@@ -13,15 +12,6 @@ describe("createAdapter", () => {
1312
expect(adapter).toBeInstanceOf(ClaudeAdapter);
1413
});
1514

16-
it("returns a MistralAdapter for agent type 'mistral'", () => {
17-
const adapter = createAdapter({
18-
agentType: "mistral",
19-
sessionId: "sess-1",
20-
worktreePath: "/tmp/wt",
21-
});
22-
expect(adapter).toBeInstanceOf(MistralAdapter);
23-
});
24-
2515
it("throws for unknown agent type", () => {
2616
expect(() =>
2717
createAdapter({
@@ -42,18 +32,4 @@ describe("createAdapter", () => {
4232
const args = adapter.buildStartArgs("test");
4333
expect(args).toContain("my-session");
4434
});
45-
46-
it("Mistral adapter builds correct arguments", () => {
47-
const adapter = createAdapter({
48-
agentType: "mistral",
49-
sessionId: "test-session",
50-
worktreePath: "/test/worktree",
51-
});
52-
const args = adapter.buildStartArgs("hello world");
53-
expect(args).toContain("-p");
54-
expect(args.some((arg) => arg.includes("hello world"))).toBe(true);
55-
// streaming is now a positional argument after -p
56-
expect(args).toContain("streaming");
57-
// Note: vibe doesn't support --session-id for new sessions like claude does
58-
});
5935
});

src/main/agents/agent-registry.test.ts.bak

Lines changed: 0 additions & 59 deletions
This file was deleted.

src/main/agents/agent-registry.ts

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,20 @@
11
import type { AgentType } from "../../shared/agent-types.js";
22
import { ClaudeAdapter } from "./claude-adapter.js";
3-
import { MistralAdapter } from "./mistral-adapter.js";
43

54
interface CreateAdapterOptions {
65
agentType: AgentType;
76
sessionId: string;
87
worktreePath: string;
9-
allowedTools?: string[];
108
additionalDirs?: string[];
11-
permissionMode?: string;
129
}
1310

14-
export function createAdapter(options: CreateAdapterOptions): ClaudeAdapter | MistralAdapter {
11+
export function createAdapter(options: CreateAdapterOptions): ClaudeAdapter {
1512
switch (options.agentType) {
1613
case "claude":
1714
return new ClaudeAdapter({
1815
sessionId: options.sessionId,
1916
worktreePath: options.worktreePath,
20-
allowedTools: options.allowedTools,
2117
additionalDirs: options.additionalDirs,
22-
permissionMode: options.permissionMode as "default" | "acceptEdits" | "bypassPermissions" | "plan",
23-
});
24-
case "mistral":
25-
return new MistralAdapter({
26-
sessionId: options.sessionId,
27-
worktreePath: options.worktreePath,
28-
allowedTools: options.allowedTools,
29-
additionalDirs: options.additionalDirs,
30-
permissionMode: options.permissionMode as "default" | "acceptEdits" | "bypassPermissions" | "plan",
3118
});
3219
default:
3320
throw new Error(`No adapter available for agent type: ${options.agentType}`);

src/main/agents/agent-registry.ts.bak

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/main/agents/claude-adapter.test.ts

Lines changed: 0 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -44,42 +44,6 @@ describe("ClaudeAdapter", () => {
4444
expect(args).not.toContain("--resume");
4545
expect(args).not.toContain("--continue");
4646
});
47-
48-
it("includes --allowedTools when allowedTools are provided", () => {
49-
const adapterWithTools = new ClaudeAdapter({
50-
sessionId: "test-session-1",
51-
worktreePath: "/tmp/worktree",
52-
allowedTools: ["Bash(git:*)", "Edit", "mcp__papershelf__*"],
53-
});
54-
const args = adapterWithTools.buildStartArgs("Hello");
55-
expect(args).toContain("--allowedTools");
56-
const toolsIndex = args.indexOf("--allowedTools");
57-
expect(args[toolsIndex + 1]).toBe("Bash(git:*)");
58-
expect(args[toolsIndex + 2]).toBe("Edit");
59-
expect(args[toolsIndex + 3]).toBe("mcp__papershelf__*");
60-
});
61-
62-
it("omits --allowedTools when none provided", () => {
63-
const args = adapter.buildStartArgs("Hello");
64-
expect(args).not.toContain("--allowedTools");
65-
});
66-
67-
it("includes --permission-mode when not default", () => {
68-
const adapterWithPerms = new ClaudeAdapter({
69-
sessionId: "test-session-1",
70-
worktreePath: "/tmp/worktree",
71-
permissionMode: "acceptEdits",
72-
});
73-
const args = adapterWithPerms.buildStartArgs("Hello");
74-
expect(args).toContain("--permission-mode");
75-
const modeIndex = args.indexOf("--permission-mode");
76-
expect(args[modeIndex + 1]).toBe("acceptEdits");
77-
});
78-
79-
it("omits --permission-mode when default", () => {
80-
const args = adapter.buildStartArgs("Hello");
81-
expect(args).not.toContain("--permission-mode");
82-
});
8347
});
8448

8549
describe("buildResumeArgs", () => {
@@ -103,20 +67,6 @@ describe("ClaudeAdapter", () => {
10367
const args = adapter.buildResumeArgs("Continue");
10468
expect(args).not.toContain("--session-id");
10569
});
106-
107-
it("includes --allowedTools on resume when provided", () => {
108-
const adapterWithTools = new ClaudeAdapter({
109-
sessionId: "test-session-1",
110-
worktreePath: "/tmp/worktree",
111-
allowedTools: ["Edit", "Read"],
112-
});
113-
adapterWithTools.setAgentSessionId("claude-session-abc");
114-
const args = adapterWithTools.buildResumeArgs("Continue");
115-
expect(args).toContain("--allowedTools");
116-
const toolsIndex = args.indexOf("--allowedTools");
117-
expect(args[toolsIndex + 1]).toBe("Edit");
118-
expect(args[toolsIndex + 2]).toBe("Read");
119-
});
12070
});
12171

12272
describe("parseLine", () => {
@@ -364,32 +314,6 @@ describe("ClaudeAdapter", () => {
364314
expect(event).toBeNull();
365315
});
366316

367-
it("maps control_request with can_use_tool to permission_request event", () => {
368-
const event = adapter.parseLine({
369-
type: "control_request",
370-
request_id: "req_1_abc123",
371-
request: {
372-
subtype: "can_use_tool",
373-
tool_name: "Bash",
374-
input: { command: "git commit -m 'fix'" },
375-
},
376-
});
377-
expect(event).not.toBeNull();
378-
expect(event?.type).toBe("permission_request");
379-
expect(event?.data.requestId).toBe("req_1_abc123");
380-
expect(event?.data.toolName).toBe("Bash");
381-
expect(event?.data.toolInput).toEqual({ command: "git commit -m 'fix'" });
382-
});
383-
384-
it("returns null for control_request with unknown subtype", () => {
385-
const event = adapter.parseLine({
386-
type: "control_request",
387-
request_id: "req_2",
388-
request: { subtype: "unknown_subtype" },
389-
});
390-
expect(event).toBeNull();
391-
});
392-
393317
it("returns null for non-delta stream events we don't map", () => {
394318
const event = adapter.parseLine({
395319
type: "stream_event",
@@ -424,22 +348,5 @@ describe("ClaudeAdapter", () => {
424348
expect(types).toContain("message_complete");
425349
expect(types).toContain("session_end");
426350
});
427-
428-
it("parses permission request fixture into expected events", () => {
429-
const lines = loadFixtureEvents("claude-stream-permission.ndjson");
430-
const events = adapter.parseLines(lines);
431-
432-
const types = events.map((e) => e.type);
433-
expect(types).toContain("session_start");
434-
expect(types).toContain("tool_use_start");
435-
expect(types).toContain("permission_request");
436-
expect(types).toContain("tool_result");
437-
expect(types).toContain("message_complete");
438-
expect(types).toContain("session_end");
439-
440-
const permEvent = events.find((e) => e.type === "permission_request");
441-
expect(permEvent.data.toolName).toBe("Bash");
442-
expect(permEvent.data.requestId).toBe("req_1_abc");
443-
});
444351
});
445352
});

0 commit comments

Comments
 (0)