skillverse 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.
Files changed (72) hide show
  1. package/.prettierrc +10 -0
  2. package/README.md +369 -0
  3. package/client/README.md +73 -0
  4. package/client/eslint.config.js +23 -0
  5. package/client/index.html +13 -0
  6. package/client/package.json +41 -0
  7. package/client/postcss.config.js +6 -0
  8. package/client/public/vite.svg +1 -0
  9. package/client/src/App.css +42 -0
  10. package/client/src/App.tsx +26 -0
  11. package/client/src/assets/react.svg +1 -0
  12. package/client/src/components/AddSkillDialog.tsx +249 -0
  13. package/client/src/components/Layout.tsx +134 -0
  14. package/client/src/components/LinkWorkspaceDialog.tsx +196 -0
  15. package/client/src/components/LoadingSpinner.tsx +57 -0
  16. package/client/src/components/SkillCard.tsx +269 -0
  17. package/client/src/components/Toast.tsx +44 -0
  18. package/client/src/components/Tooltip.tsx +132 -0
  19. package/client/src/index.css +168 -0
  20. package/client/src/lib/api.ts +196 -0
  21. package/client/src/main.tsx +10 -0
  22. package/client/src/pages/Dashboard.tsx +209 -0
  23. package/client/src/pages/Marketplace.tsx +282 -0
  24. package/client/src/pages/Settings.tsx +136 -0
  25. package/client/src/pages/SkillLibrary.tsx +163 -0
  26. package/client/src/pages/Workspaces.tsx +662 -0
  27. package/client/src/stores/appStore.ts +222 -0
  28. package/client/tailwind.config.js +82 -0
  29. package/client/tsconfig.app.json +28 -0
  30. package/client/tsconfig.json +7 -0
  31. package/client/tsconfig.node.json +26 -0
  32. package/client/vite.config.ts +26 -0
  33. package/package.json +34 -0
  34. package/registry/.env.example +5 -0
  35. package/registry/Dockerfile +42 -0
  36. package/registry/docker-compose.yml +33 -0
  37. package/registry/package.json +37 -0
  38. package/registry/prisma/schema.prisma +59 -0
  39. package/registry/src/index.ts +34 -0
  40. package/registry/src/lib/db.ts +3 -0
  41. package/registry/src/middleware/errorHandler.ts +35 -0
  42. package/registry/src/routes/auth.ts +152 -0
  43. package/registry/src/routes/skills.ts +295 -0
  44. package/registry/tsconfig.json +23 -0
  45. package/server/.env.example +11 -0
  46. package/server/package.json +60 -0
  47. package/server/prisma/schema.prisma +73 -0
  48. package/server/public/assets/index-BsYtpZSa.css +1 -0
  49. package/server/public/assets/index-Dfr_6UV8.js +20 -0
  50. package/server/public/index.html +14 -0
  51. package/server/public/vite.svg +1 -0
  52. package/server/src/bin.ts +428 -0
  53. package/server/src/config.ts +39 -0
  54. package/server/src/index.ts +112 -0
  55. package/server/src/lib/db.ts +14 -0
  56. package/server/src/middleware/errorHandler.ts +40 -0
  57. package/server/src/middleware/logger.ts +12 -0
  58. package/server/src/routes/dashboard.ts +102 -0
  59. package/server/src/routes/marketplace.ts +273 -0
  60. package/server/src/routes/skills.ts +294 -0
  61. package/server/src/routes/workspaces.ts +168 -0
  62. package/server/src/services/bundleService.ts +123 -0
  63. package/server/src/services/skillService.ts +722 -0
  64. package/server/src/services/workspaceService.ts +521 -0
  65. package/server/src/verify-sync.ts +91 -0
  66. package/server/tsconfig.json +19 -0
  67. package/server/tsup.config.ts +18 -0
  68. package/shared/package.json +21 -0
  69. package/shared/pnpm-lock.yaml +24 -0
  70. package/shared/src/index.ts +169 -0
  71. package/shared/tsconfig.json +10 -0
  72. package/tsconfig.json +25 -0
