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,306 @@
1
+ import * as readline from 'readline';
2
+ import { mkdirSync, writeFileSync, readFileSync, existsSync, appendFileSync, chmodSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+ import chalk from 'chalk';
6
+ import ora from 'ora';
7
+ const p = chalk.hex('#6C3BAA');
8
+ const pb = chalk.hex('#6C3BAA').bold;
9
+ const accent = chalk.hex('#C084FC');
10
+ const dim = chalk.hex('#7A7A8C');
11
+ function ask(rl, question) {
12
+ return new Promise((resolve) => {
13
+ rl.question(question, (answer) => resolve(answer.trim()));
14
+ });
15
+ }
16
+ function askSecret(rl, question) {
17
+ return new Promise((resolve) => {
18
+ const output = rl.output;
19
+ let muted = false;
20
+ const origWrite = output.write.bind(output);
21
+ // Suppress echo by intercepting output.write while readline reads input
22
+ output.write = ((chunk, ...args) => {
23
+ if (muted)
24
+ return true;
25
+ return origWrite(chunk, ...args);
26
+ });
27
+ rl.question(question, (answer) => {
28
+ muted = false;
29
+ output.write = origWrite;
30
+ output.write('\n');
31
+ resolve(answer.trim());
32
+ });
33
+ muted = true;
34
+ });
35
+ }
36
+ function getProfilePath() {
37
+ return join(homedir(), '.browzy', 'profile.json');
38
+ }
39
+ export function loadProfile() {
40
+ const path = getProfilePath();
41
+ if (!existsSync(path))
42
+ return null;
43
+ try {
44
+ return JSON.parse(readFileSync(path, 'utf-8'));
45
+ }
46
+ catch {
47
+ return null;
48
+ }
49
+ }
50
+ export function saveProfile(profile) {
51
+ mkdirSync(join(homedir(), '.browzy'), { recursive: true });
52
+ writeFileSync(getProfilePath(), JSON.stringify(profile, null, 2), 'utf-8');
53
+ }
54
+ export function touchProfile() {
55
+ const profile = loadProfile();
56
+ if (!profile)
57
+ return null;
58
+ profile.lastSeen = new Date().toISOString();
59
+ profile.sessionCount++;
60
+ saveProfile(profile);
61
+ return profile;
62
+ }
63
+ // ── Welcome Back Messages ───────────────────────────────────────
64
+ export function getWelcomeMessage(profile, stats) {
65
+ const hour = new Date().getHours();
66
+ const name = profile.name;
67
+ const count = profile.sessionCount;
68
+ const vibe = hour < 6 ? 'late-night' :
69
+ hour < 12 ? 'morning' :
70
+ hour < 17 ? 'afternoon' :
71
+ hour < 21 ? 'evening' :
72
+ 'late-night';
73
+ // First time — excitement
74
+ if (count <= 1) {
75
+ return `Hey ${name}! Welcome to browzy. Let's build something you'll keep coming back to.`;
76
+ }
77
+ // Second and third — reinforcement
78
+ if (count <= 3) {
79
+ const early = [
80
+ `${name}! You're back. That's how it starts.`,
81
+ `Welcome back, ${name}. Your browzy remembered everything.`,
82
+ ];
83
+ return early[(count - 2) % early.length];
84
+ }
85
+ // Regular user (4-15) — build the relationship
86
+ const regular = [
87
+ `Hey ${name}. What caught your curiosity today?`,
88
+ `${name}! Your browzy has ${stats?.articles || 'some'} articles waiting for you.`,
89
+ `Welcome back. Every session makes your browzy a little smarter.`,
90
+ `${name}. Ready to go deeper?`,
91
+ vibe === 'morning' ? `Morning, ${name}. Fresh brain, fresh research.` :
92
+ vibe === 'late-night' ? `Late night, ${name}? The best ideas come after midnight.` :
93
+ `${name}. Let's make today's research count.`,
94
+ ];
95
+ if (count <= 15)
96
+ return regular[count % regular.length];
97
+ // Power user (15+) — they're hooked, be their research partner
98
+ const power = [
99
+ `${name}. The knowledge compounds.`,
100
+ `Another day, another rabbit hole. Let's go, ${name}.`,
101
+ `${name}. Your browzy has been thinking about you.`,
102
+ `Back at it. What are we chasing today, ${name}?`,
103
+ vibe === 'morning' ? `Morning, ${name}. Your browzy is warmed up and ready.` :
104
+ vibe === 'late-night' ? `Midnight research mode. Respect, ${name}.` :
105
+ `${name}. Your future self is going to love this session.`,
106
+ `${name}. ${stats?.articles || 0} articles deep and counting. What's next?`,
107
+ ];
108
+ return power[count % power.length];
109
+ }
110
+ function getTimeSince(isoDate) {
111
+ const diff = Date.now() - new Date(isoDate).getTime();
112
+ const hours = Math.floor(diff / 3600000);
113
+ if (hours < 1)
114
+ return 'a few minutes';
115
+ if (hours < 24)
116
+ return `${hours}h`;
117
+ const days = Math.floor(hours / 24);
118
+ if (days === 1)
119
+ return '1 day';
120
+ return `${days} days`;
121
+ }
122
+ async function fetchClaudeModels(apiKey) {
123
+ try {
124
+ const { default: Anthropic } = await import('@anthropic-ai/sdk');
125
+ const client = new Anthropic({ apiKey });
126
+ const page = await client.models.list({ limit: 50 });
127
+ return page.data
128
+ .filter((m) => m.id.startsWith('claude') && !m.id.includes('instant'))
129
+ .sort((a, b) => b.created_at.localeCompare(a.created_at))
130
+ .map((m) => ({
131
+ id: m.id,
132
+ name: m.display_name,
133
+ }));
134
+ }
135
+ catch {
136
+ // Fallback if API call fails
137
+ return [
138
+ { id: 'claude-sonnet-4-20250514', name: 'Claude Sonnet 4' },
139
+ { id: 'claude-opus-4-20250514', name: 'Claude Opus 4' },
140
+ { id: 'claude-haiku-4-20250414', name: 'Claude Haiku 4' },
141
+ ];
142
+ }
143
+ }
144
+ async function fetchOpenAIModels(apiKey) {
145
+ try {
146
+ const { default: OpenAI } = await import('openai');
147
+ const client = new OpenAI({ apiKey });
148
+ const list = await client.models.list();
149
+ return Array.from(list.data)
150
+ .filter((m) => m.id.startsWith('gpt-'))
151
+ .sort((a, b) => b.id.localeCompare(a.id))
152
+ .slice(0, 10)
153
+ .map((m) => ({
154
+ id: m.id,
155
+ name: m.id,
156
+ }));
157
+ }
158
+ catch {
159
+ return [
160
+ { id: 'gpt-4o', name: 'GPT-4o' },
161
+ { id: 'gpt-4-turbo', name: 'GPT-4 Turbo' },
162
+ ];
163
+ }
164
+ }
165
+ // ── Onboarding Flow ─────────────────────────────────────────────
166
+ export async function runOnboarding() {
167
+ const rl = readline.createInterface({
168
+ input: process.stdin,
169
+ output: process.stdout,
170
+ });
171
+ console.log();
172
+ console.log(pb(' Welcome to browzy.ai'));
173
+ console.log(dim(' Your personal, LLM-powered knowledge base.\n'));
174
+ // Step 1: Name
175
+ console.log(p(' 1/3 ') + chalk.white('What\'s your name?'));
176
+ const userName = await ask(rl, p(' › '));
177
+ if (!userName) {
178
+ console.log(dim('\n Setup cancelled.'));
179
+ rl.close();
180
+ return false;
181
+ }
182
+ console.log();
183
+ console.log(accent(` Hey ${userName}! Let's get you set up.`));
184
+ // Step 2: API key
185
+ console.log();
186
+ console.log(p(' 2/3 ') + chalk.white('Connect your Claude API key'));
187
+ console.log(dim(' Get one at console.anthropic.com/settings/keys'));
188
+ const existingKey = process.env.ANTHROPIC_API_KEY;
189
+ let apiKey = existingKey || '';
190
+ if (existingKey) {
191
+ console.log(chalk.green(' ✓ ') + 'Found ANTHROPIC_API_KEY in environment');
192
+ }
193
+ else {
194
+ console.log(dim(' (input will be hidden)'));
195
+ apiKey = await askSecret(rl, p(' › '));
196
+ if (!apiKey) {
197
+ console.log(dim(' Skipped — you can set ANTHROPIC_API_KEY later in .env'));
198
+ }
199
+ else if (!apiKey.startsWith('sk-ant-')) {
200
+ console.log(chalk.yellow(' ⚠ ') + 'Key doesn\'t look like an Anthropic key (expected sk-ant-... prefix)');
201
+ console.log(dim(' Saving anyway — update in .env if this was a mistake'));
202
+ }
203
+ }
204
+ // Step 3: Pick model (fetch live from API)
205
+ let selectedModel = 'claude-sonnet-4-20250514';
206
+ if (apiKey) {
207
+ console.log();
208
+ console.log(p(' 3/3 ') + chalk.white('Choose your model'));
209
+ const spin = ora({ text: dim('fetching available models...'), color: 'white', spinner: 'dots' });
210
+ spin.start();
211
+ const models = await fetchClaudeModels(apiKey);
212
+ spin.stop();
213
+ if (models.length > 0) {
214
+ console.log();
215
+ models.forEach((m, i) => {
216
+ const marker = m.id === selectedModel ? chalk.green(' (recommended)') : '';
217
+ console.log(dim(` [${i + 1}] `) + chalk.white(m.name) + dim(` — ${m.id}`) + marker);
218
+ });
219
+ const modelChoice = await ask(rl, p(' › '));
220
+ const idx = parseInt(modelChoice, 10) - 1;
221
+ if (idx >= 0 && idx < models.length) {
222
+ selectedModel = models[idx].id;
223
+ }
224
+ console.log(chalk.green(' ✓ ') + `Using ${selectedModel}`);
225
+ }
226
+ else {
227
+ console.log(dim(' Could not fetch models. Using default: ') + chalk.white(selectedModel));
228
+ }
229
+ }
230
+ else {
231
+ console.log();
232
+ console.log(p(' 3/3 ') + chalk.white('Model'));
233
+ console.log(dim(` Defaulting to ${selectedModel} (change later in config)`));
234
+ }
235
+ rl.close();
236
+ // ── Create everything ───────────────────────────────────────
237
+ const dataDir = join(homedir(), '.browzy', 'default');
238
+ const spin = ora({ text: dim('setting up...'), color: 'white', spinner: 'dots' });
239
+ spin.start();
240
+ // Create data dirs
241
+ for (const dir of [
242
+ dataDir,
243
+ join(dataDir, 'raw'),
244
+ join(dataDir, 'raw', 'images'),
245
+ join(dataDir, 'wiki'),
246
+ join(dataDir, 'output'),
247
+ join(dataDir, '.browzy'),
248
+ ]) {
249
+ mkdirSync(dir, { recursive: true });
250
+ }
251
+ // Save user profile
252
+ const profile = {
253
+ name: userName,
254
+ createdAt: new Date().toISOString(),
255
+ lastSeen: new Date().toISOString(),
256
+ sessionCount: 1,
257
+ };
258
+ saveProfile(profile);
259
+ // Write config
260
+ const configPath = join(process.cwd(), 'browzy.config.json');
261
+ if (!existsSync(configPath)) {
262
+ const config = {
263
+ dataDir,
264
+ llm: {
265
+ provider: 'claude',
266
+ model: selectedModel,
267
+ },
268
+ compile: {
269
+ batchSize: 20,
270
+ extractConcepts: true,
271
+ },
272
+ };
273
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
274
+ }
275
+ // Write .env if key was provided and not in env
276
+ if (apiKey && !existingKey) {
277
+ const envPath = join(process.cwd(), '.env');
278
+ if (existsSync(envPath)) {
279
+ appendFileSync(envPath, `\nANTHROPIC_API_KEY=${apiKey}\n`);
280
+ }
281
+ else {
282
+ writeFileSync(envPath, `ANTHROPIC_API_KEY=${apiKey}\n`, { mode: 0o600 });
283
+ }
284
+ chmodSync(envPath, 0o600);
285
+ }
286
+ spin.stop();
287
+ // ── Done! ─────────────────────────────────────────────────────
288
+ console.log();
289
+ console.log(p(' ─────────────────────────────────────────────────────'));
290
+ console.log();
291
+ console.log(chalk.green(' ✓ ') + chalk.white.bold(`You're all set, ${userName}!`));
292
+ console.log(dim(` data: ${dataDir}`));
293
+ console.log(dim(` model: ${selectedModel}`));
294
+ console.log();
295
+ console.log(dim(' Get started:'));
296
+ console.log(` ${accent('/add')} ${dim('paste a URL or drag a file to add your first source')}`);
297
+ console.log(` ${dim('then just type a question to start exploring')}`);
298
+ console.log();
299
+ return true;
300
+ }
301
+ /**
302
+ * Check if onboarding is needed (no profile exists).
303
+ */
304
+ export function needsOnboarding() {
305
+ return loadProfile() === null;
306
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * browzy.ai — Personality engine.
3
+ *
4
+ * Every string the user sees should make them feel something.
5
+ * Progress, delight, curiosity, momentum.
6
+ */
7
+ interface StreakData {
8
+ currentStreak: number;
9
+ longestStreak: number;
10
+ lastActiveDate: string;
11
+ totalSessions: number;
12
+ totalSourcesAdded: number;
13
+ totalQueriesAsked: number;
14
+ milestonesSeen: string[];
15
+ }
16
+ export declare function loadStreak(): StreakData;
17
+ export declare function updateStreak(): StreakData;
18
+ export declare function recordSourceAdded(count?: number): void;
19
+ export declare function recordQuery(): void;
20
+ export declare function checkMilestones(stats: {
21
+ sources: number;
22
+ articles: number;
23
+ concepts: number;
24
+ }): string | null;
25
+ export declare function getThinkingMessage(): string;
26
+ export declare function getIngestingMessage(): string;
27
+ export declare function getCompilingMessage(): string;
28
+ export declare function getHealthMessage(): string;
29
+ export declare function getAddReward(title: string, articlesCreated: number, articlesUpdated: number, totalArticles: number): string;
30
+ export declare function getQueryReward(sourcesUsed: number): string;
31
+ export declare function getExitMessage(streak: StreakData): string;
32
+ export declare function getEmptyStateMessage(): string;
33
+ export declare function getHealthReward(errors: number, warnings: number, suggestions: number): string;
34
+ export {};
@@ -0,0 +1,196 @@
1
+ /**
2
+ * browzy.ai — Personality engine.
3
+ *
4
+ * Every string the user sees should make them feel something.
5
+ * Progress, delight, curiosity, momentum.
6
+ */
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
8
+ import { join } from 'path';
9
+ import { homedir } from 'os';
10
+ // ── Streak & Milestone Tracker ──────────────────────────────────
11
+ const STREAK_FILE = join(homedir(), '.browzy', 'streak.json');
12
+ function today() {
13
+ return new Date().toISOString().slice(0, 10);
14
+ }
15
+ function yesterday() {
16
+ const d = new Date();
17
+ d.setDate(d.getDate() - 1);
18
+ return d.toISOString().slice(0, 10);
19
+ }
20
+ export function loadStreak() {
21
+ try {
22
+ if (existsSync(STREAK_FILE)) {
23
+ return JSON.parse(readFileSync(STREAK_FILE, 'utf-8'));
24
+ }
25
+ }
26
+ catch { /* ignore */ }
27
+ return {
28
+ currentStreak: 0,
29
+ longestStreak: 0,
30
+ lastActiveDate: '',
31
+ totalSessions: 0,
32
+ totalSourcesAdded: 0,
33
+ totalQueriesAsked: 0,
34
+ milestonesSeen: [],
35
+ };
36
+ }
37
+ function saveStreak(data) {
38
+ mkdirSync(join(homedir(), '.browzy'), { recursive: true });
39
+ writeFileSync(STREAK_FILE, JSON.stringify(data, null, 2), 'utf-8');
40
+ }
41
+ export function updateStreak() {
42
+ const data = loadStreak();
43
+ const t = today();
44
+ if (data.lastActiveDate === t) {
45
+ // Already active today
46
+ return data;
47
+ }
48
+ data.totalSessions++;
49
+ if (data.lastActiveDate === yesterday()) {
50
+ data.currentStreak++;
51
+ }
52
+ else if (data.lastActiveDate !== t) {
53
+ data.currentStreak = 1;
54
+ }
55
+ if (data.currentStreak > data.longestStreak) {
56
+ data.longestStreak = data.currentStreak;
57
+ }
58
+ data.lastActiveDate = t;
59
+ saveStreak(data);
60
+ return data;
61
+ }
62
+ export function recordSourceAdded(count = 1) {
63
+ const data = loadStreak();
64
+ data.totalSourcesAdded += count;
65
+ saveStreak(data);
66
+ }
67
+ export function recordQuery() {
68
+ const data = loadStreak();
69
+ data.totalQueriesAsked++;
70
+ saveStreak(data);
71
+ }
72
+ const MILESTONES = [
73
+ { id: 'first-source', check: (s) => s.sources >= 1, message: "Your first source is in. The journey begins." },
74
+ { id: 'five-sources', check: (s) => s.sources >= 5, message: "5 sources ingested. Your browzy is starting to take shape." },
75
+ { id: 'ten-articles', check: (s) => s.articles >= 10, message: "10 articles compiled. Your browzy knows things now." },
76
+ { id: 'twenty-five-articles', check: (s) => s.articles >= 25, message: "25 articles. Your browzy is becoming a real knowledge base." },
77
+ { id: 'fifty-articles', check: (s) => s.articles >= 50, message: "50 articles. You know more about this topic than most people alive." },
78
+ { id: 'hundred-articles', check: (s) => s.articles >= 100, message: "100 articles. This is a serious research corpus. Respect." },
79
+ { id: 'three-day-streak', check: (_s, d) => d.currentStreak >= 3, message: "3-day research streak. Don't break the chain." },
80
+ { id: 'seven-day-streak', check: (_s, d) => d.currentStreak >= 7, message: "7-day streak. You're building a habit. This is how breakthroughs happen." },
81
+ { id: 'thirty-day-streak', check: (_s, d) => d.currentStreak >= 30, message: "30-day streak. You're not just researching — you're compounding knowledge." },
82
+ { id: 'ten-queries', check: (_s, d) => d.totalQueriesAsked >= 10, message: "10 questions asked. Your browzy is earning its keep." },
83
+ { id: 'fifty-queries', check: (_s, d) => d.totalQueriesAsked >= 50, message: "50 questions deep. You and your browzy are in a groove." },
84
+ { id: 'hundred-concepts', check: (s) => s.concepts >= 100, message: "100 concepts mapped. Your browzy has a real knowledge graph now." },
85
+ ];
86
+ export function checkMilestones(stats) {
87
+ const streak = loadStreak();
88
+ const seen = new Set(streak.milestonesSeen);
89
+ for (const m of MILESTONES) {
90
+ if (!seen.has(m.id) && m.check(stats, streak)) {
91
+ streak.milestonesSeen.push(m.id);
92
+ saveStreak(streak);
93
+ return m.message;
94
+ }
95
+ }
96
+ return null;
97
+ }
98
+ // ── Playful Loading Messages ────────────────────────────────────
99
+ const THINKING_MESSAGES = [
100
+ 'Digging through your notes...',
101
+ 'Following the thread...',
102
+ 'Connecting dots...',
103
+ 'Searching your knowledge...',
104
+ 'Reading between the lines...',
105
+ 'Pulling it all together...',
106
+ 'Tracing the connections...',
107
+ 'Going down the rabbit hole...',
108
+ ];
109
+ const INGESTING_MESSAGES = [
110
+ 'Absorbing new knowledge...',
111
+ 'Feeding your browzy...',
112
+ 'Adding to the collection...',
113
+ 'Digesting this one...',
114
+ 'Learning something new...',
115
+ ];
116
+ const COMPILING_MESSAGES = [
117
+ 'Weaving new knowledge in...',
118
+ 'Connecting the new with the old...',
119
+ 'Building bridges between ideas...',
120
+ 'Making sense of it all...',
121
+ 'Your browzy is growing...',
122
+ ];
123
+ const HEALTH_MESSAGES = [
124
+ 'Running a checkup...',
125
+ 'Inspecting the knowledge graph...',
126
+ 'Looking for loose threads...',
127
+ 'Checking the foundations...',
128
+ ];
129
+ export function getThinkingMessage() {
130
+ return THINKING_MESSAGES[Math.floor(Math.random() * THINKING_MESSAGES.length)];
131
+ }
132
+ export function getIngestingMessage() {
133
+ return INGESTING_MESSAGES[Math.floor(Math.random() * INGESTING_MESSAGES.length)];
134
+ }
135
+ export function getCompilingMessage() {
136
+ return COMPILING_MESSAGES[Math.floor(Math.random() * COMPILING_MESSAGES.length)];
137
+ }
138
+ export function getHealthMessage() {
139
+ return HEALTH_MESSAGES[Math.floor(Math.random() * HEALTH_MESSAGES.length)];
140
+ }
141
+ // ── Post-Action Rewards ─────────────────────────────────────────
142
+ export function getAddReward(title, articlesCreated, articlesUpdated, totalArticles) {
143
+ const parts = [];
144
+ if (articlesCreated > 0 && articlesUpdated > 0) {
145
+ parts.push(`Your browzy just learned about ${title}. ${articlesCreated} new articles, ${articlesUpdated} enriched.`);
146
+ }
147
+ else if (articlesCreated > 0) {
148
+ parts.push(`Your browzy just learned about ${title}. ${articlesCreated} new article${articlesCreated > 1 ? 's' : ''} created.`);
149
+ }
150
+ else if (articlesUpdated > 0) {
151
+ parts.push(`${title} deepened ${articlesUpdated} existing article${articlesUpdated > 1 ? 's' : ''}.`);
152
+ }
153
+ if (totalArticles > 0) {
154
+ parts.push(`Total: ${totalArticles} articles and growing.`);
155
+ }
156
+ return parts.join(' ');
157
+ }
158
+ export function getQueryReward(sourcesUsed) {
159
+ if (sourcesUsed === 0)
160
+ return '';
161
+ if (sourcesUsed === 1)
162
+ return 'Pulled from 1 article.';
163
+ if (sourcesUsed <= 3)
164
+ return `Pulled from ${sourcesUsed} articles.`;
165
+ return `Synthesized across ${sourcesUsed} articles. Your browzy is earning its keep.`;
166
+ }
167
+ // ── Exit Messages ───────────────────────────────────────────────
168
+ const EXIT_MESSAGES = [
169
+ "See you tomorrow. Your browzy will be here.",
170
+ "Good session. The knowledge compounds.",
171
+ "Until next time. Your research isn't going anywhere.",
172
+ "Saved. Your future self will thank you.",
173
+ "Closing up. Every session makes your browzy smarter.",
174
+ "Later. Don't forget — consistency beats intensity.",
175
+ ];
176
+ export function getExitMessage(streak) {
177
+ if (streak.currentStreak >= 3) {
178
+ return `${streak.currentStreak}-day streak. See you tomorrow — don't break the chain.`;
179
+ }
180
+ return EXIT_MESSAGES[Math.floor(Math.random() * EXIT_MESSAGES.length)];
181
+ }
182
+ // ── Empty State Messages ────────────────────────────────────────
183
+ export function getEmptyStateMessage() {
184
+ return "A blank canvas. Add your first source with /add and watch the magic happen.";
185
+ }
186
+ // ── Health Reward ───────────────────────────────────────────────
187
+ export function getHealthReward(errors, warnings, suggestions) {
188
+ const total = errors + warnings + suggestions;
189
+ if (total === 0)
190
+ return "Clean bill of health. Your browzy is in great shape.";
191
+ if (errors > 0)
192
+ return `Found ${errors} issue${errors > 1 ? 's' : ''} that need attention. Your browzy will be better for it.`;
193
+ if (warnings > 0)
194
+ return `Mostly healthy. ${warnings} small thing${warnings > 1 ? 's' : ''} to tighten up.`;
195
+ return `Looking good. ${suggestions} idea${suggestions > 1 ? 's' : ''} to make your browzy even better.`;
196
+ }
@@ -0,0 +1,20 @@
1
+ export declare class BrowzyRepl {
2
+ private rl;
3
+ private config;
4
+ private llm;
5
+ private outputFormat;
6
+ private autoSave;
7
+ constructor();
8
+ private drawPromptArea;
9
+ start(): void;
10
+ private normalizeInput;
11
+ private complete;
12
+ private handleSlashCommand;
13
+ private cmdAdd;
14
+ private parseMultipleSources;
15
+ private cmdAsk;
16
+ private cmdHealth;
17
+ private cmdRebuild;
18
+ private cmdFormat;
19
+ private cmdHelp;
20
+ }