@lovelybunch/api 1.0.69-alpha.15 → 1.0.69-alpha.17
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/env-injection.d.ts +6 -0
- package/dist/lib/env-injection.js +64 -0
- package/dist/lib/jobs/job-runner.js +21 -3
- package/dist/lib/terminal/terminal-manager.js +3 -2
- package/dist/routes/api/v1/config/index.js +1 -2
- package/dist/routes/api/v1/config/route.d.ts +0 -11
- package/dist/routes/api/v1/config/route.js +20 -104
- package/dist/routes/api/v1/resources/generate/route.js +58 -6
- package/dist/routes/api/v1/version/route.d.ts +1 -9
- package/dist/routes/api/v1/version/route.js +9 -19
- package/package.json +4 -4
- package/static/assets/index-B9teXSU8.css +33 -0
- package/static/assets/{index-DGl_L0ie.js → index-BwPO_Kh8.js} +227 -224
- package/static/index.html +2 -2
- package/static/assets/index-uyccX96d.css +0 -33
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get environment variables to inject into spawned child processes
|
|
3
|
+
* These are the API keys that coding agents (Claude Code, Codex, Gemini CLI) will use
|
|
4
|
+
* Returns a full environment object with process.env merged with API keys
|
|
5
|
+
*/
|
|
6
|
+
export declare function getInjectedEnv(): Record<string, string>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { homedir } from 'os';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { existsSync, readFileSync } from 'fs';
|
|
4
|
+
/**
|
|
5
|
+
* Get the path to the global config file
|
|
6
|
+
*/
|
|
7
|
+
function getGlobalConfigPath() {
|
|
8
|
+
const platform = process.platform;
|
|
9
|
+
let configDir;
|
|
10
|
+
if (platform === 'win32') {
|
|
11
|
+
configDir = join(process.env.APPDATA || homedir(), 'coconuts');
|
|
12
|
+
}
|
|
13
|
+
else if (platform === 'darwin') {
|
|
14
|
+
configDir = join(homedir(), 'Library', 'Application Support', 'coconuts');
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
// Linux/Unix
|
|
18
|
+
configDir = join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'coconuts');
|
|
19
|
+
}
|
|
20
|
+
return join(configDir, 'config.json');
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Load global config from OS-specific location
|
|
24
|
+
*/
|
|
25
|
+
function loadGlobalConfig() {
|
|
26
|
+
const configPath = getGlobalConfigPath();
|
|
27
|
+
if (!existsSync(configPath)) {
|
|
28
|
+
return { apiKeys: {}, defaults: {} };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
const content = readFileSync(configPath, 'utf-8');
|
|
32
|
+
return JSON.parse(content);
|
|
33
|
+
}
|
|
34
|
+
catch (error) {
|
|
35
|
+
console.warn('Warning: Could not parse global config file, using defaults');
|
|
36
|
+
return { apiKeys: {}, defaults: {} };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get environment variables to inject into spawned child processes
|
|
41
|
+
* These are the API keys that coding agents (Claude Code, Codex, Gemini CLI) will use
|
|
42
|
+
* Returns a full environment object with process.env merged with API keys
|
|
43
|
+
*/
|
|
44
|
+
export function getInjectedEnv() {
|
|
45
|
+
const config = loadGlobalConfig();
|
|
46
|
+
const env = { ...process.env };
|
|
47
|
+
// Only inject keys that are actually configured
|
|
48
|
+
if (config.apiKeys?.anthropic) {
|
|
49
|
+
env.ANTHROPIC_API_KEY = config.apiKeys.anthropic;
|
|
50
|
+
}
|
|
51
|
+
if (config.apiKeys?.openai) {
|
|
52
|
+
env.OPENAI_API_KEY = config.apiKeys.openai;
|
|
53
|
+
}
|
|
54
|
+
if (config.apiKeys?.gemini) {
|
|
55
|
+
env.GEMINI_API_KEY = config.apiKeys.gemini;
|
|
56
|
+
}
|
|
57
|
+
if (config.apiKeys?.replicate) {
|
|
58
|
+
env.REPLICATE_API_TOKEN = config.apiKeys.replicate;
|
|
59
|
+
}
|
|
60
|
+
if (config.apiKeys?.factorydroid) {
|
|
61
|
+
env.FACTORY_API_KEY = config.apiKeys.factorydroid;
|
|
62
|
+
}
|
|
63
|
+
return env;
|
|
64
|
+
}
|
|
@@ -3,6 +3,7 @@ import { createWriteStream } from 'fs';
|
|
|
3
3
|
import { promises as fs } from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import { getProjectRoot } from '../project-paths.js';
|
|
6
|
+
import { getInjectedEnv } from '../env-injection.js';
|
|
6
7
|
function shellQuote(value) {
|
|
7
8
|
if (value === '')
|
|
8
9
|
return "''";
|
|
@@ -16,6 +17,8 @@ function resolveAgent(model) {
|
|
|
16
17
|
return 'gemini';
|
|
17
18
|
if (lower.includes('codex') || lower.includes('gpt') || lower.includes('openai'))
|
|
18
19
|
return 'codex';
|
|
20
|
+
if (lower.includes('droid') || lower.includes('factory'))
|
|
21
|
+
return 'droid';
|
|
19
22
|
return 'claude';
|
|
20
23
|
}
|
|
21
24
|
function buildCommand(agent, instruction, config) {
|
|
@@ -42,6 +45,14 @@ function buildCommand(agent, instruction, config) {
|
|
|
42
45
|
: baseCmd;
|
|
43
46
|
break;
|
|
44
47
|
}
|
|
48
|
+
case 'droid': {
|
|
49
|
+
// For Factory Droid, use the --mcp flag approach if supported
|
|
50
|
+
const mcpFlags = config.mcpServers && config.mcpServers.length > 0
|
|
51
|
+
? config.mcpServers.map(server => `--mcp ${shellQuote(server)}`).join(' ')
|
|
52
|
+
: '';
|
|
53
|
+
mainCommand = `droid ${quotedInstruction} --skip-permissions-unsafe ${mcpFlags}`.trim();
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
45
56
|
case 'claude':
|
|
46
57
|
default: {
|
|
47
58
|
// Claude uses .mcp.json for MCP server configuration (no --mcp flag)
|
|
@@ -55,12 +66,14 @@ function buildCommand(agent, instruction, config) {
|
|
|
55
66
|
const CLI_AGENT_LABEL = {
|
|
56
67
|
claude: 'Claude',
|
|
57
68
|
gemini: 'Gemini',
|
|
58
|
-
codex: '
|
|
69
|
+
codex: 'Codex',
|
|
70
|
+
droid: 'Factory Droid'
|
|
59
71
|
};
|
|
60
72
|
const CLI_AGENT_BINARY = {
|
|
61
73
|
claude: 'claude',
|
|
62
74
|
gemini: 'gemini',
|
|
63
|
-
codex: 'codex'
|
|
75
|
+
codex: 'codex',
|
|
76
|
+
droid: 'droid'
|
|
64
77
|
};
|
|
65
78
|
const DEFAULT_MAX_RUNTIME_MS = 30 * 60 * 1000; // 30 minutes
|
|
66
79
|
function getMaxRuntime() {
|
|
@@ -223,9 +236,14 @@ export class JobRunner {
|
|
|
223
236
|
});
|
|
224
237
|
return;
|
|
225
238
|
}
|
|
239
|
+
// Inject API keys from global config into child process environment
|
|
240
|
+
const injectedEnv = getInjectedEnv();
|
|
226
241
|
const child = spawn('bash', ['-lc', shellCommand], {
|
|
227
242
|
cwd: projectRoot,
|
|
228
|
-
env:
|
|
243
|
+
env: {
|
|
244
|
+
...process.env,
|
|
245
|
+
...injectedEnv
|
|
246
|
+
},
|
|
229
247
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
230
248
|
});
|
|
231
249
|
const maxRuntime = getMaxRuntime();
|
|
@@ -5,6 +5,7 @@ import fs from 'fs';
|
|
|
5
5
|
import { createInitScript } from './context-helper.js';
|
|
6
6
|
import { getShellPath, getShellArgs, prepareShellInit } from './shell-utils.js';
|
|
7
7
|
import { getLogger, AgentKinds } from '@lovelybunch/core/logging';
|
|
8
|
+
import { getInjectedEnv } from '../env-injection.js';
|
|
8
9
|
export class TerminalManager {
|
|
9
10
|
sessions = new Map();
|
|
10
11
|
cleanupInterval;
|
|
@@ -82,9 +83,9 @@ export class TerminalManager {
|
|
|
82
83
|
}
|
|
83
84
|
// Get shell arguments
|
|
84
85
|
const shellArgs = getShellArgs(shellPath, initScriptPath);
|
|
85
|
-
// Prepare environment variables
|
|
86
|
+
// Prepare environment variables with API keys injected
|
|
86
87
|
const env = {
|
|
87
|
-
...
|
|
88
|
+
...getInjectedEnv(),
|
|
88
89
|
COCONUT_PROPOSAL_ID: proposalId,
|
|
89
90
|
COCONUT_CONTEXT_PATH: path.join(projectRoot, '.nut', 'context'),
|
|
90
91
|
COCONUT_PROPOSAL_PATH: path.join(projectRoot, '.nut', 'proposals', `${proposalId}.md`),
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
-
import { GET, PUT, TEST
|
|
2
|
+
import { GET, PUT, TEST } 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);
|
|
8
7
|
export default config;
|
|
@@ -30,14 +30,3 @@ 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">)>;
|
|
@@ -233,114 +233,30 @@ export async function TEST(c) {
|
|
|
233
233
|
return c.json({ success: false, message: err instanceof Error ? err.message : 'Network error' }, 200);
|
|
234
234
|
}
|
|
235
235
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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}`;
|
|
236
|
+
if (provider === 'replicate') {
|
|
237
|
+
try {
|
|
238
|
+
// Test Replicate API by listing models (lightweight endpoint)
|
|
239
|
+
const resp = await fetch('https://api.replicate.com/v1/models', {
|
|
240
|
+
method: 'GET',
|
|
241
|
+
headers: {
|
|
242
|
+
'Authorization': `Bearer ${effectiveKey}`,
|
|
243
|
+
'Content-Type': 'application/json',
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
if (!resp.ok) {
|
|
247
|
+
const text = await resp.text();
|
|
248
|
+
return c.json({ success: false, message: `Replicate rejected token: ${text.slice(0, 200)}` }, 200);
|
|
249
|
+
}
|
|
250
|
+
return c.json({ success: true, message: 'Replicate token is valid' });
|
|
323
251
|
}
|
|
324
|
-
|
|
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('');
|
|
252
|
+
catch (err) {
|
|
253
|
+
return c.json({ success: false, message: err instanceof Error ? err.message : 'Network error' }, 200);
|
|
331
254
|
}
|
|
332
|
-
updatedLines.push(`${envVarName}=${apiKey}`);
|
|
333
255
|
}
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
return c.json({
|
|
337
|
-
success: true,
|
|
338
|
-
message: `${envVarName} saved to .env file`,
|
|
339
|
-
envPath
|
|
340
|
-
});
|
|
256
|
+
// Other providers not wired up yet
|
|
257
|
+
return c.json({ success: false, message: `Provider '${provider}' test not implemented yet` }, 501);
|
|
341
258
|
}
|
|
342
259
|
catch (error) {
|
|
343
|
-
|
|
344
|
-
return c.json({ success: false, message: error instanceof Error ? error.message : 'Failed to export to env' }, 500);
|
|
260
|
+
return c.json({ success: false, message: 'Invalid request body' }, 400);
|
|
345
261
|
}
|
|
346
262
|
}
|
|
@@ -1,10 +1,49 @@
|
|
|
1
1
|
import Replicate from 'replicate';
|
|
2
2
|
import { promises as fs } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { existsSync, readFileSync } from 'fs';
|
|
6
|
+
/**
|
|
7
|
+
* Get Replicate API token from global config or environment variable
|
|
8
|
+
*/
|
|
9
|
+
function getReplicateApiToken() {
|
|
10
|
+
// First try global config
|
|
11
|
+
try {
|
|
12
|
+
const platform = process.platform;
|
|
13
|
+
let configDir;
|
|
14
|
+
if (platform === 'win32') {
|
|
15
|
+
configDir = path.join(process.env.APPDATA || homedir(), 'coconuts');
|
|
16
|
+
}
|
|
17
|
+
else if (platform === 'darwin') {
|
|
18
|
+
configDir = path.join(homedir(), 'Library', 'Application Support', 'coconuts');
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
configDir = path.join(process.env.XDG_CONFIG_HOME || path.join(homedir(), '.config'), 'coconuts');
|
|
22
|
+
}
|
|
23
|
+
const configPath = path.join(configDir, 'config.json');
|
|
24
|
+
if (existsSync(configPath)) {
|
|
25
|
+
const config = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
26
|
+
if (config.apiKeys?.replicate) {
|
|
27
|
+
return config.apiKeys.replicate;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
console.warn('Failed to load Replicate token from config:', error);
|
|
33
|
+
}
|
|
34
|
+
// Fallback to environment variable
|
|
35
|
+
return process.env.REPLICATE_API_TOKEN || null;
|
|
36
|
+
}
|
|
37
|
+
// Initialize Replicate client lazily to ensure token is loaded at request time
|
|
38
|
+
function getReplicateClient() {
|
|
39
|
+
const token = getReplicateApiToken();
|
|
40
|
+
if (!token) {
|
|
41
|
+
throw new Error('Replicate API token not configured');
|
|
42
|
+
}
|
|
43
|
+
return new Replicate({
|
|
44
|
+
auth: token,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
8
47
|
function getResourcesPath() {
|
|
9
48
|
let basePath;
|
|
10
49
|
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
@@ -69,6 +108,17 @@ function getAspectRatio(dimensions) {
|
|
|
69
108
|
}
|
|
70
109
|
export async function POST(c) {
|
|
71
110
|
try {
|
|
111
|
+
// Check if Replicate API token is configured
|
|
112
|
+
const replicateToken = getReplicateApiToken();
|
|
113
|
+
if (!replicateToken) {
|
|
114
|
+
return c.json({
|
|
115
|
+
success: false,
|
|
116
|
+
error: {
|
|
117
|
+
code: 'MISSING_API_TOKEN',
|
|
118
|
+
message: 'Replicate API token not configured. Please add it in Settings → Integrations.'
|
|
119
|
+
}
|
|
120
|
+
}, 400);
|
|
121
|
+
}
|
|
72
122
|
const body = await c.req.json();
|
|
73
123
|
const { prompt, inspiration, dimensions, resolution, model, image_input } = body;
|
|
74
124
|
if (!prompt) {
|
|
@@ -130,7 +180,8 @@ export async function POST(c) {
|
|
|
130
180
|
continue;
|
|
131
181
|
}
|
|
132
182
|
const fileBuffer = await fs.readFile(filePath);
|
|
133
|
-
const
|
|
183
|
+
const replicateClient = getReplicateClient();
|
|
184
|
+
const uploadedFile = await replicateClient.files.create(fileBuffer, {
|
|
134
185
|
resourceId,
|
|
135
186
|
originalName: resource.name,
|
|
136
187
|
});
|
|
@@ -160,7 +211,8 @@ export async function POST(c) {
|
|
|
160
211
|
}
|
|
161
212
|
// Run the model (defaulting to nano-banana-pro)
|
|
162
213
|
const modelId = model === 'Nano Banana Pro' ? 'google/nano-banana-pro' : 'google/nano-banana-pro';
|
|
163
|
-
const
|
|
214
|
+
const replicateClient = getReplicateClient();
|
|
215
|
+
const output = await replicateClient.run(modelId, { input });
|
|
164
216
|
// Extract URL from output
|
|
165
217
|
// Replicate output can be: string URL, array of URLs, or FileOutput object with url() method
|
|
166
218
|
let imageUrl;
|
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import { Context } from 'hono';
|
|
2
2
|
/**
|
|
3
3
|
* GET /api/v1/version
|
|
4
|
-
* Returns the current version of Coconut
|
|
4
|
+
* Returns the current version of Coconut
|
|
5
5
|
* Requires authentication
|
|
6
6
|
*/
|
|
7
7
|
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
8
|
success: true;
|
|
9
9
|
data: {
|
|
10
10
|
version: any;
|
|
11
|
-
name: any;
|
|
12
|
-
description: any;
|
|
13
11
|
};
|
|
14
12
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
15
13
|
success: false;
|
|
@@ -18,12 +16,6 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
|
|
|
18
16
|
message: any;
|
|
19
17
|
};
|
|
20
18
|
}, 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
19
|
success: false;
|
|
28
20
|
error: {
|
|
29
21
|
code: string;
|
|
@@ -4,29 +4,28 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import { requireAuth } from '../../../../middleware/auth.js';
|
|
5
5
|
/**
|
|
6
6
|
* GET /api/v1/version
|
|
7
|
-
* Returns the current version of Coconut
|
|
7
|
+
* Returns the current version of Coconut
|
|
8
8
|
* Requires authentication
|
|
9
9
|
*/
|
|
10
10
|
export async function GET(c) {
|
|
11
11
|
try {
|
|
12
12
|
// Require authentication
|
|
13
13
|
requireAuth(c);
|
|
14
|
-
//
|
|
15
|
-
//
|
|
14
|
+
// Read the API package's own package.json
|
|
15
|
+
// This works both in dev (from source) and production (from dist)
|
|
16
16
|
const __filename = fileURLToPath(import.meta.url);
|
|
17
17
|
const __dirname = path.dirname(__filename);
|
|
18
|
-
// Navigate up
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
// Navigate up from dist/routes/api/v1/version to the package root
|
|
19
|
+
// In dev: src/routes/api/v1/version -> ../../../../..
|
|
20
|
+
// In prod: dist/routes/api/v1/version -> ../../../../..
|
|
21
|
+
let packageJsonPath = path.resolve(__dirname, '..', '..', '..', '..', '..', 'package.json');
|
|
21
22
|
// Read package.json
|
|
22
23
|
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
23
24
|
const packageJson = JSON.parse(content);
|
|
24
25
|
return c.json({
|
|
25
26
|
success: true,
|
|
26
27
|
data: {
|
|
27
|
-
version: packageJson.version
|
|
28
|
-
name: packageJson.name,
|
|
29
|
-
description: packageJson.description
|
|
28
|
+
version: packageJson.version
|
|
30
29
|
}
|
|
31
30
|
});
|
|
32
31
|
}
|
|
@@ -41,20 +40,11 @@ export async function GET(c) {
|
|
|
41
40
|
}
|
|
42
41
|
}, 401);
|
|
43
42
|
}
|
|
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
43
|
return c.json({
|
|
54
44
|
success: false,
|
|
55
45
|
error: {
|
|
56
46
|
code: 'VERSION_ERROR',
|
|
57
|
-
message: error.message
|
|
47
|
+
message: error.message || 'Failed to read version'
|
|
58
48
|
}
|
|
59
49
|
}, 500);
|
|
60
50
|
}
|
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.17",
|
|
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.17",
|
|
40
|
+
"@lovelybunch/mcp": "^1.0.69-alpha.17",
|
|
41
|
+
"@lovelybunch/types": "^1.0.69-alpha.17",
|
|
42
42
|
"arctic": "^1.9.2",
|
|
43
43
|
"bcrypt": "^5.1.1",
|
|
44
44
|
"cookie": "^0.6.0",
|