@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
|
@@ -6,8 +6,8 @@ import readline from 'readline';
|
|
|
6
6
|
import { ZodError } from 'zod';
|
|
7
7
|
import { streamText, tool, jsonSchema, stepCountIs } from 'ai';
|
|
8
8
|
import { createAnthropic } from '@ai-sdk/anthropic';
|
|
9
|
-
import { getLogsDir,
|
|
10
|
-
import {
|
|
9
|
+
import { getLogsDir, listTasks, getTask, createTask, updateTask, deleteTask, } from '@lovelybunch/core';
|
|
10
|
+
import { tasksFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool, resourcesTool } from '@lovelybunch/mcp';
|
|
11
11
|
import matter from 'gray-matter';
|
|
12
12
|
import Fuse from 'fuse.js';
|
|
13
13
|
import { FileStorageAdapter } from '../../../../lib/storage/file-storage.js';
|
|
@@ -96,11 +96,11 @@ export async function POST(c) {
|
|
|
96
96
|
// Using tool<any, string>() to properly type jsonSchema-based tools
|
|
97
97
|
const storage = new FileStorageAdapter();
|
|
98
98
|
const aiTools = {
|
|
99
|
-
|
|
100
|
-
description:
|
|
101
|
-
inputSchema: jsonSchema(
|
|
99
|
+
tasks: tool({
|
|
100
|
+
description: tasksFullTool.description,
|
|
101
|
+
inputSchema: jsonSchema(tasksFullTool.parameters),
|
|
102
102
|
execute: async (args) => {
|
|
103
|
-
const result = await
|
|
103
|
+
const result = await executeTasksToolDirect(args, storage);
|
|
104
104
|
return JSON.stringify(result);
|
|
105
105
|
},
|
|
106
106
|
}),
|
|
@@ -144,6 +144,14 @@ export async function POST(c) {
|
|
|
144
144
|
return JSON.stringify(result);
|
|
145
145
|
},
|
|
146
146
|
}),
|
|
147
|
+
resources: tool({
|
|
148
|
+
description: resourcesTool.description,
|
|
149
|
+
inputSchema: jsonSchema(resourcesTool.parameters),
|
|
150
|
+
execute: async (args) => {
|
|
151
|
+
const result = await executeResourcesToolDirect(args);
|
|
152
|
+
return JSON.stringify(result);
|
|
153
|
+
},
|
|
154
|
+
}),
|
|
147
155
|
};
|
|
148
156
|
// Debug logging
|
|
149
157
|
console.log('AI Request Debug:', {
|
|
@@ -175,24 +183,44 @@ export async function POST(c) {
|
|
|
175
183
|
// These are unchanged from the previous implementation. They handle the actual
|
|
176
184
|
// business logic for each tool, called automatically by the AI SDK when the
|
|
177
185
|
// LLM makes tool calls.
|
|
178
|
-
async function
|
|
179
|
-
const { operation, id, filters,
|
|
186
|
+
async function executeTasksToolDirect(args, _storage) {
|
|
187
|
+
const { operation, id, filters, task, updates } = args;
|
|
180
188
|
try {
|
|
181
189
|
switch (operation) {
|
|
182
190
|
case 'list': {
|
|
183
|
-
const
|
|
191
|
+
const DEFAULT_LIST_LIMIT = 20;
|
|
192
|
+
const requestedLimit = filters?.limit;
|
|
193
|
+
const effectiveLimit = requestedLimit ?? DEFAULT_LIST_LIMIT;
|
|
194
|
+
const tasks = await listTasks(filters || {});
|
|
195
|
+
const totalCount = tasks.length;
|
|
196
|
+
const limited = tasks.slice(0, effectiveLimit);
|
|
197
|
+
// Return lightweight summaries to keep token usage manageable.
|
|
198
|
+
// The AI can use "get" with a specific task ID to retrieve full details.
|
|
199
|
+
const summaries = limited.map((t) => ({
|
|
200
|
+
id: t.id,
|
|
201
|
+
title: t.title,
|
|
202
|
+
status: t.status,
|
|
203
|
+
priority: t.metadata?.priority,
|
|
204
|
+
tags: t.metadata?.tags,
|
|
205
|
+
author: t.author ? { name: t.author.name, type: t.author.type } : undefined,
|
|
206
|
+
createdAt: t.metadata?.createdAt,
|
|
207
|
+
updatedAt: t.metadata?.updatedAt,
|
|
208
|
+
}));
|
|
184
209
|
return {
|
|
185
210
|
success: true,
|
|
186
|
-
data:
|
|
187
|
-
count:
|
|
188
|
-
|
|
211
|
+
data: summaries,
|
|
212
|
+
count: summaries.length,
|
|
213
|
+
totalCount,
|
|
214
|
+
message: totalCount > effectiveLimit
|
|
215
|
+
? `Showing ${summaries.length} of ${totalCount} tasks (limit: ${effectiveLimit}). Use filters or increase limit to see more. Use "get" with a task ID for full details.`
|
|
216
|
+
: `Found ${totalCount} tasks. Use "get" with a task ID for full details.`
|
|
189
217
|
};
|
|
190
218
|
}
|
|
191
219
|
case 'get': {
|
|
192
220
|
if (!id) {
|
|
193
221
|
return { success: false, error: 'Task ID is required for get operation' };
|
|
194
222
|
}
|
|
195
|
-
const result = await
|
|
223
|
+
const result = await getTask(id);
|
|
196
224
|
if (!result) {
|
|
197
225
|
return { success: false, error: 'Task not found' };
|
|
198
226
|
}
|
|
@@ -203,10 +231,10 @@ async function executeProposalsToolDirect(args, _storage) {
|
|
|
203
231
|
};
|
|
204
232
|
}
|
|
205
233
|
case 'create': {
|
|
206
|
-
if (!
|
|
234
|
+
if (!task) {
|
|
207
235
|
return { success: false, error: 'Task data is required for create operation' };
|
|
208
236
|
}
|
|
209
|
-
const created = await
|
|
237
|
+
const created = await createTask(task);
|
|
210
238
|
return {
|
|
211
239
|
success: true,
|
|
212
240
|
data: created,
|
|
@@ -217,11 +245,11 @@ async function executeProposalsToolDirect(args, _storage) {
|
|
|
217
245
|
if (!id) {
|
|
218
246
|
return { success: false, error: 'Task ID is required for update operation' };
|
|
219
247
|
}
|
|
220
|
-
const updateData = updates ||
|
|
248
|
+
const updateData = updates || task;
|
|
221
249
|
if (!updateData) {
|
|
222
250
|
return { success: false, error: 'Update data is required for update operation' };
|
|
223
251
|
}
|
|
224
|
-
const updated = await
|
|
252
|
+
const updated = await updateTask(id, updateData);
|
|
225
253
|
return {
|
|
226
254
|
success: true,
|
|
227
255
|
data: updated,
|
|
@@ -232,7 +260,7 @@ async function executeProposalsToolDirect(args, _storage) {
|
|
|
232
260
|
if (!id) {
|
|
233
261
|
return { success: false, error: 'Task ID is required for delete operation' };
|
|
234
262
|
}
|
|
235
|
-
const deleted = await
|
|
263
|
+
const deleted = await deleteTask(id);
|
|
236
264
|
if (!deleted) {
|
|
237
265
|
return { success: false, error: 'Task not found' };
|
|
238
266
|
}
|
|
@@ -256,7 +284,7 @@ async function executeProposalsToolDirect(args, _storage) {
|
|
|
256
284
|
}))
|
|
257
285
|
};
|
|
258
286
|
}
|
|
259
|
-
console.error('Error executing
|
|
287
|
+
console.error('Error executing tasks tool:', error);
|
|
260
288
|
return { success: false, error: error.message || 'Tool execution failed' };
|
|
261
289
|
}
|
|
262
290
|
}
|
|
@@ -817,6 +845,319 @@ async function executeRoleContextToolDirect(args) {
|
|
|
817
845
|
return { success: false, error: error.message || 'Role context tool execution failed' };
|
|
818
846
|
}
|
|
819
847
|
}
|
|
848
|
+
// ─── Resources Tool ─────────────────────────────────────────────────────────
|
|
849
|
+
// Uses internal HTTP calls to the same server's resource endpoints so that all
|
|
850
|
+
// existing validation, thumbnail generation, and error handling is reused.
|
|
851
|
+
function getInternalApiBase() {
|
|
852
|
+
const port = process.env.PORT ? parseInt(process.env.PORT) : 3001;
|
|
853
|
+
return `http://127.0.0.1:${port}`;
|
|
854
|
+
}
|
|
855
|
+
async function executeResourcesToolDirect(args) {
|
|
856
|
+
const { operation, query, type_filter, resource_id, prompt, model, aspect_ratio, text, voice, duration, resolution, url, tags, description } = args;
|
|
857
|
+
const apiBase = getInternalApiBase();
|
|
858
|
+
try {
|
|
859
|
+
switch (operation) {
|
|
860
|
+
case 'list': {
|
|
861
|
+
const response = await fetch(`${apiBase}/api/v1/resources`);
|
|
862
|
+
const result = await response.json();
|
|
863
|
+
if (!result.success || !result.data) {
|
|
864
|
+
return { success: false, error: 'Failed to list resources' };
|
|
865
|
+
}
|
|
866
|
+
let resources = result.data;
|
|
867
|
+
// Apply type filter
|
|
868
|
+
if (type_filter) {
|
|
869
|
+
const prefix = type_filter.toLowerCase();
|
|
870
|
+
resources = resources.filter((r) => r.type?.toLowerCase().startsWith(prefix));
|
|
871
|
+
}
|
|
872
|
+
// Apply search filter
|
|
873
|
+
if (query) {
|
|
874
|
+
const q = query.toLowerCase();
|
|
875
|
+
resources = resources.filter((r) => r.name?.toLowerCase().includes(q) ||
|
|
876
|
+
r.metadata?.description?.toLowerCase().includes(q) ||
|
|
877
|
+
r.metadata?.tags?.some((tag) => tag.toLowerCase().includes(q)));
|
|
878
|
+
}
|
|
879
|
+
// Return a concise summary to keep token usage reasonable
|
|
880
|
+
const summary = resources.map((r) => ({
|
|
881
|
+
id: r.id,
|
|
882
|
+
name: r.name,
|
|
883
|
+
type: r.type,
|
|
884
|
+
size: r.size,
|
|
885
|
+
tags: r.metadata?.tags,
|
|
886
|
+
description: r.metadata?.description
|
|
887
|
+
? (r.metadata.description.length > 100
|
|
888
|
+
? r.metadata.description.slice(0, 100) + '...'
|
|
889
|
+
: r.metadata.description)
|
|
890
|
+
: undefined,
|
|
891
|
+
}));
|
|
892
|
+
return {
|
|
893
|
+
success: true,
|
|
894
|
+
data: summary,
|
|
895
|
+
count: summary.length,
|
|
896
|
+
message: query
|
|
897
|
+
? `Found ${summary.length} resources matching "${query}"`
|
|
898
|
+
: `Found ${summary.length} resources`
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
case 'get': {
|
|
902
|
+
if (!resource_id) {
|
|
903
|
+
return { success: false, error: 'resource_id is required for get operation' };
|
|
904
|
+
}
|
|
905
|
+
const response = await fetch(`${apiBase}/api/v1/resources`);
|
|
906
|
+
const result = await response.json();
|
|
907
|
+
if (!result.success || !result.data) {
|
|
908
|
+
return { success: false, error: 'Failed to fetch resources' };
|
|
909
|
+
}
|
|
910
|
+
const resource = result.data.find((r) => r.id === resource_id);
|
|
911
|
+
if (!resource) {
|
|
912
|
+
return { success: false, error: `Resource '${resource_id}' not found` };
|
|
913
|
+
}
|
|
914
|
+
return {
|
|
915
|
+
success: true,
|
|
916
|
+
data: resource,
|
|
917
|
+
message: `Retrieved resource ${resource.name} (${resource.id})`
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
case 'generate_image': {
|
|
921
|
+
if (!prompt) {
|
|
922
|
+
return { success: false, error: 'prompt is required for generate_image operation' };
|
|
923
|
+
}
|
|
924
|
+
// Step 1: Generate the image via Replicate
|
|
925
|
+
const genBody = {
|
|
926
|
+
prompt,
|
|
927
|
+
model: model || 'nano-banana-pro',
|
|
928
|
+
dimensions: aspect_ratio || '16:9',
|
|
929
|
+
output_format: 'png',
|
|
930
|
+
};
|
|
931
|
+
const genResponse = await fetch(`${apiBase}/api/v1/resources/generate`, {
|
|
932
|
+
method: 'POST',
|
|
933
|
+
headers: { 'Content-Type': 'application/json' },
|
|
934
|
+
body: JSON.stringify(genBody),
|
|
935
|
+
signal: AbortSignal.timeout(300_000), // 5 min
|
|
936
|
+
});
|
|
937
|
+
const genResult = await genResponse.json();
|
|
938
|
+
if (!genResult.success || !genResult.data?.imageUrl) {
|
|
939
|
+
const msg = genResult.error?.message || 'Image generation failed';
|
|
940
|
+
return { success: false, error: msg };
|
|
941
|
+
}
|
|
942
|
+
// Step 2: Download and save to resources
|
|
943
|
+
const saved = await downloadAndSaveToResources(genResult.data.imageUrl, apiBase, { type: 'image', prompt, model: model || 'nano-banana-pro', tags, description });
|
|
944
|
+
return {
|
|
945
|
+
success: true,
|
|
946
|
+
data: saved,
|
|
947
|
+
message: `Generated and saved image "${saved.name}" (${saved.id}). It is now available in the Resources section.`
|
|
948
|
+
};
|
|
949
|
+
}
|
|
950
|
+
case 'generate_audio': {
|
|
951
|
+
if (!text) {
|
|
952
|
+
return { success: false, error: 'text is required for generate_audio operation' };
|
|
953
|
+
}
|
|
954
|
+
const audioBody = {
|
|
955
|
+
text,
|
|
956
|
+
voice_id: voice || 'English_Trustworth_Man',
|
|
957
|
+
speed: 1,
|
|
958
|
+
pitch: 0,
|
|
959
|
+
emotion: 'auto',
|
|
960
|
+
language_boost: 'English',
|
|
961
|
+
audio_format: 'mp3',
|
|
962
|
+
};
|
|
963
|
+
const audioResponse = await fetch(`${apiBase}/api/v1/resources/generate-audio`, {
|
|
964
|
+
method: 'POST',
|
|
965
|
+
headers: { 'Content-Type': 'application/json' },
|
|
966
|
+
body: JSON.stringify(audioBody),
|
|
967
|
+
signal: AbortSignal.timeout(300_000),
|
|
968
|
+
});
|
|
969
|
+
const audioResult = await audioResponse.json();
|
|
970
|
+
if (!audioResult.success || !audioResult.data?.audioUrl) {
|
|
971
|
+
const msg = audioResult.error?.message || 'Audio generation failed';
|
|
972
|
+
return { success: false, error: msg };
|
|
973
|
+
}
|
|
974
|
+
const savedAudio = await downloadAndSaveToResources(audioResult.data.audioUrl, apiBase, { type: 'audio', prompt: text, model: 'minimax/speech-02-turbo', tags, description });
|
|
975
|
+
return {
|
|
976
|
+
success: true,
|
|
977
|
+
data: savedAudio,
|
|
978
|
+
message: `Generated and saved audio "${savedAudio.name}" (${savedAudio.id}). It is now available in the Resources section.`
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
case 'generate_video': {
|
|
982
|
+
if (!prompt) {
|
|
983
|
+
return { success: false, error: 'prompt is required for generate_video operation' };
|
|
984
|
+
}
|
|
985
|
+
const videoBody = {
|
|
986
|
+
prompt,
|
|
987
|
+
duration: duration || 8,
|
|
988
|
+
resolution: resolution || '720p',
|
|
989
|
+
aspect_ratio: aspect_ratio || '16:9',
|
|
990
|
+
generate_audio: true,
|
|
991
|
+
};
|
|
992
|
+
const videoResponse = await fetch(`${apiBase}/api/v1/resources/generate-video`, {
|
|
993
|
+
method: 'POST',
|
|
994
|
+
headers: { 'Content-Type': 'application/json' },
|
|
995
|
+
body: JSON.stringify(videoBody),
|
|
996
|
+
signal: AbortSignal.timeout(300_000),
|
|
997
|
+
});
|
|
998
|
+
const videoResult = await videoResponse.json();
|
|
999
|
+
if (!videoResult.success || !videoResult.data?.videoUrl) {
|
|
1000
|
+
const msg = videoResult.error?.message || 'Video generation failed';
|
|
1001
|
+
return { success: false, error: msg };
|
|
1002
|
+
}
|
|
1003
|
+
const savedVideo = await downloadAndSaveToResources(videoResult.data.videoUrl, apiBase, { type: 'video', prompt, model: 'google/veo-3.1-fast', tags, description });
|
|
1004
|
+
return {
|
|
1005
|
+
success: true,
|
|
1006
|
+
data: savedVideo,
|
|
1007
|
+
message: `Generated and saved video "${savedVideo.name}" (${savedVideo.id}). It is now available in the Resources section.`
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
case 'add_from_url': {
|
|
1011
|
+
if (!url) {
|
|
1012
|
+
return { success: false, error: 'url is required for add_from_url operation' };
|
|
1013
|
+
}
|
|
1014
|
+
// Download the URL
|
|
1015
|
+
const dlResponse = await fetch(url);
|
|
1016
|
+
if (!dlResponse.ok) {
|
|
1017
|
+
return { success: false, error: `Failed to download URL: ${dlResponse.statusText}` };
|
|
1018
|
+
}
|
|
1019
|
+
const buffer = Buffer.from(await dlResponse.arrayBuffer());
|
|
1020
|
+
const contentType = dlResponse.headers.get('content-type') || 'application/octet-stream';
|
|
1021
|
+
// Derive filename from URL path
|
|
1022
|
+
let fileName;
|
|
1023
|
+
try {
|
|
1024
|
+
const urlPath = new URL(url).pathname;
|
|
1025
|
+
fileName = basename(urlPath);
|
|
1026
|
+
if (!fileName || fileName === '/') {
|
|
1027
|
+
fileName = `downloaded-${Date.now()}`;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
catch {
|
|
1031
|
+
fileName = `downloaded-${Date.now()}`;
|
|
1032
|
+
}
|
|
1033
|
+
// Add extension from content-type if missing
|
|
1034
|
+
if (!fileName.includes('.')) {
|
|
1035
|
+
const extMap = {
|
|
1036
|
+
'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp',
|
|
1037
|
+
'image/gif': '.gif', 'image/svg+xml': '.svg',
|
|
1038
|
+
'audio/mpeg': '.mp3', 'audio/wav': '.wav', 'audio/ogg': '.ogg',
|
|
1039
|
+
'video/mp4': '.mp4', 'video/webm': '.webm',
|
|
1040
|
+
'application/pdf': '.pdf', 'application/json': '.json',
|
|
1041
|
+
};
|
|
1042
|
+
const ext = Object.entries(extMap).find(([ct]) => contentType.includes(ct))?.[1] || '.bin';
|
|
1043
|
+
fileName += ext;
|
|
1044
|
+
}
|
|
1045
|
+
const mimeType = contentType.split(';')[0].trim();
|
|
1046
|
+
// Upload to resources via FormData
|
|
1047
|
+
const formData = new FormData();
|
|
1048
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
1049
|
+
formData.set('file', blob, fileName);
|
|
1050
|
+
if (tags)
|
|
1051
|
+
formData.set('tags', tags);
|
|
1052
|
+
if (description)
|
|
1053
|
+
formData.set('description', description);
|
|
1054
|
+
const uploadResponse = await fetch(`${apiBase}/api/v1/resources`, {
|
|
1055
|
+
method: 'POST',
|
|
1056
|
+
body: formData,
|
|
1057
|
+
});
|
|
1058
|
+
const uploadResult = await uploadResponse.json();
|
|
1059
|
+
if (!uploadResult.success || !uploadResult.data) {
|
|
1060
|
+
return { success: false, error: uploadResult.error?.message || 'Upload failed' };
|
|
1061
|
+
}
|
|
1062
|
+
return {
|
|
1063
|
+
success: true,
|
|
1064
|
+
data: uploadResult.data,
|
|
1065
|
+
message: `Downloaded and saved "${uploadResult.data.name}" (${uploadResult.data.id}) from URL. It is now available in the Resources section.`
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
1068
|
+
default:
|
|
1069
|
+
return {
|
|
1070
|
+
success: false,
|
|
1071
|
+
error: `Unknown operation: ${operation}. Supported: list, get, generate_image, generate_audio, generate_video, add_from_url`
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
catch (error) {
|
|
1076
|
+
console.error('Error executing resources tool:', error);
|
|
1077
|
+
return { success: false, error: error.message || 'Resources tool execution failed' };
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Download media from a URL and upload it to the resources API.
|
|
1082
|
+
* Used by generation operations to auto-save generated content.
|
|
1083
|
+
*/
|
|
1084
|
+
async function downloadAndSaveToResources(mediaUrl, apiBase, meta) {
|
|
1085
|
+
const response = await fetch(mediaUrl);
|
|
1086
|
+
if (!response.ok) {
|
|
1087
|
+
throw new Error(`Failed to download generated ${meta.type}`);
|
|
1088
|
+
}
|
|
1089
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
1090
|
+
const contentType = response.headers.get('content-type') || '';
|
|
1091
|
+
// Determine extension from content-type
|
|
1092
|
+
let extension = 'bin';
|
|
1093
|
+
let mimeType = contentType.split(';')[0].trim();
|
|
1094
|
+
if (meta.type === 'image') {
|
|
1095
|
+
if (contentType.includes('png'))
|
|
1096
|
+
extension = 'png';
|
|
1097
|
+
else if (contentType.includes('jpeg') || contentType.includes('jpg'))
|
|
1098
|
+
extension = 'jpg';
|
|
1099
|
+
else if (contentType.includes('webp'))
|
|
1100
|
+
extension = 'webp';
|
|
1101
|
+
else
|
|
1102
|
+
extension = 'png';
|
|
1103
|
+
mimeType = mimeType || `image/${extension}`;
|
|
1104
|
+
}
|
|
1105
|
+
else if (meta.type === 'audio') {
|
|
1106
|
+
if (contentType.includes('mp3') || contentType.includes('mpeg'))
|
|
1107
|
+
extension = 'mp3';
|
|
1108
|
+
else if (contentType.includes('wav'))
|
|
1109
|
+
extension = 'wav';
|
|
1110
|
+
else if (contentType.includes('ogg'))
|
|
1111
|
+
extension = 'ogg';
|
|
1112
|
+
else
|
|
1113
|
+
extension = 'mp3';
|
|
1114
|
+
mimeType = mimeType || `audio/${extension}`;
|
|
1115
|
+
}
|
|
1116
|
+
else if (meta.type === 'video') {
|
|
1117
|
+
if (contentType.includes('mp4'))
|
|
1118
|
+
extension = 'mp4';
|
|
1119
|
+
else if (contentType.includes('webm'))
|
|
1120
|
+
extension = 'webm';
|
|
1121
|
+
else
|
|
1122
|
+
extension = 'mp4';
|
|
1123
|
+
mimeType = mimeType || `video/${extension}`;
|
|
1124
|
+
}
|
|
1125
|
+
const fileName = `generated-${meta.type}-${Date.now()}.${extension}`;
|
|
1126
|
+
// Upload via FormData to the resources endpoint (triggers thumbnail generation, etc.)
|
|
1127
|
+
const formData = new FormData();
|
|
1128
|
+
const blob = new Blob([buffer], { type: mimeType });
|
|
1129
|
+
formData.set('file', blob, fileName);
|
|
1130
|
+
// Build description with generation metadata
|
|
1131
|
+
const descParts = [];
|
|
1132
|
+
if (meta.description)
|
|
1133
|
+
descParts.push(meta.description);
|
|
1134
|
+
if (meta.prompt)
|
|
1135
|
+
descParts.push(`Generated with: "${meta.prompt}"`);
|
|
1136
|
+
if (meta.model)
|
|
1137
|
+
descParts.push(`Model: ${meta.model}`);
|
|
1138
|
+
const fullDesc = descParts.join(' | ');
|
|
1139
|
+
if (fullDesc)
|
|
1140
|
+
formData.set('description', fullDesc);
|
|
1141
|
+
// Build tags
|
|
1142
|
+
const tagParts = [];
|
|
1143
|
+
if (meta.tags)
|
|
1144
|
+
tagParts.push(meta.tags);
|
|
1145
|
+
tagParts.push('ai-generated', meta.type);
|
|
1146
|
+
formData.set('tags', tagParts.join(', '));
|
|
1147
|
+
if (meta.prompt)
|
|
1148
|
+
formData.set('generationPrompt', meta.prompt);
|
|
1149
|
+
if (meta.model)
|
|
1150
|
+
formData.set('generationModel', meta.model);
|
|
1151
|
+
const uploadResponse = await fetch(`${apiBase}/api/v1/resources`, {
|
|
1152
|
+
method: 'POST',
|
|
1153
|
+
body: formData,
|
|
1154
|
+
});
|
|
1155
|
+
const result = await uploadResponse.json();
|
|
1156
|
+
if (!result.success || !result.data) {
|
|
1157
|
+
throw new Error('Failed to save generated media to resources');
|
|
1158
|
+
}
|
|
1159
|
+
return result.data;
|
|
1160
|
+
}
|
|
820
1161
|
// ─── Helper Functions ────────────────────────────────────────────────────────
|
|
821
1162
|
function getContextBasePath() {
|
|
822
1163
|
let basePath;
|
|
@@ -842,7 +1183,7 @@ function getKnowledgeBasePath() {
|
|
|
842
1183
|
else {
|
|
843
1184
|
basePath = pathResolve(process.cwd(), '.nut');
|
|
844
1185
|
}
|
|
845
|
-
return join(basePath, '
|
|
1186
|
+
return join(basePath, 'knowledge');
|
|
846
1187
|
}
|
|
847
1188
|
function slugify(value) {
|
|
848
1189
|
const slug = value
|
|
@@ -4,6 +4,13 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
|
|
|
4
4
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
5
|
error: string;
|
|
6
6
|
}, 404, "json">)>;
|
|
7
|
+
export declare function PUT(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
|
+
success: true;
|
|
9
|
+
chatId: string;
|
|
10
|
+
message: string;
|
|
11
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
12
|
+
error: string;
|
|
13
|
+
}, 500, "json">)>;
|
|
7
14
|
export declare function DELETE(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
15
|
success: true;
|
|
9
16
|
message: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readFile, unlink } from "fs/promises";
|
|
1
|
+
import { readFile, writeFile, unlink } from "fs/promises";
|
|
2
2
|
import { join, resolve } from "path";
|
|
3
3
|
function getChatsPath() {
|
|
4
4
|
let basePath;
|
|
@@ -30,6 +30,35 @@ export async function GET(c) {
|
|
|
30
30
|
return c.json({ error: "Chat not found" }, 404);
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
export async function PUT(c) {
|
|
34
|
+
try {
|
|
35
|
+
const id = c.req.param('id');
|
|
36
|
+
const filePath = join(CHATS_DIR, `${id}.json`);
|
|
37
|
+
// Read existing chat
|
|
38
|
+
const existing = JSON.parse(await readFile(filePath, 'utf8'));
|
|
39
|
+
const body = await c.req.json();
|
|
40
|
+
const { messages, model, context, title } = body;
|
|
41
|
+
const now = new Date().toISOString();
|
|
42
|
+
const updated = {
|
|
43
|
+
...existing,
|
|
44
|
+
messages: messages ?? existing.messages,
|
|
45
|
+
model: model ?? existing.model,
|
|
46
|
+
context: context ?? existing.context,
|
|
47
|
+
title: title ?? existing.title,
|
|
48
|
+
updatedAt: now,
|
|
49
|
+
};
|
|
50
|
+
await writeFile(filePath, JSON.stringify(updated, null, 2));
|
|
51
|
+
return c.json({
|
|
52
|
+
success: true,
|
|
53
|
+
chatId: id,
|
|
54
|
+
message: "Chat updated successfully"
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error("Error updating chat:", error);
|
|
59
|
+
return c.json({ error: "Failed to update chat" }, 500);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
33
62
|
export async function DELETE(c) {
|
|
34
63
|
try {
|
|
35
64
|
const id = c.req.param('id');
|
|
@@ -2,12 +2,10 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import project from './project/route.js';
|
|
3
3
|
import architecture from './architecture/route.js';
|
|
4
4
|
import role from './role/route.js';
|
|
5
|
-
import knowledge from './knowledge/index.js';
|
|
6
5
|
import images from './images/index.js';
|
|
7
6
|
const context = new Hono();
|
|
8
7
|
context.route('/project', project);
|
|
9
8
|
context.route('/architecture', architecture);
|
|
10
9
|
context.route('/role', role);
|
|
11
|
-
context.route('/knowledge', knowledge);
|
|
12
10
|
context.route('/images', images);
|
|
13
11
|
export default context;
|
|
@@ -331,6 +331,22 @@ app.post('/branches/:branch/merge', async (c) => {
|
|
|
331
331
|
catch { }
|
|
332
332
|
const mergeStrategy = strategy?.strategy === 'squash' || strategy?.strategy === 'rebase' ? strategy.strategy : 'merge';
|
|
333
333
|
const result = await mergeBranch(name, mergeStrategy);
|
|
334
|
+
// Determine target branch for notification
|
|
335
|
+
try {
|
|
336
|
+
const { runGit } = await import('../../../../lib/git.js');
|
|
337
|
+
const { stdout: branchOutput } = await runGit(['branch', '--show-current']);
|
|
338
|
+
const targetBranch = branchOutput.trim();
|
|
339
|
+
// Send Slack notification (non-blocking)
|
|
340
|
+
import('../../../../lib/slack/slack-service.js').then(({ getSlackService }) => {
|
|
341
|
+
getSlackService().sendNotification({
|
|
342
|
+
type: 'git.merge',
|
|
343
|
+
branch: name,
|
|
344
|
+
targetBranch,
|
|
345
|
+
message: `Merged ${name} into ${targetBranch} using ${mergeStrategy}`,
|
|
346
|
+
}).catch(err => console.warn('[git] Slack notification failed:', err));
|
|
347
|
+
}).catch(() => { });
|
|
348
|
+
}
|
|
349
|
+
catch { }
|
|
334
350
|
return c.json({ success: true, data: { branch: name, strategy: mergeStrategy, result } });
|
|
335
351
|
}
|
|
336
352
|
catch (e) {
|
|
@@ -466,6 +482,13 @@ app.post('/push', async (c) => {
|
|
|
466
482
|
remote: remoteName,
|
|
467
483
|
}
|
|
468
484
|
});
|
|
485
|
+
// Send Slack notification (non-blocking)
|
|
486
|
+
import('../../../../lib/slack/slack-service.js').then(({ getSlackService }) => {
|
|
487
|
+
getSlackService().sendNotification({
|
|
488
|
+
type: 'git.push',
|
|
489
|
+
branch: currentBranch,
|
|
490
|
+
}).catch(err => console.warn('[git] Slack notification failed:', err));
|
|
491
|
+
}).catch(() => { });
|
|
469
492
|
}
|
|
470
493
|
catch (logError) {
|
|
471
494
|
console.error('Error logging push:', logError);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './route.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './route.js';
|