ei-tui 0.1.3

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 (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/package.json +63 -0
  4. package/src/README.md +96 -0
  5. package/src/cli/README.md +47 -0
  6. package/src/cli/commands/facts.ts +25 -0
  7. package/src/cli/commands/people.ts +25 -0
  8. package/src/cli/commands/quotes.ts +19 -0
  9. package/src/cli/commands/topics.ts +25 -0
  10. package/src/cli/commands/traits.ts +25 -0
  11. package/src/cli/retrieval.ts +269 -0
  12. package/src/cli.ts +176 -0
  13. package/src/core/AGENTS.md +104 -0
  14. package/src/core/embedding-service.ts +241 -0
  15. package/src/core/handlers/index.ts +1057 -0
  16. package/src/core/index.ts +4 -0
  17. package/src/core/llm-client.ts +265 -0
  18. package/src/core/model-context-windows.ts +49 -0
  19. package/src/core/orchestrators/ceremony.ts +500 -0
  20. package/src/core/orchestrators/extraction-chunker.ts +138 -0
  21. package/src/core/orchestrators/human-extraction.ts +457 -0
  22. package/src/core/orchestrators/index.ts +28 -0
  23. package/src/core/orchestrators/persona-generation.ts +76 -0
  24. package/src/core/orchestrators/persona-topics.ts +117 -0
  25. package/src/core/personas/index.ts +5 -0
  26. package/src/core/personas/opencode-agent.ts +81 -0
  27. package/src/core/processor.ts +1413 -0
  28. package/src/core/queue-processor.ts +197 -0
  29. package/src/core/state/checkpoints.ts +68 -0
  30. package/src/core/state/human.ts +176 -0
  31. package/src/core/state/index.ts +5 -0
  32. package/src/core/state/personas.ts +217 -0
  33. package/src/core/state/queue.ts +144 -0
  34. package/src/core/state-manager.ts +347 -0
  35. package/src/core/types.ts +421 -0
  36. package/src/core/utils/decay.ts +33 -0
  37. package/src/index.ts +1 -0
  38. package/src/integrations/opencode/importer.ts +896 -0
  39. package/src/integrations/opencode/index.ts +16 -0
  40. package/src/integrations/opencode/json-reader.ts +304 -0
  41. package/src/integrations/opencode/reader-factory.ts +35 -0
  42. package/src/integrations/opencode/sqlite-reader.ts +189 -0
  43. package/src/integrations/opencode/types.ts +244 -0
  44. package/src/prompts/AGENTS.md +62 -0
  45. package/src/prompts/ceremony/description-check.ts +47 -0
  46. package/src/prompts/ceremony/expire.ts +30 -0
  47. package/src/prompts/ceremony/explore.ts +60 -0
  48. package/src/prompts/ceremony/index.ts +11 -0
  49. package/src/prompts/ceremony/types.ts +42 -0
  50. package/src/prompts/generation/descriptions.ts +91 -0
  51. package/src/prompts/generation/index.ts +15 -0
  52. package/src/prompts/generation/persona.ts +155 -0
  53. package/src/prompts/generation/seeds.ts +31 -0
  54. package/src/prompts/generation/types.ts +47 -0
  55. package/src/prompts/heartbeat/check.ts +179 -0
  56. package/src/prompts/heartbeat/ei.ts +208 -0
  57. package/src/prompts/heartbeat/index.ts +15 -0
  58. package/src/prompts/heartbeat/types.ts +70 -0
  59. package/src/prompts/human/fact-scan.ts +152 -0
  60. package/src/prompts/human/index.ts +32 -0
  61. package/src/prompts/human/item-match.ts +74 -0
  62. package/src/prompts/human/item-update.ts +322 -0
  63. package/src/prompts/human/person-scan.ts +115 -0
  64. package/src/prompts/human/topic-scan.ts +135 -0
  65. package/src/prompts/human/trait-scan.ts +115 -0
  66. package/src/prompts/human/types.ts +127 -0
  67. package/src/prompts/index.ts +90 -0
  68. package/src/prompts/message-utils.ts +39 -0
  69. package/src/prompts/persona/index.ts +16 -0
  70. package/src/prompts/persona/topics-match.ts +69 -0
  71. package/src/prompts/persona/topics-scan.ts +98 -0
  72. package/src/prompts/persona/topics-update.ts +157 -0
  73. package/src/prompts/persona/traits.ts +117 -0
  74. package/src/prompts/persona/types.ts +74 -0
  75. package/src/prompts/response/index.ts +147 -0
  76. package/src/prompts/response/sections.ts +355 -0
  77. package/src/prompts/response/types.ts +38 -0
  78. package/src/prompts/validation/ei.ts +93 -0
  79. package/src/prompts/validation/index.ts +6 -0
  80. package/src/prompts/validation/types.ts +22 -0
  81. package/src/storage/crypto.ts +96 -0
  82. package/src/storage/index.ts +5 -0
  83. package/src/storage/interface.ts +9 -0
  84. package/src/storage/local.ts +79 -0
  85. package/src/storage/merge.ts +69 -0
  86. package/src/storage/remote.ts +145 -0
  87. package/src/templates/welcome.ts +91 -0
  88. package/tui/README.md +62 -0
  89. package/tui/bunfig.toml +4 -0
  90. package/tui/src/app.tsx +55 -0
  91. package/tui/src/commands/archive.tsx +93 -0
  92. package/tui/src/commands/context.tsx +124 -0
  93. package/tui/src/commands/delete.tsx +71 -0
  94. package/tui/src/commands/details.tsx +41 -0
  95. package/tui/src/commands/editor.tsx +46 -0
  96. package/tui/src/commands/help.tsx +12 -0
  97. package/tui/src/commands/me.tsx +145 -0
  98. package/tui/src/commands/model.ts +47 -0
  99. package/tui/src/commands/new.ts +31 -0
  100. package/tui/src/commands/pause.ts +46 -0
  101. package/tui/src/commands/persona.tsx +58 -0
  102. package/tui/src/commands/provider.tsx +124 -0
  103. package/tui/src/commands/quit.ts +22 -0
  104. package/tui/src/commands/quotes.tsx +172 -0
  105. package/tui/src/commands/registry.test.ts +137 -0
  106. package/tui/src/commands/registry.ts +130 -0
  107. package/tui/src/commands/resume.ts +39 -0
  108. package/tui/src/commands/setsync.tsx +43 -0
  109. package/tui/src/commands/settings.tsx +83 -0
  110. package/tui/src/components/ConfirmOverlay.tsx +51 -0
  111. package/tui/src/components/ConflictOverlay.tsx +78 -0
  112. package/tui/src/components/HelpOverlay.tsx +69 -0
  113. package/tui/src/components/Layout.tsx +24 -0
  114. package/tui/src/components/MessageList.tsx +174 -0
  115. package/tui/src/components/PersonaListOverlay.tsx +186 -0
  116. package/tui/src/components/PromptInput.tsx +145 -0
  117. package/tui/src/components/ProviderListOverlay.tsx +208 -0
  118. package/tui/src/components/QuotesOverlay.tsx +157 -0
  119. package/tui/src/components/Sidebar.tsx +95 -0
  120. package/tui/src/components/StatusBar.tsx +77 -0
  121. package/tui/src/components/WelcomeOverlay.tsx +73 -0
  122. package/tui/src/context/ei.tsx +623 -0
  123. package/tui/src/context/keyboard.tsx +164 -0
  124. package/tui/src/context/overlay.tsx +53 -0
  125. package/tui/src/index.tsx +8 -0
  126. package/tui/src/storage/file.ts +185 -0
  127. package/tui/src/util/duration.ts +32 -0
  128. package/tui/src/util/editor.ts +188 -0
  129. package/tui/src/util/logger.ts +109 -0
  130. package/tui/src/util/persona-editor.tsx +181 -0
  131. package/tui/src/util/provider-editor.tsx +168 -0
  132. package/tui/src/util/syntax.ts +35 -0
  133. package/tui/src/util/yaml-serializers.ts +755 -0
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Cross-platform embedding service using all-MiniLM-L6-v2 (384-dim).
3
+ * Bun/Node: fastembed | Browser: @huggingface/transformers (CDN)
4
+ *
5
+ * Both implementations are loaded lazily to avoid bundler issues.
6
+ */
7
+
8
+ export const EMBEDDING_DIMENSION = 384;
9
+
10
+ export function cosineSimilarity(a: number[], b: number[]): number {
11
+ if (a.length !== b.length) {
12
+ throw new Error(`Vector dimension mismatch: ${a.length} vs ${b.length}`);
13
+ }
14
+
15
+ let dot = 0, normA = 0, normB = 0;
16
+ for (let i = 0; i < a.length; i++) {
17
+ dot += a[i] * b[i];
18
+ normA += a[i] * a[i];
19
+ normB += b[i] * b[i];
20
+ }
21
+
22
+ const magnitude = Math.sqrt(normA) * Math.sqrt(normB);
23
+ if (magnitude === 0) return 0;
24
+
25
+ return dot / magnitude;
26
+ }
27
+
28
+ export function findTopK<T extends { id: string; embedding?: number[] }>(
29
+ queryVector: number[],
30
+ candidates: T[],
31
+ k: number
32
+ ): Array<{ item: T; similarity: number }> {
33
+ const scored = candidates
34
+ .filter(c => c.embedding && c.embedding.length === EMBEDDING_DIMENSION)
35
+ .map(item => ({
36
+ item,
37
+ similarity: cosineSimilarity(queryVector, item.embedding!),
38
+ }))
39
+ .sort((a, b) => b.similarity - a.similarity);
40
+
41
+ return scored.slice(0, k);
42
+ }
43
+
44
+ export interface EmbeddingService {
45
+ embed(text: string): Promise<number[]>;
46
+ embedBatch(texts: string[]): Promise<number[][]>;
47
+ isReady(): boolean;
48
+ }
49
+
50
+ export function getItemEmbeddingText(item: { name: string; description?: string }): string {
51
+ if (item.description) {
52
+ return `${item.name}: ${item.description}`;
53
+ }
54
+ return item.name;
55
+ }
56
+
57
+ export function needsEmbeddingUpdate(
58
+ existing: { name: string; description?: string } | undefined,
59
+ incoming: { name: string; description?: string }
60
+ ): boolean {
61
+ if (!existing) return true;
62
+ return existing.name !== incoming.name || existing.description !== incoming.description;
63
+ }
64
+
65
+ export function needsQuoteEmbeddingUpdate(
66
+ existing: { text: string } | undefined,
67
+ incoming: { text: string }
68
+ ): boolean {
69
+ if (!existing) return true;
70
+ return existing.text !== incoming.text;
71
+ }
72
+
73
+ export async function computeDataItemEmbedding(
74
+ item: { name: string; description?: string }
75
+ ): Promise<number[] | undefined> {
76
+ try {
77
+ const service = getEmbeddingService();
78
+ const text = getItemEmbeddingText(item);
79
+ return await service.embed(text);
80
+ } catch (err) {
81
+ console.warn(`[computeDataItemEmbedding] Failed for "${item.name}":`, err);
82
+ return undefined;
83
+ }
84
+ }
85
+
86
+ export async function computeQuoteEmbedding(text: string): Promise<number[] | undefined> {
87
+ try {
88
+ const service = getEmbeddingService();
89
+ return await service.embed(text);
90
+ } catch (err) {
91
+ console.warn(`[computeQuoteEmbedding] Failed for "${text.slice(0, 30)}...":`, err);
92
+ return undefined;
93
+ }
94
+ }
95
+
96
+ // =============================================================================
97
+ // FACTORY - Lazy loading based on environment
98
+ // =============================================================================
99
+
100
+ let defaultService: EmbeddingService | null = null;
101
+
102
+ function isBrowserEnvironment(): boolean {
103
+ const hasProcess = typeof process !== "undefined" && typeof process.versions !== "undefined";
104
+ const hasBun = hasProcess && typeof process.versions.bun !== "undefined";
105
+ const hasNode = hasProcess && typeof process.versions.node !== "undefined";
106
+ const hasDocument = typeof document !== "undefined";
107
+
108
+ const isTUI = (hasBun || hasNode) && !hasDocument;
109
+ return !isTUI && hasDocument;
110
+ }
111
+
112
+ export function getEmbeddingService(): EmbeddingService {
113
+ if (defaultService) return defaultService;
114
+
115
+ if (isBrowserEnvironment()) {
116
+ defaultService = createBrowserService();
117
+ } else {
118
+ defaultService = createBunService();
119
+ }
120
+
121
+ return defaultService;
122
+ }
123
+
124
+ // =============================================================================
125
+ // BROWSER IMPLEMENTATION (loaded from CDN, never bundled)
126
+ // =============================================================================
127
+
128
+ const HF_TRANSFORMERS_CDN = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.5.1';
129
+
130
+ function createBrowserService(): EmbeddingService {
131
+ let embedder: any = null;
132
+ let embedderPromise: Promise<any> | null = null;
133
+ let ready = false;
134
+
135
+ async function getEmbedder(): Promise<any> {
136
+ if (embedder) return embedder;
137
+ if (embedderPromise) return embedderPromise;
138
+
139
+ embedderPromise = (async () => {
140
+ const { pipeline, env } = await import(/* @vite-ignore */ HF_TRANSFORMERS_CDN);
141
+ env.allowLocalModels = false;
142
+ embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2', {
143
+ dtype: 'fp32',
144
+ });
145
+ return embedder;
146
+ })();
147
+
148
+ return embedderPromise;
149
+ }
150
+
151
+ return {
152
+ async embed(text: string): Promise<number[]> {
153
+ const model = await getEmbedder();
154
+ const result = await model(text, { pooling: 'mean', normalize: true });
155
+ ready = true;
156
+ return Array.from(result.data as Float32Array);
157
+ },
158
+
159
+ async embedBatch(texts: string[]): Promise<number[][]> {
160
+ const results: number[][] = [];
161
+ for (const text of texts) {
162
+ results.push(await this.embed(text));
163
+ }
164
+ return results;
165
+ },
166
+
167
+ isReady(): boolean {
168
+ return ready;
169
+ }
170
+ };
171
+ }
172
+
173
+ // =============================================================================
174
+ // BUN/NODE IMPLEMENTATION (uses fastembed, loaded dynamically)
175
+ // =============================================================================
176
+
177
+ const FASTEMBED_MODULE = 'fastembed';
178
+
179
+ function createBunService(): EmbeddingService {
180
+ let embedder: any = null;
181
+ let embedderPromise: Promise<any> | null = null;
182
+ let ready = false;
183
+
184
+ function parseFastembedVector(vec: any): number[] {
185
+ if (vec && typeof vec === 'object' && '0' in vec) {
186
+ const arr: number[] = [];
187
+ const keys = Object.keys(vec).filter(k => !isNaN(Number(k)));
188
+ for (let i = 0; i < keys.length; i++) {
189
+ arr.push(vec[i]);
190
+ }
191
+ return arr;
192
+ }
193
+ if (ArrayBuffer.isView(vec)) {
194
+ return Array.from(vec as Float32Array);
195
+ }
196
+ return Array.from(vec as number[]);
197
+ }
198
+
199
+ async function getEmbedder(): Promise<any> {
200
+ if (embedder) return embedder;
201
+ if (embedderPromise) return embedderPromise;
202
+
203
+ embedderPromise = (async () => {
204
+ const mod = await import(/* @vite-ignore */ FASTEMBED_MODULE);
205
+ embedder = await mod.FlagEmbedding.init({ model: mod.EmbeddingModel.AllMiniLML6V2 });
206
+ return embedder;
207
+ })();
208
+
209
+ return embedderPromise;
210
+ }
211
+
212
+ return {
213
+ async embed(text: string): Promise<number[]> {
214
+ const vectors = await this.embedBatch([text]);
215
+ return vectors[0];
216
+ },
217
+
218
+ async embedBatch(texts: string[]): Promise<number[][]> {
219
+ if (texts.length === 0) return [];
220
+
221
+ const model = await getEmbedder();
222
+ const embeddings = model.embed(texts);
223
+
224
+ const result: number[][] = [];
225
+ for await (const batch of embeddings) {
226
+ if (Array.isArray(batch)) {
227
+ for (const vec of batch) {
228
+ result.push(parseFastembedVector(vec));
229
+ }
230
+ }
231
+ }
232
+
233
+ ready = true;
234
+ return result;
235
+ },
236
+
237
+ isReady(): boolean {
238
+ return ready;
239
+ }
240
+ };
241
+ }