diff --git a/AGENTS.md b/AGENTS.md index a40a61e85..18bd883dd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -64,4 +64,3 @@ Start here. Load only the linked `agents/` docs that are relevant to the task. - Add renderer typings in `src/renderer/types/electron-api.d.ts` for any new IPC method. - Treat `src/main/services/ptyManager.ts`, `src/main/services/ssh/**`, `src/main/db/**`, and updater code as high risk. - Avoid editing `dist/`, `release/`, and `build/` unless the task is explicitly about packaging or updater/signing behavior. -- The docs app in `docs/` is separate from the Electron renderer and also defaults to port `3000`. diff --git a/agents/architecture/overview.md b/agents/architecture/overview.md index 27de85a42..3930599ed 100644 --- a/agents/architecture/overview.md +++ b/agents/architecture/overview.md @@ -5,7 +5,6 @@ - `src/main/`: Electron main process, IPC, services, database, PTY orchestration, updater, SSH, integrations - `src/renderer/`: React UI, task views, terminals, diff review, settings, skills, MCP, kanban - `src/shared/`: provider registry, IPC helpers, shared MCP/diff/SSH/task utilities -- `docs/`: separate Next.js and Fumadocs site ## Boot Sequence diff --git a/agents/architecture/renderer.md b/agents/architecture/renderer.md index 8977772db..6222d48a1 100644 --- a/agents/architecture/renderer.md +++ b/agents/architecture/renderer.md @@ -35,4 +35,4 @@ ## When Editing Here - Keep renderer IPC usage in sync with `src/renderer/types/electron-api.d.ts`. -- If you change user-visible workflows, update the matching page in `docs/` when appropriate. +- If you change user-visible workflows, update the public docs site when appropriate. diff --git a/agents/conventions/config-files.md b/agents/conventions/config-files.md index 659a7aebd..6ea755487 100644 --- a/agents/conventions/config-files.md +++ b/agents/conventions/config-files.md @@ -17,5 +17,4 @@ ## Repo Rules - avoid editing `dist/`, `release/`, and `build/` unless the task is explicitly about packaging or signing -- the docs app in `docs/` is separate from the Electron renderer - update the narrowest relevant page in `agents/` instead of growing `AGENTS.md` diff --git a/agents/quickstart.md b/agents/quickstart.md index 3ddc8519c..324b8e07f 100644 --- a/agents/quickstart.md +++ b/agents/quickstart.md @@ -5,7 +5,6 @@ - Node: `22.20.0` from `.nvmrc` - Package manager: `pnpm@10.28.2` - Electron app root: this repo -- Docs app: `docs/` ## Core Commands @@ -28,17 +27,8 @@ pnpm run type-check pnpm exec vitest run ``` -## Docs Commands - -```bash -pnpm run docs -pnpm run docs:build -pnpm --dir docs run types:check -``` - ## Important Notes - `pnpm test` is a shortcut for `vitest run`. -- The docs app and the Electron renderer both default to port `3000`. - After native dependency changes (`sqlite3`, `node-pty`, `keytar`), run `pnpm run rebuild`. - Husky and lint-staged run formatting and linting on staged files during commit. diff --git a/agents/workflows/testing.md b/agents/workflows/testing.md index b3d83a19a..427b8c340 100644 --- a/agents/workflows/testing.md +++ b/agents/workflows/testing.md @@ -37,4 +37,3 @@ pnpm exec vitest run - after IPC changes: rerun the affected Vitest file and confirm `electron-api.d.ts` - after worktree or PTY changes: rerun the closest main-process service tests -- after docs changes: run `pnpm --dir docs run types:check` diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index bae61bd64..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -node_modules - -.DS_Store -.cache -.vercel -.output -.nitro -.next -/build/ -/api/ -/server/build -/public/build -/test-results/ -/playwright-report/ -/blob-report/ -/playwright/.cache/ -.tanstack - -src/routeTree.gen.ts -.source/ -next-env.d.ts -*.tsbuildinfo -public/md-src/ diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index d219822c4..000000000 --- a/docs/README.md +++ /dev/null @@ -1,14 +0,0 @@ -# Emdash - -This is a Tanstack Start application generated with -[Create Fumadocs](https://github.com/fuma-nama/fumadocs). - -Run development server: - -```bash -pnpm run dev -# or -pnpm dev -# or -yarn dev -``` diff --git a/docs/app/[[...slug]]/layout.tsx b/docs/app/[[...slug]]/layout.tsx deleted file mode 100644 index ecdcbe89b..000000000 --- a/docs/app/[[...slug]]/layout.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { source } from '@/lib/source'; -import { DocsLayout } from 'fumadocs-ui/layouts/docs'; -import type { ReactNode } from 'react'; -import type { Node } from 'fumadocs-core/page-tree'; -import { baseOptions } from '@/lib/layout.shared'; - -const betaPages = new Set(['/automations']); - -function addBetaBadges(nodes: Node[]): Node[] { - return nodes.map((node) => { - if (node.type === 'page' && betaPages.has(node.url)) { - return { - ...node, - name: ( - <> - {node.name} - - Beta - - - ), - }; - } - if (node.type === 'folder' && node.children) { - return { ...node, children: addBetaBadges(node.children) }; - } - return node; - }); -} - -export default function Layout({ children }: { children: ReactNode }) { - const tree = { - ...source.pageTree, - children: addBetaBadges(source.pageTree.children), - }; - - return ( - - {children} - - ); -} diff --git a/docs/app/[[...slug]]/page.tsx b/docs/app/[[...slug]]/page.tsx deleted file mode 100644 index 091999073..000000000 --- a/docs/app/[[...slug]]/page.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { source } from '@/lib/source'; -import { DocsPage, DocsBody, DocsDescription, DocsTitle } from 'fumadocs-ui/page'; -import { notFound } from 'next/navigation'; -import defaultMdxComponents from 'fumadocs-ui/mdx'; -import type { Metadata } from 'next'; -import type { MDXComponents } from 'mdx/types'; -import { CopyMarkdownButton } from '@/components/CopyMarkdownButton'; -import { CopyEmailButton } from '@/components/CopyEmailButton'; -import { LastUpdated } from '@/components/LastUpdated'; -import { getGithubLastEdit } from 'fumadocs-core/content/github'; - -async function getLastModifiedFromGitHub(filePath: string): Promise { - if (process.env.NODE_ENV === 'development') { - return null; - } - - try { - const time = await getGithubLastEdit({ - owner: 'generalaction', - repo: 'emdash', - path: `docs/content/docs/${filePath}.mdx`, - token: process.env.GIT_TOKEN ? `Bearer ${process.env.GIT_TOKEN}` : undefined, - }); - return time ? new Date(time) : null; - } catch { - return null; - } -} - -export default async function Page({ params }: { params: Promise<{ slug?: string[] }> }) { - const { slug } = await params; - const page = source.getPage(slug); - - if (!page) { - notFound(); - } - - const MDX = page.data.body; - - // Prefer plugin-derived lastModified, fallback to GitHub API - let lastModified: Date | undefined = page.data.lastModified; - if (!lastModified) { - const filePath = slug?.join('/') || 'index'; - lastModified = (await getLastModifiedFromGitHub(filePath)) ?? undefined; - } - - const components = { - ...defaultMdxComponents, - CopyEmailButton, - } as unknown as MDXComponents; - - return ( - - {page.data.title} - {page.data.description} -
- -
- - - - {lastModified && } -
- ); -} - -export async function generateStaticParams() { - return source.generateParams(); -} - -export async function generateMetadata(props: { - params: Promise<{ slug?: string[] }>; -}): Promise { - const params = await props.params; - const page = source.getPage(params.slug); - - if (!page) notFound(); - - return { - title: page.data.title, - description: page.data.description, - }; -} diff --git a/docs/app/api/search/route.ts b/docs/app/api/search/route.ts deleted file mode 100644 index b02d7cd3a..000000000 --- a/docs/app/api/search/route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { source } from '@/lib/source'; -import { createFromSource } from 'fumadocs-core/search/server'; - -export const { GET } = createFromSource(source, { - language: 'english', -}); diff --git a/docs/app/global.css b/docs/app/global.css deleted file mode 100644 index b17f8a2e9..000000000 --- a/docs/app/global.css +++ /dev/null @@ -1,18 +0,0 @@ -@import 'tailwindcss'; -@import 'fumadocs-ui/css/neutral.css'; -@import 'fumadocs-ui/css/preset.css'; - -/* Custom colors from Emdash palette */ -:root { - --color-fd-primary: #c26157; - --color-fd-primary-foreground: #f7fbfc; - --color-fd-accent: #e8ebee; - --color-fd-accent-foreground: #1f2931; -} - -.dark { - --color-fd-primary: #c26157; - --color-fd-primary-foreground: #eef0f2; - --color-fd-accent: #212a2d; - --color-fd-accent-foreground: #eef0f2; -} diff --git a/docs/app/layout.tsx b/docs/app/layout.tsx deleted file mode 100644 index 59df42f33..000000000 --- a/docs/app/layout.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import './global.css'; -import { RootProvider } from 'fumadocs-ui/provider/next'; -import type { ReactNode } from 'react'; - -export default function RootLayout({ children }: { children: ReactNode }) { - return ( - - - {children} - - - ); -} - -export const metadata = { - title: 'Emdash - Docs', - description: 'Open source Agentic Development Environment', - icons: { - icon: '/brand/favicon.ico', - }, -}; diff --git a/docs/components/Changelog.tsx b/docs/components/Changelog.tsx deleted file mode 100644 index 8d8ff6548..000000000 --- a/docs/components/Changelog.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import React from 'react'; - -interface GitHubRelease { - id: number; - tag_name: string; - name: string; - body: string | null; - published_at: string; - html_url: string; - draft: boolean; - prerelease: boolean; -} - -async function getGithubReleases(): Promise { - try { - const response = await fetch('https://api.github.com/repos/generalaction/emdash/releases', { - headers: { - Accept: 'application/vnd.github.v3+json', - ...(process.env.GITHUB_TOKEN && { - Authorization: `token ${process.env.GITHUB_TOKEN}`, - }), - }, - next: { revalidate: 3600 }, // Cache for 1 hour - }); - - if (!response.ok) { - console.error('GitHub API error:', response.status); - return []; - } - - const releases = await response.json(); - return releases.filter((r: GitHubRelease) => !r.draft); - } catch (error) { - console.error('Failed to fetch releases:', error); - return []; - } -} - -function formatDate(dateString: string): string { - const date = new Date(dateString); - return date.toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric', - }); -} - -function formatVersion(tagName: string): string { - return tagName.startsWith('v') ? tagName.substring(1) : tagName; -} - -export async function Changelog() { - const releases = await getGithubReleases(); - - if (releases.length === 0) { - return ( -
-

- No releases found. Check the{' '} - - GitHub Releases - {' '} - page. -

-
- ); - } - - return ( -
- {releases.slice(0, 20).map((release) => ( -
-
-

- - v{formatVersion(release.tag_name)} - -

- -
- - {release.body ? ( -
- -
- ) : ( -

No release notes available.

- )} -
- ))} -
- ); -} - -function ReleaseNotes({ content }: { content: string }) { - // Process content line by line, handling both markdown and HTML - const lines = content.split('\n'); - const elements: React.ReactNode[] = []; - let currentListItems: React.ReactNode[] = []; - let inCodeBlock = false; - let codeBlockContent: string[] = []; - let codeBlockStartIndex = 0; - - // Helper function to flush list items - const flushListItems = () => { - if (currentListItems.length > 0) { - elements.push( -
    - {currentListItems} -
- ); - currentListItems = []; - } - }; - - // Helper function to process links in text - const processLinks = (text: string): string => { - return ( - text - // Handle GitHub URLs, but not if they're followed by punctuation - .replace( - /https:\/\/github\.com\/[^\s\)\]]+/g, - (url) => - `${url.replace(/[.,;!?]+$/, '')}` - ) - // Handle @mentions, but avoid matching email addresses - // Look for @mentions that are preceded by whitespace or start of string - .replace( - /(^|[\s\(])@(\w+)(?=\s|$|[^\w@])/g, - (match, prefix, username) => - `${prefix}@${username}` - ) - ); - }; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - - // Check for code block start/end - if (line.startsWith('```')) { - if (inCodeBlock) { - // End code block - elements.push( -
-            {codeBlockContent.join('\n')}
-          
- ); - codeBlockContent = []; - inCodeBlock = false; - } else { - // Start code block - flushListItems(); // Flush any pending list items - inCodeBlock = true; - codeBlockStartIndex = i; - } - continue; - } - - // If we're in a code block, just collect the content - if (inCodeBlock) { - codeBlockContent.push(line); - continue; - } - - // Handle HTML img tags - if (line.includes(']+>/); - if (imgMatch) { - const srcMatch = imgMatch[0].match(/src="([^"]+)"/); - const altMatch = imgMatch[0].match(/alt="([^"]+)"/); - const widthMatch = imgMatch[0].match(/width="([^"]+)"/); - const heightMatch = imgMatch[0].match(/height="([^"]+)"/); - - if (srcMatch) { - elements.push( -
- {altMatch -
- ); - continue; - } - } - } - - // Headers - if (line.startsWith('## ')) { - flushListItems(); // Flush any pending list items - elements.push( -

