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.
Files changed (104) hide show
  1. package/README.md +324 -0
  2. package/dist/cli/app.d.ts +16 -0
  3. package/dist/cli/app.js +615 -0
  4. package/dist/cli/banner.d.ts +1 -0
  5. package/dist/cli/banner.js +60 -0
  6. package/dist/cli/commands/compile.d.ts +2 -0
  7. package/dist/cli/commands/compile.js +42 -0
  8. package/dist/cli/commands/ingest.d.ts +2 -0
  9. package/dist/cli/commands/ingest.js +32 -0
  10. package/dist/cli/commands/init.d.ts +2 -0
  11. package/dist/cli/commands/init.js +48 -0
  12. package/dist/cli/commands/lint.d.ts +2 -0
  13. package/dist/cli/commands/lint.js +40 -0
  14. package/dist/cli/commands/query.d.ts +2 -0
  15. package/dist/cli/commands/query.js +36 -0
  16. package/dist/cli/commands/search.d.ts +2 -0
  17. package/dist/cli/commands/search.js +34 -0
  18. package/dist/cli/commands/status.d.ts +2 -0
  19. package/dist/cli/commands/status.js +27 -0
  20. package/dist/cli/components/Banner.d.ts +13 -0
  21. package/dist/cli/components/Banner.js +20 -0
  22. package/dist/cli/components/Markdown.d.ts +14 -0
  23. package/dist/cli/components/Markdown.js +324 -0
  24. package/dist/cli/components/Message.d.ts +14 -0
  25. package/dist/cli/components/Message.js +17 -0
  26. package/dist/cli/components/Spinner.d.ts +7 -0
  27. package/dist/cli/components/Spinner.js +19 -0
  28. package/dist/cli/components/StatusBar.d.ts +14 -0
  29. package/dist/cli/components/StatusBar.js +19 -0
  30. package/dist/cli/components/Suggestions.d.ts +13 -0
  31. package/dist/cli/components/Suggestions.js +14 -0
  32. package/dist/cli/entry.d.ts +2 -0
  33. package/dist/cli/entry.js +61 -0
  34. package/dist/cli/helpers.d.ts +14 -0
  35. package/dist/cli/helpers.js +32 -0
  36. package/dist/cli/hooks/useAutocomplete.d.ts +11 -0
  37. package/dist/cli/hooks/useAutocomplete.js +71 -0
  38. package/dist/cli/hooks/useHistory.d.ts +13 -0
  39. package/dist/cli/hooks/useHistory.js +106 -0
  40. package/dist/cli/hooks/useSession.d.ts +16 -0
  41. package/dist/cli/hooks/useSession.js +133 -0
  42. package/dist/cli/index.d.ts +2 -0
  43. package/dist/cli/index.js +41 -0
  44. package/dist/cli/keystore.d.ts +28 -0
  45. package/dist/cli/keystore.js +59 -0
  46. package/dist/cli/onboarding.d.ts +18 -0
  47. package/dist/cli/onboarding.js +306 -0
  48. package/dist/cli/personality.d.ts +34 -0
  49. package/dist/cli/personality.js +196 -0
  50. package/dist/cli/repl.d.ts +20 -0
  51. package/dist/cli/repl.js +338 -0
  52. package/dist/cli/theme.d.ts +25 -0
  53. package/dist/cli/theme.js +64 -0
  54. package/dist/core/compile/compiler.d.ts +25 -0
  55. package/dist/core/compile/compiler.js +229 -0
  56. package/dist/core/compile/index.d.ts +2 -0
  57. package/dist/core/compile/index.js +1 -0
  58. package/dist/core/config.d.ts +10 -0
  59. package/dist/core/config.js +92 -0
  60. package/dist/core/index.d.ts +12 -0
  61. package/dist/core/index.js +11 -0
  62. package/dist/core/ingest/image.d.ts +3 -0
  63. package/dist/core/ingest/image.js +61 -0
  64. package/dist/core/ingest/index.d.ts +18 -0
  65. package/dist/core/ingest/index.js +79 -0
  66. package/dist/core/ingest/pdf.d.ts +2 -0
  67. package/dist/core/ingest/pdf.js +36 -0
  68. package/dist/core/ingest/text.d.ts +2 -0
  69. package/dist/core/ingest/text.js +38 -0
  70. package/dist/core/ingest/web.d.ts +2 -0
  71. package/dist/core/ingest/web.js +202 -0
  72. package/dist/core/lint/index.d.ts +1 -0
  73. package/dist/core/lint/index.js +1 -0
  74. package/dist/core/lint/linter.d.ts +27 -0
  75. package/dist/core/lint/linter.js +147 -0
  76. package/dist/core/llm/index.d.ts +2 -0
  77. package/dist/core/llm/index.js +1 -0
  78. package/dist/core/llm/provider.d.ts +15 -0
  79. package/dist/core/llm/provider.js +241 -0
  80. package/dist/core/prompts.d.ts +28 -0
  81. package/dist/core/prompts.js +374 -0
  82. package/dist/core/query/engine.d.ts +29 -0
  83. package/dist/core/query/engine.js +131 -0
  84. package/dist/core/query/index.d.ts +2 -0
  85. package/dist/core/query/index.js +1 -0
  86. package/dist/core/sanitization.d.ts +11 -0
  87. package/dist/core/sanitization.js +50 -0
  88. package/dist/core/storage/filesystem.d.ts +23 -0
  89. package/dist/core/storage/filesystem.js +106 -0
  90. package/dist/core/storage/index.d.ts +2 -0
  91. package/dist/core/storage/index.js +2 -0
  92. package/dist/core/storage/sqlite.d.ts +30 -0
  93. package/dist/core/storage/sqlite.js +104 -0
  94. package/dist/core/types.d.ts +95 -0
  95. package/dist/core/types.js +4 -0
  96. package/dist/core/utils.d.ts +8 -0
  97. package/dist/core/utils.js +94 -0
  98. package/dist/core/wiki/index.d.ts +1 -0
  99. package/dist/core/wiki/index.js +1 -0
  100. package/dist/core/wiki/wiki.d.ts +19 -0
  101. package/dist/core/wiki/wiki.js +37 -0
  102. package/dist/index.d.ts +2 -0
  103. package/dist/index.js +3 -0
  104. package/package.json +54 -0
