skillverse 0.1.0 → 0.1.2

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 (77) hide show
  1. package/dist/bin.js +2421 -0
  2. package/dist/bin.js.map +1 -0
  3. package/dist/index.js +1907 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +52 -26
  6. package/prisma/dev.db +0 -0
  7. package/.prettierrc +0 -10
  8. package/README.md +0 -369
  9. package/client/README.md +0 -73
  10. package/client/eslint.config.js +0 -23
  11. package/client/index.html +0 -13
  12. package/client/package.json +0 -41
  13. package/client/postcss.config.js +0 -6
  14. package/client/src/App.css +0 -42
  15. package/client/src/App.tsx +0 -26
  16. package/client/src/assets/react.svg +0 -1
  17. package/client/src/components/AddSkillDialog.tsx +0 -249
  18. package/client/src/components/Layout.tsx +0 -134
  19. package/client/src/components/LinkWorkspaceDialog.tsx +0 -196
  20. package/client/src/components/LoadingSpinner.tsx +0 -57
  21. package/client/src/components/SkillCard.tsx +0 -269
  22. package/client/src/components/Toast.tsx +0 -44
  23. package/client/src/components/Tooltip.tsx +0 -132
  24. package/client/src/index.css +0 -168
  25. package/client/src/lib/api.ts +0 -196
  26. package/client/src/main.tsx +0 -10
  27. package/client/src/pages/Dashboard.tsx +0 -209
  28. package/client/src/pages/Marketplace.tsx +0 -282
  29. package/client/src/pages/Settings.tsx +0 -136
  30. package/client/src/pages/SkillLibrary.tsx +0 -163
  31. package/client/src/pages/Workspaces.tsx +0 -662
  32. package/client/src/stores/appStore.ts +0 -222
  33. package/client/tailwind.config.js +0 -82
  34. package/client/tsconfig.app.json +0 -28
  35. package/client/tsconfig.json +0 -7
  36. package/client/tsconfig.node.json +0 -26
  37. package/client/vite.config.ts +0 -26
  38. package/registry/.env.example +0 -5
  39. package/registry/Dockerfile +0 -42
  40. package/registry/docker-compose.yml +0 -33
  41. package/registry/package.json +0 -37
  42. package/registry/prisma/schema.prisma +0 -59
  43. package/registry/src/index.ts +0 -34
  44. package/registry/src/lib/db.ts +0 -3
  45. package/registry/src/middleware/errorHandler.ts +0 -35
  46. package/registry/src/routes/auth.ts +0 -152
  47. package/registry/src/routes/skills.ts +0 -295
  48. package/registry/tsconfig.json +0 -23
  49. package/server/.env.example +0 -11
  50. package/server/package.json +0 -60
  51. package/server/public/vite.svg +0 -1
  52. package/server/src/bin.ts +0 -428
  53. package/server/src/config.ts +0 -39
  54. package/server/src/index.ts +0 -112
  55. package/server/src/lib/db.ts +0 -14
  56. package/server/src/middleware/errorHandler.ts +0 -40
  57. package/server/src/middleware/logger.ts +0 -12
  58. package/server/src/routes/dashboard.ts +0 -102
  59. package/server/src/routes/marketplace.ts +0 -273
  60. package/server/src/routes/skills.ts +0 -294
  61. package/server/src/routes/workspaces.ts +0 -168
  62. package/server/src/services/bundleService.ts +0 -123
  63. package/server/src/services/skillService.ts +0 -722
  64. package/server/src/services/workspaceService.ts +0 -521
  65. package/server/src/verify-sync.ts +0 -91
  66. package/server/tsconfig.json +0 -19
  67. package/server/tsup.config.ts +0 -18
  68. package/shared/package.json +0 -21
  69. package/shared/pnpm-lock.yaml +0 -24
  70. package/shared/src/index.ts +0 -169
  71. package/shared/tsconfig.json +0 -10
  72. package/tsconfig.json +0 -25
  73. /package/{server/prisma → prisma}/schema.prisma +0 -0
  74. /package/{server/public → public}/assets/index-BsYtpZSa.css +0 -0
  75. /package/{server/public → public}/assets/index-Dfr_6UV8.js +0 -0
  76. /package/{server/public → public}/index.html +0 -0
  77. /package/{client/public → public}/vite.svg +0 -0
