@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.
Files changed (131) hide show
  1. package/dist/lib/gait-path.d.ts +13 -0
  2. package/dist/lib/gait-path.js +57 -0
  3. package/dist/lib/project-paths.d.ts +13 -0
  4. package/dist/lib/project-paths.js +57 -0
  5. package/dist/lib/storage/file-storage.d.ts +28 -0
  6. package/dist/lib/storage/file-storage.js +224 -0
  7. package/dist/lib/symlinks/symlink-manager.d.ts +66 -0
  8. package/dist/lib/symlinks/symlink-manager.js +444 -0
  9. package/dist/lib/symlinks/types.d.ts +23 -0
  10. package/dist/lib/symlinks/types.js +4 -0
  11. package/dist/lib/terminal/context-helper.d.ts +11 -0
  12. package/dist/lib/terminal/context-helper.js +164 -0
  13. package/dist/lib/terminal/global-manager.d.ts +2 -0
  14. package/dist/lib/terminal/global-manager.js +15 -0
  15. package/dist/lib/terminal/shell-utils.d.ts +33 -0
  16. package/dist/lib/terminal/shell-utils.js +176 -0
  17. package/dist/lib/terminal/terminal-manager.d.ts +26 -0
  18. package/dist/lib/terminal/terminal-manager.js +276 -0
  19. package/dist/lib/user-preferences.d.ts +48 -0
  20. package/dist/lib/user-preferences.js +87 -0
  21. package/dist/lib/utils.d.ts +2 -0
  22. package/dist/lib/utils.js +5 -0
  23. package/dist/routes/api/symlink-status/route.d.ts +1 -0
  24. package/dist/routes/api/symlink-status/route.js +37 -0
  25. package/dist/routes/api/symlinks/[id]/route.d.ts +19 -0
  26. package/dist/routes/api/symlinks/[id]/route.js +95 -0
  27. package/dist/routes/api/symlinks/[id]/toggle/route.d.ts +11 -0
  28. package/dist/routes/api/symlinks/[id]/toggle/route.js +32 -0
  29. package/dist/routes/api/symlinks/debug/route.d.ts +1 -0
  30. package/dist/routes/api/symlinks/debug/route.js +35 -0
  31. package/dist/routes/api/symlinks/route.d.ts +9 -0
  32. package/dist/routes/api/symlinks/route.js +72 -0
  33. package/dist/routes/api/toggle-symlink/route.d.ts +2 -0
  34. package/dist/routes/api/toggle-symlink/route.js +94 -0
  35. package/dist/routes/api/v1/agents/[id]/index.d.ts +1 -0
  36. package/dist/routes/api/v1/agents/[id]/index.js +1 -0
  37. package/dist/routes/api/v1/agents/[id]/route.d.ts +3 -0
  38. package/dist/routes/api/v1/agents/[id]/route.js +163 -0
  39. package/dist/routes/api/v1/agents/index.d.ts +1 -0
  40. package/dist/routes/api/v1/agents/index.js +1 -0
  41. package/dist/routes/api/v1/agents/route.d.ts +3 -0
  42. package/dist/routes/api/v1/agents/route.js +133 -0
  43. package/dist/routes/api/v1/ai/index.d.ts +3 -0
  44. package/dist/routes/api/v1/ai/index.js +5 -0
  45. package/dist/routes/api/v1/ai/route.d.ts +8 -0
  46. package/dist/routes/api/v1/ai/route.js +86 -0
  47. package/dist/routes/api/v1/chats/[id]/index.d.ts +3 -0
  48. package/dist/routes/api/v1/chats/[id]/index.js +6 -0
  49. package/dist/routes/api/v1/chats/[id]/route.d.ts +12 -0
  50. package/dist/routes/api/v1/chats/[id]/route.js +31 -0
  51. package/dist/routes/api/v1/chats/index.d.ts +3 -0
  52. package/dist/routes/api/v1/chats/index.js +6 -0
  53. package/dist/routes/api/v1/chats/route.d.ts +32 -0
  54. package/dist/routes/api/v1/chats/route.js +67 -0
  55. package/dist/routes/api/v1/config/index.d.ts +3 -0
  56. package/dist/routes/api/v1/config/index.js +5 -0
  57. package/dist/routes/api/v1/config/route.d.ts +9 -0
  58. package/dist/routes/api/v1/config/route.js +29 -0
  59. package/dist/routes/api/v1/context/[...path]/route.d.ts +16 -0
  60. package/dist/routes/api/v1/context/[...path]/route.js +107 -0
  61. package/dist/routes/api/v1/context/architecture/route.d.ts +3 -0
  62. package/dist/routes/api/v1/context/architecture/route.js +198 -0
  63. package/dist/routes/api/v1/context/index.d.ts +3 -0
  64. package/dist/routes/api/v1/context/index.js +9 -0
  65. package/dist/routes/api/v1/context/knowledge/[filename]/index.d.ts +1 -0
  66. package/dist/routes/api/v1/context/knowledge/[filename]/index.js +1 -0
  67. package/dist/routes/api/v1/context/knowledge/[filename]/route.d.ts +3 -0
  68. package/dist/routes/api/v1/context/knowledge/[filename]/route.js +165 -0
  69. package/dist/routes/api/v1/context/knowledge/index.d.ts +1 -0
  70. package/dist/routes/api/v1/context/knowledge/index.js +1 -0
  71. package/dist/routes/api/v1/context/knowledge/route.d.ts +3 -0
  72. package/dist/routes/api/v1/context/knowledge/route.js +121 -0
  73. package/dist/routes/api/v1/context/project/route.d.ts +3 -0
  74. package/dist/routes/api/v1/context/project/route.js +153 -0
  75. package/dist/routes/api/v1/proposals/[id]/route.d.ts +337 -0
  76. package/dist/routes/api/v1/proposals/[id]/route.js +99 -0
  77. package/dist/routes/api/v1/proposals/index.d.ts +3 -0
  78. package/dist/routes/api/v1/proposals/index.js +10 -0
  79. package/dist/routes/api/v1/proposals/route.d.ts +315 -0
  80. package/dist/routes/api/v1/proposals/route.js +103 -0
  81. package/dist/routes/api/v1/resources/[id]/index.d.ts +3 -0
  82. package/dist/routes/api/v1/resources/[id]/index.js +7 -0
  83. package/dist/routes/api/v1/resources/[id]/route.d.ts +46 -0
  84. package/dist/routes/api/v1/resources/[id]/route.js +143 -0
  85. package/dist/routes/api/v1/resources/[id]/thumbnail/index.d.ts +3 -0
  86. package/dist/routes/api/v1/resources/[id]/thumbnail/index.js +5 -0
  87. package/dist/routes/api/v1/resources/[id]/thumbnail/route.d.ts +2 -0
  88. package/dist/routes/api/v1/resources/[id]/thumbnail/route.js +50 -0
  89. package/dist/routes/api/v1/resources/index.d.ts +3 -0
  90. package/dist/routes/api/v1/resources/index.js +6 -0
  91. package/dist/routes/api/v1/resources/route.d.ts +51 -0
  92. package/dist/routes/api/v1/resources/route.js +147 -0
  93. package/dist/routes/api/v1/search/route.d.ts +3 -0
  94. package/dist/routes/api/v1/search/route.js +39 -0
  95. package/dist/routes/api/v1/terminal/[proposalId]/create/index.d.ts +3 -0
  96. package/dist/routes/api/v1/terminal/[proposalId]/create/index.js +5 -0
  97. package/dist/routes/api/v1/terminal/[proposalId]/create/route.d.ts +10 -0
  98. package/dist/routes/api/v1/terminal/[proposalId]/create/route.js +27 -0
  99. package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.d.ts +3 -0
  100. package/dist/routes/api/v1/terminal/[proposalId]/destroy/index.js +5 -0
  101. package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.d.ts +10 -0
  102. package/dist/routes/api/v1/terminal/[proposalId]/destroy/route.js +21 -0
  103. package/dist/routes/api/v1/terminal/[proposalId]/resize/index.d.ts +3 -0
  104. package/dist/routes/api/v1/terminal/[proposalId]/resize/index.js +5 -0
  105. package/dist/routes/api/v1/terminal/[proposalId]/resize/route.d.ts +10 -0
  106. package/dist/routes/api/v1/terminal/[proposalId]/resize/route.js +21 -0
  107. package/dist/routes/api/v1/terminal/sessions/index.d.ts +3 -0
  108. package/dist/routes/api/v1/terminal/sessions/index.js +5 -0
  109. package/dist/routes/api/v1/terminal/sessions/route.d.ts +6 -0
  110. package/dist/routes/api/v1/terminal/sessions/route.js +29 -0
  111. package/dist/routes/api/v1/user/index.d.ts +3 -0
  112. package/dist/routes/api/v1/user/index.js +5 -0
  113. package/dist/routes/api/v1/user/preferences/route.d.ts +11 -0
  114. package/dist/routes/api/v1/user/preferences/route.js +31 -0
  115. package/dist/routes/api/v1/user/profile/route.d.ts +11 -0
  116. package/dist/routes/api/v1/user/profile/route.js +31 -0
  117. package/dist/routes/api/v1/user/settings/index.d.ts +1 -0
  118. package/dist/routes/api/v1/user/settings/index.js +1 -0
  119. package/dist/routes/api/v1/user/settings/route.d.ts +3 -0
  120. package/dist/routes/api/v1/user/settings/route.js +51 -0
  121. package/dist/server-with-static.d.ts +4 -0
  122. package/dist/server-with-static.js +144 -0
  123. package/dist/server.d.ts +1 -0
  124. package/dist/server.js +91 -0
  125. package/package.json +42 -0
  126. package/static/assets/index-BvTnrm0O.js +576 -0
  127. package/static/assets/index-Cm5dZHTl.css +33 -0
  128. package/static/assets/index-ORkAkJNi.js +576 -0
  129. package/static/assets/index-_Keadpms.js +576 -0
  130. package/static/index.html +17 -0
  131. 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,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default app;
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ import { POST } from './route.js';
3
+ const app = new Hono();
4
+ app.post('/', POST);
5
+ 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,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default app;
@@ -0,0 +1,6 @@
1
+ import { Hono } from 'hono';
2
+ import { GET, DELETE } from './route.js';
3
+ const app = new Hono();
4
+ app.get('/', GET);
5
+ app.delete('/', DELETE);
6
+ export default app;
@@ -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,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default app;
@@ -0,0 +1,6 @@
1
+ import { Hono } from 'hono';
2
+ import { GET, POST } from './route.js';
3
+ const app = new Hono();
4
+ app.get('/', GET);
5
+ app.post('/', POST);
6
+ export default app;
@@ -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,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const config: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default config;
@@ -0,0 +1,5 @@
1
+ import { Hono } from 'hono';
2
+ import { GET } from './route.js';
3
+ const config = new Hono();
4
+ config.get('/', GET);
5
+ export default config;
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import { Hono } from 'hono';
2
+ declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
3
+ export default app;