@lovelybunch/api 1.0.75-alpha.8 → 1.0.75-alpha.9

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 (131) hide show
  1. package/dist/lib/terminal/terminal-manager.d.ts +1 -3
  2. package/dist/lib/terminal/terminal-manager.js +1 -52
  3. package/dist/routes/api/v1/ai/route.js +323 -2
  4. package/dist/routes/api/v1/chats/[id]/index.js +2 -1
  5. package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
  6. package/dist/routes/api/v1/chats/[id]/route.js +30 -1
  7. package/dist/routes/api/v1/context/index.js +0 -2
  8. package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
  9. package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
  10. package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
  11. package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
  12. package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
  13. package/dist/routes/api/v1/knowledge/index.js +1 -0
  14. package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
  15. package/dist/routes/api/v1/knowledge/route.js +176 -0
  16. package/dist/routes/api/v1/mcp/index.js +78 -3
  17. package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
  18. package/dist/server-with-static.js +2 -0
  19. package/dist/server.js +2 -0
  20. package/package.json +4 -4
  21. package/static/assets/{ActivityPage-DSSML9J-.js → ActivityPage-OxRci_V2.js} +1 -1
  22. package/static/assets/{ApiKeysSettingsPage-Chw9PNL5.js → ApiKeysSettingsPage-C0evI19e.js} +2 -2
  23. package/static/assets/{ArchitectureEditPage-CIjqkpMz.js → ArchitectureEditPage-D7xcH6dY.js} +4 -4
  24. package/static/assets/{ArchitecturePage-Db__w054.js → ArchitecturePage-pvnlX-NW.js} +1 -1
  25. package/static/assets/{AuthSettingsPage-Bpooi8Z0.js → AuthSettingsPage-Bu0CZ1rY.js} +2 -2
  26. package/static/assets/{CallbackPage-BGLKeyjv.js → CallbackPage-D0lkjxCT.js} +1 -1
  27. package/static/assets/CodePage-BJ4PC5nb.js +2 -0
  28. package/static/assets/{CollapsibleSection-B6RO5o5R.js → CollapsibleSection-Bt_ZLnJc.js} +1 -1
  29. package/static/assets/DashboardPage-BiffPdmj.js +41 -0
  30. package/static/assets/{GitPage-DxjLaRWe.js → GitPage-TrTxZ27J.js} +2 -2
  31. package/static/assets/{GitSettingsPage-tKBXYAFm.js → GitSettingsPage-D7q5xQd_.js} +2 -2
  32. package/static/assets/{IdentityPage-D2yBjeYn.js → IdentityPage-CY0Ak2j0.js} +3 -3
  33. package/static/assets/{ImplementationStepsEditor-DWjDyZzP.js → ImplementationStepsEditor-Ctx0CvbU.js} +2 -2
  34. package/static/assets/IntegrationsSettingsPage-C2wJVdM7.js +1 -0
  35. package/static/assets/{JobDetailPage-BQmTHled.js → JobDetailPage-Phx_IlKX.js} +1 -1
  36. package/static/assets/KnowledgeDetailPage-BdTUfWqj.js +1 -0
  37. package/static/assets/KnowledgeEditPage-D8XK4IUf.js +1 -0
  38. package/static/assets/KnowledgePage-Ci9G7Br-.js +8 -0
  39. package/static/assets/{LoginPage-DptfKsWo.js → LoginPage-Dqxd7cTa.js} +1 -1
  40. package/static/assets/McpSettingsPage-10n35zXi.js +1 -0
  41. package/static/assets/NewKnowledgePage-BlJzzuh7.js +9 -0
  42. package/static/assets/{NewProposalPage-B_sDMBTK.js → NewProposalPage-BP7Ttoxk.js} +2 -2
  43. package/static/assets/{NewSkillPage-Cwy2MSr9.js → NewSkillPage-ByqN--mH.js} +1 -1
  44. package/static/assets/ProjectEditPage-DKJTY2uc.js +11 -0
  45. package/static/assets/{ProjectPage-DgUr4bVU.js → ProjectPage-2VblKCWz.js} +1 -1
  46. package/static/assets/PromptsSettingsPage-B4mOhXuo.js +1 -0
  47. package/static/assets/ProposalDetailPage-m3ysyzpj.js +1 -0
  48. package/static/assets/ProposalEditPage-3XVg_paW.js +1 -0
  49. package/static/assets/{ProposalsPage-C7n4-G05.js → ProposalsPage-B3u0aFFz.js} +3 -3
  50. package/static/assets/ResourceDetailPage-somBLUpC.js +1 -0
  51. package/static/assets/ResourcesPage-2BbjIWfF.js +41 -0
  52. package/static/assets/{RoleEditPage-Cu7ZB3yj.js → RoleEditPage-CLzX7Xhi.js} +1 -1
  53. package/static/assets/{RolePage-Sc-GFiL2.js → RolePage-qXWXZ2FZ.js} +1 -1
  54. package/static/assets/{RulesSettingsPage-DdMCzh9j.js → RulesSettingsPage-BtM7p8F6.js} +3 -3
  55. package/static/assets/SchedulePage-4tFcIBSs.js +4 -0
  56. package/static/assets/{SkillDetailPage-5sDxf3Of.js → SkillDetailPage-CroSdaju.js} +1 -1
  57. package/static/assets/{SkillEditPage-BDd2CtAS.js → SkillEditPage-Czlo8WWT.js} +1 -1
  58. package/static/assets/{SkillsPage-D2G7EfK8.js → SkillsPage-CgULbcI-.js} +2 -2
  59. package/static/assets/{SkillsSettingsPage-1N0JQOYc.js → SkillsSettingsPage-DKtpy7qk.js} +1 -1
  60. package/static/assets/SourceInput-BITn1Y15.js +1 -0
  61. package/static/assets/{TagInput-D_SdcypZ.js → TagInput-BK91_M1N.js} +1 -1
  62. package/static/assets/TerminalPage-8fwvnOo2.js +1 -0
  63. package/static/assets/TerminalSessionPage-BhO5U48p.js +13 -0
  64. package/static/assets/UserPreferencesPage-DrgYEcxO.js +1 -0
  65. package/static/assets/UserSettingsPage-Dj6lKLi8.js +1 -0
  66. package/static/assets/UtilitiesPage-Djr4qT5L.js +1 -0
  67. package/static/assets/{alert-BD5jo3SI.js → alert-CsMvyYoX.js} +1 -1
  68. package/static/assets/{arrow-down-BxcoVp6S.js → arrow-down-BZnfbld8.js} +1 -1
  69. package/static/assets/{arrow-left-CdM_IPng.js → arrow-left-WGBYWq3h.js} +1 -1
  70. package/static/assets/{arrow-up-BOJ6ob9X.js → arrow-up-BByVUPE7.js} +1 -1
  71. package/static/assets/{badge-DEiQk9C9.js → badge-AwLOflf5.js} +1 -1
  72. package/static/assets/{browser-modal-Dp1eMxt6.js → browser-modal-BzGNFfTG.js} +2 -2
  73. package/static/assets/{card-BCFxXzRk.js → card-SN5gKnu7.js} +1 -1
  74. package/static/assets/{chevron-left-C25izNzZ.js → chevron-left-C7uNq9l_.js} +1 -1
  75. package/static/assets/{plus-iamYJu5V.js → chevron-up-CHdIiLxL.js} +2 -2
  76. package/static/assets/{chevrons-up-DqbWMOjv.js → chevrons-up-TXwQuoUN.js} +1 -1
  77. package/static/assets/{circle-alert-CMRMPnbY.js → circle-alert-37E5gU9K.js} +1 -1
  78. package/static/assets/{circle-check-D5wZZPvj.js → circle-check-D02pWDME.js} +1 -1
  79. package/static/assets/{circle-check-big-NI18oHuP.js → circle-check-big-nY4PntB5.js} +1 -1
  80. package/static/assets/{circle-play-BhVU869u.js → circle-play-7EXFLo4F.js} +1 -1
  81. package/static/assets/{circle-x-BXDB-G_q.js → circle-x-By4JoTHB.js} +1 -1
  82. package/static/assets/{clipboard-DC2xmNVx.js → clipboard-BdymjxLO.js} +1 -1
  83. package/static/assets/{clock-CeCp7Pz1.js → clock-HDu44KTo.js} +1 -1
  84. package/static/assets/{download-ZF_XbTIA.js → download-Cv2G2Eg9.js} +1 -1
  85. package/static/assets/{droid-DqWsM2dp.js → droid-CPteN3f9.js} +1 -1
  86. package/static/assets/{external-link-CYBz87-P.js → external-link-DwMXcCCj.js} +1 -1
  87. package/static/assets/{eye-BT8MAvKY.js → eye-DYnjJzdb.js} +1 -1
  88. package/static/assets/{folder-git-2-BE9AIPnj.js → folder-git-2-COeWFPHS.js} +1 -1
  89. package/static/assets/index-9Tv-j_Ga.js +472 -0
  90. package/static/assets/index-GFQ5RqVh.css +2 -0
  91. package/static/assets/{info-DunFSp-x.js → info-BmtuPMhv.js} +1 -1
  92. package/static/assets/{label-vYhfrPMD.js → label-TGqbNfMO.js} +1 -1
  93. package/static/assets/{markdown-editor-BzZ8tCto.js → markdown-editor-ls1JPK_e.js} +1 -1
  94. package/static/assets/{pause-BHonpdnw.js → pause-CAWbvTiL.js} +1 -1
  95. package/static/assets/{play-CCo7tau2.js → play-DF_Qeu0H.js} +1 -1
  96. package/static/assets/{radio-group-Db-pBuyW.js → radio-group-DYTbywtK.js} +1 -1
  97. package/static/assets/{refresh-cw-Bg7vQzOw.js → refresh-cw-BFZxHqbC.js} +1 -1
  98. package/static/assets/{search-CH2zaibZ.js → search-Dr90tbch.js} +1 -1
  99. package/static/assets/select-Cs5qtMYV.js +1 -0
  100. package/static/assets/{switch-CH-VOgPo.js → switch-4TDb6YiQ.js} +1 -1
  101. package/static/assets/{tabs-XeRAjZYR.js → tabs-BrbEvF4V.js} +1 -1
  102. package/static/assets/{tag-CRP5nL5-.js → tag-DrQkepeD.js} +1 -1
  103. package/static/assets/{terminal-preview-DMJMuORo.js → terminal-preview-uuKF9_x4.js} +1 -1
  104. package/static/assets/use-terminal-BG5UXuVE.js +1 -0
  105. package/static/assets/video-DYA2WfbA.js +36 -0
  106. package/static/assets/{zap-BlzMp7dY.js → zap-h9QOsasv.js} +1 -1
  107. package/static/index.html +2 -2
  108. package/static/assets/CodePage-CrokcH-S.js +0 -2
  109. package/static/assets/DashboardPage-BaSQQ8Nv.js +0 -41
  110. package/static/assets/IntegrationsSettingsPage-Bx8-0Ig4.js +0 -1
  111. package/static/assets/KnowledgeDetailPage-QMU2bC3L.js +0 -1
  112. package/static/assets/KnowledgeEditPage-DbMJVcLc.js +0 -1
  113. package/static/assets/KnowledgePage-aU1GxZSZ.js +0 -8
  114. package/static/assets/McpSettingsPage-C3WxFwRB.js +0 -1
  115. package/static/assets/NewKnowledgePage-Cbiswrw_.js +0 -9
  116. package/static/assets/ProjectEditPage-BznWiBBc.js +0 -11
  117. package/static/assets/PromptsSettingsPage-CY0-870a.js +0 -1
  118. package/static/assets/ProposalDetailPage-K4iMXHEg.js +0 -1
  119. package/static/assets/ProposalEditPage-jZOtCMqP.js +0 -1
  120. package/static/assets/ResourcesPage-vB5-XkUv.js +0 -71
  121. package/static/assets/SchedulePage-Bkkf2wA0.js +0 -4
  122. package/static/assets/SourceInput-DlC0zwva.js +0 -1
  123. package/static/assets/TerminalPage--KZ7azvP.js +0 -1
  124. package/static/assets/TerminalSessionPage-ClvxK9ia.js +0 -13
  125. package/static/assets/UserPreferencesPage-CheOH7jZ.js +0 -1
  126. package/static/assets/UserSettingsPage-C8STDvfQ.js +0 -1
  127. package/static/assets/UtilitiesPage-9rTxR2md.js +0 -1
  128. package/static/assets/calendar-Cx5r9R7A.js +0 -6
  129. package/static/assets/index-DVTgTsDa.css +0 -2
  130. package/static/assets/index-hqVgTgRB.js +0 -462
  131. package/static/assets/use-terminal-DuGZuvd2.js +0 -1