@@ -0,0 +1,168 @@
1
+ import { Router, Request, Response, NextFunction } from 'express';
2
+ import { workspaceService } from '../services/workspaceService.js';
3
+ import { WorkspaceType, WorkspaceScope } from '@skillverse/shared';
4
+
5
+ const router = Router();
6
+
7
+ // GET /api/workspaces - Get all workspaces
8
+ router.get('/', async (req: Request, res: Response, next: NextFunction) => {
9
+ try {
10
+ const workspaces = await workspaceService.getAllWorkspaces();
11
+ res.json({
12
+ success: true,
13
+ data: workspaces,
14
+ });
15
+ } catch (error) {
16
+ next(error);
17
+ }
18
+ });
19
+
20
+ // GET /api/workspaces/:id - Get workspace by ID
21
+ router.get('/:id', async (req: Request, res: Response, next: NextFunction) => {
22
+ try {
23
+ const workspace = await workspaceService.getWorkspaceById(req.params.id);
24
+ res.json({
25
+ success: true,
26
+ data: workspace,
27
+ });
28
+ } catch (error) {
29
+ next(error);
30
+ }
31
+ });
32
+
33
+ // POST /api/workspaces - Create workspace
34
+ router.post('/', async (req: Request, res: Response, next: NextFunction) => {
35
+ try {
36
+ const { name, projectPath, type, scope } = req.body;
37
+
38
+ if (!name || !type || !scope) {
39
+ return res.status(400).json({
40
+ success: false,
41
+ error: 'name, type, and scope are required',
42
+ });
43
+ }
44
+
45
+ // For project scope, projectPath is required
46
+ if (scope === 'project' && !projectPath) {
47
+ return res.status(400).json({
48
+ success: false,
49
+ error: 'projectPath is required for project scope',
50
+ });
51
+ }
52
+
53
+ const validTypes: WorkspaceType[] = ['vscode', 'cursor', 'claude-desktop', 'codex', 'antigravity', 'custom'];
54
+ if (!validTypes.includes(type)) {
55
+ return res.status(400).json({
56
+ success: false,
57
+ error: `type must be one of: ${validTypes.join(', ')}`,
58
+ });
59
+ }
60
+
61
+ const validScopes: WorkspaceScope[] = ['project', 'global'];
62
+ if (!validScopes.includes(scope)) {
63
+ return res.status(400).json({
64
+ success: false,
65
+ error: `scope must be one of: ${validScopes.join(', ')}`,
66
+ });
67
+ }
68
+
69
+ const workspace = await workspaceService.createWorkspace(name, projectPath || '', type, scope);
70
+ res.status(201).json({
71
+ success: true,
72
+ data: workspace,
73
+ message: 'Workspace created successfully',
74
+ });
75
+ } catch (error) {
76
+ next(error);
77
+ }
78
+ });
79
+
80
+ // PUT /api/workspaces/:id - Update workspace
81
+ router.put('/:id', async (req: Request, res: Response, next: NextFunction) => {
82
+ try {
83
+ const { name, path, type, scope } = req.body;
84
+
85
+ if (type) {
86
+ const validTypes: WorkspaceType[] = ['vscode', 'cursor', 'claude-desktop', 'codex', 'antigravity', 'custom'];
87
+ if (!validTypes.includes(type)) {
88
+ return res.status(400).json({
89
+ success: false,
90
+ error: `type must be one of: ${validTypes.join(', ')}`,
91
+ });
92
+ }
93
+ }
94
+
95
+ if (scope) {
96
+ const validScopes: WorkspaceScope[] = ['project', 'global'];
97
+ if (!validScopes.includes(scope)) {
98
+ return res.status(400).json({
99
+ success: false,
100
+ error: `scope must be one of: ${validScopes.join(', ')}`,
101
+ });
102
+ }
103
+ }
104
+
105
+ const workspace = await workspaceService.updateWorkspace(req.params.id, {
106
+ name,
107
+ path,
108
+ type,
109
+ scope,
110
+ });
111
+ res.json({
112
+ success: true,
113
+ data: workspace,
114
+ message: 'Workspace updated successfully',
115
+ });
116
+ } catch (error) {
117
+ next(error);
118
+ }
119
+ });
120
+
121
+ // DELETE /api/workspaces/:id - Delete workspace
122
+ router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => {
123
+ try {
124
+ const result = await workspaceService.deleteWorkspace(req.params.id);
125
+ res.json(result);
126
+ } catch (error) {
127
+ next(error);
128
+ }
129
+ });
130
+
131
+ // POST /api/workspaces/:id/detect-skills - Detect existing skills in workspace
132
+ router.post('/:id/detect-skills', async (req: Request, res: Response, next: NextFunction) => {
133
+ try {
134
+ const workspace = await workspaceService.getWorkspaceById(req.params.id);
135
+ const existingSkills = await workspaceService.detectExistingSkills(workspace.path);
136
+ res.json({
137
+ success: true,
138
+ data: existingSkills,
139
+ });
140
+ } catch (error) {
141
+ next(error);
142
+ }
143
+ });
144
+
145
+ // POST /api/workspaces/:id/migrate-skills - Migrate existing skills to unified storage
146
+ router.post('/:id/migrate-skills', async (req: Request, res: Response, next: NextFunction) => {
147
+ try {
148
+ const { skillNames } = req.body;
149
+
150
+ if (!skillNames || !Array.isArray(skillNames) || skillNames.length === 0) {
151
+ return res.status(400).json({
152
+ success: false,
153
+ error: 'skillNames array is required',
154
+ });
155
+ }
156
+
157
+ const result = await workspaceService.migrateExistingSkills(req.params.id, skillNames);
158
+ res.json({
159
+ success: true,
160
+ data: result,
161
+ });
162
+ } catch (error) {
163
+ next(error);
164
+ }
165
+ });
166
+
167
+ export default router;
168
+
@@ -0,0 +1,123 @@
1
+ import { createWriteStream, existsSync, createReadStream } from 'fs';
2
+ import { mkdir, rm, readdir, stat } from 'fs/promises';
3
+ import { join, basename } from 'path';
4
+ import { pipeline } from 'stream/promises';
5
+ import archiver from 'archiver';
6
+ import { createGunzip } from 'zlib';
7
+ import { extract } from 'tar';
8
+
9
+ const BUNDLES_DIR = join(
10
+ process.env.SKILLVERSE_HOME || join(process.env.HOME || '', '.skillverse'),
11
+ 'bundles'
12
+ );
13
+
14
+ /**
15
+ * Ensures the bundles directory exists
16
+ */
17
+ async function ensureBundlesDir(): Promise<void> {
18
+ if (!existsSync(BUNDLES_DIR)) {
19
+ await mkdir(BUNDLES_DIR, { recursive: true });
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Creates a .tar.gz bundle from a skill directory
25
+ * @param skillPath - Path to the skill directory
26
+ * @param skillName - Name of the skill (used for bundle filename)
27
+ * @returns Path to the created bundle
28
+ */
29
+ export async function createBundle(skillPath: string, skillName: string): Promise<string> {
30
+ await ensureBundlesDir();
31
+
32
+ const bundleName = `${skillName}-${Date.now()}.tar.gz`;
33
+ const bundlePath = join(BUNDLES_DIR, bundleName);
34
+
35
+ return new Promise((resolve, reject) => {
36
+ const output = createWriteStream(bundlePath);
37
+ const archive = archiver('tar', {
38
+ gzip: true,
39
+ gzipOptions: { level: 9 },
40
+ });
41
+
42
+ output.on('close', () => {
43
+ console.log(`📦 Bundle created: ${bundlePath} (${archive.pointer()} bytes)`);
44
+ resolve(bundlePath);
45
+ });
46
+
47
+ archive.on('error', (err) => {
48
+ reject(err);
49
+ });
50
+
51
+ archive.pipe(output);
52
+
53
+ // Add all files from the skill directory
54
+ archive.directory(skillPath, false);
55
+
56
+ archive.finalize();
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Extracts a bundle to a target directory
62
+ * @param bundlePath - Path to the .tar.gz bundle
63
+ * @param targetDir - Directory to extract to
64
+ */
65
+ export async function extractBundle(bundlePath: string, targetDir: string): Promise<void> {
66
+ if (!existsSync(bundlePath)) {
67
+ throw new Error(`Bundle not found: ${bundlePath}`);
68
+ }
69
+
70
+ // Create target directory if it doesn't exist
71
+ if (!existsSync(targetDir)) {
72
+ await mkdir(targetDir, { recursive: true });
73
+ }
74
+
75
+ await pipeline(
76
+ createReadStream(bundlePath),
77
+ createGunzip(),
78
+ extract({ cwd: targetDir })
79
+ );
80
+
81
+ console.log(`📂 Bundle extracted to: ${targetDir}`);
82
+ }
83
+
84
+ /**
85
+ * Deletes a bundle file
86
+ * @param bundlePath - Path to the bundle to delete
87
+ */
88
+ export async function deleteBundle(bundlePath: string): Promise<void> {
89
+ if (existsSync(bundlePath)) {
90
+ await rm(bundlePath);
91
+ console.log(`🗑️ Bundle deleted: ${bundlePath}`);
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Gets the size of a bundle in bytes
97
+ * @param bundlePath - Path to the bundle
98
+ */
99
+ export async function getBundleSize(bundlePath: string): Promise<number> {
100
+ if (!existsSync(bundlePath)) {
101
+ return 0;
102
+ }
103
+ const stats = await stat(bundlePath);
104
+ return stats.size;
105
+ }
106
+
107
+ /**
108
+ * Lists all bundles in the bundles directory
109
+ */
110
+ export async function listBundles(): Promise<string[]> {
111
+ await ensureBundlesDir();
112
+ const files = await readdir(BUNDLES_DIR);
113
+ return files.filter((f) => f.endsWith('.tar.gz')).map((f) => join(BUNDLES_DIR, f));
114
+ }
115
+
116
+ export const bundleService = {
117
+ createBundle,
118
+ extractBundle,
119
+ deleteBundle,
120
+ getBundleSize,
121
+ listBundles,
122
+ BUNDLES_DIR,
123
+ };