haroo 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 (60) hide show
  1. package/README.md +58 -0
  2. package/dist/index.js +84883 -0
  3. package/package.json +73 -0
  4. package/src/__tests__/e2e/EventService.test.ts +211 -0
  5. package/src/__tests__/unit/Event.test.ts +89 -0
  6. package/src/__tests__/unit/Memory.test.ts +130 -0
  7. package/src/application/graph/builder.ts +106 -0
  8. package/src/application/graph/edges.ts +37 -0
  9. package/src/application/graph/nodes/addEvent.ts +113 -0
  10. package/src/application/graph/nodes/chat.ts +128 -0
  11. package/src/application/graph/nodes/extractMemory.ts +135 -0
  12. package/src/application/graph/nodes/index.ts +8 -0
  13. package/src/application/graph/nodes/query.ts +194 -0
  14. package/src/application/graph/nodes/respond.ts +26 -0
  15. package/src/application/graph/nodes/router.ts +82 -0
  16. package/src/application/graph/nodes/toolExecutor.ts +79 -0
  17. package/src/application/graph/nodes/types.ts +2 -0
  18. package/src/application/index.ts +4 -0
  19. package/src/application/services/DiaryService.ts +188 -0
  20. package/src/application/services/EventService.ts +61 -0
  21. package/src/application/services/index.ts +2 -0
  22. package/src/application/tools/calendarTool.ts +179 -0
  23. package/src/application/tools/diaryTool.ts +182 -0
  24. package/src/application/tools/index.ts +68 -0
  25. package/src/config/env.ts +33 -0
  26. package/src/config/index.ts +1 -0
  27. package/src/domain/entities/DiaryEntry.ts +16 -0
  28. package/src/domain/entities/Event.ts +13 -0
  29. package/src/domain/entities/Memory.ts +20 -0
  30. package/src/domain/index.ts +5 -0
  31. package/src/domain/interfaces/IDiaryRepository.ts +21 -0
  32. package/src/domain/interfaces/IEventsRepository.ts +12 -0
  33. package/src/domain/interfaces/ILanguageModel.ts +23 -0
  34. package/src/domain/interfaces/IMemoriesRepository.ts +15 -0
  35. package/src/domain/interfaces/IMemory.ts +19 -0
  36. package/src/domain/interfaces/index.ts +4 -0
  37. package/src/domain/state/AgentState.ts +30 -0
  38. package/src/index.ts +5 -0
  39. package/src/infrastructure/database/factory.ts +52 -0
  40. package/src/infrastructure/database/index.ts +21 -0
  41. package/src/infrastructure/database/sqlite-checkpointer.ts +179 -0
  42. package/src/infrastructure/database/sqlite-client.ts +69 -0
  43. package/src/infrastructure/database/sqlite-diary-repository.ts +209 -0
  44. package/src/infrastructure/database/sqlite-events-repository.ts +167 -0
  45. package/src/infrastructure/database/sqlite-memories-repository.ts +284 -0
  46. package/src/infrastructure/database/sqlite-schema.ts +98 -0
  47. package/src/infrastructure/index.ts +3 -0
  48. package/src/infrastructure/llm/base.ts +14 -0
  49. package/src/infrastructure/llm/gemini.ts +139 -0
  50. package/src/infrastructure/llm/index.ts +22 -0
  51. package/src/infrastructure/llm/ollama.ts +126 -0
  52. package/src/infrastructure/llm/openai.ts +148 -0
  53. package/src/infrastructure/memory/checkpointer.ts +19 -0
  54. package/src/infrastructure/memory/index.ts +2 -0
  55. package/src/infrastructure/settings/index.ts +96 -0
  56. package/src/interface/cli/calendar.ts +120 -0
  57. package/src/interface/cli/chat.ts +185 -0
  58. package/src/interface/cli/commands.ts +337 -0
  59. package/src/interface/cli/printer.ts +65 -0
  60. package/src/interface/index.ts +1 -0
