@lovelybunch/api 1.0.75-alpha.8 → 1.0.75

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/lib/jobs/job-runner.js +10 -2
  2. package/dist/lib/jobs/job-scheduler.js +21 -0
  3. package/dist/lib/mail/mail-runner.d.ts +51 -0
  4. package/dist/lib/mail/mail-runner.js +342 -0
  5. package/dist/lib/slack/slack-service.d.ts +2 -0
  6. package/dist/lib/slack/slack-service.js +3 -0
  7. package/dist/lib/storage/file-storage.d.ts +16 -16
  8. package/dist/lib/storage/file-storage.js +59 -64
  9. package/dist/lib/terminal/terminal-manager.d.ts +3 -5
  10. package/dist/lib/terminal/terminal-manager.js +10 -61
  11. package/dist/routes/api/v1/ai/route.js +361 -20
  12. package/dist/routes/api/v1/chats/[id]/index.js +2 -1
  13. package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
  14. package/dist/routes/api/v1/chats/[id]/route.js +30 -1
  15. package/dist/routes/api/v1/context/index.js +0 -2
  16. package/dist/routes/api/v1/git/index.js +23 -0
  17. package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
  18. package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
  19. package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
  20. package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
  21. package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
  22. package/dist/routes/api/v1/knowledge/index.js +1 -0
  23. package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
  24. package/dist/routes/api/v1/knowledge/route.js +176 -0
  25. package/dist/routes/api/v1/mail/index.d.ts +3 -0
  26. package/dist/routes/api/v1/mail/index.js +23 -0
  27. package/dist/routes/api/v1/mail/route.d.ts +294 -0
  28. package/dist/routes/api/v1/mail/route.js +344 -0
  29. package/dist/routes/api/v1/mcp/index.js +109 -34
  30. package/dist/routes/api/v1/slack/index.d.ts +3 -0
  31. package/dist/routes/api/v1/slack/index.js +15 -0
  32. package/dist/routes/api/v1/slack/route.d.ts +124 -0
  33. package/dist/routes/api/v1/slack/route.js +192 -0
  34. package/dist/routes/api/v1/tasks/[id]/route.d.ts +117 -0
  35. package/dist/routes/api/v1/tasks/[id]/route.js +166 -0
  36. package/dist/routes/api/v1/tasks/index.d.ts +3 -0
  37. package/dist/routes/api/v1/tasks/index.js +10 -0
  38. package/dist/routes/api/v1/tasks/route.d.ts +96 -0
  39. package/dist/routes/api/v1/tasks/route.js +136 -0
  40. package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
  41. package/dist/routes/api/v1/terminal/[taskId]/create/index.d.ts +3 -0
  42. package/dist/routes/api/v1/terminal/[taskId]/create/index.js +5 -0
  43. package/dist/routes/api/v1/terminal/[taskId]/create/route.d.ts +10 -0
  44. package/dist/routes/api/v1/terminal/[taskId]/create/route.js +27 -0
  45. package/dist/routes/api/v1/terminal/[taskId]/destroy/index.d.ts +3 -0
  46. package/dist/routes/api/v1/terminal/[taskId]/destroy/index.js +5 -0
  47. package/dist/routes/api/v1/terminal/[taskId]/destroy/route.d.ts +10 -0
  48. package/dist/routes/api/v1/terminal/[taskId]/destroy/route.js +21 -0
  49. package/dist/routes/api/v1/terminal/[taskId]/resize/index.d.ts +3 -0
  50. package/dist/routes/api/v1/terminal/[taskId]/resize/index.js +5 -0
  51. package/dist/routes/api/v1/terminal/[taskId]/resize/route.d.ts +10 -0
  52. package/dist/routes/api/v1/terminal/[taskId]/resize/route.js +21 -0
  53. package/dist/routes/api/v1/terminal/sessions/route.js +4 -4
  54. package/dist/server-with-static.js +14 -8
  55. package/dist/server.js +14 -8
  56. package/package.json +4 -4
  57. package/static/assets/{ActivityPage-DSSML9J-.js → ActivityPage-k4I7Q53O.js} +1 -1
  58. package/static/assets/ApiKeysSettingsPage-B1YvVdmg.js +2 -0
  59. package/static/assets/{ArchitectureEditPage-CIjqkpMz.js → ArchitectureEditPage-CpowsIx2.js} +1 -1
  60. package/static/assets/{ArchitecturePage-Db__w054.js → ArchitecturePage-DYxC_aMR.js} +1 -1
  61. package/static/assets/{AuthSettingsPage-Bpooi8Z0.js → AuthSettingsPage-DtSo78Y_.js} +2 -2
  62. package/static/assets/{CallbackPage-BGLKeyjv.js → CallbackPage-bROCGapx.js} +1 -1
  63. package/static/assets/CodePage-CPCj64rX.js +2 -0
  64. package/static/assets/{CollapsibleSection-B6RO5o5R.js → CollapsibleSection-M5cXbl92.js} +1 -1
  65. package/static/assets/DashboardPage-B9BZZfw6.js +51 -0
  66. package/static/assets/{GitPage-DxjLaRWe.js → GitPage-BiDtdSK1.js} +2 -2
  67. package/static/assets/GitSettingsPage-THm6wDjs.js +6 -0
  68. package/static/assets/IdentityPage-BC16skg6.js +6 -0
  69. package/static/assets/{ImplementationStepsEditor-DWjDyZzP.js → ImplementationStepsEditor-HliLQav5.js} +2 -2
  70. package/static/assets/IntegrationsSettingsPage-CC_VKIQa.js +1 -0
  71. package/static/assets/JobDetailPage-z1QQYvmU.js +1 -0
  72. package/static/assets/KnowledgeDetailPage-DzHXBS7Q.js +1 -0
  73. package/static/assets/KnowledgeEditPage-BwGnUH_m.js +1 -0
  74. package/static/assets/KnowledgePage-CGIVMS02.js +3 -0
  75. package/static/assets/{LoginPage-DptfKsWo.js → LoginPage-VQ3lcfLV.js} +1 -1
  76. package/static/assets/MailInboxPage-DiZKqwdU.js +1 -0
  77. package/static/assets/MailProcessingModal-DIeSQBoR.js +6 -0
  78. package/static/assets/MailReadPage-C8AACmZQ.js +1 -0
  79. package/static/assets/MailSentPage-C_5yFly_.js +1 -0
  80. package/static/assets/McpSettingsPage-i9YHcu1s.js +1 -0
  81. package/static/assets/NewKnowledgePage-BnVY7WUD.js +9 -0
  82. package/static/assets/{NewSkillPage-Cwy2MSr9.js → NewSkillPage-DwniHD6D.js} +1 -1
  83. package/static/assets/NewTaskPage-F5UX2WMc.js +90 -0
  84. package/static/assets/NotFoundPage-BbSZX_4L.js +6 -0
  85. package/static/assets/NotificationsSettingsPage-C8kjcift.js +1 -0
  86. package/static/assets/ProjectEditPage-DUUlIEqI.js +11 -0
  87. package/static/assets/{ProjectPage-DgUr4bVU.js → ProjectPage-Unz9PQpA.js} +1 -1
  88. package/static/assets/PromptsSettingsPage-DVpIuRKI.js +1 -0
  89. package/static/assets/ResourceDetailPage-DqHZ2KYD.js +1 -0
  90. package/static/assets/ResourcesPage-BP5tuAi-.js +41 -0
  91. package/static/assets/RoleEditPage-BgKu8S0-.js +13 -0
  92. package/static/assets/{RolePage-Sc-GFiL2.js → RolePage-Fed52Ov5.js} +1 -1
  93. package/static/assets/{RulesSettingsPage-DdMCzh9j.js → RulesSettingsPage-BQ2O0u66.js} +2 -2
  94. package/static/assets/SchedulePage-jkxjuzBx.js +4 -0
  95. package/static/assets/SkillDetailPage-k3Q2-NFd.js +1 -0
  96. package/static/assets/{SkillEditPage-BDd2CtAS.js → SkillEditPage-urF4snjo.js} +1 -1
  97. package/static/assets/SkillsPage-DlWDhEjR.js +8 -0
  98. package/static/assets/{SkillsSettingsPage-1N0JQOYc.js → SkillsSettingsPage-BViFgckG.js} +1 -1
  99. package/static/assets/SourceInput-CAFKTHw-.js +1 -0
  100. package/static/assets/{TagInput-D_SdcypZ.js → TagInput-C6lI-ePr.js} +1 -1
  101. package/static/assets/TaskDetailPage-DpbRHnW_.js +16 -0
  102. package/static/assets/TaskEditPage-DssRbW0h.js +1 -0
  103. package/static/assets/TasksPage-CD_eo0Bj.js +17 -0
  104. package/static/assets/TerminalPage-BG_wlccr.js +1 -0
  105. package/static/assets/TerminalSessionPage-CsK-LznK.js +8 -0
  106. package/static/assets/UserPreferencesPage-CWUq3efu.js +1 -0
  107. package/static/assets/UserSettingsPage-CduI_MGS.js +1 -0
  108. package/static/assets/UtilitiesPage-BAxokhLh.js +1 -0
  109. package/static/assets/{alert-BD5jo3SI.js → alert-BXsc6_qu.js} +1 -1
  110. package/static/assets/{arrow-down-BxcoVp6S.js → arrow-down-DmW_3gE8.js} +1 -1
  111. package/static/assets/{arrow-left-CdM_IPng.js → arrow-left-1S-835kP.js} +1 -1
  112. package/static/assets/{arrow-up-BOJ6ob9X.js → arrow-up-BYism_o1.js} +1 -1
  113. package/static/assets/arrow-up-down-Dw3J0a4i.js +6 -0
  114. package/static/assets/{badge-DEiQk9C9.js → badge-BUEY53dV.js} +1 -1
  115. package/static/assets/{browser-modal-Dp1eMxt6.js → browser-modal-DCNdI4NT.js} +2 -2
  116. package/static/assets/{card-BCFxXzRk.js → card-BcPlIAH5.js} +1 -1
  117. package/static/assets/{chevron-left-C25izNzZ.js → chevron-left-FMmNe7yP.js} +1 -1
  118. package/static/assets/{plus-iamYJu5V.js → chevron-up-CqM3won3.js} +2 -2
  119. package/static/assets/{chevrons-up-DqbWMOjv.js → chevrons-up-DTvCkIHc.js} +1 -1
  120. package/static/assets/{circle-alert-CMRMPnbY.js → circle-alert-dseM-Ib7.js} +1 -1
  121. package/static/assets/{circle-check-big-NI18oHuP.js → circle-check-big-jKg34xC-.js} +1 -1
  122. package/static/assets/{circle-check-D5wZZPvj.js → circle-check-eyo6pBP1.js} +1 -1
  123. package/static/assets/{circle-play-BhVU869u.js → circle-play-BrY_lNiH.js} +1 -1
  124. package/static/assets/{circle-x-BXDB-G_q.js → circle-x-uqmzEce1.js} +1 -1
  125. package/static/assets/{clipboard-DC2xmNVx.js → clipboard-tzPFoieb.js} +1 -1
  126. package/static/assets/{clock-CeCp7Pz1.js → clock-Bjc06QBM.js} +1 -1
  127. package/static/assets/code-DrYqPukx.js +6 -0
  128. package/static/assets/{download-ZF_XbTIA.js → download-Bg__QCLT.js} +1 -1
  129. package/static/assets/{external-link-CYBz87-P.js → external-link-CNDy2UUo.js} +1 -1
  130. package/static/assets/{eye-BT8MAvKY.js → eye-DLFBnC8t.js} +1 -1
  131. package/static/assets/{folder-git-2-BE9AIPnj.js → folder-git-2-DUqd0WRi.js} +1 -1
  132. package/static/assets/index-CHdBxVyk.css +2 -0
  133. package/static/assets/index-DFcWlnzl.js +487 -0
  134. package/static/assets/{info-DunFSp-x.js → info-D6jxZC5X.js} +1 -1
  135. package/static/assets/kiro-CX1mOsRO.js +17 -0
  136. package/static/assets/{label-vYhfrPMD.js → label-DBuh-ke5.js} +1 -1
  137. package/static/assets/{markdown-editor-BzZ8tCto.js → markdown-editor-B4YNQFT2.js} +1 -1
  138. package/static/assets/message-square-B5RWz_ff.js +6 -0
  139. package/static/assets/paperclip-4A_3MaPx.js +6 -0
  140. package/static/assets/{pause-BHonpdnw.js → pause-BzhKXHtR.js} +1 -1
  141. package/static/assets/{play-CCo7tau2.js → play-CHIf-Rcz.js} +1 -1
  142. package/static/assets/{radio-group-Db-pBuyW.js → radio-group-C1ct-VsJ.js} +1 -1
  143. package/static/assets/{refresh-cw-Bg7vQzOw.js → refresh-cw-B3OwrDUf.js} +1 -1
  144. package/static/assets/{search-CH2zaibZ.js → search-Cq1ksEdp.js} +1 -1
  145. package/static/assets/select-44mcS2_G.js +1 -0
  146. package/static/assets/{status-utils-BDOyevaX.js → status-utils-CDkPeVfP.js} +1 -1
  147. package/static/assets/{switch-CH-VOgPo.js → switch-CIwjYvCt.js} +1 -1
  148. package/static/assets/{tabs-XeRAjZYR.js → tabs-DTV6Su-h.js} +1 -1
  149. package/static/assets/{tag-CRP5nL5-.js → tag-p6yeowCW.js} +1 -1
  150. package/static/assets/{terminal-preview-DMJMuORo.js → terminal-preview-DN38x9Jm.js} +1 -1
  151. package/static/assets/use-terminal-BXJqOeJe.js +1 -0
  152. package/static/assets/video-BH5ChaoS.js +36 -0
  153. package/static/index.html +2 -2
  154. package/static/assets/ApiKeysSettingsPage-Chw9PNL5.js +0 -2
  155. package/static/assets/CodePage-CrokcH-S.js +0 -2
  156. package/static/assets/DashboardPage-BaSQQ8Nv.js +0 -41
  157. package/static/assets/GitSettingsPage-tKBXYAFm.js +0 -6
  158. package/static/assets/IdentityPage-D2yBjeYn.js +0 -11
  159. package/static/assets/IntegrationsSettingsPage-Bx8-0Ig4.js +0 -1
  160. package/static/assets/JobDetailPage-BQmTHled.js +0 -1
  161. package/static/assets/KnowledgeDetailPage-QMU2bC3L.js +0 -1
  162. package/static/assets/KnowledgeEditPage-DbMJVcLc.js +0 -1
  163. package/static/assets/KnowledgePage-aU1GxZSZ.js +0 -8
  164. package/static/assets/McpSettingsPage-C3WxFwRB.js +0 -1
  165. package/static/assets/NewKnowledgePage-Cbiswrw_.js +0 -9
  166. package/static/assets/NewProposalPage-B_sDMBTK.js +0 -90
  167. package/static/assets/ProjectEditPage-BznWiBBc.js +0 -11
  168. package/static/assets/PromptsSettingsPage-CY0-870a.js +0 -1
  169. package/static/assets/ProposalDetailPage-K4iMXHEg.js +0 -1
  170. package/static/assets/ProposalEditPage-jZOtCMqP.js +0 -1
  171. package/static/assets/ProposalsPage-C7n4-G05.js +0 -17
  172. package/static/assets/ResourcesPage-vB5-XkUv.js +0 -71
  173. package/static/assets/RoleEditPage-Cu7ZB3yj.js +0 -13
  174. package/static/assets/SchedulePage-Bkkf2wA0.js +0 -4
  175. package/static/assets/SkillDetailPage-5sDxf3Of.js +0 -1
  176. package/static/assets/SkillsPage-D2G7EfK8.js +0 -8
  177. package/static/assets/SourceInput-DlC0zwva.js +0 -1
  178. package/static/assets/TerminalPage--KZ7azvP.js +0 -1
  179. package/static/assets/TerminalSessionPage-ClvxK9ia.js +0 -13
  180. package/static/assets/UserPreferencesPage-CheOH7jZ.js +0 -1
  181. package/static/assets/UserSettingsPage-C8STDvfQ.js +0 -1
  182. package/static/assets/UtilitiesPage-9rTxR2md.js +0 -1
  183. package/static/assets/calendar-Cx5r9R7A.js +0 -6
  184. package/static/assets/droid-DqWsM2dp.js +0 -17
  185. package/static/assets/index-DVTgTsDa.css +0 -2
  186. package/static/assets/index-hqVgTgRB.js +0 -462
  187. package/static/assets/use-terminal-DuGZuvd2.js +0 -1
  188. package/static/assets/zap-BlzMp7dY.js +0 -6