- {line.substring(3)} -

- ); - continue; - } - if (line.startsWith('### ')) { - flushListItems(); // Flush any pending list items - elements.push( -

- {line.substring(4)} -

- ); - continue; - } - - // List items (support both * and - prefixes) - if (line.startsWith('* ') || line.startsWith('- ')) { - const item = line.substring(2); - const withLinks = processLinks(item); - currentListItems.push(
  • ); - continue; - } - - // If we hit a non-list item, flush any pending list items - if (currentListItems.length > 0 && !line.startsWith(' ')) { - flushListItems(); - } - - // Bold text (like **New Contributors**) - if (line.includes('**')) { - flushListItems(); // Flush any pending list items - const formatted = line.replace(/\*\*(.*?)\*\*/g, '$1'); - const withLinks = processLinks(formatted); - elements.push( -

    - ); - continue; - } - - // Full Changelog link - if (line.includes('Full Changelog:')) { - flushListItems(); // Flush any pending list items - const match = line.match(/https:\/\/github\.com\/[^\s\)\]]+/); - if (match) { - elements.push( -

    - Full Changelog:{' '} - - View diff - -

    - ); - continue; - } - } - - // Empty lines - if (line.trim() === '') { - flushListItems(); // Flush any pending list items - continue; - } - - // Regular text with link processing - flushListItems(); // Flush any pending list items - const withLinks = processLinks(line); - elements.push(

    ); - } - - // Flush any remaining list items - flushListItems(); - - return <>{elements}; -} diff --git a/docs/components/CopyEmailButton.tsx b/docs/components/CopyEmailButton.tsx deleted file mode 100644 index e86461e11..000000000 --- a/docs/components/CopyEmailButton.tsx +++ /dev/null @@ -1,22 +0,0 @@ -'use client'; - -import * as React from 'react'; - -export function CopyEmailButton({ email }: { email: string }) { - const [state, setState] = React.useState<'idle' | 'copied'>('idle'); - - function onCopy() { - navigator.clipboard.writeText(email); - setState('copied'); - window.setTimeout(() => setState('idle'), 1200); - } - - return ( - - ); -} diff --git a/docs/components/CopyMarkdownButton.tsx b/docs/components/CopyMarkdownButton.tsx deleted file mode 100644 index f6d8fc0e2..000000000 --- a/docs/components/CopyMarkdownButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -'use client'; - -import * as React from 'react'; - -export function CopyMarkdownButton({ markdownUrl }: { markdownUrl: string }) { - const [state, setState] = React.useState<'idle' | 'copied' | 'error'>('idle'); - - async function onCopy() { - try { - setState('idle'); - const res = await fetch(markdownUrl, { cache: 'no-store' }); - if (!res.ok) throw new Error(`Failed to fetch markdown: ${res.status}`); - const md = await res.text(); - await navigator.clipboard.writeText(md); - setState('copied'); - window.setTimeout(() => setState('idle'), 1200); - } catch { - setState('error'); - window.setTimeout(() => setState('idle'), 1500); - } - } - - return ( - - ); -} diff --git a/docs/components/LastUpdated.tsx b/docs/components/LastUpdated.tsx deleted file mode 100644 index b0670491a..000000000 --- a/docs/components/LastUpdated.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export function LastUpdated({ date }: { date: Date }) { - if (!date || isNaN(date.getTime())) { - return null; - } - - const formatted = new Intl.DateTimeFormat(undefined, { - year: 'numeric', - month: 'long', - day: 'numeric', - }).format(date); - - return ( -

    -
    - Last updated on {formatted} -
    -
    - ); -} diff --git a/docs/content/docs/automations.mdx b/docs/content/docs/automations.mdx deleted file mode 100644 index cb3293ed7..000000000 --- a/docs/content/docs/automations.mdx +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Automations -description: Schedule recurring tasks in Emdash ---- - - - Beta - - -Automations let you run the same task on a schedule. Each automation stores a prompt, one project, one agent, and a schedule. When it fires, Emdash creates a background task and starts the agent automatically. - -## Creating an automation - -1. Open **Automations** from the left sidebar -2. Click **New Automation** -3. Enter a title and prompt -4. Pick a project -5. Choose an agent -6. Choose **Worktree** or **Direct** -7. Set a schedule -8. Click **Create** - -Use `⌘/Ctrl+Enter` to create quickly. - -## Schedule types - -Emdash supports four schedule types: - -- **Hourly** -- **Daily** -- **Weekly** -- **Monthly** - -Automations run in the background and appear in the **Active** list on the Automations page. - -## Worktree or Direct - -**Worktree** creates a separate git worktree for each run, just like a normal [task](/tasks). This is the safer default because changes stay isolated from your main checkout. - -**Direct** runs against the main project checkout without creating a new worktree. - -If your project needs preserved files, setup scripts, shell setup, or tmux configuration, set that up in [Project Configuration](/project-config) first. - -## Managing automations - -Each automation row supports: - -- **Run now** -- **Pause / Resume** -- **Edit** -- **Delete** - -When an automation runs: - -- a background task is created -- the selected agent starts automatically -- the task appears in the **Automation Tasks** section -- the run count opens the run log history - -## Important behavior - -- Emdash must be open for automations to run on time. Automations are scheduled by the app, not by your operating system. -- If Emdash was closed during a scheduled run, it will catch up once when you reopen the app. -- Each automation belongs to a single project. -- Frequent schedules can create a lot of tasks and worktrees. Review and clean up old runs regularly. - -## Recommended workflow - -Before scheduling an automation, test the prompt in a normal [task](/tasks) first. Make sure it behaves the way you expect, then turn it into an automation. - -Start with a slower schedule like daily or weekly before moving to something more frequent. - -If the automation starts local servers, use `EMDASH_PORT` in your [Project Configuration](/project-config) so parallel runs do not collide. - -## Good examples - -- Daily code review -- Dependency update checks -- Docs coverage checks -- Security scans -- Performance audits diff --git a/docs/content/docs/best-of-n.mdx b/docs/content/docs/best-of-n.mdx deleted file mode 100644 index 5c730617f..000000000 --- a/docs/content/docs/best-of-n.mdx +++ /dev/null @@ -1,66 +0,0 @@ ---- -title: Best of N -description: Run multiple agents in parallel and pick the best result ---- - -Run multiple agents on the same task, let them work in parallel, then pick the best output. Each agent gets its own worktree so they don't interfere with each other. - -Running multiple agents in parallel - -## Why Use It - -Different agents have different strengths. Running them in parallel lets you: - -- Compare solutions from different providers (Claude vs Codex vs Gemini) -- Get multiple attempts at a tricky problem -- Race agents to see which finishes first - -## How to Start - -When creating a task: - -1. Click the provider dropdown -2. Select multiple providers, or increase the count for a single provider (e.g., 3× Claude Code) -3. Click Create - -Emdash creates a separate git branch and worktree for each agent. They all start from the same base commit but work independently. - -Multiple agents working in separate worktrees - -## Working with Multiple Agents - -Each agent spawns in its own terminal tab. Switch between tabs to watch them work, or use the shared input bar to send the same message to all of them. - -## Comparing Results - -After agents finish, review their changes in the [diff view](/diff-view). Each agent's worktree has its own set of changes. Check the diff stats (files changed, lines added/removed) for a quick sense of each approach. - -Pick the best solution and merge that branch. Discard the rest. - -## Tips - -- Start with 2-3 agents. More than that gets hard to compare. -- Use the same initial prompt for fair comparison. -- Complex tasks benefit most from multiple perspectives. diff --git a/docs/content/docs/bring-your-own-infrastructure.mdx b/docs/content/docs/bring-your-own-infrastructure.mdx deleted file mode 100644 index 64f551e9d..000000000 --- a/docs/content/docs/bring-your-own-infrastructure.mdx +++ /dev/null @@ -1,323 +0,0 @@ ---- -title: Bring Your Own Infrastructure -description: Automatically provision and tear down remote workspaces for each task using your own infrastructure ---- - -
    - Contact us to get started: - -
    - -Plug in custom shell scripts that create and destroy remote environments on demand. When a developer creates a task, Emdash runs your provision script, connects the terminal via SSH, and runs coding agents inside the workspace. When the task is deleted, Emdash runs your teardown script to clean up. - -This works with any infrastructure backend: cloud VMs, Kubernetes pods, container-based dev environments, or internal workspace platforms. You just provide the scripts. - -
    - AWS - Hetzner - Azure - Docker -
    - -## How It Works - -1. Developer creates a task with **"Remote workspace"** enabled -2. Emdash runs your **provision script** as a child process -3. Script progress (stderr) streams live in the UI -4. Script outputs JSON (stdout) with SSH connection details -5. Emdash connects the terminal to the workspace via SSH -6. Coding agents run inside the remote workspace -7. When the task is deleted, Emdash runs your **teardown script** - -### Compared to Remote Projects - -| | [Remote Projects](/docs/remote-projects) | Workspace Provider | -| -------------------- | ---------------------------------------- | ------------------------------- | -| **Lifecycle** | Persistent server, shared across tasks | Per-task, provisioned on demand | -| **Setup** | Manual connection in Settings | Automated via scripts | -| **Infra management** | You manage the server | Scripts handle create/destroy | -| **Use case** | Dedicated dev server | Ephemeral, isolated workspaces | - -Both features share the same remote capabilities once connected: terminal access, coding agents, Git operations, and file browsing all work identically. - -## Setup - -### Step 1: Create Your Scripts - -Create two shell scripts in your project root (or anywhere accessible from the project directory). - -#### Provision Script - -The provision script creates a workspace and outputs SSH connection details as JSON to stdout. All log messages must go to stderr. - -**Environment variables provided by Emdash:** - -| Variable | Description | -| ----------------- | ---------------------------------------------------- | -| `EMDASH_TASK_ID` | Unique identifier for the task | -| `EMDASH_REPO_URL` | Repository URL (e.g., `git@github.com:org/repo.git`) | -| `EMDASH_BRANCH` | Branch name for this task | -| `EMDASH_BASE_REF` | Base branch (e.g., `main`) | - -**Required output (JSON to stdout):** - -```json -{ - "host": "workspace-hostname-or-ip", - "id": "optional-external-id", - "port": 22, - "username": "dev", - "worktreePath": "/path/to/repo/on/workspace" -} -``` - -| Field | Required | Description | -| -------------- | -------- | ---------------------------------------------------------------- | -| `host` | Yes | Hostname, IP address, or SSH config alias | -| `id` | No | External identifier (passed to teardown as `EMDASH_INSTANCE_ID`) | -| `port` | No | SSH port (default: 22) | -| `username` | No | SSH username (default: current user) | -| `worktreePath` | No | Path to the repository on the workspace | - -**Example provision script:** - -```bash -#!/bin/bash -set -euo pipefail - -echo "[$(date)] Creating workspace for $EMDASH_BRANCH..." >&2 - -# Replace this section with your infrastructure commands -WORKSPACE_ID="ws-$(date +%s)" -HOST="dev-${WORKSPACE_ID}.internal.example.com" - -echo "[$(date)] Provisioning VM..." >&2 -# your-cli create-workspace --id "$WORKSPACE_ID" \ -# --repo "$EMDASH_REPO_URL" --branch "$EMDASH_BRANCH" - -echo "[$(date)] Ready!" >&2 - -# Output JSON to stdout (this is what Emdash parses) -cat <&2`) — Emdash streams these lines live in the UI -- Only the JSON object goes to **stdout** — this is parsed for connection details -- Exit with code **0** on success, non-zero on failure -- Timeout: **5 minutes** - -#### Teardown Script - -The teardown script destroys the workspace when the task is deleted. - -**Environment variables provided by Emdash:** - -| Variable | Description | -| -------------------- | ----------------------------------------------------------------------- | -| `EMDASH_INSTANCE_ID` | The `id` from your provision output (or `host` if no `id` was provided) | -| `EMDASH_TASK_ID` | The task identifier (same value passed to the provision script) | - -**Example teardown script:** - -```bash -#!/bin/bash -set -euo pipefail - -echo "[$(date)] Destroying workspace $EMDASH_INSTANCE_ID..." >&2 -# your-cli delete-workspace "$EMDASH_INSTANCE_ID" -echo "[$(date)] Done." >&2 -``` - -Make both scripts executable: - -```bash -chmod +x provision.sh teardown.sh -``` - -### Step 2: Configure `.emdash.json` - -Add the workspace provider configuration to `.emdash.json` in your project root: - -```json -{ - "workspaceProvider": { - "type": "script", - "provisionCommand": "./provision.sh", - "terminateCommand": "./teardown.sh" - } -} -``` - -Alternatively, configure via the Emdash UI: - -1. Open your project -2. Click the **gear icon** (Project Settings) -3. Scroll to the **Workspace Provider** section -4. Enter your provision and terminate commands -5. Save - -### Step 3: Create a Task - -1. Click **New Task** -2. In the task creation dialog, expand **Advanced Settings** -3. Check **"Remote workspace (provision via script)"** -4. Create the task - -Emdash will: - -1. Show a provisioning overlay with live progress from your script's stderr -2. Parse the JSON output to get connection details -3. Connect the terminal to the workspace via SSH -4. Start your coding agent inside the workspace - -## Workspace Requirements - -For the best experience, ensure your provisioned workspaces have these tools installed: - -### Required - -- **SSH server** — Emdash connects via your system's `ssh` command -- **Git** — For repository operations, configured with `user.name` and `user.email` -- **A coding agent** — At least one CLI agent ([Claude Code, Codex, etc.](/docs/providers)) - -### Recommended - -- **GitHub CLI (`gh`)** — For PR creation and GitHub operations from the Emdash UI -- **tmux** — Emdash automatically uses tmux for workspace sessions to preserve state across reconnects - -### GitHub Access (for push/pull) - -If your workflow involves pushing code, configure SSH access to GitHub on the workspace: - -```bash -ssh-keygen -t ed25519 -C "workspace" -f ~/.ssh/id_github -N "" -# Add ~/.ssh/id_github.pub to GitHub (deploy key or user key) -git config --global core.sshCommand "ssh -i /home/dev/.ssh/id_github" -ssh-keyscan -t ed25519 github.com >> ~/.ssh/known_hosts 2>/dev/null -``` - -## SSH Authentication - -Emdash connects to workspaces using your local system's `ssh` command, which means: - -- **SSH config aliases work** — Your provision script can return an SSH config `Host` alias as the `host` field -- **SSH agent works** — Keys in your local SSH agent (including macOS Keychain) are available -- **~/.ssh/config works** — ProxyJump, custom ports, identity files, and other SSH config directives are respected - -No additional SSH configuration is needed in Emdash itself. - -## Task Lifecycle - -### Creating a Task - -When you create a task with remote workspace enabled: - -1. Provision script runs (progress streams in the UI) -2. On success: SSH connection is established, agent starts in the workspace -3. On failure: error is shown in the UI with a retry option - -### Deleting a Task - -When you delete a workspace-backed task: - -1. Teardown script runs with `EMDASH_INSTANCE_ID` -2. On success: workspace is destroyed, database is cleaned up -3. On failure: instance is marked as error, task is kept for retry - -### App Restart - -If Emdash is restarted while workspaces are active: - -- **Provisioning** instances are marked as error (the child process is gone) -- **Ready** instances remain — the SSH connection is re-established when you open the task - -## Troubleshooting - -### Provision Script Fails - -**Symptoms:** Error overlay with script output - -**Solutions:** - -1. Check the error message in the overlay — it includes the last 500 chars of stderr -2. Run the script manually to debug: - ```bash - EMDASH_TASK_ID=test EMDASH_REPO_URL=git@github.com:org/repo.git \ - EMDASH_BRANCH=test-branch EMDASH_BASE_REF=main \ - ./provision.sh - ``` -3. Verify the JSON output is valid: pipe stdout through `jq` -4. Ensure log output goes to stderr, not stdout - -### SSH Connection Fails After Provisioning - -**Symptoms:** Terminal shows SSH error after provision succeeds - -**Solutions:** - -1. Verify you can SSH manually: `ssh ` (using the host from your script output) -2. Check your `~/.ssh/config` for the host -3. Verify your SSH agent has the right key loaded: `ssh-add -l` -4. If using an SSH config alias, ensure the alias is configured on the machine running Emdash - -### Script Times Out - -Provision scripts have a 5-minute timeout and teardown scripts have a 2-minute timeout. If your infrastructure takes longer, optimize the provisioning pipeline or pre-warm environments. - -### "Invalid JSON" Error - -**Symptoms:** "Provision script output is not valid JSON" - -**Solutions:** - -1. Ensure **only** JSON is printed to stdout — all log messages must use `>&2` -2. Check for stray `echo` or `printf` statements without `>&2` -3. Validate your output: `./provision.sh 2>/dev/null | jq .` - -## Script Contract Reference - -### Provision Script - -| Aspect | Details | -| --------------------- | ----------------------------------------------------------------------- | -| **Execution** | `bash -c ` | -| **Working directory** | Project root | -| **Env vars** | `EMDASH_TASK_ID`, `EMDASH_REPO_URL`, `EMDASH_BRANCH`, `EMDASH_BASE_REF` | -| **Stdout** | JSON object with at least `host` field | -| **Stderr** | Log lines (streamed to UI) | -| **Exit code** | 0 = success, non-zero = failure | -| **Timeout** | 5 minutes | - -### Teardown Script - -| Aspect | Details | -| --------------------- | -------------------------------------- | -| **Execution** | `bash -c ` | -| **Working directory** | Project root | -| **Env vars** | `EMDASH_INSTANCE_ID`, `EMDASH_TASK_ID` | -| **Stdout** | (ignored) | -| **Stderr** | Log lines | -| **Exit code** | 0 = success, non-zero = failure | -| **Timeout** | 2 minutes | diff --git a/docs/content/docs/changelog.mdx b/docs/content/docs/changelog.mdx deleted file mode 100644 index 1a0eec111..000000000 --- a/docs/content/docs/changelog.mdx +++ /dev/null @@ -1,22 +0,0 @@ ---- -title: Changelog -description: Version history and release notes for Emdash ---- - -import { Changelog } from '../../components/Changelog'; - - - ---- - -## Release Process - -Emdash follows [Semantic Versioning](https://semver.org/): - -- **Patch versions** (0.0.x): Bug fixes and minor improvements -- **Minor versions** (0.x.0): New features and capabilities -- **Major versions** (x.0.0): Breaking changes - -## Contributing - -See our [Contributing Guide](/contributing) to learn how you can help improve Emdash. diff --git a/docs/content/docs/ci-checks.mdx b/docs/content/docs/ci-checks.mdx deleted file mode 100644 index ad892b438..000000000 --- a/docs/content/docs/ci-checks.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: CI/CD Checks -description: Monitor GitHub Actions check runs directly in Emdash ---- - -Emdash surfaces your GitHub Actions CI/CD status inside the app so you can monitor check runs without switching to a browser. - -CI/CD checks panel in Emdash - -## Viewing Checks - -Once a task has a Pull Request, a **Checks** tab appears in the file changes panel. Click it to see all check runs for that PR's branch. - -Each check shows: - -- **Status**: Green checkmark (pass), red X (fail), amber spinner (pending), or grey dash (skipped/cancelled) -- **Name and workflow**: Which check ran and from which workflow -- **Duration**: How long the check took -- **External link**: Click to open the full check run on GitHub - -A summary at the top shows the total count of passed, failed, and pending checks. - -## Auto-Refresh - -Emdash automatically polls for check updates: - -- **Every 10 seconds** while checks are still running -- **Every 60 seconds** once all checks complete -- **On window focus** — checks refresh when you switch back to Emdash - -Polling pauses when the app is in the background to save resources. - -## Requirements - -- The [GitHub CLI](https://cli.github.com/) (`gh`) must be installed and authenticated -- The task's branch must have an open Pull Request on GitHub -- Your repository must have GitHub Actions workflows configured - -If the Checks tab is disabled, it means no PR exists for the current task yet. Push a branch and open a PR to start seeing checks. - -## Tips - -- The Checks tab automatically activates when a PR exists but there are no local uncommitted changes. -- Failed checks show a red dot on the tab badge so you can spot failures at a glance. -- A spinning indicator on the tab badge means checks are still in progress. -- Click the external link icon on any check to jump straight to the run details on GitHub. diff --git a/docs/content/docs/contributing.mdx b/docs/content/docs/contributing.mdx deleted file mode 100644 index ecaab77fe..000000000 --- a/docs/content/docs/contributing.mdx +++ /dev/null @@ -1,189 +0,0 @@ ---- -title: Contributing -description: Help improve Emdash - contribution guidelines and development setup ---- - -Thanks for your interest in contributing! We favor small, focused PRs and clear intent over big bangs. This guide explains how to get set up, the workflow we use, and a few project‑specific conventions. - -## Quick Start - -### Prerequisites - -- **Node.js 20.0.0+ (recommended: 22.20.0)** and Git -- Optional (recommended for end‑to‑end testing): - - GitHub CLI (`brew install gh`; then `gh auth login`) - - At least one supported coding agent CLI (see [Providers](/providers)) - -### Setup - -```bash -# Fork this repo, then clone your fork -git clone https://github.com//emdash.git -cd emdash - -# Use the correct Node.js version (if using nvm) -nvm use - -# Quick start: install dependencies and run dev server -pnpm run d - -# Or run separately: -pnpm install -pnpm run dev - -# Type checking, lint, build -pnpm run type-check -pnpm run lint -pnpm run build -``` - -**Tip:** During development, the renderer hot‑reloads. Changes to the Electron main process (files in `src/main`) require a restart of the dev app. - -## Project Overview - -- `src/main/` – Electron main process, IPC handlers, services (Git, worktrees, PTY manager, DB, etc.) -- `src/renderer/` – React UI (Vite), hooks, components -- Local database – SQLite file created under the OS userData folder (see "Local DB" below) -- Worktrees – Git worktrees are created outside your repo root in a sibling `worktrees/` folder -- Logs – Agent terminal output and app logs are written to the OS userData folder (not inside repos) - -## Development Workflow - -### 1. Create a feature branch - -```bash -git checkout -b feat/ -``` - -### 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 - -```bash -pnpm run format # Format code with Prettier (required) -pnpm run type-check # TypeScript type checking -pnpm run lint # ESLint -pnpm run build # Build both main and renderer -``` - -Pre-commit hooks run automatically via Husky + lint-staged. On each commit, staged files are auto-formatted with Prettier and linted with ESLint. You don't need to remember to run these manually. Type checking and tests run in CI only since they need the full project context and are slower to execute. - -If you need to skip the hook for a work-in-progress commit, use `git commit --no-verify`. The checks will still run in CI when you open a PR. - -### 4. Commit using Conventional Commits - -- `feat:` – new user‑facing capability -- `fix:` – bug fix -- `chore:`, `refactor:`, `docs:`, `perf:`, `test:` etc. - -**Examples:** - -``` -fix(opencode): change initialPromptFlag from -p to --prompt for TUI - -feat(docs): add changelog tab with GitHub releases integration -``` - -### 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 + Prettier - -Pre-commit hooks handle formatting and linting automatically on staged files. For full-project checks you can run them manually: - -- `pnpm run format` -- format all files with Prettier -- `pnpm run type-check` -- TypeScript type checking (whole project) -- `pnpm run lint` -- ESLint across all files -- `pnpm exec vitest run` -- run the test suite - -### Electron main (Node side) - -- Prefer `execFile` over `exec` to avoid shell quoting issues. -- Never write logs into Git worktrees. All 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 - -### Documentation - -When writing or updating docs, keep the tone clear and conversational. Use complete sentences and natural language rather than long bullet point lists. Avoid em dashes (—); use commas, periods, or rephrase instead. - -### Renderer (React) - -- Components live under `src/renderer/components`; hooks under `src/renderer/hooks`. -- Agent CLIs are embedded via terminal emulation (xterm.js) - each agent runs in its own PTY. -- Use existing UI primitives and Tailwind utility classes for consistency. -- 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) - -Use pnpm's built-in versioning to ensure consistency: - -```bash -# For bug fixes (0.2.9 → 0.2.10) -pnpm version patch - -# For new features (0.2.9 → 0.3.0) -pnpm version minor - -# For breaking changes (0.2.9 → 1.0.0) -pnpm version major -``` - -This automatically: - -1. Updates `package.json` and `pnpm-lock.yaml` -2. Creates a git commit with the version number (e.g., `"0.2.10"`) -3. Creates a git tag (e.g., `v0.2.10`) - -Then push to trigger the CI/CD pipeline. - -### What happens next - -Two GitHub Actions workflows trigger on version tags: - -**macOS Release** (`.github/workflows/release.yml`): - -1. Builds the TypeScript and Vite bundles -2. Signs the app with Apple Developer ID -3. Notarizes via Apple's notary service -4. Creates a GitHub Release with DMG artifacts for arm64 and x64 - -**Linux/Nix Build** (`.github/workflows/nix-build.yml`): - -1. Computes the correct dependency hash from `pnpm-lock.yaml` -2. Builds the x86_64-linux package via Nix flake -3. Pushes build artifacts to Cachix diff --git a/docs/content/docs/diff-view.mdx b/docs/content/docs/diff-view.mdx deleted file mode 100644 index 8c8ad22d8..000000000 --- a/docs/content/docs/diff-view.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -title: Diff View -description: Review and manage code changes ---- - -The diff view shows all file changes in a task's worktree. Stage files, review diffs, write commit messages, and create pull requests from one panel. - -Diff view interface in Emdash - -## Where to Find It - -The diff view lives in the right sidebar when you have a task open. It updates automatically as the agent makes changes (polls every 5 seconds). - -## What You See - -For each changed file: - -- File path and type icon -- Lines added (green) and removed (red) -- Staged or unstaged status - -The header shows total files changed, overall additions/deletions, and PR status if a pull request exists. - -## Actions - -**Stage a file**: Click the + icon to add a file to the staging area. - -**Unstage or revert**: Click the undo icon. For staged files, this unstages them. For unstaged files, this discards all changes (resets to last commit). - -**View diff**: Click a file to open it in the diff viewer. You can edit the file directly in the diff view and save your changes. - -**View all changes**: Click "View All" to see diffs for every changed file in one scrollable view. - -**Commit and push**: Type a commit message and press Enter. Emdash commits staged changes and pushes to the branch. - -**Create PR**: After pushing, a "Create PR" button appears if your branch is ahead of main. - -## Inline Editing - -The diff viewer isn't read-only. Edit the modified version directly, then save. This is useful for quick fixes without switching to your editor. diff --git a/docs/content/docs/file-editor.mdx b/docs/content/docs/file-editor.mdx deleted file mode 100644 index 3243aa564..000000000 --- a/docs/content/docs/file-editor.mdx +++ /dev/null @@ -1,55 +0,0 @@ ---- -title: File Editor -description: Edit files directly in Emdash ---- - -The file editor lets you browse and edit code in a task's worktree without switching to an external editor. Use it to explore the codebase, make edits yourself, or review what an agent has done. - -File editor interface in Emdash - -## Opening the Editor - -Click the code icon in the titlebar when viewing a task. The editor opens in a side panel with a file tree on the left and the code editor on the right. - -## Navigating Files - -The left panel shows your project's file tree. Click a file to open it. Files open in tabs at the top of the editor, so you can switch between multiple files. - -Common system directories like `node_modules`, `.git`, and build output are hidden by default. Click the eye icon to show or hide them. - -## Editing - -The editor is a full-featured code editor with syntax highlighting, find/replace, and the usual keyboard shortcuts. Changes auto-save after 2 seconds of inactivity, or press `⌘S` to save immediately. - -Git diff markers appear in the gutter: - -- Green dots for added lines -- Orange dots for modified lines -- Red markers for deleted lines - -These update automatically as you edit, so you can see what's changed compared to the last commit. - -## Saving - -Files with unsaved changes show a dot in their tab. Use `⌘⇧S` to save all open files at once, or click "Save All" in the header. - -After saving, the [diff view](/diff-view) updates to reflect your changes. - -## Images - -The editor also handles images. Click an image file to preview it instead of showing raw bytes. - -## Resizing - -Drag the divider between the file tree and editor to adjust panel widths. The layout remembers your preference. diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx deleted file mode 100644 index e6ff48ad1..000000000 --- a/docs/content/docs/index.mdx +++ /dev/null @@ -1,37 +0,0 @@ ---- -title: Emdash Overview -description: An Open Source Agentic Development Environment (ADE) ---- - -Emdash is an open source desktop app for running multiple coding agents in parallel. Each agent works in an isolated Git worktree, so they don't interfere with each other. - -## Capabilities - -- **[Parallel agents](/tasks):** Run multiple agents simultaneously, each in its own worktree -- **[Provider support](/providers):** Use any of 18+ CLI-based agents: Claude Code, Codex, Gemini, OpenCode, and more -- **[Best-of-N](/best-of-n):** Run multiple agents on the same task and pick the best result -- **[Diff view](/diff-view):** Review changes across agents side-by-side -- **[Kanban view](/kanban-view):** Organize tasks visually across your workflow -- **[Issue integration](/issues):** Pull tasks from Linear, Jira, or GitHub Issues directly - -## How It Works - -Click "Add Task" to create one or more worktrees. Each worktree runs its own agent. You can then review the diff when done, iterate if needed, and open a PR inside of Emdash. - -## Demo - -