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
2 changes: 1 addition & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
"es2020": true,
"node": true
}
}
}
10 changes: 5 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ on:
workflow_dispatch:
inputs:
arch:
description: "Architecture to build (arm64 | x64 | both)"
description: 'Architecture to build (arm64 | x64 | both)'
required: false
default: "both"
default: 'both'
dry_run:
description: "Build signed+stapled but DO NOT publish a GitHub Release (artifacts only)"
description: 'Build signed+stapled but DO NOT publish a GitHub Release (artifacts only)'
required: false
default: "false"
default: 'false'

permissions:
contents: read
Expand Down Expand Up @@ -277,7 +277,7 @@ jobs:
xcrun stapler validate "$DMG"
done

- name: "End-to-end check: app inside DMG passes Gatekeeper"
- name: 'End-to-end check: app inside DMG passes Gatekeeper'
run: |
set -euo pipefail
for DMG in release/*.dmg; do
Expand Down
7 changes: 7 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"semi": true,
"singleQuote": true,
"trailingComma": "es5",
"printWidth": 100,
"tabWidth": 2
}
25 changes: 20 additions & 5 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@ Thanks for your interest in contributing! We favor small, focused PRs and clear
## Quick Start

Prerequisites

- Node.js 18+ and Git
- Optional (recommended for end‑to‑end):
- Codex CLI (`npm install -g @openai/codex` or `brew install codex`; then run `codex` to authenticate)
- GitHub CLI (`brew install gh`; then `gh auth login`)

Setup

```
# Fork this repo, then clone your fork
git clone https://github.com/<you>/emdash.git
Expand Down Expand Up @@ -38,59 +40,69 @@ Tip: During development, the renderer hot‑reloads. Changes to the Electron mai

## Development Workflow

1) Create a feature branch
1. Create a feature branch

```
git checkout -b feat/<short-slug>
```

2) Make changes and keep PRs small and focused
2. Make changes and keep PRs small and focused

- Prefer a series of small PRs over one large one.
- Include UI screenshots/GIFs when modifying the interface.
- Update docs (README or inline help) when behavior changes.

3) Run checks locally
3. Run checks locally

```
npm run type-check
npm run lint
npm run build
```

4) Commit using Conventional Commits
4. Commit using Conventional Commits

- `feat:` – new user‑facing capability
- `fix:` – bug fix
- `chore:`, `refactor:`, `docs:`, `perf:`, `test:` etc.

Examples

```
fix(chat): preserve stream state across workspace switches

feat(ci): add type-check + build workflow for PRs
```

5) Open a Pull Request
5. Open a Pull Request

- Describe the change, rationale, and testing steps.
- Link related Issues.
- Keep the PR title in Conventional Commit format if possible.

## Code Style and Patterns

TypeScript + ESLint

- Keep code type‑safe. Run `npm run type-check` before pushing.
- Run `npm run lint` and address warnings where reasonable.

Electron main (Node side)

- Prefer `execFile` over `exec` to avoid shell quoting issues.
- Never write logs into Git worktrees. Stream logs belong in the Electron `userData` folder.
- Be conservative with console logging; noisy logs reduce signal. Use clear prefixes.

Git and worktrees

- The app creates worktrees in a sibling `../worktrees/` folder.
- Do not delete worktree folders from Finder/Explorer; if you need cleanup, use:
- `git worktree prune` (from the main repo)
- or the in‑app workspace removal
- The file `codex-stream.log` is intentionally excluded from Git status and auto‑ignored in new worktrees.

Renderer (React)

- Components live under `src/renderer/components`; hooks under `src/renderer/hooks`.
- Streaming UI conventions:
- “Reasoning” content renders inside a collapsible.
Expand All @@ -100,20 +112,23 @@ Renderer (React)
- Aim for accessible elements (labels, `aria-*` where appropriate).

Local DB (SQLite)

- Location (Electron `app.getPath('userData')`):
- macOS: `~/Library/Application Support/emdash/emdash.db`
- Linux: `~/.config/emdash/emdash.db`
- Windows: `%APPDATA%\emdash\emdash.db`
- Reset: quit the app, delete the file, relaunch (the schema is recreated).

## Issue Reports and Feature Requests

- Use GitHub Issues. Include:
- OS, Node version
- Steps to reproduce
- Relevant logs (renderer console, terminal output)
- Screenshots/GIFs for UI issues

## Release Process (maintainers)

- Bump version in `package.json`.
- Tag and publish a GitHub Release.
- Keep the CHANGELOG concise and user‑facing.
1 change: 0 additions & 1 deletion LICENSE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

19 changes: 19 additions & 0 deletions instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,17 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Prereqs

- Node 18+, Git 2.38+, pnpm (or npm/yarn)
- Electron + Vite scaffold (or create one)
- Optional CLIs installed on PATH: `aider`, `ollama`, `anthropic`, `openai`

---

## Step 1 — Scaffold & Preload

**Tasks**

- Create or use an Electron+Vite project.
- In `BrowserWindow`, enable `preload`, `contextIsolation:true`, `nodeIntegration:false`.
- In `preload.ts`, `contextBridge.exposeInMainWorld('ipc', { invoke, on })`.
Expand All @@ -22,7 +25,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 2 — PTY & Terminal UI Basics

**Tasks**

- Install: `pnpm add node-pty xterm && pnpm add -D @types/node`
- Create `src/main/ptyManager.ts` with:
- `startPty(id, cwd, shell?) -> IPty` (stores in `Map`)
Expand All @@ -41,7 +46,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 3 — Worktrees (Branch Isolation)

**Tasks**

- Add `src/main/git.ts` with `ensureWorktree(mainRepo, name, branch): Promise<string>` using `child_process.execFile('git', ['worktree','add', worktreePath, branch], { cwd: mainRepo })`. Reuse if exists.
- UI “New Session” modal:
- Pick **main repo root**, enter **branch**, optional **worktree name** (default = branch).
Expand All @@ -52,7 +59,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 4 — Invoke CLI Assistants (Inside PTY)

**Tasks**

- Add `ipcMain.handle('agent:launchCli', ({ id, cmd, args }))` → `writePty(id, \`${cmd} ${args.join(' ')}\r\`)`.
- UI: “Launch CLI” button with presets (`aider`, `ollama`, `anthropic`, `openai`) + freeform args.
- Optional: PATH check (`which`/`where`) → show install tips if missing.
Expand All @@ -62,7 +71,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 5 — Multiple Sessions/Tabs

**Tasks**

- Simple session store: `{ id, label, cwd, cli? }`.
- Render one `TerminalPane` per session (tabs or splits).
- Controls per session: **Kill**, **Clear**, **Resize to fit**.
Expand All @@ -72,7 +83,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 6 — BYO Keys (Local Env)

**Tasks**

- Dev only: `dotenv` in **main**; ship `.env.example` with commented keys (e.g., `ANTHROPIC_API_KEY=`, `OPENAI_API_KEY=`).
- Pass `env` to `node-pty.spawn` (merge `process.env` + optional per-session overrides stored locally).
- Settings UI: allow per-session env overrides (never send secrets to renderer except for display with masking).
Expand All @@ -82,7 +95,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 7 — Quality-of-Life

**Tasks**

- Resize: call `pty.resize()` on container resize (e.g., via `ResizeObserver`).
- Scrollback: 5k–10k lines in xterm.
- Save logs: stream PTY output to `${worktree}/.agentlogs/session-YYYYMMDD-HHMM.log`.
Expand All @@ -93,7 +108,9 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Step 8 — Packaging & OSS Hygiene

**Tasks**

- Add `LICENSE` (MIT), `.env.example`, `.gitignore` for `.env`, `CONTRIBUTING.md`, `SECURITY.md`.
- Configure packaging (`electron-builder`/`electron-vite`); rebuild native deps if needed.
- Smoke test packaged app on your target OSes.
Expand All @@ -103,6 +120,7 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Minimal “Ask Codex” Prompts (per step)

- **Step 1**: “Add preload and secure IPC in Electron+Vite; expose `ipc.invoke`/`ipc.on`; implement a ping handler.”
- **Step 2**: “Add node-pty in main, xterm in renderer; wire IPC to echo shell output and keystrokes.”
- **Step 3**: “Implement `ensureWorktree(mainRepo, name, branch)` using `git worktree`; add a modal to create and start a session.”
Expand All @@ -115,6 +133,7 @@ Goal: Electron app that opens **multiple pseudo-terminals**, one per **Git workt
---

## Gotchas (read once)

- **node-pty** native ABI must match Electron: use `electron-rebuild` if needed.
- **macOS GUI PATH** may be empty: set PATH in `env` or use login shell.
- **Windows quoting**: keep args simple or wrap in `.cmd`/`.sh`.
Expand Down
2 changes: 1 addition & 1 deletion postcss.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ module.exports = {
tailwindcss: {},
autoprefixer: {},
},
}
};
13 changes: 6 additions & 7 deletions src/main/app/lifecycle.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { app, BrowserWindow } from 'electron'
import { createMainWindow } from './window'
import { app, BrowserWindow } from 'electron';
import { createMainWindow } from './window';

export function registerAppLifecycle() {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
app.quit();
}
})
});

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow()
createMainWindow();
}
})
});
}

30 changes: 15 additions & 15 deletions src/main/app/window.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { BrowserWindow } from 'electron'
import { join } from 'path'
import { isDev } from '../utils/dev'
import { registerExternalLinkHandlers } from '../utils/externalLinks'
import { BrowserWindow } from 'electron';
import { join } from 'path';
import { isDev } from '../utils/dev';
import { registerExternalLinkHandlers } from '../utils/externalLinks';

let mainWindow: BrowserWindow | null = null
let mainWindow: BrowserWindow | null = null;

export function createMainWindow(): BrowserWindow {
mainWindow = new BrowserWindow({
Expand All @@ -20,31 +20,31 @@ export function createMainWindow(): BrowserWindow {
},
titleBarStyle: 'hiddenInset',
show: false,
})
});

if (isDev) {
mainWindow.loadURL('http://localhost:3000')
mainWindow.loadURL('http://localhost:3000');
} else {
// renderer build outputs to dist/renderer
mainWindow.loadFile(join(__dirname, '..', '..', 'renderer', 'index.html'))
mainWindow.loadFile(join(__dirname, '..', '..', 'renderer', 'index.html'));
}

// Route external links to the user’s default browser
registerExternalLinkHandlers(mainWindow, isDev)
registerExternalLinkHandlers(mainWindow, isDev);

// Show when ready
mainWindow.once('ready-to-show', () => {
mainWindow?.show()
})
mainWindow?.show();
});

// Cleanup reference on close
mainWindow.on('closed', () => {
mainWindow = null
})
mainWindow = null;
});

return mainWindow
return mainWindow;
}

export function getMainWindow(): BrowserWindow | null {
return mainWindow
return mainWindow;
}
Loading