@lovelybunch/api 1.0.75-alpha.8 → 1.0.75

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 (188) hide show
  1. package/dist/lib/jobs/job-runner.js +10 -2
  2. package/dist/lib/jobs/job-scheduler.js +21 -0
  3. package/dist/lib/mail/mail-runner.d.ts +51 -0
  4. package/dist/lib/mail/mail-runner.js +342 -0
  5. package/dist/lib/slack/slack-service.d.ts +2 -0
  6. package/dist/lib/slack/slack-service.js +3 -0
  7. package/dist/lib/storage/file-storage.d.ts +16 -16
  8. package/dist/lib/storage/file-storage.js +59 -64
  9. package/dist/lib/terminal/terminal-manager.d.ts +3 -5
  10. package/dist/lib/terminal/terminal-manager.js +10 -61
  11. package/dist/routes/api/v1/ai/route.js +361 -20
  12. package/dist/routes/api/v1/chats/[id]/index.js +2 -1
  13. package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
  14. package/dist/routes/api/v1/chats/[id]/route.js +30 -1
  15. package/dist/routes/api/v1/context/index.js +0 -2
  16. package/dist/routes/api/v1/git/index.js +23 -0
  17. package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
  18. package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
  19. package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
  20. package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
  21. package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
  22. package/dist/routes/api/v1/knowledge/index.js +1 -0
  23. package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
  24. package/dist/routes/api/v1/knowledge/route.js +176 -0
  25. package/dist/routes/api/v1/mail/index.d.ts +3 -0
  26. package/dist/routes/api/v1/mail/index.js +23 -0
  27. package/dist/routes/api/v1/mail/route.d.ts +294 -0
  28. package/dist/routes/api/v1/mail/route.js +344 -0
  29. package/dist/routes/api/v1/mcp/index.js +109 -34
  30. package/dist/routes/api/v1/slack/index.d.ts +3 -0
  31. package/dist/routes/api/v1/slack/index.js +15 -0
  32. package/dist/routes/api/v1/slack/route.d.ts +124 -0
  33. package/dist/routes/api/v1/slack/route.js +192 -0
  34. package/dist/routes/api/v1/tasks/[id]/route.d.ts +117 -0
  35. package/dist/routes/api/v1/tasks/[id]/route.js +166 -0
  36. package/dist/routes/api/v1/tasks/index.d.ts +3 -0
  37. package/dist/routes/api/v1/tasks/index.js +10 -0
  38. package/dist/routes/api/v1/tasks/route.d.ts +96 -0
  39. package/dist/routes/api/v1/tasks/route.js +136 -0
  40. package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
  41. package/dist/routes/api/v1/terminal/[taskId]/create/index.d.ts +3 -0
  42. package/dist/routes/api/v1/terminal/[taskId]/create/index.js +5 -0
  43. package/dist/routes/api/v1/terminal/[taskId]/create/route.d.ts +10 -0
  44. package/dist/routes/api/v1/terminal/[taskId]/create/route.js +27 -0
  45. package/dist/routes/api/v1/terminal/[taskId]/destroy/index.d.ts +3 -0
  46. package/dist/routes/api/v1/terminal/[taskId]/destroy/index.js +5 -0
  47. package/dist/routes/api/v1/terminal/[taskId]/destroy/route.d.ts +10 -0
  48. package/dist/routes/api/v1/terminal/[taskId]/destroy/route.js +21 -0
  49. package/dist/routes/api/v1/terminal/[taskId]/resize/index.d.ts +3 -0
  50. package/dist/routes/api/v1/terminal/[taskId]/resize/index.js +5 -0
  51. package/dist/routes/api/v1/terminal/[taskId]/resize/route.d.ts +10 -0
  52. package/dist/routes/api/v1/terminal/[taskId]/resize/route.js +21 -0
  53. package/dist/routes/api/v1/terminal/sessions/route.js +4 -4
  54. package/dist/server-with-static.js +14 -8
  55. package/dist/server.js +14 -8
  56. package/package.json +4 -4
  57. package/static/assets/{ActivityPage-DSSML9J-.js → ActivityPage-k4I7Q53O.js} +1 -1
  58. package/static/assets/ApiKeysSettingsPage-B1YvVdmg.js +2 -0
  59. package/static/assets/{ArchitectureEditPage-CIjqkpMz.js → ArchitectureEditPage-CpowsIx2.js} +1 -1
  60. package/static/assets/{ArchitecturePage-Db__w054.js → ArchitecturePage-DYxC_aMR.js} +1 -1
  61. package/static/assets/{AuthSettingsPage-Bpooi8Z0.js → AuthSettingsPage-DtSo78Y_.js} +2 -2
  62. package/static/assets/{CallbackPage-BGLKeyjv.js → CallbackPage-bROCGapx.js} +1 -1
  63. package/static/assets/CodePage-CPCj64rX.js +2 -0
  64. package/static/assets/{CollapsibleSection-B6RO5o5R.js → CollapsibleSection-M5cXbl92.js} +1 -1
  65. package/static/assets/DashboardPage-B9BZZfw6.js +51 -0
  66. package/static/assets/{GitPage-DxjLaRWe.js → GitPage-BiDtdSK1.js} +2 -2
  67. package/static/assets/GitSettingsPage-THm6wDjs.js +6 -0
  68. package/static/assets/IdentityPage-BC16skg6.js +6 -0
  69. package/static/assets/{ImplementationStepsEditor-DWjDyZzP.js → ImplementationStepsEditor-HliLQav5.js} +2 -2
  70. package/static/assets/IntegrationsSettingsPage-CC_VKIQa.js +1 -0
  71. package/static/assets/JobDetailPage-z1QQYvmU.js +1 -0
  72. package/static/assets/KnowledgeDetailPage-DzHXBS7Q.js +1 -0
  73. package/static/assets/KnowledgeEditPage-BwGnUH_m.js +1 -0
  74. package/static/assets/KnowledgePage-CGIVMS02.js +3 -0
  75. package/static/assets/{LoginPage-DptfKsWo.js → LoginPage-VQ3lcfLV.js} +1 -1
  76. package/static/assets/MailInboxPage-DiZKqwdU.js +1 -0
  77. package/static/assets/MailProcessingModal-DIeSQBoR.js +6 -0
  78. package/static/assets/MailReadPage-C8AACmZQ.js +1 -0
  79. package/static/assets/MailSentPage-C_5yFly_.js +1 -0
  80. package/static/assets/McpSettingsPage-i9YHcu1s.js +1 -0
  81. package/static/assets/NewKnowledgePage-BnVY7WUD.js +9 -0
  82. package/static/assets/{NewSkillPage-Cwy2MSr9.js → NewSkillPage-DwniHD6D.js} +1 -1
  83. package/static/assets/NewTaskPage-F5UX2WMc.js +90 -0
  84. package/static/assets/NotFoundPage-BbSZX_4L.js +6 -0
  85. package/static/assets/NotificationsSettingsPage-C8kjcift.js +1 -0
  86. package/static/assets/ProjectEditPage-DUUlIEqI.js +11 -0
  87. package/static/assets/{ProjectPage-DgUr4bVU.js → ProjectPage-Unz9PQpA.js} +1 -1
  88. package/static/assets/PromptsSettingsPage-DVpIuRKI.js +1 -0
  89. package/static/assets/ResourceDetailPage-DqHZ2KYD.js +1 -0
  90. package/static/assets/ResourcesPage-BP5tuAi-.js +41 -0
  91. package/static/assets/RoleEditPage-BgKu8S0-.js +13 -0
  92. package/static/assets/{RolePage-Sc-GFiL2.js → RolePage-Fed52Ov5.js} +1 -1
  93. package/static/assets/{RulesSettingsPage-DdMCzh9j.js → RulesSettingsPage-BQ2O0u66.js} +2 -2
  94. package/static/assets/SchedulePage-jkxjuzBx.js +4 -0
  95. package/static/assets/SkillDetailPage-k3Q2-NFd.js +1 -0
  96. package/static/assets/{SkillEditPage-BDd2CtAS.js → SkillEditPage-urF4snjo.js} +1 -1
  97. package/static/assets/SkillsPage-DlWDhEjR.js +8 -0
  98. package/static/assets/{SkillsSettingsPage-1N0JQOYc.js → SkillsSettingsPage-BViFgckG.js} +1 -1
  99. package/static/assets/SourceInput-CAFKTHw-.js +1 -0
  100. package/static/assets/{TagInput-D_SdcypZ.js → TagInput-C6lI-ePr.js} +1 -1
  101. package/static/assets/TaskDetailPage-DpbRHnW_.js +16 -0
  102. package/static/assets/TaskEditPage-DssRbW0h.js +1 -0
  103. package/static/assets/TasksPage-CD_eo0Bj.js +17 -0
  104. package/static/assets/TerminalPage-BG_wlccr.js +1 -0
  105. package/static/assets/TerminalSessionPage-CsK-LznK.js +8 -0
  106. package/static/assets/UserPreferencesPage-CWUq3efu.js +1 -0
  107. package/static/assets/UserSettingsPage-CduI_MGS.js +1 -0
  108. package/static/assets/UtilitiesPage-BAxokhLh.js +1 -0
  109. package/static/assets/{alert-BD5jo3SI.js → alert-BXsc6_qu.js} +1 -1
  110. package/static/assets/{arrow-down-BxcoVp6S.js → arrow-down-DmW_3gE8.js} +1 -1
  111. package/static/assets/{arrow-left-CdM_IPng.js → arrow-left-1S-835kP.js} +1 -1
  112. package/static/assets/{arrow-up-BOJ6ob9X.js → arrow-up-BYism_o1.js} +1 -1
  113. package/static/assets/arrow-up-down-Dw3J0a4i.js +6 -0
  114. package/static/assets/{badge-DEiQk9C9.js → badge-BUEY53dV.js} +1 -1
  115. package/static/assets/{browser-modal-Dp1eMxt6.js → browser-modal-DCNdI4NT.js} +2 -2
  116. package/static/assets/{card-BCFxXzRk.js → card-BcPlIAH5.js} +1 -1
  117. package/static/assets/{chevron-left-C25izNzZ.js → chevron-left-FMmNe7yP.js} +1 -1
  118. package/static/assets/{plus-iamYJu5V.js → chevron-up-CqM3won3.js} +2 -2
  119. package/static/assets/{chevrons-up-DqbWMOjv.js → chevrons-up-DTvCkIHc.js} +1 -1
  120. package/static/assets/{circle-alert-CMRMPnbY.js → circle-alert-dseM-Ib7.js} +1 -1
  121. package/static/assets/{circle-check-big-NI18oHuP.js → circle-check-big-jKg34xC-.js} +1 -1
  122. package/static/assets/{circle-check-D5wZZPvj.js → circle-check-eyo6pBP1.js} +1 -1
  123. package/static/assets/{circle-play-BhVU869u.js → circle-play-BrY_lNiH.js} +1 -1
  124. package/static/assets/{circle-x-BXDB-G_q.js → circle-x-uqmzEce1.js} +1 -1
  125. package/static/assets/{clipboard-DC2xmNVx.js → clipboard-tzPFoieb.js} +1 -1
  126. package/static/assets/{clock-CeCp7Pz1.js → clock-Bjc06QBM.js} +1 -1
  127. package/static/assets/code-DrYqPukx.js +6 -0
  128. package/static/assets/{download-ZF_XbTIA.js → download-Bg__QCLT.js} +1 -1
  129. package/static/assets/{external-link-CYBz87-P.js → external-link-CNDy2UUo.js} +1 -1
  130. package/static/assets/{eye-BT8MAvKY.js → eye-DLFBnC8t.js} +1 -1
  131. package/static/assets/{folder-git-2-BE9AIPnj.js → folder-git-2-DUqd0WRi.js} +1 -1
  132. package/static/assets/index-CHdBxVyk.css +2 -0
  133. package/static/assets/index-DFcWlnzl.js +487 -0
  134. package/static/assets/{info-DunFSp-x.js → info-D6jxZC5X.js} +1 -1
  135. package/static/assets/kiro-CX1mOsRO.js +17 -0
  136. package/static/assets/{label-vYhfrPMD.js → label-DBuh-ke5.js} +1 -1
  137. package/static/assets/{markdown-editor-BzZ8tCto.js → markdown-editor-B4YNQFT2.js} +1 -1
  138. package/static/assets/message-square-B5RWz_ff.js +6 -0
  139. package/static/assets/paperclip-4A_3MaPx.js +6 -0
  140. package/static/assets/{pause-BHonpdnw.js → pause-BzhKXHtR.js} +1 -1
  141. package/static/assets/{play-CCo7tau2.js → play-CHIf-Rcz.js} +1 -1
  142. package/static/assets/{radio-group-Db-pBuyW.js → radio-group-C1ct-VsJ.js} +1 -1
  143. package/static/assets/{refresh-cw-Bg7vQzOw.js → refresh-cw-B3OwrDUf.js} +1 -1
  144. package/static/assets/{search-CH2zaibZ.js → search-Cq1ksEdp.js} +1 -1
  145. package/static/assets/select-44mcS2_G.js +1 -0
  146. package/static/assets/{status-utils-BDOyevaX.js → status-utils-CDkPeVfP.js} +1 -1
  147. package/static/assets/{switch-CH-VOgPo.js → switch-CIwjYvCt.js} +1 -1
  148. package/static/assets/{tabs-XeRAjZYR.js → tabs-DTV6Su-h.js} +1 -1
  149. package/static/assets/{tag-CRP5nL5-.js → tag-p6yeowCW.js} +1 -1
  150. package/static/assets/{terminal-preview-DMJMuORo.js → terminal-preview-DN38x9Jm.js} +1 -1
  151. package/static/assets/use-terminal-BXJqOeJe.js +1 -0
  152. package/static/assets/video-BH5ChaoS.js +36 -0
  153. package/static/index.html +2 -2
  154. package/static/assets/ApiKeysSettingsPage-Chw9PNL5.js +0 -2
  155. package/static/assets/CodePage-CrokcH-S.js +0 -2
  156. package/static/assets/DashboardPage-BaSQQ8Nv.js +0 -41
  157. package/static/assets/GitSettingsPage-tKBXYAFm.js +0 -6
  158. package/static/assets/IdentityPage-D2yBjeYn.js +0 -11
  159. package/static/assets/IntegrationsSettingsPage-Bx8-0Ig4.js +0 -1
  160. package/static/assets/JobDetailPage-BQmTHled.js +0 -1
  161. package/static/assets/KnowledgeDetailPage-QMU2bC3L.js +0 -1
  162. package/static/assets/KnowledgeEditPage-DbMJVcLc.js +0 -1
  163. package/static/assets/KnowledgePage-aU1GxZSZ.js +0 -8
  164. package/static/assets/McpSettingsPage-C3WxFwRB.js +0 -1
  165. package/static/assets/NewKnowledgePage-Cbiswrw_.js +0 -9
  166. package/static/assets/NewProposalPage-B_sDMBTK.js +0 -90
  167. package/static/assets/ProjectEditPage-BznWiBBc.js +0 -11
  168. package/static/assets/PromptsSettingsPage-CY0-870a.js +0 -1
  169. package/static/assets/ProposalDetailPage-K4iMXHEg.js +0 -1
  170. package/static/assets/ProposalEditPage-jZOtCMqP.js +0 -1
  171. package/static/assets/ProposalsPage-C7n4-G05.js +0 -17
  172. package/static/assets/ResourcesPage-vB5-XkUv.js +0 -71
  173. package/static/assets/RoleEditPage-Cu7ZB3yj.js +0 -13
  174. package/static/assets/SchedulePage-Bkkf2wA0.js +0 -4
  175. package/static/assets/SkillDetailPage-5sDxf3Of.js +0 -1
  176. package/static/assets/SkillsPage-D2G7EfK8.js +0 -8
  177. package/static/assets/SourceInput-DlC0zwva.js +0 -1
  178. package/static/assets/TerminalPage--KZ7azvP.js +0 -1
  179. package/static/assets/TerminalSessionPage-ClvxK9ia.js +0 -13
  180. package/static/assets/UserPreferencesPage-CheOH7jZ.js +0 -1
  181. package/static/assets/UserSettingsPage-C8STDvfQ.js +0 -1
  182. package/static/assets/UtilitiesPage-9rTxR2md.js +0 -1
  183. package/static/assets/calendar-Cx5r9R7A.js +0 -6
  184. package/static/assets/droid-DqWsM2dp.js +0 -17
  185. package/static/assets/index-DVTgTsDa.css +0 -2
  186. package/static/assets/index-hqVgTgRB.js +0 -462
  187. package/static/assets/use-terminal-DuGZuvd2.js +0 -1
  188. package/static/assets/zap-BlzMp7dY.js +0 -6
