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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (131) hide show
  1. package/dist/lib/terminal/terminal-manager.d.ts +1 -3
  2. package/dist/lib/terminal/terminal-manager.js +1 -52
  3. package/dist/routes/api/v1/ai/route.js +323 -2
  4. package/dist/routes/api/v1/chats/[id]/index.js +2 -1
  5. package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
  6. package/dist/routes/api/v1/chats/[id]/route.js +30 -1
  7. package/dist/routes/api/v1/context/index.js +0 -2
  8. package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
  9. package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
  10. package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
  11. package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
  12. package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
  13. package/dist/routes/api/v1/knowledge/index.js +1 -0
  14. package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
  15. package/dist/routes/api/v1/knowledge/route.js +176 -0
  16. package/dist/routes/api/v1/mcp/index.js +78 -3
  17. package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
  18. package/dist/server-with-static.js +2 -0
  19. package/dist/server.js +2 -0
  20. package/package.json +4 -4
  21. package/static/assets/{ActivityPage-DSSML9J-.js → ActivityPage-OxRci_V2.js} +1 -1
  22. package/static/assets/{ApiKeysSettingsPage-Chw9PNL5.js → ApiKeysSettingsPage-C0evI19e.js} +2 -2
  23. package/static/assets/{ArchitectureEditPage-CIjqkpMz.js → ArchitectureEditPage-D7xcH6dY.js} +4 -4
  24. package/static/assets/{ArchitecturePage-Db__w054.js → ArchitecturePage-pvnlX-NW.js} +1 -1
  25. package/static/assets/{AuthSettingsPage-Bpooi8Z0.js → AuthSettingsPage-Bu0CZ1rY.js} +2 -2
  26. package/static/assets/{CallbackPage-BGLKeyjv.js → CallbackPage-D0lkjxCT.js} +1 -1
  27. package/static/assets/CodePage-BJ4PC5nb.js +2 -0
  28. package/static/assets/{CollapsibleSection-B6RO5o5R.js → CollapsibleSection-Bt_ZLnJc.js} +1 -1
  29. package/static/assets/DashboardPage-BiffPdmj.js +41 -0
  30. package/static/assets/{GitPage-DxjLaRWe.js → GitPage-TrTxZ27J.js} +2 -2
  31. package/static/assets/{GitSettingsPage-tKBXYAFm.js → GitSettingsPage-D7q5xQd_.js} +2 -2
  32. package/static/assets/{IdentityPage-D2yBjeYn.js → IdentityPage-CY0Ak2j0.js} +3 -3
  33. package/static/assets/{ImplementationStepsEditor-DWjDyZzP.js → ImplementationStepsEditor-Ctx0CvbU.js} +2 -2
  34. package/static/assets/IntegrationsSettingsPage-C2wJVdM7.js +1 -0
  35. package/static/assets/{JobDetailPage-BQmTHled.js → JobDetailPage-Phx_IlKX.js} +1 -1
  36. package/static/assets/KnowledgeDetailPage-BdTUfWqj.js +1 -0
  37. package/static/assets/KnowledgeEditPage-D8XK4IUf.js +1 -0
  38. package/static/assets/KnowledgePage-Ci9G7Br-.js +8 -0
  39. package/static/assets/{LoginPage-DptfKsWo.js → LoginPage-Dqxd7cTa.js} +1 -1
  40. package/static/assets/McpSettingsPage-10n35zXi.js +1 -0
  41. package/static/assets/NewKnowledgePage-BlJzzuh7.js +9 -0
  42. package/static/assets/{NewProposalPage-B_sDMBTK.js → NewProposalPage-BP7Ttoxk.js} +2 -2
  43. package/static/assets/{NewSkillPage-Cwy2MSr9.js → NewSkillPage-ByqN--mH.js} +1 -1
  44. package/static/assets/ProjectEditPage-DKJTY2uc.js +11 -0
  45. package/static/assets/{ProjectPage-DgUr4bVU.js → ProjectPage-2VblKCWz.js} +1 -1
  46. package/static/assets/PromptsSettingsPage-B4mOhXuo.js +1 -0
  47. package/static/assets/ProposalDetailPage-m3ysyzpj.js +1 -0
  48. package/static/assets/ProposalEditPage-3XVg_paW.js +1 -0
  49. package/static/assets/{ProposalsPage-C7n4-G05.js → ProposalsPage-B3u0aFFz.js} +3 -3
  50. package/static/assets/ResourceDetailPage-somBLUpC.js +1 -0
  51. package/static/assets/ResourcesPage-2BbjIWfF.js +41 -0
  52. package/static/assets/{RoleEditPage-Cu7ZB3yj.js → RoleEditPage-CLzX7Xhi.js} +1 -1
  53. package/static/assets/{RolePage-Sc-GFiL2.js → RolePage-qXWXZ2FZ.js} +1 -1
  54. package/static/assets/{RulesSettingsPage-DdMCzh9j.js → RulesSettingsPage-BtM7p8F6.js} +3 -3
  55. package/static/assets/SchedulePage-4tFcIBSs.js +4 -0
  56. package/static/assets/{SkillDetailPage-5sDxf3Of.js → SkillDetailPage-CroSdaju.js} +1 -1
  57. package/static/assets/{SkillEditPage-BDd2CtAS.js → SkillEditPage-Czlo8WWT.js} +1 -1
  58. package/static/assets/{SkillsPage-D2G7EfK8.js → SkillsPage-CgULbcI-.js} +2 -2
  59. package/static/assets/{SkillsSettingsPage-1N0JQOYc.js → SkillsSettingsPage-DKtpy7qk.js} +1 -1
  60. package/static/assets/SourceInput-BITn1Y15.js +1 -0
  61. package/static/assets/{TagInput-D_SdcypZ.js → TagInput-BK91_M1N.js} +1 -1
  62. package/static/assets/TerminalPage-8fwvnOo2.js +1 -0
  63. package/static/assets/TerminalSessionPage-BhO5U48p.js +13 -0
  64. package/static/assets/UserPreferencesPage-DrgYEcxO.js +1 -0
  65. package/static/assets/UserSettingsPage-Dj6lKLi8.js +1 -0
  66. package/static/assets/UtilitiesPage-Djr4qT5L.js +1 -0
  67. package/static/assets/{alert-BD5jo3SI.js → alert-CsMvyYoX.js} +1 -1
  68. package/static/assets/{arrow-down-BxcoVp6S.js → arrow-down-BZnfbld8.js} +1 -1
  69. package/static/assets/{arrow-left-CdM_IPng.js → arrow-left-WGBYWq3h.js} +1 -1
  70. package/static/assets/{arrow-up-BOJ6ob9X.js → arrow-up-BByVUPE7.js} +1 -1
  71. package/static/assets/{badge-DEiQk9C9.js → badge-AwLOflf5.js} +1 -1
  72. package/static/assets/{browser-modal-Dp1eMxt6.js → browser-modal-BzGNFfTG.js} +2 -2
  73. package/static/assets/{card-BCFxXzRk.js → card-SN5gKnu7.js} +1 -1
  74. package/static/assets/{chevron-left-C25izNzZ.js → chevron-left-C7uNq9l_.js} +1 -1
  75. package/static/assets/{plus-iamYJu5V.js → chevron-up-CHdIiLxL.js} +2 -2
  76. package/static/assets/{chevrons-up-DqbWMOjv.js → chevrons-up-TXwQuoUN.js} +1 -1
  77. package/static/assets/{circle-alert-CMRMPnbY.js → circle-alert-37E5gU9K.js} +1 -1
  78. package/static/assets/{circle-check-D5wZZPvj.js → circle-check-D02pWDME.js} +1 -1
  79. package/static/assets/{circle-check-big-NI18oHuP.js → circle-check-big-nY4PntB5.js} +1 -1
  80. package/static/assets/{circle-play-BhVU869u.js → circle-play-7EXFLo4F.js} +1 -1
  81. package/static/assets/{circle-x-BXDB-G_q.js → circle-x-By4JoTHB.js} +1 -1
  82. package/static/assets/{clipboard-DC2xmNVx.js → clipboard-BdymjxLO.js} +1 -1
  83. package/static/assets/{clock-CeCp7Pz1.js → clock-HDu44KTo.js} +1 -1
  84. package/static/assets/{download-ZF_XbTIA.js → download-Cv2G2Eg9.js} +1 -1
  85. package/static/assets/{droid-DqWsM2dp.js → droid-CPteN3f9.js} +1 -1
  86. package/static/assets/{external-link-CYBz87-P.js → external-link-DwMXcCCj.js} +1 -1
  87. package/static/assets/{eye-BT8MAvKY.js → eye-DYnjJzdb.js} +1 -1
  88. package/static/assets/{folder-git-2-BE9AIPnj.js → folder-git-2-COeWFPHS.js} +1 -1
  89. package/static/assets/index-9Tv-j_Ga.js +472 -0
  90. package/static/assets/index-GFQ5RqVh.css +2 -0
  91. package/static/assets/{info-DunFSp-x.js → info-BmtuPMhv.js} +1 -1
  92. package/static/assets/{label-vYhfrPMD.js → label-TGqbNfMO.js} +1 -1
  93. package/static/assets/{markdown-editor-BzZ8tCto.js → markdown-editor-ls1JPK_e.js} +1 -1
  94. package/static/assets/{pause-BHonpdnw.js → pause-CAWbvTiL.js} +1 -1
  95. package/static/assets/{play-CCo7tau2.js → play-DF_Qeu0H.js} +1 -1
  96. package/static/assets/{radio-group-Db-pBuyW.js → radio-group-DYTbywtK.js} +1 -1
  97. package/static/assets/{refresh-cw-Bg7vQzOw.js → refresh-cw-BFZxHqbC.js} +1 -1
  98. package/static/assets/{search-CH2zaibZ.js → search-Dr90tbch.js} +1 -1
  99. package/static/assets/select-Cs5qtMYV.js +1 -0
  100. package/static/assets/{switch-CH-VOgPo.js → switch-4TDb6YiQ.js} +1 -1
  101. package/static/assets/{tabs-XeRAjZYR.js → tabs-BrbEvF4V.js} +1 -1
  102. package/static/assets/{tag-CRP5nL5-.js → tag-DrQkepeD.js} +1 -1
  103. package/static/assets/{terminal-preview-DMJMuORo.js → terminal-preview-uuKF9_x4.js} +1 -1
  104. package/static/assets/use-terminal-BG5UXuVE.js +1 -0
  105. package/static/assets/video-DYA2WfbA.js +36 -0
  106. package/static/assets/{zap-BlzMp7dY.js → zap-h9QOsasv.js} +1 -1
  107. package/static/index.html +2 -2
  108. package/static/assets/CodePage-CrokcH-S.js +0 -2
  109. package/static/assets/DashboardPage-BaSQQ8Nv.js +0 -41
  110. package/static/assets/IntegrationsSettingsPage-Bx8-0Ig4.js +0 -1
  111. package/static/assets/KnowledgeDetailPage-QMU2bC3L.js +0 -1
  112. package/static/assets/KnowledgeEditPage-DbMJVcLc.js +0 -1
  113. package/static/assets/KnowledgePage-aU1GxZSZ.js +0 -8
  114. package/static/assets/McpSettingsPage-C3WxFwRB.js +0 -1
  115. package/static/assets/NewKnowledgePage-Cbiswrw_.js +0 -9
  116. package/static/assets/ProjectEditPage-BznWiBBc.js +0 -11
  117. package/static/assets/PromptsSettingsPage-CY0-870a.js +0 -1
  118. package/static/assets/ProposalDetailPage-K4iMXHEg.js +0 -1
  119. package/static/assets/ProposalEditPage-jZOtCMqP.js +0 -1
  120. package/static/assets/ResourcesPage-vB5-XkUv.js +0 -71
  121. package/static/assets/SchedulePage-Bkkf2wA0.js +0 -4
  122. package/static/assets/SourceInput-DlC0zwva.js +0 -1
  123. package/static/assets/TerminalPage--KZ7azvP.js +0 -1
  124. package/static/assets/TerminalSessionPage-ClvxK9ia.js +0 -13
  125. package/static/assets/UserPreferencesPage-CheOH7jZ.js +0 -1
  126. package/static/assets/UserSettingsPage-C8STDvfQ.js +0 -1
  127. package/static/assets/UtilitiesPage-9rTxR2md.js +0 -1
  128. package/static/assets/calendar-Cx5r9R7A.js +0 -6
  129. package/static/assets/index-DVTgTsDa.css +0 -2
  130. package/static/assets/index-hqVgTgRB.js +0 -462
  131. package/static/assets/use-terminal-DuGZuvd2.js +0 -1
