@lovelybunch/api 1.0.69-alpha.1 → 1.0.69-alpha.10
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/git.d.ts +29 -1
- package/dist/lib/git.js +145 -3
- package/dist/lib/jobs/job-runner.d.ts +4 -0
- package/dist/lib/jobs/job-runner.js +126 -15
- package/dist/lib/jobs/job-store.js +6 -0
- package/dist/lib/user-preferences.d.ts +3 -1
- package/dist/routes/api/v1/config/route.d.ts +1 -1
- package/dist/routes/api/v1/config/route.js +45 -1
- 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/git/index.js +115 -4
- package/dist/routes/api/v1/jobs/[id]/route.d.ts +6 -0
- package/dist/routes/api/v1/jobs/[id]/route.js +9 -0
- package/dist/routes/api/v1/jobs/[id]/runs/[runId]/log/route.d.ts +14 -0
- package/dist/routes/api/v1/jobs/[id]/runs/[runId]/log/route.js +64 -0
- package/dist/routes/api/v1/jobs/[id]/runs/[runId]/route.d.ts +28 -0
- package/dist/routes/api/v1/jobs/[id]/runs/[runId]/route.js +39 -0
- package/dist/routes/api/v1/jobs/index.js +4 -0
- package/dist/routes/api/v1/jobs/route.d.ts +6 -0
- package/dist/routes/api/v1/jobs/route.js +3 -0
- package/dist/routes/api/v1/user/settings/route.js +43 -3
- package/package.json +4 -4
- package/static/assets/index-CDMfOGVc.css +33 -0
- package/static/assets/index-DarwWmEe.js +911 -0
- package/static/index.html +2 -2
- package/static/assets/index-DIVD0EVP.css +0 -33
- package/static/assets/index-gdnIvn_s.js +0 -894
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
2
|
import crypto from 'crypto';
|
|
3
|
-
import { getRepoStatus, listBranches, createBranch, deleteBranch, switchBranch, mergeBranch, pushCurrent, pullCurrent, listWorktrees, addWorktree, removeWorktree, commitInWorktree, pushWorktree, pullWorktree, checkRemoteAuth, getCredentialConfig, storeCredentials, } from '../../../../lib/git.js';
|
|
4
|
-
import { saveGithubToken, clearGithubToken } from '../../../../lib/github-token.js';
|
|
3
|
+
import { getRepoStatus, listBranches, createBranch, deleteBranch, switchBranch, mergeBranch, pushCurrent, pullCurrent, listWorktrees, addWorktree, removeWorktree, commitInWorktree, pushWorktree, pullWorktree, checkRemoteAuth, getCredentialConfig, storeCredentials, getCommitDetails, getFileDiff, setRemoteUrl, } from '../../../../lib/git.js';
|
|
4
|
+
import { saveGithubToken, clearGithubToken, readGithubToken, isGithubTokenValid } from '../../../../lib/github-token.js';
|
|
5
5
|
import { createGithubAuthState, consumeGithubAuthState } from '../../../../lib/github-auth-state.js';
|
|
6
6
|
import { resolveCoconutId, resolveControlPlaneUrl } from '../../../../lib/coconut-context.js';
|
|
7
7
|
import { loadGitSettings, saveGitSettings, isGitAuthMode } from '../../../../lib/git-settings.js';
|
|
@@ -78,16 +78,50 @@ app.get('/credential-config', async (c) => {
|
|
|
78
78
|
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
79
79
|
}
|
|
80
80
|
});
|
|
81
|
+
// Set Remote URL
|
|
82
|
+
app.put('/remote', async (c) => {
|
|
83
|
+
try {
|
|
84
|
+
const body = await c.req.json();
|
|
85
|
+
const remoteUrl = String(body?.remoteUrl || '');
|
|
86
|
+
if (!remoteUrl) {
|
|
87
|
+
return c.json({ success: false, error: { message: 'Remote URL is required' } }, 400);
|
|
88
|
+
}
|
|
89
|
+
await setRemoteUrl(remoteUrl);
|
|
90
|
+
// If a GitHub token exists and is valid, store it in the credential helper now that we have a remote
|
|
91
|
+
try {
|
|
92
|
+
const tokenRecord = await readGithubToken();
|
|
93
|
+
if (tokenRecord && isGithubTokenValid(tokenRecord)) {
|
|
94
|
+
try {
|
|
95
|
+
await storeCredentials('x-access-token', tokenRecord.token);
|
|
96
|
+
console.log('[git] Successfully stored GitHub token in credential helper after remote creation');
|
|
97
|
+
}
|
|
98
|
+
catch (credError) {
|
|
99
|
+
console.warn('[git] Failed to store GitHub token in credential helper after remote creation:', credError);
|
|
100
|
+
// Don't fail the remote creation if credential storage fails
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
catch (tokenError) {
|
|
105
|
+
// Ignore token read errors - remote creation should still succeed
|
|
106
|
+
console.warn('[git] Could not check for GitHub token after remote creation:', tokenError);
|
|
107
|
+
}
|
|
108
|
+
return c.json({ success: true, data: { message: 'Remote URL set successfully' } });
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
81
114
|
// Store Credentials
|
|
82
115
|
app.post('/credentials', async (c) => {
|
|
83
116
|
try {
|
|
84
117
|
const body = await c.req.json();
|
|
85
118
|
const username = String(body?.username || '');
|
|
86
119
|
const password = String(body?.password || '');
|
|
120
|
+
const remoteUrl = body?.remoteUrl ? String(body.remoteUrl) : undefined;
|
|
87
121
|
if (!username || !password) {
|
|
88
122
|
return c.json({ success: false, error: { message: 'Username and password are required' } }, 400);
|
|
89
123
|
}
|
|
90
|
-
await storeCredentials(username, password);
|
|
124
|
+
await storeCredentials(username, password, remoteUrl);
|
|
91
125
|
return c.json({ success: true, data: { message: 'Credentials stored successfully' } });
|
|
92
126
|
}
|
|
93
127
|
catch (e) {
|
|
@@ -162,11 +196,21 @@ app.post('/providers/github/token', async (c) => {
|
|
|
162
196
|
}
|
|
163
197
|
}
|
|
164
198
|
const record = await saveGithubToken(token, expiresAt);
|
|
199
|
+
// Try to store credentials in the credential helper if a remote exists
|
|
165
200
|
try {
|
|
166
201
|
await storeCredentials('x-access-token', token);
|
|
202
|
+
console.log('[git] Successfully stored GitHub token in credential helper');
|
|
167
203
|
}
|
|
168
204
|
catch (credError) {
|
|
169
|
-
|
|
205
|
+
// Check if the error is because no remote exists (expected case)
|
|
206
|
+
const errorMessage = credError?.message || '';
|
|
207
|
+
if (errorMessage.includes('No git remote configured')) {
|
|
208
|
+
console.log('[git] GitHub token saved, but no remote configured yet. Token will be stored in credential helper when remote is created.');
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
console.warn('[git] Failed to install GitHub token into credential store:', credError);
|
|
212
|
+
}
|
|
213
|
+
// Don't fail token storage if credential helper setup fails - the token is still saved
|
|
170
214
|
}
|
|
171
215
|
return c.json({ success: true, data: { expiresAt: record.expiresAt } });
|
|
172
216
|
}
|
|
@@ -340,6 +384,46 @@ app.post('/commits', async (c) => {
|
|
|
340
384
|
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
341
385
|
}
|
|
342
386
|
});
|
|
387
|
+
// Discard file changes
|
|
388
|
+
app.post('/discard', async (c) => {
|
|
389
|
+
try {
|
|
390
|
+
const body = await c.req.json();
|
|
391
|
+
const filePath = String(body?.file || body?.path || '');
|
|
392
|
+
if (!filePath) {
|
|
393
|
+
return c.json({ success: false, error: { message: 'file path required' } }, 400);
|
|
394
|
+
}
|
|
395
|
+
const { runGit, getRepoStatus } = await import('../../../../lib/git.js');
|
|
396
|
+
// Get current status to determine file state
|
|
397
|
+
const status = await getRepoStatus();
|
|
398
|
+
const fileChange = status.changes.find((c) => c.path === filePath);
|
|
399
|
+
if (!fileChange) {
|
|
400
|
+
return c.json({ success: false, error: { message: 'File not found in uncommitted changes' } }, 404);
|
|
401
|
+
}
|
|
402
|
+
const statusCode = fileChange.status.trim();
|
|
403
|
+
// Handle different file statuses
|
|
404
|
+
if (statusCode === '??' || statusCode.includes('U')) {
|
|
405
|
+
// Untracked file - remove it
|
|
406
|
+
const { unlink } = await import('fs/promises');
|
|
407
|
+
const { getRepoRoot } = await import('../../../../lib/git.js');
|
|
408
|
+
const repoRoot = await getRepoRoot();
|
|
409
|
+
const { join } = await import('path');
|
|
410
|
+
const fullPath = join(repoRoot, filePath);
|
|
411
|
+
await unlink(fullPath);
|
|
412
|
+
}
|
|
413
|
+
else if (statusCode.includes('D')) {
|
|
414
|
+
// Deleted file - restore it
|
|
415
|
+
await runGit(['checkout', '--', filePath]);
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
// Modified or Added file - discard changes
|
|
419
|
+
await runGit(['checkout', '--', filePath]);
|
|
420
|
+
}
|
|
421
|
+
return c.json({ success: true, data: { file: filePath, status: statusCode } });
|
|
422
|
+
}
|
|
423
|
+
catch (e) {
|
|
424
|
+
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
343
427
|
// Push / Pull (current)
|
|
344
428
|
app.post('/push', async (c) => {
|
|
345
429
|
try {
|
|
@@ -523,4 +607,31 @@ app.post('/worktrees/:name/pull', async (c) => {
|
|
|
523
607
|
return c.json({ success: false, error: { message: e.message } }, 500);
|
|
524
608
|
}
|
|
525
609
|
});
|
|
610
|
+
// Get commit details
|
|
611
|
+
app.get('/commits/:sha', async (c) => {
|
|
612
|
+
try {
|
|
613
|
+
const sha = c.req.param('sha');
|
|
614
|
+
const details = await getCommitDetails(sha);
|
|
615
|
+
return c.json({ success: true, data: details });
|
|
616
|
+
}
|
|
617
|
+
catch (e) {
|
|
618
|
+
return c.json({ success: false, error: { message: e.message || 'Failed to get commit details' } }, 500);
|
|
619
|
+
}
|
|
620
|
+
});
|
|
621
|
+
// Get file diff for a specific commit
|
|
622
|
+
app.get('/commits/:sha/files/:filepath{.+}', async (c) => {
|
|
623
|
+
try {
|
|
624
|
+
const sha = c.req.param('sha');
|
|
625
|
+
const filepath = c.req.param('filepath');
|
|
626
|
+
if (!filepath) {
|
|
627
|
+
return c.json({ success: false, error: { message: 'filepath is required' } }, 400);
|
|
628
|
+
}
|
|
629
|
+
const diff = await getFileDiff(sha, filepath);
|
|
630
|
+
// Return as plain text for easier viewing
|
|
631
|
+
return c.text(diff);
|
|
632
|
+
}
|
|
633
|
+
catch (e) {
|
|
634
|
+
return c.json({ success: false, error: { message: e.message || 'Failed to get file diff' } }, 500);
|
|
635
|
+
}
|
|
636
|
+
});
|
|
526
637
|
export default app;
|
|
@@ -47,6 +47,9 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
|
|
|
47
47
|
}[];
|
|
48
48
|
tags?: string[];
|
|
49
49
|
contextPaths?: string[];
|
|
50
|
+
agentId?: string;
|
|
51
|
+
agentIds?: string[];
|
|
52
|
+
mcpServers?: string[];
|
|
50
53
|
};
|
|
51
54
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
52
55
|
success: false;
|
|
@@ -108,6 +111,9 @@ export declare function PATCH(c: Context): Promise<(Response & import("hono").Ty
|
|
|
108
111
|
}[];
|
|
109
112
|
tags?: string[];
|
|
110
113
|
contextPaths?: string[];
|
|
114
|
+
agentId?: string;
|
|
115
|
+
agentIds?: string[];
|
|
116
|
+
mcpServers?: string[];
|
|
111
117
|
};
|
|
112
118
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
113
119
|
success: false;
|
|
@@ -82,6 +82,15 @@ export async function PATCH(c) {
|
|
|
82
82
|
schedule,
|
|
83
83
|
tags: Array.isArray(body.tags) ? body.tags : existing.tags,
|
|
84
84
|
contextPaths: Array.isArray(body.contextPaths) ? body.contextPaths : existing.contextPaths,
|
|
85
|
+
agentId: body.agentId !== undefined
|
|
86
|
+
? (typeof body.agentId === 'string' && body.agentId ? body.agentId : undefined)
|
|
87
|
+
: existing.agentId,
|
|
88
|
+
agentIds: body.agentIds !== undefined
|
|
89
|
+
? (Array.isArray(body.agentIds) ? body.agentIds.filter((s) => typeof s === 'string') : undefined)
|
|
90
|
+
: existing.agentIds,
|
|
91
|
+
mcpServers: body.mcpServers !== undefined
|
|
92
|
+
? (Array.isArray(body.mcpServers) ? body.mcpServers.filter((s) => typeof s === 'string') : undefined)
|
|
93
|
+
: existing.mcpServers,
|
|
85
94
|
metadata: {
|
|
86
95
|
...existing.metadata,
|
|
87
96
|
updatedAt: new Date()
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
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
|
+
success: false;
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
9
|
+
success: false;
|
|
10
|
+
error: {
|
|
11
|
+
code: string;
|
|
12
|
+
message: any;
|
|
13
|
+
};
|
|
14
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { JobStore } from '../../../../../../../../lib/jobs/job-store.js';
|
|
4
|
+
import { getProjectRoot } from '../../../../../../../../lib/project-paths.js';
|
|
5
|
+
const store = new JobStore();
|
|
6
|
+
export async function GET(c) {
|
|
7
|
+
try {
|
|
8
|
+
const jobId = c.req.param('id');
|
|
9
|
+
const runId = c.req.param('runId');
|
|
10
|
+
const job = await store.getJob(jobId);
|
|
11
|
+
if (!job) {
|
|
12
|
+
return c.json({
|
|
13
|
+
success: false,
|
|
14
|
+
error: {
|
|
15
|
+
code: 'JOB_NOT_FOUND',
|
|
16
|
+
message: `Job ${jobId} not found`
|
|
17
|
+
}
|
|
18
|
+
}, 404);
|
|
19
|
+
}
|
|
20
|
+
const run = job.runs.find((r) => r.id === runId);
|
|
21
|
+
if (!run) {
|
|
22
|
+
return c.json({
|
|
23
|
+
success: false,
|
|
24
|
+
error: {
|
|
25
|
+
code: 'RUN_NOT_FOUND',
|
|
26
|
+
message: `Run ${runId} not found for job ${jobId}`
|
|
27
|
+
}
|
|
28
|
+
}, 404);
|
|
29
|
+
}
|
|
30
|
+
// Construct log path - use outputPath from run if available, otherwise construct it
|
|
31
|
+
const projectRoot = await getProjectRoot();
|
|
32
|
+
const logPath = run.outputPath
|
|
33
|
+
? path.join(projectRoot, run.outputPath)
|
|
34
|
+
: path.join(projectRoot, '.nut', 'jobs', 'logs', jobId, `${runId}.log`);
|
|
35
|
+
try {
|
|
36
|
+
const logContent = await fs.readFile(logPath, 'utf-8');
|
|
37
|
+
// Return as plain text for easier viewing
|
|
38
|
+
return c.text(logContent);
|
|
39
|
+
}
|
|
40
|
+
catch (fileError) {
|
|
41
|
+
if (fileError?.code === 'ENOENT') {
|
|
42
|
+
return c.json({
|
|
43
|
+
success: false,
|
|
44
|
+
error: {
|
|
45
|
+
code: 'LOG_NOT_FOUND',
|
|
46
|
+
message: `Log file not found for run ${runId}`,
|
|
47
|
+
logPath: run.outputPath || `(expected at ${logPath})`
|
|
48
|
+
}
|
|
49
|
+
}, 404);
|
|
50
|
+
}
|
|
51
|
+
throw fileError;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error('Failed to fetch job run log:', error);
|
|
56
|
+
return c.json({
|
|
57
|
+
success: false,
|
|
58
|
+
error: {
|
|
59
|
+
code: 'GET_LOG_ERROR',
|
|
60
|
+
message: error?.message ?? 'Unknown error retrieving job run log'
|
|
61
|
+
}
|
|
62
|
+
}, 500);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
success: false;
|
|
4
|
+
error: {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
};
|
|
8
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
9
|
+
success: true;
|
|
10
|
+
data: {
|
|
11
|
+
id: string;
|
|
12
|
+
jobId: string;
|
|
13
|
+
trigger: import("@lovelybunch/types").ScheduledJobTrigger;
|
|
14
|
+
status: import("@lovelybunch/types").ScheduledJobRunStatus;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
finishedAt?: string;
|
|
17
|
+
outputPath?: string;
|
|
18
|
+
summary?: string;
|
|
19
|
+
error?: string;
|
|
20
|
+
cliCommand?: string;
|
|
21
|
+
};
|
|
22
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
23
|
+
success: false;
|
|
24
|
+
error: {
|
|
25
|
+
code: string;
|
|
26
|
+
message: any;
|
|
27
|
+
};
|
|
28
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { JobStore } from '../../../../../../../lib/jobs/job-store.js';
|
|
2
|
+
const store = new JobStore();
|
|
3
|
+
export async function GET(c) {
|
|
4
|
+
try {
|
|
5
|
+
const jobId = c.req.param('id');
|
|
6
|
+
const runId = c.req.param('runId');
|
|
7
|
+
const job = await store.getJob(jobId);
|
|
8
|
+
if (!job) {
|
|
9
|
+
return c.json({
|
|
10
|
+
success: false,
|
|
11
|
+
error: {
|
|
12
|
+
code: 'JOB_NOT_FOUND',
|
|
13
|
+
message: `Job ${jobId} not found`
|
|
14
|
+
}
|
|
15
|
+
}, 404);
|
|
16
|
+
}
|
|
17
|
+
const run = job.runs.find((r) => r.id === runId);
|
|
18
|
+
if (!run) {
|
|
19
|
+
return c.json({
|
|
20
|
+
success: false,
|
|
21
|
+
error: {
|
|
22
|
+
code: 'RUN_NOT_FOUND',
|
|
23
|
+
message: `Run ${runId} not found for job ${jobId}`
|
|
24
|
+
}
|
|
25
|
+
}, 404);
|
|
26
|
+
}
|
|
27
|
+
return c.json({ success: true, data: run });
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
console.error('Failed to fetch job run:', error);
|
|
31
|
+
return c.json({
|
|
32
|
+
success: false,
|
|
33
|
+
error: {
|
|
34
|
+
code: 'GET_RUN_ERROR',
|
|
35
|
+
message: error?.message ?? 'Unknown error retrieving job run'
|
|
36
|
+
}
|
|
37
|
+
}, 500);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -2,6 +2,8 @@ import { Hono } from 'hono';
|
|
|
2
2
|
import { GET, POST } from './route.js';
|
|
3
3
|
import { GET as getJob, PATCH as patchJob, DELETE as deleteJob } from './[id]/route.js';
|
|
4
4
|
import { POST as runJob } from './[id]/run/route.js';
|
|
5
|
+
import { GET as getRun } from './[id]/runs/[runId]/route.js';
|
|
6
|
+
import { GET as getRunLog } from './[id]/runs/[runId]/log/route.js';
|
|
5
7
|
import { GET as getStatus } from './status/route.js';
|
|
6
8
|
const jobs = new Hono();
|
|
7
9
|
jobs.get('/', GET);
|
|
@@ -11,4 +13,6 @@ jobs.get('/:id', getJob);
|
|
|
11
13
|
jobs.patch('/:id', patchJob);
|
|
12
14
|
jobs.delete('/:id', deleteJob);
|
|
13
15
|
jobs.post('/:id/run', runJob);
|
|
16
|
+
jobs.get('/:id/runs/:runId', getRun);
|
|
17
|
+
jobs.get('/:id/runs/:runId/log', getRunLog);
|
|
14
18
|
export default jobs;
|
|
@@ -43,6 +43,9 @@ export declare function GET(c: Context): Promise<(Response & import("hono").Type
|
|
|
43
43
|
}[];
|
|
44
44
|
tags?: string[];
|
|
45
45
|
contextPaths?: string[];
|
|
46
|
+
agentId?: string;
|
|
47
|
+
agentIds?: string[];
|
|
48
|
+
mcpServers?: string[];
|
|
46
49
|
}[];
|
|
47
50
|
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
48
51
|
success: false;
|
|
@@ -98,6 +101,9 @@ export declare function POST(c: Context): Promise<(Response & import("hono").Typ
|
|
|
98
101
|
}[];
|
|
99
102
|
tags?: string[];
|
|
100
103
|
contextPaths?: string[];
|
|
104
|
+
agentId?: string;
|
|
105
|
+
agentIds?: string[];
|
|
106
|
+
mcpServers?: string[];
|
|
101
107
|
};
|
|
102
108
|
}, 201, "json">) | (Response & import("hono").TypedResponse<{
|
|
103
109
|
success: false;
|
|
@@ -122,6 +122,9 @@ export async function POST(c) {
|
|
|
122
122
|
runs: [],
|
|
123
123
|
tags: Array.isArray(body.tags) ? body.tags : [],
|
|
124
124
|
contextPaths: Array.isArray(body.contextPaths) ? body.contextPaths : [],
|
|
125
|
+
agentId: body.agentId && typeof body.agentId === 'string' ? body.agentId : undefined,
|
|
126
|
+
agentIds: Array.isArray(body.agentIds) ? body.agentIds.filter((s) => typeof s === 'string') : undefined,
|
|
127
|
+
mcpServers: Array.isArray(body.mcpServers) ? body.mcpServers.filter((s) => typeof s === 'string') : undefined,
|
|
125
128
|
};
|
|
126
129
|
await store.saveJob(job);
|
|
127
130
|
await scheduler.refresh(job.id);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Hono } from 'hono';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
2
4
|
import { loadUserSettings, saveUserSettings } from '../../../../../lib/user-preferences.js';
|
|
5
|
+
import { findGaitDirectory } from '../../../../../lib/gait-path.js';
|
|
3
6
|
const app = new Hono();
|
|
4
7
|
/**
|
|
5
8
|
* GET /api/v1/user/settings
|
|
@@ -22,9 +25,9 @@ app.get('/', async (c) => {
|
|
|
22
25
|
app.put('/', async (c) => {
|
|
23
26
|
try {
|
|
24
27
|
const body = await c.req.json();
|
|
25
|
-
const { profile, preferences } = body;
|
|
26
|
-
if (!profile && !preferences) {
|
|
27
|
-
return c.json({ success: false, error: 'Profile or
|
|
28
|
+
const { profile, preferences, coconut } = body;
|
|
29
|
+
if (!profile && !preferences && !coconut) {
|
|
30
|
+
return c.json({ success: false, error: 'Profile, preferences, or coconut data required' }, 400);
|
|
28
31
|
}
|
|
29
32
|
let settings = await loadUserSettings();
|
|
30
33
|
if (profile) {
|
|
@@ -41,6 +44,43 @@ app.put('/', async (c) => {
|
|
|
41
44
|
};
|
|
42
45
|
}
|
|
43
46
|
await saveUserSettings(settings);
|
|
47
|
+
// If coconut.id is provided, update .nut/config.json
|
|
48
|
+
if (coconut?.id) {
|
|
49
|
+
try {
|
|
50
|
+
const gaitDir = await findGaitDirectory();
|
|
51
|
+
if (gaitDir) {
|
|
52
|
+
const configPath = path.join(gaitDir, 'config.json');
|
|
53
|
+
// Load existing config
|
|
54
|
+
let config = {};
|
|
55
|
+
try {
|
|
56
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
57
|
+
config = JSON.parse(content);
|
|
58
|
+
}
|
|
59
|
+
catch (error) {
|
|
60
|
+
if (error.code === 'ENOENT') {
|
|
61
|
+
config = {};
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Ensure .nut directory exists
|
|
68
|
+
const configDir = path.dirname(configPath);
|
|
69
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
70
|
+
// Update coconut.id
|
|
71
|
+
if (!config.coconut) {
|
|
72
|
+
config.coconut = {};
|
|
73
|
+
}
|
|
74
|
+
config.coconut.id = coconut.id;
|
|
75
|
+
// Save config
|
|
76
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2), 'utf-8');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error('Failed to update coconut.id in config.json:', error);
|
|
81
|
+
// Don't fail the request if config update fails
|
|
82
|
+
}
|
|
83
|
+
}
|
|
44
84
|
return c.json({ success: true, data: settings });
|
|
45
85
|
}
|
|
46
86
|
catch (error) {
|
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.10",
|
|
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.10",
|
|
40
|
+
"@lovelybunch/mcp": "^1.0.69-alpha.10",
|
|
41
|
+
"@lovelybunch/types": "^1.0.69-alpha.10",
|
|
42
42
|
"arctic": "^1.9.2",
|
|
43
43
|
"bcrypt": "^5.1.1",
|
|
44
44
|
"cookie": "^0.6.0",
|