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.
- package/.prettierrc +10 -0
- package/README.md +369 -0
- package/client/README.md +73 -0
- package/client/eslint.config.js +23 -0
- package/client/index.html +13 -0
- package/client/package.json +41 -0
- package/client/postcss.config.js +6 -0
- package/client/public/vite.svg +1 -0
- package/client/src/App.css +42 -0
- package/client/src/App.tsx +26 -0
- package/client/src/assets/react.svg +1 -0
- package/client/src/components/AddSkillDialog.tsx +249 -0
- package/client/src/components/Layout.tsx +134 -0
- package/client/src/components/LinkWorkspaceDialog.tsx +196 -0
- package/client/src/components/LoadingSpinner.tsx +57 -0
- package/client/src/components/SkillCard.tsx +269 -0
- package/client/src/components/Toast.tsx +44 -0
- package/client/src/components/Tooltip.tsx +132 -0
- package/client/src/index.css +168 -0
- package/client/src/lib/api.ts +196 -0
- package/client/src/main.tsx +10 -0
- package/client/src/pages/Dashboard.tsx +209 -0
- package/client/src/pages/Marketplace.tsx +282 -0
- package/client/src/pages/Settings.tsx +136 -0
- package/client/src/pages/SkillLibrary.tsx +163 -0
- package/client/src/pages/Workspaces.tsx +662 -0
- package/client/src/stores/appStore.ts +222 -0
- package/client/tailwind.config.js +82 -0
- package/client/tsconfig.app.json +28 -0
- package/client/tsconfig.json +7 -0
- package/client/tsconfig.node.json +26 -0
- package/client/vite.config.ts +26 -0
- package/package.json +34 -0
- package/registry/.env.example +5 -0
- package/registry/Dockerfile +42 -0
- package/registry/docker-compose.yml +33 -0
- package/registry/package.json +37 -0
- package/registry/prisma/schema.prisma +59 -0
- package/registry/src/index.ts +34 -0
- package/registry/src/lib/db.ts +3 -0
- package/registry/src/middleware/errorHandler.ts +35 -0
- package/registry/src/routes/auth.ts +152 -0
- package/registry/src/routes/skills.ts +295 -0
- package/registry/tsconfig.json +23 -0
- package/server/.env.example +11 -0
- package/server/package.json +60 -0
- package/server/prisma/schema.prisma +73 -0
- package/server/public/assets/index-BsYtpZSa.css +1 -0
- package/server/public/assets/index-Dfr_6UV8.js +20 -0
- package/server/public/index.html +14 -0
- package/server/public/vite.svg +1 -0
- package/server/src/bin.ts +428 -0
- package/server/src/config.ts +39 -0
- package/server/src/index.ts +112 -0
- package/server/src/lib/db.ts +14 -0
- package/server/src/middleware/errorHandler.ts +40 -0
- package/server/src/middleware/logger.ts +12 -0
- package/server/src/routes/dashboard.ts +102 -0
- package/server/src/routes/marketplace.ts +273 -0
- package/server/src/routes/skills.ts +294 -0
- package/server/src/routes/workspaces.ts +168 -0
- package/server/src/services/bundleService.ts +123 -0
- package/server/src/services/skillService.ts +722 -0
- package/server/src/services/workspaceService.ts +521 -0
- package/server/src/verify-sync.ts +91 -0
- package/server/tsconfig.json +19 -0
- package/server/tsup.config.ts +18 -0
- package/shared/package.json +21 -0
- package/shared/pnpm-lock.yaml +24 -0
- package/shared/src/index.ts +169 -0
- package/shared/tsconfig.json +10 -0
- 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
|
+
};
|