@@ -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;
@@ -3,7 +3,7 @@ import { promises as fs } from 'fs';
3
3
  import path from 'path';
4
4
  import { ZodError } from 'zod';
5
5
  import { listProposals, getProposal, createProposal, updateProposal, deleteProposal, getContext, updateContext, appendContext, replaceContextSection, listKnowledge, getKnowledge, createKnowledge, updateKnowledge, listEvents, } from '@lovelybunch/core';
6
- import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
6
+ import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool, resourcesTool } from '@lovelybunch/mcp';
7
7
  import { FileStorageAdapter } from '../../../../lib/storage/file-storage.js';
8
8
  const app = new Hono();
9
9
  const storage = new FileStorageAdapter();
@@ -85,7 +85,8 @@ app.get('/', async (c) => {
85
85
  activity_events: eventsTool,
86
86
  project_context: projectContextTool,
87
87
  architecture_context: architectureContextTool,
88
- role_context: roleContextTool
88
+ role_context: roleContextTool,
89
+ resources: resourcesTool
89
90
  };
90
91
  return c.json({
91
92
  success: true,
@@ -113,7 +114,8 @@ app.get('/schema', async (c) => {
113
114
  activity_events: eventsTool,
114
115
  project_context: projectContextTool,
115
116
  architecture_context: architectureContextTool,
116
- role_context: roleContextTool
117
+ role_context: roleContextTool,
118
+ resources: resourcesTool
117
119
  }
118
120
  };
119
121
  return c.json(schema);
@@ -151,6 +153,9 @@ app.post('/execute', async (c) => {
151
153
  if (tool === 'role_context') {
152
154
  return await executeRoleContextTool(c, args);
153
155
  }
156
+ if (tool === 'resources') {
157
+ return await executeResourcesTool(c, args);
158
+ }
154
159
  return c.json({ success: false, error: 'Unknown tool' }, 400);
155
160
  }
156
161
  catch (error) {
@@ -449,6 +454,76 @@ async function executeArchitectureContextTool(c, args) {
449
454
  async function executeRoleContextTool(c, args) {
450
455
  return executeContextTool(c, 'role', args);
451
456
  }
457
+ // Resources tool executor — proxies to the resource API endpoints on the same server
458
+ async function executeResourcesTool(c, args) {
459
+ const { operation, query, type_filter, resource_id, prompt, model, aspect_ratio, text, voice, duration, resolution, url, tags, description } = args;
460
+ const port = process.env.PORT ? parseInt(process.env.PORT) : 3001;
461
+ const apiBase = `http://127.0.0.1:${port}`;
462
+ try {
463
+ switch (operation) {
464
+ case 'list': {
465
+ const response = await fetch(`${apiBase}/api/v1/resources`);
466
+ const result = await response.json();
467
+ if (!result.success || !result.data) {
468
+ return c.json({ success: false, error: 'Failed to list resources' }, 500);
469
+ }
470
+ let resources = result.data;
471
+ if (type_filter) {
472
+ const prefix = type_filter.toLowerCase();
473
+ resources = resources.filter((r) => r.type?.toLowerCase().startsWith(prefix));
474
+ }
475
+ if (query) {
476
+ const q = query.toLowerCase();
477
+ resources = resources.filter((r) => r.name?.toLowerCase().includes(q) ||
478
+ r.metadata?.description?.toLowerCase().includes(q) ||
479
+ r.metadata?.tags?.some((tag) => tag.toLowerCase().includes(q)));
480
+ }
481
+ return c.json({
482
+ success: true,
483
+ data: resources.map((r) => ({
484
+ id: r.id, name: r.name, type: r.type, size: r.size,
485
+ tags: r.metadata?.tags, description: r.metadata?.description
486
+ })),
487
+ count: resources.length,
488
+ message: query ? `Found ${resources.length} resources matching "${query}"` : `Found ${resources.length} resources`
489
+ });
490
+ }
491
+ case 'get': {
492
+ if (!resource_id) {
493
+ return c.json({ success: false, error: 'resource_id is required for get operation' }, 400);
494
+ }
495
+ const response = await fetch(`${apiBase}/api/v1/resources`);
496
+ const result = await response.json();
497
+ if (!result.success || !result.data) {
498
+ return c.json({ success: false, error: 'Failed to fetch resources' }, 500);
499
+ }
500
+ const resource = result.data.find((r) => r.id === resource_id);
501
+ if (!resource) {
502
+ return c.json({ success: false, error: `Resource '${resource_id}' not found` }, 404);
503
+ }
504
+ return c.json({ success: true, data: resource, message: `Retrieved resource ${resource.name}` });
505
+ }
506
+ // For write operations, return a not-implemented message since these require
507
+ // long-running Replicate calls that are better handled via the AI route's
508
+ // tool execution (which runs within streamText's tool loop).
509
+ case 'generate_image':
510
+ case 'generate_audio':
511
+ case 'generate_video':
512
+ case 'add_from_url': {
513
+ return c.json({
514
+ success: false,
515
+ error: `The '${operation}' operation is only available through the AI assistant chat. Use the assistant sidebar to generate or add resources.`
516
+ }, 400);
517
+ }
518
+ default:
519
+ return c.json({ success: false, error: `Unknown operation: ${operation}` }, 400);
520
+ }
521
+ }
522
+ catch (error) {
523
+ console.error('Error executing resources tool:', error);
524
+ return c.json({ success: false, error: error.message || 'Resources tool execution failed' }, 500);
525
+ }
526
+ }
452
527
  /**
453
528
  * GET /api/v1/mcp/raw-config
454
529
  * Returns the raw MCP configuration for editing in settings UI
@@ -4,7 +4,7 @@ export async function POST(c) {
4
4
  try {
5
5
  const proposalId = c.req.param('proposalId');
6
6
  const body = await c.req.json().catch(() => ({}));
7
- const { enableLogging = false, startupCommand } = body;
7
+ const { startupCommand } = body;
8
8
  if (!proposalId) {
9
9
  return c.json({ error: 'Proposal ID is required' }, 400);
10
10
  }
@@ -13,7 +13,7 @@ export async function POST(c) {
13
13
  const shellPreference = (userSettings.preferences.terminalShell || 'bash');
14
14
  // Create a new terminal session for this proposal
15
15
  const terminalManager = getGlobalTerminalManager();
16
- const session = await terminalManager.createSession(proposalId, enableLogging, shellPreference, startupCommand);
16
+ const session = await terminalManager.createSession(proposalId, shellPreference, startupCommand);
17
17
  return c.json({
18
18
  sessionId: session.id,
19
19
  proposalId: session.proposalId,
@@ -169,6 +169,7 @@ import resources from './routes/api/v1/resources/index.js';
169
169
  import resourcesById from './routes/api/v1/resources/[id]/index.js';
170
170
  import resourcesThumbnail from './routes/api/v1/resources/[id]/thumbnail/index.js';
171
171
  import context from './routes/api/v1/context/index.js';
172
+ import knowledge from './routes/api/v1/knowledge/index.js';
172
173
  import config from './routes/api/v1/config/index.js';
173
174
  import user from './routes/api/v1/user/index.js';
174
175
  import skills from './routes/api/v1/skills/index.js';
@@ -197,6 +198,7 @@ app.route('/api/v1/resources', resources);
197
198
  app.route('/api/v1/resources/:id', resourcesById);
198
199
  app.route('/api/v1/resources/:id/thumbnail', resourcesThumbnail);
199
200
  app.route('/api/v1/context', context);
201
+ app.route('/api/v1/knowledge', knowledge);
200
202
  app.route('/api/v1/config', config);
201
203
  app.route('/api/v1/user', user);
202
204
  app.route('/api/v1/skills', skills);
package/dist/server.js CHANGED
@@ -168,6 +168,7 @@ import resources from './routes/api/v1/resources/index.js';
168
168
  import resourcesById from './routes/api/v1/resources/[id]/index.js';
169
169
  import resourcesThumbnail from './routes/api/v1/resources/[id]/thumbnail/index.js';
170
170
  import context from './routes/api/v1/context/index.js';
171
+ import knowledge from './routes/api/v1/knowledge/index.js';
171
172
  import config from './routes/api/v1/config/index.js';
172
173
  import user from './routes/api/v1/user/index.js';
173
174
  import skills from './routes/api/v1/skills/index.js';
@@ -195,6 +196,7 @@ app.route('/api/v1/resources', resources);
195
196
  app.route('/api/v1/resources/:id', resourcesById);
196
197
  app.route('/api/v1/resources/:id/thumbnail', resourcesThumbnail);
197
198
  app.route('/api/v1/context', context);
199
+ app.route('/api/v1/knowledge', knowledge);
198
200
  app.route('/api/v1/config', config);
199
201
  app.route('/api/v1/user', user);
200
202
  app.route('/api/v1/skills', skills);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovelybunch/api",
3
- "version": "1.0.75-alpha.8",
3
+ "version": "1.0.75-alpha.9",
4
4
  "type": "module",
5
5
  "main": "dist/server-with-static.js",
6
6
  "exports": {
@@ -37,9 +37,9 @@
37
37
  "@ai-sdk/anthropic": "^3.0.41",
38
38
  "@hono/node-server": "^1.13.7",
39
39
  "@hono/node-ws": "^1.0.6",
40
- "@lovelybunch/core": "^1.0.75-alpha.8",
41
- "@lovelybunch/mcp": "^1.0.75-alpha.8",
42
- "@lovelybunch/types": "^1.0.75-alpha.8",
40
+ "@lovelybunch/core": "^1.0.75-alpha.9",
41
+ "@lovelybunch/mcp": "^1.0.75-alpha.9",
42
+ "@lovelybunch/types": "^1.0.75-alpha.9",
43
43
  "adm-zip": "^0.5.16",
44
44
  "ai": "^6.0.79",
45
45
  "arctic": "^1.9.2",
@@ -1 +1 @@
1
- import{r as a,A as p,j as e,H as v,bh as y,B as N,v as w,C as b}from"./index-hqVgTgRB.js";import{C as c,a as d,b as k,c as C}from"./card-BCFxXzRk.js";import{B as m}from"./badge-DEiQk9C9.js";import{R as E}from"./refresh-cw-Bg7vQzOw.js";const A=5e3;function $(){const[n,o]=a.useState([]),[i,x]=a.useState(!0),[h,u]=a.useState(new Set),l=a.useCallback(async()=>{x(!0);try{const s=await fetch(`${p}/api/v1/events?limit=${A}`);if(!s.ok)throw new Error("Failed to load events");const t=await s.json();o(Array.isArray(t.items)?[...t.items].reverse():[])}catch(s){console.error("Failed to load events:",s),o([])}finally{x(!1)}},[]),g=a.useCallback(s=>{u(t=>{const r=new Set(t);return r.has(s)?r.delete(s):r.add(s),r})},[]);a.useEffect(()=>{l()},[l]);const f=s=>{if(!s)return"Unknown time";try{return new Date(s).toLocaleString()}catch{return s}},j=s=>{switch(s?.toLowerCase()){case"error":return"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200";case"warn":case"warning":return"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200";case"info":return"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200";case"debug":return"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200";default:return"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"}};return i?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsx("p",{className:"text-muted-foreground",children:"View system activity and events"})]}),e.jsx(c,{children:e.jsx(d,{className:"pt-6",children:e.jsxs("div",{className:"flex items-center justify-center",children:[e.jsx(v,{className:"h-8 w-8 animate-spin text-muted-foreground"}),e.jsx("span",{className:"ml-2 text-muted-foreground",children:"Loading events..."})]})})})]}):n.length===0?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsx("p",{className:"text-muted-foreground",children:"View system activity and events"})]}),e.jsx(c,{children:e.jsx(d,{className:"pt-6",children:e.jsxs("div",{className:"text-center",children:[e.jsx(y,{className:"mx-auto h-12 w-12 text-muted-foreground"}),e.jsx("h3",{className:"mt-4 text-lg font-semibold",children:"No Events Found"}),e.jsx("p",{className:"mt-2 text-sm text-muted-foreground",children:"Activity events will appear here as they occur."})]})})})]}):e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsxs("p",{className:"text-muted-foreground",children:["View system activity and events (",n.length," events)"]})]}),e.jsxs(N,{variant:"outline",size:"sm",onClick:()=>void l(),disabled:i,children:[e.jsx(E,{className:`h-4 w-4 mr-2 ${i?"animate-spin":""}`}),"Refresh"]})]}),e.jsx("div",{className:"space-y-3",children:n.map(s=>{const t=h.has(s.seq);return e.jsxs(c,{className:"transition-colors",children:[e.jsx(k,{className:"py-3 cursor-pointer hover:bg-muted/30",onClick:()=>g(s.seq),children:e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"flex items-start gap-2 flex-1 min-w-0",children:[t?e.jsx(w,{className:"h-4 w-4 mt-0.5 text-muted-foreground shrink-0"}):e.jsx(b,{className:"h-4 w-4 mt-0.5 text-muted-foreground shrink-0"}),e.jsxs("div",{className:"space-y-1 flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs(C,{className:"text-sm font-medium",children:["#",s.seq]}),s.kind&&e.jsx(m,{variant:"outline",className:"text-xs",children:s.kind}),s.level&&e.jsx(m,{className:`text-xs ${j(s.level)}`,children:s.level})]}),s.message&&e.jsx("p",{className:`text-sm text-muted-foreground ${t?"":"truncate"}`,children:s.message})]})]}),e.jsx("span",{className:"text-xs text-muted-foreground whitespace-nowrap",children:f(s.ts)})]})}),t&&e.jsx(d,{className:"pt-0 pb-4",children:e.jsx("pre",{className:"text-xs bg-muted p-3 rounded-md overflow-x-auto",children:JSON.stringify(s,null,2)})})]},s.seq)})})]})}export{$ as default};
1
+ import{r as a,A as p,j as e,x as y,bt as v,B as N,p as w,C as b}from"./index-9Tv-j_Ga.js";import{C as c,a as d,b as k,c as C}from"./card-SN5gKnu7.js";import{B as m}from"./badge-AwLOflf5.js";import{R as E}from"./refresh-cw-BFZxHqbC.js";const A=5e3;function $(){const[n,o]=a.useState([]),[i,x]=a.useState(!0),[h,u]=a.useState(new Set),l=a.useCallback(async()=>{x(!0);try{const s=await fetch(`${p}/api/v1/events?limit=${A}`);if(!s.ok)throw new Error("Failed to load events");const t=await s.json();o(Array.isArray(t.items)?[...t.items].reverse():[])}catch(s){console.error("Failed to load events:",s),o([])}finally{x(!1)}},[]),g=a.useCallback(s=>{u(t=>{const r=new Set(t);return r.has(s)?r.delete(s):r.add(s),r})},[]);a.useEffect(()=>{l()},[l]);const f=s=>{if(!s)return"Unknown time";try{return new Date(s).toLocaleString()}catch{return s}},j=s=>{switch(s?.toLowerCase()){case"error":return"bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200";case"warn":case"warning":return"bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200";case"info":return"bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200";case"debug":return"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200";default:return"bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"}};return i?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsx("p",{className:"text-muted-foreground",children:"View system activity and events"})]}),e.jsx(c,{children:e.jsx(d,{className:"pt-6",children:e.jsxs("div",{className:"flex items-center justify-center",children:[e.jsx(y,{className:"h-8 w-8 animate-spin text-muted-foreground"}),e.jsx("span",{className:"ml-2 text-muted-foreground",children:"Loading events..."})]})})})]}):n.length===0?e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsx("p",{className:"text-muted-foreground",children:"View system activity and events"})]}),e.jsx(c,{children:e.jsx(d,{className:"pt-6",children:e.jsxs("div",{className:"text-center",children:[e.jsx(v,{className:"mx-auto h-12 w-12 text-muted-foreground"}),e.jsx("h3",{className:"mt-4 text-lg font-semibold",children:"No Events Found"}),e.jsx("p",{className:"mt-2 text-sm text-muted-foreground",children:"Activity events will appear here as they occur."})]})})})]}):e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold tracking-tight",children:"Activity"}),e.jsxs("p",{className:"text-muted-foreground",children:["View system activity and events (",n.length," events)"]})]}),e.jsxs(N,{variant:"outline",size:"sm",onClick:()=>void l(),disabled:i,children:[e.jsx(E,{className:`h-4 w-4 mr-2 ${i?"animate-spin":""}`}),"Refresh"]})]}),e.jsx("div",{className:"space-y-3",children:n.map(s=>{const t=h.has(s.seq);return e.jsxs(c,{className:"transition-colors",children:[e.jsx(k,{className:"py-3 cursor-pointer hover:bg-muted/30",onClick:()=>g(s.seq),children:e.jsxs("div",{className:"flex items-start justify-between gap-4",children:[e.jsxs("div",{className:"flex items-start gap-2 flex-1 min-w-0",children:[t?e.jsx(w,{className:"h-4 w-4 mt-0.5 text-muted-foreground shrink-0"}):e.jsx(b,{className:"h-4 w-4 mt-0.5 text-muted-foreground shrink-0"}),e.jsxs("div",{className:"space-y-1 flex-1 min-w-0",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-wrap",children:[e.jsxs(C,{className:"text-sm font-medium",children:["#",s.seq]}),s.kind&&e.jsx(m,{variant:"outline",className:"text-xs",children:s.kind}),s.level&&e.jsx(m,{className:`text-xs ${j(s.level)}`,children:s.level})]}),s.message&&e.jsx("p",{className:`text-sm text-muted-foreground ${t?"":"truncate"}`,children:s.message})]})]}),e.jsx("span",{className:"text-xs text-muted-foreground whitespace-nowrap",children:f(s.ts)})]})}),t&&e.jsx(d,{className:"pt-0 pb-4",children:e.jsx("pre",{className:"text-xs bg-muted p-3 rounded-md overflow-x-auto",children:JSON.stringify(s,null,2)})})]},s.seq)})})]})}export{$ as default};