@ian2018cs/agenthub 0.1.0
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/LICENSE +675 -0
- package/README.md +330 -0
- package/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
- package/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
- package/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
- package/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
- package/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
- package/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
- package/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
- package/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
- package/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
- package/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
- package/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
- package/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
- package/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
- package/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
- package/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
- package/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
- package/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
- package/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
- package/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
- package/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
- package/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
- package/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
- package/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
- package/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
- package/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
- package/dist/assets/index-B4ru3EJb.css +32 -0
- package/dist/assets/index-DDFuyrpY.js +154 -0
- package/dist/assets/vendor-codemirror-C_VWDoZS.js +39 -0
- package/dist/assets/vendor-icons-CJV4dnDL.js +326 -0
- package/dist/assets/vendor-katex-DK8hFnhL.js +261 -0
- package/dist/assets/vendor-markdown-VwNYkg_0.js +35 -0
- package/dist/assets/vendor-react-BeVl62c0.js +59 -0
- package/dist/assets/vendor-syntax-CdGaPJRS.js +16 -0
- package/dist/assets/vendor-utils-00TdZexr.js +1 -0
- package/dist/assets/vendor-xterm-CvdiG4-n.js +66 -0
- package/dist/clear-cache.html +85 -0
- package/dist/convert-icons.md +53 -0
- package/dist/favicon.png +0 -0
- package/dist/favicon.svg +9 -0
- package/dist/generate-icons.js +49 -0
- package/dist/icons/claude-ai-icon.svg +1 -0
- package/dist/icons/codex-white.svg +3 -0
- package/dist/icons/codex.svg +3 -0
- package/dist/icons/cursor-white.svg +12 -0
- package/dist/icons/cursor.svg +1 -0
- package/dist/icons/generate-icons.md +19 -0
- package/dist/icons/icon-128x128.png +0 -0
- package/dist/icons/icon-128x128.svg +12 -0
- package/dist/icons/icon-144x144.png +0 -0
- package/dist/icons/icon-144x144.svg +12 -0
- package/dist/icons/icon-152x152.png +0 -0
- package/dist/icons/icon-152x152.svg +12 -0
- package/dist/icons/icon-192x192.png +0 -0
- package/dist/icons/icon-192x192.svg +12 -0
- package/dist/icons/icon-384x384.png +0 -0
- package/dist/icons/icon-384x384.svg +12 -0
- package/dist/icons/icon-512x512.png +0 -0
- package/dist/icons/icon-512x512.svg +12 -0
- package/dist/icons/icon-72x72.png +0 -0
- package/dist/icons/icon-72x72.svg +12 -0
- package/dist/icons/icon-96x96.png +0 -0
- package/dist/icons/icon-96x96.svg +12 -0
- package/dist/icons/icon-template.svg +12 -0
- package/dist/index.html +57 -0
- package/dist/logo-128.png +0 -0
- package/dist/logo-256.png +0 -0
- package/dist/logo-32.png +0 -0
- package/dist/logo-512.png +0 -0
- package/dist/logo-64.png +0 -0
- package/dist/logo.svg +17 -0
- package/dist/manifest.json +61 -0
- package/dist/screenshots/cli-selection.png +0 -0
- package/dist/screenshots/desktop-main.png +0 -0
- package/dist/screenshots/mobile-chat.png +0 -0
- package/dist/screenshots/tools-modal.png +0 -0
- package/dist/sw.js +49 -0
- package/package.json +113 -0
- package/server/claude-sdk.js +791 -0
- package/server/cli.js +330 -0
- package/server/database/auth.db +0 -0
- package/server/database/db.js +523 -0
- package/server/database/init.sql +23 -0
- package/server/index.js +1678 -0
- package/server/load-env.js +27 -0
- package/server/middleware/auth.js +118 -0
- package/server/projects.js +899 -0
- package/server/routes/admin.js +89 -0
- package/server/routes/auth.js +144 -0
- package/server/routes/commands.js +570 -0
- package/server/routes/mcp-utils.js +37 -0
- package/server/routes/mcp.js +593 -0
- package/server/routes/projects.js +216 -0
- package/server/routes/skills.js +891 -0
- package/server/routes/usage.js +206 -0
- package/server/services/pricing.js +196 -0
- package/server/services/usage-scanner.js +283 -0
- package/server/services/user-directories.js +123 -0
- package/server/utils/commandParser.js +303 -0
- package/server/utils/mcp-detector.js +73 -0
- package/shared/modelConstants.js +23 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { promises as fs } from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
import { addProjectManually } from '../projects.js';
|
|
6
|
+
import { getUserPaths } from '../services/user-directories.js';
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// Project name validation: letters, numbers, hyphens, underscores, 1-100 characters
|
|
11
|
+
const PROJECT_NAME_REGEX = /^[a-zA-Z0-9_-]{1,100}$/;
|
|
12
|
+
|
|
13
|
+
// Trusted git hosting domains
|
|
14
|
+
const TRUSTED_GIT_HOSTS = ['github.com', 'gitlab.com', 'bitbucket.org'];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validates a project name
|
|
18
|
+
* @param {string} name - The project name to validate
|
|
19
|
+
* @returns {{valid: boolean, error?: string}}
|
|
20
|
+
*/
|
|
21
|
+
function validateProjectName(name) {
|
|
22
|
+
if (!name || typeof name !== 'string') {
|
|
23
|
+
return { valid: false, error: 'Project name is required' };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!PROJECT_NAME_REGEX.test(name)) {
|
|
27
|
+
return {
|
|
28
|
+
valid: false,
|
|
29
|
+
error: 'Project name must be 1-100 characters and contain only letters, numbers, hyphens, and underscores'
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { valid: true };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Validates a GitHub/Git repository URL
|
|
38
|
+
* @param {string} url - The repository URL to validate
|
|
39
|
+
* @returns {{valid: boolean, error?: string}}
|
|
40
|
+
*/
|
|
41
|
+
function validateGitHubUrl(url) {
|
|
42
|
+
if (!url || typeof url !== 'string') {
|
|
43
|
+
return { valid: false, error: 'Repository URL is required' };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Parse the URL
|
|
47
|
+
let parsedUrl;
|
|
48
|
+
try {
|
|
49
|
+
parsedUrl = new URL(url);
|
|
50
|
+
} catch {
|
|
51
|
+
return { valid: false, error: 'Invalid URL format' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Only allow HTTPS protocol
|
|
55
|
+
if (parsedUrl.protocol !== 'https:') {
|
|
56
|
+
return { valid: false, error: 'Only HTTPS URLs are allowed for security reasons' };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Check if host is a trusted git hosting provider
|
|
60
|
+
const host = parsedUrl.hostname.toLowerCase();
|
|
61
|
+
if (!TRUSTED_GIT_HOSTS.includes(host)) {
|
|
62
|
+
return {
|
|
63
|
+
valid: false,
|
|
64
|
+
error: `Only trusted git hosts are allowed: ${TRUSTED_GIT_HOSTS.join(', ')}`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return { valid: true };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Create a new project
|
|
73
|
+
* POST /api/projects/create-workspace
|
|
74
|
+
*
|
|
75
|
+
* Body:
|
|
76
|
+
* - name: string (project name, 1-100 chars, alphanumeric with hyphens/underscores)
|
|
77
|
+
* - githubUrl?: string (optional, for cloning a public repository)
|
|
78
|
+
*/
|
|
79
|
+
router.post('/create-workspace', async (req, res) => {
|
|
80
|
+
try {
|
|
81
|
+
const { name, githubUrl } = req.body;
|
|
82
|
+
|
|
83
|
+
// Validate project name
|
|
84
|
+
const nameValidation = validateProjectName(name);
|
|
85
|
+
if (!nameValidation.valid) {
|
|
86
|
+
return res.status(400).json({ error: nameValidation.error });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get user UUID from authenticated request
|
|
90
|
+
const userUuid = req.user?.uuid;
|
|
91
|
+
if (!userUuid) {
|
|
92
|
+
return res.status(401).json({ error: 'User authentication required' });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Get user's projects directory
|
|
96
|
+
const { projectsDir } = getUserPaths(userUuid);
|
|
97
|
+
|
|
98
|
+
// Calculate absolute path for the new project
|
|
99
|
+
const absolutePath = path.join(projectsDir, name);
|
|
100
|
+
|
|
101
|
+
// Check if directory already exists
|
|
102
|
+
try {
|
|
103
|
+
await fs.access(absolutePath);
|
|
104
|
+
return res.status(400).json({
|
|
105
|
+
error: 'A project with this name already exists. Please choose a different name.'
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
if (error.code !== 'ENOENT') {
|
|
109
|
+
throw error;
|
|
110
|
+
}
|
|
111
|
+
// Path doesn't exist - good, we can create it
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// If GitHub URL is provided, validate it BEFORE creating the directory
|
|
115
|
+
if (githubUrl) {
|
|
116
|
+
const urlValidation = validateGitHubUrl(githubUrl);
|
|
117
|
+
if (!urlValidation.valid) {
|
|
118
|
+
return res.status(400).json({ error: urlValidation.error });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create the project directory (also creates parent directories if needed)
|
|
123
|
+
await fs.mkdir(absolutePath, { recursive: true });
|
|
124
|
+
|
|
125
|
+
// If GitHub URL is provided, clone the repository (public repos only)
|
|
126
|
+
if (githubUrl) {
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
await cloneGitHubRepository(githubUrl, absolutePath);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
// Clean up created directory on failure
|
|
132
|
+
try {
|
|
133
|
+
await fs.rm(absolutePath, { recursive: true, force: true });
|
|
134
|
+
} catch (cleanupError) {
|
|
135
|
+
console.error('Failed to clean up directory after clone failure:', cleanupError);
|
|
136
|
+
}
|
|
137
|
+
throw new Error(`Failed to clone repository: ${error.message}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Add the new project to the project list
|
|
142
|
+
const project = await addProjectManually(absolutePath, null, userUuid);
|
|
143
|
+
|
|
144
|
+
return res.json({
|
|
145
|
+
success: true,
|
|
146
|
+
project,
|
|
147
|
+
message: githubUrl
|
|
148
|
+
? 'Project created and repository cloned successfully'
|
|
149
|
+
: 'Project created successfully'
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
} catch (error) {
|
|
153
|
+
console.error('Error creating project:', error);
|
|
154
|
+
res.status(500).json({
|
|
155
|
+
error: error.message || 'Failed to create project',
|
|
156
|
+
details: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Helper function to clone a GitHub repository (public repos only)
|
|
163
|
+
*/
|
|
164
|
+
function cloneGitHubRepository(githubUrl, destinationPath) {
|
|
165
|
+
return new Promise((resolve, reject) => {
|
|
166
|
+
const gitProcess = spawn('git', ['clone', githubUrl, destinationPath], {
|
|
167
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
168
|
+
env: {
|
|
169
|
+
...process.env,
|
|
170
|
+
GIT_TERMINAL_PROMPT: '0' // Disable git password prompts
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
let stdout = '';
|
|
175
|
+
let stderr = '';
|
|
176
|
+
|
|
177
|
+
gitProcess.stdout.on('data', (data) => {
|
|
178
|
+
stdout += data.toString();
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
gitProcess.stderr.on('data', (data) => {
|
|
182
|
+
stderr += data.toString();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
gitProcess.on('close', (code) => {
|
|
186
|
+
if (code === 0) {
|
|
187
|
+
resolve({ stdout, stderr });
|
|
188
|
+
} else {
|
|
189
|
+
// Parse git error messages to provide helpful feedback
|
|
190
|
+
let errorMessage = 'Git clone failed';
|
|
191
|
+
|
|
192
|
+
if (stderr.includes('Authentication failed') || stderr.includes('could not read Username')) {
|
|
193
|
+
errorMessage = 'Authentication failed. Please check your GitHub token.';
|
|
194
|
+
} else if (stderr.includes('Repository not found')) {
|
|
195
|
+
errorMessage = 'Repository not found. Please check the URL and ensure you have access.';
|
|
196
|
+
} else if (stderr.includes('already exists')) {
|
|
197
|
+
errorMessage = 'Directory already exists';
|
|
198
|
+
} else if (stderr) {
|
|
199
|
+
errorMessage = stderr;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
reject(new Error(errorMessage));
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
gitProcess.on('error', (error) => {
|
|
207
|
+
if (error.code === 'ENOENT') {
|
|
208
|
+
reject(new Error('Git is not installed or not in PATH'));
|
|
209
|
+
} else {
|
|
210
|
+
reject(error);
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export default router;
|