goatchain 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +529 -0
- package/cli/args.mjs +113 -0
- package/cli/clack.mjs +111 -0
- package/cli/clipboard.mjs +320 -0
- package/cli/files.mjs +247 -0
- package/cli/index.mjs +299 -0
- package/cli/itermPaste.mjs +147 -0
- package/cli/persist.mjs +205 -0
- package/cli/repl.mjs +3141 -0
- package/cli/sdk.mjs +341 -0
- package/cli/sessionTransfer.mjs +118 -0
- package/cli/turn.mjs +751 -0
- package/cli/ui.mjs +138 -0
- package/cli.mjs +5 -0
- package/dist/index.cjs +4860 -0
- package/dist/index.d.cts +3479 -0
- package/dist/index.d.ts +3479 -0
- package/dist/index.js +4795 -0
- package/package.json +68 -0
package/cli/ui.mjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
export function formatOneLine(text, maxLen) {
|
|
2
|
+
const s = String(text ?? '').replace(/\s+/g, ' ').trim()
|
|
3
|
+
if (s.length <= maxLen)
|
|
4
|
+
return s
|
|
5
|
+
return `${s.slice(0, Math.max(0, maxLen - 1))}…`
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function supportsAnsi() {
|
|
9
|
+
if (!process.stdout.isTTY)
|
|
10
|
+
return false
|
|
11
|
+
if (process.env.TERM === 'dumb')
|
|
12
|
+
return false
|
|
13
|
+
if ('NO_COLOR' in process.env)
|
|
14
|
+
return false
|
|
15
|
+
return true
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function wrapAnsi(text, ...opens) {
|
|
19
|
+
if (!supportsAnsi())
|
|
20
|
+
return String(text ?? '')
|
|
21
|
+
return `${opens.join('')}${String(text ?? '')}\u001B[0m`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const ansi = {
|
|
25
|
+
bold: text => wrapAnsi(text, '\u001B[1m'),
|
|
26
|
+
dim: text => wrapAnsi(text, '\u001B[2m'),
|
|
27
|
+
gray: text => wrapAnsi(text, '\u001B[90m'),
|
|
28
|
+
red: text => wrapAnsi(text, '\u001B[31m'),
|
|
29
|
+
green: text => wrapAnsi(text, '\u001B[32m'),
|
|
30
|
+
yellow: text => wrapAnsi(text, '\u001B[33m'),
|
|
31
|
+
cyan: text => wrapAnsi(text, '\u001B[36m'),
|
|
32
|
+
inverse: text => wrapAnsi(text, '\u001B[7m'),
|
|
33
|
+
header: text => wrapAnsi(text, '\u001B[1m', '\u001B[7m'),
|
|
34
|
+
highlight: text => wrapAnsi(text, '\u001B[30m', '\u001B[43m'),
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function messageToTextPreview(message) {
|
|
38
|
+
if (!message || typeof message !== 'object')
|
|
39
|
+
return ''
|
|
40
|
+
const c = message.content
|
|
41
|
+
if (typeof c === 'string')
|
|
42
|
+
return c
|
|
43
|
+
try {
|
|
44
|
+
return JSON.stringify(c)
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return String(c)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function deriveSessionTitle(messages) {
|
|
52
|
+
const firstUser = Array.isArray(messages)
|
|
53
|
+
? messages.find(m => m && typeof m === 'object' && m.role === 'user' && formatOneLine(messageToTextPreview(m), 999).length > 0)
|
|
54
|
+
: undefined
|
|
55
|
+
const txt = firstUser ? messageToTextPreview(firstUser) : ''
|
|
56
|
+
return formatOneLine(txt || 'New session', 60) || 'New session'
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function deriveSessionSummary(messages) {
|
|
60
|
+
const arr = Array.isArray(messages) ? messages : []
|
|
61
|
+
const firstUser = arr.find(m => m && typeof m === 'object' && m.role === 'user')
|
|
62
|
+
const lastUser = [...arr].reverse().find(m => m && typeof m === 'object' && m.role === 'user')
|
|
63
|
+
const lastAssistant = [...arr].reverse().find(m => m && typeof m === 'object' && m.role === 'assistant')
|
|
64
|
+
const a = formatOneLine(firstUser ? messageToTextPreview(firstUser) : '', 80)
|
|
65
|
+
const b = formatOneLine(lastUser ? messageToTextPreview(lastUser) : '', 80)
|
|
66
|
+
const c = formatOneLine(lastAssistant ? messageToTextPreview(lastAssistant) : '', 80)
|
|
67
|
+
const parts = [a ? `first: ${a}` : '', b && b !== a ? `last user: ${b}` : '', c ? `last assistant: ${c}` : ''].filter(Boolean)
|
|
68
|
+
return formatOneLine(parts.join(' | '), 160)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function formatWhen(ts) {
|
|
72
|
+
if (typeof ts !== 'number' || !Number.isFinite(ts))
|
|
73
|
+
return ''
|
|
74
|
+
try {
|
|
75
|
+
const d = new Date(ts)
|
|
76
|
+
const pad = n => String(n).padStart(2, '0')
|
|
77
|
+
const yyyy = d.getFullYear()
|
|
78
|
+
const mm = pad(d.getMonth() + 1)
|
|
79
|
+
const dd = pad(d.getDate())
|
|
80
|
+
const hh = pad(d.getHours())
|
|
81
|
+
const min = pad(d.getMinutes())
|
|
82
|
+
const now = new Date()
|
|
83
|
+
const includeYear = yyyy !== now.getFullYear()
|
|
84
|
+
return includeYear ? `${yyyy}-${mm}-${dd} ${hh}:${min}` : `${mm}-${dd} ${hh}:${min}`
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return ''
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function printHistory(messages, options = {}) {
|
|
92
|
+
const arr = Array.isArray(messages) ? messages : []
|
|
93
|
+
const maxMessages = typeof options.maxMessages === 'number' ? options.maxMessages : 12
|
|
94
|
+
const maxCharsPerMessage = typeof options.maxCharsPerMessage === 'number' ? options.maxCharsPerMessage : 600
|
|
95
|
+
const useEmoji = Boolean(options.emoji) && process.stdout.isTTY && process.env.TERM !== 'dumb'
|
|
96
|
+
|
|
97
|
+
const filtered = arr.filter(m => m && typeof m === 'object' && m.role !== 'system')
|
|
98
|
+
if (filtered.length === 0) {
|
|
99
|
+
process.stdout.write('[no history]\n')
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const start = Math.max(0, filtered.length - maxMessages)
|
|
104
|
+
const slice = filtered.slice(start)
|
|
105
|
+
const omitted = start
|
|
106
|
+
|
|
107
|
+
if (omitted > 0)
|
|
108
|
+
process.stdout.write(ansi.dim(`[... ${omitted} earlier messages omitted ...]\n`))
|
|
109
|
+
|
|
110
|
+
for (const m of slice) {
|
|
111
|
+
const role = typeof m.role === 'string' ? m.role : 'unknown'
|
|
112
|
+
const label = role === 'assistant' ? 'assistant' : role === 'user' ? 'user' : role
|
|
113
|
+
const icon = useEmoji
|
|
114
|
+
? label === 'assistant'
|
|
115
|
+
? '🤖'
|
|
116
|
+
: label === 'user'
|
|
117
|
+
? '👤'
|
|
118
|
+
: label === 'tool'
|
|
119
|
+
? '🛠️'
|
|
120
|
+
: '📌'
|
|
121
|
+
: ''
|
|
122
|
+
const labelText = useEmoji ? `${icon} ${label}` : label
|
|
123
|
+
const labelStyled = label === 'assistant'
|
|
124
|
+
? ansi.cyan(labelText)
|
|
125
|
+
: label === 'user'
|
|
126
|
+
? ansi.green(labelText)
|
|
127
|
+
: ansi.gray(label)
|
|
128
|
+
const text = formatOneLine(messageToTextPreview(m), maxCharsPerMessage)
|
|
129
|
+
process.stdout.write(`[${labelStyled}] ${text}\n`)
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function clearScreen() {
|
|
134
|
+
if (!process.stdout.isTTY)
|
|
135
|
+
return
|
|
136
|
+
// Clear screen + move cursor to home.
|
|
137
|
+
process.stdout.write('\u001B[2J\u001B[H')
|
|
138
|
+
}
|