@lovelybunch/api 1.0.75-alpha.1 → 1.0.75-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/auth/auth-manager.d.ts +5 -0
- package/dist/lib/auth/auth-manager.js +9 -0
- package/dist/lib/storage/file-storage.d.ts +16 -16
- package/dist/lib/storage/file-storage.js +84 -79
- package/dist/lib/terminal/terminal-manager.d.ts +3 -5
- package/dist/lib/terminal/terminal-manager.js +19 -63
- package/dist/middleware/auth.js +36 -0
- package/dist/routes/api/v1/ai/index.js +0 -2
- package/dist/routes/api/v1/ai/route.js +445 -259
- package/dist/routes/api/v1/chats/[id]/index.js +2 -1
- package/dist/routes/api/v1/chats/[id]/route.d.ts +7 -0
- package/dist/routes/api/v1/chats/[id]/route.js +30 -1
- package/dist/routes/api/v1/context/index.js +0 -2
- package/dist/routes/api/v1/events/route.js +1 -1
- package/dist/routes/api/v1/events/status/route.d.ts +1 -1
- package/dist/routes/api/v1/jobs/[id]/run/route.d.ts +2 -2
- package/dist/routes/api/v1/jobs/[id]/runs/[runId]/route.d.ts +2 -2
- package/dist/routes/api/v1/jobs/status/route.d.ts +1 -1
- package/dist/routes/api/v1/knowledge/[filename]/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/index.js +1 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/[filename]/route.js +254 -0
- package/dist/routes/api/v1/knowledge/index.d.ts +1 -0
- package/dist/routes/api/v1/knowledge/index.js +1 -0
- package/dist/routes/api/v1/knowledge/route.d.ts +3 -0
- package/dist/routes/api/v1/knowledge/route.js +176 -0
- package/dist/routes/api/v1/mcp/index.js +109 -34
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +8 -8
- package/dist/routes/api/v1/skills/[id]/index.d.ts +1 -0
- package/dist/routes/api/v1/skills/[id]/index.js +1 -0
- package/dist/routes/api/v1/skills/[id]/route.d.ts +3 -0
- package/dist/routes/api/v1/skills/[id]/route.js +199 -0
- package/dist/routes/api/v1/skills/index.d.ts +1 -0
- package/dist/routes/api/v1/skills/index.js +1 -0
- package/dist/routes/api/v1/skills/route.d.ts +3 -0
- package/dist/routes/api/v1/skills/route.js +329 -0
- package/dist/routes/api/v1/tasks/[id]/route.d.ts +351 -0
- package/dist/routes/api/v1/tasks/[id]/route.js +156 -0
- package/dist/routes/api/v1/tasks/index.d.ts +3 -0
- package/dist/routes/api/v1/tasks/index.js +10 -0
- package/dist/routes/api/v1/tasks/route.d.ts +329 -0
- package/dist/routes/api/v1/tasks/route.js +126 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +2 -2
- package/dist/routes/api/v1/terminal/[taskId]/create/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/create/route.js +27 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/destroy/route.js +21 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/index.js +5 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[taskId]/resize/route.js +21 -0
- package/dist/routes/api/v1/terminal/sessions/route.js +4 -4
- package/dist/server-with-static.js +18 -14
- package/dist/server.js +15 -13
- package/package.json +8 -4
- package/static/assets/{ActivityPage-BYrDJlH6.js → ActivityPage-CbmEnYhg.js} +1 -1
- package/static/assets/ApiKeysSettingsPage-DLxQIqTT.js +2 -0
- package/static/assets/{ArchitectureEditPage-Bw2d_hmk.js → ArchitectureEditPage-CbtzgIQv.js} +4 -4
- package/static/assets/{ArchitecturePage-36wO0UD7.js → ArchitecturePage-CcAMfFZ8.js} +1 -1
- package/static/assets/{AuthSettingsPage-CKifiWsx.js → AuthSettingsPage-4Prb7nAt.js} +2 -2
- package/static/assets/{CallbackPage-Ck0dddEW.js → CallbackPage-CmWv_5Nh.js} +1 -1
- package/static/assets/CodePage-COC7rcwM.js +2 -0
- package/static/assets/{CollapsibleSection-BKY-lfsm.js → CollapsibleSection-C_VVeVSc.js} +1 -1
- package/static/assets/DashboardPage-BACFVIaW.js +41 -0
- package/static/assets/{GitPage-BgCxzm-C.js → GitPage-rFngEr_A.js} +2 -2
- package/static/assets/GitSettingsPage-CDQovOqx.js +6 -0
- package/static/assets/IdentityPage-CS4REq2E.js +11 -0
- package/static/assets/{ImplementationStepsEditor-iqrCqyOb.js → ImplementationStepsEditor-CBIgppkZ.js} +2 -2
- package/static/assets/IntegrationsSettingsPage-B7HoraBH.js +1 -0
- package/static/assets/JobDetailPage-QUhWsaWV.js +1 -0
- package/static/assets/KnowledgeDetailPage-DCJstmIr.js +1 -0
- package/static/assets/KnowledgeEditPage-EYpFPfcJ.js +1 -0
- package/static/assets/KnowledgePage-D-7skvn5.js +8 -0
- package/static/assets/{LoginPage-DqbNxfY6.js → LoginPage-L2aoqSDT.js} +1 -1
- package/static/assets/McpSettingsPage-CLwqDjw_.js +1 -0
- package/static/assets/NewKnowledgePage-Fq_QD8um.js +9 -0
- package/static/assets/NewSkillPage-DRcgovk0.js +1 -0
- package/static/assets/NewTaskPage-CleA8rH5.js +90 -0
- package/static/assets/ProjectEditPage-GIMOKgmh.js +11 -0
- package/static/assets/ProjectPage-DlWZOqnb.js +1 -0
- package/static/assets/PromptsSettingsPage-DFagGfo-.js +1 -0
- package/static/assets/ResourceDetailPage-CnFRpDec.js +1 -0
- package/static/assets/ResourcesPage-Cw3fu0JE.js +41 -0
- package/static/assets/{RoleEditPage-DhVneoNR.js → RoleEditPage-ARXq_eCs.js} +1 -1
- package/static/assets/{RolePage-BOI2T8QG.js → RolePage--XBBIrq8.js} +1 -1
- package/static/assets/{RulesSettingsPage-VsteFA-j.js → RulesSettingsPage-DEqaRH96.js} +3 -3
- package/static/assets/SchedulePage-C_B2k9P6.js +4 -0
- package/static/assets/SkillDetailPage-Dr6xyKAX.js +1 -0
- package/static/assets/SkillEditPage-BsHXmfEZ.js +1 -0
- package/static/assets/SkillsPage-BrDdAAPx.js +8 -0
- package/static/assets/SkillsSettingsPage-Cvi7xaDE.js +1 -0
- package/static/assets/SourceInput-DxF_GGj8.js +1 -0
- package/static/assets/{TagInput-BWLBdW8h.js → TagInput-DYvHbVZq.js} +1 -1
- package/static/assets/TaskDetailPage-DzcD6t03.js +1 -0
- package/static/assets/TaskEditPage-C7a6FOeJ.js +1 -0
- package/static/assets/TasksPage-ChWRSO_S.js +17 -0
- package/static/assets/TerminalPage-CoPwn8cU.js +1 -0
- package/static/assets/TerminalSessionPage-BQBZrOJa.js +13 -0
- package/static/assets/UserPreferencesPage-DjtU7veO.js +1 -0
- package/static/assets/UserSettingsPage-CfU8boJQ.js +1 -0
- package/static/assets/UtilitiesPage-sP1Crg-X.js +1 -0
- package/static/assets/{alert-C7sSXJf0.js → alert-BqZa-crG.js} +1 -1
- package/static/assets/{arrow-down-DNa4SyO2.js → arrow-down-3faV_GyO.js} +1 -1
- package/static/assets/{arrow-left-DkoECaEJ.js → arrow-left-FD3wQmzH.js} +1 -1
- package/static/assets/{arrow-up-DQlu6uQE.js → arrow-up-BzP0YNVk.js} +1 -1
- package/static/assets/{badge-2ZOe5ynf.js → badge-DRyeFib9.js} +1 -1
- package/static/assets/{browser-modal-DUCXGaha.js → browser-modal-vnePkRfO.js} +2 -2
- package/static/assets/{card-By4vbQ_f.js → card-CuQs3dpy.js} +1 -1
- package/static/assets/{chevron-left-CJfNRqcH.js → chevron-left-QZIoYcVa.js} +1 -1
- package/static/assets/{plus-D0SUNNMH.js → chevron-up-DreyvhRd.js} +2 -2
- package/static/assets/{chevrons-up-DoBrp0tt.js → chevrons-up-CsAkc9vE.js} +1 -1
- package/static/assets/{circle-alert-B4JJ0jKl.js → circle-alert-ewz28SE3.js} +1 -1
- package/static/assets/{circle-check-DC5Ek4MP.js → circle-check-2TuD-EHs.js} +1 -1
- package/static/assets/{circle-check-big-Dw4YJZos.js → circle-check-big-1xNuBPkR.js} +1 -1
- package/static/assets/{circle-play-D9VS6Vdc.js → circle-play-C_w-qCn4.js} +1 -1
- package/static/assets/{circle-x-BReHgv4g.js → circle-x-B_d4wjG-.js} +1 -1
- package/static/assets/{clipboard-CuF9OlXN.js → clipboard-B6vBm1BP.js} +1 -1
- package/static/assets/{clock-CDTi2Cen.js → clock-BIzEsx1g.js} +1 -1
- package/static/assets/{download-DHIAgGlY.js → download-Dv-RIvKK.js} +1 -1
- package/static/assets/droid-GYYyVzN-.js +18 -0
- package/static/assets/external-link-Cr8wjV6X.js +6 -0
- package/static/assets/{eye-Ch7ecV9Z.js → eye-DN958vyL.js} +1 -1
- package/static/assets/{folder-git-2-BYI_osdA.js → folder-git-2-BproRzAR.js} +1 -1
- package/static/assets/index-Co_SJV3n.js +472 -0
- package/static/assets/index-GFQ5RqVh.css +2 -0
- package/static/assets/info-CzKk8mbR.js +6 -0
- package/static/assets/{label-BUv8Ltyw.js → label-h5GIKGcJ.js} +1 -1
- package/static/assets/{markdown-editor-BP8Xkecg.js → markdown-editor-C6il4XWv.js} +1 -1
- package/static/assets/{pause-B0clczfE.js → pause-BDsjEmXM.js} +1 -1
- package/static/assets/{play-BHUpJCX1.js → play-CGsVQUJG.js} +1 -1
- package/static/assets/{radio-group-CSBH8ca-.js → radio-group-Bsd75ahK.js} +1 -1
- package/static/assets/{refresh-cw-C-yGwsHL.js → refresh-cw-qE1iNkL_.js} +1 -1
- package/static/assets/{search-DFtqbVgP.js → search-dvi0J4Dr.js} +1 -1
- package/static/assets/select-DylRS99W.js +1 -0
- package/static/assets/status-utils-CDkPeVfP.js +1 -0
- package/static/assets/{switch-WtOd8h5Z.js → switch-CsB3wpq9.js} +1 -1
- package/static/assets/{tabs-D93ZjKR7.js → tabs-Bw_4k2Rs.js} +1 -1
- package/static/assets/{tag-EEDDoCc_.js → tag-Dh5PraRd.js} +1 -1
- package/static/assets/{terminal-preview-sns5QTN_.js → terminal-preview-CfOb7xMx.js} +1 -1
- package/static/assets/use-terminal-T_tdJTCU.js +1 -0
- package/static/assets/video-bO6uuAjA.js +36 -0
- package/static/assets/{zap-BYFYWzmj.js → zap-DHZ91NcK.js} +1 -1
- package/static/index.html +2 -2
- package/static/assets/AgentDetailPage-D6yg4CtT.js +0 -1
- package/static/assets/AgentEditPage-CTnjxjX7.js +0 -1
- package/static/assets/AgentsPage-NdB5OB1C.js +0 -3
- package/static/assets/AgentsSettingsPage-CYXp908g.js +0 -6
- package/static/assets/ApiKeysSettingsPage-Jl6vvVmW.js +0 -7
- package/static/assets/CodePage-b3JR4eWW.js +0 -2
- package/static/assets/DashboardPage-Cv2-duAx.js +0 -41
- package/static/assets/GitSettingsPage-CKIWkAnw.js +0 -6
- package/static/assets/IdentityPage-BsZnszF-.js +0 -11
- package/static/assets/IntegrationsSettingsPage-C1dC_vVy.js +0 -1
- package/static/assets/JobDetailPage-BgGQJkND.js +0 -1
- package/static/assets/KnowledgeDetailPage-Dzy2V7yT.js +0 -1
- package/static/assets/KnowledgeEditPage-av1ALt0h.js +0 -1
- package/static/assets/KnowledgePage-C_UDfD9X.js +0 -8
- package/static/assets/McpSettingsPage-BqbErEWS.js +0 -1
- package/static/assets/NewAgentPage-UEVIYkt3.js +0 -1
- package/static/assets/NewKnowledgePage-CzYXdoDb.js +0 -9
- package/static/assets/NewProposalPage-DrU0ihJX.js +0 -90
- package/static/assets/ProjectEditPage-j4KporiC.js +0 -11
- package/static/assets/ProjectPage-BQNa0qA-.js +0 -1
- package/static/assets/PromptsSettingsPage-sYphs8Dn.js +0 -1
- package/static/assets/ProposalDetailPage-CHR1MmwC.js +0 -1
- package/static/assets/ProposalEditPage-CkcZqgax.js +0 -1
- package/static/assets/ProposalsPage-CUB_aLXW.js +0 -17
- package/static/assets/ResourcesPage-DzjwPoyS.js +0 -71
- package/static/assets/SchedulePage-_v4YfdHo.js +0 -4
- package/static/assets/SourceInput-BFxoOvqS.js +0 -1
- package/static/assets/TerminalPage-DtVDWX7G.js +0 -1
- package/static/assets/TerminalSessionPage-BUHzje8A.js +0 -13
- package/static/assets/UserPreferencesPage-CkXJuogz.js +0 -1
- package/static/assets/UserSettingsPage-Dz-R1ijS.js +0 -1
- package/static/assets/UtilitiesPage-CRqQYVsb.js +0 -1
- package/static/assets/calendar-tNgwmWmG.js +0 -6
- package/static/assets/droid-BvMEm3eg.js +0 -8
- package/static/assets/index-CCs6x1Au.js +0 -468
- package/static/assets/index-CzjbtPHw.css +0 -2
- package/static/assets/use-terminal-BW9XYY8N.js +0 -1
|
@@ -128,3 +128,8 @@ export declare class AuthManager {
|
|
|
128
128
|
* @param configPath - Optional custom path to auth.json. If not provided, uses OS app data directory.
|
|
129
129
|
*/
|
|
130
130
|
export declare function getAuthManager(configPath?: string): AuthManager;
|
|
131
|
+
/**
|
|
132
|
+
* Reset the singleton AuthManager (for testing isolation).
|
|
133
|
+
* Only effective when NODE_ENV is 'test'.
|
|
134
|
+
*/
|
|
135
|
+
export declare function resetAuthManagerForTesting(): void;
|
|
@@ -420,3 +420,12 @@ export function getAuthManager(configPath) {
|
|
|
420
420
|
}
|
|
421
421
|
return authManagerInstance;
|
|
422
422
|
}
|
|
423
|
+
/**
|
|
424
|
+
* Reset the singleton AuthManager (for testing isolation).
|
|
425
|
+
* Only effective when NODE_ENV is 'test'.
|
|
426
|
+
*/
|
|
427
|
+
export function resetAuthManagerForTesting() {
|
|
428
|
+
if (process.env.NODE_ENV === 'test') {
|
|
429
|
+
authManagerInstance = null;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export interface
|
|
3
|
-
status?:
|
|
1
|
+
import { Task, TaskStatus } from '@lovelybunch/types';
|
|
2
|
+
export interface TaskFilter {
|
|
3
|
+
status?: TaskStatus;
|
|
4
4
|
author?: string;
|
|
5
5
|
priority?: string;
|
|
6
6
|
tags?: string[];
|
|
7
7
|
search?: string;
|
|
8
8
|
}
|
|
9
9
|
export interface StorageAdapter {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
createTask(task: Task): Promise<void>;
|
|
11
|
+
getTask(id: string): Promise<Task | null>;
|
|
12
|
+
updateTask(id: string, task: Partial<Task>): Promise<void>;
|
|
13
|
+
deleteTask(id: string): Promise<void>;
|
|
14
|
+
listTasks(filter?: TaskFilter): Promise<Task[]>;
|
|
15
|
+
searchTasks(query: string): Promise<Task[]>;
|
|
16
16
|
}
|
|
17
17
|
export declare class FileStorageAdapter implements StorageAdapter {
|
|
18
18
|
private basePath;
|
|
19
19
|
private sanitizeForYAML;
|
|
20
20
|
constructor(basePath?: string);
|
|
21
21
|
ensureDirectories(): Promise<void>;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
private
|
|
22
|
+
createTask(task: Task): Promise<void>;
|
|
23
|
+
getTask(id: string): Promise<Task | null>;
|
|
24
|
+
updateTask(id: string, updates: Partial<Task>): Promise<void>;
|
|
25
|
+
deleteTask(id: string): Promise<void>;
|
|
26
|
+
listTasks(filter?: TaskFilter): Promise<Task[]>;
|
|
27
|
+
searchTasks(query: string): Promise<Task[]>;
|
|
28
|
+
private frontmatterToTask;
|
|
29
29
|
private getDefaultContent;
|
|
30
30
|
}
|
|
@@ -49,68 +49,76 @@ export class FileStorageAdapter {
|
|
|
49
49
|
}
|
|
50
50
|
}
|
|
51
51
|
async ensureDirectories() {
|
|
52
|
-
const dirs = ['
|
|
52
|
+
const dirs = ['tasks', 'specs', 'flags', 'experiments', 'templates', 'jobs'];
|
|
53
53
|
for (const dir of dirs) {
|
|
54
54
|
await fs.mkdir(path.join(this.basePath, dir), { recursive: true });
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
async
|
|
57
|
+
async createTask(task) {
|
|
58
58
|
await this.ensureDirectories();
|
|
59
|
-
// Extract content from the
|
|
60
|
-
const { content, ...frontmatter } =
|
|
59
|
+
// Extract content from the task if it exists
|
|
60
|
+
const { content, ...frontmatter } = task;
|
|
61
61
|
// Normalize date fields to Date instances in case callers provide ISO strings
|
|
62
|
-
const createdAt =
|
|
63
|
-
?
|
|
64
|
-
: new Date(
|
|
65
|
-
const updatedAt =
|
|
66
|
-
?
|
|
67
|
-
: new Date(
|
|
68
|
-
// Convert the
|
|
62
|
+
const createdAt = task.metadata.createdAt instanceof Date
|
|
63
|
+
? task.metadata.createdAt
|
|
64
|
+
: new Date(task.metadata.createdAt);
|
|
65
|
+
const updatedAt = task.metadata.updatedAt instanceof Date
|
|
66
|
+
? task.metadata.updatedAt
|
|
67
|
+
: new Date(task.metadata.updatedAt);
|
|
68
|
+
// Convert the task to markdown with YAML frontmatter
|
|
69
69
|
const frontmatterData = this.sanitizeForYAML({
|
|
70
70
|
// Required fields
|
|
71
|
-
id:
|
|
72
|
-
title:
|
|
71
|
+
id: task.id,
|
|
72
|
+
title: task.title,
|
|
73
73
|
// Deprecated: intent is kept for backward compatibility during migration
|
|
74
74
|
// TODO: Remove intent field in future version
|
|
75
|
-
...(
|
|
75
|
+
...(task.intent && { intent: task.intent }),
|
|
76
76
|
createdAt: createdAt.toISOString(),
|
|
77
77
|
updatedAt: updatedAt.toISOString(),
|
|
78
|
-
status:
|
|
79
|
-
priority:
|
|
78
|
+
status: task.status,
|
|
79
|
+
priority: task.metadata.priority || 'medium',
|
|
80
80
|
// Author information
|
|
81
81
|
author: {
|
|
82
|
-
id:
|
|
83
|
-
name:
|
|
84
|
-
email:
|
|
82
|
+
id: task.author.id,
|
|
83
|
+
name: task.author.name,
|
|
84
|
+
email: task.author.email || '',
|
|
85
85
|
role: 'engineer', // Default role
|
|
86
|
-
type:
|
|
87
|
-
},
|
|
88
|
-
// Plan
|
|
89
|
-
plan: {
|
|
90
|
-
estimatedDuration: 'TBD',
|
|
91
|
-
dependencies: [],
|
|
92
|
-
steps: cp.planSteps.map(step => ({
|
|
93
|
-
id: step.id,
|
|
94
|
-
description: step.description,
|
|
95
|
-
status: step.status
|
|
96
|
-
}))
|
|
86
|
+
type: task.author.type
|
|
97
87
|
},
|
|
88
|
+
// Steps (top-level for consistency with CLI storage format)
|
|
89
|
+
steps: task.planSteps.map(step => ({
|
|
90
|
+
id: step.id,
|
|
91
|
+
description: step.description,
|
|
92
|
+
status: step.status,
|
|
93
|
+
...(step.command && { command: step.command }),
|
|
94
|
+
...(step.expectedOutcome && { expectedOutcome: step.expectedOutcome }),
|
|
95
|
+
...(step.output && { output: step.output }),
|
|
96
|
+
...(step.executedAt && { executedAt: step.executedAt instanceof Date ? step.executedAt.toISOString() : step.executedAt }),
|
|
97
|
+
})),
|
|
98
98
|
// Metadata
|
|
99
|
-
tags:
|
|
99
|
+
tags: task.metadata.tags || [],
|
|
100
100
|
labels: [],
|
|
101
|
-
|
|
101
|
+
// Comments use consistent field names: id, author, content, createdAt
|
|
102
|
+
comments: (task.comments || [])
|
|
103
|
+
.filter((c) => c !== undefined)
|
|
104
|
+
.map((c) => ({
|
|
105
|
+
id: c.id,
|
|
106
|
+
author: typeof c.author === 'string' ? c.author : c.author?.name || 'Unknown',
|
|
107
|
+
content: c.content || c.text || '',
|
|
108
|
+
createdAt: c.createdAt || c.timestamp || new Date().toISOString(),
|
|
109
|
+
}))
|
|
102
110
|
});
|
|
103
|
-
const markdown = matter.stringify(content || this.getDefaultContent(
|
|
104
|
-
const filePath = path.join(this.basePath, '
|
|
111
|
+
const markdown = matter.stringify(content || this.getDefaultContent(task), frontmatterData);
|
|
112
|
+
const filePath = path.join(this.basePath, 'tasks', `${task.id}.md`);
|
|
105
113
|
await fs.writeFile(filePath, markdown, 'utf-8');
|
|
106
114
|
}
|
|
107
|
-
async
|
|
115
|
+
async getTask(id) {
|
|
108
116
|
try {
|
|
109
|
-
const filePath = path.join(this.basePath, '
|
|
117
|
+
const filePath = path.join(this.basePath, 'tasks', `${id}.md`);
|
|
110
118
|
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
111
119
|
const { data, content } = matter(fileContent);
|
|
112
|
-
// Convert the frontmatter back to a
|
|
113
|
-
return this.
|
|
120
|
+
// Convert the frontmatter back to a Task
|
|
121
|
+
return this.frontmatterToTask(data, content);
|
|
114
122
|
}
|
|
115
123
|
catch (error) {
|
|
116
124
|
if (error.code === 'ENOENT')
|
|
@@ -118,21 +126,19 @@ export class FileStorageAdapter {
|
|
|
118
126
|
throw error;
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
|
-
async
|
|
122
|
-
const existing = await this.
|
|
129
|
+
async updateTask(id, updates) {
|
|
130
|
+
const existing = await this.getTask(id);
|
|
123
131
|
if (!existing)
|
|
124
|
-
throw new Error(`
|
|
125
|
-
// Never allow id to be updated to prevent overwriting other
|
|
132
|
+
throw new Error(`Task ${id} not found`);
|
|
133
|
+
// Never allow id to be updated to prevent overwriting other tasks
|
|
126
134
|
const { id: _, ...safeUpdates } = updates;
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
let commentsToStore = existing.
|
|
135
|
+
// Comments live at root level on Task (not in metadata).
|
|
136
|
+
// Accept comments from root level or metadata.comments for backward compat.
|
|
137
|
+
let commentsToStore = existing.comments || [];
|
|
130
138
|
if (safeUpdates.comments) {
|
|
131
|
-
// If comments are provided at root level, use them
|
|
132
139
|
commentsToStore = safeUpdates.comments;
|
|
133
140
|
}
|
|
134
141
|
else if (safeUpdates.metadata?.comments) {
|
|
135
|
-
// If comments are provided in metadata, use them
|
|
136
142
|
commentsToStore = safeUpdates.metadata.comments;
|
|
137
143
|
}
|
|
138
144
|
const updated = {
|
|
@@ -142,41 +148,39 @@ export class FileStorageAdapter {
|
|
|
142
148
|
...existing.metadata,
|
|
143
149
|
...safeUpdates.metadata,
|
|
144
150
|
updatedAt: new Date(),
|
|
145
|
-
comments: commentsToStore
|
|
146
151
|
},
|
|
147
|
-
// Store comments at the root level for the file format
|
|
148
152
|
comments: commentsToStore
|
|
149
153
|
};
|
|
150
|
-
await this.
|
|
154
|
+
await this.createTask(updated);
|
|
151
155
|
}
|
|
152
|
-
async
|
|
153
|
-
const filePath = path.join(this.basePath, '
|
|
156
|
+
async deleteTask(id) {
|
|
157
|
+
const filePath = path.join(this.basePath, 'tasks', `${id}.md`);
|
|
154
158
|
await fs.unlink(filePath);
|
|
155
159
|
}
|
|
156
|
-
async
|
|
157
|
-
const
|
|
160
|
+
async listTasks(filter) {
|
|
161
|
+
const tasksDir = path.join(this.basePath, 'tasks');
|
|
158
162
|
try {
|
|
159
|
-
const files = await fs.readdir(
|
|
160
|
-
const
|
|
163
|
+
const files = await fs.readdir(tasksDir);
|
|
164
|
+
const tasks = await Promise.all(files
|
|
161
165
|
.filter(f => f.endsWith('.md'))
|
|
162
166
|
.map(async (file) => {
|
|
163
|
-
const content = await fs.readFile(path.join(
|
|
167
|
+
const content = await fs.readFile(path.join(tasksDir, file), 'utf-8');
|
|
164
168
|
const { data, content: body } = matter(content);
|
|
165
|
-
return this.
|
|
169
|
+
return this.frontmatterToTask(data, body);
|
|
166
170
|
}));
|
|
167
171
|
// Apply filters
|
|
168
|
-
let filtered =
|
|
169
|
-
if (!
|
|
172
|
+
let filtered = tasks.filter(task => {
|
|
173
|
+
if (!task)
|
|
170
174
|
return false;
|
|
171
|
-
if (filter?.status &&
|
|
175
|
+
if (filter?.status && task.status !== filter.status)
|
|
172
176
|
return false;
|
|
173
|
-
if (filter?.author &&
|
|
177
|
+
if (filter?.author && task.author.id !== filter.author)
|
|
174
178
|
return false;
|
|
175
|
-
if (filter?.priority &&
|
|
179
|
+
if (filter?.priority && task.metadata.priority !== filter.priority)
|
|
176
180
|
return false;
|
|
177
181
|
if (filter?.tags && filter.tags.length > 0) {
|
|
178
|
-
const
|
|
179
|
-
if (!filter.tags.some(tag =>
|
|
182
|
+
const taskTags = task.metadata.tags || [];
|
|
183
|
+
if (!filter.tags.some(tag => taskTags.includes(tag)))
|
|
180
184
|
return false;
|
|
181
185
|
}
|
|
182
186
|
return true;
|
|
@@ -204,9 +208,9 @@ export class FileStorageAdapter {
|
|
|
204
208
|
throw error;
|
|
205
209
|
}
|
|
206
210
|
}
|
|
207
|
-
async
|
|
208
|
-
const
|
|
209
|
-
const fuse = new Fuse(
|
|
211
|
+
async searchTasks(query) {
|
|
212
|
+
const allTasks = await this.listTasks();
|
|
213
|
+
const fuse = new Fuse(allTasks, {
|
|
210
214
|
keys: [
|
|
211
215
|
{ name: 'title', weight: 2 },
|
|
212
216
|
{ name: 'intent', weight: 2 }, // Deprecated fallback
|
|
@@ -219,15 +223,16 @@ export class FileStorageAdapter {
|
|
|
219
223
|
});
|
|
220
224
|
return fuse.search(query).map(result => result.item);
|
|
221
225
|
}
|
|
222
|
-
|
|
223
|
-
// Handle both old and new format
|
|
224
|
-
const steps = data.
|
|
225
|
-
//
|
|
226
|
-
|
|
226
|
+
frontmatterToTask(data, content) {
|
|
227
|
+
// Handle both old format (plan.steps) and new format (top-level steps)
|
|
228
|
+
const steps = data.steps || data.plan?.steps || [];
|
|
229
|
+
// Normalize comments using consistent field names (content/createdAt).
|
|
230
|
+
// Also handle legacy format (text/timestamp) for backward compatibility.
|
|
231
|
+
const normalizedComments = (data.comments || []).map((comment) => ({
|
|
227
232
|
id: comment.id,
|
|
228
|
-
text: comment.content || comment.text || '', // Handle both content and text fields
|
|
229
233
|
author: typeof comment.author === 'string' ? comment.author : comment.author?.name || 'Unknown',
|
|
230
|
-
|
|
234
|
+
content: comment.content || comment.text || '',
|
|
235
|
+
createdAt: comment.createdAt || comment.timestamp || new Date().toISOString(),
|
|
231
236
|
}));
|
|
232
237
|
// Use title as primary, fall back to deprecated intent for backward compatibility
|
|
233
238
|
const titleValue = data.title || data.intent || '';
|
|
@@ -260,6 +265,7 @@ export class FileStorageAdapter {
|
|
|
260
265
|
telemetryContracts: data.telemetryContracts || [],
|
|
261
266
|
releasePlan: data.releasePlan || { strategy: 'immediate' },
|
|
262
267
|
status: data.status || 'draft',
|
|
268
|
+
comments: normalizedComments,
|
|
263
269
|
metadata: {
|
|
264
270
|
createdAt: new Date(data.createdAt || Date.now()),
|
|
265
271
|
updatedAt: new Date(data.updatedAt || Date.now()),
|
|
@@ -267,16 +273,15 @@ export class FileStorageAdapter {
|
|
|
267
273
|
aiInteractions: data.aiInteractions || [],
|
|
268
274
|
tags: data.tags || [],
|
|
269
275
|
priority: data.priority || 'medium',
|
|
270
|
-
comments: transformedComments
|
|
271
276
|
},
|
|
272
277
|
content // Store the markdown content
|
|
273
278
|
};
|
|
274
279
|
}
|
|
275
|
-
getDefaultContent(
|
|
276
|
-
return `# ${
|
|
280
|
+
getDefaultContent(task) {
|
|
281
|
+
return `# ${task.title}
|
|
277
282
|
|
|
278
283
|
## Problem Statement
|
|
279
|
-
Describe the problem this
|
|
284
|
+
Describe the problem this task addresses.
|
|
280
285
|
|
|
281
286
|
## Proposed Solution
|
|
282
287
|
Describe the solution you're proposing.
|
|
@@ -2,13 +2,11 @@ import { WebSocket } from 'ws';
|
|
|
2
2
|
import { ShellType } from './shell-utils.js';
|
|
3
3
|
export interface TerminalSession {
|
|
4
4
|
id: string;
|
|
5
|
-
|
|
5
|
+
taskId: string;
|
|
6
6
|
pty: any;
|
|
7
7
|
websocket?: WebSocket;
|
|
8
8
|
createdAt: Date;
|
|
9
9
|
lastActivity: Date;
|
|
10
|
-
enableLogging?: boolean;
|
|
11
|
-
logFilePath?: string;
|
|
12
10
|
backlog: string;
|
|
13
11
|
previewSockets?: Set<WebSocket>;
|
|
14
12
|
previewBuffer?: string;
|
|
@@ -21,9 +19,9 @@ export declare class TerminalManager {
|
|
|
21
19
|
private cleanupInterval;
|
|
22
20
|
private static readonly MAX_BACKLOG_BYTES;
|
|
23
21
|
constructor();
|
|
24
|
-
createSession(
|
|
22
|
+
createSession(taskId: string, shellPreference?: ShellType, startupCommand?: string): Promise<TerminalSession>;
|
|
25
23
|
getSession(sessionId: string): TerminalSession | undefined;
|
|
26
|
-
|
|
24
|
+
getSessionsByTask(taskId: string): TerminalSession[];
|
|
27
25
|
attachWebSocket(sessionId: string, ws: WebSocket): boolean;
|
|
28
26
|
destroySession(sessionId: string): boolean;
|
|
29
27
|
resizeSession(sessionId: string, cols: number, rows: number): boolean;
|
|
@@ -16,8 +16,8 @@ export class TerminalManager {
|
|
|
16
16
|
this.cleanupInactiveSessions();
|
|
17
17
|
}, 5 * 60 * 1000);
|
|
18
18
|
}
|
|
19
|
-
async createSession(
|
|
20
|
-
const sessionId = `${
|
|
19
|
+
async createSession(taskId, shellPreference = 'bash', startupCommand) {
|
|
20
|
+
const sessionId = `${taskId}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
21
21
|
// Get the project root directory
|
|
22
22
|
let projectRoot;
|
|
23
23
|
if (process.env.NODE_ENV === 'development' && process.env.GAIT_DEV_ROOT) {
|
|
@@ -32,8 +32,8 @@ export class TerminalManager {
|
|
|
32
32
|
}
|
|
33
33
|
// Prepare context information
|
|
34
34
|
const contextInfo = {
|
|
35
|
-
proposalId,
|
|
36
|
-
proposalPath: path.join(projectRoot, '.nut', '
|
|
35
|
+
proposalId: taskId,
|
|
36
|
+
proposalPath: path.join(projectRoot, '.nut', 'tasks', `${taskId}.md`),
|
|
37
37
|
contextPath: path.join(projectRoot, '.nut', 'context'),
|
|
38
38
|
projectRoot,
|
|
39
39
|
};
|
|
@@ -65,30 +65,21 @@ export class TerminalManager {
|
|
|
65
65
|
catch (error) {
|
|
66
66
|
console.error('Error creating init script:', error);
|
|
67
67
|
}
|
|
68
|
-
// Set up logging if enabled
|
|
69
|
-
let logFilePath;
|
|
70
|
-
if (enableLogging) {
|
|
71
|
-
const sessionsDir = path.join(projectRoot, '.nut', 'sessions');
|
|
72
|
-
try {
|
|
73
|
-
fs.mkdirSync(sessionsDir, { recursive: true });
|
|
74
|
-
logFilePath = path.join(sessionsDir, `${sessionId}.log`);
|
|
75
|
-
// Create log file with session header
|
|
76
|
-
const logHeader = `# Terminal Session Log\n# Session ID: ${sessionId}\n# Proposal ID: ${proposalId}\n# Created: ${new Date().toISOString()}\n\n`;
|
|
77
|
-
fs.writeFileSync(logFilePath, logHeader);
|
|
78
|
-
}
|
|
79
|
-
catch (error) {
|
|
80
|
-
console.error('Error setting up session logging:', error);
|
|
81
|
-
logFilePath = undefined;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
68
|
// Get shell arguments
|
|
85
69
|
const shellArgs = getShellArgs(shellPath, initScriptPath);
|
|
86
|
-
// Prepare environment variables with API keys injected
|
|
70
|
+
// Prepare environment variables with API keys injected.
|
|
71
|
+
// Remove internal dev-server env vars (GAIT_DEV_ROOT, NODE_ENV=development)
|
|
72
|
+
// so that tools like `nut` invoked inside the terminal resolve paths
|
|
73
|
+
// relative to the terminal's cwd (projectRoot) rather than the API package.
|
|
74
|
+
const injected = getInjectedEnv();
|
|
75
|
+
delete injected.GAIT_DEV_ROOT;
|
|
76
|
+
delete injected.NODE_ENV;
|
|
87
77
|
const env = {
|
|
88
|
-
...
|
|
89
|
-
|
|
78
|
+
...injected,
|
|
79
|
+
COCONUT_TASK_ID: taskId,
|
|
90
80
|
COCONUT_CONTEXT_PATH: path.join(projectRoot, '.nut', 'context'),
|
|
91
|
-
|
|
81
|
+
COCONUT_TASK_PATH: path.join(projectRoot, '.nut', 'tasks', `${taskId}.md`),
|
|
82
|
+
GAIT_DATA_PATH: projectRoot,
|
|
92
83
|
TERM: 'xterm-256color',
|
|
93
84
|
COLORTERM: 'truecolor',
|
|
94
85
|
};
|
|
@@ -109,12 +100,10 @@ export class TerminalManager {
|
|
|
109
100
|
});
|
|
110
101
|
const session = {
|
|
111
102
|
id: sessionId,
|
|
112
|
-
|
|
103
|
+
taskId,
|
|
113
104
|
pty: ptyProcess,
|
|
114
105
|
createdAt: new Date(),
|
|
115
106
|
lastActivity: new Date(),
|
|
116
|
-
enableLogging,
|
|
117
|
-
logFilePath,
|
|
118
107
|
backlog: '',
|
|
119
108
|
previewSockets: new Set(),
|
|
120
109
|
previewBuffer: '',
|
|
@@ -133,7 +122,7 @@ export class TerminalManager {
|
|
|
133
122
|
tags: ["agent", "terminal", "session"],
|
|
134
123
|
payload: {
|
|
135
124
|
sessionId,
|
|
136
|
-
|
|
125
|
+
taskId,
|
|
137
126
|
cwd: projectRoot,
|
|
138
127
|
shell: shellPath,
|
|
139
128
|
}
|
|
@@ -146,17 +135,6 @@ export class TerminalManager {
|
|
|
146
135
|
// Set up PTY event handlers
|
|
147
136
|
ptyProcess.onData((data) => {
|
|
148
137
|
session.lastActivity = new Date();
|
|
149
|
-
// Log data if logging is enabled
|
|
150
|
-
if (session.enableLogging && session.logFilePath) {
|
|
151
|
-
try {
|
|
152
|
-
const timestamp = new Date().toISOString();
|
|
153
|
-
const logEntry = `[${timestamp}] OUTPUT: ${data}`;
|
|
154
|
-
fs.appendFileSync(session.logFilePath, logEntry);
|
|
155
|
-
}
|
|
156
|
-
catch (error) {
|
|
157
|
-
console.error('Error writing to session log:', error);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
138
|
// Maintain in-memory backlog (trim to last MAX_BACKLOG_BYTES)
|
|
161
139
|
try {
|
|
162
140
|
// Append and trim to max size
|
|
@@ -180,17 +158,6 @@ export class TerminalManager {
|
|
|
180
158
|
ptyProcess.onExit((e) => {
|
|
181
159
|
const endTime = new Date();
|
|
182
160
|
const duration = endTime.getTime() - session.createdAt.getTime();
|
|
183
|
-
// Log session end if logging is enabled
|
|
184
|
-
if (session.enableLogging && session.logFilePath) {
|
|
185
|
-
try {
|
|
186
|
-
const timestamp = endTime.toISOString();
|
|
187
|
-
const logEntry = `\n[${timestamp}] SESSION ENDED: Exit code ${e.exitCode}\n`;
|
|
188
|
-
fs.appendFileSync(session.logFilePath, logEntry);
|
|
189
|
-
}
|
|
190
|
-
catch (error) {
|
|
191
|
-
console.error('Error writing session end to log:', error);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
161
|
// Log session end to activity log
|
|
195
162
|
try {
|
|
196
163
|
const logger = getLogger();
|
|
@@ -242,8 +209,8 @@ export class TerminalManager {
|
|
|
242
209
|
getSession(sessionId) {
|
|
243
210
|
return this.sessions.get(sessionId);
|
|
244
211
|
}
|
|
245
|
-
|
|
246
|
-
return Array.from(this.sessions.values()).filter(session => session.
|
|
212
|
+
getSessionsByTask(taskId) {
|
|
213
|
+
return Array.from(this.sessions.values()).filter(session => session.taskId === taskId);
|
|
247
214
|
}
|
|
248
215
|
attachWebSocket(sessionId, ws) {
|
|
249
216
|
const session = this.sessions.get(sessionId);
|
|
@@ -282,17 +249,6 @@ export class TerminalManager {
|
|
|
282
249
|
const data = JSON.parse(message.toString());
|
|
283
250
|
switch (data.type) {
|
|
284
251
|
case 'input':
|
|
285
|
-
// Log input if logging is enabled
|
|
286
|
-
if (session.enableLogging && session.logFilePath) {
|
|
287
|
-
try {
|
|
288
|
-
const timestamp = new Date().toISOString();
|
|
289
|
-
const logEntry = `[${timestamp}] INPUT: ${data.data}`;
|
|
290
|
-
fs.appendFileSync(session.logFilePath, logEntry);
|
|
291
|
-
}
|
|
292
|
-
catch (error) {
|
|
293
|
-
console.error('Error writing input to session log:', error);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
252
|
session.pty.write(data.data);
|
|
297
253
|
session.lastActivity = new Date();
|
|
298
254
|
break;
|
package/dist/middleware/auth.js
CHANGED
|
@@ -1,11 +1,36 @@
|
|
|
1
1
|
import { getCookie } from 'hono/cookie';
|
|
2
|
+
import { getConnInfo } from '@hono/node-server/conninfo';
|
|
2
3
|
import { getAuthManager } from '../lib/auth/auth-manager.js';
|
|
4
|
+
const LOOPBACK_ADDRESSES = new Set(['127.0.0.1', '::1', '::ffff:127.0.0.1']);
|
|
5
|
+
/**
|
|
6
|
+
* Check if a request is a direct localhost connection (not proxied).
|
|
7
|
+
* Used to allow CLI tools running on the server to bypass auth.
|
|
8
|
+
*
|
|
9
|
+
* Safe because:
|
|
10
|
+
* - Raw socket address cannot be spoofed (TCP guarantees this)
|
|
11
|
+
* - Proxied requests (nginx/ALB → node) include X-Forwarded-For, so they
|
|
12
|
+
* are excluded even though the socket address is loopback
|
|
13
|
+
*/
|
|
14
|
+
function isDirectLocalConnection(c) {
|
|
15
|
+
try {
|
|
16
|
+
// If X-Forwarded-For is present, the request came through a proxy
|
|
17
|
+
if (c.req.header('x-forwarded-for')) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const info = getConnInfo(c);
|
|
21
|
+
return LOOPBACK_ADDRESSES.has(info.remote.address || '');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
3
27
|
// Public routes that don't require authentication
|
|
4
28
|
const PUBLIC_ROUTES = [
|
|
5
29
|
'/api/health',
|
|
6
30
|
'/api/v1/auth/status',
|
|
7
31
|
'/api/v1/auth/login',
|
|
8
32
|
'/api/v1/auth/register',
|
|
33
|
+
'/api/v1/auth/logout',
|
|
9
34
|
'/api/v1/auth/oauth',
|
|
10
35
|
'/api/v1/auth/callback',
|
|
11
36
|
];
|
|
@@ -40,6 +65,13 @@ export async function authMiddleware(c, next) {
|
|
|
40
65
|
// Auth is disabled, allow all requests
|
|
41
66
|
return next();
|
|
42
67
|
}
|
|
68
|
+
// Allow direct localhost connections (CLI on the same machine) to bypass auth.
|
|
69
|
+
// This is safe: the raw TCP socket address can't be spoofed, and proxied
|
|
70
|
+
// requests are excluded by checking for the X-Forwarded-For header.
|
|
71
|
+
if (isDirectLocalConnection(c)) {
|
|
72
|
+
c.set('authType', 'localhost');
|
|
73
|
+
return next();
|
|
74
|
+
}
|
|
43
75
|
const path = c.req.path;
|
|
44
76
|
// Allow public routes without authentication
|
|
45
77
|
if (isPublicRoute(path)) {
|
|
@@ -129,6 +161,10 @@ export function requireAuth(c) {
|
|
|
129
161
|
// Return null to indicate auth passed but no session available
|
|
130
162
|
return null;
|
|
131
163
|
}
|
|
164
|
+
// Direct localhost connections bypass auth (CLI on the same machine)
|
|
165
|
+
if (authType === 'localhost') {
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
132
168
|
throw new Error('Authentication required');
|
|
133
169
|
}
|
|
134
170
|
/**
|