@lovelybunch/api 1.0.69-alpha.14 → 1.0.69-alpha.15
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/storage/file-storage.js +16 -7
- package/dist/routes/api/v1/config/index.js +2 -1
- package/dist/routes/api/v1/config/route.d.ts +11 -0
- package/dist/routes/api/v1/config/route.js +104 -0
- package/dist/routes/api/v1/jobs/[id]/runs/[runId]/log/route.d.ts +2 -2
- package/dist/routes/api/v1/resources/[id]/route.js +11 -7
- package/dist/routes/api/v1/resources/generate/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/generate/index.js +5 -0
- package/dist/routes/api/v1/resources/generate/route.d.ts +19 -0
- package/dist/routes/api/v1/resources/generate/route.js +205 -0
- package/dist/routes/api/v1/resources/index.js +2 -0
- package/dist/routes/api/v1/version/index.d.ts +3 -0
- package/dist/routes/api/v1/version/index.js +5 -0
- package/dist/routes/api/v1/version/route.d.ts +32 -0
- package/dist/routes/api/v1/version/route.js +61 -0
- package/dist/server-with-static.js +2 -0
- package/dist/server.js +2 -0
- package/package.json +5 -4
- package/static/assets/{index-CxKjZTkY.js → index-DGl_L0ie.js} +269 -214
- package/static/assets/index-uyccX96d.css +33 -0
- package/static/index.html +2 -2
- package/static/assets/index-DXvwYSJZ.css +0 -33
|
@@ -122,19 +122,28 @@ export class FileStorageAdapter {
|
|
|
122
122
|
// Never allow id to be updated to prevent overwriting other proposals
|
|
123
123
|
const { id: _, ...safeUpdates } = updates;
|
|
124
124
|
// Handle comments separately as they're stored at root level in file format
|
|
125
|
-
|
|
125
|
+
// Comments can come from either root level or metadata.comments
|
|
126
|
+
let commentsToStore = existing.metadata.comments || [];
|
|
127
|
+
if (safeUpdates.comments) {
|
|
128
|
+
// If comments are provided at root level, use them
|
|
129
|
+
commentsToStore = safeUpdates.comments;
|
|
130
|
+
}
|
|
131
|
+
else if (safeUpdates.metadata?.comments) {
|
|
132
|
+
// If comments are provided in metadata, use them
|
|
133
|
+
commentsToStore = safeUpdates.metadata.comments;
|
|
134
|
+
}
|
|
135
|
+
const updated = {
|
|
126
136
|
...existing,
|
|
127
137
|
...safeUpdates,
|
|
128
138
|
metadata: {
|
|
129
139
|
...existing.metadata,
|
|
130
140
|
...safeUpdates.metadata,
|
|
131
|
-
updatedAt: new Date()
|
|
132
|
-
|
|
141
|
+
updatedAt: new Date(),
|
|
142
|
+
comments: commentsToStore
|
|
143
|
+
},
|
|
144
|
+
// Store comments at the root level for the file format
|
|
145
|
+
comments: commentsToStore
|
|
133
146
|
};
|
|
134
|
-
// If comments are being updated, store them at the root level for the file format
|
|
135
|
-
if (safeUpdates.comments) {
|
|
136
|
-
updated.comments = safeUpdates.comments;
|
|
137
|
-
}
|
|
138
147
|
await this.createCP(updated);
|
|
139
148
|
}
|
|
140
149
|
async deleteCP(id) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { GET, PUT, TEST } from './route.js';
|
|
2
|
+
import { GET, PUT, TEST, EXPORT } from './route.js';
|
|
3
3
|
const config = new Hono();
|
|
4
4
|
config.get('/', GET);
|
|
5
5
|
config.put('/', PUT);
|
|
6
6
|
config.post('/test', TEST);
|
|
7
|
+
config.post('/export-to-env', EXPORT);
|
|
7
8
|
export default config;
|
|
@@ -30,3 +30,14 @@ export declare function TEST(c: Context): Promise<(Response & import("hono").Typ
|
|
|
30
30
|
success: false;
|
|
31
31
|
message: string;
|
|
32
32
|
}, 501, "json">)>;
|
|
33
|
+
export declare function EXPORT(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
34
|
+
success: false;
|
|
35
|
+
message: string;
|
|
36
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
37
|
+
success: false;
|
|
38
|
+
message: string;
|
|
39
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
40
|
+
success: true;
|
|
41
|
+
message: string;
|
|
42
|
+
envPath: string;
|
|
43
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
@@ -240,3 +240,107 @@ export async function TEST(c) {
|
|
|
240
240
|
return c.json({ success: false, message: 'Invalid request body' }, 400);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
+
// Provider to environment variable mapping
|
|
244
|
+
const PROVIDER_ENV_VARS = {
|
|
245
|
+
openrouter: 'OPENROUTER_API_KEY',
|
|
246
|
+
anthropic: 'ANTHROPIC_API_KEY',
|
|
247
|
+
openai: 'OPENAI_API_KEY',
|
|
248
|
+
gemini: 'GEMINI_API_KEY',
|
|
249
|
+
factorydroid: 'FACTORY_DROID_API_KEY',
|
|
250
|
+
bedrock: 'AWS_BEDROCK_API_KEY',
|
|
251
|
+
baseten: 'BASETEN_API_KEY',
|
|
252
|
+
fireworks: 'FIREWORKS_API_KEY',
|
|
253
|
+
deepinfra: 'DEEPINFRA_API_KEY'
|
|
254
|
+
};
|
|
255
|
+
// Helper to find workspace root (where .env should be written)
|
|
256
|
+
async function findWorkspaceRoot() {
|
|
257
|
+
// Try to find .nut directory first
|
|
258
|
+
let currentDir = process.cwd();
|
|
259
|
+
while (currentDir !== path.parse(currentDir).root) {
|
|
260
|
+
const nutPath = path.join(currentDir, '.nut');
|
|
261
|
+
try {
|
|
262
|
+
await fs.access(nutPath);
|
|
263
|
+
// Found .nut directory, return parent as workspace root
|
|
264
|
+
return currentDir;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
currentDir = path.dirname(currentDir);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
// Fallback to cwd if no .nut found
|
|
271
|
+
return process.cwd();
|
|
272
|
+
}
|
|
273
|
+
// POST /api/v1/config/export-to-env
|
|
274
|
+
// Body: { provider: string }
|
|
275
|
+
// Exports the saved API key for a provider to a .env file in workspace root
|
|
276
|
+
export async function EXPORT(c) {
|
|
277
|
+
try {
|
|
278
|
+
const body = await c.req.json();
|
|
279
|
+
const provider = (body?.provider || '').toString();
|
|
280
|
+
if (!provider) {
|
|
281
|
+
return c.json({ success: false, message: 'Missing provider' }, 400);
|
|
282
|
+
}
|
|
283
|
+
const envVarName = PROVIDER_ENV_VARS[provider];
|
|
284
|
+
if (!envVarName) {
|
|
285
|
+
return c.json({ success: false, message: `Unknown provider: ${provider}` }, 400);
|
|
286
|
+
}
|
|
287
|
+
// Load the API key from global config
|
|
288
|
+
const configPath = await getGlobalConfigPath();
|
|
289
|
+
let config = { apiKeys: {}, defaults: {} };
|
|
290
|
+
try {
|
|
291
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
292
|
+
config = JSON.parse(content);
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
// Config doesn't exist yet
|
|
296
|
+
}
|
|
297
|
+
const apiKey = config.apiKeys?.[provider];
|
|
298
|
+
if (!apiKey) {
|
|
299
|
+
return c.json({ success: false, message: `No API key configured for ${provider}` }, 400);
|
|
300
|
+
}
|
|
301
|
+
// Find workspace root
|
|
302
|
+
const workspaceRoot = await findWorkspaceRoot();
|
|
303
|
+
if (!workspaceRoot) {
|
|
304
|
+
return c.json({ success: false, message: 'Could not find workspace root' }, 500);
|
|
305
|
+
}
|
|
306
|
+
const envPath = path.join(workspaceRoot, '.env');
|
|
307
|
+
// Read existing .env file if it exists
|
|
308
|
+
let envContent = '';
|
|
309
|
+
try {
|
|
310
|
+
envContent = await fs.readFile(envPath, 'utf-8');
|
|
311
|
+
}
|
|
312
|
+
catch {
|
|
313
|
+
// File doesn't exist, will create new
|
|
314
|
+
}
|
|
315
|
+
// Parse existing env vars
|
|
316
|
+
const envLines = envContent.split('\n');
|
|
317
|
+
let found = false;
|
|
318
|
+
const updatedLines = envLines.map(line => {
|
|
319
|
+
const trimmed = line.trim();
|
|
320
|
+
if (trimmed.startsWith(envVarName + '=') || trimmed.startsWith(envVarName + ' =')) {
|
|
321
|
+
found = true;
|
|
322
|
+
return `${envVarName}=${apiKey}`;
|
|
323
|
+
}
|
|
324
|
+
return line;
|
|
325
|
+
});
|
|
326
|
+
// If not found, append to the end
|
|
327
|
+
if (!found) {
|
|
328
|
+
// Add a newline if the file doesn't end with one
|
|
329
|
+
if (updatedLines.length > 0 && updatedLines[updatedLines.length - 1] !== '') {
|
|
330
|
+
updatedLines.push('');
|
|
331
|
+
}
|
|
332
|
+
updatedLines.push(`${envVarName}=${apiKey}`);
|
|
333
|
+
}
|
|
334
|
+
// Write back to .env file
|
|
335
|
+
await fs.writeFile(envPath, updatedLines.join('\n'), 'utf-8');
|
|
336
|
+
return c.json({
|
|
337
|
+
success: true,
|
|
338
|
+
message: `${envVarName} saved to .env file`,
|
|
339
|
+
envPath
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
console.error('Error exporting to env:', error);
|
|
344
|
+
return c.json({ success: false, message: error instanceof Error ? error.message : 'Failed to export to env' }, 500);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { Context } from 'hono';
|
|
2
|
-
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
2
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<string, import("hono/utils/http-status").ContentfulStatusCode, "text">) | (Response & import("hono").TypedResponse<{
|
|
3
3
|
success: false;
|
|
4
4
|
error: {
|
|
5
5
|
code: string;
|
|
6
6
|
message: string;
|
|
7
7
|
};
|
|
8
|
-
}, 404, "json">) | (Response & import("hono").TypedResponse<
|
|
8
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
9
9
|
success: false;
|
|
10
10
|
error: {
|
|
11
11
|
code: string;
|
|
@@ -52,10 +52,11 @@ export async function GET(c) {
|
|
|
52
52
|
}
|
|
53
53
|
const url = new URL(c.req.url);
|
|
54
54
|
const download = url.searchParams.get('download');
|
|
55
|
+
// Return the actual file (either for download or inline display)
|
|
56
|
+
const filePath = path.join(FILES_DIR, resource.path);
|
|
57
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
55
58
|
if (download === 'true') {
|
|
56
|
-
//
|
|
57
|
-
const filePath = path.join(FILES_DIR, resource.path);
|
|
58
|
-
const fileBuffer = await fs.readFile(filePath);
|
|
59
|
+
// Force download with Content-Disposition: attachment
|
|
59
60
|
return new Response(fileBuffer, {
|
|
60
61
|
headers: {
|
|
61
62
|
'Content-Type': resource.type,
|
|
@@ -64,10 +65,13 @@ export async function GET(c) {
|
|
|
64
65
|
}
|
|
65
66
|
});
|
|
66
67
|
}
|
|
67
|
-
//
|
|
68
|
-
return
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
// Serve file inline (for preview in iframe, img, video, etc.)
|
|
69
|
+
return new Response(fileBuffer, {
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': resource.type,
|
|
72
|
+
'Content-Length': resource.size.toString(),
|
|
73
|
+
'Cache-Control': 'public, max-age=31536000'
|
|
74
|
+
}
|
|
71
75
|
});
|
|
72
76
|
}
|
|
73
77
|
catch (error) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
success: false;
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
9
|
+
success: true;
|
|
10
|
+
data: {
|
|
11
|
+
imageUrl: string;
|
|
12
|
+
};
|
|
13
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
14
|
+
success: false;
|
|
15
|
+
error: {
|
|
16
|
+
code: string;
|
|
17
|
+
message: any;
|
|
18
|
+
};
|
|
19
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import Replicate from 'replicate';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
const REPLICATE_API_TOKEN = 'r8_6IsX89og0JuK6Ay8RXADjp8RjnEOpfK3BpNXu';
|
|
5
|
+
const replicate = new Replicate({
|
|
6
|
+
auth: REPLICATE_API_TOKEN,
|
|
7
|
+
});
|
|
8
|
+
function getResourcesPath() {
|
|
9
|
+
let basePath;
|
|
10
|
+
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
11
|
+
basePath = process.env.GAIT_DEV_ROOT;
|
|
12
|
+
}
|
|
13
|
+
else if (process.env.GAIT_DATA_PATH) {
|
|
14
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
18
|
+
}
|
|
19
|
+
return path.join(basePath, 'resources');
|
|
20
|
+
}
|
|
21
|
+
const RESOURCES_DIR = getResourcesPath();
|
|
22
|
+
const FILES_DIR = path.join(RESOURCES_DIR, 'files');
|
|
23
|
+
const METADATA_DIR = path.join(RESOURCES_DIR, 'metadata');
|
|
24
|
+
const THUMBNAILS_DIR = path.join(RESOURCES_DIR, 'thumbnails');
|
|
25
|
+
async function getResourceMetadata(id) {
|
|
26
|
+
try {
|
|
27
|
+
const metadataPath = path.join(METADATA_DIR, `${id}.json`);
|
|
28
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
29
|
+
return JSON.parse(content);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
if (error.code === 'ENOENT')
|
|
33
|
+
return null;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
function extractResourceId(imageRef) {
|
|
38
|
+
if (typeof imageRef !== 'string')
|
|
39
|
+
return null;
|
|
40
|
+
// Allow direct resource IDs or URLs containing the ID
|
|
41
|
+
const directMatch = imageRef.match(/^(res-[A-Za-z0-9-]+)/);
|
|
42
|
+
if (directMatch && directMatch[1]) {
|
|
43
|
+
return directMatch[1];
|
|
44
|
+
}
|
|
45
|
+
const urlMatch = imageRef.match(/\/resources\/([^/?]+)/);
|
|
46
|
+
if (urlMatch && urlMatch[1]) {
|
|
47
|
+
return urlMatch[1];
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
// Map dimensions to aspect ratios
|
|
52
|
+
function getAspectRatio(dimensions) {
|
|
53
|
+
if (!dimensions)
|
|
54
|
+
return '1:1';
|
|
55
|
+
// Legacy support for previous values
|
|
56
|
+
const legacyMapping = {
|
|
57
|
+
'1x1': '1:1',
|
|
58
|
+
'3x2': '3:2',
|
|
59
|
+
'2x3': '2:3',
|
|
60
|
+
'6x9': '2:3',
|
|
61
|
+
};
|
|
62
|
+
if (legacyMapping[dimensions]) {
|
|
63
|
+
return legacyMapping[dimensions];
|
|
64
|
+
}
|
|
65
|
+
if (dimensions === 'match_input_image') {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
return dimensions;
|
|
69
|
+
}
|
|
70
|
+
export async function POST(c) {
|
|
71
|
+
try {
|
|
72
|
+
const body = await c.req.json();
|
|
73
|
+
const { prompt, inspiration, dimensions, resolution, model, image_input } = body;
|
|
74
|
+
if (!prompt) {
|
|
75
|
+
return c.json({
|
|
76
|
+
success: false,
|
|
77
|
+
error: {
|
|
78
|
+
code: 'MISSING_PROMPT',
|
|
79
|
+
message: 'Prompt is required'
|
|
80
|
+
}
|
|
81
|
+
}, 400);
|
|
82
|
+
}
|
|
83
|
+
// Build the full prompt with inspiration if provided
|
|
84
|
+
let fullPrompt = prompt;
|
|
85
|
+
if (inspiration) {
|
|
86
|
+
fullPrompt = `${inspiration}: ${prompt}`;
|
|
87
|
+
}
|
|
88
|
+
// Get aspect ratio from dimensions
|
|
89
|
+
const mappedAspectRatio = getAspectRatio(dimensions || '4:3');
|
|
90
|
+
// Process image_input: upload files to Replicate and get URLs
|
|
91
|
+
// image_input can contain resource IDs or URLs (or be empty if none selected)
|
|
92
|
+
const imageInputArray = [];
|
|
93
|
+
const imageInputs = Array.isArray(image_input)
|
|
94
|
+
? image_input
|
|
95
|
+
: typeof image_input === 'string' && image_input
|
|
96
|
+
? [image_input]
|
|
97
|
+
: [];
|
|
98
|
+
for (const imageRef of imageInputs) {
|
|
99
|
+
try {
|
|
100
|
+
const resourceId = extractResourceId(imageRef);
|
|
101
|
+
if (!resourceId) {
|
|
102
|
+
console.warn(`Unable to resolve resource id from image reference: ${imageRef}`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const resource = await getResourceMetadata(resourceId);
|
|
106
|
+
if (!resource) {
|
|
107
|
+
console.warn(`Resource metadata not found for id ${resourceId}`);
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
const candidatePaths = [];
|
|
111
|
+
if (resource.path) {
|
|
112
|
+
candidatePaths.push(path.join(FILES_DIR, resource.path));
|
|
113
|
+
}
|
|
114
|
+
if (resource.thumbnailPath) {
|
|
115
|
+
candidatePaths.push(path.join(THUMBNAILS_DIR, resource.thumbnailPath));
|
|
116
|
+
}
|
|
117
|
+
let filePath = null;
|
|
118
|
+
for (const candidate of candidatePaths) {
|
|
119
|
+
try {
|
|
120
|
+
await fs.access(candidate);
|
|
121
|
+
filePath = candidate;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
// Try next candidate
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!filePath) {
|
|
129
|
+
console.warn(`No accessible file found for resource ${resourceId}`);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
133
|
+
const uploadedFile = await replicate.files.create(fileBuffer, {
|
|
134
|
+
resourceId,
|
|
135
|
+
originalName: resource.name,
|
|
136
|
+
});
|
|
137
|
+
const fileUrl = uploadedFile?.urls?.get;
|
|
138
|
+
if (fileUrl) {
|
|
139
|
+
imageInputArray.push(fileUrl);
|
|
140
|
+
}
|
|
141
|
+
else {
|
|
142
|
+
console.warn(`Replicate file upload did not return a URL for resource ${resourceId}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
console.error(`Error processing image input ${imageRef}:`, error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const input = {
|
|
150
|
+
prompt: fullPrompt,
|
|
151
|
+
resolution: resolution || '1K',
|
|
152
|
+
output_format: 'png',
|
|
153
|
+
safety_filter_level: 'block_only_high'
|
|
154
|
+
};
|
|
155
|
+
if (mappedAspectRatio) {
|
|
156
|
+
input.aspect_ratio = mappedAspectRatio;
|
|
157
|
+
}
|
|
158
|
+
if (imageInputArray.length > 0) {
|
|
159
|
+
input.image_input = imageInputArray;
|
|
160
|
+
}
|
|
161
|
+
// Run the model (defaulting to nano-banana-pro)
|
|
162
|
+
const modelId = model === 'Nano Banana Pro' ? 'google/nano-banana-pro' : 'google/nano-banana-pro';
|
|
163
|
+
const output = await replicate.run(modelId, { input });
|
|
164
|
+
// Extract URL from output
|
|
165
|
+
// Replicate output can be: string URL, array of URLs, or FileOutput object with url() method
|
|
166
|
+
let imageUrl;
|
|
167
|
+
if (typeof output === 'string') {
|
|
168
|
+
imageUrl = output;
|
|
169
|
+
}
|
|
170
|
+
else if (Array.isArray(output) && output.length > 0) {
|
|
171
|
+
imageUrl = output[0];
|
|
172
|
+
}
|
|
173
|
+
else if (output && typeof output === 'object') {
|
|
174
|
+
// Check for url() method (FileOutput object)
|
|
175
|
+
if (typeof output.url === 'function') {
|
|
176
|
+
imageUrl = output.url();
|
|
177
|
+
}
|
|
178
|
+
else if ('url' in output) {
|
|
179
|
+
imageUrl = output.url;
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
throw new Error('Unexpected output format from Replicate');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
throw new Error('Unexpected output format from Replicate');
|
|
187
|
+
}
|
|
188
|
+
return c.json({
|
|
189
|
+
success: true,
|
|
190
|
+
data: {
|
|
191
|
+
imageUrl
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
console.error('Error generating image:', error);
|
|
197
|
+
return c.json({
|
|
198
|
+
success: false,
|
|
199
|
+
error: {
|
|
200
|
+
code: 'GENERATION_ERROR',
|
|
201
|
+
message: error.message || 'Failed to generate image'
|
|
202
|
+
}
|
|
203
|
+
}, 500);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
/**
|
|
3
|
+
* GET /api/v1/version
|
|
4
|
+
* Returns the current version of Coconut from the root package.json
|
|
5
|
+
* Requires authentication
|
|
6
|
+
*/
|
|
7
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
|
+
success: true;
|
|
9
|
+
data: {
|
|
10
|
+
version: any;
|
|
11
|
+
name: any;
|
|
12
|
+
description: any;
|
|
13
|
+
};
|
|
14
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
15
|
+
success: false;
|
|
16
|
+
error: {
|
|
17
|
+
code: string;
|
|
18
|
+
message: any;
|
|
19
|
+
};
|
|
20
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
21
|
+
success: false;
|
|
22
|
+
error: {
|
|
23
|
+
code: string;
|
|
24
|
+
message: string;
|
|
25
|
+
};
|
|
26
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
27
|
+
success: false;
|
|
28
|
+
error: {
|
|
29
|
+
code: string;
|
|
30
|
+
message: any;
|
|
31
|
+
};
|
|
32
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { requireAuth } from '../../../../middleware/auth.js';
|
|
5
|
+
/**
|
|
6
|
+
* GET /api/v1/version
|
|
7
|
+
* Returns the current version of Coconut from the root package.json
|
|
8
|
+
* Requires authentication
|
|
9
|
+
*/
|
|
10
|
+
export async function GET(c) {
|
|
11
|
+
try {
|
|
12
|
+
// Require authentication
|
|
13
|
+
requireAuth(c);
|
|
14
|
+
// Find the root package.json
|
|
15
|
+
// Get the directory of this file
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
18
|
+
// Navigate up to the root: route.ts -> version -> v1 -> api -> routes -> src -> api -> packages -> root
|
|
19
|
+
const rootDir = path.resolve(__dirname, '..', '..', '..', '..', '..', '..');
|
|
20
|
+
const packageJsonPath = path.join(rootDir, 'package.json');
|
|
21
|
+
// Read package.json
|
|
22
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
23
|
+
const packageJson = JSON.parse(content);
|
|
24
|
+
return c.json({
|
|
25
|
+
success: true,
|
|
26
|
+
data: {
|
|
27
|
+
version: packageJson.version,
|
|
28
|
+
name: packageJson.name,
|
|
29
|
+
description: packageJson.description
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
console.error('Error reading version:', error);
|
|
35
|
+
if (error.message === 'Authentication required' || error.message === 'Admin access required') {
|
|
36
|
+
return c.json({
|
|
37
|
+
success: false,
|
|
38
|
+
error: {
|
|
39
|
+
code: 'UNAUTHORIZED',
|
|
40
|
+
message: error.message
|
|
41
|
+
}
|
|
42
|
+
}, 401);
|
|
43
|
+
}
|
|
44
|
+
if (error.code === 'ENOENT') {
|
|
45
|
+
return c.json({
|
|
46
|
+
success: false,
|
|
47
|
+
error: {
|
|
48
|
+
code: 'VERSION_NOT_FOUND',
|
|
49
|
+
message: 'Could not find package.json'
|
|
50
|
+
}
|
|
51
|
+
}, 404);
|
|
52
|
+
}
|
|
53
|
+
return c.json({
|
|
54
|
+
success: false,
|
|
55
|
+
error: {
|
|
56
|
+
code: 'VERSION_ERROR',
|
|
57
|
+
message: error.message
|
|
58
|
+
}
|
|
59
|
+
}, 500);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -163,6 +163,7 @@ import git from './routes/api/v1/git/index.js';
|
|
|
163
163
|
import mcp from './routes/api/v1/mcp/index.js';
|
|
164
164
|
import jobs from './routes/api/v1/jobs/index.js';
|
|
165
165
|
import events from './routes/api/v1/events/index.js';
|
|
166
|
+
import version from './routes/api/v1/version/index.js';
|
|
166
167
|
// Register API routes FIRST
|
|
167
168
|
console.log('🔗 Registering API routes...');
|
|
168
169
|
app.route('/api/v1/auth', auth);
|
|
@@ -189,6 +190,7 @@ app.route('/api/v1/git', git);
|
|
|
189
190
|
app.route('/api/v1/mcp', mcp);
|
|
190
191
|
app.route('/api/v1/jobs', jobs);
|
|
191
192
|
app.route('/api/v1/events', events);
|
|
193
|
+
app.route('/api/v1/version', version);
|
|
192
194
|
console.log('✅ API routes registered');
|
|
193
195
|
app.get(PUBLIC_AGENT_CARD_PATH, authMiddleware, async (c) => {
|
|
194
196
|
try {
|
package/dist/server.js
CHANGED
|
@@ -163,6 +163,7 @@ import mcp from './routes/api/v1/mcp/index.js';
|
|
|
163
163
|
import symlinks from './routes/api/v1/symlinks/index.js';
|
|
164
164
|
import jobs from './routes/api/v1/jobs/index.js';
|
|
165
165
|
import events from './routes/api/v1/events/index.js';
|
|
166
|
+
import version from './routes/api/v1/version/index.js';
|
|
166
167
|
// Register API routes
|
|
167
168
|
app.route('/api/v1/auth', auth);
|
|
168
169
|
app.route('/api/v1/auth-settings', authSettings);
|
|
@@ -189,6 +190,7 @@ app.route('/api/v1/mcp', mcp);
|
|
|
189
190
|
app.route('/api/v1/symlinks', symlinks);
|
|
190
191
|
app.route('/api/v1/jobs', jobs);
|
|
191
192
|
app.route('/api/v1/events', events);
|
|
193
|
+
app.route('/api/v1/version', version);
|
|
192
194
|
app.get(PUBLIC_AGENT_CARD_PATH, authMiddleware, async (c) => {
|
|
193
195
|
try {
|
|
194
196
|
const document = await readAgentCard();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovelybunch/api",
|
|
3
|
-
"version": "1.0.69-alpha.
|
|
3
|
+
"version": "1.0.69-alpha.15",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/server-with-static.js",
|
|
6
6
|
"exports": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@hono/node-server": "^1.13.7",
|
|
38
38
|
"@hono/node-ws": "^1.0.6",
|
|
39
|
-
"@lovelybunch/core": "^1.0.69-alpha.
|
|
40
|
-
"@lovelybunch/mcp": "^1.0.69-alpha.
|
|
41
|
-
"@lovelybunch/types": "^1.0.69-alpha.
|
|
39
|
+
"@lovelybunch/core": "^1.0.69-alpha.15",
|
|
40
|
+
"@lovelybunch/mcp": "^1.0.69-alpha.15",
|
|
41
|
+
"@lovelybunch/types": "^1.0.69-alpha.15",
|
|
42
42
|
"arctic": "^1.9.2",
|
|
43
43
|
"bcrypt": "^5.1.1",
|
|
44
44
|
"cookie": "^0.6.0",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"hono": "^4.9.5",
|
|
49
49
|
"jsonwebtoken": "^9.0.2",
|
|
50
50
|
"node-pty": "^1.0.0",
|
|
51
|
+
"replicate": "^0.34.1",
|
|
51
52
|
"ws": "^8.18.0"
|
|
52
53
|
},
|
|
53
54
|
"devDependencies": {
|