diff --git a/BUILD.md b/BUILD.md index 771dcf90..6043f022 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,6 +1,6 @@ -# Building OpenCode Client Binaries +# Building CodeNomad Binaries -This guide explains how to build distributable binaries for OpenCode Client. +This guide explains how to build distributable binaries for CodeNomad. ## Prerequisites @@ -81,17 +81,17 @@ Binaries are generated in the `release/` directory: ``` release/ -├── OpenCode Client-0.1.0-mac-universal.dmg -├── OpenCode Client-0.1.0-mac-universal.zip -├── OpenCode Client-0.1.0-win-x64.exe -├── OpenCode Client-0.1.0-linux-x64.AppImage +├── CodeNomad-0.1.0-mac-universal.dmg +├── CodeNomad-0.1.0-mac-universal.zip +├── CodeNomad-0.1.0-win-x64.exe +├── CodeNomad-0.1.0-linux-x64.AppImage └── ... ``` ## File Naming Convention ``` -OpenCode Client-{version}-{os}-{arch}.{ext} +CodeNomad-{version}-{os}-{arch}.{ext} ``` - **version**: From package.json (e.g., `0.1.0`) @@ -215,6 +215,16 @@ Edit `package.json` → `build` section to customize: See [electron-builder docs](https://www.electron.build/) for details. +## Brand Assets + +- `images/CodeNomad-Icon.png` — primary asset for in-app logo placements and the 1024×1024 master icon used to generate packaged app icons + +To update the binaries: + +1. Run `node scripts/generate-icons.js images/CodeNomad-Icon.png electron/resources` to round the corners and emit fresh `icon.icns`, `icon.ico`, and `icon.png` files. +2. (Optional) Pass `--radius` to tweak the corner curvature or `--name` to change the filename prefix. +3. If you prefer manual control, export `images/CodeNomad-Icon.png` with your tool of choice and place the generated files in `electron/resources/`. + ## Clean Build Remove previous builds: diff --git a/PROGRESS.md b/PROGRESS.md index 95ddfc90..cce670bf 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -1,4 +1,4 @@ -# OpenCode Client - Development Progress +# CodeNomad - Development Progress ## Completed Tasks diff --git a/README.md b/README.md index 9cb87069..7051e60a 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# OpenCode Client +# CodeNomad A cross-platform desktop application for interacting with OpenCode servers, built with Electron and SolidJS. ## Overview -OpenCode Client provides a multi-instance, multi-session interface for working with AI-powered coding assistants. It manages OpenCode server processes, handles real-time message streaming, and provides an intuitive UI for coding with AI. +CodeNomad provides a multi-instance, multi-session interface for working with AI-powered coding assistants. It manages OpenCode server processes, handles real-time message streaming, and provides an intuitive UI for coding with AI. **🎯 MVP Focus:** This project prioritizes functionality over performance. Performance optimization is intentionally deferred to post-MVP phases. See [docs/MVP-PRINCIPLES.md](docs/MVP-PRINCIPLES.md) for details. diff --git a/TOOL_CALL_IMPLEMENTATION.md b/TOOL_CALL_IMPLEMENTATION.md index 8d368cfd..507159b6 100644 --- a/TOOL_CALL_IMPLEMENTATION.md +++ b/TOOL_CALL_IMPLEMENTATION.md @@ -1,6 +1,6 @@ # Tool Call Rendering Implementation -This document describes how tool calls are rendered in the OpenCode Client, following the patterns established in the TUI. +This document describes how tool calls are rendered in the CodeNomad, following the patterns established in the TUI. ## Overview diff --git a/docs/MVP-PRINCIPLES.md b/docs/MVP-PRINCIPLES.md index 8605164c..f16579c1 100644 --- a/docs/MVP-PRINCIPLES.md +++ b/docs/MVP-PRINCIPLES.md @@ -298,7 +298,7 @@ When the time comes: > **Make it work, then make it better, then make it fast.** -For OpenCode Client MVP: +For CodeNomad MVP: - **Phase 1-7:** Make it work, make it better - **Phase 8+:** Make it fast diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index f592b53e..253270a9 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,4 +1,4 @@ -# OpenCode Client - Project Summary +# CodeNomad - Project Summary ## Current Status @@ -6,7 +6,7 @@ We have completed the MVP milestones (Phases 1-3) and are now operating in post- ## What We've Created -A comprehensive specification and task breakdown for building the OpenCode Client desktop application. +A comprehensive specification and task breakdown for building the CodeNomad desktop application. ## Directory Structure diff --git a/docs/architecture.md b/docs/architecture.md index 246b5959..6c8dec9f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -1,8 +1,8 @@ -# OpenCode Client Architecture +# CodeNomad Architecture ## Overview -OpenCode Client is a cross-platform desktop application built with Electron that provides a multi-instance, multi-session interface for interacting with OpenCode servers. Each instance manages its own OpenCode server process and can handle multiple concurrent sessions. +CodeNomad is a cross-platform desktop application built with Electron that provides a multi-instance, multi-session interface for interacting with OpenCode servers. Each instance manages its own OpenCode server process and can handle multiple concurrent sessions. ## High-Level Architecture diff --git a/docs/build-roadmap.md b/docs/build-roadmap.md index 00829dfb..1fd48cb3 100644 --- a/docs/build-roadmap.md +++ b/docs/build-roadmap.md @@ -1,8 +1,8 @@ -# OpenCode Client Build Roadmap +# CodeNomad Build Roadmap ## Overview -This document outlines the phased approach to building the OpenCode Client desktop application. Each phase builds incrementally on the previous, with clear deliverables and milestones. +This document outlines the phased approach to building the CodeNomad desktop application. Each phase builds incrementally on the previous, with clear deliverables and milestones. **Status:** MVP (Phases 1-3) is complete. Focus now shifts to post-MVP phases starting with multi-instance support and advanced input refinements. diff --git a/docs/user-interface.md b/docs/user-interface.md index 285c3a04..3ea3f62c 100644 --- a/docs/user-interface.md +++ b/docs/user-interface.md @@ -2,7 +2,7 @@ ## Overview -The OpenCode Client interface consists of a two-level tabbed layout with instance tabs at the top and session tabs below. Each session displays a message stream and prompt input. +The CodeNomad interface consists of a two-level tabbed layout with instance tabs at the top and session tabs below. Each session displays a message stream and prompt input. ## Layout Structure @@ -346,7 +346,7 @@ Appears when instance starts: │ │ │ [Folder Icon] │ │ │ -│ Welcome to OpenCode Client │ +│ Start Coding with AI │ │ │ │ Select a folder to start coding with AI │ │ │ diff --git a/electron/main/main.ts b/electron/main/main.ts index 9818e8d2..145f0f50 100644 --- a/electron/main/main.ts +++ b/electron/main/main.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, dialog, ipcMain, nativeTheme, session } from "electron" +import { app, BrowserWindow, dialog, ipcMain, nativeImage, nativeTheme, session } from "electron" import { join } from "path" import { createApplicationMenu } from "./menu" import { setupInstanceIPC } from "./ipc" @@ -15,9 +15,18 @@ setupStorageIPC() let mainWindow: BrowserWindow | null = null +function getIconPath() { + if (app.isPackaged) { + return join(process.resourcesPath, "icon.png") + } + + return join(app.getAppPath(), "electron/resources/icon.png") +} + function createWindow() { const prefersDark = true //nativeTheme.shouldUseDarkColors const backgroundColor = prefersDark ? "#1a1a1a" : "#ffffff" + const iconPath = getIconPath() mainWindow = new BrowserWindow({ width: 1400, @@ -25,6 +34,7 @@ function createWindow() { minWidth: 800, minHeight: 600, backgroundColor, + icon: iconPath, webPreferences: { preload: join(__dirname, "../preload/index.js"), contextIsolation: true, @@ -65,6 +75,13 @@ app.whenReady().then(() => { app.on("browser-window-created", (_, window) => { window.webContents.session.setSpellCheckerEnabled(false) }) + + if (app.dock) { + const dockIcon = nativeImage.createFromPath(getIconPath()) + if (!dockIcon.isEmpty()) { + app.dock.setIcon(dockIcon) + } + } } console.log("[spellcheck] default session enabled:", session.defaultSession.isSpellCheckerEnabled()) diff --git a/electron/main/menu.ts b/electron/main/menu.ts index 6ce07972..37f9e340 100644 --- a/electron/main/menu.ts +++ b/electron/main/menu.ts @@ -7,7 +7,7 @@ export function createApplicationMenu(mainWindow: BrowserWindow) { ...(isMac ? [ { - label: "OpenCode Client", + label: "CodeNomad", submenu: [ { role: "about" as const }, { type: "separator" as const }, diff --git a/electron/resources/icon.icns b/electron/resources/icon.icns new file mode 100644 index 00000000..4431bb43 Binary files /dev/null and b/electron/resources/icon.icns differ diff --git a/electron/resources/icon.ico b/electron/resources/icon.ico new file mode 100644 index 00000000..7d50243c Binary files /dev/null and b/electron/resources/icon.ico differ diff --git a/electron/resources/icon.png b/electron/resources/icon.png new file mode 100644 index 00000000..4c08915d Binary files /dev/null and b/electron/resources/icon.png differ diff --git a/images/CodeNomad-Icon-original.png b/images/CodeNomad-Icon-original.png new file mode 100644 index 00000000..9ce5d712 Binary files /dev/null and b/images/CodeNomad-Icon-original.png differ diff --git a/images/CodeNomad-Icon.png b/images/CodeNomad-Icon.png new file mode 100644 index 00000000..754b997c Binary files /dev/null and b/images/CodeNomad-Icon.png differ diff --git a/package-lock.json b/package-lock.json index e4dd3129..93f46eb3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,6 +26,8 @@ "electron": "39.0.0", "electron-builder": "^24.0.0", "electron-vite": "4.0.1", + "png2icons": "^2.0.1", + "pngjs": "^7.0.0", "postcss": "8.5.6", "tailwindcss": "3", "typescript": "^5.3.0", @@ -77,7 +79,6 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -2288,7 +2289,6 @@ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2464,6 +2464,7 @@ "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^2.1.0", "async": "^3.2.4", @@ -2483,6 +2484,7 @@ "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.1.4", "graceful-fs": "^4.2.0", @@ -2505,6 +2507,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2520,7 +2523,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.1.1", @@ -2528,6 +2532,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -2746,6 +2751,7 @@ "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -2821,7 +2827,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -3285,6 +3290,7 @@ "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "buffer-crc32": "^0.2.13", "crc32-stream": "^4.0.2", @@ -3391,6 +3397,7 @@ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "crc32": "bin/crc32.njs" }, @@ -3404,6 +3411,7 @@ "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "crc-32": "^1.2.0", "readable-stream": "^3.4.0" @@ -3636,7 +3644,6 @@ "integrity": "sha512-rcJUkMfnJpfCboZoOOPf4L29TRtEieHNOeAbYPWPxlaBw/Z1RKrRA86dOI9rwaI4tQSc/RD82zTNHprfUHXsoQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "builder-util": "24.13.1", @@ -3821,6 +3828,7 @@ "integrity": "sha512-oHkV0iogWfyK+ah9ZIvMDpei1m9ZRpdXcvde1wTpra2U8AFDNNpqJdnin5z+PM1GbQ5BoaKCWas2HSjtR0HwMg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "app-builder-lib": "24.13.3", "archiver": "^5.3.1", @@ -3834,6 +3842,7 @@ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -3849,6 +3858,7 @@ "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "universalify": "^2.0.0" }, @@ -3862,6 +3872,7 @@ "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">= 10.0.0" } @@ -4343,7 +4354,8 @@ "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fs-extra": { "version": "8.1.0", @@ -5062,7 +5074,8 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/isbinaryfile": { "version": "5.0.6", @@ -5124,7 +5137,6 @@ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", "dev": true, "license": "MIT", - "peer": true, "bin": { "jiti": "bin/jiti.js" } @@ -5230,6 +5242,7 @@ "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "readable-stream": "^2.0.5" }, @@ -5243,6 +5256,7 @@ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -5258,7 +5272,8 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lazystream/node_modules/string_decoder": { "version": "1.1.1", @@ -5266,6 +5281,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -5302,35 +5318,40 @@ "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lowercase-keys": { "version": "2.0.0", @@ -5985,6 +6006,26 @@ "node": ">=10.4.0" } }, + "node_modules/png2icons": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/png2icons/-/png2icons-2.0.1.tgz", + "integrity": "sha512-GDEQJr8OG4e6JMp7mABtXFSEpgJa1CCpbQiAR+EjhkHJHnUL9zPPtbOrjsMD8gUbikgv3j7x404b0YJsV3aVFA==", + "dev": true, + "license": "MIT", + "bin": { + "png2icons": "png2icons-cli.js" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -6005,7 +6046,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -6154,7 +6194,8 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/progress": { "version": "2.0.3", @@ -6303,6 +6344,7 @@ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -6318,6 +6360,7 @@ "integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "minimatch": "^5.1.0" } @@ -6535,7 +6578,8 @@ "url": "https://feross.org/support" } ], - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/safer-buffer": { "version": "2.1.2", @@ -6601,7 +6645,6 @@ "resolved": "https://registry.npmjs.org/seroval/-/seroval-1.3.2.tgz", "integrity": "sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -6729,7 +6772,6 @@ "resolved": "https://registry.npmjs.org/solid-js/-/solid-js-1.9.10.tgz", "integrity": "sha512-Coz956cos/EPDlhs6+jsdTxKuJDPT7B5SVIWgABwROyxjY7Xbr8wkzD68Et+NxnV7DLJ3nJdAC2r9InuV/4Jew==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.1.0", "seroval": "~1.3.0", @@ -6849,6 +6891,7 @@ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "safe-buffer": "~5.2.0" } @@ -7106,6 +7149,7 @@ "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", @@ -7489,7 +7533,6 @@ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", @@ -8152,6 +8195,7 @@ "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "archiver-utils": "^3.0.4", "compress-commons": "^4.1.2", @@ -8167,6 +8211,7 @@ "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "glob": "^7.2.3", "graceful-fs": "^4.2.0", diff --git a/package.json b/package.json index f2bd4968..538d34cc 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@opencode-ai/client", "version": "0.1.0", - "description": "OpenCode desktop client - multi-instance, multi-session AI coding interface", + "description": "CodeNomad desktop client - multi-instance, multi-session AI coding interface", "author": "OpenCode Team", "type": "module", "main": "dist/main/main.js", @@ -41,6 +41,8 @@ "@tsconfig/bun": "^1.0.9", "autoprefixer": "10.4.21", "electron": "39.0.0", + "png2icons": "^2.0.1", + "pngjs": "^7.0.0", "electron-builder": "^24.0.0", "electron-vite": "4.0.1", "postcss": "8.5.6", @@ -51,7 +53,7 @@ }, "build": { "appId": "ai.opencode.client", - "productName": "OpenCode Client", + "productName": "CodeNomad", "directories": { "output": "release", "buildResources": "electron/resources" diff --git a/scripts/build.js b/scripts/build.js index a6e07059..bd2d8a22 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -111,7 +111,7 @@ const platform = process.argv[2] || "mac" console.log(` ╔════════════════════════════════════════╗ -║ OpenCode Client - Binary Builder ║ +║ CodeNomad - Binary Builder ║ ╚════════════════════════════════════════╝ `) diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js new file mode 100644 index 00000000..c049ef62 --- /dev/null +++ b/scripts/generate-icons.js @@ -0,0 +1,155 @@ +#!/usr/bin/env node + +import { mkdirSync, readFileSync, writeFileSync } from "fs" +import { resolve, join, basename } from "path" +import { PNG } from "pngjs" +import png2icons from "png2icons" + +function printUsage() { + console.log(`\nUsage: node scripts/generate-icons.js [outputDir] [--name icon] [--radius 0.22]\n\nOptions:\n --name Base filename for generated assets (default: icon)\n --radius Corner radius ratio between 0 and 0.5 (default: 0.22)\n --help Show this message\n`) +} + +function parseArgs(argv) { + const args = [...argv] + const options = { + name: "icon", + radius: 0.22, + } + + for (let i = 0; i < args.length; i++) { + const token = args[i] + if (token === "--help" || token === "-h") { + options.help = true + continue + } + if (token === "--name" && i + 1 < args.length) { + options.name = args[i + 1] + i++ + continue + } + if (token === "--radius" && i + 1 < args.length) { + options.radius = Number(args[i + 1]) + i++ + continue + } + if (!options.input) { + options.input = token + continue + } + if (!options.output) { + options.output = token + continue + } + } + + return options +} + +function applyRoundedCorners(png, ratio) { + const { width, height, data } = png + const clamped = Math.max(0, Math.min(ratio, 0.5)) + if (clamped === 0) return png + + const radius = Math.max(1, Math.min(width, height) * clamped) + const radiusSq = radius * radius + const rightThreshold = width - radius + const bottomThreshold = height - radius + + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const idx = (width * y + x) * 4 + if (data[idx + 3] === 0) continue + + const px = x + 0.5 + const py = y + 0.5 + + const inLeft = px < radius + const inRight = px > rightThreshold + const inTop = py < radius + const inBottom = py > bottomThreshold + + let outside = false + + if (inLeft && inTop) { + outside = (px - radius) ** 2 + (py - radius) ** 2 > radiusSq + } else if (inRight && inTop) { + outside = (px - rightThreshold) ** 2 + (py - radius) ** 2 > radiusSq + } else if (inLeft && inBottom) { + outside = (px - radius) ** 2 + (py - bottomThreshold) ** 2 > radiusSq + } else if (inRight && inBottom) { + outside = (px - rightThreshold) ** 2 + (py - bottomThreshold) ** 2 > radiusSq + } + + if (outside) { + data[idx + 3] = 0 + } + } + } + + return png +} + +async function main() { + const args = parseArgs(process.argv.slice(2)) + + if (args.help || !args.input) { + printUsage() + process.exit(args.help ? 0 : 1) + } + + const inputPath = resolve(args.input) + const outputDir = resolve(args.output || "electron/resources") + const baseName = args.name || basename(inputPath, ".png") + const radiusRatio = Number.isFinite(args.radius) ? args.radius : 0.22 + + let buffer + try { + buffer = readFileSync(inputPath) + } catch (error) { + console.error(`Failed to read ${inputPath}:`, error.message) + process.exit(1) + } + + let png + try { + png = PNG.sync.read(buffer) + } catch (error) { + console.error("Input must be a valid PNG:", error.message) + process.exit(1) + } + + applyRoundedCorners(png, radiusRatio) + + const roundedBuffer = PNG.sync.write(png) + + try { + mkdirSync(outputDir, { recursive: true }) + } catch (error) { + console.error("Failed to create output directory:", error.message) + process.exit(1) + } + + const pngPath = join(outputDir, `${baseName}.png`) + writeFileSync(pngPath, roundedBuffer) + + const icns = png2icons.createICNS(roundedBuffer, png2icons.BICUBIC, false) + if (!icns) { + console.error("Failed to create ICNS file. Make sure the source PNG is at least 256x256.") + process.exit(1) + } + writeFileSync(join(outputDir, `${baseName}.icns`), icns) + + const ico = png2icons.createICO(roundedBuffer, png2icons.BICUBIC, false) + if (!ico) { + console.error("Failed to create ICO file. Make sure the source PNG is at least 256x256.") + process.exit(1) + } + writeFileSync(join(outputDir, `${baseName}.ico`), ico) + + console.log(`\nGenerated assets in ${outputDir}:`) + console.log(`- ${baseName}.png`) + console.log(`- ${baseName}.icns`) + console.log(`- ${baseName}.ico`) +} + +main() diff --git a/src/components/advanced-settings-modal.tsx b/src/components/advanced-settings-modal.tsx index 5ae364a4..06a60bbe 100644 --- a/src/components/advanced-settings-modal.tsx +++ b/src/components/advanced-settings-modal.tsx @@ -20,9 +20,6 @@ const AdvancedSettingsModal: Component = (props) =>
Advanced Settings - - Configure the OpenCode binary and environment variables used when launching new instances. -
diff --git a/src/components/empty-state.tsx b/src/components/empty-state.tsx index a765ddeb..e0e690ad 100644 --- a/src/components/empty-state.tsx +++ b/src/components/empty-state.tsx @@ -1,5 +1,7 @@ import { Component } from "solid-js" -import { Folder, Loader2 } from "lucide-solid" +import { Loader2 } from "lucide-solid" + +const codeNomadIcon = new URL("../../images/CodeNomad-Icon.png", import.meta.url).href interface EmptyStateProps { onSelectFolder: () => void @@ -11,13 +13,13 @@ const EmptyState: Component = (props) => {
- + CodeNomad logo
-

Welcome to OpenCode Client

- +

CodeNomad

Select a folder to start coding with AI

+
- - + + +
- - )} - - - - - -
-
-

Browse for Folder

-

Select any folder on your computer

-
- -
- -
- - {/* Advanced settings section */} -
- -
- Configure the OpenCode binary and environment variables. -
-
-
- - - - -
-
-
-

Starting instance…

-

Hang tight while we prepare your workspace.

+ +
+
+
+

Starting instance…

+

Hang tight while we prepare your workspace.

+
-
-
-
+ +
- OpenCode Client + CodeNomad