@@ -7,8 +7,6 @@ export interface TerminalSession {
7
7
  websocket?: WebSocket;
8
8
  createdAt: Date;
9
9
  lastActivity: Date;
10
- enableLogging?: boolean;
11
- logFilePath?: string;
12
10
  backlog: string;
13
11
  previewSockets?: Set<WebSocket>;
14
12
  previewBuffer?: string;
@@ -21,7 +19,7 @@ export declare class TerminalManager {
21
19
  private cleanupInterval;
22
20
  private static readonly MAX_BACKLOG_BYTES;
23
21
  constructor();
24
- createSession(proposalId: string, enableLogging?: boolean, shellPreference?: ShellType, startupCommand?: string): Promise<TerminalSession>;
22
+ createSession(proposalId: string, shellPreference?: ShellType, startupCommand?: string): Promise<TerminalSession>;
25
23
  getSession(sessionId: string): TerminalSession | undefined;
26
24
  getSessionsByProposal(proposalId: string): TerminalSession[];
27
25
  attachWebSocket(sessionId: string, ws: WebSocket): boolean;
@@ -16,7 +16,7 @@ export class TerminalManager {
16
16
  this.cleanupInactiveSessions();
17
17
  }, 5 * 60 * 1000);
18
18
  }
19
- async createSession(proposalId, enableLogging = false, shellPreference = 'bash', startupCommand) {
19
+ async createSession(proposalId, shellPreference = 'bash', startupCommand) {
20
20
  const sessionId = `${proposalId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
21
21
  // Get the project root directory
22
22
  let projectRoot;
@@ -65,22 +65,6 @@ export class TerminalManager {
65
65
  catch (error) {
66
66
  console.error('Error creating init script:', error);
67
67
  }
68
- // Set up logging if enabled
69
- let logFilePath;
70
- if (enableLogging) {
71
- const sessionsDir = path.join(projectRoot, '.nut', 'sessions');
72
- try {
73
- fs.mkdirSync(sessionsDir, { recursive: true });
74
- logFilePath = path.join(sessionsDir, `${sessionId}.log`);
75
- // Create log file with session header
76
- const logHeader = `# Terminal Session Log\n# Session ID: ${sessionId}\n# Proposal ID: ${proposalId}\n# Created: ${new Date().toISOString()}\n\n`;
77
- fs.writeFileSync(logFilePath, logHeader);
78
- }
79
- catch (error) {
80
- console.error('Error setting up session logging:', error);
81
- logFilePath = undefined;
82
- }
83
- }
84
68
  // Get shell arguments
