browzy 1.0.0
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 +324 -0
- package/dist/cli/app.d.ts +16 -0
- package/dist/cli/app.js +615 -0
- package/dist/cli/banner.d.ts +1 -0
- package/dist/cli/banner.js +60 -0
- package/dist/cli/commands/compile.d.ts +2 -0
- package/dist/cli/commands/compile.js +42 -0
- package/dist/cli/commands/ingest.d.ts +2 -0
- package/dist/cli/commands/ingest.js +32 -0
- package/dist/cli/commands/init.d.ts +2 -0
- package/dist/cli/commands/init.js +48 -0
- package/dist/cli/commands/lint.d.ts +2 -0
- package/dist/cli/commands/lint.js +40 -0
- package/dist/cli/commands/query.d.ts +2 -0
- package/dist/cli/commands/query.js +36 -0
- package/dist/cli/commands/search.d.ts +2 -0
- package/dist/cli/commands/search.js +34 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.js +27 -0
- package/dist/cli/components/Banner.d.ts +13 -0
- package/dist/cli/components/Banner.js +20 -0
- package/dist/cli/components/Markdown.d.ts +14 -0
- package/dist/cli/components/Markdown.js +324 -0
- package/dist/cli/components/Message.d.ts +14 -0
- package/dist/cli/components/Message.js +17 -0
- package/dist/cli/components/Spinner.d.ts +7 -0
- package/dist/cli/components/Spinner.js +19 -0
- package/dist/cli/components/StatusBar.d.ts +14 -0
- package/dist/cli/components/StatusBar.js +19 -0
- package/dist/cli/components/Suggestions.d.ts +13 -0
- package/dist/cli/components/Suggestions.js +14 -0
- package/dist/cli/entry.d.ts +2 -0
- package/dist/cli/entry.js +61 -0
- package/dist/cli/helpers.d.ts +14 -0
- package/dist/cli/helpers.js +32 -0
- package/dist/cli/hooks/useAutocomplete.d.ts +11 -0
- package/dist/cli/hooks/useAutocomplete.js +71 -0
- package/dist/cli/hooks/useHistory.d.ts +13 -0
- package/dist/cli/hooks/useHistory.js +106 -0
- package/dist/cli/hooks/useSession.d.ts +16 -0
- package/dist/cli/hooks/useSession.js +133 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +41 -0
- package/dist/cli/keystore.d.ts +28 -0
- package/dist/cli/keystore.js +59 -0
- package/dist/cli/onboarding.d.ts +18 -0
- package/dist/cli/onboarding.js +306 -0
- package/dist/cli/personality.d.ts +34 -0
- package/dist/cli/personality.js +196 -0
- package/dist/cli/repl.d.ts +20 -0
- package/dist/cli/repl.js +338 -0
- package/dist/cli/theme.d.ts +25 -0
- package/dist/cli/theme.js +64 -0
- package/dist/core/compile/compiler.d.ts +25 -0
- package/dist/core/compile/compiler.js +229 -0
- package/dist/core/compile/index.d.ts +2 -0
- package/dist/core/compile/index.js +1 -0
- package/dist/core/config.d.ts +10 -0
- package/dist/core/config.js +92 -0
- package/dist/core/index.d.ts +12 -0
- package/dist/core/index.js +11 -0
- package/dist/core/ingest/image.d.ts +3 -0
- package/dist/core/ingest/image.js +61 -0
- package/dist/core/ingest/index.d.ts +18 -0
- package/dist/core/ingest/index.js +79 -0
- package/dist/core/ingest/pdf.d.ts +2 -0
- package/dist/core/ingest/pdf.js +36 -0
- package/dist/core/ingest/text.d.ts +2 -0
- package/dist/core/ingest/text.js +38 -0
- package/dist/core/ingest/web.d.ts +2 -0
- package/dist/core/ingest/web.js +202 -0
- package/dist/core/lint/index.d.ts +1 -0
- package/dist/core/lint/index.js +1 -0
- package/dist/core/lint/linter.d.ts +27 -0
- package/dist/core/lint/linter.js +147 -0
- package/dist/core/llm/index.d.ts +2 -0
- package/dist/core/llm/index.js +1 -0
- package/dist/core/llm/provider.d.ts +15 -0
- package/dist/core/llm/provider.js +241 -0
- package/dist/core/prompts.d.ts +28 -0
- package/dist/core/prompts.js +374 -0
- package/dist/core/query/engine.d.ts +29 -0
- package/dist/core/query/engine.js +131 -0
- package/dist/core/query/index.d.ts +2 -0
- package/dist/core/query/index.js +1 -0
- package/dist/core/sanitization.d.ts +11 -0
- package/dist/core/sanitization.js +50 -0
- package/dist/core/storage/filesystem.d.ts +23 -0
- package/dist/core/storage/filesystem.js +106 -0
- package/dist/core/storage/index.d.ts +2 -0
- package/dist/core/storage/index.js +2 -0
- package/dist/core/storage/sqlite.d.ts +30 -0
- package/dist/core/storage/sqlite.js +104 -0
- package/dist/core/types.d.ts +95 -0
- package/dist/core/types.js +4 -0
- package/dist/core/utils.d.ts +8 -0
- package/dist/core/utils.js +94 -0
- package/dist/core/wiki/index.d.ts +1 -0
- package/dist/core/wiki/index.js +1 -0
- package/dist/core/wiki/wiki.d.ts +19 -0
- package/dist/core/wiki/wiki.js +37 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/package.json +54 -0
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { getTheme } from '../theme.js';
|
|
6
|
+
const tokenCache = new Map();
|
|
7
|
+
const MAX_CACHE = 500;
|
|
8
|
+
// Check a larger sample for markdown syntax
|
|
9
|
+
const MD_SYNTAX_RE = /[#*`|[\]>\-_]|\n\n|^\d+\. /;
|
|
10
|
+
/**
|
|
11
|
+
* Render markdown as styled terminal text.
|
|
12
|
+
* Handles: headers, bold, italic, code blocks, blockquotes,
|
|
13
|
+
* lists, wiki links, markdown links, horizontal rules, tables.
|
|
14
|
+
* Strikethrough disabled (~ used for "approximately" too often).
|
|
15
|
+
*/
|
|
16
|
+
export function renderMarkdown(input) {
|
|
17
|
+
// Fast path: skip parsing for plain text (check first 1000 chars)
|
|
18
|
+
if (!MD_SYNTAX_RE.test(input.slice(0, 1000))) {
|
|
19
|
+
return input;
|
|
20
|
+
}
|
|
21
|
+
const cached = tokenCache.get(input);
|
|
22
|
+
if (cached)
|
|
23
|
+
return cached;
|
|
24
|
+
const theme = getTheme();
|
|
25
|
+
const lines = input.split('\n');
|
|
26
|
+
const result = [];
|
|
27
|
+
let inCodeBlock = false;
|
|
28
|
+
let codeBlockLang = '';
|
|
29
|
+
let codeLines = [];
|
|
30
|
+
let inTable = false;
|
|
31
|
+
let tableRows = [];
|
|
32
|
+
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
|
33
|
+
const line = lines[lineIdx];
|
|
34
|
+
// Display math blocks $$...$$ (standalone lines)
|
|
35
|
+
if (line.trim().startsWith('$$') && !inCodeBlock) {
|
|
36
|
+
// Collect until closing $$
|
|
37
|
+
let mathContent = line.trim().slice(2);
|
|
38
|
+
if (mathContent.endsWith('$$')) {
|
|
39
|
+
// Single-line display math
|
|
40
|
+
mathContent = mathContent.slice(0, -2);
|
|
41
|
+
result.push(' ' + chalk.hex(theme.accent)(latexToUnicode(mathContent.trim())));
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Multi-line: collect until $$
|
|
45
|
+
const mathLines = [mathContent];
|
|
46
|
+
while (++lineIdx < lines.length) {
|
|
47
|
+
const ml = lines[lineIdx];
|
|
48
|
+
if (ml.trim().endsWith('$$')) {
|
|
49
|
+
mathLines.push(ml.trim().slice(0, -2));
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
mathLines.push(ml);
|
|
53
|
+
}
|
|
54
|
+
const fullMath = mathLines.join(' ').trim();
|
|
55
|
+
result.push(' ' + chalk.hex(theme.accent)(latexToUnicode(fullMath)));
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
// Code blocks
|
|
59
|
+
if (line.startsWith('```')) {
|
|
60
|
+
if (inCodeBlock) {
|
|
61
|
+
result.push(chalk.dim(' ┌─' + (codeBlockLang ? ` ${codeBlockLang} ` : '') + '─'));
|
|
62
|
+
for (const cl of codeLines) {
|
|
63
|
+
result.push(chalk.dim(' │ ') + chalk.hex(theme.text)(cl));
|
|
64
|
+
}
|
|
65
|
+
result.push(chalk.dim(' └─'));
|
|
66
|
+
codeLines = [];
|
|
67
|
+
codeBlockLang = '';
|
|
68
|
+
inCodeBlock = false;
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
inCodeBlock = true;
|
|
72
|
+
codeBlockLang = line.slice(3).trim();
|
|
73
|
+
}
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (inCodeBlock) {
|
|
77
|
+
codeLines.push(line);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
// Tables: detect pipe-delimited lines
|
|
81
|
+
if (line.includes('|') && line.trim().startsWith('|')) {
|
|
82
|
+
const cells = line.split('|').slice(1, -1).map(c => c.trim());
|
|
83
|
+
// Skip separator rows (---|---|---)
|
|
84
|
+
if (cells.every(c => /^[-:]+$/.test(c)))
|
|
85
|
+
continue;
|
|
86
|
+
if (!inTable) {
|
|
87
|
+
inTable = true;
|
|
88
|
+
tableRows = [];
|
|
89
|
+
}
|
|
90
|
+
tableRows.push(cells);
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
else if (inTable) {
|
|
94
|
+
// Flush table
|
|
95
|
+
result.push(...renderTable(tableRows, theme));
|
|
96
|
+
tableRows = [];
|
|
97
|
+
inTable = false;
|
|
98
|
+
}
|
|
99
|
+
// Headers
|
|
100
|
+
const headerMatch = line.match(/^(#{1,3})\s+(.+)/);
|
|
101
|
+
if (headerMatch) {
|
|
102
|
+
const level = headerMatch[1].length;
|
|
103
|
+
const text = headerMatch[2];
|
|
104
|
+
const color = level === 1 ? theme.brand : level === 2 ? theme.brandLight : theme.accent;
|
|
105
|
+
result.push(chalk.hex(color).bold(text));
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Blockquotes
|
|
109
|
+
if (line.startsWith('> ')) {
|
|
110
|
+
result.push(chalk.hex(theme.textMuted)(' │ ') + chalk.italic(formatInline(line.slice(2), theme)));
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Horizontal rules
|
|
114
|
+
if (/^[-*_]{3,}\s*$/.test(line.trim())) {
|
|
115
|
+
result.push(chalk.hex(theme.separator)('─'.repeat(40)));
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
// List items — numbered and bulleted
|
|
119
|
+
const bulletMatch = line.match(/^(\s*)([-*+])\s+(.*)/);
|
|
120
|
+
if (bulletMatch) {
|
|
121
|
+
const indent = bulletMatch[1];
|
|
122
|
+
const content = bulletMatch[3];
|
|
123
|
+
result.push(`${indent}${chalk.hex(theme.accent)('•')} ${formatInline(content, theme)}`);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const numberedMatch = line.match(/^(\s*)(\d+)\.\s+(.*)/);
|
|
127
|
+
if (numberedMatch) {
|
|
128
|
+
const indent = numberedMatch[1];
|
|
129
|
+
const num = numberedMatch[2];
|
|
130
|
+
const content = numberedMatch[3];
|
|
131
|
+
result.push(`${indent}${chalk.hex(theme.accent)(num + '.')} ${formatInline(content, theme)}`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
// Regular text with inline formatting
|
|
135
|
+
result.push(formatInline(line, theme));
|
|
136
|
+
}
|
|
137
|
+
// Flush any remaining table
|
|
138
|
+
if (inTable && tableRows.length > 0) {
|
|
139
|
+
result.push(...renderTable(tableRows, theme));
|
|
140
|
+
}
|
|
141
|
+
const output = result.join('\n');
|
|
142
|
+
// LRU-ish cache
|
|
143
|
+
if (tokenCache.size >= MAX_CACHE) {
|
|
144
|
+
const firstKey = tokenCache.keys().next().value;
|
|
145
|
+
if (firstKey !== undefined)
|
|
146
|
+
tokenCache.delete(firstKey);
|
|
147
|
+
}
|
|
148
|
+
tokenCache.set(input, output);
|
|
149
|
+
return output;
|
|
150
|
+
}
|
|
151
|
+
function formatInline(text, theme) {
|
|
152
|
+
let formatted = text;
|
|
153
|
+
// LaTeX math: inline $...$ and display $$...$$
|
|
154
|
+
formatted = formatted.replace(/\$\$([^$]+)\$\$/g, (_m, tex) => chalk.hex(theme.accent)(latexToUnicode(tex.trim())));
|
|
155
|
+
formatted = formatted.replace(/\$([^$]+)\$/g, (_m, tex) => chalk.hex(theme.accent)(latexToUnicode(tex.trim())));
|
|
156
|
+
// Bold **text**
|
|
157
|
+
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, (_m, t) => chalk.bold(t));
|
|
158
|
+
// Italic *text* (not bold)
|
|
159
|
+
formatted = formatted.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_m, t) => chalk.italic(t));
|
|
160
|
+
// Inline code `text`
|
|
161
|
+
formatted = formatted.replace(/`([^`]+)`/g, (_m, t) => chalk.hex(theme.accent)(t));
|
|
162
|
+
// Wiki links [[slug]] — internal refs, styled but not clickable
|
|
163
|
+
formatted = formatted.replace(/\[\[([^\]]+)\]\]/g, (_m, t) => chalk.hex(theme.link)(`[${t}]`));
|
|
164
|
+
// Markdown links [text](url) — show URL explicitly since OSC 8 is unreliable through Ink
|
|
165
|
+
formatted = formatted.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (_m, text, url) => {
|
|
166
|
+
if (text === url) {
|
|
167
|
+
// URL-only link: just show it
|
|
168
|
+
return chalk.hex(theme.link).underline(url);
|
|
169
|
+
}
|
|
170
|
+
// Named link: show text + URL
|
|
171
|
+
return chalk.hex(theme.link).underline(text) + chalk.hex(theme.textMuted)(` (${url})`);
|
|
172
|
+
});
|
|
173
|
+
// Escaped characters
|
|
174
|
+
formatted = formatted.replace(/\\([()\\`*_{}[\]#+-.])/g, '$1');
|
|
175
|
+
return formatted;
|
|
176
|
+
}
|
|
177
|
+
// ── LaTeX to Unicode ────────────────────────────────────────────
|
|
178
|
+
const LATEX_SYMBOLS = {
|
|
179
|
+
// Greek
|
|
180
|
+
'\\alpha': 'α', '\\beta': 'β', '\\gamma': 'γ', '\\delta': 'δ',
|
|
181
|
+
'\\epsilon': 'ε', '\\zeta': 'ζ', '\\eta': 'η', '\\theta': 'θ',
|
|
182
|
+
'\\iota': 'ι', '\\kappa': 'κ', '\\lambda': 'λ', '\\mu': 'μ',
|
|
183
|
+
'\\nu': 'ν', '\\xi': 'ξ', '\\pi': 'π', '\\rho': 'ρ',
|
|
184
|
+
'\\sigma': 'σ', '\\tau': 'τ', '\\upsilon': 'υ', '\\phi': 'φ',
|
|
185
|
+
'\\chi': 'χ', '\\psi': 'ψ', '\\omega': 'ω',
|
|
186
|
+
'\\Gamma': 'Γ', '\\Delta': 'Δ', '\\Theta': 'Θ', '\\Lambda': 'Λ',
|
|
187
|
+
'\\Xi': 'Ξ', '\\Pi': 'Π', '\\Sigma': 'Σ', '\\Phi': 'Φ',
|
|
188
|
+
'\\Psi': 'Ψ', '\\Omega': 'Ω',
|
|
189
|
+
// Operators
|
|
190
|
+
'\\times': '×', '\\div': '÷', '\\cdot': '·', '\\pm': '±',
|
|
191
|
+
'\\mp': '∓', '\\leq': '≤', '\\geq': '≥', '\\neq': '≠',
|
|
192
|
+
'\\approx': '≈', '\\equiv': '≡', '\\sim': '∼', '\\propto': '∝',
|
|
193
|
+
'\\infty': '∞', '\\partial': '∂', '\\nabla': '∇',
|
|
194
|
+
// Set theory
|
|
195
|
+
'\\in': '∈', '\\notin': '∉', '\\subset': '⊂', '\\supset': '⊃',
|
|
196
|
+
'\\subseteq': '⊆', '\\supseteq': '⊇', '\\cup': '∪', '\\cap': '∩',
|
|
197
|
+
'\\emptyset': '∅', '\\varnothing': '∅',
|
|
198
|
+
// Logic
|
|
199
|
+
'\\forall': '∀', '\\exists': '∃', '\\neg': '¬', '\\land': '∧',
|
|
200
|
+
'\\lor': '∨', '\\implies': '⟹', '\\iff': '⟺',
|
|
201
|
+
'\\therefore': '∴', '\\because': '∵',
|
|
202
|
+
// Arrows
|
|
203
|
+
'\\to': '→', '\\rightarrow': '→', '\\leftarrow': '←',
|
|
204
|
+
'\\leftrightarrow': '↔', '\\Rightarrow': '⇒', '\\Leftarrow': '⇐',
|
|
205
|
+
'\\mapsto': '↦',
|
|
206
|
+
// Big operators
|
|
207
|
+
'\\sum': '∑', '\\prod': '∏', '\\int': '∫', '\\oint': '∮',
|
|
208
|
+
'\\bigcup': '⋃', '\\bigcap': '⋂', '\\bigoplus': '⊕',
|
|
209
|
+
// Misc
|
|
210
|
+
'\\star': '⋆', '\\circ': '∘', '\\bullet': '•',
|
|
211
|
+
'\\ldots': '…', '\\cdots': '⋯', '\\vdots': '⋮', '\\ddots': '⋱',
|
|
212
|
+
'\\langle': '⟨', '\\rangle': '⟩',
|
|
213
|
+
'\\lceil': '⌈', '\\rceil': '⌉', '\\lfloor': '⌊', '\\rfloor': '⌋',
|
|
214
|
+
'\\triangle': '△', '\\square': '□', '\\diamond': '◇',
|
|
215
|
+
'\\perp': '⊥', '\\parallel': '∥', '\\angle': '∠',
|
|
216
|
+
'\\checkmark': '✓', '\\qed': '∎',
|
|
217
|
+
};
|
|
218
|
+
const SUPERSCRIPTS = {
|
|
219
|
+
'0': '⁰', '1': '¹', '2': '²', '3': '³', '4': '⁴',
|
|
220
|
+
'5': '⁵', '6': '⁶', '7': '⁷', '8': '⁸', '9': '⁹',
|
|
221
|
+
'+': '⁺', '-': '⁻', '=': '⁼', '(': '⁽', ')': '⁾',
|
|
222
|
+
'n': 'ⁿ', 'i': 'ⁱ', 'd': 'ᵈ', 'k': 'ᵏ', 'a': 'ᵃ',
|
|
223
|
+
'b': 'ᵇ', 'c': 'ᶜ', 'e': 'ᵉ', 'f': 'ᶠ', 'g': 'ᵍ',
|
|
224
|
+
'h': 'ʰ', 'j': 'ʲ', 'l': 'ˡ', 'm': 'ᵐ', 'o': 'ᵒ',
|
|
225
|
+
'p': 'ᵖ', 'r': 'ʳ', 's': 'ˢ', 't': 'ᵗ', 'u': 'ᵘ',
|
|
226
|
+
'v': 'ᵛ', 'w': 'ʷ', 'x': 'ˣ', 'y': 'ʸ', 'z': 'ᶻ',
|
|
227
|
+
'T': 'ᵀ',
|
|
228
|
+
};
|
|
229
|
+
const SUBSCRIPTS = {
|
|
230
|
+
'0': '₀', '1': '₁', '2': '₂', '3': '₃', '4': '₄',
|
|
231
|
+
'5': '₅', '6': '₆', '7': '₇', '8': '₈', '9': '₉',
|
|
232
|
+
'+': '₊', '-': '₋', '=': '₌', '(': '₍', ')': '₎',
|
|
233
|
+
'a': 'ₐ', 'e': 'ₑ', 'i': 'ᵢ', 'j': 'ⱼ', 'k': 'ₖ',
|
|
234
|
+
'n': 'ₙ', 'o': 'ₒ', 'p': 'ₚ', 'r': 'ᵣ', 's': 'ₛ',
|
|
235
|
+
't': 'ₜ', 'u': 'ᵤ', 'v': 'ᵥ', 'x': 'ₓ',
|
|
236
|
+
};
|
|
237
|
+
const MATHBB = {
|
|
238
|
+
'A': '𝔸', 'B': '𝔹', 'C': 'ℂ', 'D': '𝔻', 'E': '𝔼',
|
|
239
|
+
'F': '𝔽', 'G': '𝔾', 'H': 'ℍ', 'I': '𝕀', 'J': '𝕁',
|
|
240
|
+
'K': '𝕂', 'L': '𝕃', 'M': '𝕄', 'N': 'ℕ', 'O': '𝕆',
|
|
241
|
+
'P': 'ℙ', 'Q': 'ℚ', 'R': 'ℝ', 'S': '𝕊', 'T': '𝕋',
|
|
242
|
+
'U': '𝕌', 'V': '𝕍', 'W': '𝕎', 'X': '𝕏', 'Y': '𝕐', 'Z': 'ℤ',
|
|
243
|
+
};
|
|
244
|
+
const MATHCAL = {
|
|
245
|
+
'A': '𝒜', 'B': 'ℬ', 'C': '𝒞', 'D': '𝒟', 'E': 'ℰ',
|
|
246
|
+
'F': 'ℱ', 'G': '𝒢', 'H': 'ℋ', 'I': 'ℐ', 'J': '𝒥',
|
|
247
|
+
'K': '𝒦', 'L': 'ℒ', 'M': 'ℳ', 'N': '𝒩', 'O': '𝒪',
|
|
248
|
+
'P': '𝒫', 'Q': '𝒬', 'R': 'ℛ', 'S': '𝒮', 'T': '𝒯',
|
|
249
|
+
'U': '𝒰', 'V': '𝒱', 'W': '𝒲', 'X': '𝒳', 'Y': '𝒴', 'Z': '𝒵',
|
|
250
|
+
};
|
|
251
|
+
function latexToUnicode(tex) {
|
|
252
|
+
let result = tex;
|
|
253
|
+
// \mathbb{X} → double-struck
|
|
254
|
+
result = result.replace(/\\mathbb\{([A-Z])\}/g, (_m, c) => MATHBB[c] || c);
|
|
255
|
+
// \mathcal{X} → calligraphic
|
|
256
|
+
result = result.replace(/\\mathcal\{([A-Z])\}/g, (_m, c) => MATHCAL[c] || c);
|
|
257
|
+
// \text{...} → just the text
|
|
258
|
+
result = result.replace(/\\text\{([^}]*)\}/g, '$1');
|
|
259
|
+
result = result.replace(/\\textbf\{([^}]*)\}/g, '$1');
|
|
260
|
+
result = result.replace(/\\mathrm\{([^}]*)\}/g, '$1');
|
|
261
|
+
// Fractions \frac{a}{b} → a/b
|
|
262
|
+
result = result.replace(/\\frac\{([^}]*)\}\{([^}]*)\}/g, '($1/$2)');
|
|
263
|
+
// Square root \sqrt{x} → √x
|
|
264
|
+
result = result.replace(/\\sqrt\{([^}]*)\}/g, '√($1)');
|
|
265
|
+
result = result.replace(/\\sqrt\[(\d+)\]\{([^}]*)\}/g, '$1√($2)');
|
|
266
|
+
// Superscripts ^{...} → Unicode superscript
|
|
267
|
+
result = result.replace(/\^{([^}]*)}/g, (_m, inner) => {
|
|
268
|
+
return inner.split('').map((c) => SUPERSCRIPTS[c] || c).join('');
|
|
269
|
+
});
|
|
270
|
+
result = result.replace(/\^([a-zA-Z0-9])/g, (_m, c) => SUPERSCRIPTS[c] || `^${c}`);
|
|
271
|
+
// Subscripts _{...} → Unicode subscript
|
|
272
|
+
result = result.replace(/_{([^}]*)}/g, (_m, inner) => {
|
|
273
|
+
return inner.split('').map((c) => SUBSCRIPTS[c] || c).join('');
|
|
274
|
+
});
|
|
275
|
+
result = result.replace(/_([a-zA-Z0-9])/g, (_m, c) => SUBSCRIPTS[c] || `_${c}`);
|
|
276
|
+
// Named symbols
|
|
277
|
+
for (const [cmd, sym] of Object.entries(LATEX_SYMBOLS)) {
|
|
278
|
+
// Use word boundary to avoid partial matches
|
|
279
|
+
result = result.split(cmd).join(sym);
|
|
280
|
+
}
|
|
281
|
+
// \left and \right delimiters
|
|
282
|
+
result = result.replace(/\\left\s*/g, '');
|
|
283
|
+
result = result.replace(/\\right\s*/g, '');
|
|
284
|
+
result = result.replace(/\\big\s*/g, '');
|
|
285
|
+
result = result.replace(/\\Big\s*/g, '');
|
|
286
|
+
// Clean up remaining backslash commands we don't handle
|
|
287
|
+
result = result.replace(/\\[a-zA-Z]+/g, (match) => match.slice(1));
|
|
288
|
+
// Clean up extra braces
|
|
289
|
+
result = result.replace(/\{([^{}]*)\}/g, '$1');
|
|
290
|
+
result = result.replace(/\{([^{}]*)\}/g, '$1'); // Second pass for nested
|
|
291
|
+
// Clean whitespace
|
|
292
|
+
result = result.replace(/\s+/g, ' ').trim();
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
function renderTable(rows, theme) {
|
|
296
|
+
if (rows.length === 0)
|
|
297
|
+
return [];
|
|
298
|
+
// Calculate column widths
|
|
299
|
+
const colCount = Math.max(...rows.map(r => r.length));
|
|
300
|
+
const colWidths = [];
|
|
301
|
+
for (let c = 0; c < colCount; c++) {
|
|
302
|
+
colWidths.push(Math.max(...rows.map(r => (r[c] || '').length), 3));
|
|
303
|
+
}
|
|
304
|
+
const result = [];
|
|
305
|
+
const separator = chalk.dim(' ' + colWidths.map(w => '─'.repeat(w + 2)).join('┼'));
|
|
306
|
+
for (let r = 0; r < rows.length; r++) {
|
|
307
|
+
const cells = rows[r];
|
|
308
|
+
const line = cells.map((cell, c) => {
|
|
309
|
+
const padded = (cell || '').padEnd(colWidths[c] || 3);
|
|
310
|
+
return r === 0 ? chalk.bold(padded) : padded;
|
|
311
|
+
}).join(chalk.dim(' │ '));
|
|
312
|
+
result.push(' ' + line);
|
|
313
|
+
if (r === 0)
|
|
314
|
+
result.push(separator);
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Ink component for rendering markdown.
|
|
320
|
+
*/
|
|
321
|
+
export const MarkdownText = React.memo(({ children }) => {
|
|
322
|
+
const rendered = renderMarkdown(children);
|
|
323
|
+
return _jsx(Text, { children: rendered });
|
|
324
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export type MessageRole = 'user' | 'assistant' | 'system';
|
|
3
|
+
export interface MessageData {
|
|
4
|
+
id: string;
|
|
5
|
+
role: MessageRole;
|
|
6
|
+
content: string;
|
|
7
|
+
sources?: string[];
|
|
8
|
+
timestamp: number;
|
|
9
|
+
}
|
|
10
|
+
interface MessageProps {
|
|
11
|
+
message: MessageData;
|
|
12
|
+
}
|
|
13
|
+
export declare const Message: React.FC<MessageProps>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Text, Box } from 'ink';
|
|
4
|
+
import { getTheme } from '../theme.js';
|
|
5
|
+
import { renderMarkdown } from './Markdown.js';
|
|
6
|
+
export const Message = React.memo(({ message }) => {
|
|
7
|
+
const theme = getTheme();
|
|
8
|
+
if (message.role === 'user') {
|
|
9
|
+
return (_jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: _jsxs(Text, { bold: true, color: theme.userMessage, children: ['› ', message.content] }) }));
|
|
10
|
+
}
|
|
11
|
+
if (message.role === 'system') {
|
|
12
|
+
return (_jsx(Box, { marginBottom: 1, paddingLeft: 2, children: _jsx(Text, { color: theme.systemMessage, children: message.content }) }));
|
|
13
|
+
}
|
|
14
|
+
// Assistant message — render with markdown
|
|
15
|
+
const rendered = renderMarkdown(message.content);
|
|
16
|
+
return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, paddingLeft: 2, children: [_jsx(Text, { children: rendered }), message.sources && message.sources.length > 0 && (_jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: theme.textMuted, children: ["sources: ", message.sources.join(', ')] }) }))] }));
|
|
17
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Text } from 'ink';
|
|
4
|
+
import { getTheme } from '../theme.js';
|
|
5
|
+
const FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
6
|
+
export const BrowzySpinner = ({ label, elapsed }) => {
|
|
7
|
+
const [frame, setFrame] = useState(0);
|
|
8
|
+
const theme = getTheme();
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
const timer = setInterval(() => {
|
|
11
|
+
setFrame(f => (f + 1) % FRAMES.length);
|
|
12
|
+
}, 80);
|
|
13
|
+
return () => clearInterval(timer);
|
|
14
|
+
}, []);
|
|
15
|
+
const elapsedStr = elapsed !== undefined && elapsed > 2
|
|
16
|
+
? ` ${elapsed.toFixed(1)}s`
|
|
17
|
+
: '';
|
|
18
|
+
return (_jsxs(Text, { children: [_jsx(Text, { color: theme.accent, children: FRAMES[frame] }), label && _jsxs(Text, { color: theme.textDim, children: [" ", label] }), elapsedStr && _jsx(Text, { color: theme.textMuted, children: elapsedStr })] }));
|
|
19
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
interface StatusBarProps {
|
|
3
|
+
model: string;
|
|
4
|
+
sources: number;
|
|
5
|
+
articles: number;
|
|
6
|
+
tokenUsage?: {
|
|
7
|
+
input: number;
|
|
8
|
+
output: number;
|
|
9
|
+
};
|
|
10
|
+
hint?: string;
|
|
11
|
+
temporaryStatus?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const StatusBar: React.FC<StatusBarProps>;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { Text, Box, useStdout } from 'ink';
|
|
4
|
+
import { getTheme } from '../theme.js';
|
|
5
|
+
export const StatusBar = ({ model, sources, articles, tokenUsage, hint, temporaryStatus, }) => {
|
|
6
|
+
const theme = getTheme();
|
|
7
|
+
const { stdout } = useStdout();
|
|
8
|
+
const cols = stdout.columns || 80;
|
|
9
|
+
const [showTemp, setShowTemp] = useState(false);
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (temporaryStatus) {
|
|
12
|
+
setShowTemp(true);
|
|
13
|
+
const timer = setTimeout(() => setShowTemp(false), 4000);
|
|
14
|
+
return () => clearTimeout(timer);
|
|
15
|
+
}
|
|
16
|
+
}, [temporaryStatus]);
|
|
17
|
+
const left = `${sources} sources · ${articles} articles${articles > 0 ? ' · growing' : ''}`;
|
|
18
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { color: theme.separator, children: '─'.repeat(cols) }) }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(Box, { children: showTemp && temporaryStatus ? (_jsxs(Text, { color: theme.accent, children: [" ", temporaryStatus] })) : (_jsxs(Text, { color: theme.textMuted, children: [" ", left] })) }), _jsx(Box, { children: _jsxs(Text, { color: theme.textMuted, children: [model, " "] }) })] }), hint && (_jsx(Box, { children: _jsxs(Text, { color: theme.textMuted, children: [" ", hint] }) }))] }));
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export interface Suggestion {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
usage?: string;
|
|
6
|
+
}
|
|
7
|
+
interface SuggestionsProps {
|
|
8
|
+
items: Suggestion[];
|
|
9
|
+
selectedIndex: number;
|
|
10
|
+
visible: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare const SuggestionList: React.FC<SuggestionsProps>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Text, Box } from 'ink';
|
|
3
|
+
import { getTheme } from '../theme.js';
|
|
4
|
+
const MAX_VISIBLE = 5;
|
|
5
|
+
export const SuggestionList = ({ items, selectedIndex, visible }) => {
|
|
6
|
+
const theme = getTheme();
|
|
7
|
+
if (!visible || items.length === 0)
|
|
8
|
+
return null;
|
|
9
|
+
const visibleItems = items.slice(0, MAX_VISIBLE);
|
|
10
|
+
return (_jsxs(Box, { flexDirection: "column", marginTop: 0, children: [visibleItems.map((item, i) => {
|
|
11
|
+
const isSelected = i === selectedIndex;
|
|
12
|
+
return (_jsxs(Box, { children: [_jsxs(Text, { color: isSelected ? theme.accent : theme.textDim, bold: isSelected, children: [isSelected ? '› ' : ' ', (item.usage || item.name).padEnd(28)] }), _jsx(Text, { color: theme.textMuted, children: item.description })] }, item.name));
|
|
13
|
+
}), items.length > MAX_VISIBLE && (_jsxs(Text, { color: theme.textMuted, children: [" ... ", items.length - MAX_VISIBLE, " more"] }))] }));
|
|
14
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import 'dotenv/config';
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { render } from 'ink';
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { BrowzyApp, BrowzyErrorBoundary } from './app.js';
|
|
7
|
+
import { needsOnboarding, runOnboarding } from './onboarding.js';
|
|
8
|
+
import { initCommand } from './commands/init.js';
|
|
9
|
+
import { ingestCommand } from './commands/ingest.js';
|
|
10
|
+
import { compileCommand } from './commands/compile.js';
|
|
11
|
+
import { queryCommand } from './commands/query.js';
|
|
12
|
+
import { lintCommand } from './commands/lint.js';
|
|
13
|
+
import { statusCommand } from './commands/status.js';
|
|
14
|
+
import { searchCommand } from './commands/search.js';
|
|
15
|
+
// Prevent corepack issues
|
|
16
|
+
process.env.COREPACK_ENABLE_AUTO_PIN = '0';
|
|
17
|
+
// Signal handlers for graceful shutdown
|
|
18
|
+
process.on('SIGTERM', () => process.exit(143));
|
|
19
|
+
process.on('SIGHUP', () => process.exit(129));
|
|
20
|
+
process.on('unhandledRejection', (reason) => {
|
|
21
|
+
console.error('Unhandled rejection:', reason instanceof Error ? reason.message : reason);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
});
|
|
24
|
+
async function main() {
|
|
25
|
+
if (process.argv.length <= 2) {
|
|
26
|
+
if (needsOnboarding()) {
|
|
27
|
+
const success = await runOnboarding();
|
|
28
|
+
if (!success)
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const { waitUntilExit } = render(React.createElement(BrowzyErrorBoundary, null, React.createElement(BrowzyApp)), { exitOnCtrlC: false });
|
|
33
|
+
await waitUntilExit();
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
// Ensure terminal is restored even on crash
|
|
37
|
+
process.stdout.write('\x1B[?25h'); // Show cursor
|
|
38
|
+
console.error('browzy error:', err);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
const program = new Command();
|
|
44
|
+
program
|
|
45
|
+
.name('browzy')
|
|
46
|
+
.description('LLM-powered personal knowledge base engine')
|
|
47
|
+
.version('1.0.0');
|
|
48
|
+
program.addCommand(initCommand);
|
|
49
|
+
program.addCommand(ingestCommand);
|
|
50
|
+
program.addCommand(compileCommand);
|
|
51
|
+
program.addCommand(queryCommand);
|
|
52
|
+
program.addCommand(lintCommand);
|
|
53
|
+
program.addCommand(statusCommand);
|
|
54
|
+
program.addCommand(searchCommand);
|
|
55
|
+
program.parse();
|
|
56
|
+
}
|
|
57
|
+
main().catch(err => {
|
|
58
|
+
process.stdout.write('\x1B[?25h'); // Show cursor
|
|
59
|
+
console.error(err);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BrowzyConfig } from '../core/types.js';
|
|
2
|
+
import type { LLMProvider } from '../core/llm/provider.js';
|
|
3
|
+
export declare function getConfig(): BrowzyConfig;
|
|
4
|
+
export declare function getConfigAndLLM(): {
|
|
5
|
+
config: BrowzyConfig;
|
|
6
|
+
llm: LLMProvider;
|
|
7
|
+
};
|
|
8
|
+
export declare function ensureDirs(config: BrowzyConfig): void;
|
|
9
|
+
export declare function spinner(text: string): import("ora").Ora;
|
|
10
|
+
export declare function success(msg: string): void;
|
|
11
|
+
export declare function warn(msg: string): void;
|
|
12
|
+
export declare function error(msg: string): void;
|
|
13
|
+
export declare function info(msg: string): void;
|
|
14
|
+
export declare function table(data: Record<string, unknown>[]): void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { loadConfig, ensureDataDirs, createProvider } from '../core/index.js';
|
|
4
|
+
export function getConfig() {
|
|
5
|
+
return loadConfig();
|
|
6
|
+
}
|
|
7
|
+
export function getConfigAndLLM() {
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const llm = createProvider(config.llm);
|
|
10
|
+
return { config, llm };
|
|
11
|
+
}
|
|
12
|
+
export function ensureDirs(config) {
|
|
13
|
+
ensureDataDirs(config);
|
|
14
|
+
}
|
|
15
|
+
export function spinner(text) {
|
|
16
|
+
return ora({ text, color: 'cyan' });
|
|
17
|
+
}
|
|
18
|
+
export function success(msg) {
|
|
19
|
+
console.log(chalk.green('✓'), msg);
|
|
20
|
+
}
|
|
21
|
+
export function warn(msg) {
|
|
22
|
+
console.log(chalk.yellow('⚠'), msg);
|
|
23
|
+
}
|
|
24
|
+
export function error(msg) {
|
|
25
|
+
console.log(chalk.red('✗'), msg);
|
|
26
|
+
}
|
|
27
|
+
export function info(msg) {
|
|
28
|
+
console.log(chalk.blue('ℹ'), msg);
|
|
29
|
+
}
|
|
30
|
+
export function table(data) {
|
|
31
|
+
console.table(data);
|
|
32
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Suggestion } from '../components/Suggestions.js';
|
|
2
|
+
export declare function useAutocomplete(): {
|
|
3
|
+
getMatches: (input: string) => Suggestion[];
|
|
4
|
+
selectedIndex: number;
|
|
5
|
+
visible: boolean;
|
|
6
|
+
updateForInput: (input: string) => void;
|
|
7
|
+
moveSelection: (direction: "up" | "down", input: string) => void;
|
|
8
|
+
acceptSuggestion: (input: string) => string | null;
|
|
9
|
+
getGhostText: (input: string) => string;
|
|
10
|
+
setVisible: import("react").Dispatch<import("react").SetStateAction<boolean>>;
|
|
11
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
const COMMANDS = [
|
|
3
|
+
{ name: '/add', description: 'Feed your browzy new knowledge', usage: '/add <urls or paths...>' },
|
|
4
|
+
{ name: '/health', description: 'How is your browzy doing?' },
|
|
5
|
+
{ name: '/model', description: 'Switch models', usage: '/model [model-id]' },
|
|
6
|
+
{ name: '/rebuild', description: 'Recompile from scratch' },
|
|
7
|
+
{ name: '/export', description: 'Save this session as markdown' },
|
|
8
|
+
{ name: '/help', description: 'All commands' },
|
|
9
|
+
{ name: '/quit', description: 'Exit' },
|
|
10
|
+
];
|
|
11
|
+
export function useAutocomplete() {
|
|
12
|
+
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
13
|
+
const [visible, setVisible] = useState(false);
|
|
14
|
+
const getMatches = useCallback((input) => {
|
|
15
|
+
if (!input.startsWith('/'))
|
|
16
|
+
return [];
|
|
17
|
+
const query = input.toLowerCase();
|
|
18
|
+
return COMMANDS.filter(c => c.name.startsWith(query));
|
|
19
|
+
}, []);
|
|
20
|
+
const updateForInput = useCallback((input) => {
|
|
21
|
+
if (input.startsWith('/') && !input.includes(' ')) {
|
|
22
|
+
const matches = getMatches(input);
|
|
23
|
+
setVisible(matches.length > 0 && input !== matches[0]?.name);
|
|
24
|
+
setSelectedIndex(0);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
setVisible(false);
|
|
28
|
+
}
|
|
29
|
+
}, [getMatches]);
|
|
30
|
+
const moveSelection = useCallback((direction, input) => {
|
|
31
|
+
const matches = getMatches(input);
|
|
32
|
+
if (matches.length === 0)
|
|
33
|
+
return;
|
|
34
|
+
setSelectedIndex(prev => {
|
|
35
|
+
if (direction === 'up')
|
|
36
|
+
return Math.max(0, prev - 1);
|
|
37
|
+
return Math.min(matches.length - 1, prev + 1);
|
|
38
|
+
});
|
|
39
|
+
}, [getMatches]);
|
|
40
|
+
const acceptSuggestion = useCallback((input) => {
|
|
41
|
+
const matches = getMatches(input);
|
|
42
|
+
if (matches.length === 0 || !visible)
|
|
43
|
+
return null;
|
|
44
|
+
const selected = matches[selectedIndex];
|
|
45
|
+
if (!selected)
|
|
46
|
+
return null;
|
|
47
|
+
setVisible(false);
|
|
48
|
+
return selected.name + ' ';
|
|
49
|
+
}, [getMatches, selectedIndex, visible]);
|
|
50
|
+
const getGhostText = useCallback((input) => {
|
|
51
|
+
if (!visible)
|
|
52
|
+
return '';
|
|
53
|
+
const matches = getMatches(input);
|
|
54
|
+
if (matches.length === 0)
|
|
55
|
+
return '';
|
|
56
|
+
const match = matches[selectedIndex] || matches[0];
|
|
57
|
+
if (!match || !match.name.startsWith(input))
|
|
58
|
+
return '';
|
|
59
|
+
return match.name.slice(input.length);
|
|
60
|
+
}, [getMatches, selectedIndex, visible]);
|
|
61
|
+
return {
|
|
62
|
+
getMatches,
|
|
63
|
+
selectedIndex,
|
|
64
|
+
visible,
|
|
65
|
+
updateForInput,
|
|
66
|
+
moveSelection,
|
|
67
|
+
acceptSuggestion,
|
|
68
|
+
getGhostText,
|
|
69
|
+
setVisible,
|
|
70
|
+
};
|
|
71
|
+
}
|