sourcefire 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,607 @@
1
+ /* ============================================================
2
+ Sourcefire — Obsidian Forge
3
+ by Athar Wani
4
+ ============================================================ */
5
+
6
+ /* ── Tokens ──────────────────────────────────────────────── */
7
+ :root {
8
+ --bg: #09090B;
9
+ --surface-1: #111114;
10
+ --surface-2: #18181B;
11
+ --surface-3: #1F1F23;
12
+ --border: #27272A;
13
+ --border-hi: #3F3F46;
14
+ --text: #E4E4E7;
15
+ --text-dim: #A1A1AA;
16
+ --text-muted: #52525B;
17
+ --accent: #F59E0B;
18
+ --accent-dim: #B45309;
19
+ --accent-glow: rgba(245,158,11,0.08);
20
+ --blue: #3B82F6;
21
+ --green: #22C55E;
22
+ --red: #EF4444;
23
+ --purple: #A855F7;
24
+ --cyan: #06B6D4;
25
+
26
+ --font-sans: 'Satoshi', system-ui, -apple-system, sans-serif;
27
+ --font-display:'Clash Display', 'Satoshi', sans-serif;
28
+ --font-mono: 'JetBrains Mono', 'Fira Code', monospace;
29
+
30
+ --topbar-h: 52px;
31
+ --inputbar-h: auto;
32
+ --radius: 8px;
33
+ --radius-sm: 5px;
34
+ }
35
+
36
+ /* ── Reset ───────────────────────────────────────────────── */
37
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
38
+
39
+ html, body {
40
+ height: 100%;
41
+ overflow: hidden;
42
+ background: var(--bg);
43
+ color: var(--text);
44
+ font-family: var(--font-sans);
45
+ font-size: 14px;
46
+ line-height: 1.6;
47
+ -webkit-font-smoothing: antialiased;
48
+ }
49
+
50
+ .visually-hidden {
51
+ position: absolute; width: 1px; height: 1px;
52
+ overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
53
+ }
54
+
55
+ /* ── Scrollbar ───────────────────────────────────────────── */
56
+ ::-webkit-scrollbar { width: 5px; height: 5px; }
57
+ ::-webkit-scrollbar-track { background: transparent; }
58
+ ::-webkit-scrollbar-thumb { background: var(--border); border-radius: 3px; }
59
+ ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
60
+
61
+ /* ── Top Bar ─────────────────────────────────────────────── */
62
+ .topbar {
63
+ position: fixed; top: 0; left: 0; right: 0;
64
+ height: var(--topbar-h);
65
+ z-index: 100;
66
+ display: flex; align-items: center; justify-content: space-between;
67
+ padding: 0 20px;
68
+ background: var(--surface-1);
69
+ border-bottom: 1px solid var(--border);
70
+ }
71
+
72
+ .topbar__left, .topbar__right {
73
+ display: flex; align-items: center; gap: 12px;
74
+ }
75
+
76
+ .topbar__logo {
77
+ font-family: var(--font-display);
78
+ font-weight: 600;
79
+ font-size: 18px;
80
+ color: var(--text);
81
+ letter-spacing: -0.02em;
82
+ user-select: none;
83
+ }
84
+
85
+ .topbar__logo--accent { color: var(--accent); }
86
+
87
+ .topbar__logo--sub {
88
+ font-family: var(--font-sans);
89
+ font-size: 9px;
90
+ font-weight: 400;
91
+ color: var(--text-muted);
92
+ letter-spacing: 0.04em;
93
+ vertical-align: baseline;
94
+ position: relative;
95
+ top: 2px;
96
+ }
97
+
98
+ .lang-badge {
99
+ font-family: var(--font-mono);
100
+ font-size: 10px;
101
+ font-weight: 500;
102
+ color: var(--accent);
103
+ background: var(--accent-glow);
104
+ border: 1px solid var(--accent-dim);
105
+ border-radius: 3px;
106
+ padding: 1px 7px;
107
+ text-transform: uppercase;
108
+ letter-spacing: 0.06em;
109
+ }
110
+
111
+ .index-badge {
112
+ font-family: var(--font-mono);
113
+ font-size: 10px;
114
+ font-weight: 500;
115
+ color: var(--text-muted);
116
+ background: var(--surface-2);
117
+ border: 1px solid var(--border);
118
+ border-radius: 3px;
119
+ padding: 2px 8px;
120
+ text-transform: uppercase;
121
+ letter-spacing: 0.04em;
122
+ transition: all 200ms;
123
+ }
124
+
125
+ .index-badge.is-ready { color: var(--green); border-color: var(--green); }
126
+ .index-badge.is-error { color: var(--red); border-color: var(--red); }
127
+
128
+ .model-select {
129
+ appearance: none;
130
+ background: var(--surface-2);
131
+ color: var(--text-dim);
132
+ border: 1px solid var(--border);
133
+ border-radius: var(--radius-sm);
134
+ padding: 4px 24px 4px 10px;
135
+ font-family: var(--font-mono);
136
+ font-size: 11px;
137
+ cursor: pointer;
138
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M0 0l5 6 5-6z' fill='%2352525B'/%3E%3C/svg%3E");
139
+ background-repeat: no-repeat;
140
+ background-position: right 7px center;
141
+ outline: none;
142
+ transition: border-color 200ms;
143
+ }
144
+
145
+ .model-select:hover, .model-select:focus { border-color: var(--accent); color: var(--text); }
146
+
147
+ /* ── Main / Chat ─────────────────────────────────────────── */
148
+ .main {
149
+ position: fixed;
150
+ top: var(--topbar-h);
151
+ bottom: 0;
152
+ left: 0; right: 0;
153
+ overflow: hidden;
154
+ display: flex;
155
+ justify-content: center;
156
+ }
157
+
158
+ .chat {
159
+ width: 100%;
160
+ max-width: 820px;
161
+ overflow-y: auto;
162
+ padding: 24px 20px 200px;
163
+ display: flex;
164
+ flex-direction: column;
165
+ gap: 8px;
166
+ }
167
+
168
+ /* ── Welcome ─────────────────────────────────────────────── */
169
+ .welcome {
170
+ display: flex;
171
+ flex-direction: column;
172
+ align-items: center;
173
+ justify-content: center;
174
+ text-align: center;
175
+ padding: 80px 20px 40px;
176
+ gap: 12px;
177
+ }
178
+
179
+ .welcome__icon {
180
+ font-size: 28px;
181
+ color: var(--accent);
182
+ opacity: 0.6;
183
+ }
184
+
185
+ .welcome__title {
186
+ font-family: var(--font-display);
187
+ font-weight: 700;
188
+ font-size: 36px;
189
+ color: var(--text);
190
+ letter-spacing: -0.03em;
191
+ }
192
+
193
+ .welcome__title--accent { color: var(--accent); }
194
+
195
+ .welcome__title--sub {
196
+ font-family: var(--font-sans);
197
+ font-size: 12px;
198
+ font-weight: 400;
199
+ color: var(--text-muted);
200
+ letter-spacing: 0.04em;
201
+ vertical-align: baseline;
202
+ position: relative;
203
+ top: 3px;
204
+ }
205
+
206
+ .welcome__sub {
207
+ color: var(--text-dim);
208
+ font-size: 14px;
209
+ max-width: 420px;
210
+ }
211
+
212
+ .welcome__modes {
213
+ display: flex;
214
+ flex-direction: column;
215
+ gap: 6px;
216
+ margin-top: 16px;
217
+ }
218
+
219
+ .welcome__mode {
220
+ font-size: 12px;
221
+ color: var(--text-muted);
222
+ display: flex;
223
+ align-items: center;
224
+ gap: 8px;
225
+ }
226
+
227
+ /* ── Dots ────────────────────────────────────────────────── */
228
+ .dot {
229
+ display: inline-block;
230
+ width: 7px; height: 7px;
231
+ border-radius: 50%;
232
+ flex-shrink: 0;
233
+ }
234
+
235
+ .dot--debug { background: var(--red); }
236
+ .dot--feature { background: var(--purple); }
237
+ .dot--explain { background: var(--blue); }
238
+
239
+ /* ── Messages ────────────────────────────────────────────── */
240
+ @keyframes msgIn {
241
+ from { opacity: 0; transform: translateY(8px); }
242
+ to { opacity: 1; transform: translateY(0); }
243
+ }
244
+
245
+ .msg {
246
+ animation: msgIn 250ms ease forwards;
247
+ border-radius: var(--radius);
248
+ font-size: 14px;
249
+ line-height: 1.7;
250
+ }
251
+
252
+ .msg--user {
253
+ align-self: flex-end;
254
+ max-width: 75%;
255
+ background: var(--accent-glow);
256
+ border: 1px solid rgba(245,158,11,0.2);
257
+ padding: 10px 14px;
258
+ color: var(--text);
259
+ }
260
+
261
+ .msg--assistant {
262
+ align-self: flex-start;
263
+ max-width: 100%;
264
+ padding: 4px 0;
265
+ }
266
+
267
+ /* ── Status Timeline ─────────────────────────────────────── */
268
+ .status-timeline {
269
+ display: flex;
270
+ flex-wrap: wrap;
271
+ gap: 6px;
272
+ padding: 4px 0 8px;
273
+ }
274
+
275
+ @keyframes statusIn {
276
+ from { opacity: 0; transform: scale(0.9) translateY(4px); }
277
+ to { opacity: 1; transform: scale(1) translateY(0); }
278
+ }
279
+
280
+ .status-tag {
281
+ display: inline-flex;
282
+ align-items: center;
283
+ gap: 5px;
284
+ font-family: var(--font-mono);
285
+ font-size: 11px;
286
+ font-weight: 500;
287
+ padding: 3px 10px;
288
+ border-radius: 20px;
289
+ border: 1px solid var(--border);
290
+ background: var(--surface-2);
291
+ color: var(--text-dim);
292
+ animation: statusIn 200ms ease forwards;
293
+ white-space: nowrap;
294
+ }
295
+
296
+ .status-tag__icon {
297
+ font-size: 10px;
298
+ flex-shrink: 0;
299
+ }
300
+
301
+ /* Active / pulsing states */
302
+ @keyframes statusPulse {
303
+ 0%, 100% { opacity: 0.6; }
304
+ 50% { opacity: 1; }
305
+ }
306
+
307
+ .status-tag--active {
308
+ border-color: var(--accent-dim);
309
+ color: var(--accent);
310
+ animation: statusIn 200ms ease forwards, statusPulse 1.8s ease-in-out infinite;
311
+ }
312
+
313
+ .status-tag--done {
314
+ border-color: var(--green);
315
+ color: var(--green);
316
+ }
317
+
318
+ .status-tag--tool {
319
+ border-color: var(--cyan);
320
+ color: var(--cyan);
321
+ background: rgba(6,182,212,0.06);
322
+ }
323
+
324
+ .status-tag--context {
325
+ border-color: var(--blue);
326
+ color: var(--blue);
327
+ }
328
+
329
+ .status-tag--error {
330
+ border-color: var(--red);
331
+ color: var(--red);
332
+ }
333
+
334
+ /* ── Thinking indicator ──────────────────────────────────── */
335
+ @keyframes pulse {
336
+ 0%, 100% { opacity: 0.3; }
337
+ 50% { opacity: 1; }
338
+ }
339
+
340
+ .thinking {
341
+ display: flex; align-items: center; gap: 4px;
342
+ padding: 12px 0;
343
+ }
344
+
345
+ .thinking__dot {
346
+ width: 5px; height: 5px;
347
+ border-radius: 50%;
348
+ background: var(--text-muted);
349
+ animation: pulse 1.2s ease-in-out infinite;
350
+ }
351
+
352
+ .thinking__dot:nth-child(2) { animation-delay: 0.2s; }
353
+ .thinking__dot:nth-child(3) { animation-delay: 0.4s; }
354
+
355
+ /* ── Message content ─────────────────────────────────────── */
356
+ .msg code {
357
+ font-family: var(--font-mono);
358
+ font-size: 12px;
359
+ background: var(--surface-3);
360
+ color: var(--accent);
361
+ padding: 2px 6px;
362
+ border-radius: 3px;
363
+ }
364
+
365
+ .msg pre {
366
+ background: var(--surface-1);
367
+ border: 1px solid var(--border);
368
+ border-radius: var(--radius-sm);
369
+ padding: 14px;
370
+ margin: 10px 0;
371
+ overflow-x: auto;
372
+ font-size: 12px;
373
+ line-height: 1.55;
374
+ }
375
+
376
+ .msg pre code {
377
+ background: transparent;
378
+ color: inherit;
379
+ padding: 0;
380
+ font-size: inherit;
381
+ }
382
+
383
+ .msg p + p { margin-top: 10px; }
384
+ .msg ul, .msg ol { padding-left: 20px; margin: 8px 0; }
385
+ .msg li { margin: 3px 0; }
386
+ .msg strong { color: var(--text); font-weight: 600; }
387
+
388
+ .msg a {
389
+ color: var(--blue);
390
+ text-decoration: none;
391
+ }
392
+ .msg a:hover { text-decoration: underline; }
393
+
394
+ /* ── File refs ───────────────────────────────────────────── */
395
+ .file-ref {
396
+ color: var(--accent) !important;
397
+ text-decoration: underline !important;
398
+ text-decoration-style: dotted !important;
399
+ text-underline-offset: 2px;
400
+ cursor: pointer;
401
+ transition: color 150ms;
402
+ }
403
+
404
+ .file-ref:hover {
405
+ color: var(--text) !important;
406
+ text-decoration-style: solid !important;
407
+ }
408
+
409
+ /* ── Input Bar ───────────────────────────────────────────── */
410
+ .inputbar {
411
+ position: fixed;
412
+ bottom: 0; left: 0; right: 0;
413
+ z-index: 100;
414
+ display: flex;
415
+ justify-content: center;
416
+ padding: 0 20px 16px;
417
+ pointer-events: none;
418
+ }
419
+
420
+ .inputbar__inner {
421
+ width: 100%;
422
+ max-width: 820px;
423
+ pointer-events: auto;
424
+ background: var(--surface-1);
425
+ border: 1px solid var(--border);
426
+ border-radius: 12px;
427
+ overflow: hidden;
428
+ box-shadow: 0 -8px 40px rgba(0,0,0,0.4);
429
+ }
430
+
431
+ .mode-pills {
432
+ display: flex;
433
+ gap: 2px;
434
+ padding: 8px 12px 0;
435
+ }
436
+
437
+ .mode-pill {
438
+ display: flex;
439
+ align-items: center;
440
+ gap: 5px;
441
+ padding: 5px 12px;
442
+ background: transparent;
443
+ border: 1px solid transparent;
444
+ border-radius: 20px;
445
+ color: var(--text-muted);
446
+ font-family: var(--font-sans);
447
+ font-size: 12px;
448
+ font-weight: 500;
449
+ cursor: pointer;
450
+ transition: all 200ms;
451
+ user-select: none;
452
+ }
453
+
454
+ .mode-pill:hover { color: var(--text-dim); }
455
+
456
+ .mode-pill--active {
457
+ color: var(--text);
458
+ background: var(--surface-3);
459
+ border-color: var(--border);
460
+ }
461
+
462
+ .input-row {
463
+ display: flex;
464
+ align-items: flex-end;
465
+ gap: 8px;
466
+ padding: 8px 12px;
467
+ }
468
+
469
+ .query-textarea {
470
+ flex: 1;
471
+ resize: none;
472
+ background: transparent;
473
+ color: var(--text);
474
+ border: none;
475
+ padding: 8px 4px;
476
+ font-family: var(--font-sans);
477
+ font-size: 14px;
478
+ line-height: 1.5;
479
+ outline: none;
480
+ min-height: 24px;
481
+ max-height: 200px;
482
+ }
483
+
484
+ .query-textarea::placeholder { color: var(--text-muted); }
485
+
486
+ .send-btn {
487
+ flex-shrink: 0;
488
+ width: 36px; height: 36px;
489
+ display: flex;
490
+ align-items: center;
491
+ justify-content: center;
492
+ background: var(--accent);
493
+ color: var(--bg);
494
+ border: none;
495
+ border-radius: 8px;
496
+ cursor: pointer;
497
+ transition: opacity 150ms, transform 100ms;
498
+ }
499
+
500
+ .send-btn:hover { opacity: 0.85; }
501
+ .send-btn:active { transform: scale(0.92); }
502
+ .send-btn:disabled { opacity: 0.25; cursor: not-allowed; transform: none; }
503
+
504
+ .inputbar__footer {
505
+ display: flex;
506
+ justify-content: space-between;
507
+ padding: 4px 14px 8px;
508
+ font-size: 10px;
509
+ color: var(--text-muted);
510
+ }
511
+
512
+ .retrieval-stats { font-family: var(--font-mono); }
513
+ .credit { opacity: 0.5; }
514
+
515
+ /* ── Source Modal ────────────────────────────────────────── */
516
+ .source-modal {
517
+ position: fixed; inset: 0;
518
+ z-index: 200;
519
+ display: flex; align-items: center; justify-content: center;
520
+ }
521
+
522
+ .source-modal[hidden] { display: none; }
523
+
524
+ .source-modal__backdrop {
525
+ position: absolute; inset: 0;
526
+ background: rgba(0,0,0,0.65);
527
+ backdrop-filter: blur(6px);
528
+ }
529
+
530
+ .source-modal__panel {
531
+ position: relative;
532
+ width: 90vw; max-width: 940px;
533
+ height: 80vh;
534
+ background: var(--surface-1);
535
+ border: 1px solid var(--border);
536
+ border-radius: 12px;
537
+ display: flex; flex-direction: column;
538
+ overflow: hidden;
539
+ box-shadow: 0 24px 80px rgba(0,0,0,0.5);
540
+ }
541
+
542
+ .source-modal__header {
543
+ display: flex; align-items: center; justify-content: space-between;
544
+ padding: 10px 16px;
545
+ background: var(--surface-2);
546
+ border-bottom: 1px solid var(--border);
547
+ flex-shrink: 0;
548
+ }
549
+
550
+ .source-modal__title {
551
+ font-family: var(--font-mono);
552
+ font-size: 12px;
553
+ font-weight: 500;
554
+ color: var(--accent);
555
+ overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
556
+ }
557
+
558
+ .source-modal__close {
559
+ background: none; border: none;
560
+ color: var(--text-muted);
561
+ font-size: 20px;
562
+ cursor: pointer;
563
+ padding: 0 4px;
564
+ line-height: 1;
565
+ transition: color 150ms;
566
+ }
567
+
568
+ .source-modal__close:hover { color: var(--text); }
569
+
570
+ .source-modal__body {
571
+ flex: 1;
572
+ overflow: auto;
573
+ font-family: var(--font-mono);
574
+ font-size: 12px;
575
+ line-height: 1.55;
576
+ }
577
+
578
+ .source-modal__body pre { margin: 0; padding: 12px 0; }
579
+
580
+ .source-line { display: flex; padding: 0 16px; }
581
+ .source-line:hover { background: var(--surface-2); }
582
+
583
+ .source-line__num {
584
+ flex-shrink: 0;
585
+ width: 48px;
586
+ text-align: right;
587
+ padding-right: 16px;
588
+ color: var(--text-muted);
589
+ user-select: none;
590
+ opacity: 0.5;
591
+ }
592
+
593
+ .source-line__code {
594
+ flex: 1;
595
+ white-space: pre;
596
+ }
597
+
598
+ /* ── highlight.js override ───────────────────────────────── */
599
+ .hljs { background: transparent !important; padding: 0 !important; }
600
+
601
+ /* ── Responsive ──────────────────────────────────────────── */
602
+ @media (max-width: 640px) {
603
+ .chat { padding: 16px 12px 200px; }
604
+ .inputbar { padding: 0 8px 10px; }
605
+ .welcome__title { font-size: 28px; }
606
+ .msg--user { max-width: 88%; }
607
+ }
sourcefire/watcher.py ADDED
@@ -0,0 +1,105 @@
1
+ """File watcher for live incremental re-indexing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import fnmatch
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from watchfiles import awatch, Change
11
+
12
+ from sourcefire.config import SourcefireConfig
13
+ from sourcefire.db import delete_file_chunks
14
+ from sourcefire.indexer.language_profiles import LanguageProfile
15
+ from sourcefire.indexer.pipeline import index_files
16
+ from sourcefire.retriever.graph import ImportGraph
17
+
18
+
19
+ def _should_watch(rel_path: str, config: SourcefireConfig) -> bool:
20
+ """Return True if the file matches include patterns and not exclude patterns."""
21
+ for pattern in config.exclude:
22
+ if fnmatch.fnmatch(rel_path, pattern):
23
+ return False
24
+
25
+ if not config.include:
26
+ return True
27
+
28
+ for pattern in config.include:
29
+ if fnmatch.fnmatch(rel_path, pattern):
30
+ return True
31
+
32
+ return False
33
+
34
+
35
+ async def watch_and_reindex(
36
+ config: SourcefireConfig,
37
+ collection: Any,
38
+ graph: ImportGraph,
39
+ profile: LanguageProfile | None,
40
+ ) -> None:
41
+ """Watch project directory and incrementally re-index changed files.
42
+
43
+ Runs as a background asyncio task. Batches changes within a 1-second
44
+ debounce window.
45
+ """
46
+ project_dir = config.project_dir
47
+
48
+ print(f"[watcher] Watching {project_dir} for changes...")
49
+
50
+ async for changes in awatch(
51
+ project_dir,
52
+ debounce=1000,
53
+ recursive=True,
54
+ step=200,
55
+ ):
56
+ changed_files: list[Path] = []
57
+ deleted_files: list[str] = []
58
+
59
+ for change_type, path_str in changes:
60
+ path = Path(path_str)
61
+ try:
62
+ rel = path.relative_to(project_dir).as_posix()
63
+ except ValueError:
64
+ continue
65
+
66
+ if not _should_watch(rel, config):
67
+ continue
68
+
69
+ if change_type in (Change.added, Change.modified):
70
+ if path.is_file():
71
+ changed_files.append(path)
72
+ elif change_type == Change.deleted:
73
+ deleted_files.append(rel)
74
+
75
+ # Handle deletions
76
+ for rel in deleted_files:
77
+ try:
78
+ loop = asyncio.get_event_loop()
79
+ await loop.run_in_executor(None, delete_file_chunks, collection, rel)
80
+ graph.remove_file(rel)
81
+ print(f"[watcher] Removed: {rel}")
82
+ except Exception as exc:
83
+ print(f"[watcher] Error removing {rel}: {exc}")
84
+
85
+ # Handle additions/modifications
86
+ if changed_files:
87
+ try:
88
+ loop = asyncio.get_event_loop()
89
+ file_imports = await loop.run_in_executor(
90
+ None, index_files, collection, changed_files, config, profile
91
+ )
92
+
93
+ # Update graph
94
+ for file_path in changed_files:
95
+ rel = file_path.relative_to(project_dir).as_posix()
96
+ graph.remove_file(rel)
97
+
98
+ for source_file, imports in file_imports.items():
99
+ for imp in imports:
100
+ graph.add_edge(source_file, ImportGraph._resolve_import(source_file, imp))
101
+
102
+ rel_names = [p.relative_to(project_dir).as_posix() for p in changed_files]
103
+ print(f"[watcher] Re-indexed: {', '.join(rel_names)}")
104
+ except Exception as exc:
105
+ print(f"[watcher] Error re-indexing: {exc}")