@lovelybunch/api 1.0.69-alpha.9 → 1.0.70
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/auth/auth-manager.d.ts +10 -2
- package/dist/lib/auth/auth-manager.js +16 -5
- package/dist/lib/env-injection.d.ts +6 -0
- package/dist/lib/env-injection.js +64 -0
- package/dist/lib/git.d.ts +1 -0
- package/dist/lib/git.js +39 -1
- package/dist/lib/jobs/job-runner.js +22 -3
- package/dist/lib/jobs/job-scheduler.js +12 -1
- package/dist/lib/jobs/job-store.d.ts +1 -0
- package/dist/lib/jobs/job-store.js +150 -28
- package/dist/lib/storage/file-storage.js +16 -7
- package/dist/lib/terminal/terminal-manager.js +3 -2
- package/dist/routes/api/v1/config/route.js +20 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/route.js +18 -11
- package/dist/routes/api/v1/context/knowledge/route.js +5 -2
- package/dist/routes/api/v1/events/purge/route.d.ts +0 -2
- package/dist/routes/api/v1/events/purge/route.js +2 -14
- package/dist/routes/api/v1/events/route.d.ts +0 -2
- package/dist/routes/api/v1/events/route.js +2 -14
- package/dist/routes/api/v1/events/status/route.d.ts +0 -2
- package/dist/routes/api/v1/events/status/route.js +2 -14
- package/dist/routes/api/v1/events/stream/route.js +2 -14
- package/dist/routes/api/v1/git/index.js +66 -6
- package/dist/routes/api/v1/jobs/[id]/run/route.d.ts +2 -2
- package/dist/routes/api/v1/jobs/status/route.d.ts +1 -1
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +8 -8
- 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 +257 -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 +24 -0
- package/dist/routes/api/v1/version/route.js +51 -0
- package/dist/server-with-static.js +40 -23
- package/dist/server.js +40 -23
- package/package.json +5 -4
- package/static/assets/index-BmLW21zG.js +969 -0
- package/static/assets/index-CfRmV6nM.css +33 -0
- package/static/index.html +2 -2
- package/static/assets/index-BFXazLjO.js +0 -911
- package/static/assets/index-CHBcfq10.css +0 -33
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import Replicate from 'replicate';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
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
|
+
}
|
|
47
|
+
function getResourcesPath() {
|
|
48
|
+
let basePath;
|
|
49
|
+
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
50
|
+
basePath = process.env.GAIT_DEV_ROOT;
|
|
51
|
+
}
|
|
52
|
+
else if (process.env.GAIT_DATA_PATH) {
|
|
53
|
+
basePath = path.resolve(process.env.GAIT_DATA_PATH, '.nut');
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
basePath = path.resolve(process.cwd(), '.nut');
|
|
57
|
+
}
|
|
58
|
+
return path.join(basePath, 'resources');
|
|
59
|
+
}
|
|
60
|
+
const RESOURCES_DIR = getResourcesPath();
|
|
61
|
+
const FILES_DIR = path.join(RESOURCES_DIR, 'files');
|
|
62
|
+
const METADATA_DIR = path.join(RESOURCES_DIR, 'metadata');
|
|
63
|
+
const THUMBNAILS_DIR = path.join(RESOURCES_DIR, 'thumbnails');
|
|
64
|
+
async function getResourceMetadata(id) {
|
|
65
|
+
try {
|
|
66
|
+
const metadataPath = path.join(METADATA_DIR, `${id}.json`);
|
|
67
|
+
const content = await fs.readFile(metadataPath, 'utf-8');
|
|
68
|
+
return JSON.parse(content);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error.code === 'ENOENT')
|
|
72
|
+
return null;
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function extractResourceId(imageRef) {
|
|
77
|
+
if (typeof imageRef !== 'string')
|
|
78
|
+
return null;
|
|
79
|
+
// Allow direct resource IDs or URLs containing the ID
|
|
80
|
+
const directMatch = imageRef.match(/^(res-[A-Za-z0-9-]+)/);
|
|
81
|
+
if (directMatch && directMatch[1]) {
|
|
82
|
+
return directMatch[1];
|
|
83
|
+
}
|
|
84
|
+
const urlMatch = imageRef.match(/\/resources\/([^/?]+)/);
|
|
85
|
+
if (urlMatch && urlMatch[1]) {
|
|
86
|
+
return urlMatch[1];
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
// Map dimensions to aspect ratios
|
|
91
|
+
function getAspectRatio(dimensions) {
|
|
92
|
+
if (!dimensions)
|
|
93
|
+
return '1:1';
|
|
94
|
+
// Legacy support for previous values
|
|
95
|
+
const legacyMapping = {
|
|
96
|
+
'1x1': '1:1',
|
|
97
|
+
'3x2': '3:2',
|
|
98
|
+
'2x3': '2:3',
|
|
99
|
+
'6x9': '2:3',
|
|
100
|
+
};
|
|
101
|
+
if (legacyMapping[dimensions]) {
|
|
102
|
+
return legacyMapping[dimensions];
|
|
103
|
+
}
|
|
104
|
+
if (dimensions === 'match_input_image') {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return dimensions;
|
|
108
|
+
}
|
|
109
|
+
export async function POST(c) {
|
|
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
|
+
}
|
|
122
|
+
const body = await c.req.json();
|
|
123
|
+
const { prompt, inspiration, dimensions, resolution, model, image_input } = body;
|
|
124
|
+
if (!prompt) {
|
|
125
|
+
return c.json({
|
|
126
|
+
success: false,
|
|
127
|
+
error: {
|
|
128
|
+
code: 'MISSING_PROMPT',
|
|
129
|
+
message: 'Prompt is required'
|
|
130
|
+
}
|
|
131
|
+
}, 400);
|
|
132
|
+
}
|
|
133
|
+
// Build the full prompt with inspiration if provided
|
|
134
|
+
let fullPrompt = prompt;
|
|
135
|
+
if (inspiration) {
|
|
136
|
+
fullPrompt = `${inspiration}: ${prompt}`;
|
|
137
|
+
}
|
|
138
|
+
// Get aspect ratio from dimensions
|
|
139
|
+
const mappedAspectRatio = getAspectRatio(dimensions || '4:3');
|
|
140
|
+
// Process image_input: upload files to Replicate and get URLs
|
|
141
|
+
// image_input can contain resource IDs or URLs (or be empty if none selected)
|
|
142
|
+
const imageInputArray = [];
|
|
143
|
+
const imageInputs = Array.isArray(image_input)
|
|
144
|
+
? image_input
|
|
145
|
+
: typeof image_input === 'string' && image_input
|
|
146
|
+
? [image_input]
|
|
147
|
+
: [];
|
|
148
|
+
for (const imageRef of imageInputs) {
|
|
149
|
+
try {
|
|
150
|
+
const resourceId = extractResourceId(imageRef);
|
|
151
|
+
if (!resourceId) {
|
|
152
|
+
console.warn(`Unable to resolve resource id from image reference: ${imageRef}`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const resource = await getResourceMetadata(resourceId);
|
|
156
|
+
if (!resource) {
|
|
157
|
+
console.warn(`Resource metadata not found for id ${resourceId}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const candidatePaths = [];
|
|
161
|
+
if (resource.path) {
|
|
162
|
+
candidatePaths.push(path.join(FILES_DIR, resource.path));
|
|
163
|
+
}
|
|
164
|
+
if (resource.thumbnailPath) {
|
|
165
|
+
candidatePaths.push(path.join(THUMBNAILS_DIR, resource.thumbnailPath));
|
|
166
|
+
}
|
|
167
|
+
let filePath = null;
|
|
168
|
+
for (const candidate of candidatePaths) {
|
|
169
|
+
try {
|
|
170
|
+
await fs.access(candidate);
|
|
171
|
+
filePath = candidate;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
// Try next candidate
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (!filePath) {
|
|
179
|
+
console.warn(`No accessible file found for resource ${resourceId}`);
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
const fileBuffer = await fs.readFile(filePath);
|
|
183
|
+
const replicateClient = getReplicateClient();
|
|
184
|
+
const uploadedFile = await replicateClient.files.create(fileBuffer, {
|
|
185
|
+
resourceId,
|
|
186
|
+
originalName: resource.name,
|
|
187
|
+
});
|
|
188
|
+
const fileUrl = uploadedFile?.urls?.get;
|
|
189
|
+
if (fileUrl) {
|
|
190
|
+
imageInputArray.push(fileUrl);
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
console.warn(`Replicate file upload did not return a URL for resource ${resourceId}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error(`Error processing image input ${imageRef}:`, error);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const input = {
|
|
201
|
+
prompt: fullPrompt,
|
|
202
|
+
resolution: resolution || '1K',
|
|
203
|
+
output_format: 'png',
|
|
204
|
+
safety_filter_level: 'block_only_high'
|
|
205
|
+
};
|
|
206
|
+
if (mappedAspectRatio) {
|
|
207
|
+
input.aspect_ratio = mappedAspectRatio;
|
|
208
|
+
}
|
|
209
|
+
if (imageInputArray.length > 0) {
|
|
210
|
+
input.image_input = imageInputArray;
|
|
211
|
+
}
|
|
212
|
+
// Run the model (defaulting to nano-banana-pro)
|
|
213
|
+
const modelId = model === 'Nano Banana Pro' ? 'google/nano-banana-pro' : 'google/nano-banana-pro';
|
|
214
|
+
const replicateClient = getReplicateClient();
|
|
215
|
+
const output = await replicateClient.run(modelId, { input });
|
|
216
|
+
// Extract URL from output
|
|
217
|
+
// Replicate output can be: string URL, array of URLs, or FileOutput object with url() method
|
|
218
|
+
let imageUrl;
|
|
219
|
+
if (typeof output === 'string') {
|
|
220
|
+
imageUrl = output;
|
|
221
|
+
}
|
|
222
|
+
else if (Array.isArray(output) && output.length > 0) {
|
|
223
|
+
imageUrl = output[0];
|
|
224
|
+
}
|
|
225
|
+
else if (output && typeof output === 'object') {
|
|
226
|
+
// Check for url() method (FileOutput object)
|
|
227
|
+
if (typeof output.url === 'function') {
|
|
228
|
+
imageUrl = output.url();
|
|
229
|
+
}
|
|
230
|
+
else if ('url' in output) {
|
|
231
|
+
imageUrl = output.url;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
throw new Error('Unexpected output format from Replicate');
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
throw new Error('Unexpected output format from Replicate');
|
|
239
|
+
}
|
|
240
|
+
return c.json({
|
|
241
|
+
success: true,
|
|
242
|
+
data: {
|
|
243
|
+
imageUrl
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (error) {
|
|
248
|
+
console.error('Error generating image:', error);
|
|
249
|
+
return c.json({
|
|
250
|
+
success: false,
|
|
251
|
+
error: {
|
|
252
|
+
code: 'GENERATION_ERROR',
|
|
253
|
+
message: error.message || 'Failed to generate image'
|
|
254
|
+
}
|
|
255
|
+
}, 500);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
/**
|
|
3
|
+
* GET /api/v1/version
|
|
4
|
+
* Returns the current version of Coconut
|
|
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
|
+
};
|
|
12
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
13
|
+
success: false;
|
|
14
|
+
error: {
|
|
15
|
+
code: string;
|
|
16
|
+
message: any;
|
|
17
|
+
};
|
|
18
|
+
}, 401, "json">) | (Response & import("hono").TypedResponse<{
|
|
19
|
+
success: false;
|
|
20
|
+
error: {
|
|
21
|
+
code: string;
|
|
22
|
+
message: any;
|
|
23
|
+
};
|
|
24
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
|
8
|
+
* Requires authentication
|
|
9
|
+
*/
|
|
10
|
+
export async function GET(c) {
|
|
11
|
+
try {
|
|
12
|
+
// Require authentication
|
|
13
|
+
requireAuth(c);
|
|
14
|
+
// Read the API package's own package.json
|
|
15
|
+
// This works both in dev (from source) and production (from dist)
|
|
16
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
17
|
+
const __dirname = path.dirname(__filename);
|
|
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');
|
|
22
|
+
// Read package.json
|
|
23
|
+
const content = await fs.readFile(packageJsonPath, 'utf-8');
|
|
24
|
+
const packageJson = JSON.parse(content);
|
|
25
|
+
return c.json({
|
|
26
|
+
success: true,
|
|
27
|
+
data: {
|
|
28
|
+
version: packageJson.version
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
console.error('Error reading version:', error);
|
|
34
|
+
if (error.message === 'Authentication required' || error.message === 'Admin access required') {
|
|
35
|
+
return c.json({
|
|
36
|
+
success: false,
|
|
37
|
+
error: {
|
|
38
|
+
code: 'UNAUTHORIZED',
|
|
39
|
+
message: error.message
|
|
40
|
+
}
|
|
41
|
+
}, 401);
|
|
42
|
+
}
|
|
43
|
+
return c.json({
|
|
44
|
+
success: false,
|
|
45
|
+
error: {
|
|
46
|
+
code: 'VERSION_ERROR',
|
|
47
|
+
message: error.message || 'Failed to read version'
|
|
48
|
+
}
|
|
49
|
+
}, 500);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -11,6 +11,7 @@ import { getGlobalTerminalManager } from './lib/terminal/global-manager.js';
|
|
|
11
11
|
import { getGlobalJobScheduler } from './lib/jobs/global-job-scheduler.js';
|
|
12
12
|
import { fileURLToPath } from 'url';
|
|
13
13
|
import { getLogger } from '@lovelybunch/core/logging';
|
|
14
|
+
import { getLogsDir } from '@lovelybunch/core';
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url);
|
|
15
16
|
const __dirname = path.dirname(__filename);
|
|
16
17
|
// Load environment variables from .env file in project root
|
|
@@ -30,11 +31,15 @@ function findNutDirectorySync() {
|
|
|
30
31
|
}
|
|
31
32
|
return null;
|
|
32
33
|
}
|
|
33
|
-
// Initialize logger with config from .nut/config.json
|
|
34
|
+
// Initialize logger with config from .nut/config.json or use OS app data directory
|
|
34
35
|
// This must happen BEFORE importing route handlers (they call getLogger at module level)
|
|
35
36
|
console.log('🔍 Initializing activity logging...');
|
|
36
37
|
try {
|
|
37
38
|
const nutDir = findNutDirectorySync();
|
|
39
|
+
let logsDir = getLogsDir(); // Default to OS app data directory
|
|
40
|
+
let coconutId = 'unknown.coconut';
|
|
41
|
+
let rotateBytes = 128 * 1024 * 1024;
|
|
42
|
+
let loggingEnabled = true; // Default to enabled
|
|
38
43
|
if (nutDir) {
|
|
39
44
|
const projectRoot = path.dirname(nutDir);
|
|
40
45
|
const configPath = path.join(nutDir, 'config.json');
|
|
@@ -42,32 +47,42 @@ try {
|
|
|
42
47
|
console.log(' Config path:', configPath);
|
|
43
48
|
const configData = fs.readFileSync(configPath, 'utf-8');
|
|
44
49
|
const config = JSON.parse(configData);
|
|
45
|
-
if
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
coconutId: config.coconut?.id || 'unknown.coconut',
|
|
49
|
-
logsDir: logsDir,
|
|
50
|
-
rotateBytes: config.logging?.rotateBytes || 128 * 1024 * 1024
|
|
51
|
-
});
|
|
52
|
-
console.log('📝 Activity logging ENABLED');
|
|
53
|
-
console.log(' Logs directory:', logsDir);
|
|
54
|
-
console.log(' Coconut ID:', config.coconut?.id || 'unknown.coconut');
|
|
55
|
-
// Test log immediately
|
|
56
|
-
logger.log({
|
|
57
|
-
kind: 'system.startup',
|
|
58
|
-
actor: 'system',
|
|
59
|
-
subject: 'server',
|
|
60
|
-
tags: ['system', 'startup'],
|
|
61
|
-
payload: { message: 'Server starting with logging enabled' }
|
|
62
|
-
});
|
|
63
|
-
console.log(' ✓ Test event logged');
|
|
50
|
+
// Check if logging is explicitly disabled in config
|
|
51
|
+
if (config.logging?.enabled === false) {
|
|
52
|
+
loggingEnabled = false;
|
|
64
53
|
}
|
|
65
|
-
|
|
66
|
-
|
|
54
|
+
// Allow config to override logs location (relative paths resolve from project root)
|
|
55
|
+
if (config.logging?.location) {
|
|
56
|
+
logsDir = path.resolve(projectRoot, config.logging.location);
|
|
67
57
|
}
|
|
58
|
+
if (config.coconut?.id) {
|
|
59
|
+
coconutId = config.coconut.id;
|
|
60
|
+
}
|
|
61
|
+
if (config.logging?.rotateBytes) {
|
|
62
|
+
rotateBytes = config.logging.rotateBytes;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (loggingEnabled) {
|
|
66
|
+
const logger = getLogger({
|
|
67
|
+
coconutId,
|
|
68
|
+
logsDir,
|
|
69
|
+
rotateBytes
|
|
70
|
+
});
|
|
71
|
+
console.log('📝 Activity logging ENABLED');
|
|
72
|
+
console.log(' Logs directory:', logsDir);
|
|
73
|
+
console.log(' Coconut ID:', coconutId);
|
|
74
|
+
// Test log immediately
|
|
75
|
+
logger.log({
|
|
76
|
+
kind: 'system.startup',
|
|
77
|
+
actor: 'system',
|
|
78
|
+
subject: 'server',
|
|
79
|
+
tags: ['system', 'startup'],
|
|
80
|
+
payload: { message: 'Server starting with logging enabled' }
|
|
81
|
+
});
|
|
82
|
+
console.log(' ✓ Test event logged');
|
|
68
83
|
}
|
|
69
84
|
else {
|
|
70
|
-
console.log('
|
|
85
|
+
console.log('📝 Activity logging disabled in config');
|
|
71
86
|
}
|
|
72
87
|
}
|
|
73
88
|
catch (error) {
|
|
@@ -163,6 +178,7 @@ import git from './routes/api/v1/git/index.js';
|
|
|
163
178
|
import mcp from './routes/api/v1/mcp/index.js';
|
|
164
179
|
import jobs from './routes/api/v1/jobs/index.js';
|
|
165
180
|
import events from './routes/api/v1/events/index.js';
|
|
181
|
+
import version from './routes/api/v1/version/index.js';
|
|
166
182
|
// Register API routes FIRST
|
|
167
183
|
console.log('🔗 Registering API routes...');
|
|
168
184
|
app.route('/api/v1/auth', auth);
|
|
@@ -189,6 +205,7 @@ app.route('/api/v1/git', git);
|
|
|
189
205
|
app.route('/api/v1/mcp', mcp);
|
|
190
206
|
app.route('/api/v1/jobs', jobs);
|
|
191
207
|
app.route('/api/v1/events', events);
|
|
208
|
+
app.route('/api/v1/version', version);
|
|
192
209
|
console.log('✅ API routes registered');
|
|
193
210
|
app.get(PUBLIC_AGENT_CARD_PATH, authMiddleware, async (c) => {
|
|
194
211
|
try {
|
package/dist/server.js
CHANGED
|
@@ -10,6 +10,7 @@ import path from 'path';
|
|
|
10
10
|
import { fileURLToPath } from 'url';
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import { getLogger } from '@lovelybunch/core/logging';
|
|
13
|
+
import { getLogsDir } from '@lovelybunch/core';
|
|
13
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
14
15
|
const __dirname = path.dirname(__filename);
|
|
15
16
|
// Load environment variables from .env file in project root
|
|
@@ -29,11 +30,15 @@ function findNutDirectorySync() {
|
|
|
29
30
|
}
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
32
|
-
// Initialize logger with config from .nut/config.json
|
|
33
|
+
// Initialize logger with config from .nut/config.json or use OS app data directory
|
|
33
34
|
// This must happen BEFORE importing route handlers (they call getLogger at module level)
|
|
34
35
|
console.log('🔍 Initializing activity logging...');
|
|
35
36
|
try {
|
|
36
37
|
const nutDir = findNutDirectorySync();
|
|
38
|
+
let logsDir = getLogsDir(); // Default to OS app data directory
|
|
39
|
+
let coconutId = 'unknown.coconut';
|
|
40
|
+
let rotateBytes = 128 * 1024 * 1024;
|
|
41
|
+
let loggingEnabled = true; // Default to enabled
|
|
37
42
|
if (nutDir) {
|
|
38
43
|
const projectRoot = path.dirname(nutDir);
|
|
39
44
|
const configPath = path.join(nutDir, 'config.json');
|
|
@@ -41,32 +46,42 @@ try {
|
|
|
41
46
|
console.log(' Config path:', configPath);
|
|
42
47
|
const configData = fs.readFileSync(configPath, 'utf-8');
|
|
43
48
|
const config = JSON.parse(configData);
|
|
44
|
-
if
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
coconutId: config.coconut?.id || 'unknown.coconut',
|
|
48
|
-
logsDir: logsDir,
|
|
49
|
-
rotateBytes: config.logging?.rotateBytes || 128 * 1024 * 1024
|
|
50
|
-
});
|
|
51
|
-
console.log('📝 Activity logging ENABLED');
|
|
52
|
-
console.log(' Logs directory:', logsDir);
|
|
53
|
-
console.log(' Coconut ID:', config.coconut?.id || 'unknown.coconut');
|
|
54
|
-
// Test log immediately
|
|
55
|
-
logger.log({
|
|
56
|
-
kind: 'system.startup',
|
|
57
|
-
actor: 'system',
|
|
58
|
-
subject: 'server',
|
|
59
|
-
tags: ['system', 'startup'],
|
|
60
|
-
payload: { message: 'Server starting with logging enabled' }
|
|
61
|
-
});
|
|
62
|
-
console.log(' ✓ Test event logged');
|
|
49
|
+
// Check if logging is explicitly disabled in config
|
|
50
|
+
if (config.logging?.enabled === false) {
|
|
51
|
+
loggingEnabled = false;
|
|
63
52
|
}
|
|
64
|
-
|
|
65
|
-
|
|
53
|
+
// Allow config to override logs location (relative paths resolve from project root)
|
|
54
|
+
if (config.logging?.location) {
|
|
55
|
+
logsDir = path.resolve(projectRoot, config.logging.location);
|
|
66
56
|
}
|
|
57
|
+
if (config.coconut?.id) {
|
|
58
|
+
coconutId = config.coconut.id;
|
|
59
|
+
}
|
|
60
|
+
if (config.logging?.rotateBytes) {
|
|
61
|
+
rotateBytes = config.logging.rotateBytes;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (loggingEnabled) {
|
|
65
|
+
const logger = getLogger({
|
|
66
|
+
coconutId,
|
|
67
|
+
logsDir,
|
|
68
|
+
rotateBytes
|
|
69
|
+
});
|
|
70
|
+
console.log('📝 Activity logging ENABLED');
|
|
71
|
+
console.log(' Logs directory:', logsDir);
|
|
72
|
+
console.log(' Coconut ID:', coconutId);
|
|
73
|
+
// Test log immediately
|
|
74
|
+
logger.log({
|
|
75
|
+
kind: 'system.startup',
|
|
76
|
+
actor: 'system',
|
|
77
|
+
subject: 'server',
|
|
78
|
+
tags: ['system', 'startup'],
|
|
79
|
+
payload: { message: 'Server starting with logging enabled' }
|
|
80
|
+
});
|
|
81
|
+
console.log(' ✓ Test event logged');
|
|
67
82
|
}
|
|
68
83
|
else {
|
|
69
|
-
console.log('
|
|
84
|
+
console.log('📝 Activity logging disabled in config');
|
|
70
85
|
}
|
|
71
86
|
}
|
|
72
87
|
catch (error) {
|
|
@@ -163,6 +178,7 @@ import mcp from './routes/api/v1/mcp/index.js';
|
|
|
163
178
|
import symlinks from './routes/api/v1/symlinks/index.js';
|
|
164
179
|
import jobs from './routes/api/v1/jobs/index.js';
|
|
165
180
|
import events from './routes/api/v1/events/index.js';
|
|
181
|
+
import version from './routes/api/v1/version/index.js';
|
|
166
182
|
// Register API routes
|
|
167
183
|
app.route('/api/v1/auth', auth);
|
|
168
184
|
app.route('/api/v1/auth-settings', authSettings);
|
|
@@ -189,6 +205,7 @@ app.route('/api/v1/mcp', mcp);
|
|
|
189
205
|
app.route('/api/v1/symlinks', symlinks);
|
|
190
206
|
app.route('/api/v1/jobs', jobs);
|
|
191
207
|
app.route('/api/v1/events', events);
|
|
208
|
+
app.route('/api/v1/version', version);
|
|
192
209
|
app.get(PUBLIC_AGENT_CARD_PATH, authMiddleware, async (c) => {
|
|
193
210
|
try {
|
|
194
211
|
const document = await readAgentCard();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lovelybunch/api",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.70",
|
|
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.
|
|
40
|
-
"@lovelybunch/mcp": "^1.0.
|
|
41
|
-
"@lovelybunch/types": "^1.0.
|
|
39
|
+
"@lovelybunch/core": "^1.0.70",
|
|
40
|
+
"@lovelybunch/mcp": "^1.0.70",
|
|
41
|
+
"@lovelybunch/types": "^1.0.70",
|
|
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": {
|