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,421 @@
1
+ /**
2
+ * EI V1 Core Types
3
+ * Source of truth: CONTRACTS.md
4
+ */
5
+
6
+ // =============================================================================
7
+ // ENUMS
8
+ // =============================================================================
9
+
10
+ export enum ContextStatus {
11
+ Default = "default",
12
+ Always = "always",
13
+ Never = "never",
14
+ }
15
+
16
+ export enum ValidationLevel {
17
+ None = "none", // Fresh data, never acknowledged
18
+ Ei = "ei", // Ei mentioned it to user (don't mention again)
19
+ Human = "human", // User explicitly confirmed (locked)
20
+ }
21
+
22
+ export enum LLMRequestType {
23
+ Response = "response",
24
+ JSON = "json",
25
+ Raw = "raw",
26
+ }
27
+
28
+ export enum LLMPriority {
29
+ High = "high",
30
+ Normal = "normal",
31
+ Low = "low",
32
+ }
33
+
34
+ export enum LLMNextStep {
35
+ HandlePersonaResponse = "handlePersonaResponse",
36
+ HandlePersonaGeneration = "handlePersonaGeneration",
37
+ HandlePersonaDescriptions = "handlePersonaDescriptions",
38
+ HandleHumanFactScan = "handleHumanFactScan",
39
+ HandleHumanTraitScan = "handleHumanTraitScan",
40
+ HandleHumanTopicScan = "handleHumanTopicScan",
41
+ HandleHumanPersonScan = "handleHumanPersonScan",
42
+ HandleHumanItemMatch = "handleHumanItemMatch",
43
+ HandleHumanItemUpdate = "handleHumanItemUpdate",
44
+ HandlePersonaTraitExtraction = "handlePersonaTraitExtraction",
45
+ HandlePersonaTopicScan = "handlePersonaTopicScan",
46
+ HandlePersonaTopicMatch = "handlePersonaTopicMatch",
47
+ HandlePersonaTopicUpdate = "handlePersonaTopicUpdate",
48
+ HandleHeartbeatCheck = "handleHeartbeatCheck",
49
+ HandleEiHeartbeat = "handleEiHeartbeat",
50
+ HandleEiValidation = "handleEiValidation",
51
+ HandleOneShot = "handleOneShot",
52
+ // Ceremony handlers
53
+ HandlePersonaExpire = "handlePersonaExpire",
54
+ HandlePersonaExplore = "handlePersonaExplore",
55
+ HandleDescriptionCheck = "handleDescriptionCheck",
56
+ }
57
+
58
+ // =============================================================================
59
+ // DATA ITEMS
60
+ // =============================================================================
61
+
62
+ export interface DataItemBase {
63
+ id: string;
64
+ name: string;
65
+ description: string;
66
+ sentiment: number;
67
+ last_updated: string;
68
+ learned_by?: string; // Persona ID that learned this item
69
+ persona_groups?: string[];
70
+ embedding?: number[];
71
+ }
72
+
73
+ export interface Fact extends DataItemBase {
74
+ validated: ValidationLevel;
75
+ validated_date: string;
76
+ }
77
+
78
+ export interface Trait extends DataItemBase {
79
+ strength?: number;
80
+ }
81
+
82
+ export interface Topic extends DataItemBase {
83
+ category?: string; // Interest, Goal, Dream, Conflict, Concern, Fear, Hope, Plan, Project
84
+ exposure_current: number;
85
+ exposure_desired: number;
86
+ }
87
+
88
+ /**
89
+ * PersonaTopic - How a persona engages with a topic
90
+ *
91
+ * Different from Human Topic because:
92
+ * - Persona-local (not shared across personas via groups)
93
+ * - Richer fields: perspective, approach, personal_stake
94
+ * - Not "learned" - generated during Ceremony
95
+ */
96
+ export interface PersonaTopic {
97
+ id: string;
98
+ name: string;
99
+ perspective: string; // Their view/opinion on this topic
100
+ approach: string; // How they prefer to engage with this topic
101
+ personal_stake: string; // Why this topic matters to them personally
102
+ sentiment: number; // -1.0 to 1.0
103
+ exposure_current: number; // 0.0 to 1.0 (how recently discussed)
104
+ exposure_desired: number; // 0.0 to 1.0 (how much they want to discuss)
105
+ last_updated: string; // ISO timestamp
106
+ }
107
+
108
+ export interface Person extends DataItemBase {
109
+ relationship: string;
110
+ exposure_current: number;
111
+ exposure_desired: number;
112
+ }
113
+
114
+ export interface Quote {
115
+ id: string; // UUID (use crypto.randomUUID())
116
+ message_id: string | null; // FK to Message.id (nullable for manual quotes)
117
+ data_item_ids: string[]; // FK[] to DataItemBase.id
118
+ persona_groups: string[]; // Visibility groups
119
+ text: string; // The quote content
120
+ speaker: "human" | string; // Who said it (persona ID or "human")
121
+ timestamp: string; // ISO timestamp (from original message)
122
+ start: number | null; // Character offset in message (null = can't highlight)
123
+ end: number | null; // Character offset in message (null = can't highlight)
124
+ created_at: string; // ISO timestamp when captured
125
+ created_by: "extraction" | "human"; // How it was created
126
+ embedding?: number[]; // Semantic embedding for similarity search
127
+ }
128
+
129
+ // =============================================================================
130
+ // PROVIDER ACCOUNTS
131
+ // =============================================================================
132
+
133
+ export enum ProviderType {
134
+ LLM = "llm",
135
+ Storage = "storage",
136
+ }
137
+
138
+ /**
139
+ * ProviderAccount - Configuration for external service connections
140
+ *
141
+ * Used for both LLM providers (OpenRouter, Bedrock, etc.) and storage providers
142
+ * (flare576.com, Dropbox, Google Drive, etc.).
143
+ *
144
+ * Model specification format: `account-name:model` (e.g., `MyOpenRouter:mistralai/mistral-7b`)
145
+ * Falls back to environment variables if no matching account is found.
146
+ */
147
+ export interface ProviderAccount {
148
+ id: string; // UUID
149
+ name: string; // User-defined display name (e.g., "OpenRouter-Free", "Work Bedrock")
150
+ type: ProviderType; // "llm" | "storage"
151
+ url: string; // Base URL for API (e.g., "https://openrouter.ai/api/v1")
152
+
153
+ // Auth - use api_key for Bearer token, or username+password for basic/custom auth
154
+ api_key?: string; // Bearer token auth (most common)
155
+ username?: string; // Basic auth or custom (for storage providers)
156
+ password?: string; // Basic auth or custom (for storage providers)
157
+
158
+ // LLM-specific
159
+ default_model?: string; // Default model for this account
160
+ token_limit?: number; // Context window override (tokens). Used for extraction chunking.
161
+
162
+ // Provider-specific extras (e.g., OpenRouter needs HTTP-Referer, X-Title)
163
+ extra_headers?: Record<string, string>;
164
+
165
+ // Metadata
166
+ enabled?: boolean; // Default: true
167
+ created_at: string; // ISO timestamp
168
+ }
169
+
170
+ // =============================================================================
171
+ // ENTITIES
172
+ // =============================================================================
173
+
174
+ export interface SyncCredentials {
175
+ username: string;
176
+ passphrase: string;
177
+ }
178
+
179
+ export interface OpenCodeSettings {
180
+ integration?: boolean;
181
+ polling_interval_ms?: number; // Default: 1800000 (30 min)
182
+ last_sync?: string; // ISO timestamp
183
+ extraction_point?: string; // ISO timestamp - earliest unprocessed message, gradual extraction advances this
184
+ }
185
+
186
+ export interface CeremonyConfig {
187
+ time: string; // "HH:MM" format (e.g., "09:00")
188
+ last_ceremony?: string; // ISO timestamp
189
+ decay_rate?: number; // Default: 0.1
190
+ explore_threshold?: number; // Default: 3
191
+ }
192
+
193
+ export interface HumanSettings {
194
+ default_model?: string;
195
+ queue_paused?: boolean;
196
+ skip_quote_delete_confirm?: boolean;
197
+ name_display?: string;
198
+ time_mode?: "24h" | "12h" | "local" | "utc";
199
+ accounts?: ProviderAccount[];
200
+ sync?: SyncCredentials;
201
+ opencode?: OpenCodeSettings;
202
+ ceremony?: CeremonyConfig;
203
+ }
204
+
205
+ export interface HumanEntity {
206
+ entity: "human";
207
+ facts: Fact[];
208
+ traits: Trait[];
209
+ topics: Topic[];
210
+ people: Person[];
211
+ quotes: Quote[];
212
+ last_updated: string;
213
+ last_activity: string;
214
+ settings?: HumanSettings;
215
+ }
216
+
217
+ export interface PersonaEntity {
218
+ id: string; // UUID (or "ei" for built-in Ei persona)
219
+ display_name: string; // What shows in UI (user's chosen name)
220
+ entity: "system";
221
+ aliases?: string[]; // For fuzzy matching (user types "/persona Bob")
222
+ short_description?: string;
223
+ long_description?: string;
224
+ model?: string;
225
+ group_primary?: string | null;
226
+ groups_visible?: string[];
227
+ traits: Trait[];
228
+ topics: PersonaTopic[];
229
+ is_paused: boolean;
230
+ pause_until?: string;
231
+ is_archived: boolean;
232
+ archived_at?: string;
233
+ is_static: boolean;
234
+ heartbeat_delay_ms?: number;
235
+ context_window_hours?: number;
236
+ context_boundary?: string; // ISO timestamp - messages before this excluded from LLM context
237
+ last_updated: string;
238
+ last_activity: string;
239
+ last_heartbeat?: string;
240
+ last_extraction?: string;
241
+ last_inactivity_ping?: string;
242
+ }
243
+
244
+ export interface PersonaCreationInput {
245
+ name: string;
246
+ aliases?: string[];
247
+ long_description?: string;
248
+ short_description?: string;
249
+ traits?: Partial<Trait>[];
250
+ topics?: Partial<Topic>[];
251
+ model?: string;
252
+ group_primary?: string;
253
+ groups_visible?: string[];
254
+ }
255
+
256
+ // Message pruning thresholds (shared by ceremony and import)
257
+ export const MESSAGE_MIN_COUNT = 200;
258
+ export const MESSAGE_MAX_AGE_DAYS = 14;
259
+
260
+ // Reserved persona names (command keywords that conflict with /persona subcommands)
261
+ export const RESERVED_PERSONA_NAMES = ["new", "clone"] as const;
262
+ export type ReservedPersonaName = typeof RESERVED_PERSONA_NAMES[number];
263
+
264
+ export function isReservedPersonaName(name: string): boolean {
265
+ return RESERVED_PERSONA_NAMES.includes(name.toLowerCase() as ReservedPersonaName);
266
+ }
267
+
268
+ // =============================================================================
269
+ // MESSAGES
270
+ // =============================================================================
271
+
272
+ export interface Message {
273
+ id: string;
274
+ role: "human" | "system";
275
+ content: string;
276
+ timestamp: string;
277
+ read: boolean;
278
+ context_status: ContextStatus;
279
+ f?: boolean; // fact extraction done
280
+ r?: boolean; // trait extraction done
281
+ p?: boolean; // person extraction done
282
+ o?: boolean; // topic extraction done
283
+ }
284
+
285
+ export interface ChatMessage {
286
+ role: "system" | "user" | "assistant";
287
+ content: string;
288
+ }
289
+
290
+ // =============================================================================
291
+ // LLM TYPES
292
+ // =============================================================================
293
+
294
+ export interface LLMRequest {
295
+ id: string;
296
+ created_at: string;
297
+ attempts: number;
298
+ last_attempt?: string;
299
+ retry_after?: string;
300
+ type: LLMRequestType;
301
+ priority: LLMPriority;
302
+ system: string;
303
+ user: string;
304
+ next_step: LLMNextStep;
305
+ model?: string;
306
+ data: Record<string, unknown>;
307
+ }
308
+
309
+
310
+ export interface QueueFailResult {
311
+ dropped: boolean;
312
+ retryDelay?: number;
313
+ }
314
+ export interface LLMResponse {
315
+ request: LLMRequest;
316
+ success: boolean;
317
+ content: string | null;
318
+ parsed?: unknown;
319
+ error?: string;
320
+ finish_reason?: string;
321
+ }
322
+
323
+ // =============================================================================
324
+ // API TYPES
325
+ // =============================================================================
326
+
327
+ export interface PersonaSummary {
328
+ id: string;
329
+ display_name: string;
330
+ aliases: string[];
331
+ short_description?: string;
332
+ is_paused: boolean;
333
+ is_archived: boolean;
334
+ unread_count: number;
335
+ last_activity?: string;
336
+ context_boundary?: string;
337
+ }
338
+
339
+ export interface MessageQueryOptions {
340
+ after?: string;
341
+ before?: string;
342
+ limit?: number;
343
+ includeOutOfContext?: boolean;
344
+ }
345
+
346
+ export interface QueueStatus {
347
+ state: "idle" | "busy" | "paused";
348
+ pending_count: number;
349
+ current_operation?: string;
350
+ }
351
+
352
+ export interface EiError {
353
+ code: string;
354
+ message: string;
355
+ }
356
+
357
+ // =============================================================================
358
+ // EI_INTERFACE (Processor -> Frontend events)
359
+ // =============================================================================
360
+
361
+ export interface Ei_Interface {
362
+ onPersonaAdded?: () => void;
363
+ onPersonaRemoved?: () => void;
364
+ onPersonaUpdated?: (personaId: string) => void;
365
+ onMessageAdded?: (personaId: string) => void;
366
+ onMessageRecalled?: (personaId: string, content: string) => void;
367
+ onMessageProcessing?: (personaId: string) => void;
368
+ onMessageQueued?: (personaId: string) => void;
369
+ onHumanUpdated?: () => void;
370
+ onQuoteAdded?: () => void;
371
+ onQuoteUpdated?: () => void;
372
+ onQuoteRemoved?: () => void;
373
+ onQueueStateChanged?: (state: "idle" | "busy" | "paused") => void;
374
+ onError?: (error: EiError) => void;
375
+ onStateImported?: () => void;
376
+ onOneShotReturned?: (guid: string, content: string) => void;
377
+ onContextBoundaryChanged?: (personaId: string) => void;
378
+ onSaveAndExitStart?: () => void;
379
+ onSaveAndExitFinish?: () => void;
380
+ onStateConflict?: (data: StateConflictData) => void;
381
+ }
382
+
383
+ // =============================================================================
384
+ // SYNC TYPES
385
+ // =============================================================================
386
+
387
+ export type StateConflictResolution = "local" | "server" | "yolo";
388
+
389
+ export interface StateConflictData {
390
+ localTimestamp: Date;
391
+ remoteTimestamp: Date;
392
+ hasLocalState: boolean;
393
+ }
394
+
395
+ // =============================================================================
396
+ // STORAGE TYPES
397
+ // =============================================================================
398
+
399
+ export interface StorageState {
400
+ version: number;
401
+ timestamp: string;
402
+ human: HumanEntity;
403
+ personas: Record<
404
+ string,
405
+ {
406
+ entity: PersonaEntity;
407
+ messages: Message[];
408
+ }
409
+ >;
410
+ queue: LLMRequest[];
411
+ }
412
+
413
+
414
+
415
+ // =============================================================================
416
+ // DATA ITEM TYPE HELPERS
417
+ // =============================================================================
418
+
419
+ export type DataItemType = "fact" | "trait" | "topic" | "person";
420
+
421
+ export type DataItem = Fact | Trait | Topic | Person;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Logarithmic decay utility for exposure values.
3
+ * Ported from V0: v0/src/topic-decay.ts
4
+ *
5
+ * The formula decays faster at extremes (0 and 1), slower in the middle.
6
+ * K=0.1 means ~10% decay per day at midpoint (0.5).
7
+ */
8
+
9
+ export function calculateLogarithmicDecay(
10
+ currentValue: number,
11
+ hoursSinceUpdate: number,
12
+ K: number = 0.1
13
+ ): number {
14
+ const decay = K * currentValue * (1 - currentValue) * hoursSinceUpdate;
15
+ return Math.max(0, Math.min(1, currentValue - decay));
16
+ }
17
+
18
+ export function applyDecayToValue(
19
+ currentValue: number,
20
+ lastUpdated: string,
21
+ now: Date = new Date(),
22
+ K: number = 0.1
23
+ ): { newValue: number; hoursSinceUpdate: number } {
24
+ const lastUpdatedTime = new Date(lastUpdated).getTime();
25
+ const hoursSinceUpdate = (now.getTime() - lastUpdatedTime) / (1000 * 60 * 60);
26
+
27
+ if (hoursSinceUpdate < 0.1) {
28
+ return { newValue: currentValue, hoursSinceUpdate };
29
+ }
30
+
31
+ const newValue = calculateLogarithmicDecay(currentValue, hoursSinceUpdate, K);
32
+ return { newValue, hoursSinceUpdate };
33
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./core/index.js";