Compare commits

...

5 Commits

Author SHA1 Message Date
Shantur Rathore
ec6428702b Bump version to 0.7.3 2026-01-20 18:49:18 +00:00
Shantur Rathore
e08ebb2057 fix(server): honor --host binding
Fixes #75
2026-01-20 18:47:40 +00:00
Shantur Rathore
9683f90f7e fix(ui): insert full paths for @file mentions 2026-01-20 18:47:40 +00:00
Shantur Rathore
06cb986aa6 fix(ui): allow Tab to select from picker
Fixes #77
2026-01-20 18:47:40 +00:00
Shantur Rathore
a85c2f1700 fix(ui): collapse prompt input after send
Fixes #76
2026-01-20 18:47:40 +00:00
12 changed files with 53 additions and 27 deletions

12
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "codenomad-workspace", "name": "codenomad-workspace",
"version": "0.7.2", "version": "0.7.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "codenomad-workspace", "name": "codenomad-workspace",
"version": "0.7.2", "version": "0.7.3",
"dependencies": { "dependencies": {
"7zip-bin": "^5.2.0", "7zip-bin": "^5.2.0",
"google-auth-library": "^10.5.0" "google-auth-library": "^10.5.0"
@@ -7389,7 +7389,7 @@
}, },
"packages/electron-app": { "packages/electron-app": {
"name": "@neuralnomads/codenomad-electron-app", "name": "@neuralnomads/codenomad-electron-app",
"version": "0.7.2", "version": "0.7.3",
"dependencies": { "dependencies": {
"@codenomad/ui": "file:../ui", "@codenomad/ui": "file:../ui",
"@neuralnomads/codenomad": "file:../server" "@neuralnomads/codenomad": "file:../server"
@@ -7423,7 +7423,7 @@
}, },
"packages/server": { "packages/server": {
"name": "@neuralnomads/codenomad", "name": "@neuralnomads/codenomad",
"version": "0.7.2", "version": "0.7.3",
"dependencies": { "dependencies": {
"@fastify/cors": "^8.5.0", "@fastify/cors": "^8.5.0",
"@fastify/reply-from": "^9.8.0", "@fastify/reply-from": "^9.8.0",
@@ -7458,14 +7458,14 @@
}, },
"packages/tauri-app": { "packages/tauri-app": {
"name": "@codenomad/tauri-app", "name": "@codenomad/tauri-app",
"version": "0.7.2", "version": "0.7.3",
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^2.9.4" "@tauri-apps/cli": "^2.9.4"
} }
}, },
"packages/ui": { "packages/ui": {
"name": "@codenomad/ui", "name": "@codenomad/ui",
"version": "0.7.2", "version": "0.7.3",
"dependencies": { "dependencies": {
"@git-diff-view/solid": "^0.0.8", "@git-diff-view/solid": "^0.0.8",
"@kobalte/core": "0.13.11", "@kobalte/core": "0.13.11",

View File

@@ -1,6 +1,6 @@
{ {
"name": "codenomad-workspace", "name": "codenomad-workspace",
"version": "0.7.2", "version": "0.7.3",
"private": true, "private": true,
"description": "CodeNomad monorepo workspace", "description": "CodeNomad monorepo workspace",
"workspaces": { "workspaces": {

View File

@@ -1,6 +1,6 @@
{ {
"name": "@neuralnomads/codenomad-electron-app", "name": "@neuralnomads/codenomad-electron-app",
"version": "0.7.2", "version": "0.7.3",
"description": "CodeNomad - AI coding assistant", "description": "CodeNomad - AI coding assistant",
"author": { "author": {
"name": "Neural Nomads", "name": "Neural Nomads",

View File

@@ -1,12 +1,12 @@
{ {
"name": "@neuralnomads/codenomad", "name": "@neuralnomads/codenomad",
"version": "0.7.2", "version": "0.7.3",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@neuralnomads/codenomad", "name": "@neuralnomads/codenomad",
"version": "0.7.2", "version": "0.7.3",
"dependencies": { "dependencies": {
"@fastify/cors": "^8.5.0", "@fastify/cors": "^8.5.0",
"commander": "^12.1.0", "commander": "^12.1.0",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@neuralnomads/codenomad", "name": "@neuralnomads/codenomad",
"version": "0.7.2", "version": "0.7.3",
"description": "CodeNomad Server", "description": "CodeNomad Server",
"author": { "author": {
"name": "Neural Nomads", "name": "Neural Nomads",

View File

@@ -127,10 +127,18 @@ function parsePort(input: string): number {
} }
function resolveHost(input: string | undefined): string { function resolveHost(input: string | undefined): string {
if (input && input.trim() === "0.0.0.0") { const trimmed = input?.trim()
if (!trimmed) return DEFAULT_HOST
if (trimmed === "0.0.0.0") {
return "0.0.0.0" return "0.0.0.0"
} }
return DEFAULT_HOST
if (trimmed === "localhost") {
return DEFAULT_HOST
}
return trimmed
} }
async function main() { async function main() {
@@ -149,11 +157,13 @@ async function main() {
const eventBus = new EventBus(eventLogger) const eventBus = new EventBus(eventLogger)
const isLoopbackHost = (host: string) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.")
const serverMeta: ServerMeta = { const serverMeta: ServerMeta = {
httpBaseUrl: `http://${options.host}:${options.port}`, httpBaseUrl: `http://${options.host}:${options.port}`,
eventsUrl: `/api/events`, eventsUrl: `/api/events`,
host: options.host, host: options.host,
listeningMode: options.host === "0.0.0.0" ? "all" : "local", listeningMode: isLoopbackHost(options.host) ? "local" : "all",
port: options.port, port: options.port,
hostLabel: options.host, hostLabel: options.host,
workspaceRoot: options.rootDir, workspaceRoot: options.rootDir,

View File

@@ -93,6 +93,7 @@ export function createHttpServer(deps: HttpServerDeps) {
}) })
const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"]) const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"])
const isLoopbackHost = (host: string) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.")
app.register(cors, { app.register(cors, {
origin: (origin, cb) => { origin: (origin, cb) => {
@@ -113,10 +114,17 @@ export function createHttpServer(deps: HttpServerDeps) {
return return
} }
if (allowedDevOrigins.has(origin)) { if (allowedDevOrigins.has(origin)) {
cb(null, true) cb(null, true)
return return
} }
// When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access.
if (deps.host === "0.0.0.0" || !isLoopbackHost(deps.host)) {
cb(null, true)
return
}
cb(null, false) cb(null, false)
}, },
@@ -275,13 +283,13 @@ export function createHttpServer(deps: HttpServerDeps) {
} }
} }
const displayHost = deps.host === "0.0.0.0" ? "127.0.0.1" : deps.host === "127.0.0.1" ? "localhost" : deps.host const displayHost = deps.host === "127.0.0.1" ? "localhost" : deps.host
const serverUrl = `http://${displayHost}:${actualPort}` const serverUrl = `http://${displayHost}:${actualPort}`
deps.serverMeta.httpBaseUrl = serverUrl deps.serverMeta.httpBaseUrl = serverUrl
deps.serverMeta.host = deps.host deps.serverMeta.host = deps.host
deps.serverMeta.port = actualPort deps.serverMeta.port = actualPort
deps.serverMeta.listeningMode = deps.host === "0.0.0.0" ? "all" : "local" deps.serverMeta.listeningMode = deps.host === "0.0.0.0" || !isLoopbackHost(deps.host) ? "all" : "local"
deps.logger.info({ port: actualPort, host: deps.host }, "HTTP server listening") deps.logger.info({ port: actualPort, host: deps.host }, "HTTP server listening")
console.log(`CodeNomad Server is ready at ${serverUrl}`) console.log(`CodeNomad Server is ready at ${serverUrl}`)

View File

@@ -17,7 +17,7 @@ function buildMetaResponse(meta: ServerMeta): ServerMeta {
return { return {
...meta, ...meta,
port, port,
listeningMode: meta.host === "0.0.0.0" ? "all" : "local", listeningMode: meta.host === "0.0.0.0" || !isLoopbackHost(meta.host) ? "all" : "local",
addresses, addresses,
} }
} }
@@ -35,6 +35,10 @@ function resolvePort(meta: ServerMeta): number {
} }
} }
function isLoopbackHost(host: string): boolean {
return host === "127.0.0.1" || host === "::1" || host.startsWith("127.")
}
function resolveAddresses(port: number, host: string): NetworkAddress[] { function resolveAddresses(port: number, host: string): NetworkAddress[] {
const interfaces = os.networkInterfaces() const interfaces = os.networkInterfaces()
const seen = new Set<string>() const seen = new Set<string>()

View File

@@ -1,6 +1,6 @@
{ {
"name": "@codenomad/tauri-app", "name": "@codenomad/tauri-app",
"version": "0.7.2", "version": "0.7.3",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "tauri dev", "dev": "tauri dev",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@codenomad/ui", "name": "@codenomad/ui",
"version": "0.7.2", "version": "0.7.3",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {

View File

@@ -604,6 +604,7 @@ export default function PromptInput(props: PromptInputProps) {
} }
} }
setExpandState("normal")
clearPrompt() clearPrompt()
// Ignore attachments for slash commands, but keep them for next prompt. // Ignore attachments for slash commands, but keep them for next prompt.
@@ -843,7 +844,10 @@ export default function PromptInput(props: PromptInputProps) {
const currentPrompt = prompt() const currentPrompt = prompt()
const pos = atPosition() const pos = atPosition()
const cursorPos = textareaRef?.selectionStart || 0 const cursorPos = textareaRef?.selectionStart || 0
const folderMention = relativePath === "." || relativePath === "" ? "/" : displayPath const folderMention =
relativePath === "." || relativePath === ""
? "/"
: relativePath.replace(/\/+$/, "") + "/"
if (pos !== null) { if (pos !== null) {
const before = currentPrompt.substring(0, pos + 1) const before = currentPrompt.substring(0, pos + 1)
@@ -887,7 +891,7 @@ export default function PromptInput(props: PromptInputProps) {
if (pos !== null) { if (pos !== null) {
const before = currentPrompt.substring(0, pos) const before = currentPrompt.substring(0, pos)
const after = currentPrompt.substring(cursorPos) const after = currentPrompt.substring(cursorPos)
const attachmentText = `@${filename}` const attachmentText = `@${normalizedPath}`
const newPrompt = before + attachmentText + " " + after const newPrompt = before + attachmentText + " " + after
setPrompt(newPrompt) setPrompt(newPrompt)

View File

@@ -339,7 +339,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
e.preventDefault() e.preventDefault()
setSelectedIndex((prev) => Math.max(prev - 1, 0)) setSelectedIndex((prev) => Math.max(prev - 1, 0))
scrollToSelected() scrollToSelected()
} else if (e.key === "Enter") { } else if (e.key === "Enter" || e.key === "Tab") {
e.preventDefault() e.preventDefault()
const selected = items[selectedIndex()] const selected = items[selectedIndex()]
if (selected) { if (selected) {
@@ -534,7 +534,7 @@ const UnifiedPicker: Component<UnifiedPickerProps> = (props) => {
<div class="dropdown-footer"> <div class="dropdown-footer">
<div> <div>
<span class="font-medium"></span> navigate <span class="font-medium">Enter</span> select {" "} <span class="font-medium"></span> navigate <span class="font-medium">Tab/Enter</span> select {" "}
<span class="font-medium">Esc</span> close <span class="font-medium">Esc</span> close
</div> </div>
</div> </div>