@lovelybunch/api 1.0.69-alpha.12 → 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.
@@ -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
+ }
@@ -1,6 +1,8 @@
1
1
  import { Hono } from 'hono';
2
2
  import { GET, POST } from './route.js';
3
+ import generate from './generate/index.js';
3
4
  const app = new Hono();
4
5
  app.get('/', GET);
5
6
  app.post('/', POST);
7
+ app.route('/generate', generate);
6
8
  export default app;
@@ -0,0 +1,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const version: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default version;
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ import { GET } from './route.js';
3
+ const version = new Hono();
4
+ version.get('/', GET);
5
+ export default version;
@@ -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.12",
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.12",
40
- "@lovelybunch/mcp": "^1.0.69-alpha.12",
41
- "@lovelybunch/types": "^1.0.69-alpha.12",
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": {