@@ -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, listProposals, getProposal, createProposal, updateProposal, deleteProposal, } from '@lovelybunch/core';
10
- import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
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
- change_proposals: tool({
100
- description: proposalsFullTool.description,
101
- inputSchema: jsonSchema(proposalsFullTool.parameters),
99
+ tasks: tool({
100
+ description: tasksFullTool.description,
101
+ inputSchema: jsonSchema(tasksFullTool.parameters),
102
102
  execute: async (args) => {
103
- const result = await executeProposalsToolDirect(args, storage);
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 executeProposalsToolDirect(args, _storage) {
179
- const { operation, id, filters, proposal, updates } = args;
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 proposals = await listProposals(filters || {});
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: proposals,
187
- count: proposals.length,
188
- message: `Found ${proposals.length} tasks`
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 getProposal(id);
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 (!proposal) {
234
+ if (!task) {
207
235
  return { success: false, error: 'Task data is required for create operation' };
208
236
  }
209
- const created = await createProposal(proposal);
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 || proposal;
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 updateProposal(id, updateData);
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 deleteProposal(id);
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 proposals tool:', error);
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, 'context', 'knowledge');
1186
+ return join(basePath, 'knowledge');
846
1187
  }
847
1188
  function slugify(value) {
848
1189
  const slug = value
@@ -1,6 +1,7 @@
1
1
  import { Hono } from 'hono';
2
- import { GET, DELETE } from './route.js';
2
+ import { GET, PUT, DELETE } from './route.js';
3
3
  const app = new Hono();
4
4
  app.get('/', GET);
5
+ app.put('/', PUT);
5
6
  app.delete('/', DELETE);
6
7
  export default app;
@@ -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';
@@ -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;