diff --git a/README.md b/README.md index 9276ad4c..eae634f3 100644 --- a/README.md +++ b/README.md @@ -44,19 +44,22 @@ Run CodeNomad as a local server and access it via your web browser. Perfect for npx @neuralnomads/codenomad --launch ``` -For dev version +Full server/CLI documentation (flags + env vars, TLS, auth, remote access): +- [packages/server/README.md](packages/server/README.md) + +To see all available options: + +```bash +npx @neuralnomads/codenomad --help +``` + +### 🧪 Dev Releases +Bleeding-edge builds are published as GitHub pre-releases and are generated automatically from the `dev` branch. ```bash npx @neuralnomads/codenomad-dev --launch ``` -Dev builds are published as GitHub pre-releases: -https://github.com/shantur/CodeNomad/releases - -Dev releases are bleeding-edge builds, generated automatically every time a new commit is pushed to the `dev` branch. - -This command starts the server and opens the web client in your default browser. - ## Highlights - **Multi-Instance**: Juggle several OpenCode sessions side-by-side with tabs. diff --git a/packages/server/README.md b/packages/server/README.md index 2eff0a24..cb798eb1 100644 --- a/packages/server/README.md +++ b/packages/server/README.md @@ -31,6 +31,12 @@ You can run CodeNomad directly without installing it: npx @neuralnomads/codenomad --launch ``` +To list all CLI options: + +```sh +npx @neuralnomads/codenomad --help +``` + On startup, CodeNomad prints two URLs: - `Local Connection URL : ...` (used by desktop shells) @@ -44,6 +50,16 @@ npm install -g @neuralnomads/codenomad codenomad --launch ``` +### Install Locally (per-project) +If you prefer to install CodeNomad into a project and run the local binary: + +```sh +npm install @neuralnomads/codenomad +npx codenomad --launch +``` + +(`npx codenomad ...` will use `./node_modules/.bin/codenomad` when present.) + ### Common Flags You can configure the server using flags or environment variables: @@ -63,10 +79,30 @@ You can configure the server using flags or environment variables: | `--config ` | `CLI_CONFIG` | Config file location | | `--launch` | `CLI_LAUNCH` | Open the UI in a Chromium-based browser | | `--log-level ` | `CLI_LOG_LEVEL` | Logging level (trace, debug, info, warn, error) | +| `--log-destination ` | `CLI_LOG_DESTINATION` | Log destination file (defaults to stdout) | | `--username ` | `CODENOMAD_SERVER_USERNAME` | Username for CodeNomad's internal auth (default `codenomad`) | | `--password ` | `CODENOMAD_SERVER_PASSWORD` | Password for CodeNomad's internal auth | | `--generate-token` | `CODENOMAD_GENERATE_TOKEN` | Emit a one-time local bootstrap token for desktop flows | | `--dangerously-skip-auth` | `CODENOMAD_SKIP_AUTH` | Disable CodeNomad's internal auth (use only behind a trusted perimeter) | +| `--ui-dir ` | `CLI_UI_DIR` | Directory containing the built UI bundle | +| `--ui-dev-server ` | `CLI_UI_DEV_SERVER` | Proxy UI requests to a running dev server (requires `--https=false --http=true`) | +| `--ui-no-update` | `CLI_UI_NO_UPDATE` | Disable remote UI updates | +| `--ui-auto-update ` | `CLI_UI_AUTO_UPDATE` | Enable remote UI updates (true|false) | +| `--ui-manifest-url ` | `CLI_UI_MANIFEST_URL` | Remote UI manifest URL | + +### Dev Releases (Advanced) +If you want the latest bleeding-edge builds (published as GitHub pre-releases), use the dev package: + +```sh +npx @neuralnomads/codenomad-dev --launch +``` + +These environment variables control how CodeNomad checks for dev updates: + +| Env Variable | Description | +|-------------|-------------| +| `CODENOMAD_UPDATE_CHANNEL` | Update channel (use `dev` to enable dev build update checks) | +| `CODENOMAD_GITHUB_REPO` | GitHub repo used for dev release checks (default `NeuralNomadsAI/CodeNomad`) | ### HTTP vs HTTPS diff --git a/packages/ui/src/App.tsx b/packages/ui/src/App.tsx index c4e747c8..717f4641 100644 --- a/packages/ui/src/App.tsx +++ b/packages/ui/src/App.tsx @@ -61,6 +61,7 @@ const App: Component = () => { serverSettings, recordWorkspaceLaunch, toggleShowThinkingBlocks, + toggleKeyboardShortcutHints, toggleShowTimelineTools, toggleAutoCleanupBlankSessions, toggleUsageMetrics, @@ -81,6 +82,13 @@ const App: Component = () => { const [remoteAccessOpen, setRemoteAccessOpen] = createSignal(false) const [instanceTabBarHeight, setInstanceTabBarHeight] = createSignal(0) + createEffect(() => { + if (typeof document === "undefined") return + const shouldShow = + runtimeEnv.host !== "web" && runtimeEnv.platform !== "mobile" && (preferences().showKeyboardShortcutHints ?? true) + document.documentElement.dataset.keyboardHints = shouldShow ? "show" : "hide" + }) + const updateInstanceTabBarHeight = () => { if (typeof document === "undefined") return const element = document.querySelector(".tab-bar-instance") @@ -294,6 +302,7 @@ const App: Component = () => { preferences, toggleAutoCleanupBlankSessions, toggleShowThinkingBlocks, + toggleKeyboardShortcutHints, toggleShowTimelineTools, toggleUsageMetrics, togglePromptSubmitOnEnter, diff --git a/packages/ui/src/components/command-palette.tsx b/packages/ui/src/components/command-palette.tsx index 36416617..87947b5a 100644 --- a/packages/ui/src/components/command-palette.tsx +++ b/packages/ui/src/components/command-palette.tsx @@ -112,6 +112,10 @@ const CommandPalette: Component = (props) => { const groupedCommandList = () => processedCommands().groups const orderedCommands = () => processedCommands().ordered + + const isCommandDisabled = (command: Command) => { + return command.disabled ? Boolean(resolveResolvable(command.disabled)) : false + } const selectedIndex = createMemo(() => { const ordered = orderedCommands() if (ordered.length === 0) return -1 @@ -138,10 +142,11 @@ const CommandPalette: Component = (props) => { } return } - + const currentId = selectedCommandId() if (!currentId || !ordered.some((cmd) => cmd.id === currentId)) { - setSelectedCommandId(ordered[0].id) + const firstEnabled = ordered.find((cmd) => !isCommandDisabled(cmd)) + setSelectedCommandId((firstEnabled || ordered[0])?.id ?? null) } }) @@ -195,12 +200,14 @@ const CommandPalette: Component = (props) => { if (index < 0 || index >= ordered.length) return const command = ordered[index] if (!command) return + if (isCommandDisabled(command)) return props.onExecute(command) props.onClose() } } function handleCommandClick(command: Command) { + if (isCommandDisabled(command)) return props.onExecute(command) props.onClose() } @@ -265,11 +272,13 @@ const CommandPalette: Component = (props) => { {(command, localIndex) => { const commandIndex = group.startIndex + localIndex() + const disabled = isCommandDisabled(command) return ( @@ -573,7 +573,7 @@ const FolderSelectionView: Component = (props) => { - @@ -539,7 +539,7 @@ const InstanceWelcomeView: Component = (props) => { - -
+
= (props) => {
- + Cmd+Enter diff --git a/packages/ui/src/lib/commands.ts b/packages/ui/src/lib/commands.ts index a38b2fec..f158da5f 100644 --- a/packages/ui/src/lib/commands.ts +++ b/packages/ui/src/lib/commands.ts @@ -18,6 +18,7 @@ export interface Command { description: Resolvable keywords?: Resolvable shortcut?: KeyboardShortcut + disabled?: Resolvable action: () => void | Promise category?: Resolvable } diff --git a/packages/ui/src/lib/hooks/use-commands.ts b/packages/ui/src/lib/hooks/use-commands.ts index bdd2e8d5..c89a95d4 100644 --- a/packages/ui/src/lib/hooks/use-commands.ts +++ b/packages/ui/src/lib/hooks/use-commands.ts @@ -14,6 +14,7 @@ import { getLogger } from "../logger" import { requestData } from "../opencode-api" import { emitSessionSidebarRequest } from "../session-sidebar-events" import { tGlobal } from "../i18n" +import { runtimeEnv } from "../runtime-env" const log = getLogger("actions") @@ -28,6 +29,7 @@ function splitKeywords(key: string): string[] { export interface UseCommandsOptions { preferences: Accessor toggleShowThinkingBlocks: () => void + toggleKeyboardShortcutHints: () => void toggleShowTimelineTools: () => void toggleUsageMetrics: () => void toggleAutoCleanupBlankSessions: () => void @@ -454,6 +456,26 @@ export function useCommands(options: UseCommandsOptions) { action: options.toggleShowTimelineTools, }) + commandRegistry.register({ + id: "keyboard-shortcut-hints", + label: () => + tGlobal( + options.preferences().showKeyboardShortcutHints + ? "commands.keyboardShortcutHints.label.hide" + : "commands.keyboardShortcutHints.label.show", + ), + description: () => + tGlobal( + runtimeEnv.host === "web" + ? "commands.keyboardShortcutHints.description.disabledWeb" + : "commands.keyboardShortcutHints.description", + ), + category: "System", + keywords: () => splitKeywords("commands.keyboardShortcutHints.keywords"), + disabled: () => runtimeEnv.host === "web", + action: options.toggleKeyboardShortcutHints, + }) + commandRegistry.register({ id: "thinking-default-visibility", label: () => { diff --git a/packages/ui/src/lib/i18n/messages/en/commands.ts b/packages/ui/src/lib/i18n/messages/en/commands.ts index 66ff78f7..5b26c0c9 100644 --- a/packages/ui/src/lib/i18n/messages/en/commands.ts +++ b/packages/ui/src/lib/i18n/messages/en/commands.ts @@ -97,6 +97,12 @@ export const commandMessages = { "commands.timelineToolCalls.description": "Toggle tool call entries in the message timeline", "commands.timelineToolCalls.keywords": "timeline, tool, toggle", + "commands.keyboardShortcutHints.label.show": "Show Keyboard Shortcut Hints", + "commands.keyboardShortcutHints.label.hide": "Hide Keyboard Shortcut Hints", + "commands.keyboardShortcutHints.description": "Show or hide keyboard shortcut hints across the UI", + "commands.keyboardShortcutHints.description.disabledWeb": "Disabled in WebUI (shortcut hints are always hidden)", + "commands.keyboardShortcutHints.keywords": "shortcut, shortcuts, keyboard, keybind, hints", + "commands.common.expanded": "Expanded", "commands.common.collapsed": "Collapsed", "commands.common.visible": "Visible", diff --git a/packages/ui/src/lib/i18n/messages/es/commands.ts b/packages/ui/src/lib/i18n/messages/es/commands.ts index 0bad4e2f..c6a75e7e 100644 --- a/packages/ui/src/lib/i18n/messages/es/commands.ts +++ b/packages/ui/src/lib/i18n/messages/es/commands.ts @@ -97,6 +97,12 @@ export const commandMessages = { "commands.timelineToolCalls.description": "Alternar entradas de llamadas de herramienta en la línea de tiempo de mensajes", "commands.timelineToolCalls.keywords": "línea de tiempo, herramienta, alternar", + "commands.keyboardShortcutHints.label.show": "Mostrar atajos de teclado", + "commands.keyboardShortcutHints.label.hide": "Ocultar atajos de teclado", + "commands.keyboardShortcutHints.description": "Mostrar u ocultar sugerencias de atajos de teclado en la interfaz", + "commands.keyboardShortcutHints.description.disabledWeb": "Desactivado en WebUI (los atajos siempre se ocultan)", + "commands.keyboardShortcutHints.keywords": "atajo, atajos, teclado, keybind, pistas", + "commands.common.expanded": "Expandido", "commands.common.collapsed": "Colapsado", "commands.common.visible": "Visible", diff --git a/packages/ui/src/lib/i18n/messages/fr/commands.ts b/packages/ui/src/lib/i18n/messages/fr/commands.ts index 52bdea76..63e7c666 100644 --- a/packages/ui/src/lib/i18n/messages/fr/commands.ts +++ b/packages/ui/src/lib/i18n/messages/fr/commands.ts @@ -97,6 +97,12 @@ export const commandMessages = { "commands.timelineToolCalls.description": "Afficher/masquer les entrées d'appel d'outil dans la timeline des messages", "commands.timelineToolCalls.keywords": "timeline, outil, basculer", + "commands.keyboardShortcutHints.label.show": "Afficher les raccourcis clavier", + "commands.keyboardShortcutHints.label.hide": "Masquer les raccourcis clavier", + "commands.keyboardShortcutHints.description": "Afficher ou masquer les indices de raccourcis clavier dans l'interface", + "commands.keyboardShortcutHints.description.disabledWeb": "Désactivé en WebUI (les raccourcis sont toujours masqués)", + "commands.keyboardShortcutHints.keywords": "raccourci, raccourcis, clavier, keybind, indices", + "commands.common.expanded": "Développé", "commands.common.collapsed": "Réduit", "commands.common.visible": "Visible", diff --git a/packages/ui/src/lib/i18n/messages/ja/commands.ts b/packages/ui/src/lib/i18n/messages/ja/commands.ts index 30a2adc5..75c1c5f3 100644 --- a/packages/ui/src/lib/i18n/messages/ja/commands.ts +++ b/packages/ui/src/lib/i18n/messages/ja/commands.ts @@ -97,6 +97,12 @@ export const commandMessages = { "commands.timelineToolCalls.description": "メッセージタイムラインのツールコール表示を切り替え", "commands.timelineToolCalls.keywords": "タイムライン, ツール, 切り替え, timeline, tool, toggle", + "commands.keyboardShortcutHints.label.show": "キーボードショートカットのヒントを表示", + "commands.keyboardShortcutHints.label.hide": "キーボードショートカットのヒントを非表示", + "commands.keyboardShortcutHints.description": "UI 全体のキーボードショートカットヒントを表示/非表示", + "commands.keyboardShortcutHints.description.disabledWeb": "WebUI では無効(ヒントは常に非表示)", + "commands.keyboardShortcutHints.keywords": "ショートカット, キーボード, ヒント, shortcuts, keyboard, hints", + "commands.common.expanded": "展開", "commands.common.collapsed": "折りたたみ", "commands.common.visible": "表示", diff --git a/packages/ui/src/lib/i18n/messages/ru/commands.ts b/packages/ui/src/lib/i18n/messages/ru/commands.ts index 6c3f28ec..068f020d 100644 --- a/packages/ui/src/lib/i18n/messages/ru/commands.ts +++ b/packages/ui/src/lib/i18n/messages/ru/commands.ts @@ -97,6 +97,12 @@ export const commandMessages = { "commands.timelineToolCalls.description": "Переключить отображение вызовов инструментов в таймлайне сообщений", "commands.timelineToolCalls.keywords": "таймлайн, tool, переключить", + "commands.keyboardShortcutHints.label.show": "Показать подсказки сочетаний", + "commands.keyboardShortcutHints.label.hide": "Скрыть подсказки сочетаний", + "commands.keyboardShortcutHints.description": "Показать или скрыть подсказки сочетаний клавиш в интерфейсе", + "commands.keyboardShortcutHints.description.disabledWeb": "Отключено в WebUI (подсказки всегда скрыты)", + "commands.keyboardShortcutHints.keywords": "shortcut, shortcuts, keyboard, keybind, подсказки", + "commands.common.expanded": "Развернуто", "commands.common.collapsed": "Свернуто", "commands.common.visible": "Видимо", diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts index 9c95f63e..85997488 100644 --- a/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts +++ b/packages/ui/src/lib/i18n/messages/zh-Hans/commands.ts @@ -97,6 +97,12 @@ export const commandMessages = { "commands.timelineToolCalls.description": "切换消息时间轴中的工具调用条目", "commands.timelineToolCalls.keywords": "timeline, tool, toggle, 时间轴, 工具, 切换", + "commands.keyboardShortcutHints.label.show": "显示键盘快捷键提示", + "commands.keyboardShortcutHints.label.hide": "隐藏键盘快捷键提示", + "commands.keyboardShortcutHints.description": "显示或隐藏界面中的键盘快捷键提示", + "commands.keyboardShortcutHints.description.disabledWeb": "WebUI 中已禁用(提示始终隐藏)", + "commands.keyboardShortcutHints.keywords": "shortcuts, keyboard, hints, 快捷键, 键盘, 提示", + "commands.common.expanded": "展开", "commands.common.collapsed": "折叠", "commands.common.visible": "可见", diff --git a/packages/ui/src/stores/preferences.tsx b/packages/ui/src/stores/preferences.tsx index e66af3e9..96ec386a 100644 --- a/packages/ui/src/stores/preferences.tsx +++ b/packages/ui/src/stores/preferences.tsx @@ -29,6 +29,7 @@ export type ListeningMode = "local" | "all" export interface UiSettings { showThinkingBlocks: boolean + showKeyboardShortcutHints: boolean thinkingBlocksExpansion: ExpansionPreference showTimelineTools: boolean promptSubmitOnEnter: boolean @@ -100,6 +101,7 @@ const MAX_FAVORITE_MODELS = 50 const defaultUiSettings: UiSettings = { showThinkingBlocks: false, + showKeyboardShortcutHints: true, thinkingBlocksExpansion: "expanded", showTimelineTools: true, promptSubmitOnEnter: false, @@ -119,6 +121,8 @@ function normalizeUiSettings(input?: Partial | null): UiSettings { const sanitized = input ?? {} return { showThinkingBlocks: sanitized.showThinkingBlocks ?? defaultUiSettings.showThinkingBlocks, + showKeyboardShortcutHints: + sanitized.showKeyboardShortcutHints ?? defaultUiSettings.showKeyboardShortcutHints, thinkingBlocksExpansion: sanitized.thinkingBlocksExpansion ?? defaultUiSettings.thinkingBlocksExpansion, showTimelineTools: sanitized.showTimelineTools ?? defaultUiSettings.showTimelineTools, promptSubmitOnEnter: sanitized.promptSubmitOnEnter ?? defaultUiSettings.promptSubmitOnEnter, @@ -444,6 +448,10 @@ function toggleShowThinkingBlocks(): void { updateUiSettings({ showThinkingBlocks: !preferences().showThinkingBlocks }) } +function toggleKeyboardShortcutHints(): void { + updatePreferences({ showKeyboardShortcutHints: !preferences().showKeyboardShortcutHints }) +} + function toggleShowTimelineTools(): void { updateUiSettings({ showTimelineTools: !preferences().showTimelineTools }) } @@ -519,6 +527,7 @@ interface ConfigContextValue { // ui settings helpers toggleShowThinkingBlocks: typeof toggleShowThinkingBlocks + toggleKeyboardShortcutHints: typeof toggleKeyboardShortcutHints toggleShowTimelineTools: typeof toggleShowTimelineTools toggleUsageMetrics: typeof toggleUsageMetrics toggleAutoCleanupBlankSessions: typeof toggleAutoCleanupBlankSessions @@ -561,6 +570,7 @@ const configContextValue: ConfigContextValue = { getModelThinkingSelection, setModelThinkingSelection, toggleShowThinkingBlocks, + toggleKeyboardShortcutHints, toggleShowTimelineTools, toggleUsageMetrics, toggleAutoCleanupBlankSessions, @@ -635,6 +645,7 @@ export { getModelThinkingSelection, setModelThinkingSelection, toggleShowThinkingBlocks, + toggleKeyboardShortcutHints, toggleShowTimelineTools, toggleUsageMetrics, toggleAutoCleanupBlankSessions, diff --git a/packages/ui/src/styles/panels/modal.css b/packages/ui/src/styles/panels/modal.css index 2fa598b5..4edc3b81 100644 --- a/packages/ui/src/styles/panels/modal.css +++ b/packages/ui/src/styles/panels/modal.css @@ -46,6 +46,11 @@ color: var(--text-primary); } +.modal-item:disabled { + opacity: 0.55; + cursor: not-allowed; +} + .modal-list-container[data-pointer-mode="pointer"] .modal-item:hover { background-color: var(--surface-hover); } diff --git a/packages/ui/src/styles/utilities.css b/packages/ui/src/styles/utilities.css index 230f9bd0..44aaa1fd 100644 --- a/packages/ui/src/styles/utilities.css +++ b/packages/ui/src/styles/utilities.css @@ -153,6 +153,19 @@ @apply opacity-50; } +/* + Shortcut hints are useful on desktop native apps, but are noisy/irrelevant on + touch-first devices and in WebUI where browser shortcuts often conflict. +*/ +html[data-runtime-host="web"] .keyboard-hints, +html[data-runtime-host="web"] .kbd-hint, +html[data-runtime-platform="mobile"] .keyboard-hints, +html[data-runtime-platform="mobile"] .kbd-hint, +html[data-keyboard-hints="hide"] .keyboard-hints, +html[data-keyboard-hints="hide"] .kbd-hint { + display: none !important; +} + /* Truncate from the start (keeps end visible; good for paths) */ .truncate-start { overflow: hidden;