chore: uptick
This commit is contained in:
@@ -1,29 +0,0 @@
|
|||||||
import { Box, useStdout } from 'ink'
|
|
||||||
import { type PropsWithChildren, useEffect } from 'react'
|
|
||||||
|
|
||||||
const ENTER = '\x1b[?1049h\x1b[2J\x1b[H'
|
|
||||||
const LEAVE = '\x1b[?1049l'
|
|
||||||
|
|
||||||
export function AltScreen({ children }: PropsWithChildren) {
|
|
||||||
const { stdout } = useStdout()
|
|
||||||
const rows = stdout?.rows ?? 24
|
|
||||||
const cols = stdout?.columns ?? 80
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
process.stdout.write(ENTER)
|
|
||||||
|
|
||||||
const leave = () => process.stdout.write(LEAVE)
|
|
||||||
process.on('exit', leave)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
leave()
|
|
||||||
process.off('exit', leave)
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box flexDirection="column" height={rows} overflow="hidden" width={cols}>
|
|
||||||
{children}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,9 @@ import { TextInput } from './components/textInput.js'
|
|||||||
import { Thinking } from './components/thinking.js'
|
import { Thinking } from './components/thinking.js'
|
||||||
import { HOTKEYS, INTERPOLATION_RE, PLACEHOLDERS, TOOL_VERBS, ZERO } from './constants.js'
|
import { HOTKEYS, INTERPOLATION_RE, PLACEHOLDERS, TOOL_VERBS, ZERO } from './constants.js'
|
||||||
import { type GatewayClient, type GatewayEvent } from './gatewayClient.js'
|
import { type GatewayClient, type GatewayEvent } from './gatewayClient.js'
|
||||||
import * as inputHistory from './lib/history.js'
|
import { useCompletion } from './hooks/useCompletion.js'
|
||||||
|
import { useInputHistory } from './hooks/useInputHistory.js'
|
||||||
|
import { useQueue } from './hooks/useQueue.js'
|
||||||
import { writeOsc52Clipboard } from './lib/osc52.js'
|
import { writeOsc52Clipboard } from './lib/osc52.js'
|
||||||
import { fmtK, hasInterpolation, pick } from './lib/text.js'
|
import { fmtK, hasInterpolation, pick } from './lib/text.js'
|
||||||
import { DEFAULT_THEME, fromSkin, type Theme } from './theme.js'
|
import { DEFAULT_THEME, fromSkin, type Theme } from './theme.js'
|
||||||
@@ -42,8 +44,6 @@ const introMsg = (info: SessionInfo): Msg => ({
|
|||||||
info
|
info
|
||||||
})
|
})
|
||||||
|
|
||||||
const TAB_PATH_RE = /((?:\.\.?\/|~\/|\/|@)[^\s]*)$/
|
|
||||||
|
|
||||||
function StatusRule({
|
function StatusRule({
|
||||||
cols,
|
cols,
|
||||||
color,
|
color,
|
||||||
@@ -113,60 +113,24 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||||||
const [thinkingText, setThinkingText] = useState('')
|
const [thinkingText, setThinkingText] = useState('')
|
||||||
const [statusBar, setStatusBar] = useState(true)
|
const [statusBar, setStatusBar] = useState(true)
|
||||||
const [lastUserMsg, setLastUserMsg] = useState('')
|
const [lastUserMsg, setLastUserMsg] = useState('')
|
||||||
const [queueEditIdx, setQueueEditIdx] = useState<number | null>(null)
|
|
||||||
const [historyIdx, setHistoryIdx] = useState<number | null>(null)
|
|
||||||
const [streaming, setStreaming] = useState('')
|
const [streaming, setStreaming] = useState('')
|
||||||
const [queuedDisplay, setQueuedDisplay] = useState<string[]>([])
|
|
||||||
const [catalog, setCatalog] = useState<SlashCatalog | null>(null)
|
const [catalog, setCatalog] = useState<SlashCatalog | null>(null)
|
||||||
|
|
||||||
const buf = useRef('')
|
const buf = useRef('')
|
||||||
const interruptedRef = useRef(false)
|
const interruptedRef = useRef(false)
|
||||||
const queueRef = useRef<string[]>([])
|
|
||||||
const historyRef = useRef<string[]>(inputHistory.load())
|
|
||||||
const historyDraftRef = useRef('')
|
|
||||||
const queueEditRef = useRef<number | null>(null)
|
|
||||||
const lastEmptyAt = useRef(0)
|
const lastEmptyAt = useRef(0)
|
||||||
const lastStatusNoteRef = useRef('')
|
const lastStatusNoteRef = useRef('')
|
||||||
const protocolWarnedRef = useRef(false)
|
const protocolWarnedRef = useRef(false)
|
||||||
const pasteCounterRef = useRef(0)
|
const pasteCounterRef = useRef(0)
|
||||||
|
|
||||||
|
const { queueRef, queueEditRef, queuedDisplay, queueEditIdx, enqueue, dequeue, replaceQ, setQueueEdit, syncQueue } =
|
||||||
|
useQueue()
|
||||||
|
|
||||||
|
const { historyRef, historyIdx, setHistoryIdx, historyDraftRef, pushHistory } = useInputHistory()
|
||||||
|
|
||||||
const empty = !messages.length
|
const empty = !messages.length
|
||||||
const blocked = !!(clarify || approval || sudo || secret || picker)
|
const blocked = !!(clarify || approval || sudo || secret || picker)
|
||||||
|
|
||||||
const syncQueue = () => setQueuedDisplay([...queueRef.current])
|
|
||||||
|
|
||||||
const setQueueEdit = (idx: number | null) => {
|
|
||||||
queueEditRef.current = idx
|
|
||||||
setQueueEditIdx(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
const enqueue = (text: string) => {
|
|
||||||
queueRef.current.push(text)
|
|
||||||
syncQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
const dequeue = () => {
|
|
||||||
const [head, ...rest] = queueRef.current
|
|
||||||
queueRef.current = rest
|
|
||||||
syncQueue()
|
|
||||||
|
|
||||||
return head
|
|
||||||
}
|
|
||||||
|
|
||||||
const replaceQ = (i: number, text: string) => {
|
|
||||||
queueRef.current[i] = text
|
|
||||||
syncQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
const pushHistory = (text: string) => {
|
|
||||||
const trimmed = text.trim()
|
|
||||||
|
|
||||||
if (trimmed && historyRef.current.at(-1) !== trimmed) {
|
|
||||||
historyRef.current.push(trimmed)
|
|
||||||
inputHistory.append(trimmed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!sid || !stdout) {
|
if (!sid || !stdout) {
|
||||||
return
|
return
|
||||||
@@ -180,63 +144,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
|||||||
}
|
}
|
||||||
}, [sid, stdout]) // eslint-disable-line react-hooks/exhaustive-deps
|
}, [sid, stdout]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
const [completions, setCompletions] = useState<{ text: string; display: string; meta: string }[]>([])
|
const { completions, compIdx, setCompIdx, compReplace } = useCompletion(input, blocked, gw)
|
||||||
const [compIdx, setCompIdx] = useState(0)
|
|
||||||
const [compReplace, setCompReplace] = useState(0)
|
|
||||||
const compInputRef = useRef('')
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (blocked) {
|
|
||||||
if (completions.length) {
|
|
||||||
setCompletions([])
|
|
||||||
setCompIdx(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input === compInputRef.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
compInputRef.current = input
|
|
||||||
|
|
||||||
const isSlash = input.startsWith('/')
|
|
||||||
const pathWord = !isSlash ? (input.match(TAB_PATH_RE)?.[1] ?? null) : null
|
|
||||||
|
|
||||||
if (!isSlash && !pathWord) {
|
|
||||||
if (completions.length) {
|
|
||||||
setCompletions([])
|
|
||||||
setCompIdx(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const t = setTimeout(() => {
|
|
||||||
if (compInputRef.current !== input) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const req = isSlash
|
|
||||||
? gw.request('complete.slash', { text: input })
|
|
||||||
: gw.request('complete.path', { word: pathWord })
|
|
||||||
|
|
||||||
req
|
|
||||||
.then((r: any) => {
|
|
||||||
if (compInputRef.current !== input) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setCompletions(r?.items ?? [])
|
|
||||||
setCompIdx(0)
|
|
||||||
setCompReplace(isSlash ? (r?.replace_from ?? 1) : input.length - (pathWord?.length ?? 0))
|
|
||||||
})
|
|
||||||
.catch(() => {})
|
|
||||||
}, 60)
|
|
||||||
|
|
||||||
return () => clearTimeout(t)
|
|
||||||
}, [input, blocked, gw]) // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
const appendMessage = useCallback((msg: Msg) => {
|
const appendMessage = useCallback((msg: Msg) => {
|
||||||
setMessages(prev => [...prev, msg])
|
setMessages(prev => [...prev, msg])
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Box, Text } from 'ink'
|
|
||||||
|
|
||||||
import type { Theme } from '../theme.js'
|
|
||||||
|
|
||||||
export function CommandPalette({ matches, t }: { matches: [string, string][]; t: Theme }) {
|
|
||||||
if (!matches.length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Box borderColor={t.color.bronze} borderStyle="single" flexDirection="column" paddingX={1}>
|
|
||||||
{matches.map(([cmd, desc], i) => (
|
|
||||||
<Text key={`${i}-${cmd}`}>
|
|
||||||
<Text bold color={t.color.amber}>
|
|
||||||
{cmd}
|
|
||||||
</Text>
|
|
||||||
{desc ? <Text color={t.color.dim}> — {desc}</Text> : null}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -116,6 +116,7 @@ export function TextInput({ value, onChange, onSubmit, onLargePaste, placeholder
|
|||||||
|
|
||||||
let c = cur,
|
let c = cur,
|
||||||
v = value
|
v = value
|
||||||
|
|
||||||
const mod = k.ctrl || k.meta
|
const mod = k.ctrl || k.meta
|
||||||
|
|
||||||
if (k.home || (k.ctrl && inp === 'a')) {
|
if (k.home || (k.ctrl && inp === 'a')) {
|
||||||
@@ -161,11 +162,13 @@ export function TextInput({ value, onChange, onSubmit, onLargePaste, placeholder
|
|||||||
if (!pasteBuf.current) {
|
if (!pasteBuf.current) {
|
||||||
pastePos.current = c
|
pastePos.current = c
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteBuf.current += raw
|
pasteBuf.current += raw
|
||||||
|
|
||||||
if (pasteTimer.current) {
|
if (pasteTimer.current) {
|
||||||
clearTimeout(pasteTimer.current)
|
clearTimeout(pasteTimer.current)
|
||||||
}
|
}
|
||||||
|
|
||||||
pasteTimer.current = setTimeout(flushPaste, 50)
|
pasteTimer.current = setTimeout(flushPaste, 50)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
67
ui-tui/src/hooks/useCompletion.ts
Normal file
67
ui-tui/src/hooks/useCompletion.ts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import type { GatewayClient } from '../gatewayClient.js'
|
||||||
|
|
||||||
|
const TAB_PATH_RE = /((?:\.\.?\/|~\/|\/|@)[^\s]*)$/
|
||||||
|
|
||||||
|
export function useCompletion(input: string, blocked: boolean, gw: GatewayClient) {
|
||||||
|
const [completions, setCompletions] = useState<{ text: string; display: string; meta: string }[]>([])
|
||||||
|
const [compIdx, setCompIdx] = useState(0)
|
||||||
|
const [compReplace, setCompReplace] = useState(0)
|
||||||
|
const ref = useRef('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (blocked) {
|
||||||
|
if (completions.length) {
|
||||||
|
setCompletions([])
|
||||||
|
setCompIdx(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input === ref.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.current = input
|
||||||
|
|
||||||
|
const isSlash = input.startsWith('/')
|
||||||
|
const pathWord = !isSlash ? (input.match(TAB_PATH_RE)?.[1] ?? null) : null
|
||||||
|
|
||||||
|
if (!isSlash && !pathWord) {
|
||||||
|
if (completions.length) {
|
||||||
|
setCompletions([])
|
||||||
|
setCompIdx(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = setTimeout(() => {
|
||||||
|
if (ref.current !== input) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const req = isSlash
|
||||||
|
? gw.request('complete.slash', { text: input })
|
||||||
|
: gw.request('complete.path', { word: pathWord })
|
||||||
|
|
||||||
|
req
|
||||||
|
.then((r: any) => {
|
||||||
|
if (ref.current !== input) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCompletions(r?.items ?? [])
|
||||||
|
setCompIdx(0)
|
||||||
|
setCompReplace(isSlash ? (r?.replace_from ?? 1) : input.length - (pathWord?.length ?? 0))
|
||||||
|
})
|
||||||
|
.catch(() => {})
|
||||||
|
}, 60)
|
||||||
|
|
||||||
|
return () => clearTimeout(t)
|
||||||
|
}, [input, blocked, gw]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return { completions, compIdx, setCompIdx, compReplace }
|
||||||
|
}
|
||||||
20
ui-tui/src/hooks/useInputHistory.ts
Normal file
20
ui-tui/src/hooks/useInputHistory.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
|
||||||
|
import * as inputHistory from '../lib/history.js'
|
||||||
|
|
||||||
|
export function useInputHistory() {
|
||||||
|
const historyRef = useRef<string[]>(inputHistory.load())
|
||||||
|
const [historyIdx, setHistoryIdx] = useState<number | null>(null)
|
||||||
|
const historyDraftRef = useRef('')
|
||||||
|
|
||||||
|
const pushHistory = (text: string) => {
|
||||||
|
const trimmed = text.trim()
|
||||||
|
|
||||||
|
if (trimmed && historyRef.current.at(-1) !== trimmed) {
|
||||||
|
historyRef.current.push(trimmed)
|
||||||
|
inputHistory.append(trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { historyRef, historyIdx, setHistoryIdx, historyDraftRef, pushHistory }
|
||||||
|
}
|
||||||
35
ui-tui/src/hooks/useQueue.ts
Normal file
35
ui-tui/src/hooks/useQueue.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { useRef, useState } from 'react'
|
||||||
|
|
||||||
|
export function useQueue() {
|
||||||
|
const queueRef = useRef<string[]>([])
|
||||||
|
const [queuedDisplay, setQueuedDisplay] = useState<string[]>([])
|
||||||
|
const queueEditRef = useRef<number | null>(null)
|
||||||
|
const [queueEditIdx, setQueueEditIdx] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const syncQueue = () => setQueuedDisplay([...queueRef.current])
|
||||||
|
|
||||||
|
const setQueueEdit = (idx: number | null) => {
|
||||||
|
queueEditRef.current = idx
|
||||||
|
setQueueEditIdx(idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
const enqueue = (text: string) => {
|
||||||
|
queueRef.current.push(text)
|
||||||
|
syncQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
const dequeue = () => {
|
||||||
|
const [head, ...rest] = queueRef.current
|
||||||
|
queueRef.current = rest
|
||||||
|
syncQueue()
|
||||||
|
|
||||||
|
return head
|
||||||
|
}
|
||||||
|
|
||||||
|
const replaceQ = (i: number, text: string) => {
|
||||||
|
queueRef.current[i] = text
|
||||||
|
syncQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
return { queueRef, queueEditRef, queuedDisplay, queueEditIdx, enqueue, dequeue, replaceQ, setQueueEdit, syncQueue }
|
||||||
|
}
|
||||||
@@ -70,10 +70,12 @@ export function append(line: string): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ts = new Date().toISOString().replace('T', ' ').replace('Z', '')
|
const ts = new Date().toISOString().replace('T', ' ').replace('Z', '')
|
||||||
|
|
||||||
const encoded = trimmed
|
const encoded = trimmed
|
||||||
.split('\n')
|
.split('\n')
|
||||||
.map(l => '+' + l)
|
.map(l => '+' + l)
|
||||||
.join('\n')
|
.join('\n')
|
||||||
|
|
||||||
appendFileSync(file, `\n# ${ts}\n${encoded}\n`)
|
appendFileSync(file, `\n# ${ts}\n${encoded}\n`)
|
||||||
} catch {
|
} catch {
|
||||||
/* ignore */
|
/* ignore */
|
||||||
|
|||||||
@@ -1,124 +0,0 @@
|
|||||||
import type { SlashCatalog } from '../types.js'
|
|
||||||
|
|
||||||
/** Match SlashCommandCompleter: command names, subcommands, then skills. */
|
|
||||||
export function paletteForLine(line: string, c: SlashCatalog | null): [string, string][] {
|
|
||||||
if (!c || !line.startsWith('/')) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = line.split(/\s+/)
|
|
||||||
const baseRaw = parts[0]!
|
|
||||||
const base = baseRaw.toLowerCase()
|
|
||||||
const inSub = parts.length > 1 || (parts.length === 1 && line.endsWith(' '))
|
|
||||||
|
|
||||||
if (inSub) {
|
|
||||||
const subText = parts.length > 1 ? parts.slice(1).join(' ') : ''
|
|
||||||
|
|
||||||
if (subText.includes(' ') || parts.length > 2) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const head = subText.split(/\s+/)[0] ?? ''
|
|
||||||
|
|
||||||
if (subText.includes(' ') && head !== subText) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const canonical = c.canon[base] ?? baseRaw
|
|
||||||
const subs = c.sub[canonical]
|
|
||||||
|
|
||||||
if (!subs?.length) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const lo = head.toLowerCase()
|
|
||||||
|
|
||||||
return subs
|
|
||||||
.filter(s => s.toLowerCase().startsWith(lo) && s.toLowerCase() !== lo)
|
|
||||||
.slice(0, 14)
|
|
||||||
.map(s => [s, ''])
|
|
||||||
}
|
|
||||||
|
|
||||||
const word = line.slice(1)
|
|
||||||
|
|
||||||
return c.pairs
|
|
||||||
.filter(([k]) => k.slice(1).startsWith(word))
|
|
||||||
.slice(0, 16)
|
|
||||||
.map(([k, d]) => [k, d])
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Tab: longest common prefix of palette matches, or first unique completion + space. */
|
|
||||||
export function tabAdvance(line: string, c: SlashCatalog | null): string | null {
|
|
||||||
if (!c || !line.startsWith('/')) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const rows = paletteForLine(line, c)
|
|
||||||
|
|
||||||
if (!rows.length) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const parts = line.split(/\s+/)
|
|
||||||
const baseRaw = parts[0]!
|
|
||||||
const base = baseRaw.toLowerCase()
|
|
||||||
const inSub = parts.length > 1 || (parts.length === 1 && line.endsWith(' '))
|
|
||||||
|
|
||||||
if (inSub) {
|
|
||||||
const subText = parts.length > 1 ? parts.slice(1).join(' ') : ''
|
|
||||||
const head = subText.split(/\s+/)[0] ?? ''
|
|
||||||
const picks = rows.map(([s]) => s)
|
|
||||||
|
|
||||||
if (picks.length === 1) {
|
|
||||||
return `${baseRaw} ${picks[0]!} `
|
|
||||||
}
|
|
||||||
|
|
||||||
const cp = commonPrefix(picks)
|
|
||||||
|
|
||||||
if (cp.length > head.length) {
|
|
||||||
return `${baseRaw} ${cp}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const word = line.slice(1)
|
|
||||||
const names = rows.map(([k]) => k.slice(1))
|
|
||||||
const cp = commonPrefix(names)
|
|
||||||
|
|
||||||
if (names.length === 1) {
|
|
||||||
return `/${names[0]!} `
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cp.length > word.length) {
|
|
||||||
return `/${cp}`
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
function commonPrefix(xs: string[]): string {
|
|
||||||
if (!xs.length) {
|
|
||||||
return ''
|
|
||||||
}
|
|
||||||
|
|
||||||
let n = 0
|
|
||||||
|
|
||||||
outer: while (true) {
|
|
||||||
const ch = xs[0]![n]
|
|
||||||
|
|
||||||
if (ch === undefined) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const x of xs) {
|
|
||||||
if (x[n] !== ch) {
|
|
||||||
break outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
n++
|
|
||||||
}
|
|
||||||
|
|
||||||
return xs[0]!.slice(0, n)
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import { render } from 'ink'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
import { App } from './app.js'
|
|
||||||
import { GatewayClient } from './gatewayClient.js'
|
|
||||||
|
|
||||||
if (!process.stdin.isTTY) {
|
|
||||||
console.log('hermes-tui: no TTY')
|
|
||||||
process.exit(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
const gw = new GatewayClient()
|
|
||||||
gw.start()
|
|
||||||
render(<App gw={gw} />, { exitOnCtrlC: false })
|
|
||||||
Reference in New Issue
Block a user