@@ -0,0 +1,254 @@
1
+ import { Hono } from 'hono';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import matter from 'gray-matter';
5
+ import { getLogger, KnowledgeKinds } from '@lovelybunch/core/logging';
6
+ import { requireAuth } from '../../../../../middleware/auth.js';
7
+ // Helper function to generate a simple summary from content
8
+ function generateSummary(content, maxLines = 3, maxChars = 200) {
9
+ // Remove markdown formatting for cleaner summary
10
+ const cleanContent = content
11
+ .replace(/^#+\s+/gm, '') // Remove headings
12
+ .replace(/[*_`]/g, '') // Remove bold, italic, code
13
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
14
+ .trim();
15
+ // Get first few lines
16
+ const lines = cleanContent.split('\n').filter(line => line.trim().length > 0);
17
+ const summary = lines.slice(0, maxLines).join(' ');
18
+ // Truncate if too long
19
+ return summary.length > maxChars
20
+ ? summary.substring(0, maxChars) + '...'
21
+ : summary;
22
+ }
23
+ const app = new Hono();
24
+ function getKnowledgePath() {
25
+ let basePath;
26
+ if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
27
+ // Dev mode: use project root .nut directory
28
+ basePath = process.env.GAIT_DEV_ROOT;
29
+ }
30
+ else if (process.env.GAIT_DATA_PATH) {
31
+ // Production mode: use GAIT_DATA_PATH (set by CLI)
32
+ basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
33
+ }
34
+ else {
35
+ // Fallback: use current directory .nut
36
+ basePath = path.resolve(process.cwd(), '.nut');
37
+ }
38
+ return path.join(basePath, 'knowledge');
39
+ }
40
+ function generateFilename(title) {
41
+ // Convert title to filename-safe format
42
+ return title
43
+ .toLowerCase()
44
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special characters
45
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
46
+ .replace(/--+/g, '-') // Replace multiple hyphens with single
47
+ .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
48
+ + '.md';
49
+ }
50
+ /**
51
+ * GET /api/v1/knowledge/:filename
52
+ * Load a specific knowledge document
53
+ */
54
+ app.get('/:filename', async (c) => {
55
+ try {
56
+ const filename = c.req.param('filename');
57
+ const knowledgePath = getKnowledgePath();
58
+ const actualFilename = filename.endsWith('.md') ? filename : `${filename}.md`;
59
+ const filePath = path.join(knowledgePath, actualFilename);
60
+ const [fileContent, stats] = await Promise.all([
61
+ fs.readFile(filePath, 'utf-8'),
62
+ fs.stat(filePath)
63
+ ]);
64
+ const { data, content } = matter(fileContent);
65
+ // Extract title from metadata, first heading, or use filename
66
+ const title = data.title ||
67
+ content.match(/^#\s+(.+)$/m)?.[1] ||
68
+ actualFilename.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
69
+ const document = {
70
+ filename: actualFilename,
71
+ metadata: {
72
+ ...data,
73
+ title, // Include title in metadata
74
+ updated: stats.mtime.toISOString(),
75
+ tags: data.tags || [],
76
+ sources: data.sources || []
77
+ },
78
+ content,
79
+ title
80
+ };
81
+ return c.json({
82
+ success: true,
83
+ document
84
+ });
85
+ }
86
+ catch (error) {
87
+ if (error.code === 'ENOENT') {
88
+ return c.json({ success: false, error: 'Knowledge document not found' }, 404);
89
+ }
90
+ console.error('Error loading knowledge document:', error);
91
+ return c.json({ success: false, error: 'Failed to load knowledge document' }, 500);
92
+ }
93
+ });
94
+ /**
95
+ * PUT /api/v1/knowledge/:filename
96
+ * Update a specific knowledge document
97
+ */
98
+ app.put('/:filename', async (c) => {
99
+ try {
100
+ const filename = c.req.param('filename');
101
+ const body = await c.req.json();
102
+ const knowledgePath = getKnowledgePath();
103
+ const actualFilename = filename.endsWith('.md') ? filename : `${filename}.md`;
104
+ const filePath = path.join(knowledgePath, actualFilename);
105
+ // Check if file exists
106
+ try {
107
+ await fs.access(filePath);
108
+ }
109
+ catch {
110
+ return c.json({ success: false, error: 'Knowledge document not found' }, 404);
111
+ }
112
+ // Read current content
113
+ const currentContent = await fs.readFile(filePath, 'utf-8');
114
+ const { data: currentData, content: currentMarkdown } = matter(currentContent);
115
+ // Extract current title from markdown content or metadata
116
+ const currentTitle = currentMarkdown.match(/^#\s+(.+)$/m)?.[1] ||
117
+ currentData.title ||
118
+ actualFilename.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
119
+ // Prepare updated content
120
+ const updatedContent = body.content !== undefined ? body.content : currentMarkdown;
121
+ // Determine if title has changed
122
+ const newTitle = body.title !== undefined ? body.title : currentTitle;
123
+ const titleChanged = body.title !== undefined && body.title !== currentTitle;
124
+ const updatedMetadata = {
125
+ ...currentData,
126
+ ...body.metadata,
127
+ title: newTitle, // Store title in metadata
128
+ updated: new Date().toISOString(),
129
+ // Ensure these are arrays
130
+ tags: body.metadata?.tags !== undefined ? body.metadata.tags : (currentData.tags || []),
131
+ sources: body.metadata?.sources !== undefined ? body.metadata.sources : (currentData.sources || [])
132
+ };
133
+ // Handle title change - might need to rename file
134
+ let newFilename = actualFilename;
135
+ let newFilePath = filePath;
136
+ // Only rename file if title explicitly changed
137
+ if (titleChanged) {
138
+ newFilename = generateFilename(newTitle);
139
+ newFilePath = path.join(knowledgePath, newFilename);
140
+ // Check if new filename conflicts with existing file (unless it's the same file)
141
+ if (newFilename !== actualFilename) {
142
+ try {
143
+ await fs.access(newFilePath);
144
+ return c.json({ success: false, error: 'A document with this title already exists' }, 409);
145
+ }
146
+ catch {
147
+ // File doesn't exist, which is what we want
148
+ }
149
+ }
150
+ }
151
+ // Create the updated markdown content with frontmatter
152
+ const fileContent = matter.stringify(updatedContent, updatedMetadata);
153
+ // Write to new location (or same location if filename unchanged)
154
+ await fs.writeFile(newFilePath, fileContent, 'utf-8');
155
+ // If filename changed, delete old file
156
+ if (newFilename !== actualFilename) {
157
+ await fs.unlink(filePath);
158
+ }
159
+ // Log knowledge update event
160
+ try {
161
+ const session = await requireAuth(c);
162
+ const actor = session ? `human:${session.email}` : "human:unknown";
163
+ const logger = getLogger();
164
+ logger.log({
165
+ kind: KnowledgeKinds.UPDATE,
166
+ actor,
167
+ subject: `knowledge:${newFilename}`,
168
+ tags: ["knowledge", ...(updatedMetadata.tags || [])],
169
+ payload: {
170
+ filename: newFilename,
171
+ oldFilename: actualFilename !== newFilename ? actualFilename : undefined,
172
+ title: newTitle,
173
+ category: updatedMetadata.category,
174
+ summary: generateSummary(updatedContent),
175
+ }
176
+ });
177
+ }
178
+ catch (logError) {
179
+ console.error('Error logging knowledge update:', logError);
180
+ }
181
+ return c.json({
182
+ success: true,
183
+ document: {
184
+ filename: newFilename,
185
+ title: newTitle,
186
+ metadata: updatedMetadata,
187
+ content: updatedContent
188
+ }
189
+ });
190
+ }
191
+ catch (error) {
192
+ console.error('Error updating knowledge document:', error);
193
+ return c.json({ success: false, error: 'Failed to update knowledge document' }, 500);
194
+ }
195
+ });
196
+ /**
197
+ * DELETE /api/v1/knowledge/:filename
198
+ * Delete a specific knowledge document
199
+ */
200
+ app.delete('/:filename', async (c) => {
201
+ try {
202
+ const filename = c.req.param('filename');
203
+ const knowledgePath = getKnowledgePath();
204
+ const actualFilename = filename.endsWith('.md') ? filename : `${filename}.md`;
205
+ const filePath = path.join(knowledgePath, actualFilename);
206
+ // Check if file exists
207
+ try {
208
+ await fs.access(filePath);
209
+ }
210
+ catch {
211
+ return c.json({ success: false, error: 'Knowledge document not found' }, 404);
212
+ }
213
+ // Read file before deletion for logging
214
+ let title;
215
+ try {
216
+ const fileContent = await fs.readFile(filePath, 'utf-8');
217
+ const { content } = matter(fileContent);
218
+ title = content.match(/^#\s+(.+)$/m)?.[1];
219
+ }
220
+ catch {
221
+ // Ignore if we can't read the file
222
+ }
223
+ // Delete the file
224
+ await fs.unlink(filePath);
225
+ // Log knowledge deletion event
226
+ try {
227
+ const session = await requireAuth(c);
228
+ const actor = session ? `human:${session.email}` : "human:unknown";
229
+ const logger = getLogger();
230
+ logger.log({
231
+ kind: KnowledgeKinds.DELETE,
232
+ actor,
233
+ subject: `knowledge:${actualFilename}`,
234
+ tags: ["knowledge"],
235
+ payload: {
236
+ filename: actualFilename,
237
+ title,
238
+ }
239
+ });
240
+ }
241
+ catch (logError) {
242
+ console.error('Error logging knowledge deletion:', logError);
243
+ }
244
+ return c.json({
245
+ success: true,
246
+ message: 'Knowledge document deleted successfully'
247
+ });
248
+ }
249
+ catch (error) {
250
+ console.error('Error deleting knowledge document:', error);
251
+ return c.json({ success: false, error: 'Failed to delete knowledge document' }, 500);
252
+ }
253
+ });
254
+ export default app;
@@ -0,0 +1 @@
1
+ export { default } from './route.js';
@@ -0,0 +1 @@
1
+ export { default } from './route.js';
@@ -0,0 +1,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default app;
@@ -0,0 +1,176 @@
1
+ import { Hono } from 'hono';
2
+ import { promises as fs } from 'fs';
3
+ import path from 'path';
4
+ import matter from 'gray-matter';
5
+ import filenameRoute from './[filename]/index.js';
6
+ import { getLogger, KnowledgeKinds } from '@lovelybunch/core/logging';
7
+ import { requireAuth } from '../../../../middleware/auth.js';
8
+ // Helper function to generate a simple summary from content
9
+ function generateSummary(content, maxLines = 3, maxChars = 200) {
10
+ // Remove markdown formatting for cleaner summary
11
+ const cleanContent = content
12
+ .replace(/^#+\s+/gm, '') // Remove headings
13
+ .replace(/[*_`]/g, '') // Remove bold, italic, code
14
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Convert links to text
15
+ .trim();
16
+ // Get first few lines
17
+ const lines = cleanContent.split('\n').filter(line => line.trim().length > 0);
18
+ const summary = lines.slice(0, maxLines).join(' ');
19
+ // Truncate if too long
20
+ return summary.length > maxChars
21
+ ? summary.substring(0, maxChars) + '...'
22
+ : summary;
23
+ }
24
+ const app = new Hono();
25
+ function getKnowledgePath() {
26
+ let basePath;
27
+ if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
28
+ // Dev mode: use project root .nut directory
29
+ basePath = process.env.GAIT_DEV_ROOT;
30
+ }
31
+ else if (process.env.GAIT_DATA_PATH) {
32
+ // Production mode: use GAIT_DATA_PATH (set by CLI)
33
+ basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
34
+ }
35
+ else {
36
+ // Fallback: use current directory .nut
37
+ basePath = path.resolve(process.cwd(), '.nut');
38
+ }
39
+ return path.join(basePath, 'knowledge');
40
+ }
41
+ function generateFilename(title) {
42
+ // Convert title to filename-safe format
43
+ return title
44
+ .toLowerCase()
45
+ .replace(/[^a-z0-9\s-]/g, '') // Remove special characters
46
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
47
+ .replace(/--+/g, '-') // Replace multiple hyphens with single
48
+ .replace(/^-|-$/g, '') // Remove leading/trailing hyphens
49
+ + '.md';
50
+ }
51
+ /**
52
+ * GET /api/v1/knowledge
53
+ * Load all knowledge documents
54
+ */
55
+ app.get('/', async (c) => {
56
+ try {
57
+ const knowledgePath = getKnowledgePath();
58
+ // Ensure directory exists
59
+ await fs.mkdir(knowledgePath, { recursive: true });
60
+ const files = await fs.readdir(knowledgePath);
61
+ const documents = await Promise.all(files
62
+ .filter(file => file.endsWith('.md'))
63
+ .map(async (file) => {
64
+ const filePath = path.join(knowledgePath, file);
65
+ const [fileContent, stats] = await Promise.all([
66
+ fs.readFile(filePath, 'utf-8'),
67
+ fs.stat(filePath)
68
+ ]);
69
+ const { data, content } = matter(fileContent);
70
+ // Extract title from metadata, first heading, or use filename
71
+ const title = data.title ||
72
+ content.match(/^#\s+(.+)$/m)?.[1] ||
73
+ file.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase());
74
+ return {
75
+ filename: file,
76
+ metadata: {
77
+ ...data,
78
+ title, // Include title in metadata
79
+ updated: stats.mtime.toISOString(),
80
+ tags: data.tags || [],
81
+ sources: data.sources || []
82
+ },
83
+ content,
84
+ title
85
+ };
86
+ }));
87
+ return c.json({
88
+ success: true,
89
+ documents: documents.sort((a, b) => a.filename.localeCompare(b.filename))
90
+ });
91
+ }
92
+ catch (error) {
93
+ console.error('Error loading knowledge documents:', error);
94
+ return c.json({
95
+ success: false,
96
+ error: 'Failed to load knowledge documents',
97
+ documents: []
98
+ }, 500);
99
+ }
100
+ });
101
+ /**
102
+ * POST /api/v1/knowledge
103
+ * Create a new knowledge document
104
+ */
105
+ app.post('/', async (c) => {
106
+ try {
107
+ const body = await c.req.json();
108
+ if (!body.title || !body.content) {
109
+ return c.json({ success: false, error: 'Title and content are required' }, 400);
110
+ }
111
+ const knowledgePath = getKnowledgePath();
112
+ await fs.mkdir(knowledgePath, { recursive: true });
113
+ const filename = generateFilename(body.title);
114
+ const filePath = path.join(knowledgePath, filename);
115
+ // Check if file already exists
116
+ try {
117
+ await fs.access(filePath);
118
+ return c.json({ success: false, error: 'A document with this title already exists' }, 409);
119
+ }
120
+ catch {
121
+ // File doesn't exist, which is what we want
122
+ }
123
+ // Prepare frontmatter
124
+ const now = new Date();
125
+ const frontmatter = {
126
+ version: '1.0',
127
+ title: body.title, // Store title in metadata
128
+ updated: now.toISOString(),
129
+ type: 'knowledge',
130
+ category: body.metadata?.category || 'general',
131
+ tags: body.metadata?.tags || [],
132
+ sources: body.metadata?.sources || [],
133
+ ...body.metadata
134
+ };
135
+ // Create the markdown content with frontmatter
136
+ const fileContent = matter.stringify(body.content, frontmatter);
137
+ await fs.writeFile(filePath, fileContent, 'utf-8');
138
+ // Log knowledge creation event
139
+ try {
140
+ const session = await requireAuth(c);
141
+ const actor = session ? `human:${session.email}` : "human:unknown";
142
+ const logger = getLogger();
143
+ logger.log({
144
+ kind: KnowledgeKinds.CREATE,
145
+ actor,
146
+ subject: `knowledge:${filename}`,
147
+ tags: ["knowledge", ...(frontmatter.tags || [])],
148
+ payload: {
149
+ filename,
150
+ title: body.title,
151
+ category: frontmatter.category,
152
+ summary: generateSummary(body.content),
153
+ }
154
+ });
155
+ }
156
+ catch (logError) {
157
+ console.error('Error logging knowledge creation:', logError);
158
+ }
159
+ return c.json({
160
+ success: true,
161
+ document: {
162
+ filename,
163
+ title: body.title,
164
+ metadata: frontmatter,
165
+ content: body.content
166
+ }
167
+ }, 201);
168
+ }
169
+ catch (error) {
170
+ console.error('Error creating knowledge document:', error);
171
+ return c.json({ success: false, error: 'Failed to create knowledge document' }, 500);
172
+ }
173
+ });
174
+ // Register filename route for individual documents
175
+ app.route('/', filenameRoute);
176
+ export default app;
@@ -0,0 +1,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const mailRoutes: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export { mailRoutes };
@@ -0,0 +1,23 @@
1
+ import { Hono } from 'hono';
2
+ import { listMailHandler, getMailHandler, setMailStatusHandler, setMailActionHandler, replyMailHandler, sendMailHandler, deleteMailHandler, inboundWebhookHandler, getMailProcessingHandler, stopMailProcessingHandler, } from './route.js';
3
+ const mailRoutes = new Hono();
4
+ // Resend inbound webhook
5
+ mailRoutes.post('/inbound', inboundWebhookHandler);
6
+ // Send email (coming soon)
7
+ mailRoutes.post('/send', sendMailHandler);
8
+ // Processing status and control
9
+ mailRoutes.get('/:id/processing', getMailProcessingHandler);
10
+ mailRoutes.post('/:id/processing/stop', stopMailProcessingHandler);
11
+ // Set email status (read/unread)
12
+ mailRoutes.put('/:id/status', setMailStatusHandler);
13
+ // Set agent action summary
14
+ mailRoutes.put('/:id/action', setMailActionHandler);
15
+ // Reply to email
16
+ mailRoutes.post('/:id/reply', replyMailHandler);
17
+ // List emails in folder
18
+ mailRoutes.get('/:folder', listMailHandler);
19
+ // Get specific email in folder
20
+ mailRoutes.get('/:folder/:id', getMailHandler);
21
+ // Delete email from folder
22
+ mailRoutes.delete('/:folder/:id', deleteMailHandler);
23
+ export { mailRoutes };