chore: fmt
This commit is contained in:
@@ -82,6 +82,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||
if (!stdout) {
|
||||
return
|
||||
}
|
||||
|
||||
const sync = () => setCols(stdout.columns ?? 80)
|
||||
stdout.on('resize', sync)
|
||||
|
||||
@@ -197,6 +198,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||
if (input === compInputRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
compInputRef.current = input
|
||||
|
||||
const isSlash = input.startsWith('/')
|
||||
@@ -225,6 +227,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||
if (compInputRef.current !== input) {
|
||||
return
|
||||
}
|
||||
|
||||
setCompletions(r?.items ?? [])
|
||||
setCompIdx(0)
|
||||
setCompReplace(isSlash ? (r?.replace_from ?? 1) : input.length - (pathWord?.length ?? 0))
|
||||
@@ -880,6 +883,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||
if (!sid) {
|
||||
return true
|
||||
}
|
||||
|
||||
rpc('session.undo', { session_id: sid }).then((r: any) => {
|
||||
if (r.removed > 0) {
|
||||
setMessages(prev => {
|
||||
@@ -913,6 +917,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||
if (sid) {
|
||||
gw.request('session.undo', { session_id: sid }).catch(() => {})
|
||||
}
|
||||
|
||||
setMessages(prev => {
|
||||
const q = [...prev]
|
||||
|
||||
@@ -1174,6 +1179,7 @@ export function App({ gw }: { gw: GatewayClient }) {
|
||||
if (r.info) {
|
||||
appendHistory(introMsg(r.info))
|
||||
}
|
||||
|
||||
setUsage(ZERO)
|
||||
lastStatusNoteRef.current = ''
|
||||
protocolWarnedRef.current = false
|
||||
|
||||
@@ -75,7 +75,11 @@ export function SessionPanel({ info, sid, t }: { info: SessionInfo; sid?: string
|
||||
<Text color={t.color.cornsilk}>{truncLine(strip(k) + ': ', vs)}</Text>
|
||||
</Text>
|
||||
))}
|
||||
{overflow > 0 && <Text color={t.color.dim}>(and {overflow} {overflowLabel})</Text>}
|
||||
{overflow > 0 && (
|
||||
<Text color={t.color.dim}>
|
||||
(and {overflow} {overflowLabel})
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -90,13 +94,17 @@ export function SessionPanel({ info, sid, t }: { info: SessionInfo; sid?: string
|
||||
{info.model.split('/').pop()}
|
||||
<Text color={t.color.dim}> · Nous Research</Text>
|
||||
</Text>
|
||||
<Text color={t.color.dim} wrap="truncate-end">{cwd}</Text>
|
||||
<Text color={t.color.dim} wrap="truncate-end">
|
||||
{cwd}
|
||||
</Text>
|
||||
{sid && <Text color={t.color.dim}>Session: {sid}</Text>}
|
||||
</Box>
|
||||
)}
|
||||
<Box flexDirection="column" width={w}>
|
||||
<Box justifyContent="center" marginBottom={1}>
|
||||
<Text bold color={t.color.gold}>{title}</Text>
|
||||
<Text bold color={t.color.gold}>
|
||||
{title}
|
||||
</Text>
|
||||
</Box>
|
||||
{section('Tools', info.tools, 8, 'more toolsets…')}
|
||||
{section('Skills', info.skills)}
|
||||
@@ -110,9 +118,17 @@ export function SessionPanel({ info, sid, t }: { info: SessionInfo; sid?: string
|
||||
{typeof info.update_behind === 'number' && info.update_behind > 0 && (
|
||||
<Text bold color="yellow">
|
||||
⚠ {info.update_behind} {info.update_behind === 1 ? 'commit' : 'commits'} behind
|
||||
<Text bold={false} color="yellow" dimColor> — run </Text>
|
||||
<Text bold color="yellow">{info.update_command || 'hermes update'}</Text>
|
||||
<Text bold={false} color="yellow" dimColor> to update</Text>
|
||||
<Text bold={false} color="yellow" dimColor>
|
||||
{' '}
|
||||
— run{' '}
|
||||
</Text>
|
||||
<Text bold color="yellow">
|
||||
{info.update_command || 'hermes update'}
|
||||
</Text>
|
||||
<Text bold={false} color="yellow" dimColor>
|
||||
{' '}
|
||||
to update
|
||||
</Text>
|
||||
</Text>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
@@ -23,7 +23,11 @@ function MdInline({ t, text }: { t: Theme; text: string }) {
|
||||
</Text>
|
||||
)
|
||||
} else if (m[4]) {
|
||||
parts.push(<Text bold key={parts.length}>{m[4]}</Text>)
|
||||
parts.push(
|
||||
<Text bold key={parts.length}>
|
||||
{m[4]}
|
||||
</Text>
|
||||
)
|
||||
} else if (m[5]) {
|
||||
parts.push(
|
||||
<Text color={t.color.amber} dimColor key={parts.length}>
|
||||
@@ -31,7 +35,11 @@ function MdInline({ t, text }: { t: Theme; text: string }) {
|
||||
</Text>
|
||||
)
|
||||
} else if (m[6]) {
|
||||
parts.push(<Text italic key={parts.length}>{m[6]}</Text>)
|
||||
parts.push(
|
||||
<Text italic key={parts.length}>
|
||||
{m[6]}
|
||||
</Text>
|
||||
)
|
||||
} else if (m[7]) {
|
||||
parts.push(
|
||||
<Text color={t.color.amber} key={parts.length} underline>
|
||||
@@ -58,7 +66,7 @@ export function Md({ compact, t, text }: { compact?: boolean; t: Theme; text: st
|
||||
|
||||
const gap = () => {
|
||||
if (nodes.length && prevKind !== 'blank') {
|
||||
nodes.push(<Text key={`gap-${nodes.length}`}>{' '}</Text>)
|
||||
nodes.push(<Text key={`gap-${nodes.length}`}> </Text>)
|
||||
prevKind = 'blank'
|
||||
}
|
||||
}
|
||||
@@ -67,6 +75,7 @@ export function Md({ compact, t, text }: { compact?: boolean; t: Theme; text: st
|
||||
if (prevKind && prevKind !== 'blank' && prevKind !== kind) {
|
||||
gap()
|
||||
}
|
||||
|
||||
prevKind = kind
|
||||
}
|
||||
|
||||
@@ -104,9 +113,7 @@ export function Md({ compact, t, text }: { compact?: boolean; t: Theme; text: st
|
||||
{lang && !isDiff && <Text color={t.color.dim}>{'─ ' + lang}</Text>}
|
||||
{block.map((l, j) => (
|
||||
<Text
|
||||
color={
|
||||
isDiff && l.startsWith('+') ? '#a6e3a1' : isDiff && l.startsWith('-') ? '#f38ba8' : undefined
|
||||
}
|
||||
color={isDiff && l.startsWith('+') ? '#a6e3a1' : isDiff && l.startsWith('-') ? '#f38ba8' : undefined}
|
||||
dimColor={isDiff && !l.startsWith('+') && !l.startsWith('-') && l.startsWith(' ')}
|
||||
key={j}
|
||||
>
|
||||
|
||||
@@ -5,9 +5,20 @@ import { LONG_MSG, ROLE } from '../constants.js'
|
||||
import { hasAnsi, userDisplay } from '../lib/text.js'
|
||||
import type { Theme } from '../theme.js'
|
||||
import type { Msg } from '../types.js'
|
||||
|
||||
import { Md } from './markdown.js'
|
||||
|
||||
export const MessageLine = memo(function MessageLine({ cols, compact, msg, t }: { cols: number; compact?: boolean; msg: Msg; t: Theme }) {
|
||||
export const MessageLine = memo(function MessageLine({
|
||||
cols,
|
||||
compact,
|
||||
msg,
|
||||
t
|
||||
}: {
|
||||
cols: number
|
||||
compact?: boolean
|
||||
msg: Msg
|
||||
t: Theme
|
||||
}) {
|
||||
const { body, glyph, prefix } = ROLE[msg.role](t)
|
||||
const contentWidth = Math.max(20, cols - 5)
|
||||
|
||||
@@ -20,8 +31,9 @@ export const MessageLine = memo(function MessageLine({ cols, compact, msg, t }:
|
||||
}
|
||||
|
||||
const content = (() => {
|
||||
if (msg.role === 'assistant')
|
||||
if (msg.role === 'assistant') {
|
||||
return hasAnsi(msg.text) ? <Text wrap="wrap">{msg.text}</Text> : <Md compact={compact} t={t} text={msg.text} />
|
||||
}
|
||||
|
||||
if (msg.role === 'user' && msg.text.length > LONG_MSG) {
|
||||
const [head, ...rest] = userDisplay(msg.text).split('[long message]')
|
||||
@@ -29,7 +41,9 @@ export const MessageLine = memo(function MessageLine({ cols, compact, msg, t }:
|
||||
return (
|
||||
<Text color={body}>
|
||||
{head}
|
||||
<Text color={t.color.dim} dimColor>[long message]</Text>
|
||||
<Text color={t.color.dim} dimColor>
|
||||
[long message]
|
||||
</Text>
|
||||
{rest.join('')}
|
||||
</Text>
|
||||
)
|
||||
@@ -40,16 +54,16 @@ export const MessageLine = memo(function MessageLine({ cols, compact, msg, t }:
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{(msg.role === 'user' || msg.role === 'assistant') && <Text>{' '}</Text>}
|
||||
{(msg.role === 'user' || msg.role === 'assistant') && <Text> </Text>}
|
||||
|
||||
<Box>
|
||||
<Box flexShrink={0} width={3}>
|
||||
<Text bold={msg.role === 'user'} color={prefix}>{glyph} </Text>
|
||||
<Text bold={msg.role === 'user'} color={prefix}>
|
||||
{glyph}{' '}
|
||||
</Text>
|
||||
</Box>
|
||||
|
||||
<Box width={contentWidth}>
|
||||
{content}
|
||||
</Box>
|
||||
<Box width={contentWidth}>{content}</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
|
||||
@@ -3,15 +3,29 @@ import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
function wl(s: string, p: number) {
|
||||
let i = p - 1
|
||||
while (i > 0 && /\s/.test(s[i]!)) i--
|
||||
while (i > 0 && !/\s/.test(s[i - 1]!)) i--
|
||||
|
||||
while (i > 0 && /\s/.test(s[i]!)) {
|
||||
i--
|
||||
}
|
||||
|
||||
while (i > 0 && !/\s/.test(s[i - 1]!)) {
|
||||
i--
|
||||
}
|
||||
|
||||
return Math.max(0, i)
|
||||
}
|
||||
|
||||
function wr(s: string, p: number) {
|
||||
let i = p
|
||||
while (i < s.length && !/\s/.test(s[i]!)) i++
|
||||
while (i < s.length && /\s/.test(s[i]!)) i++
|
||||
|
||||
while (i < s.length && !/\s/.test(s[i]!)) {
|
||||
i++
|
||||
}
|
||||
|
||||
while (i < s.length && /\s/.test(s[i]!)) {
|
||||
i++
|
||||
}
|
||||
|
||||
return i
|
||||
}
|
||||
|
||||
@@ -21,7 +35,7 @@ const INV_OFF = ESC + '[27m'
|
||||
const DIM = ESC + '[2m'
|
||||
const DIM_OFF = ESC + '[22m'
|
||||
const PRINTABLE = /^[ -~\u00a0-\uffff]+$/
|
||||
const BRACKET_PASTE = /\x1b\[20[01]~/g
|
||||
const BRACKET_PASTE = new RegExp(`${ESC}\\[20[01]~`, 'g')
|
||||
|
||||
interface Props {
|
||||
value: string
|
||||
@@ -42,7 +56,11 @@ export function TextInput({ value, onChange, onSubmit, onLargePaste, placeholder
|
||||
vRef.current = value
|
||||
|
||||
useEffect(() => {
|
||||
if (selfChange.current) { selfChange.current = false } else { setCur(value.length) }
|
||||
if (selfChange.current) {
|
||||
selfChange.current = false
|
||||
} else {
|
||||
setCur(value.length)
|
||||
}
|
||||
}, [value])
|
||||
|
||||
const flushPaste = () => {
|
||||
@@ -50,9 +68,13 @@ export function TextInput({ value, onChange, onSubmit, onLargePaste, placeholder
|
||||
const at = pastePos.current
|
||||
pasteBuf.current = ''
|
||||
pasteTimer.current = null
|
||||
if (!pasted) return
|
||||
|
||||
if (!pasted) {
|
||||
return
|
||||
}
|
||||
|
||||
const v = vRef.current
|
||||
|
||||
if (pasted.split('\n').length >= 5 || pasted.length > 500) {
|
||||
const ph = onLargePaste?.(pasted) ?? pasted.replace(/\n/g, ' ')
|
||||
const nv = v.slice(0, at) + ph + v.slice(at)
|
||||
@@ -61,6 +83,7 @@ export function TextInput({ value, onChange, onSubmit, onLargePaste, placeholder
|
||||
setCur(at + ph.length)
|
||||
} else {
|
||||
const clean = pasted.replace(/\n/g, ' ')
|
||||
|
||||
if (clean.length && PRINTABLE.test(clean)) {
|
||||
const nv = v.slice(0, at) + clean + v.slice(at)
|
||||
selfChange.current = true
|
||||
@@ -72,57 +95,120 @@ export function TextInput({ value, onChange, onSubmit, onLargePaste, placeholder
|
||||
|
||||
useInput(
|
||||
(inp, k) => {
|
||||
if (k.upArrow || k.downArrow || (k.ctrl && inp === 'c') || k.tab || (k.shift && k.tab) || k.pageUp || k.pageDown || k.escape)
|
||||
if (
|
||||
k.upArrow ||
|
||||
k.downArrow ||
|
||||
(k.ctrl && inp === 'c') ||
|
||||
k.tab ||
|
||||
(k.shift && k.tab) ||
|
||||
k.pageUp ||
|
||||
k.pageDown ||
|
||||
k.escape
|
||||
) {
|
||||
return
|
||||
if (k.return) { onSubmit?.(value); return }
|
||||
}
|
||||
|
||||
let c = cur, v = value
|
||||
if (k.return) {
|
||||
onSubmit?.(value)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let c = cur,
|
||||
v = value
|
||||
const mod = k.ctrl || k.meta
|
||||
|
||||
if (k.home || (k.ctrl && inp === 'a')) c = 0
|
||||
else if (k.end || (k.ctrl && inp === 'e')) c = v.length
|
||||
else if (k.leftArrow) c = mod ? wl(v, c) : Math.max(0, c - 1)
|
||||
else if (k.rightArrow) c = mod ? wr(v, c) : Math.min(v.length, c + 1)
|
||||
else if ((k.backspace || k.delete) && c > 0) {
|
||||
if (mod) { const t = wl(v, c); v = v.slice(0, t) + v.slice(c); c = t }
|
||||
else { v = v.slice(0, c - 1) + v.slice(c); c-- }
|
||||
}
|
||||
else if (k.ctrl && inp === 'w' && c > 0) { const t = wl(v, c); v = v.slice(0, t) + v.slice(c); c = t }
|
||||
else if (k.ctrl && inp === 'u') { v = v.slice(c); c = 0 }
|
||||
else if (k.ctrl && inp === 'k') v = v.slice(0, c)
|
||||
else if (k.meta && inp === 'b') c = wl(v, c)
|
||||
else if (k.meta && inp === 'f') c = wr(v, c)
|
||||
else if (inp.length > 0) {
|
||||
if (k.home || (k.ctrl && inp === 'a')) {
|
||||
c = 0
|
||||
} else if (k.end || (k.ctrl && inp === 'e')) {
|
||||
c = v.length
|
||||
} else if (k.leftArrow) {
|
||||
c = mod ? wl(v, c) : Math.max(0, c - 1)
|
||||
} else if (k.rightArrow) {
|
||||
c = mod ? wr(v, c) : Math.min(v.length, c + 1)
|
||||
} else if ((k.backspace || k.delete) && c > 0) {
|
||||
if (mod) {
|
||||
const t = wl(v, c)
|
||||
v = v.slice(0, t) + v.slice(c)
|
||||
c = t
|
||||
} else {
|
||||
v = v.slice(0, c - 1) + v.slice(c)
|
||||
c--
|
||||
}
|
||||
} else if (k.ctrl && inp === 'w' && c > 0) {
|
||||
const t = wl(v, c)
|
||||
v = v.slice(0, t) + v.slice(c)
|
||||
c = t
|
||||
} else if (k.ctrl && inp === 'u') {
|
||||
v = v.slice(c)
|
||||
c = 0
|
||||
} else if (k.ctrl && inp === 'k') {
|
||||
v = v.slice(0, c)
|
||||
} else if (k.meta && inp === 'b') {
|
||||
c = wl(v, c)
|
||||
} else if (k.meta && inp === 'f') {
|
||||
c = wr(v, c)
|
||||
} else if (inp.length > 0) {
|
||||
const raw = inp.replace(BRACKET_PASTE, '').replace(/\r\n/g, '\n').replace(/\r/g, '\n')
|
||||
if (!raw) return
|
||||
|
||||
if (!raw) {
|
||||
return
|
||||
}
|
||||
|
||||
const isMultiChar = raw.length > 1 || raw.includes('\n')
|
||||
|
||||
if (isMultiChar) {
|
||||
if (!pasteBuf.current) pastePos.current = c
|
||||
if (!pasteBuf.current) {
|
||||
pastePos.current = c
|
||||
}
|
||||
pasteBuf.current += raw
|
||||
if (pasteTimer.current) clearTimeout(pasteTimer.current)
|
||||
|
||||
if (pasteTimer.current) {
|
||||
clearTimeout(pasteTimer.current)
|
||||
}
|
||||
pasteTimer.current = setTimeout(flushPaste, 50)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if (PRINTABLE.test(raw)) { v = v.slice(0, c) + raw + v.slice(c); c += raw.length }
|
||||
else return
|
||||
if (PRINTABLE.test(raw)) {
|
||||
v = v.slice(0, c) + raw + v.slice(c)
|
||||
c += raw.length
|
||||
} else {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
else return
|
||||
|
||||
c = Math.max(0, Math.min(c, v.length))
|
||||
setCur(c)
|
||||
if (v !== value) { selfChange.current = true; onChange(v) }
|
||||
|
||||
if (v !== value) {
|
||||
selfChange.current = true
|
||||
onChange(v)
|
||||
}
|
||||
},
|
||||
{ isActive: focus }
|
||||
)
|
||||
|
||||
if (!focus) return <Text>{value || (placeholder ? DIM + placeholder + DIM_OFF : '')}</Text>
|
||||
if (!value && placeholder) return <Text>{INV + (placeholder[0] ?? ' ') + INV_OFF + DIM + placeholder.slice(1) + DIM_OFF}</Text>
|
||||
if (!focus) {
|
||||
return <Text>{value || (placeholder ? DIM + placeholder + DIM_OFF : '')}</Text>
|
||||
}
|
||||
|
||||
if (!value && placeholder) {
|
||||
return <Text>{INV + (placeholder[0] ?? ' ') + INV_OFF + DIM + placeholder.slice(1) + DIM_OFF}</Text>
|
||||
}
|
||||
|
||||
let r = ''
|
||||
for (let i = 0; i < value.length; i++) r += i === cur ? INV + value[i] + INV_OFF : value[i]
|
||||
if (cur === value.length) r += INV + ' ' + INV_OFF
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
r += i === cur ? INV + value[i] + INV_OFF : value[i]
|
||||
}
|
||||
|
||||
if (cur === value.length) {
|
||||
r += INV + ' ' + INV_OFF
|
||||
}
|
||||
|
||||
return <Text>{r}</Text>
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ function Spinner({ color }: { color: string }) {
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => setI(p => (p + 1) % SPINNER.length), 80)
|
||||
|
||||
return () => clearInterval(id)
|
||||
}, [])
|
||||
|
||||
@@ -18,15 +19,23 @@ function Spinner({ color }: { color: string }) {
|
||||
}
|
||||
|
||||
export const Thinking = memo(function Thinking({
|
||||
reasoning, t, tools
|
||||
reasoning,
|
||||
t,
|
||||
tools
|
||||
}: {
|
||||
reasoning: string; t: Theme; tools: ActiveTool[]
|
||||
reasoning: string
|
||||
t: Theme
|
||||
tools: ActiveTool[]
|
||||
}) {
|
||||
const [verb, setVerb] = useState(() => pick(VERBS))
|
||||
const [face, setFace] = useState(() => pick(FACES))
|
||||
|
||||
useEffect(() => {
|
||||
const id = setInterval(() => { setVerb(pick(VERBS)); setFace(pick(FACES)) }, 1100)
|
||||
const id = setInterval(() => {
|
||||
setVerb(pick(VERBS))
|
||||
setFace(pick(FACES))
|
||||
}, 1100)
|
||||
|
||||
return () => clearInterval(id)
|
||||
}, [])
|
||||
|
||||
@@ -47,7 +56,11 @@ export const Thinking = memo(function Thinking({
|
||||
</Text>
|
||||
)}
|
||||
|
||||
{tail && <Text color={t.color.dim} dimColor wrap="truncate-end">💭 {tail}</Text>}
|
||||
{tail && (
|
||||
<Text color={t.color.dim} dimColor wrap="truncate-end">
|
||||
💭 {tail}
|
||||
</Text>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -35,7 +35,7 @@ export const HOTKEYS: [string, string][] = [
|
||||
['Home/End', 'start / end of line'],
|
||||
['\\+Enter', 'multi-line continuation'],
|
||||
['!cmd', 'run shell command'],
|
||||
['{!cmd}', 'interpolate shell output inline'],
|
||||
['{!cmd}', 'interpolate shell output inline']
|
||||
]
|
||||
|
||||
export const INTERPOLATION_RE = /\{!(.+?)\}/g
|
||||
|
||||
@@ -9,10 +9,16 @@ const file = join(dir, '.hermes_history')
|
||||
let cache: string[] | null = null
|
||||
|
||||
export function load(): string[] {
|
||||
if (cache) return cache
|
||||
if (cache) {
|
||||
return cache
|
||||
}
|
||||
|
||||
try {
|
||||
if (!existsSync(file)) { cache = []; return cache }
|
||||
if (!existsSync(file)) {
|
||||
cache = []
|
||||
|
||||
return cache
|
||||
}
|
||||
|
||||
const lines = readFileSync(file, 'utf8').split('\n')
|
||||
const entries: string[] = []
|
||||
@@ -26,7 +32,10 @@ export function load(): string[] {
|
||||
current = []
|
||||
}
|
||||
}
|
||||
if (current.length) entries.push(current.join('\n'))
|
||||
|
||||
if (current.length) {
|
||||
entries.push(current.join('\n'))
|
||||
}
|
||||
|
||||
cache = entries.slice(-MAX)
|
||||
} catch {
|
||||
@@ -38,21 +47,37 @@ export function load(): string[] {
|
||||
|
||||
export function append(line: string): void {
|
||||
const trimmed = line.trim()
|
||||
if (!trimmed) return
|
||||
|
||||
if (!trimmed) {
|
||||
return
|
||||
}
|
||||
|
||||
const items = load()
|
||||
if (items.at(-1) === trimmed) return
|
||||
|
||||
if (items.at(-1) === trimmed) {
|
||||
return
|
||||
}
|
||||
|
||||
items.push(trimmed)
|
||||
if (items.length > MAX) items.splice(0, items.length - MAX)
|
||||
|
||||
if (items.length > MAX) {
|
||||
items.splice(0, items.length - MAX)
|
||||
}
|
||||
|
||||
try {
|
||||
if (!existsSync(dir)) mkdirSync(dir, { recursive: true })
|
||||
if (!existsSync(dir)) {
|
||||
mkdirSync(dir, { recursive: true })
|
||||
}
|
||||
|
||||
const ts = new Date().toISOString().replace('T', ' ').replace('Z', '')
|
||||
const encoded = trimmed.split('\n').map(l => '+' + l).join('\n')
|
||||
const encoded = trimmed
|
||||
.split('\n')
|
||||
.map(l => '+' + l)
|
||||
.join('\n')
|
||||
appendFileSync(file, `\n# ${ts}\n${encoded}\n`)
|
||||
} catch { /* ignore */ }
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function all(): string[] {
|
||||
|
||||
Reference in New Issue
Block a user