@@ -1,152 +0,0 @@
1
- import { Router, Request, Response, NextFunction } from 'express';
2
- import bcrypt from 'bcryptjs';
3
- import jwt from 'jsonwebtoken';
4
- import { prisma } from '../lib/db.js';
5
- import { AppError } from '../middleware/errorHandler.js';
6
-
7
- const router = Router();
8
- const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-change-me';
9
-
10
- // POST /api/auth/register - Create new account
11
- router.post('/register', async (req: Request, res: Response, next: NextFunction) => {
12
- try {
13
- const { username, email, password, displayName } = req.body;
14
-
15
- if (!username || !email || !password) {
16
- throw new AppError('VALIDATION_ERROR', 'Username, email, and password are required', 400);
17
- }
18
-
19
- // Check if user exists
20
- const existingUser = await prisma.user.findFirst({
21
- where: {
22
- OR: [{ username }, { email }],
23
- },
24
- });
25
-
26
- if (existingUser) {
27
- throw new AppError('ALREADY_EXISTS', 'Username or email already exists', 409);
28
- }
29
-
30
- // Hash password
31
- const passwordHash = await bcrypt.hash(password, 12);
32
-
33
- // Create user
34
- const user = await prisma.user.create({
35
- data: {
36
- username,
37
- email,
38
- passwordHash,
39
- displayName: displayName || username,
40
- },
41
- select: {
42
- id: true,
43
- username: true,
44
- email: true,
45
- displayName: true,
46
- createdAt: true,
47
- },
48
- });
49
-
50
- // Generate token
51
- const token = jwt.sign({ userId: user.id, username: user.username }, JWT_SECRET, {
52
- expiresIn: '30d',
53
- });
54
-
55
- res.status(201).json({
56
- success: true,
57
- data: { user, token },
58
- message: 'Account created successfully',
59
- });
60
- } catch (error) {
61
- next(error);
62
- }
63
- });
64
-
65
- // POST /api/auth/login - Login
66
- router.post('/login', async (req: Request, res: Response, next: NextFunction) => {
67
- try {
68
- const { username, password } = req.body;
69
-
70
- if (!username || !password) {
71
- throw new AppError('VALIDATION_ERROR', 'Username and password are required', 400);
72
- }
73
-
74
- // Find user
75
- const user = await prisma.user.findFirst({
76
- where: {
77
- OR: [{ username }, { email: username }],
78
- },
79
- });
80
-
81
- if (!user) {
82
- throw new AppError('AUTH_FAILED', 'Invalid credentials', 401);
83
- }
84
-
85
- // Verify password
86
- const isValid = await bcrypt.compare(password, user.passwordHash);
87
- if (!isValid) {
88
- throw new AppError('AUTH_FAILED', 'Invalid credentials', 401);
89
- }
90
-
91
- // Generate token
92
- const token = jwt.sign({ userId: user.id, username: user.username }, JWT_SECRET, {
93
- expiresIn: '30d',
94
- });
95
-
96
- res.json({
97
- success: true,
98
- data: {
99
- user: {
100
- id: user.id,
101
- username: user.username,
102
- email: user.email,
103
- displayName: user.displayName,
104
- },
105
- token,
106
- },
107
- });
108
- } catch (error) {
109
- next(error);
110
- }
111
- });
112
-
113
- // GET /api/auth/me - Get current user
114
- router.get('/me', async (req: Request, res: Response, next: NextFunction) => {
115
- try {
116
- const authHeader = req.headers.authorization;
117
- if (!authHeader?.startsWith('Bearer ')) {
118
- throw new AppError('AUTH_REQUIRED', 'Authentication required', 401);
119
- }
120
-
121
- const token = authHeader.substring(7);
122
- const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
123
-
124
- const user = await prisma.user.findUnique({
125
- where: { id: decoded.userId },
126
- select: {
127
- id: true,
128
- username: true,
129
- email: true,
130
- displayName: true,
131
- avatarUrl: true,
132
- createdAt: true,
133
- },
134
- });
135
-
136
- if (!user) {
137
- throw new AppError('NOT_FOUND', 'User not found', 404);
138
- }
139
-
140
- res.json({
141
- success: true,
142
- data: user,
143
- });
144
- } catch (error) {
145
- if (error instanceof jwt.JsonWebTokenError) {
146
- return next(new AppError('AUTH_INVALID', 'Invalid token', 401));
147
- }
148
- next(error);
149
- }
150
- });
151
-
152
- export default router;
@@ -1,295 +0,0 @@
1
- import { Router, Request, Response, NextFunction } from 'express';
2
- import multer from 'multer';
3
- import jwt from 'jsonwebtoken';
4
- import { existsSync } from 'fs';
5
- import { mkdir, readFile, writeFile, rm } from 'fs/promises';
6
- import { join } from 'path';
7
- import { prisma } from '../lib/db.js';
8
- import { AppError } from '../middleware/errorHandler.js';
9
-
10
- const router = Router();
11
- const JWT_SECRET = process.env.JWT_SECRET || 'fallback-secret-change-me';
12
- const STORAGE_PATH = process.env.STORAGE_PATH || './uploads';
13
-
14
- // Configure multer for file uploads
15
- const storage = multer.diskStorage({
16
- destination: async (req, file, cb) => {
17
- const uploadDir = join(STORAGE_PATH, 'bundles');
18
- if (!existsSync(uploadDir)) {
19
- await mkdir(uploadDir, { recursive: true });
20
- }
21
- cb(null, uploadDir);
22
- },
23
- filename: (req, file, cb) => {
24
- const uniqueName = `${Date.now()}-${file.originalname}`;
25
- cb(null, uniqueName);
26
- },
27
- });
28
-
29
- const upload = multer({
30
- storage,
31
- limits: { fileSize: 50 * 1024 * 1024 }, // 50MB limit
32
- fileFilter: (req, file, cb) => {
33
- if (file.mimetype === 'application/gzip' || file.originalname.endsWith('.tar.gz')) {
34
- cb(null, true);
35
- } else {
36
- cb(new Error('Only .tar.gz files are allowed'));
37
- }
38
- },
39
- });
40
-
41
- // Auth middleware
42
- async function requireAuth(req: Request, res: Response, next: NextFunction) {
43
- try {
44
- const authHeader = req.headers.authorization;
45
- if (!authHeader?.startsWith('Bearer ')) {
46
- throw new AppError('AUTH_REQUIRED', 'Authentication required', 401);
47
- }
48
-
49
- const token = authHeader.substring(7);
50
- const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };
51
- (req as any).userId = decoded.userId;
52
- next();
53
- } catch (error) {
54
- if (error instanceof jwt.JsonWebTokenError) {
55
- return next(new AppError('AUTH_INVALID', 'Invalid token', 401));
56
- }
57
- next(error);
58
- }
59
- }
60
-
61
- // GET /api/skills - Search/list skills
62
- router.get('/', async (req: Request, res: Response, next: NextFunction) => {
63
- try {
64
- const { search, page = '1', pageSize = '20' } = req.query;
65
-
66
- const where: any = {};
67
- if (search) {
68
- where.OR = [
69
- { name: { contains: search as string, mode: 'insensitive' } },
70
- { description: { contains: search as string, mode: 'insensitive' } },
71
- ];
72
- }
73
-
74
- const [items, total] = await Promise.all([
75
- prisma.registrySkill.findMany({
76
- where,
77
- include: {
78
- publisher: {
79
- select: {
80
- id: true,
81
- username: true,
82
- displayName: true,
83
- },
84
- },
85
- },
86
- orderBy: { downloads: 'desc' },
87
- skip: (parseInt(page as string) - 1) * parseInt(pageSize as string),
88
- take: parseInt(pageSize as string),
89
- }),
90
- prisma.registrySkill.count({ where }),
91
- ]);
92
-
93
- res.json({
94
- success: true,
95
- data: {
96
- items,
97
- total,
98
- page: parseInt(page as string),
99
- pageSize: parseInt(pageSize as string),
100
- },
101
- });
102
- } catch (error) {
103
- next(error);
104
- }
105
- });
106
-
107
- // GET /api/skills/:name - Get skill details
108
- router.get('/:name', async (req: Request, res: Response, next: NextFunction) => {
109
- try {
110
- const skill = await prisma.registrySkill.findUnique({
111
- where: { name: req.params.name },
112
- include: {
113
- publisher: {
114
- select: {
115
- id: true,
116
- username: true,
117
- displayName: true,
118
- },
119
- },
120
- },
121
- });
122
-
123
- if (!skill) {
124
- throw new AppError('NOT_FOUND', 'Skill not found', 404);
125
- }
126
-
127
- res.json({
128
- success: true,
129
- data: skill,
130
- });
131
- } catch (error) {
132
- next(error);
133
- }
134
- });
135
-
136
- // GET /api/skills/:name/download - Download skill bundle
137
- router.get('/:name/download', async (req: Request, res: Response, next: NextFunction) => {
138
- try {
139
- const skill = await prisma.registrySkill.findUnique({
140
- where: { name: req.params.name },
141
- });
142
-
143
- if (!skill) {
144
- throw new AppError('NOT_FOUND', 'Skill not found', 404);
145
- }
146
-
147
- if (!existsSync(skill.bundlePath)) {
148
- throw new AppError('NOT_FOUND', 'Bundle file not found', 404);
149
- }
150
-
151
- // Increment download count
152
- await prisma.registrySkill.update({
153
- where: { id: skill.id },
154
- data: { downloads: { increment: 1 } },
155
- });
156
-
157
- res.setHeader('Content-Type', 'application/gzip');
158
- res.setHeader('Content-Disposition', `attachment; filename="${skill.name}.tar.gz"`);
159
- res.sendFile(skill.bundlePath, { root: process.cwd() });
160
- } catch (error) {
161
- next(error);
162
- }
163
- });
164
-
165
- // POST /api/skills/publish - Upload new skill
166
- router.post(
167
- '/publish',
168
- requireAuth,
169
- upload.single('bundle'),
170
- async (req: Request, res: Response, next: NextFunction) => {
171
- try {
172
- const { name, description, version, keywords, sourceType, sourceUrl, readme } = req.body;
173
- const userId = (req as any).userId;
174
- const file = req.file;
175
-
176
- if (!name) {
177
- throw new AppError('VALIDATION_ERROR', 'Skill name is required', 400);
178
- }
179
-
180
- if (!file) {
181
- throw new AppError('VALIDATION_ERROR', 'Bundle file is required', 400);
182
- }
183
-
184
- // Check if skill already exists
185
- const existingSkill = await prisma.registrySkill.findUnique({
186
- where: { name },
187
- });
188
-
189
- if (existingSkill) {
190
- // Only owner can update
191
- if (existingSkill.publisherId !== userId) {
192
- throw new AppError('FORBIDDEN', 'You can only update your own skills', 403);
193
- }
194
-
195
- // Delete old bundle
196
- if (existsSync(existingSkill.bundlePath)) {
197
- await rm(existingSkill.bundlePath);
198
- }
199
-
200
- // Update existing skill
201
- const updatedSkill = await prisma.registrySkill.update({
202
- where: { id: existingSkill.id },
203
- data: {
204
- version: version || existingSkill.version,
205
- description: description || existingSkill.description,
206
- readme: readme || existingSkill.readme,
207
- keywords: keywords ? JSON.parse(keywords) : existingSkill.keywords,
208
- bundlePath: file.path,
209
- bundleSize: file.size,
210
- sourceType,
211
- sourceUrl,
212
- },
213
- include: {
214
- publisher: {
215
- select: { id: true, username: true, displayName: true },
216
- },
217
- },
218
- });
219
-
220
- return res.json({
221
- success: true,
222
- data: updatedSkill,
223
- message: 'Skill updated successfully',
224
- });
225
- }
226
-
227
- // Create new skill
228
- const skill = await prisma.registrySkill.create({
229
- data: {
230
- name,
231
- version: version || '1.0.0',
232
- description: description || '',
233
- readme: readme || '',
234
- keywords: keywords ? JSON.parse(keywords) : [],
235
- publisherId: userId,
236
- bundlePath: file.path,
237
- bundleSize: file.size,
238
- sourceType,
239
- sourceUrl,
240
- },
241
- include: {
242
- publisher: {
243
- select: { id: true, username: true, displayName: true },
244
- },
245
- },
246
- });
247
-
248
- res.status(201).json({
249
- success: true,
250
- data: skill,
251
- message: 'Skill published successfully',
252
- });
253
- } catch (error) {
254
- next(error);
255
- }
256
- }
257
- );
258
-
259
- // DELETE /api/skills/:name - Unpublish skill
260
- router.delete('/:name', requireAuth, async (req: Request, res: Response, next: NextFunction) => {
261
- try {
262
- const userId = (req as any).userId;
263
-
264
- const skill = await prisma.registrySkill.findUnique({
265
- where: { name: req.params.name },
266
- });
267
-
268
- if (!skill) {
269
- throw new AppError('NOT_FOUND', 'Skill not found', 404);
270
- }
271
-
272
- if (skill.publisherId !== userId) {
273
- throw new AppError('FORBIDDEN', 'You can only delete your own skills', 403);
274
- }
275
-
276
- // Delete bundle file
277
- if (existsSync(skill.bundlePath)) {
278
- await rm(skill.bundlePath);
279
- }
280
-
281
- // Delete from database
282
- await prisma.registrySkill.delete({
283
- where: { id: skill.id },
284
- });
285
-
286
- res.json({
287
- success: true,
288
- message: 'Skill deleted successfully',
289
- });
290
- } catch (error) {
291
- next(error);
292
- }
293
- });
294
-
295
- export default router;
@@ -1,23 +0,0 @@
1
- {
2
- "extends": "../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "module": "ESNext",
7
- "moduleResolution": "bundler",
8
- "target": "ES2022",
9
- "lib": [
10
- "ES2022"
11
- ],
12
- "types": [
13
- "node"
14
- ]
15
- },
16
- "include": [
17
- "src/**/*"
18
- ],
19
- "exclude": [
20
- "node_modules",
21
- "dist"
22
- ]
23
- }
@@ -1,11 +0,0 @@
1
- # Database
2
- DATABASE_URL="file:./dev.db"
3
-
4
- # Server
5
- PORT=3001
6
- NODE_ENV=development
7
-
8
- # SkillVerse Storage
9
- SKILLVERSE_HOME="${HOME}/.skillverse"
10
- SKILLS_DIR="${SKILLVERSE_HOME}/skills"
11
- MARKETPLACE_DIR="${SKILLVERSE_HOME}/marketplace"
@@ -1,60 +0,0 @@
1
- {
2
- "name": "skillverse-cli",
3
- "version": "0.1.0",
4
- "description": "SkillVerse - A local-first skill management platform for AI coding assistants",
5
- "author": "SkillVerse Team",
6
- "license": "MIT",
7
- "type": "module",
8
- "scripts": {
9
- "dev": "tsx watch src/index.ts",
10
- "build": "tsup",
11
- "prepublishOnly": "npm run build:client && npm run build",
12
- "build:client": "cd .. && npm run build --workspace=client",
13
- "start": "node dist/index.js",
14
- "postinstall": "prisma generate",
15
- "typecheck": "tsc --noEmit",
16
- "db:generate": "prisma generate",
17
- "db:push": "prisma db push",
18
- "db:migrate": "prisma migrate dev",
19
- "db:studio": "prisma studio",
20
- "start:cli": "tsx src/bin.ts"
21
- },
22
- "files": [
23
- "dist",
24
- "public",
25
- "prisma",
26
- "README.md"
27
- ],
28
- "bin": {
29
- "skillverse": "./dist/bin.js"
30
- },
31
- "dependencies": {
32
- "@prisma/client": "^5.8.1",
33
- "@skillverse/shared": "*",
34
- "@types/archiver": "^7.0.0",
35
- "@types/tar": "^6.1.13",
36
- "adm-zip": "^0.5.10",
37
- "archiver": "^7.0.1",
38
- "commander": "^14.0.3",
39
- "cors": "^2.8.5",
40
- "dotenv": "^16.3.1",
41
- "express": "^4.18.2",
42
- "form-data": "^4.0.5",
43
- "gray-matter": "^4.0.3",
44
- "multer": "^1.4.5-lts.1",
45
- "open": "^11.0.0",
46
- "simple-git": "^3.22.0",
47
- "tar": "^7.5.7",
48
- "zod": "^3.22.4"
49
- },
50
- "devDependencies": {
51
- "@types/adm-zip": "^0.5.5",
52
- "@types/cors": "^2.8.17",
53
- "@types/express": "^4.17.21",
54
- "@types/multer": "^1.4.11",
55
- "prisma": "^5.8.1",
56
- "tsup": "^8.5.1",
57
- "tsx": "^4.7.0",
58
- "typescript": "^5.3.3"
59
- }
60
- }
@@ -1 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>