@@ -0,0 +1,337 @@
1
+ import { Command } from "commander";
2
+ import { EventService, DiaryService } from "../../application/services";
3
+ import { createRepositoriesFromEnv } from "../../infrastructure/database/factory";
4
+ import { createLLM } from "../../infrastructure/llm";
5
+ import {
6
+ getSettings,
7
+ saveSettings,
8
+ getEffectiveProvider,
9
+ getEffectiveModel,
10
+ } from "../../infrastructure/settings";
11
+ import { formatTodayEvents, formatUpcomingEvents } from "./calendar";
12
+ import { startChat } from "./chat";
13
+ import { printer } from "./printer";
14
+
15
+ export type LLMProvider = "openai" | "gemini" | "ollama";
16
+ const VALID_PROVIDERS: LLMProvider[] = ["openai", "gemini", "ollama"];
17
+
18
+ export function createProgram(): Command {
19
+ const program = new Command();
20
+
21
+ program.name("haroo").description("AI-powered personal logging CLI").version("1.0.0");
22
+
23
+ program
24
+ .command("chat")
25
+ .description("Start an interactive chat session")
26
+ .option("-p, --provider <provider>", "LLM provider (openai, gemini, ollama)")
27
+ .option("-m, --model <model>", "Model name to use")
28
+ .option("-t, --thread <id>", "Resume a previous thread")
29
+ .action(async (options) => {
30
+ try {
31
+ const repositories = createRepositoriesFromEnv();
32
+ const provider = getEffectiveProvider(options.provider);
33
+ const model = getEffectiveModel(options.model);
34
+
35
+ await startChat({
36
+ provider,
37
+ model,
38
+ threadId: options.thread,
39
+ repositories,
40
+ });
41
+ } catch (error) {
42
+ const errorMessage = error instanceof Error ? error.message : String(error);
43
+ printer.error(`Failed to start chat: ${errorMessage}`);
44
+ process.exit(1);
45
+ }
46
+ });
47
+
48
+ program
49
+ .command("calendar")
50
+ .description("Show calendar events")
51
+ .option("-d, --days <number>", "Number of upcoming days to show", "7")
52
+ .option("--today", "Show only today's events")
53
+ .action(async (options) => {
54
+ try {
55
+ const repositories = createRepositoriesFromEnv();
56
+ const eventService = new EventService(repositories.events);
57
+
58
+ if (options.today) {
59
+ const events = await eventService.getToday();
60
+ console.log(formatTodayEvents(events));
61
+ } else {
62
+ const days = parseInt(options.days, 10);
63
+ const events = await eventService.getUpcoming(days);
64
+ console.log(formatUpcomingEvents(events, days));
65
+ }
66
+ } catch (error) {
67
+ const errorMessage = error instanceof Error ? error.message : String(error);
68
+ printer.error(`Failed to load calendar: ${errorMessage}`);
69
+ process.exit(1);
70
+ }
71
+ });
72
+
73
+ program
74
+ .command("sessions")
75
+ .description("List saved sessions")
76
+ .action(async () => {
77
+ printer.warn("Sessions listing not yet implemented");
78
+ });
79
+
80
+ // Model configuration command
81
+ program
82
+ .command("model")
83
+ .description("View or set default LLM provider and model")
84
+ .argument("[provider]", "Provider to set (openai, gemini, ollama)")
85
+ .argument("[model]", "Model name to use")
86
+ .action((provider?: string, model?: string) => {
87
+ // If no arguments, show current settings
88
+ if (!provider) {
89
+ const settings = getSettings();
90
+ const effectiveProvider = getEffectiveProvider();
91
+ const effectiveModel = getEffectiveModel();
92
+
93
+ printer.blank();
94
+ printer.info("Current Model Settings");
95
+ printer.divider();
96
+ printer.system(
97
+ `Provider: ${effectiveProvider}${settings.defaultProvider ? "" : " (default)"}`
98
+ );
99
+ if (effectiveModel) {
100
+ printer.system(`Model: ${effectiveModel}${settings.defaultModel ? "" : " (default)"}`);
101
+ } else {
102
+ printer.system("Model: (provider default)");
103
+ }
104
+ printer.blank();
105
+ printer.system("Saved settings file: ~/.log/settings.json");
106
+ printer.blank();
107
+ return;
108
+ }
109
+
110
+ // Validate provider
111
+ if (!VALID_PROVIDERS.includes(provider as LLMProvider)) {
112
+ printer.error(`Invalid provider: ${provider}`);
113
+ printer.info(`Valid providers: ${VALID_PROVIDERS.join(", ")}`);
114
+ process.exit(1);
115
+ }
116
+
117
+ // Save settings
118
+ saveSettings({
119
+ defaultProvider: provider as LLMProvider,
120
+ defaultModel: model,
121
+ });
122
+
123
+ if (model) {
124
+ printer.success(`Default model set to: ${provider} / ${model}`);
125
+ } else {
126
+ printer.success(`Default provider set to: ${provider}`);
127
+ }
128
+ });
129
+
130
+ // Diary commands
131
+ const diaryCmd = program.command("diary").description("Diary/journal operations");
132
+
133
+ diaryCmd
134
+ .command("generate")
135
+ .description("Generate diary entry for a date")
136
+ .option("-d, --date <date>", "Date to generate (YYYY-MM-DD), defaults to yesterday")
137
+ .option("-p, --provider <provider>", "LLM provider (openai, gemini, ollama)")
138
+ .option("-m, --model <model>", "Model name to use")
139
+ .action(async (options) => {
140
+ try {
141
+ const repositories = createRepositoriesFromEnv();
142
+ const provider = getEffectiveProvider(options.provider);
143
+ const model = getEffectiveModel(options.model);
144
+ const llm = createLLM(provider, model);
145
+ const diaryService = new DiaryService(repositories.diary, repositories.checkpointer, llm);
146
+
147
+ let targetDate: Date;
148
+ if (options.date) {
149
+ targetDate = new Date(options.date);
150
+ targetDate.setHours(0, 0, 0, 0);
151
+ } else {
152
+ // Default to yesterday
153
+ targetDate = new Date();
154
+ targetDate.setDate(targetDate.getDate() - 1);
155
+ targetDate.setHours(0, 0, 0, 0);
156
+ }
157
+
158
+ const dateStr = targetDate.toISOString().split("T")[0];
159
+ const spinner = printer.spinner(`Generating diary entry for ${dateStr}...`);
160
+ spinner.start();
161
+
162
+ const entry = await diaryService.generateDiaryEntry(targetDate);
163
+ spinner.stop();
164
+
165
+ if (entry) {
166
+ printer.success(`Diary entry created for ${dateStr}`);
167
+ printer.blank();
168
+ printer.info(`Mood: ${entry.mood} (${entry.moodScore}/10)`);
169
+ printer.blank();
170
+ printer.system("Summary:");
171
+ console.log(` ${entry.summary}`);
172
+ printer.blank();
173
+ printer.system("Therapeutic Advice:");
174
+ console.log(` ${entry.therapeuticAdvice}`);
175
+ printer.blank();
176
+ printer.system(`Messages analyzed: ${entry.messageCount}`);
177
+ } else {
178
+ printer.warn(`No conversations found for ${dateStr}`);
179
+ }
180
+ } catch (error) {
181
+ const errorMessage = error instanceof Error ? error.message : String(error);
182
+ printer.error(`Failed to generate diary entry: ${errorMessage}`);
183
+ process.exit(1);
184
+ }
185
+ });
186
+
187
+ diaryCmd
188
+ .command("show")
189
+ .description("Show recent diary entries")
190
+ .option("-n, --count <number>", "Number of entries to show", "7")
191
+ .action(async (options) => {
192
+ try {
193
+ const repositories = createRepositoriesFromEnv();
194
+ const count = parseInt(options.count, 10);
195
+
196
+ const entries = await repositories.diary.getRecent(count);
197
+
198
+ if (entries.length === 0) {
199
+ printer.warn("No diary entries found");
200
+ return;
201
+ }
202
+
203
+ printer.blank();
204
+ printer.divider();
205
+ printer.success(`Recent Diary Entries (${entries.length})`);
206
+ printer.divider();
207
+
208
+ for (const entry of entries) {
209
+ const dateStr = entry.entryDate.toISOString().split("T")[0];
210
+ printer.blank();
211
+ printer.info(`${dateStr} - ${entry.mood} (${entry.moodScore}/10)`);
212
+ console.log(` ${entry.summary}`);
213
+ printer.system(` Advice: ${entry.therapeuticAdvice}`);
214
+ }
215
+
216
+ printer.blank();
217
+ } catch (error) {
218
+ const errorMessage = error instanceof Error ? error.message : String(error);
219
+ printer.error(`Failed to load diary entries: ${errorMessage}`);
220
+ process.exit(1);
221
+ }
222
+ });
223
+
224
+ // Memory commands
225
+ const memoryCmd = program.command("memory").description("Manage stored memories");
226
+
227
+ memoryCmd
228
+ .command("list", { isDefault: true })
229
+ .description("View stored memories")
230
+ .option("-t, --type <type>", "Filter by type")
231
+ .option("-n, --count <number>", "Number to show", "20")
232
+ .action(async (options) => {
233
+ try {
234
+ const repositories = createRepositoriesFromEnv();
235
+ const count = parseInt(options.count, 10);
236
+
237
+ let memories;
238
+ if (options.type) {
239
+ memories = await repositories.memories.getByType(options.type);
240
+ } else {
241
+ memories = await repositories.memories.getAll(count);
242
+ }
243
+
244
+ if (memories.length === 0) {
245
+ printer.warn("No memories stored yet");
246
+ return;
247
+ }
248
+
249
+ printer.blank();
250
+ printer.success(`Memories (${memories.length})`);
251
+ printer.divider();
252
+
253
+ for (const memory of memories) {
254
+ printer.info(`[${memory.id.slice(0, 8)}] [${memory.type}] ${memory.content}`);
255
+ printer.system(` Importance: ${memory.importance}/10`);
256
+ }
257
+ printer.blank();
258
+ } catch (error) {
259
+ const errorMessage = error instanceof Error ? error.message : String(error);
260
+ printer.error(`Failed to load memories: ${errorMessage}`);
261
+ process.exit(1);
262
+ }
263
+ });
264
+
265
+ memoryCmd
266
+ .command("add <content>")
267
+ .description("Add a new memory")
268
+ .option("-t, --type <type>", "Memory type", "fact")
269
+ .option("-i, --importance <number>", "Importance 1-10", "5")
270
+ .action(async (content, options) => {
271
+ try {
272
+ const repositories = createRepositoriesFromEnv();
273
+
274
+ const memory = {
275
+ id: crypto.randomUUID(),
276
+ type: options.type as
277
+ | "fact"
278
+ | "preference"
279
+ | "routine"
280
+ | "relationship"
281
+ | "communication_style"
282
+ | "interest",
283
+ content: content,
284
+ importance: parseInt(options.importance, 10),
285
+ createdAt: new Date(),
286
+ lastAccessed: new Date(),
287
+ };
288
+
289
+ await repositories.memories.create(memory);
290
+ printer.success(`Memory added: ${memory.id.slice(0, 8)}`);
291
+ } catch (error) {
292
+ const errorMessage = error instanceof Error ? error.message : String(error);
293
+ printer.error(`Failed to add memory: ${errorMessage}`);
294
+ process.exit(1);
295
+ }
296
+ });
297
+
298
+ memoryCmd
299
+ .command("edit <id> <content>")
300
+ .description("Edit a memory")
301
+ .action(async (id, content) => {
302
+ try {
303
+ const repositories = createRepositoriesFromEnv();
304
+ const result = await repositories.memories.update(id, { content });
305
+ if (result) {
306
+ printer.success("Memory updated");
307
+ } else {
308
+ printer.error("Memory not found");
309
+ }
310
+ } catch (error) {
311
+ const errorMessage = error instanceof Error ? error.message : String(error);
312
+ printer.error(`Failed to edit memory: ${errorMessage}`);
313
+ process.exit(1);
314
+ }
315
+ });
316
+
317
+ memoryCmd
318
+ .command("delete <id>")
319
+ .description("Delete a memory")
320
+ .action(async (id) => {
321
+ try {
322
+ const repositories = createRepositoriesFromEnv();
323
+ const deleted = await repositories.memories.delete(id);
324
+ if (deleted) {
325
+ printer.success("Memory deleted");
326
+ } else {
327
+ printer.error("Memory not found");
328
+ }
329
+ } catch (error) {
330
+ const errorMessage = error instanceof Error ? error.message : String(error);
331
+ printer.error(`Failed to delete memory: ${errorMessage}`);
332
+ process.exit(1);
333
+ }
334
+ });
335
+
336
+ return program;
337
+ }
@@ -0,0 +1,65 @@
1
+ import chalk from "chalk";
2
+ import ora, { type Ora } from "ora";
3
+
4
+ export interface Printer {
5
+ info(message: string): void;
6
+ success(message: string): void;
7
+ error(message: string): void;
8
+ warn(message: string): void;
9
+ user(message: string): void;
10
+ assistant(message: string): void;
11
+ system(message: string): void;
12
+ blank(): void;
13
+ divider(): void;
14
+ spinner(message: string): Ora;
15
+ }
16
+
17
+ export function createPrinter(): Printer {
18
+ return {
19
+ info(message: string): void {
20
+ console.log(chalk.blue("ℹ"), message);
21
+ },
22
+
23
+ success(message: string): void {
24
+ console.log(chalk.green("✓"), message);
25
+ },
26
+
27
+ error(message: string): void {
28
+ console.log(chalk.red("✗"), message);
29
+ },
30
+
31
+ warn(message: string): void {
32
+ console.log(chalk.yellow("⚠"), message);
33
+ },
34
+
35
+ user(message: string): void {
36
+ console.log(chalk.cyan.bold("You:"), message);
37
+ },
38
+
39
+ assistant(message: string): void {
40
+ console.log(chalk.magenta.bold("Assistant:"), message);
41
+ },
42
+
43
+ system(message: string): void {
44
+ console.log(chalk.dim(`[${message}]`));
45
+ },
46
+
47
+ blank(): void {
48
+ console.log();
49
+ },
50
+
51
+ divider(): void {
52
+ console.log(chalk.dim("─".repeat(50)));
53
+ },
54
+
55
+ spinner(message: string): Ora {
56
+ return ora({
57
+ text: message,
58
+ color: "cyan",
59
+ discardStdin: false,
60
+ });
61
+ },
62
+ };
63
+ }
64
+
65
+ export const printer = createPrinter();
@@ -0,0 +1 @@
1
+ export { createProgram } from "./cli/commands";