85
69
  const shellArgs = getShellArgs(shellPath, initScriptPath);
86
70
  // Prepare environment variables with API keys injected.
@@ -120,8 +104,6 @@ export class TerminalManager {
120
104
  pty: ptyProcess,
121
105
  createdAt: new Date(),
122
106
  lastActivity: new Date(),
123
- enableLogging,
124
- logFilePath,
125
107
  backlog: '',
126
108
  previewSockets: new Set(),
127
109
  previewBuffer: '',
@@ -153,17 +135,6 @@ export class TerminalManager {
153
135
  // Set up PTY event handlers
154
136
  ptyProcess.onData((data) => {
155
137
  session.lastActivity = new Date();
156
- // Log data if logging is enabled
157
- if (session.enableLogging && session.logFilePath) {
158
- try {
159
- const timestamp = new Date().toISOString();
160
- const logEntry = `[${timestamp}] OUTPUT: ${data}`;
161
- fs.appendFileSync(session.logFilePath, logEntry);
162
- }
163
- catch (error) {
164
- console.error('Error writing to session log:', error);
165
- }
166
- }
167
138
  // Maintain in-memory backlog (trim to last MAX_BACKLOG_BYTES)
168
139
  try {
169
140
  // Append and trim to max size
@@ -187,17 +158,6 @@ export class TerminalManager {
187
158
  ptyProcess.onExit((e) => {
188
159
  const endTime = new Date();
189
160
  const duration = endTime.getTime() - session.createdAt.getTime();
190
- // Log session end if logging is enabled
191
- if (session.enableLogging && session.logFilePath) {
192
- try {
193
- const timestamp = endTime.toISOString();
194
- const logEntry = `\n[${timestamp}] SESSION ENDED: Exit code ${e.exitCode}\n`;
195
- fs.appendFileSync(session.logFilePath, logEntry);
196
- }
197
- catch (error) {
198
- console.error('Error writing session end to log:', error);
199
- }
200
- }
201
161
  // Log session end to activity log
202
162
  try {
203
163
  const logger = getLogger();
@@ -289,17 +249,6 @@ export class TerminalManager {
289
249
  const data = JSON.parse(message.toString());
290
250
  switch (data.type) {
291
251
  case 'input':
292
- // Log input if logging is enabled
293
- if (session.enableLogging && session.logFilePath) {
294
- try {
295
- const timestamp = new Date().toISOString();
296
- const logEntry = `[${timestamp}] INPUT: ${data.data}`;
297
- fs.appendFileSync(session.logFilePath, logEntry);
298
- }
299
- catch (error) {
300
- console.error('Error writing input to session log:', error);
301
- }
302
- }
303
252
  session.pty.write(data.data);
304
253
  session.lastActivity = new Date();
305
254
  break;
@@ -7,7 +7,7 @@ import { ZodError } from 'zod';
7
7
  import { streamText, tool, jsonSchema, stepCountIs } from 'ai';
8
8
  import { createAnthropic } from '@ai-sdk/anthropic';
9
9
  import { getLogsDir, listProposals, getProposal, createProposal, updateProposal, deleteProposal, } from '@lovelybunch/core';
10
- import { proposalsFullTool, knowledgeTool, normalizeKnowledgeMetadata, eventsTool, projectContextTool, architectureContextTool, roleContextTool } from '@lovelybunch/mcp';
10
+ import { proposalsFullTool, 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';
@@ -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:', {
@@ -817,6 +825,319 @@ async function executeRoleContextToolDirect(args) {
817
825
  return { success: false, error: error.message || 'Role context tool execution failed' };
818
826
  }
819
827
  }
828
+ // ─── Resources Tool ─────────────────────────────────────────────────────────
829
+ // Uses internal HTTP calls to the same server's resource endpoints so that all
830
+ // existing validation, thumbnail generation, and error handling is reused.
831
+ function getInternalApiBase() {
832
+ const port = process.env.PORT ? parseInt(process.env.PORT) : 3001;
833
+ return `http://127.0.0.1:${port}`;
834
+ }
835
+ async function executeResourcesToolDirect(args) {
836
+ const { operation, query, type_filter, resource_id, prompt, model, aspect_ratio, text, voice, duration, resolution, url, tags, description } = args;
837
+ const apiBase = getInternalApiBase();
838
+ try {
839
+ switch (operation) {
840
+ case 'list': {
841
+ const response = await fetch(`${apiBase}/api/v1/resources`);
842
+ const result = await response.json();
843
+ if (!result.success || !result.data) {
844
+ return { success: false, error: 'Failed to list resources' };
845
+ }
846
+ let resources = result.data;
847
+ // Apply type filter
848
+ if (type_filter) {
849
+ const prefix = type_filter.toLowerCase();
850
+ resources = resources.filter((r) => r.type?.toLowerCase().startsWith(prefix));
851
+ }
852
+ // Apply search filter
853
+ if (query) {
854
+ const q = query.toLowerCase();
855
+ resources = resources.filter((r) => r.name?.toLowerCase().includes(q) ||
856
+ r.metadata?.description?.toLowerCase().includes(q) ||
857
+ r.metadata?.tags?.some((tag) => tag.toLowerCase().includes(q)));
858
+ }
859
+ // Return a concise summary to keep token usage reasonable
860
+ const summary = resources.map((r) => ({
861
+ id: r.id,
862
+ name: r.name,
863
+ type: r.type,
864
+ size: r.size,
865
+ tags: r.metadata?.tags,
866
+ description: r.metadata?.description
867
+ ? (r.metadata.description.length > 100
868
+ ? r.metadata.description.slice(0, 100) + '...'
869
+ : r.metadata.description)
870
+ : undefined,
871
+ }));
872
+ return {
873
+ success: true,
874
+ data: summary,
875
+ count: summary.length,
876
+ message: query
877
+ ? `Found ${summary.length} resources matching "${query}"`
878
+ : `Found ${summary.length} resources`
879
+ };
880
+ }
881
+ case 'get': {
882
+ if (!resource_id) {
883
+ return { success: false, error: 'resource_id is required for get operation' };
884
+ }
885
+ const response = await fetch(`${apiBase}/api/v1/resources`);
886
+ const result = await response.json();
887
+ if (!result.success || !result.data) {
888
+ return { success: false, error: 'Failed to fetch resources' };
889
+ }
890
+ const resource = result.data.find((r) => r.id === resource_id);
891
+ if (!resource) {
892
+ return { success: false, error: `Resource '${resource_id}' not found` };
893
+ }
894
+ return {
895
+ success: true,
896
+ data: resource,
897
+ message: `Retrieved resource ${resource.name} (${resource.id})`
898
+ };
899
+ }
900
+ case 'generate_image': {
901
+ if (!prompt) {
902
+ return { success: false, error: 'prompt is required for generate_image operation' };
903
+ }
904
+ // Step 1: Generate the image via Replicate
905
+ const genBody = {
906
+ prompt,
907
+ model: model || 'nano-banana-pro',
908
+ dimensions: aspect_ratio || '16:9',
909
+ output_format: 'png',
910
+ };
911
+ const genResponse = await fetch(`${apiBase}/api/v1/resources/generate`, {
912
+ method: 'POST',
913
+ headers: { 'Content-Type': 'application/json' },
914
+ body: JSON.stringify(genBody),
915
+ signal: AbortSignal.timeout(300_000), // 5 min
916
+ });
917
+ const genResult = await genResponse.json();
918
+ if (!genResult.success || !genResult.data?.imageUrl) {
919
+ const msg = genResult.error?.message || 'Image generation failed';
920
+ return { success: false, error: msg };
921
+ }
922
+ // Step 2: Download and save to resources
923
+ const saved = await downloadAndSaveToResources(genResult.data.imageUrl, apiBase, { type: 'image', prompt, model: model || 'nano-banana-pro', tags, description });
924
+ return {
925
+ success: true,
926
+ data: saved,
927
+ message: `Generated and saved image "${saved.name}" (${saved.id}). It is now available in the Resources section.`
928
+ };
929
+ }
930
+ case 'generate_audio': {
931
+ if (!text) {
932
+ return { success: false, error: 'text is required for generate_audio operation' };
933
+ }
934
+ const audioBody = {
935
+ text,
936
+ voice_id: voice || 'English_Trustworth_Man',
937
+ speed: 1,
938
+ pitch: 0,
939
+ emotion: 'auto',
940
+ language_boost: 'English',
941
+ audio_format: 'mp3',
942
+ };
943
+ const audioResponse = await fetch(`${apiBase}/api/v1/resources/generate-audio`, {
944
+ method: 'POST',
945
+ headers: { 'Content-Type': 'application/json' },
946
+ body: JSON.stringify(audioBody),
947
+ signal: AbortSignal.timeout(300_000),
948
+ });
949
+ const audioResult = await audioResponse.json();
950
+ if (!audioResult.success || !audioResult.data?.audioUrl) {
951
+ const msg = audioResult.error?.message || 'Audio generation failed';
952
+ return { success: false, error: msg };
953
+ }
954
+ const savedAudio = await downloadAndSaveToResources(audioResult.data.audioUrl, apiBase, { type: 'audio', prompt: text, model: 'minimax/speech-02-turbo', tags, description });
955
+ return {
956
+ success: true,
957
+ data: savedAudio,
958
+ message: `Generated and saved audio "${savedAudio.name}" (${savedAudio.id}). It is now available in the Resources section.`
959
+ };
960
+ }
961
+ case 'generate_video': {
962
+ if (!prompt) {
963
+ return { success: false, error: 'prompt is required for generate_video operation' };
964
+ }
965
+ const videoBody = {
966
+ prompt,
967
+ duration: duration || 8,
968
+ resolution: resolution || '720p',
969
+ aspect_ratio: aspect_ratio || '16:9',
970
+ generate_audio: true,
971
+ };
972
+ const videoResponse = await fetch(`${apiBase}/api/v1/resources/generate-video`, {
973
+ method: 'POST',
974
+ headers: { 'Content-Type': 'application/json' },
975
+ body: JSON.stringify(videoBody),
976
+ signal: AbortSignal.timeout(300_000),
977
+ });
978
+ const videoResult = await videoResponse.json();
979
+ if (!videoResult.success || !videoResult.data?.videoUrl) {
980
+ const msg = videoResult.error?.message || 'Video generation failed';
981
+ return { success: false, error: msg };
982
+ }
983
+ const savedVideo = await downloadAndSaveToResources(videoResult.data.videoUrl, apiBase, { type: 'video', prompt, model: 'google/veo-3.1-fast', tags, description });
984
+ return {
985
+ success: true,
986
+ data: savedVideo,
987
+ message: `Generated and saved video "${savedVideo.name}" (${savedVideo.id}). It is now available in the Resources section.`
988
+ };
989
+ }
990
+ case 'add_from_url': {
991
+ if (!url) {
992
+ return { success: false, error: 'url is required for add_from_url operation' };
993
+ }
994
+ // Download the URL
995
+ const dlResponse = await fetch(url);
996
+ if (!dlResponse.ok) {
997
+ return { success: false, error: `Failed to download URL: ${dlResponse.statusText}` };
998
+ }
999
+ const buffer = Buffer.from(await dlResponse.arrayBuffer());
1000
+ const contentType = dlResponse.headers.get('content-type') || 'application/octet-stream';
1001
+ // Derive filename from URL path
1002
+ let fileName;
1003
+ try {
1004
+ const urlPath = new URL(url).pathname;
1005
+ fileName = basename(urlPath);
1006
+ if (!fileName || fileName === '/') {
1007
+ fileName = `downloaded-${Date.now()}`;
1008
+ }
1009
+ }
1010
+ catch {
1011
+ fileName = `downloaded-${Date.now()}`;
1012
+ }
1013
+ // Add extension from content-type if missing
1014
+ if (!fileName.includes('.')) {
1015
+ const extMap = {
1016
+ 'image/png': '.png', 'image/jpeg': '.jpg', 'image/webp': '.webp',
1017
+ 'image/gif': '.gif', 'image/svg+xml': '.svg',
1018
+ 'audio/mpeg': '.mp3', 'audio/wav': '.wav', 'audio/ogg': '.ogg',
1019
+ 'video/mp4': '.mp4', 'video/webm': '.webm',
1020
+ 'application/pdf': '.pdf', 'application/json': '.json',
1021
+ };
1022
+ const ext = Object.entries(extMap).find(([ct]) => contentType.includes(ct))?.[1] || '.bin';
1023
+ fileName += ext;
1024
+ }
1025
+ const mimeType = contentType.split(';')[0].trim();
1026
+ // Upload to resources via FormData
1027
+ const formData = new FormData();
1028
+ const blob = new Blob([buffer], { type: mimeType });
1029
+ formData.set('file', blob, fileName);
1030
+ if (tags)
1031
+ formData.set('tags', tags);
1032
+ if (description)
1033
+ formData.set('description', description);
1034
+ const uploadResponse = await fetch(`${apiBase}/api/v1/resources`, {
1035
+ method: 'POST',
1036
+ body: formData,
1037
+ });
1038
+ const uploadResult = await uploadResponse.json();
1039
+ if (!uploadResult.success || !uploadResult.data) {
1040
+ return { success: false, error: uploadResult.error?.message || 'Upload failed' };
1041
+ }
1042
+ return {
1043
+ success: true,
1044
+ data: uploadResult.data,
1045
+ message: `Downloaded and saved "${uploadResult.data.name}" (${uploadResult.data.id}) from URL. It is now available in the Resources section.`
1046
+ };
1047
+ }
1048
+ default:
1049
+ return {
1050
+ success: false,
1051
+ error: `Unknown operation: ${operation}. Supported: list, get, generate_image, generate_audio, generate_video, add_from_url`
1052
+ };
1053
+ }
1054
+ }
1055
+ catch (error) {
1056
+ console.error('Error executing resources tool:', error);
1057
+ return { success: false, error: error.message || 'Resources tool execution failed' };
1058
+ }
1059
+ }
1060
+ /**
1061
+ * Download media from a URL and upload it to the resources API.
1062
+ * Used by generation operations to auto-save generated content.
1063
+ */
1064
+ async function downloadAndSaveToResources(mediaUrl, apiBase, meta) {
1065
+ const response = await fetch(mediaUrl);
1066
+ if (!response.ok) {
1067
+ throw new Error(`Failed to download generated ${meta.type}`);
1068
+ }
1069
+ const buffer = Buffer.from(await response.arrayBuffer());
1070
+ const contentType = response.headers.get('content-type') || '';
1071
+ // Determine extension from content-type
1072
+ let extension = 'bin';
1073
+ let mimeType = contentType.split(';')[0].trim();
1074
+ if (meta.type === 'image') {
1075
+ if (contentType.includes('png'))
1076
+ extension = 'png';
1077
+ else if (contentType.includes('jpeg') || contentType.includes('jpg'))
1078
+ extension = 'jpg';
1079
+ else if (contentType.includes('webp'))
1080
+ extension = 'webp';
1081
+ else
1082
+ extension = 'png';
1083
+ mimeType = mimeType || `image/${extension}`;
1084
+ }
1085
+ else if (meta.type === 'audio') {
1086
+ if (contentType.includes('mp3') || contentType.includes('mpeg'))
1087
+ extension = 'mp3';
1088
+ else if (contentType.includes('wav'))
1089
+ extension = 'wav';
1090
+ else if (contentType.includes('ogg'))
1091
+ extension = 'ogg';
1092
+ else
1093
+ extension = 'mp3';
1094
+ mimeType = mimeType || `audio/${extension}`;
1095
+ }
1096
+ else if (meta.type === 'video') {
1097
+ if (contentType.includes('mp4'))
1098
+ extension = 'mp4';
1099
+ else if (contentType.includes('webm'))
1100
+ extension = 'webm';
1101
+ else
1102
+ extension = 'mp4';
1103
+ mimeType = mimeType || `video/${extension}`;
1104
+ }
1105
+ const fileName = `generated-${meta.type}-${Date.now()}.${extension}`;
1106
+ // Upload via FormData to the resources endpoint (triggers thumbnail generation, etc.)
1107
+ const formData = new FormData();
1108
+ const blob = new Blob([buffer], { type: mimeType });
1109
+ formData.set('file', blob, fileName);
1110
+ // Build description with generation metadata
1111
+ const descParts = [];
1112
+ if (meta.description)
1113
+ descParts.push(meta.description);
1114
+ if (meta.prompt)
1115
+ descParts.push(`Generated with: "${meta.prompt}"`);
1116
+ if (meta.model)
1117
+ descParts.push(`Model: ${meta.model}`);
1118
+ const fullDesc = descParts.join(' | ');
1119
+ if (fullDesc)
1120
+ formData.set('description', fullDesc);
1121
+ // Build tags
1122
+ const tagParts = [];
1123
+ if (meta.tags)
1124
+ tagParts.push(meta.tags);
1125
+ tagParts.push('ai-generated', meta.type);
1126
+ formData.set('tags', tagParts.join(', '));
1127
+ if (meta.prompt)
1128
+ formData.set('generationPrompt', meta.prompt);
1129
+ if (meta.model)
1130
+ formData.set('generationModel', meta.model);
1131
+ const uploadResponse = await fetch(`${apiBase}/api/v1/resources`, {
1132
+ method: 'POST',
1133
+ body: formData,
1134
+ });
1135
+ const result = await uploadResponse.json();
1136
+ if (!result.success || !result.data) {
1137
+ throw new Error('Failed to save generated media to resources');
1138
+ }
1139
+ return result.data;
1140
+ }
820
1141
  // ─── Helper Functions ────────────────────────────────────────────────────────
821
1142
  function getContextBasePath() {
822
1143
  let basePath;
@@ -842,7 +1163,7 @@ function getKnowledgeBasePath() {
842
1163
  else {
843
1164
  basePath = pathResolve(process.cwd(), '.nut');
844
1165
  }
845
- return join(basePath, 'context', 'knowledge');
1166
+ return join(basePath, 'knowledge');
846
1167
  }
847
1168
  function slugify(value) {
848
1169
  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;
@@ -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;