TOES_URL and TOES_HOST support

This commit is contained in:
Chris Wanstrath 2026-01-30 16:51:36 -08:00
parent bde7a2c287
commit 1da7e77f00
6 changed files with 86 additions and 23 deletions

View File

@ -17,6 +17,19 @@ Plug it in, turn it on, and forget about the cloud.
- https://toes.local web UI for managing your projects.
- Per-branch staging environments for Claude.
## cli configuration
by default, the CLI connects to `localhost:3000` in dev and `toes.local:80` in production.
```bash
toes config # show current host
TOES_URL=http://192.168.1.50:3000 toes list # full URL
TOES_HOST=mypi.local toes list # hostname (port 80)
TOES_HOST=mypi.local PORT=3000 toes list # hostname + port
```
set `NODE_ENV=production` to default to `toes.local:80`.
## fun stuff
- textOS (TODO, more?)

View File

@ -1,5 +1,6 @@
export { logApp } from './logs'
export {
configShow,
infoApp,
listApps,
newApp,

View File

@ -1,5 +1,5 @@
import type { LogLine } from '@types'
import { get, makeUrl } from '../http'
import { get, handleError, makeUrl } from '../http'
import { resolveAppName } from '../name'
export const printLog = (line: LogLine) =>
@ -29,31 +29,35 @@ export async function logApp(arg: string | undefined, options: { follow?: boolea
}
export async function tailLogs(name: string) {
const url = makeUrl(`/api/apps/${name}/logs/stream`)
const res = await fetch(url)
if (!res.ok) {
console.error(`App not found: ${name}`)
return
}
if (!res.body) return
try {
const url = makeUrl(`/api/apps/${name}/logs/stream`)
const res = await fetch(url)
if (!res.ok) {
console.error(`App not found: ${name}`)
return
}
if (!res.body) return
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
while (true) {
const { done, value } = await reader.read()
if (done) break
while (true) {
const { done, value } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n\n')
buffer = lines.pop() ?? ''
buffer += decoder.decode(value, { stream: true })
const lines = buffer.split('\n\n')
buffer = lines.pop() ?? ''
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6)) as LogLine
printLog(data)
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6)) as LogLine
printLog(data)
}
}
}
} catch (error) {
handleError(error)
}
}

View File

@ -3,7 +3,7 @@ import { generateTemplates, type TemplateType } from '%templates'
import color from 'kleur'
import { existsSync, mkdirSync, writeFileSync } from 'fs'
import { basename, join } from 'path'
import { del, get, getManifest, post } from '../http'
import { del, get, getManifest, HOST, post } from '../http'
import { confirm, prompt } from '../prompts'
import { resolveAppName } from '../name'
import { pushApp } from './sync'
@ -15,6 +15,33 @@ export const STATE_ICONS: Record<string, string> = {
invalid: color.red('◌'),
}
export async function configShow() {
console.log(`Host: ${color.bold(HOST)}`)
const source = process.env.TOES_URL
? 'TOES_URL'
: process.env.TOES_HOST
? 'TOES_HOST' + (process.env.PORT ? ' + PORT' : '')
: process.env.NODE_ENV === 'production'
? 'default (production)'
: 'default (development)'
console.log(`Source: ${color.gray(source)}`)
if (process.env.TOES_URL) {
console.log(` TOES_URL=${process.env.TOES_URL}`)
}
if (process.env.TOES_HOST) {
console.log(` TOES_HOST=${process.env.TOES_HOST}`)
}
if (process.env.PORT) {
console.log(` PORT=${process.env.PORT}`)
}
if (process.env.NODE_ENV) {
console.log(` NODE_ENV=${process.env.NODE_ENV}`)
}
}
export async function infoApp(arg?: string) {
const name = resolveAppName(arg)
if (!name) return

View File

@ -1,6 +1,17 @@
import type { Manifest } from '@types'
export const HOST = `http://localhost:${process.env.PORT ?? 3000}`
function getDefaultHost(): string {
if (process.env.NODE_ENV === 'production') {
return `http://toes.local:${process.env.PORT ?? 80}`
}
return `http://localhost:${process.env.PORT ?? 3000}`
}
const defaultPort = process.env.NODE_ENV === 'production' ? 80 : 3000
export const HOST = process.env.TOES_URL
?? (process.env.TOES_HOST ? `http://${process.env.TOES_HOST}:${process.env.PORT ?? defaultPort}` : undefined)
?? getDefaultHost()
export function makeUrl(path: string): string {
return `${HOST}${path}`
@ -9,6 +20,7 @@ export function makeUrl(path: string): string {
export function handleError(error: unknown): void {
if (error instanceof Error && 'code' in error && error.code === 'ConnectionRefused') {
console.error(`🐾 Can't connect to toes server at ${HOST}`)
console.error(` Set TOES_URL or TOES_HOST to connect to a different host`)
return
}
console.error(error)

View File

@ -3,6 +3,7 @@ import { readFileSync } from 'fs'
import color from 'kleur'
import {
configShow,
diffApp,
getApp,
infoApp,
@ -45,6 +46,11 @@ program
.command('version', { hidden: true })
.action(() => console.log(program.version()))
program
.command('config')
.description('Show current host configuration')
.action(configShow)
program
.command('info')
.description('Show info for an app')