@lovelybunch/api 1.0.7
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/gait-path.d.ts +13 -0
- package/dist/lib/gait-path.js +57 -0
- package/dist/lib/project-paths.d.ts +13 -0
- package/dist/lib/project-paths.js +57 -0
- package/dist/lib/storage/file-storage.d.ts +28 -0
- package/dist/lib/storage/file-storage.js +224 -0
- package/dist/lib/symlinks/symlink-manager.d.ts +66 -0
- package/dist/lib/symlinks/symlink-manager.js +444 -0
- package/dist/lib/symlinks/types.d.ts +23 -0
- package/dist/lib/symlinks/types.js +4 -0
- package/dist/lib/terminal/context-helper.d.ts +11 -0
- package/dist/lib/terminal/context-helper.js +164 -0
- package/dist/lib/terminal/global-manager.d.ts +2 -0
- package/dist/lib/terminal/global-manager.js +15 -0
- package/dist/lib/terminal/shell-utils.d.ts +33 -0
- package/dist/lib/terminal/shell-utils.js +176 -0
- package/dist/lib/terminal/terminal-manager.d.ts +26 -0
- package/dist/lib/terminal/terminal-manager.js +276 -0
- package/dist/lib/user-preferences.d.ts +48 -0
- package/dist/lib/user-preferences.js +87 -0
- package/dist/lib/utils.d.ts +2 -0
- package/dist/lib/utils.js +5 -0
- package/dist/routes/api/symlink-status/route.d.ts +1 -0
- package/dist/routes/api/symlink-status/route.js +37 -0
- package/dist/routes/api/symlinks/[id]/route.d.ts +19 -0
- package/dist/routes/api/symlinks/[id]/route.js +95 -0
- package/dist/routes/api/symlinks/[id]/toggle/route.d.ts +11 -0
- package/dist/routes/api/symlinks/[id]/toggle/route.js +32 -0
- package/dist/routes/api/symlinks/debug/route.d.ts +1 -0
- package/dist/routes/api/symlinks/debug/route.js +35 -0
- package/dist/routes/api/symlinks/route.d.ts +9 -0
- package/dist/routes/api/symlinks/route.js +72 -0
- package/dist/routes/api/toggle-symlink/route.d.ts +2 -0
- package/dist/routes/api/toggle-symlink/route.js +94 -0
- package/dist/routes/api/v1/agents/[id]/index.d.ts +1 -0
- package/dist/routes/api/v1/agents/[id]/index.js +1 -0
- package/dist/routes/api/v1/agents/[id]/route.d.ts +3 -0
- package/dist/routes/api/v1/agents/[id]/route.js +163 -0
- package/dist/routes/api/v1/agents/index.d.ts +1 -0
- package/dist/routes/api/v1/agents/index.js +1 -0
- package/dist/routes/api/v1/agents/route.d.ts +3 -0
- package/dist/routes/api/v1/agents/route.js +133 -0
- package/dist/routes/api/v1/ai/index.d.ts +3 -0
- package/dist/routes/api/v1/ai/index.js +5 -0
- package/dist/routes/api/v1/ai/route.d.ts +8 -0
- package/dist/routes/api/v1/ai/route.js +86 -0
- package/dist/routes/api/v1/chats/[id]/index.d.ts +3 -0
- package/dist/routes/api/v1/chats/[id]/index.js +6 -0
- package/dist/routes/api/v1/chats/[id]/route.d.ts +12 -0
- package/dist/routes/api/v1/chats/[id]/route.js +31 -0
- package/dist/routes/api/v1/chats/index.d.ts +3 -0
- package/dist/routes/api/v1/chats/index.js +6 -0
- package/dist/routes/api/v1/chats/route.d.ts +32 -0
- package/dist/routes/api/v1/chats/route.js +67 -0
- package/dist/routes/api/v1/config/index.d.ts +3 -0
- package/dist/routes/api/v1/config/index.js +5 -0
- package/dist/routes/api/v1/config/route.d.ts +9 -0
- package/dist/routes/api/v1/config/route.js +29 -0
- package/dist/routes/api/v1/context/[...path]/route.d.ts +16 -0
- package/dist/routes/api/v1/context/[...path]/route.js +107 -0
- package/dist/routes/api/v1/context/architecture/route.d.ts +3 -0
- package/dist/routes/api/v1/context/architecture/route.js +198 -0
- package/dist/routes/api/v1/context/index.d.ts +3 -0
- package/dist/routes/api/v1/context/index.js +9 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/index.d.ts +1 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/index.js +1 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/route.d.ts +3 -0
- package/dist/routes/api/v1/context/knowledge/[filename]/route.js +165 -0
- package/dist/routes/api/v1/context/knowledge/index.d.ts +1 -0
- package/dist/routes/api/v1/context/knowledge/index.js +1 -0
- package/dist/routes/api/v1/context/knowledge/route.d.ts +3 -0
- package/dist/routes/api/v1/context/knowledge/route.js +121 -0
- package/dist/routes/api/v1/context/project/route.d.ts +3 -0
- package/dist/routes/api/v1/context/project/route.js +153 -0
- package/dist/routes/api/v1/proposals/[id]/route.d.ts +337 -0
- package/dist/routes/api/v1/proposals/[id]/route.js +99 -0
- package/dist/routes/api/v1/proposals/index.d.ts +3 -0
- package/dist/routes/api/v1/proposals/index.js +10 -0
- package/dist/routes/api/v1/proposals/route.d.ts +315 -0
- package/dist/routes/api/v1/proposals/route.js +103 -0
- package/dist/routes/api/v1/resources/[id]/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/[id]/index.js +7 -0
- package/dist/routes/api/v1/resources/[id]/route.d.ts +46 -0
- package/dist/routes/api/v1/resources/[id]/route.js +143 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/index.js +5 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/route.d.ts +2 -0
- package/dist/routes/api/v1/resources/[id]/thumbnail/route.js +50 -0
- package/dist/routes/api/v1/resources/index.d.ts +3 -0
- package/dist/routes/api/v1/resources/index.js +6 -0
- package/dist/routes/api/v1/resources/route.d.ts +51 -0
- package/dist/routes/api/v1/resources/route.js +147 -0
- package/dist/routes/api/v1/search/route.d.ts +3 -0
- package/dist/routes/api/v1/search/route.js +39 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/index.js +5 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +27 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.js +5 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.js +21 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/index.js +5 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/route.d.ts +10 -0
- package/dist/routes/api/v1/terminal/[proposalId]/resize/route.js +21 -0
- package/dist/routes/api/v1/terminal/sessions/index.d.ts +3 -0
- package/dist/routes/api/v1/terminal/sessions/index.js +5 -0
- package/dist/routes/api/v1/terminal/sessions/route.d.ts +6 -0
- package/dist/routes/api/v1/terminal/sessions/route.js +29 -0
- package/dist/routes/api/v1/user/index.d.ts +3 -0
- package/dist/routes/api/v1/user/index.js +5 -0
- package/dist/routes/api/v1/user/preferences/route.d.ts +11 -0
- package/dist/routes/api/v1/user/preferences/route.js +31 -0
- package/dist/routes/api/v1/user/profile/route.d.ts +11 -0
- package/dist/routes/api/v1/user/profile/route.js +31 -0
- package/dist/routes/api/v1/user/settings/index.d.ts +1 -0
- package/dist/routes/api/v1/user/settings/index.js +1 -0
- package/dist/routes/api/v1/user/settings/route.d.ts +3 -0
- package/dist/routes/api/v1/user/settings/route.js +51 -0
- package/dist/server-with-static.d.ts +4 -0
- package/dist/server-with-static.js +144 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +91 -0
- package/package.json +42 -0
- package/static/assets/index-BvTnrm0O.js +576 -0
- package/static/assets/index-Cm5dZHTl.css +33 -0
- package/static/assets/index-ORkAkJNi.js +576 -0
- package/static/assets/index-_Keadpms.js +576 -0
- package/static/index.html +17 -0
- package/static/vite.svg +1 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import matter from 'gray-matter';
|
|
5
|
+
const app = new Hono();
|
|
6
|
+
function getAgentsPath() {
|
|
7
|
+
const basePath = process.env.GAIT_DATA_PATH ?
|
|
8
|
+
path.resolve(process.env.GAIT_DATA_PATH, '.gait') :
|
|
9
|
+
path.resolve(process.cwd(), '.gait');
|
|
10
|
+
return path.join(basePath, 'agents');
|
|
11
|
+
}
|
|
12
|
+
function generateFilename(name) {
|
|
13
|
+
// Convert name to filename-safe format
|
|
14
|
+
return name
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9\s-]/g, '') // Remove special characters
|
|
17
|
+
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
|
18
|
+
.replace(/--+/g, '-') // Replace multiple hyphens with single
|
|
19
|
+
.replace(/^-|-$/g, '') // Remove leading/trailing hyphens
|
|
20
|
+
+ '.md';
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* GET /api/v1/agents
|
|
24
|
+
* Load all agent documents
|
|
25
|
+
*/
|
|
26
|
+
app.get('/', async (c) => {
|
|
27
|
+
try {
|
|
28
|
+
const agentsPath = getAgentsPath();
|
|
29
|
+
// Ensure directory exists
|
|
30
|
+
await fs.mkdir(agentsPath, { recursive: true });
|
|
31
|
+
const files = await fs.readdir(agentsPath);
|
|
32
|
+
const agents = await Promise.all(files
|
|
33
|
+
.filter(file => file.endsWith('.md'))
|
|
34
|
+
.map(async (file) => {
|
|
35
|
+
const filePath = path.join(agentsPath, file);
|
|
36
|
+
const fileContent = await fs.readFile(filePath, 'utf-8');
|
|
37
|
+
try {
|
|
38
|
+
const { data, content } = matter(fileContent);
|
|
39
|
+
// Extract title from name in frontmatter or use filename
|
|
40
|
+
const title = data.name ||
|
|
41
|
+
file.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase());
|
|
42
|
+
return {
|
|
43
|
+
filename: file,
|
|
44
|
+
metadata: {
|
|
45
|
+
name: data.name || title,
|
|
46
|
+
description: data.description || '',
|
|
47
|
+
color: data.color,
|
|
48
|
+
tools: data.tools,
|
|
49
|
+
...data
|
|
50
|
+
},
|
|
51
|
+
content,
|
|
52
|
+
title
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
console.warn(`Error parsing agent ${file}:`, error instanceof Error ? error.message : String(error));
|
|
57
|
+
// Return a basic structure for files that can't be parsed
|
|
58
|
+
const title = file.replace('.md', '').replace(/[_-]/g, ' ').replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substring(1).toLowerCase());
|
|
59
|
+
return {
|
|
60
|
+
filename: file,
|
|
61
|
+
metadata: {
|
|
62
|
+
name: title,
|
|
63
|
+
description: 'Error parsing agent metadata',
|
|
64
|
+
color: 'red'
|
|
65
|
+
},
|
|
66
|
+
content: fileContent,
|
|
67
|
+
title
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}));
|
|
71
|
+
return c.json({
|
|
72
|
+
success: true,
|
|
73
|
+
documents: agents.sort((a, b) => a.filename.localeCompare(b.filename))
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error('Error loading agent documents:', error);
|
|
78
|
+
return c.json({
|
|
79
|
+
success: false,
|
|
80
|
+
error: 'Failed to load agent documents',
|
|
81
|
+
documents: []
|
|
82
|
+
}, 500);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* POST /api/v1/agents
|
|
87
|
+
* Create a new agent document
|
|
88
|
+
*/
|
|
89
|
+
app.post('/', async (c) => {
|
|
90
|
+
try {
|
|
91
|
+
const body = await c.req.json();
|
|
92
|
+
if (!body.name || !body.description) {
|
|
93
|
+
return c.json({ success: false, error: 'Name and description are required' }, 400);
|
|
94
|
+
}
|
|
95
|
+
const agentsPath = getAgentsPath();
|
|
96
|
+
await fs.mkdir(agentsPath, { recursive: true });
|
|
97
|
+
const filename = generateFilename(body.name);
|
|
98
|
+
const filePath = path.join(agentsPath, filename);
|
|
99
|
+
// Check if file already exists
|
|
100
|
+
try {
|
|
101
|
+
await fs.access(filePath);
|
|
102
|
+
return c.json({ success: false, error: 'An agent with this name already exists' }, 409);
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
// File doesn't exist, which is what we want
|
|
106
|
+
}
|
|
107
|
+
// Prepare frontmatter - filter out undefined values
|
|
108
|
+
const frontmatter = Object.fromEntries(Object.entries({
|
|
109
|
+
name: body.name,
|
|
110
|
+
description: body.description,
|
|
111
|
+
color: body.metadata?.color || 'blue',
|
|
112
|
+
tools: body.metadata?.tools,
|
|
113
|
+
...body.metadata
|
|
114
|
+
}).filter(([_, value]) => value !== undefined));
|
|
115
|
+
// Create the markdown content with frontmatter
|
|
116
|
+
const fileContent = matter.stringify(body.content || '', frontmatter);
|
|
117
|
+
await fs.writeFile(filePath, fileContent, 'utf-8');
|
|
118
|
+
return c.json({
|
|
119
|
+
success: true,
|
|
120
|
+
document: {
|
|
121
|
+
filename,
|
|
122
|
+
title: body.name,
|
|
123
|
+
metadata: frontmatter,
|
|
124
|
+
content: body.content
|
|
125
|
+
}
|
|
126
|
+
}, 201);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
console.error('Error creating agent document:', error);
|
|
130
|
+
return c.json({ success: false, error: 'Failed to create agent document' }, 500);
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
export default app;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
error: string;
|
|
4
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
|
+
error: string;
|
|
6
|
+
}, 500, "json">) | (Response & import("hono").TypedResponse<{
|
|
7
|
+
response: any;
|
|
8
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export async function POST(c) {
|
|
2
|
+
try {
|
|
3
|
+
const { message, model, context, contextContent } = await c.req.json();
|
|
4
|
+
if (!message) {
|
|
5
|
+
return c.json({ error: "Message is required" }, 400);
|
|
6
|
+
}
|
|
7
|
+
const openRouterKey = process.env.OPENROUTER_API_KEY;
|
|
8
|
+
if (!openRouterKey) {
|
|
9
|
+
return c.json({ error: "OpenRouter API key not configured" }, 500);
|
|
10
|
+
}
|
|
11
|
+
const systemPrompt = getSystemPrompt(context, contextContent);
|
|
12
|
+
const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
|
|
13
|
+
method: "POST",
|
|
14
|
+
headers: {
|
|
15
|
+
"Authorization": `Bearer ${openRouterKey}`,
|
|
16
|
+
"Content-Type": "application/json",
|
|
17
|
+
"HTTP-Referer": process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3001",
|
|
18
|
+
"X-Title": "GAIT AI Assistant",
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({
|
|
21
|
+
model: model || "anthropic/claude-3-sonnet",
|
|
22
|
+
messages: [
|
|
23
|
+
{
|
|
24
|
+
role: "system",
|
|
25
|
+
content: systemPrompt,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
role: "user",
|
|
29
|
+
content: message,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
temperature: 0.7,
|
|
33
|
+
max_tokens: 1000,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
const error = await response.text();
|
|
38
|
+
console.error("OpenRouter API error:", error);
|
|
39
|
+
return c.json({ error: "Failed to get AI response" }, 500);
|
|
40
|
+
}
|
|
41
|
+
const data = await response.json();
|
|
42
|
+
const aiResponse = data.choices?.[0]?.message?.content;
|
|
43
|
+
if (!aiResponse) {
|
|
44
|
+
return c.json({ error: "No response from AI model" }, 500);
|
|
45
|
+
}
|
|
46
|
+
return c.json({ response: aiResponse });
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error("AI API error:", error);
|
|
50
|
+
return c.json({ error: "Internal server error" }, 500);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function getSystemPrompt(context, contextContent) {
|
|
54
|
+
const basePrompt = `You are an AI assistant integrated into GAIT (Git-Adjacent Intent Tracking), a source control management system for agent-native development. You help developers with proposals, architecture decisions, and project planning.`;
|
|
55
|
+
switch (context) {
|
|
56
|
+
case "proposals":
|
|
57
|
+
case "proposals-edit":
|
|
58
|
+
case "proposals-view":
|
|
59
|
+
let proposalPrompt = `${basePrompt} You are currently in the proposals section. Help users:
|
|
60
|
+
- Refine and improve their change proposals
|
|
61
|
+
- Suggest implementation approaches
|
|
62
|
+
- Identify potential challenges or risks
|
|
63
|
+
- Break down complex changes into manageable steps
|
|
64
|
+
- Provide feedback on proposal clarity and completeness`;
|
|
65
|
+
if (contextContent) {
|
|
66
|
+
proposalPrompt += `\n\nYou have access to the following proposal data:\n\n${contextContent}`;
|
|
67
|
+
}
|
|
68
|
+
return proposalPrompt;
|
|
69
|
+
case "context":
|
|
70
|
+
return `${basePrompt} You are currently in the context section (architecture, decisions, knowledge). Help users:
|
|
71
|
+
- Analyze architectural decisions and patterns
|
|
72
|
+
- Suggest improvements to documentation
|
|
73
|
+
- Identify gaps in knowledge or decisions
|
|
74
|
+
- Recommend best practices
|
|
75
|
+
- Help maintain consistency across the project`;
|
|
76
|
+
case "settings":
|
|
77
|
+
return `${basePrompt} You are currently in the settings section. Help users:
|
|
78
|
+
- Configure their GAIT environment
|
|
79
|
+
- Set up integrations and workflows
|
|
80
|
+
- Optimize their development process
|
|
81
|
+
- Understand configuration options
|
|
82
|
+
- Troubleshoot setup issues`;
|
|
83
|
+
default:
|
|
84
|
+
return `${basePrompt} Provide helpful, contextual assistance based on the user's current needs in their GAIT workflow.`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
chat: any;
|
|
4
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
|
+
error: string;
|
|
6
|
+
}, 404, "json">)>;
|
|
7
|
+
export declare function DELETE(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
8
|
+
success: true;
|
|
9
|
+
message: string;
|
|
10
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
11
|
+
error: string;
|
|
12
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readFile, unlink } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const CHATS_DIR = join(process.cwd(), ".gait", "chats");
|
|
4
|
+
export async function GET(c) {
|
|
5
|
+
try {
|
|
6
|
+
const id = c.req.param('id');
|
|
7
|
+
const filePath = join(CHATS_DIR, `${id}.json`);
|
|
8
|
+
const content = await readFile(filePath, 'utf8');
|
|
9
|
+
const chat = JSON.parse(content);
|
|
10
|
+
return c.json({ chat });
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
console.error("Error reading chat:", error);
|
|
14
|
+
return c.json({ error: "Chat not found" }, 404);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export async function DELETE(c) {
|
|
18
|
+
try {
|
|
19
|
+
const id = c.req.param('id');
|
|
20
|
+
const filePath = join(CHATS_DIR, `${id}.json`);
|
|
21
|
+
await unlink(filePath);
|
|
22
|
+
return c.json({
|
|
23
|
+
success: true,
|
|
24
|
+
message: "Chat deleted successfully"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
console.error("Error deleting chat:", error);
|
|
29
|
+
return c.json({ error: "Failed to delete chat" }, 500);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
chats: {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
messages: {
|
|
7
|
+
role: "user" | "assistant" | "system";
|
|
8
|
+
content: string;
|
|
9
|
+
timestamp: string;
|
|
10
|
+
}[];
|
|
11
|
+
model: string;
|
|
12
|
+
context?: {
|
|
13
|
+
type: string;
|
|
14
|
+
content: string | null;
|
|
15
|
+
displayId?: string;
|
|
16
|
+
contextFiles?: string;
|
|
17
|
+
};
|
|
18
|
+
createdAt: string;
|
|
19
|
+
updatedAt: string;
|
|
20
|
+
}[];
|
|
21
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
22
|
+
error: string;
|
|
23
|
+
}, 500, "json">)>;
|
|
24
|
+
export declare function POST(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
25
|
+
error: string;
|
|
26
|
+
}, 400, "json">) | (Response & import("hono").TypedResponse<{
|
|
27
|
+
success: true;
|
|
28
|
+
chatId: string;
|
|
29
|
+
message: string;
|
|
30
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
31
|
+
error: string;
|
|
32
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { writeFile, readdir, readFile, mkdir } from "fs/promises";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
const CHATS_DIR = join(process.cwd(), ".gait", "chats");
|
|
4
|
+
export async function GET(c) {
|
|
5
|
+
try {
|
|
6
|
+
// Ensure chats directory exists
|
|
7
|
+
await mkdir(CHATS_DIR, { recursive: true });
|
|
8
|
+
const files = await readdir(CHATS_DIR);
|
|
9
|
+
const chatFiles = files.filter(f => f.endsWith('.json'));
|
|
10
|
+
const chats = await Promise.all(chatFiles.map(async (file) => {
|
|
11
|
+
const content = await readFile(join(CHATS_DIR, file), 'utf8');
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
}));
|
|
14
|
+
// Sort by updatedAt descending
|
|
15
|
+
chats.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
16
|
+
return c.json({ chats });
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
console.error("Error reading chats:", error);
|
|
20
|
+
return c.json({ error: "Failed to read chats" }, 500);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export async function POST(c) {
|
|
24
|
+
try {
|
|
25
|
+
const body = await c.req.json();
|
|
26
|
+
const { messages, model, context, title } = body;
|
|
27
|
+
if (!messages || !Array.isArray(messages)) {
|
|
28
|
+
return c.json({ error: "Messages array is required" }, 400);
|
|
29
|
+
}
|
|
30
|
+
// Ensure chats directory exists
|
|
31
|
+
await mkdir(CHATS_DIR, { recursive: true });
|
|
32
|
+
const chatId = `chat-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
const chat = {
|
|
35
|
+
id: chatId,
|
|
36
|
+
title: title || generateChatTitle(messages),
|
|
37
|
+
messages: messages.map((msg) => ({
|
|
38
|
+
...msg,
|
|
39
|
+
timestamp: msg.timestamp || now
|
|
40
|
+
})),
|
|
41
|
+
model: model || "anthropic/claude-sonnet-4",
|
|
42
|
+
context,
|
|
43
|
+
createdAt: now,
|
|
44
|
+
updatedAt: now
|
|
45
|
+
};
|
|
46
|
+
await writeFile(join(CHATS_DIR, `${chatId}.json`), JSON.stringify(chat, null, 2));
|
|
47
|
+
return c.json({
|
|
48
|
+
success: true,
|
|
49
|
+
chatId,
|
|
50
|
+
message: "Chat saved successfully"
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
console.error("Error saving chat:", error);
|
|
55
|
+
return c.json({ error: "Failed to save chat" }, 500);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function generateChatTitle(messages) {
|
|
59
|
+
// Find the first user message
|
|
60
|
+
const firstUserMessage = messages.find(msg => msg.role === "user");
|
|
61
|
+
if (firstUserMessage) {
|
|
62
|
+
// Truncate to first 50 characters and add ellipsis if needed
|
|
63
|
+
const content = firstUserMessage.content.trim();
|
|
64
|
+
return content.length > 50 ? content.substring(0, 50) + "..." : content;
|
|
65
|
+
}
|
|
66
|
+
return "New Chat";
|
|
67
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Context } from 'hono';
|
|
2
|
+
export declare function GET(c: Context): Promise<(Response & import("hono").TypedResponse<{
|
|
3
|
+
error: string;
|
|
4
|
+
}, 404, "json">) | (Response & import("hono").TypedResponse<{
|
|
5
|
+
success: true;
|
|
6
|
+
data: any;
|
|
7
|
+
}, import("hono/utils/http-status").ContentfulStatusCode, "json">) | (Response & import("hono").TypedResponse<{
|
|
8
|
+
error: string;
|
|
9
|
+
}, 500, "json">)>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
async function getConfigPath() {
|
|
4
|
+
// Use the same approach as FileStorageAdapter for consistency
|
|
5
|
+
const basePath = process.env.GAIT_DATA_PATH ?
|
|
6
|
+
path.resolve(process.env.GAIT_DATA_PATH, '.gait') :
|
|
7
|
+
path.resolve(process.cwd(), '.gait');
|
|
8
|
+
return path.join(basePath, 'config.json');
|
|
9
|
+
}
|
|
10
|
+
export async function GET(c) {
|
|
11
|
+
try {
|
|
12
|
+
const configPath = await getConfigPath();
|
|
13
|
+
if (!configPath) {
|
|
14
|
+
return c.json({ error: 'GAIT directory not found' }, 404);
|
|
15
|
+
}
|
|
16
|
+
const content = await fs.readFile(configPath, 'utf-8');
|
|
17
|
+
const config = JSON.parse(content);
|
|
18
|
+
return c.json({
|
|
19
|
+
success: true,
|
|
20
|
+
data: config
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
if (error.code === 'ENOENT') {
|
|
25
|
+
return c.json({ error: 'Config file not found' }, 404);
|
|
26
|
+
}
|
|
27
|
+
return c.json({ error: 'Failed to read config' }, 500);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
export declare function GET(request: NextRequest, { params }: {
|
|
3
|
+
params: {
|
|
4
|
+
path: string[];
|
|
5
|
+
};
|
|
6
|
+
}): Promise<any>;
|
|
7
|
+
export declare function PUT(request: NextRequest, { params }: {
|
|
8
|
+
params: {
|
|
9
|
+
path: string[];
|
|
10
|
+
};
|
|
11
|
+
}): Promise<any>;
|
|
12
|
+
export declare function DELETE(request: NextRequest, { params }: {
|
|
13
|
+
params: {
|
|
14
|
+
path: string[];
|
|
15
|
+
};
|
|
16
|
+
}): Promise<any>;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { NextResponse } from 'next/server';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
async function getContextPath(relativePath) {
|
|
5
|
+
// Use the same approach as FileStorageAdapter for consistency
|
|
6
|
+
const basePath = process.env.GAIT_DATA_PATH ?
|
|
7
|
+
path.resolve(process.env.GAIT_DATA_PATH, '.gait') :
|
|
8
|
+
path.resolve(process.cwd(), '.gait');
|
|
9
|
+
const contextDir = path.join(basePath, 'context');
|
|
10
|
+
const contextPath = path.join(contextDir, ...relativePath);
|
|
11
|
+
return { contextPath, contextDir };
|
|
12
|
+
}
|
|
13
|
+
export async function GET(request, { params }) {
|
|
14
|
+
try {
|
|
15
|
+
const pathInfo = await getContextPath(params.path);
|
|
16
|
+
if (!pathInfo) {
|
|
17
|
+
return NextResponse.json({ error: 'GAIT directory not found' }, { status: 404 });
|
|
18
|
+
}
|
|
19
|
+
const { contextPath: filePath, contextDir } = pathInfo;
|
|
20
|
+
// Security check - ensure path is within context directory
|
|
21
|
+
if (!filePath.startsWith(contextDir)) {
|
|
22
|
+
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
|
|
23
|
+
}
|
|
24
|
+
// Check if it's a directory or file
|
|
25
|
+
const stat = await fs.stat(filePath);
|
|
26
|
+
if (stat.isDirectory()) {
|
|
27
|
+
// Return directory listing
|
|
28
|
+
const files = await fs.readdir(filePath);
|
|
29
|
+
const fileList = await Promise.all(files.map(async (file) => {
|
|
30
|
+
const fileStats = await fs.stat(path.join(filePath, file));
|
|
31
|
+
return {
|
|
32
|
+
name: file,
|
|
33
|
+
isDirectory: fileStats.isDirectory(),
|
|
34
|
+
size: fileStats.size,
|
|
35
|
+
modified: fileStats.mtime
|
|
36
|
+
};
|
|
37
|
+
}));
|
|
38
|
+
return NextResponse.json({ files: fileList });
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
// Return file content
|
|
42
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
43
|
+
return new NextResponse(content, {
|
|
44
|
+
headers: {
|
|
45
|
+
'Content-Type': 'text/plain; charset=utf-8',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code === 'ENOENT') {
|
|
52
|
+
return NextResponse.json({ error: 'File not found' }, { status: 404 });
|
|
53
|
+
}
|
|
54
|
+
console.error('Error reading context file:', error);
|
|
55
|
+
return NextResponse.json({ error: 'Failed to read context file' }, { status: 500 });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export async function PUT(request, { params }) {
|
|
59
|
+
try {
|
|
60
|
+
const pathInfo = await getContextPath(params.path);
|
|
61
|
+
if (!pathInfo) {
|
|
62
|
+
return NextResponse.json({ error: 'GAIT directory not found' }, { status: 404 });
|
|
63
|
+
}
|
|
64
|
+
const { contextPath: filePath, contextDir } = pathInfo;
|
|
65
|
+
// Security check - ensure path is within context directory
|
|
66
|
+
if (!filePath.startsWith(contextDir)) {
|
|
67
|
+
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
|
|
68
|
+
}
|
|
69
|
+
const content = await request.text();
|
|
70
|
+
// Basic validation for markdown files
|
|
71
|
+
if (filePath.endsWith('.md') && !content.startsWith('---')) {
|
|
72
|
+
return NextResponse.json({ error: 'Markdown files must start with YAML frontmatter (---)' }, { status: 400 });
|
|
73
|
+
}
|
|
74
|
+
// Ensure the directory exists
|
|
75
|
+
const dir = path.dirname(filePath);
|
|
76
|
+
await fs.mkdir(dir, { recursive: true });
|
|
77
|
+
// Write the file
|
|
78
|
+
await fs.writeFile(filePath, content, 'utf-8');
|
|
79
|
+
return NextResponse.json({ success: true });
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
console.error('Error updating context file:', error);
|
|
83
|
+
return NextResponse.json({ error: 'Failed to update context file' }, { status: 500 });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export async function DELETE(request, { params }) {
|
|
87
|
+
try {
|
|
88
|
+
const pathInfo = await getContextPath(params.path);
|
|
89
|
+
if (!pathInfo) {
|
|
90
|
+
return NextResponse.json({ error: 'GAIT directory not found' }, { status: 404 });
|
|
91
|
+
}
|
|
92
|
+
const { contextPath: filePath, contextDir } = pathInfo;
|
|
93
|
+
// Security check - ensure path is within context directory
|
|
94
|
+
if (!filePath.startsWith(contextDir)) {
|
|
95
|
+
return NextResponse.json({ error: 'Access denied' }, { status: 403 });
|
|
96
|
+
}
|
|
97
|
+
await fs.unlink(filePath);
|
|
98
|
+
return NextResponse.json({ success: true });
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error.code === 'ENOENT') {
|
|
102
|
+
return NextResponse.json({ error: 'File not found' }, { status: 404 });
|
|
103
|
+
}
|
|
104
|
+
console.error('Error deleting context file:', error);
|
|
105
|
+
return NextResponse.json({ error: 'Failed to delete context file' }, { status: 500 });
|
|
106
|
+
}
|
|
107
|
+
}
|