@@ -0,0 +1,60 @@
1
+ import chalk from 'chalk';
2
+ import { loadConfig } from '../core/config.js';
3
+ import { Wiki } from '../core/wiki/wiki.js';
4
+ import { touchProfile, getWelcomeMessage } from './onboarding.js';
5
+ // Brand color: #6C3BAA (purple)
6
+ const p = chalk.hex('#6C3BAA');
7
+ const pb = chalk.hex('#6C3BAA').bold;
8
+ const pl = chalk.hex('#9B6ED8'); // lighter purple
9
+ const dim = chalk.hex('#7A7A8C');
10
+ const accent = chalk.hex('#C084FC');
11
+ const bright = chalk.white.bold;
12
+ const LOGO = `
13
+ ${pb('██████╗ ██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗')}
14
+ ${pb('██╔══██╗██╔══██╗██╔═══██╗██║ ██║╚══███╔╝╚██╗ ██╔╝')}
15
+ ${pb('██████╔╝██████╔╝██║ ██║██║ █╗ ██║ ███╔╝ ╚████╔╝')}
16
+ ${pl('██╔══██╗██╔══██╗██║ ██║██║███╗██║ ███╔╝ ╚██╔╝')}
17
+ ${pl('██████╔╝██║ ██║╚██████╔╝╚███╔███╔╝███████╗ ██║')}
18
+ ${pl('╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝ ╚═╝')}
19
+ `;
20
+ export function showBanner() {
21
+ console.log(LOGO);
22
+ // Personalized welcome
23
+ const profile = touchProfile();
24
+ if (profile) {
25
+ const welcome = getWelcomeMessage(profile);
26
+ console.log(` ${accent(welcome)}`);
27
+ }
28
+ else {
29
+ console.log(dim(' LLM-powered knowledge base engine'));
30
+ }
31
+ console.log(dim(`${''.padEnd(52)}v1.0.0`));
32
+ console.log();
33
+ try {
34
+ const config = loadConfig();
35
+ const wiki = new Wiki(config.dataDir);
36
+ const stats = wiki.stats();
37
+ wiki.close();
38
+ const model = config.llm.model || 'default';
39
+ console.log(p(' ─────────────────────────────────────────────────────'));
40
+ console.log();
41
+ console.log(` ${dim('sources')} ${bright(String(stats.sources).padStart(4))} ${dim('articles')} ${bright(String(stats.articles).padStart(4))} ${dim('concepts')} ${bright(String(stats.concepts).padStart(4))}`);
42
+ console.log(` ${dim('model')} ${chalk.white(model)}`);
43
+ console.log(` ${dim('data')} ${chalk.white(config.dataDir)}`);
44
+ }
45
+ catch {
46
+ console.log(dim(' No knowledge base found.'));
47
+ console.log(` Run ${accent('browzy init')} to get started.`);
48
+ }
49
+ console.log();
50
+ console.log(p(' ─────────────────────────────────────────────────────'));
51
+ console.log();
52
+ console.log(` ${dim('Just type a question, or use / commands:')}`);
53
+ console.log();
54
+ console.log(` ${accent('/add <sources...>')} ${dim('Add URLs, PDFs, images, text files')}`);
55
+ console.log(` ${accent('/ask <question>')} ${dim('Search + ask your knowledge base')}`);
56
+ console.log(` ${accent('/health')} ${dim('Stats, checks & suggestions')}`);
57
+ console.log(` ${accent('/rebuild')} ${dim('Force recompile wiki')}`);
58
+ console.log(` ${accent('/help')} ${dim('All commands')}`);
59
+ console.log();
60
+ }
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const compileCommand: Command;
@@ -0,0 +1,42 @@
1
+ import { Command } from 'commander';
2
+ import { WikiCompiler } from '../../core/compile/index.js';
3
+ import { getConfigAndLLM, ensureDirs, spinner, success, info, error } from '../helpers.js';
4
+ import { clampInt } from '../../core/utils.js';
5
+ export const compileCommand = new Command('compile')
6
+ .description('Compile raw sources into wiki articles')
7
+ .option('-b, --batch-size <n>', 'Max sources to process', '20')
8
+ .option('--no-concepts', 'Skip concept extraction')
9
+ .action(async (opts) => {
10
+ const spin = spinner('Compiling wiki...');
11
+ spin.start();
12
+ try {
13
+ const { config, llm } = getConfigAndLLM();
14
+ ensureDirs(config);
15
+ const compiler = new WikiCompiler(config.dataDir, llm);
16
+ const result = await compiler.compile({
17
+ batchSize: clampInt(opts.batchSize, 1, 100, 20),
18
+ extractConcepts: opts.concepts !== false,
19
+ });
20
+ spin.stop();
21
+ if (result.articlesCreated.length > 0) {
22
+ success(`Created ${result.articlesCreated.length} articles:`);
23
+ result.articlesCreated.forEach(s => console.log(` + ${s}`));
24
+ }
25
+ if (result.articlesUpdated.length > 0) {
26
+ success(`Updated ${result.articlesUpdated.length} articles:`);
27
+ result.articlesUpdated.forEach(s => console.log(` ~ ${s}`));
28
+ }
29
+ if (result.conceptsExtracted.length > 0) {
30
+ info(`Suggested ${result.conceptsExtracted.length} new concepts:`);
31
+ result.conceptsExtracted.forEach(s => console.log(` ? ${s}`));
32
+ }
33
+ if (result.articlesCreated.length === 0 && result.articlesUpdated.length === 0) {
34
+ info('Wiki is up to date — nothing to compile');
35
+ }
36
+ }
37
+ catch (err) {
38
+ spin.stop();
39
+ error(`Compilation failed: ${err.message}`);
40
+ process.exit(1);
41
+ }
42
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const ingestCommand: Command;
@@ -0,0 +1,32 @@
1
+ import { Command } from 'commander';
2
+ import { ingest } from '../../core/ingest/index.js';
3
+ import { getConfigAndLLM, ensureDirs, spinner, success, error } from '../helpers.js';
4
+ export const ingestCommand = new Command('ingest')
5
+ .description('Ingest a source into the knowledge base')
6
+ .argument('<source>', 'URL or file path to ingest')
7
+ .option('-t, --type <type>', 'Source type (web, pdf, image, text, markdown)')
8
+ .action(async (source, opts) => {
9
+ const spin = spinner(`Ingesting: ${source}`);
10
+ spin.start();
11
+ try {
12
+ const { config, llm } = getConfigAndLLM();
13
+ ensureDirs(config);
14
+ const result = await ingest(source, config.dataDir, {
15
+ llm,
16
+ type: opts.type,
17
+ });
18
+ spin.stop();
19
+ success(`Ingested: ${result.title}`);
20
+ console.log(` Type: ${result.type}`);
21
+ console.log(` ID: ${result.id}`);
22
+ console.log(` Path: ${result.path}`);
23
+ if (result.images.length > 0) {
24
+ console.log(` Images: ${result.images.length}`);
25
+ }
26
+ }
27
+ catch (err) {
28
+ spin.stop();
29
+ error(`Failed to ingest: ${err.message}`);
30
+ process.exit(1);
31
+ }
32
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
@@ -0,0 +1,48 @@
1
+ import { Command } from 'commander';
2
+ import { writeFileSync, existsSync, mkdirSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import { success, info } from '../helpers.js';
6
+ export const initCommand = new Command('init')
7
+ .description('Initialize a new browzy knowledge base')
8
+ .option('-d, --dir <path>', 'Data directory path', join(homedir(), '.browzy', 'default'))
9
+ .option('-p, --provider <provider>', 'LLM provider (claude or openai)', 'claude')
10
+ .action(async (opts) => {
11
+ const dataDir = opts.dir;
12
+ const provider = opts.provider;
13
+ // Create data directories
14
+ const dirs = [
15
+ dataDir,
16
+ join(dataDir, 'raw'),
17
+ join(dataDir, 'raw', 'images'),
18
+ join(dataDir, 'wiki'),
19
+ join(dataDir, 'output'),
20
+ join(dataDir, '.browzy'),
21
+ ];
22
+ for (const dir of dirs) {
23
+ mkdirSync(dir, { recursive: true });
24
+ }
25
+ // Write config file
26
+ const configPath = join(process.cwd(), 'browzy.config.json');
27
+ if (!existsSync(configPath)) {
28
+ const config = {
29
+ dataDir,
30
+ llm: {
31
+ provider,
32
+ model: provider === 'claude' ? 'claude-sonnet-4-20250514' : 'gpt-4o',
33
+ },
34
+ compile: {
35
+ batchSize: 20,
36
+ extractConcepts: true,
37
+ },
38
+ };
39
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
40
+ success(`Created config: ${configPath}`);
41
+ }
42
+ else {
43
+ info('Config already exists, skipping');
44
+ }
45
+ success(`Knowledge base initialized at: ${dataDir}`);
46
+ info(`Set your API key: export ${provider === 'claude' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'}=...`);
47
+ info('Start ingesting: browzy ingest <url-or-file>');
48
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const lintCommand: Command;
@@ -0,0 +1,40 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { WikiLinter } from '../../core/lint/index.js';
4
+ import { getConfigAndLLM, ensureDirs, spinner, success, info, error } from '../helpers.js';
5
+ export const lintCommand = new Command('lint')
6
+ .description('Run health checks on the wiki')
7
+ .action(async () => {
8
+ const spin = spinner('Linting wiki...');
9
+ spin.start();
10
+ try {
11
+ const { config, llm } = getConfigAndLLM();
12
+ ensureDirs(config);
13
+ const linter = new WikiLinter(config.dataDir, llm);
14
+ const issues = await linter.lint();
15
+ spin.stop();
16
+ if (issues.length === 0) {
17
+ success('Wiki is healthy — no issues found');
18
+ return;
19
+ }
20
+ const errors = issues.filter(i => i.severity === 'error');
21
+ const warnings = issues.filter(i => i.severity === 'warning');
22
+ const suggestions = issues.filter(i => i.severity === 'suggestion');
23
+ for (const issue of issues) {
24
+ const icon = issue.severity === 'error' ? chalk.red('✗') :
25
+ issue.severity === 'warning' ? chalk.yellow('⚠') :
26
+ chalk.blue('💡');
27
+ console.log(`${icon} [${issue.article}] ${issue.message}`);
28
+ if (issue.suggestion) {
29
+ console.log(` → ${chalk.dim(issue.suggestion)}`);
30
+ }
31
+ }
32
+ console.log();
33
+ info(`${errors.length} errors, ${warnings.length} warnings, ${suggestions.length} suggestions`);
34
+ }
35
+ catch (err) {
36
+ spin.stop();
37
+ error(`Lint failed: ${err.message}`);
38
+ process.exit(1);
39
+ }
40
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const queryCommand: Command;
@@ -0,0 +1,36 @@
1
+ import { Command } from 'commander';
2
+ import { QueryEngine } from '../../core/query/index.js';
3
+ import { getConfigAndLLM, ensureDirs, spinner, success, info, error } from '../helpers.js';
4
+ export const queryCommand = new Command('query')
5
+ .description('Ask a question against the knowledge base')
6
+ .argument('<question>', 'Your question')
7
+ .option('-f, --format <format>', 'Output format (markdown, marp, json)', 'markdown')
8
+ .option('-s, --save', 'Save output to a file')
9
+ .action(async (question, opts) => {
10
+ const spin = spinner('Researching...');
11
+ spin.start();
12
+ try {
13
+ const { config, llm } = getConfigAndLLM();
14
+ ensureDirs(config);
15
+ const engine = new QueryEngine(config.dataDir, llm);
16
+ const result = await engine.query(question, {
17
+ format: opts.format,
18
+ save: opts.save,
19
+ });
20
+ spin.stop();
21
+ console.log();
22
+ console.log(result.answer);
23
+ console.log();
24
+ if (result.sourcesUsed.length > 0) {
25
+ info(`Sources: ${result.sourcesUsed.join(', ')}`);
26
+ }
27
+ if (result.outputPath) {
28
+ success(`Saved to: ${result.outputPath}`);
29
+ }
30
+ }
31
+ catch (err) {
32
+ spin.stop();
33
+ error(`Query failed: ${err.message}`);
34
+ process.exit(1);
35
+ }
36
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const searchCommand: Command;
@@ -0,0 +1,34 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { Wiki } from '../../core/wiki/index.js';
4
+ import { getConfig, ensureDirs, info, error } from '../helpers.js';
5
+ import { clampInt } from '../../core/utils.js';
6
+ export const searchCommand = new Command('search')
7
+ .description('Search the knowledge base')
8
+ .argument('<query>', 'Search query')
9
+ .option('-n, --limit <n>', 'Max results', '10')
10
+ .action(async (query, opts) => {
11
+ try {
12
+ const config = getConfig();
13
+ ensureDirs(config);
14
+ const wiki = new Wiki(config.dataDir);
15
+ const results = wiki.search(query, clampInt(opts.limit, 1, 100, 10));
16
+ if (results.length === 0) {
17
+ info('No results found');
18
+ wiki.close();
19
+ return;
20
+ }
21
+ console.log();
22
+ for (const r of results) {
23
+ console.log(`${chalk.cyan(r.slug)} ${chalk.dim(`(${r.score.toFixed(1)})`)}`);
24
+ console.log(` ${chalk.bold(r.title)}`);
25
+ console.log(` ${chalk.dim(r.snippet)}`);
26
+ console.log();
27
+ }
28
+ wiki.close();
29
+ }
30
+ catch (err) {
31
+ error(`Search failed: ${err.message}`);
32
+ process.exit(1);
33
+ }
34
+ });
@@ -0,0 +1,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const statusCommand: Command;
@@ -0,0 +1,27 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import { Wiki } from '../../core/wiki/index.js';
4
+ import { getConfig, ensureDirs, info } from '../helpers.js';
5
+ export const statusCommand = new Command('status')
6
+ .description('Show knowledge base status')
7
+ .action(async () => {
8
+ try {
9
+ const config = getConfig();
10
+ ensureDirs(config);
11
+ const wiki = new Wiki(config.dataDir);
12
+ const stats = wiki.stats();
13
+ console.log();
14
+ console.log(chalk.bold('browzy knowledge base'));
15
+ console.log(chalk.dim(`Data: ${config.dataDir}`));
16
+ console.log(chalk.dim(`LLM: ${config.llm.provider} (${config.llm.model || 'default'})`));
17
+ console.log();
18
+ console.log(` Sources: ${chalk.cyan(stats.sources)}`);
19
+ console.log(` Articles: ${chalk.cyan(stats.articles)}`);
20
+ console.log(` Concepts: ${chalk.cyan(stats.concepts)}`);
21
+ console.log();
22
+ wiki.close();
23
+ }
24
+ catch (err) {
25
+ info(`No knowledge base found. Run: browzy init`);
26
+ }
27
+ });
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ interface BannerProps {
3
+ welcome: string;
4
+ stats: {
5
+ sources: number;
6
+ articles: number;
7
+ concepts: number;
8
+ };
9
+ model: string;
10
+ dataDir: string;
11
+ }
12
+ export declare const Banner: React.FC<BannerProps>;
13
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Text, Box } from 'ink';
4
+ import { getTheme } from '../theme.js';
5
+ import { loadStreak } from '../personality.js';
6
+ const LOGO_TOP = [
7
+ '██████╗ ██████╗ ██████╗ ██╗ ██╗███████╗██╗ ██╗',
8
+ '██╔══██╗██╔══██╗██╔═══██╗██║ ██║╚══███╔╝╚██╗ ██╔╝',
9
+ '██████╔╝██████╔╝██║ ██║██║ █╗ ██║ ███╔╝ ╚████╔╝',
10
+ ];
11
+ const LOGO_BOTTOM = [
12
+ '██╔══██╗██╔══██╗██║ ██║██║███╗██║ ███╔╝ ╚██╔╝',
13
+ '██████╔╝██║ ██║╚██████╔╝╚███╔███╔╝███████╗ ██║',
14
+ '╚═════╝ ╚═╝ ╚═╝ ╚═════╝ ╚══╝╚══╝ ╚══════╝ ╚═╝',
15
+ ];
16
+ export const Banner = React.memo(({ welcome, stats, model, dataDir }) => {
17
+ const theme = getTheme();
18
+ const streak = loadStreak();
19
+ return (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, children: [_jsxs(Box, { flexDirection: "column", marginBottom: 0, children: [LOGO_TOP.map((line, i) => (_jsx(Text, { bold: true, color: theme.brand, children: line }, `top-${i}`))), LOGO_BOTTOM.map((line, i) => (_jsx(Text, { color: theme.brandLight, children: line }, `bot-${i}`)))] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.accent, children: welcome }) }), _jsxs(Text, { color: theme.textMuted, children: [''.padEnd(50), "v1.0.0"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.separator, children: '─'.repeat(55) }) }), _jsxs(Box, { marginTop: 1, gap: 2, children: [_jsxs(Text, { children: [_jsx(Text, { color: theme.textMuted, children: "sources " }), _jsx(Text, { bold: true, color: theme.text, children: stats.sources })] }), _jsx(Text, { color: theme.textMuted, children: "\u00B7" }), _jsxs(Text, { children: [_jsx(Text, { color: theme.textMuted, children: "articles " }), _jsx(Text, { bold: true, color: theme.text, children: stats.articles })] }), _jsx(Text, { color: theme.textMuted, children: "\u00B7" }), _jsxs(Text, { children: [_jsx(Text, { color: theme.textMuted, children: "concepts " }), _jsx(Text, { bold: true, color: theme.text, children: stats.concepts })] }), streak.currentStreak >= 2 && (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textMuted, children: "\u00B7" }), _jsxs(Text, { color: theme.accent, children: [streak.currentStreak, "-day streak"] })] }))] }), _jsxs(Box, { children: [_jsx(Text, { color: theme.textMuted, children: "model " }), _jsx(Text, { color: theme.text, children: model })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: theme.separator, children: '─'.repeat(55) }) }), _jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: stats.articles === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.accent, children: " A blank canvas. Add your first source and watch the magic happen." }), _jsx(Text, { children: " " }), _jsxs(Text, { children: [_jsxs(Text, { color: theme.accent, children: [" /add ", '<url or file>', " "] }), _jsx(Text, { color: theme.textMuted, children: "Feed your browzy its first source" })] })] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { color: theme.textMuted, children: " Just type a question, or use / commands:" }), _jsxs(Text, { children: [_jsxs(Text, { color: theme.accent, children: [" /add ", '<sources...>', " "] }), _jsx(Text, { color: theme.textMuted, children: "Feed your browzy new knowledge" })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.accent, children: " /health " }), _jsx(Text, { color: theme.textMuted, children: "How is your browzy doing?" })] }), _jsxs(Text, { children: [_jsx(Text, { color: theme.accent, children: " /help " }), _jsx(Text, { color: theme.textMuted, children: "All commands" })] })] })) })] }));
20
+ });
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ /**
3
+ * Render markdown as styled terminal text.
4
+ * Handles: headers, bold, italic, code blocks, blockquotes,
5
+ * lists, wiki links, markdown links, horizontal rules, tables.
6
+ * Strikethrough disabled (~ used for "approximately" too often).
7
+ */
8
+ export declare function renderMarkdown(input: string): string;
9
+ /**
10
+ * Ink component for rendering markdown.
11
+ */
12
+ export declare const MarkdownText: React.FC<{
13
+ children: string;
14
+ }>;