diff --git a/.github/workflows/help-pages.yml b/.github/workflows/help-pages.yml new file mode 100644 index 000000000..3350d5ef7 --- /dev/null +++ b/.github/workflows/help-pages.yml @@ -0,0 +1,60 @@ +name: Publish help + +on: + push: + branches: [dev] + paths: + - 'docs/help/**' + - 'docs/screenshots/workspace-0.20.png' + - 'images/CodeNomad-Icon.png' + - 'scripts/build-help.mjs' + - 'scripts/test-help.mjs' + - 'package-lock.json' + - '.github/workflows/help-pages.yml' + pull_request: + paths: + - 'docs/help/**' + - 'docs/screenshots/workspace-0.20.png' + - 'images/CodeNomad-Icon.png' + - 'scripts/build-help.mjs' + - 'scripts/test-help.mjs' + - 'package-lock.json' + - '.github/workflows/help-pages.yml' + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: .node-version + cache: npm + - run: npm ci --ignore-scripts + - run: node --test scripts/test-help.mjs + - run: node scripts/build-help.mjs + - uses: actions/upload-pages-artifact@v3 + with: + path: dist/help + + deploy: + if: github.ref == 'refs/heads/dev' && github.event_name != 'pull_request' + needs: build + runs-on: ubuntu-24.04 + permissions: + pages: write + id-token: write + concurrency: + group: help-pages + cancel-in-progress: false + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/configure-pages@v5 + - uses: actions/deploy-pages@v4 + id: deployment diff --git a/AGENTS.md b/AGENTS.md index 24545d716..a067f10b1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -94,6 +94,8 @@ ## Multi-Language Support (i18n) +- The separate public user guide lives in `docs/help/` and is intentionally English-only for now. `scripts/build-help.mjs` renders its Markdown using the existing `marked` dependency; `docs/help/help.css` owns the standalone site's square, responsive chrome. It does not import the application runtime or add an app locale. Validate it with `npm run test:help`; GitHub Pages publishes `dist/help` through `.github/workflows/help-pages.yml`. + The UI uses a small custom i18n layer (no ICU/messageformat). When building features, never hardcode user-visible strings. - **Runtime API:** use `useI18n()` in components (`const { t } = useI18n();`) and `tGlobal(...)` in stores/non-component code. diff --git a/README.md b/README.md index 422dc6014..bf98db61f 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,8 @@ CodeNomad is a **desktop and browser workspace for OpenCode V2** — built for d *The 0.20 interface, rendered with demonstration data. [Download the latest stable release](https://github.com/NeuralNomadsAI/CodeNomad/releases/latest).* +[User help](https://neuralnomadsai.github.io/CodeNomad/) · [Read the guide on GitHub](docs/help/index.md) — a short English guide to current CodeNomad and OpenCode V2. + --- ## Features diff --git a/dev-docs/USER_HELP.md b/dev-docs/USER_HELP.md new file mode 100644 index 000000000..f9225cb4c --- /dev/null +++ b/dev-docs/USER_HELP.md @@ -0,0 +1,44 @@ +# Public user help + +The English guide in `docs/help/` covers current CodeNomad with OpenCode V2, +not the old runtime or every upstream configuration option. Assume readers +already know OpenCode: document CodeNomad's controls, workflow differences, +and state/scope boundaries instead of teaching agents, prompts, Git, or MCP. +Keep pages short, use current English interface labels, and link to the +official V2 docs for native configuration details. + +## Build and check + +```sh +npm ci --ignore-scripts +npm run test:help +npm run build:help +``` + +Output is `dist/help/`. Serve that directory with any static HTTP server to +preview it; no app backend, credentials, or OpenCode service is needed. The +builder uses the existing `marked` dependency, copies local screenshots/logo, +and generates relative links that work under a GitHub project-site path. +The site has no client JavaScript, remote fonts, analytics, or runtime requests. + +## Publish on GitHub Pages + +In **Repository Settings → Pages → Build and deployment**, select **GitHub +Actions** as the source. The repository currently has no Pages site; this is a +one-time administrator setup, not something the workflow silently enables. +Allow the `github-pages` environment to deploy from `dev` if its protection +rules require a branch allowlist. + +After merging the workflow into `dev`, it builds and publishes changes to the +help sources. It can also be started manually from `dev`. Pull requests build +and check the site but never deploy it. Deployment needs `pages: write` and +`id-token: write`; the build job only reads repository contents. + +Expected URL: **https://neuralnomadsai.github.io/CodeNomad/**. No separate +repository, paid hosting, domain, or additional deployment secret is needed. +The site follows `dev` (the current implementation); it is not an archive of +versioned guides. Update the help alongside user-visible behavior changes. + +The desktop Help link should use this URL once deployment is available. Native +Help menu items are maintained in both desktop hosts; the updater work from +issue #606 remains separate from documentation hosting. diff --git a/docs/help/conversations.md b/docs/help/conversations.md new file mode 100644 index 000000000..489668d41 --- /dev/null +++ b/docs/help/conversations.md @@ -0,0 +1,32 @@ +# Conversations + +The controls around your OpenCode sessions: composer context, pending requests, and full-history navigation. + +## Composer controls + +**Agent**, **model**, and **thinking** selectors live in the composer footer, alongside the session's worktree selector. Resize the composer without changing its draft; long drafts scroll within it. + +The composer remains available during execution. Subsequent submissions may enter the session's pending queue; **Stop** targets the current execution. + +## Context attachments + +- **`@`** searches project files, agents, and skills available in the session's Location. Selecting a skill attaches a removable badge rather than inserting its instructions into your draft. +- The **attachment** action, drop, and paste use files from your client device. Project file references resolve on the execution host. +- **`/`** lists the Location's commands. **`/btw`** opens a temporary side-question window without replacing the main conversation. + +## Requests across conversations + +Pending questions and permissions are edited **above the composer**, not in transcript cards. Request drafts survive queue refreshes and session navigation. + +The request area includes the open session, its descendants, and other conversations. Origin labels identify the source; **View conversation** navigates there. External requests start compact and can be answered in place. Arrival does not replace the request being edited or switch your session. Use previous/next controls when several requests are pending. + +CodeNomad's **Yolo mode** applies to the session family: it automatically approves permissions, not questions or other Forms. + +## History navigation + +- **Search** queries full history, not just mounted transcript rows. +- **Timeline** markers jump to messages and tool activity. Hover/focus previews let you inspect a point without loading a whole transcript section. At narrow conversation widths, the rail is hidden and header actions move into overflow. +- **Message content visibility** controls reasoning and tool presentation without changing stored history. +- **Fork** continues from a history point in a separate session. + +Subsessions appear beneath their parent in normal browsing. Session search uses flat results, with an option to include subsessions. View closure, session deletion, and history pruning are separate operations; the latter two can remove stored content. diff --git a/docs/help/files-and-tools.md b/docs/help/files-and-tools.md new file mode 100644 index 000000000..29628959c --- /dev/null +++ b/docs/help/files-and-tools.md @@ -0,0 +1,37 @@ +# Files and tools + +The right panel combines workspace browsing, Git inspection, and project services. Readers open centrally, above the composer. + +## Workspace files + +In **Files → Workspace**, expand folders and select a row. Use its **eye** action to open the central file reader; selecting a row alone does not open it. + +Text files, including Markdown source, are editable. Use **Save** or **Ctrl/Cmd+S** to write changes. If the file changed on disk, CodeNomad asks you to resolve that conflict rather than silently replacing it. Images and binary previews are read-only. + +Close the reader with its **X** to return to the conversation. + +## Review Git changes + +Switch **Files** to **Changes** to inspect staged and unstaged files. Open a diff with the eye action, stage or unstage files, and use the commit controls at the top of the staged section. + +Switch to **Commits** to browse repository history and inspect historical diffs. Diffs are read-only; use the layout switch to choose unified or split views. + +## Check Status + +The **Status** panel shows project services and background activity, including **MCP Servers** and **Plugins**. + +- **MCP Servers** exposes connection state and activation controls. +- **Plugins** have separate **Global** and **Project** activation switches. Global changes can affect other projects; a project rule can override the global choice. +- Background shell activity is distinct from an interactive terminal and from the agent's current reply. + +## Preview a web app + +Open the session's **browser preview** from the conversation toolbar. It is attached to that session; the agent can target the visible preview through CodeNomad's browser tools. Opening it does not start a development server. + +Agent-driven native browser control is available in Electron and **Windows Tauri**. Other Tauri platforms use an iframe preview; that is not equivalent to native browser automation. Some sites also prevent iframe embedding. + +## Add a SideCar + +**SideCars** are tabs for web tools running on the CodeNomad server host, such as an editor or terminal service. + +Start the tool separately, then add it in **Settings → SideCars** with its name, port, and protocol. The port refers to the **server host**, not necessarily the device displaying CodeNomad. Match the prefix mode to the tool's base-path support; see the [SideCar examples](https://github.com/NeuralNomadsAI/CodeNomad#sidecars). diff --git a/docs/help/getting-started.md b/docs/help/getting-started.md new file mode 100644 index 000000000..2e7660c89 --- /dev/null +++ b/docs/help/getting-started.md @@ -0,0 +1,31 @@ +# Connect OpenCode + +Use your existing OpenCode V2 setup from a local desktop host or through a CodeNomad server. + +## Local desktop + +Download [the current desktop build](https://github.com/NeuralNomadsAI/CodeNomad/releases/latest), then select your OpenCode executable in setup. CodeNomad prefers executables on `PATH`; **Settings → OpenCode** exposes the selected CLI and the running shared service separately. An installation action is available if you need it. Desktop builds bundle Node.js/npm. + +Open the repository or folder whose sessions you want to access. CodeNomad uses directory ownership to determine which sessions belong to that project; see [projects and worktrees](projects.md). + +## Backend versus execution host + +Git must be available in the **CodeNomad backend's `PATH`**, even when OpenCode executes inside WSL. Git installed only in WSL does not satisfy a Windows backend. Restart the backend after changing its environment. + +With Git unavailable, folder-only sessions remain usable, but Git/worktree features and cross-checkout discovery are unavailable. + +## Browser and remote desktop + +Run CodeNomad on the host containing your projects and OpenCode setup. The standalone server requires Node.js 24 LTS/npm: + +```sh +npx @neuralnomads/codenomad --password "your-password" --launch +``` + +Use the printed connection URL in a browser or CodeNomad's remote connection screen. The server binds to loopback by default; use the [server guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md#remote-access-binding-rules) for remote binding, authentication, and TLS options. + +Project paths, commands, and SideCar ports refer to the **server host**, not the client displaying CodeNomad. Device attachments are the exception: they come from the client device. + +## CLI updates are not service restarts + +Updating OpenCode through CodeNomad changes the installed executable, not the running service. Restart explicitly when you want to activate it; this can interrupt work in all connected clients. Closing CodeNomad leaves the service running. diff --git a/docs/help/help.css b/docs/help/help.css new file mode 100644 index 000000000..7a53207c4 --- /dev/null +++ b/docs/help/help.css @@ -0,0 +1,93 @@ +:root { + color-scheme: light dark; + --canvas: #edf0f4; + --panel: #e0e6ed; + --text: #283a4a; + --muted: #526373; + --accent: #375c9b; + --border: #bdc9d6; + --code: #dce3ec; + font-family: "Segoe UI", system-ui, sans-serif; + line-height: 1.65; + background: var(--canvas); + color: var(--text); +} +@media (prefers-color-scheme: dark) { + :root { + --canvas: #272f40; + --panel: #333e52; + --text: #dce3ef; + --muted: #adbed4; + --accent: #c6b58c; + --border: #53647c; + --code: #333e52; + } +} +* { box-sizing: border-box; } +body { margin: 0; } +a { color: var(--accent); text-underline-offset: .2em; } +a:hover { text-decoration-thickness: 2px; } +:focus-visible { outline: 2px solid var(--accent); outline-offset: 4px; } +.skip-link { position: fixed; top: -100px; left: 1rem; padding: .5rem 1rem; background: var(--canvas); z-index: 3; } +.skip-link:focus { top: 1rem; } +.site-header { display: flex; justify-content: space-between; align-items: center; gap: 1rem; padding: 1rem 2rem; border-bottom: 1px solid var(--border); } +.brand { display: flex; align-items: center; gap: .7rem; color: var(--text); font-size: 1.25rem; font-weight: 650; text-decoration: none; } +.brand span { font-size: .875rem; font-weight: 400; color: var(--muted); border-left: 1px solid var(--border); padding-left: .7rem; } +.site-header nav { display: flex; gap: 1.5rem; font-size: .875rem; } +.layout { display: grid; grid-template-columns: 230px minmax(0, 760px) 190px; gap: 3rem; max-width: 1400px; margin: auto; padding: 2.5rem 2rem; } +.sidebar, .outline { position: sticky; top: 2rem; align-self: start; max-height: calc(100vh - 4rem); overflow: auto; font-size: .875rem; } +.eyebrow { margin: 0 0 .75rem; text-transform: uppercase; letter-spacing: .1em; font-size: .75rem; color: var(--muted); font-family: ui-monospace, monospace; } +.sidebar nav { display: grid; gap: .2rem; } +.sidebar nav a { padding: .6rem .75rem; color: var(--text); text-decoration: none; border-left: 3px solid transparent; } +.sidebar nav a:hover { background: var(--panel); } +.sidebar nav a[aria-current] { background: var(--panel); border-color: var(--accent); font-weight: 600; } +.scope { color: var(--muted); border-top: 1px solid var(--border); margin-top: 1.5rem; padding: 1rem .75rem; font-size: .8rem; } +main { min-width: 0; } +main:focus { outline: none; } +h1, h2, h3 { font-family: "Segoe UI", system-ui, sans-serif; line-height: 1.25; font-weight: 650; scroll-margin-top: 1.5rem; } +h1 { font-size: clamp(2rem, 3vw, 2.8rem); letter-spacing: -.035em; margin: 0 0 1.2rem; } +h2 { font-size: 1.4rem; margin: 2.5rem 0 1rem; } +h3 { font-size: 1.1rem; margin-top: 2rem; } +article > p:first-of-type { font-size: 1.15rem; color: var(--muted); } +p, ul, ol { margin: 0 0 1.2rem; } +ul, ol { padding-left: 1.5rem; } +li { margin: .4rem 0; } +article img { max-width: 100%; height: auto; display: block; border: 1px solid var(--border); margin: 1.5rem 0; } +strong { font-weight: 650; } +code, kbd, pre { font-family: "Cascadia Code", "SFMono-Regular", Consolas, monospace; font-size: .875em; } +code { background: var(--code); padding: .15em .3em; overflow-wrap: anywhere; } +pre { background: var(--code); border-left: 3px solid var(--accent); padding: 1rem; overflow-x: auto; } +pre code { padding: 0; overflow-wrap: normal; } +kbd { border: 1px solid var(--border); padding: .1em .3em; } +table { border-collapse: collapse; width: 100%; font-size: .95rem; margin: 1.5rem 0; } +th, td { border-bottom: 1px solid var(--border); padding: .65rem .5rem; text-align: left; overflow-wrap: anywhere; } +th { font-weight: 600; } +blockquote { border-left: 3px solid var(--accent); margin: 1.5rem 0; padding-left: 1rem; color: var(--muted); } +.outline { color: var(--muted); font-size: .8rem; } +summary { cursor: pointer; color: var(--text); font-weight: 600; } +.outline ul { list-style: none; padding: .5rem 0; margin-bottom: 1rem; } +.outline a { color: var(--muted); text-decoration: none; display: block; padding: .2rem 0; } +.outline a:hover { color: var(--accent); } +footer { display: flex; justify-content: space-between; flex-wrap: wrap; gap: 1rem; margin-top: 3rem; padding-top: 1rem; border-top: 1px solid var(--border); font-size: .8rem; } +@media (max-width: 1150px) { + .layout { grid-template-columns: 210px minmax(0, 760px); gap: 2rem; } + .outline { display: none; } +} +@media (max-width: 720px) { + .site-header { padding: 1rem; gap: .5rem; } + .brand { font-size: 1rem; gap: .4rem; } + .brand span { padding-left: .4rem; } + .site-header nav { gap: .75rem; font-size: .8rem; } + .layout { display: block; padding: 1rem; } + .sidebar { position: static; max-height: none; overflow: visible; border-bottom: 1px solid var(--border); margin-bottom: 2rem; padding-bottom: 1rem; } + .sidebar nav { display: flex; flex-wrap: wrap; gap: .25rem; } + .sidebar nav a { padding: .45rem .6rem; } + .scope { border: 0; margin: .75rem 0 0; padding: 0; } + .scope br { display: none; } + td:last-child { width: 42%; } +} +@media print { + .site-header, .sidebar, .outline, footer, .skip-link { display: none; } + .layout { display: block; padding: 0; } + :root { --canvas: white; --text: black; --muted: #333; --accent: #222; --code: #eee; } +} diff --git a/docs/help/index.md b/docs/help/index.md new file mode 100644 index 000000000..2fa56bf4e --- /dev/null +++ b/docs/help/index.md @@ -0,0 +1,26 @@ +# CodeNomad help + +An OpenCode workspace, across projects, sessions, and worktrees. + +This guide assumes you already use **OpenCode V2**. It covers CodeNomad's interface and workflow, not the basics of agents or OpenCode configuration. + +![CodeNomad workspace with project tabs, sessions, a conversation, and the Status panel](../screenshots/workspace-0.20.png) + +## Find the control you need + +| I want to… | Read | +|---|---| +| Connect CodeNomad to my OpenCode setup | [Connect OpenCode](getting-started.md) | +| Navigate history, attach context, handle requests across sessions | [Conversations](conversations.md) | +| Move a session family between checkouts | [Projects and worktrees](projects.md) | +| Use the file reader, Git views, browser preview, and SideCars | [Files and tools](files-and-tools.md) | +| Control model visibility, accounts, and Global/Project settings | [Settings](settings.md) | +| Diagnose a CodeNomad-specific problem | [Troubleshooting](troubleshooting.md) | + +## What stays shared + +CodeNomad uses the shared OpenCode service. Sessions and history remain available to compatible OpenCode clients; tabs, drafts, and layout are restored separately per CodeNomad window. Closing a tab or window does **not** stop the service. + +## Reference + +For native configuration, use the [OpenCode V2 documentation](https://opencode.ai/v2/docs/). For hosting options, use the [CodeNomad server guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md). diff --git a/docs/help/projects.md b/docs/help/projects.md new file mode 100644 index 000000000..cae529f18 --- /dev/null +++ b/docs/help/projects.md @@ -0,0 +1,31 @@ +# Projects and worktrees + +CodeNomad groups an opened repository and its registered Git worktrees into one project. Session placement still follows its execution directory. + +## Switch projects and sessions + +Use project tabs to switch folders and the session list to switch conversations. Selecting another conversation does not move its files or change its working directory. + +The session list normally shows parent sessions and their subsessions. Search and filters let you find sessions directly, including subsessions when that option is enabled. + +Desktop windows can show different projects or sessions. Tabs, drafts, and layout are restored per window; session history belongs to the shared OpenCode service. + +## Use a Git worktree + +In the composer's worktree selector, select an existing checkout or choose **Create and use worktree**. Both actions move the session and its subsessions to the destination. + +Uncommitted file changes stay in the original checkout; they are not copied to the destination. Creating a worktree outside this selector, for example with Git, does not by itself move a CodeNomad session. + +CodeNomad's default worktree location is **`.codenomad/worktrees`** inside the main project checkout. The Files panel can browse another worktree without moving your session or checking out a branch. + +## Know where execution happens + +Prompts and session commands run in the session's working directory on its execution host. A remote desktop client or browser is only the interface: your project files and tools live on the server host. + +On Windows, a configured WSL OpenCode executable runs inside its selected distribution. Keep its tools and paths available there; Git must also be available to the CodeNomad backend for repository features. + +## Close without stopping work + +Closing a project tab or CodeNomad window detaches that view. It does not shut down the shared OpenCode service. + +**Stop**, workspace-stop actions, and shared-service restarts have different scopes. Read the confirmation before interrupting work beyond the selected session. diff --git a/docs/help/settings.md b/docs/help/settings.md new file mode 100644 index 000000000..5e183669d --- /dev/null +++ b/docs/help/settings.md @@ -0,0 +1,39 @@ +# Settings + +Open **Settings** for CodeNomad's display preferences and native configuration controls. On a local desktop, this opens a separate **Preferences** window. + +## Providers and models + +**Settings → Providers** separates **Models** from **Web search**. + +**Manage models** filters which models CodeNomad shows in selectors. **Active account** switches the provider credential when multiple accounts are available; environment-based connections are read-only. **Connect** adds a connection through the provider's native flow. + +Provider credentials are shared by projects using the same OpenCode service. Changing the active account can affect other clients connected to that service. + +## Web search + +In the Web search group, connect a search provider, then set **Default search** or **For the current project**. A project override takes precedence over the global choice. Search credentials are global; connecting a provider does not by itself select it for every project. + +## OpenCode + +**Settings → OpenCode** shows the selected executable and running service. You can install or update the CLI, check status, and use explicit service controls. + +- **Update** changes the installed executable. +- **Restart shared service** replaces the running OpenCode process and can interrupt work in every connected client. +- **Reload OpenCode configuration** is different from restarting. It rebuilds loaded Locations and can cancel pending requests and close terminals/background commands. Use it deliberately, not as a routine refresh. + +Follow the compatibility information shown by the current CodeNomad release. A newer installed CLI and an older running service can coexist until you restart it. + +## Appearance and interaction + +Choose a light, dark, or automatic appearance and a palette for each mode. You can also adjust language, font sizing, notifications, and conversation content visibility. + +**Ctrl/Cmd+Shift+P** opens the **Command Palette**. **Settings → General → Enter to submit** controls composer submission; shortcut hints reflect the active behavior. + +## Environment variables + +CodeNomad applies the profile's environment variables before each prompt or session command on the execution host. Editing them does not restart the service or change a session until the next send. The client device's environment is not substituted for a remote host's. + +## Voice and speech + +Configure voice input and text-to-speech in their settings sections. Microphone use requires device/browser permission. Availability depends on the selected service and your setup. diff --git a/docs/help/troubleshooting.md b/docs/help/troubleshooting.md new file mode 100644 index 000000000..2d10416ee --- /dev/null +++ b/docs/help/troubleshooting.md @@ -0,0 +1,45 @@ +# Troubleshooting + +Checks for the CodeNomad interface and its connection to OpenCode. Shared-service restarts affect other connected clients too. + +## OpenCode will not connect + +Open **Settings → OpenCode** and check the selected executable, running-service status, and compatibility details. Use **Check status and updates**; install or select a supported OpenCode V2 executable if needed. + +If an update is installed but the old service is still running, use the explicit restart action when it is safe to interrupt shared work. A configuration reload is not an executable upgrade. + +## No model is available + +If a model works in OpenCode but is missing in CodeNomad, check **Settings → Providers → Manage models** for selector visibility, then check the active account. **Models** and **Web search** are separate groups. + +## Git or worktrees are unavailable + +Check Git in the CodeNomad **backend process's `PATH`**, not only in the agent's shell. Restart CodeNomad or its standalone server after changing that environment. + +Git inside WSL alone does not satisfy a Windows backend. Until Git is available, folder-only conversations can still work, but repository and worktree features cannot. + +## A reply seems stuck + +Pending requests are in the area **above the composer**, including requests from subsessions and other conversations. Check that area rather than looking for an editable transcript form. **Status** shows background activity; message-content filters can hide reasoning and tool details. + +Use **Stop** if you want to interrupt the execution. If sending failed, verify the current state before trying again—do not assume the server rejected it before doing any work. + +If the interface itself freezes, [report it](https://github.com/NeuralNomadsAI/CodeNomad/issues) with your CodeNomad version, OpenCode version, OS, desktop host (Tauri/Electron) or browser, and whether the server is remote. Include repeatable steps where possible, but remove credentials and private project content. + +## Remote login or HTTPS fails + +The remote URL and login belong to the **CodeNomad server**, not directly to the OpenCode service. Check its reachability and authentication independently of provider credentials. + +Only trust a certificate warning after verifying that the server is yours. For remote hosts, prefer a trusted certificate or secure tunnel. See the [server authentication and TLS guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md). + +## Desktop-specific startup issues + +For an unnotarized macOS download that Gatekeeper marks as damaged, use the documented [macOS steps](https://github.com/NeuralNomadsAI/CodeNomad#troubleshooting) only after verifying the download's source. + +On Linux with Wayland/NVIDIA, a Tauri WebKitGTK startup failure may be a graphics issue. The [same troubleshooting section](https://github.com/NeuralNomadsAI/CodeNomad#troubleshooting) lists the workaround; Electron is an alternative host. The Tauri Debian package is currently qualified on Ubuntu 24.04, not every Debian-based distribution. + +## Go deeper + +- [OpenCode V2 documentation](https://opencode.ai/v2/docs/) for agents, commands, skills, and tool configuration. +- [CodeNomad server guide](https://github.com/NeuralNomadsAI/CodeNomad/blob/dev/packages/server/README.md) for hosting and connection options. +- [CodeNomad issues](https://github.com/NeuralNomadsAI/CodeNomad/issues) for bugs and feature requests. diff --git a/package.json b/package.json index c3e4b863c..b6f86f5f7 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,9 @@ "build:mac-x64": "npm run build:mac-x64 --workspace @neuralnomads/codenomad-electron-app", "build:binaries": "npm run build:binaries --workspace @neuralnomads/codenomad-electron-app", "typecheck": "npm run typecheck --workspace @codenomad/ui && npm run typecheck --workspace @neuralnomads/codenomad-electron-app", - "bumpVersion": "node ./scripts/bump-version.js" + "bumpVersion": "node ./scripts/bump-version.js", + "build:help": "node scripts/build-help.mjs", + "test:help": "node --test scripts/test-help.mjs" }, "dependencies": { "7zip-bin": "^5.2.0", diff --git a/scripts/build-help.mjs b/scripts/build-help.mjs new file mode 100644 index 000000000..19ef79b12 --- /dev/null +++ b/scripts/build-help.mjs @@ -0,0 +1,79 @@ +import { cp, mkdir, readFile, writeFile } from "node:fs/promises" +import { fileURLToPath } from "node:url" +import { resolve } from "node:path" +import { Marked } from "marked" + +const root = fileURLToPath(new URL("../", import.meta.url)) +const source = resolve(root, "docs/help") +const output = resolve(process.argv[2] ?? resolve(root, "dist/help")) +const pages = [ + ["index", "Overview"], + ["getting-started", "Connect OpenCode"], + ["conversations", "Conversations"], + ["projects", "Projects and worktrees"], + ["files-and-tools", "Files and tools"], + ["settings", "Settings"], + ["troubleshooting", "Troubleshooting"], +] +const repo = "https://github.com/NeuralNomadsAI/CodeNomad" +const escape = (value) => value.replace(/[&<>"']/g, (char) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[char]) + +await mkdir(resolve(output, "assets"), { recursive: true }) +await Promise.all([ + cp(resolve(source, "help.css"), resolve(output, "assets/help.css")), + cp(resolve(root, "docs/screenshots/workspace-0.20.png"), resolve(output, "assets/workspace.png")), + cp(resolve(root, "images/CodeNomad-Icon.png"), resolve(output, "assets/logo.png")), + writeFile(resolve(output, ".nojekyll"), ""), +]) + +for (const [name, label] of pages) { + const headings = [] + const used = new Map() + const markdown = new Marked({ renderer: { + heading(text, level, raw) { + const slug = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") + const count = used.get(slug) ?? 0 + used.set(slug, count + 1) + const id = count ? `${slug}-${count}` : slug + if (level === 2) headings.push({ text: raw, id }) + return `${text}\n` + }, + link(href, title, text) { + const target = href.replace(/^([\w-]+)\.md(?=#|$)/, "$1.html") + return `${text}` + }, + image(href, title, text) { + const target = href === "../screenshots/workspace-0.20.png" ? "assets/workspace.png" : href + return `${escape(text)}` + }, + } }) + const content = markdown.parse(await readFile(resolve(source, `${name}.md`), "utf8")) + const nav = pages.map(([id, title]) => `${title}`).join("\n") + const toc = headings.map(({ text, id }) => `
  • ${escape(text)}
  • `).join("\n") + await writeFile(resolve(output, `${name}.html`), ` + + + + + + + ${label} — CodeNomad help + + + + + + +
    + +
    ${content}
    + +
    + + +`) +} +console.log(`Built ${pages.length} help pages in ${output}`) diff --git a/scripts/test-help.mjs b/scripts/test-help.mjs new file mode 100644 index 000000000..b264f77e8 --- /dev/null +++ b/scripts/test-help.mjs @@ -0,0 +1,35 @@ +import assert from "node:assert/strict" +import { test } from "node:test" +import { mkdtemp, readFile, readdir, rm } from "node:fs/promises" +import { tmpdir } from "node:os" +import { join, resolve } from "node:path" +import { fileURLToPath } from "node:url" +import { execFileSync } from "node:child_process" + +const root = fileURLToPath(new URL("../", import.meta.url)) + +test("English help builds standalone pages with working local links, assets, and anchors", async (ctx) => { + const output = await mkdtemp(join(tmpdir(), "codenomad-help-")) + ctx.after(() => rm(output, { recursive: true, force: true })) + execFileSync(process.execPath, [resolve(root, "scripts/build-help.mjs"), output], { cwd: root }) + const pages = (await readdir(output)).filter((name) => name.endsWith(".html")) + assert.equal(pages.length, 7) + for (const page of pages) { + const html = await readFile(join(output, page), "utf8") + assert.match(html, //) + assert.equal((html.match(/

    match[1]) + assert.equal(ids.length, new Set(ids).size, "unique anchors") + for (const [, url] of html.matchAll(/(?:href|src)="([^"]+)"/g)) { + if (url.startsWith("https://")) continue + assert.ok(!url.startsWith("/"), `project Pages links must stay relative: ${url}`) + assert.ok(!url.includes(".md"), `Markdown link not converted: ${url}`) + const [path, fragment] = url.split("#") + const target = path ? await readFile(join(output, path), "utf8") : html + if (fragment) assert.ok(target.includes(`id="${fragment}"`), `broken anchor: ${page} → ${url}`) + } + } +})