@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.
- package/dist/lib/jobs/job-runner.js +10 -2
- package/dist/lib/jobs/job-scheduler.js +21 -0
- package/dist/lib/mail/mail-runner.d.ts +51 -0
- package/dist/lib/mail/mail-runner.js +342 -0
- package/dist/lib/slack/slack-service.d.ts +2 -0
- package/dist/lib/slack/slack-service.js +3 -0
- package/dist/lib/storage/file-storage.d.ts +16 -16
- package/dist/lib/storage/file-storage.js +59 -64
- package/dist/lib/terminal/terminal-manager.d.ts +3 -5
- package/dist/lib/terminal/terminal-manager.js +10 -61
- package/dist/routes/api/v1/ai/route.js +361 -20
- package/dist/routes/api/v1/chats/[id]/index.js +2 -1
- package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
- package/dist/routes/api/v1/chats/[id]/route.js +30 -1
- package/dist/routes/api/v1/context/index.js +0 -2
- package/dist/routes/api/v1/git/index.js +23 -0
- package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
- package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/index.js +1 -0
- package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/route.js +176 -0
- package/dist/routes/api/v1/mail/index.d.ts +3 -0
- package/dist/routes/api/v1/mail/index.js +23 -0
- package/dist/routes/api/v1/mail/route.d.ts +294 -0
- package/dist/routes/api/v1/mail/route.js +344 -0
- package/dist/routes/api/v1/mcp/index.js +109 -34
- package/dist/routes/api/v1/slack/index.d.ts +3 -0
- package/dist/routes/api/v1/slack/index.js +15 -0
- package/dist/routes/api/v1/slack/route.d.ts +124 -0
- package/dist/routes/api/v1/slack/route.js +192 -0
- package/dist/routes/api/v1/tasks/[id]/route.d.ts +117 -0
- package/dist/routes/api/v1/tasks/[id]/route.js +166 -0
- package/dist/routes/api/v1/tasks/index.d.ts +3 -0
- package/dist/routes/api/v1/tasks/index.js +10 -0
- package/dist/routes/api/v1/tasks/route.d.ts +96 -0
- package/dist/routes/api/v1/tasks/route.js +136 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
- package/dist/routes/api/v1/terminal/[taskId]/create/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/route.js +27 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/route.js +21 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/route.js +21 -0
- package/dist/routes/api/v1/terminal/sessions/route.js +4 -4
- package/dist/server-with-static.js +14 -8
- package/dist/server.js +14 -8
- package/package.json +4 -4
- package/static/assets/{ActivityPage-DSSML9J-.js → ActivityPage-k4I7Q53O.js} +1 -1
- package/static/assets/ApiKeysSettingsPage-B1YvVdmg.js +2 -0
- package/static/assets/{ArchitectureEditPage-CIjqkpMz.js → ArchitectureEditPage-CpowsIx2.js} +1 -1
- package/static/assets/{ArchitecturePage-Db__w054.js → ArchitecturePage-DYxC_aMR.js} +1 -1
- package/static/assets/{AuthSettingsPage-Bpooi8Z0.js → AuthSettingsPage-DtSo78Y_.js} +2 -2
- package/static/assets/{CallbackPage-BGLKeyjv.js → CallbackPage-bROCGapx.js} +1 -1
- package/static/assets/CodePage-CPCj64rX.js +2 -0
- package/static/assets/{CollapsibleSection-B6RO5o5R.js → CollapsibleSection-M5cXbl92.js} +1 -1
- package/static/assets/DashboardPage-B9BZZfw6.js +51 -0
- package/static/assets/{GitPage-DxjLaRWe.js → GitPage-BiDtdSK1.js} +2 -2
- package/static/assets/GitSettingsPage-THm6wDjs.js +6 -0
- package/static/assets/IdentityPage-BC16skg6.js +6 -0
- package/static/assets/{ImplementationStepsEditor-DWjDyZzP.js → ImplementationStepsEditor-HliLQav5.js} +2 -2
- package/static/assets/IntegrationsSettingsPage-CC_VKIQa.js +1 -0
- package/static/assets/JobDetailPage-z1QQYvmU.js +1 -0
- package/static/assets/KnowledgeDetailPage-DzHXBS7Q.js +1 -0
- package/static/assets/KnowledgeEditPage-BwGnUH_m.js +1 -0
- package/static/assets/KnowledgePage-CGIVMS02.js +3 -0
- package/static/assets/{LoginPage-DptfKsWo.js → LoginPage-VQ3lcfLV.js} +1 -1
- package/static/assets/MailInboxPage-DiZKqwdU.js +1 -0
- package/static/assets/MailProcessingModal-DIeSQBoR.js +6 -0
- package/static/assets/MailReadPage-C8AACmZQ.js +1 -0
- package/static/assets/MailSentPage-C_5yFly_.js +1 -0
- package/static/assets/McpSettingsPage-i9YHcu1s.js +1 -0
- package/static/assets/NewKnowledgePage-BnVY7WUD.js +9 -0
- package/static/assets/{NewSkillPage-Cwy2MSr9.js → NewSkillPage-DwniHD6D.js} +1 -1
- package/static/assets/NewTaskPage-F5UX2WMc.js +90 -0
- package/static/assets/NotFoundPage-BbSZX_4L.js +6 -0
- package/static/assets/NotificationsSettingsPage-C8kjcift.js +1 -0
- package/static/assets/ProjectEditPage-DUUlIEqI.js +11 -0
- package/static/assets/{ProjectPage-DgUr4bVU.js → ProjectPage-Unz9PQpA.js} +1 -1
- package/static/assets/PromptsSettingsPage-DVpIuRKI.js +1 -0
- package/static/assets/ResourceDetailPage-DqHZ2KYD.js +1 -0
- package/static/assets/ResourcesPage-BP5tuAi-.js +41 -0
- package/static/assets/RoleEditPage-BgKu8S0-.js +13 -0
- package/static/assets/{RolePage-Sc-GFiL2.js → RolePage-Fed52Ov5.js} +1 -1
- package/static/assets/{RulesSettingsPage-DdMCzh9j.js → RulesSettingsPage-BQ2O0u66.js} +2 -2
- package/static/assets/SchedulePage-jkxjuzBx.js +4 -0
- package/static/assets/SkillDetailPage-k3Q2-NFd.js +1 -0
- package/static/assets/{SkillEditPage-BDd2CtAS.js → SkillEditPage-urF4snjo.js} +1 -1
- package/static/assets/SkillsPage-DlWDhEjR.js +8 -0
- package/static/assets/{SkillsSettingsPage-1N0JQOYc.js → SkillsSettingsPage-BViFgckG.js} +1 -1
- package/static/assets/SourceInput-CAFKTHw-.js +1 -0
- package/static/assets/{TagInput-D_SdcypZ.js → TagInput-C6lI-ePr.js} +1 -1
- package/static/assets/TaskDetailPage-DpbRHnW_.js +16 -0
- package/static/assets/TaskEditPage-DssRbW0h.js +1 -0
- package/static/assets/TasksPage-CD_eo0Bj.js +17 -0
- package/static/assets/TerminalPage-BG_wlccr.js +1 -0
- package/static/assets/TerminalSessionPage-CsK-LznK.js +8 -0
- package/static/assets/UserPreferencesPage-CWUq3efu.js +1 -0
- package/static/assets/UserSettingsPage-CduI_MGS.js +1 -0
- package/static/assets/UtilitiesPage-BAxokhLh.js +1 -0
- package/static/assets/{alert-BD5jo3SI.js → alert-BXsc6_qu.js} +1 -1
- package/static/assets/{arrow-down-BxcoVp6S.js → arrow-down-DmW_3gE8.js} +1 -1
- package/static/assets/{arrow-left-CdM_IPng.js → arrow-left-1S-835kP.js} +1 -1
- package/static/assets/{arrow-up-BOJ6ob9X.js → arrow-up-BYism_o1.js} +1 -1
- package/static/assets/arrow-up-down-Dw3J0a4i.js +6 -0
- package/static/assets/{badge-DEiQk9C9.js → badge-BUEY53dV.js} +1 -1
- package/static/assets/{browser-modal-Dp1eMxt6.js → browser-modal-DCNdI4NT.js} +2 -2
- package/static/assets/{card-BCFxXzRk.js → card-BcPlIAH5.js} +1 -1
- package/static/assets/{chevron-left-C25izNzZ.js → chevron-left-FMmNe7yP.js} +1 -1
- package/static/assets/{plus-iamYJu5V.js → chevron-up-CqM3won3.js} +2 -2
- package/static/assets/{chevrons-up-DqbWMOjv.js → chevrons-up-DTvCkIHc.js} +1 -1
- package/static/assets/{circle-alert-CMRMPnbY.js → circle-alert-dseM-Ib7.js} +1 -1
- package/static/assets/{circle-check-big-NI18oHuP.js → circle-check-big-jKg34xC-.js} +1 -1
- package/static/assets/{circle-check-D5wZZPvj.js → circle-check-eyo6pBP1.js} +1 -1
- package/static/assets/{circle-play-BhVU869u.js → circle-play-BrY_lNiH.js} +1 -1
- package/static/assets/{circle-x-BXDB-G_q.js → circle-x-uqmzEce1.js} +1 -1
- package/static/assets/{clipboard-DC2xmNVx.js → clipboard-tzPFoieb.js} +1 -1
- package/static/assets/{clock-CeCp7Pz1.js → clock-Bjc06QBM.js} +1 -1
- package/static/assets/code-DrYqPukx.js +6 -0
- package/static/assets/{download-ZF_XbTIA.js → download-Bg__QCLT.js} +1 -1
- package/static/assets/{external-link-CYBz87-P.js → external-link-CNDy2UUo.js} +1 -1
- package/static/assets/{eye-BT8MAvKY.js → eye-DLFBnC8t.js} +1 -1
- package/static/assets/{folder-git-2-BE9AIPnj.js → folder-git-2-DUqd0WRi.js} +1 -1
- package/static/assets/index-CHdBxVyk.css +2 -0
- package/static/assets/index-DFcWlnzl.js +487 -0
- package/static/assets/{info-DunFSp-x.js → info-D6jxZC5X.js} +1 -1
- package/static/assets/kiro-CX1mOsRO.js +17 -0
- package/static/assets/{label-vYhfrPMD.js → label-DBuh-ke5.js} +1 -1
- package/static/assets/{markdown-editor-BzZ8tCto.js → markdown-editor-B4YNQFT2.js} +1 -1
- package/static/assets/message-square-B5RWz_ff.js +6 -0
- package/static/assets/paperclip-4A_3MaPx.js +6 -0
- package/static/assets/{pause-BHonpdnw.js → pause-BzhKXHtR.js} +1 -1
- package/static/assets/{play-CCo7tau2.js → play-CHIf-Rcz.js} +1 -1
- package/static/assets/{radio-group-Db-pBuyW.js → radio-group-C1ct-VsJ.js} +1 -1
- package/static/assets/{refresh-cw-Bg7vQzOw.js → refresh-cw-B3OwrDUf.js} +1 -1
- package/static/assets/{search-CH2zaibZ.js → search-Cq1ksEdp.js} +1 -1
- package/static/assets/select-44mcS2_G.js +1 -0
- package/static/assets/{status-utils-BDOyevaX.js → status-utils-CDkPeVfP.js} +1 -1
- package/static/assets/{switch-CH-VOgPo.js → switch-CIwjYvCt.js} +1 -1
- package/static/assets/{tabs-XeRAjZYR.js → tabs-DTV6Su-h.js} +1 -1
- package/static/assets/{tag-CRP5nL5-.js → tag-p6yeowCW.js} +1 -1
- package/static/assets/{terminal-preview-DMJMuORo.js → terminal-preview-DN38x9Jm.js} +1 -1
- package/static/assets/use-terminal-BXJqOeJe.js +1 -0
- package/static/assets/video-BH5ChaoS.js +36 -0
- package/static/index.html +2 -2
- package/static/assets/ApiKeysSettingsPage-Chw9PNL5.js +0 -2
- package/static/assets/CodePage-CrokcH-S.js +0 -2
- package/static/assets/DashboardPage-BaSQQ8Nv.js +0 -41
- package/static/assets/GitSettingsPage-tKBXYAFm.js +0 -6
- package/static/assets/IdentityPage-D2yBjeYn.js +0 -11
- package/static/assets/IntegrationsSettingsPage-Bx8-0Ig4.js +0 -1
- package/static/assets/JobDetailPage-BQmTHled.js +0 -1
- package/static/assets/KnowledgeDetailPage-QMU2bC3L.js +0 -1
- package/static/assets/KnowledgeEditPage-DbMJVcLc.js +0 -1
- package/static/assets/KnowledgePage-aU1GxZSZ.js +0 -8
- package/static/assets/McpSettingsPage-C3WxFwRB.js +0 -1
- package/static/assets/NewKnowledgePage-Cbiswrw_.js +0 -9
- package/static/assets/NewProposalPage-B_sDMBTK.js +0 -90
- package/static/assets/ProjectEditPage-BznWiBBc.js +0 -11
- package/static/assets/PromptsSettingsPage-CY0-870a.js +0 -1
- package/static/assets/ProposalDetailPage-K4iMXHEg.js +0 -1
- package/static/assets/ProposalEditPage-jZOtCMqP.js +0 -1
- package/static/assets/ProposalsPage-C7n4-G05.js +0 -17
- package/static/assets/ResourcesPage-vB5-XkUv.js +0 -71
- package/static/assets/RoleEditPage-Cu7ZB3yj.js +0 -13
- package/static/assets/SchedulePage-Bkkf2wA0.js +0 -4
- package/static/assets/SkillDetailPage-5sDxf3Of.js +0 -1
- package/static/assets/SkillsPage-D2G7EfK8.js +0 -8
- package/static/assets/SourceInput-DlC0zwva.js +0 -1
- package/static/assets/TerminalPage--KZ7azvP.js +0 -1
- package/static/assets/TerminalSessionPage-ClvxK9ia.js +0 -13
- package/static/assets/UserPreferencesPage-CheOH7jZ.js +0 -1
- package/static/assets/UserSettingsPage-C8STDvfQ.js +0 -1
- package/static/assets/UtilitiesPage-9rTxR2md.js +0 -1
- package/static/assets/calendar-Cx5r9R7A.js +0 -6
- package/static/assets/droid-DqWsM2dp.js +0 -17
- package/static/assets/index-DVTgTsDa.css +0 -2
- package/static/assets/index-hqVgTgRB.js +0 -462
- package/static/assets/use-terminal-DuGZuvd2.js +0 -1
- 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,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,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 };
|