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,196 @@
1
+ import axios from 'axios';
2
+ import type {
3
+ Skill,
4
+ Workspace,
5
+ MarketplaceSkill,
6
+ DashboardStats,
7
+ ApiResponse,
8
+ PaginatedResponse,
9
+ CreateSkillFromGitDto,
10
+ CreateWorkspaceDto,
11
+ UpdateSkillDto,
12
+ UpdateWorkspaceDto,
13
+ PublishSkillDto,
14
+ SkillWorkspace,
15
+ UpdateCheckResponse,
16
+ } from '@skillverse/shared';
17
+
18
+ const API_BASE_URL = import.meta.env.VITE_API_URL || 'http://localhost:3001/api';
19
+
20
+ const api = axios.create({
21
+ baseURL: API_BASE_URL,
22
+ headers: {
23
+ 'Content-Type': 'application/json',
24
+ },
25
+ });
26
+
27
+ // Response interceptor for error handling
28
+ api.interceptors.response.use(
29
+ (response) => response,
30
+ (error) => {
31
+ const message = error.response?.data?.error || error.message || 'An error occurred';
32
+ console.error('API Error:', message);
33
+ return Promise.reject(new Error(message));
34
+ }
35
+ );
36
+
37
+ // Skills API
38
+ export const skillsApi = {
39
+ getAll: async (): Promise<Skill[]> => {
40
+ const { data } = await api.get<ApiResponse<Skill[]>>('/skills');
41
+ return data.data || [];
42
+ },
43
+
44
+ getById: async (id: string): Promise<Skill> => {
45
+ const { data } = await api.get<ApiResponse<Skill>>(`/skills/${id}`);
46
+ return data.data!;
47
+ },
48
+
49
+ createFromGit: async (dto: CreateSkillFromGitDto): Promise<Skill> => {
50
+ const { data } = await api.post<ApiResponse<Skill>>('/skills/from-git', dto);
51
+ return data.data!;
52
+ },
53
+
54
+ createFromLocal: async (name: string, file: File, description?: string): Promise<Skill> => {
55
+ const formData = new FormData();
56
+ formData.append('name', name);
57
+ formData.append('file', file);
58
+ if (description) formData.append('description', description);
59
+
60
+ const { data } = await api.post<ApiResponse<Skill>>('/skills/from-local', formData, {
61
+ headers: { 'Content-Type': 'multipart/form-data' },
62
+ });
63
+ return data.data!;
64
+ },
65
+
66
+ update: async (id: string, dto: UpdateSkillDto): Promise<Skill> => {
67
+ const { data } = await api.put<ApiResponse<Skill>>(`/skills/${id}`, dto);
68
+ return data.data!;
69
+ },
70
+
71
+ delete: async (id: string, removeFiles = true): Promise<void> => {
72
+ await api.delete(`/skills/${id}?removeFiles=${removeFiles}`);
73
+ },
74
+
75
+ linkToWorkspace: async (skillId: string, workspaceId: string): Promise<SkillWorkspace> => {
76
+ const { data } = await api.post<ApiResponse<SkillWorkspace>>(`/skills/${skillId}/link`, {
77
+ workspaceId,
78
+ });
79
+ return data.data!;
80
+ },
81
+
82
+ unlinkFromWorkspace: async (skillId: string, workspaceId: string): Promise<void> => {
83
+ await api.delete(`/skills/${skillId}/unlink/${workspaceId}`);
84
+ },
85
+
86
+ checkUpdate: async (id: string): Promise<UpdateCheckResponse> => {
87
+ const { data } = await api.get<ApiResponse<UpdateCheckResponse>>(`/skills/${id}/check-update`);
88
+ return data.data!;
89
+ },
90
+
91
+ upgrade: async (id: string): Promise<Skill> => {
92
+ const { data } = await api.post<ApiResponse<Skill>>(`/skills/${id}/upgrade`);
93
+ return data.data!;
94
+ },
95
+
96
+ checkUpdates: async (ids?: string[]): Promise<Record<string, UpdateCheckResponse>> => {
97
+ const { data } = await api.post<ApiResponse<Record<string, UpdateCheckResponse>>>('/skills/check-updates', { ids });
98
+ return data.data!;
99
+ },
100
+
101
+ getSkillMd: async (id: string): Promise<{ exists: boolean; content: string | null }> => {
102
+ const { data } = await api.get<ApiResponse<{ exists: boolean; content: string | null }>>(`/skills/${id}/skill-md`);
103
+ return data.data!;
104
+ },
105
+ };
106
+
107
+ // Workspaces API
108
+ export const workspacesApi = {
109
+ getAll: async (): Promise<Workspace[]> => {
110
+ const { data } = await api.get<ApiResponse<Workspace[]>>('/workspaces');
111
+ return data.data || [];
112
+ },
113
+
114
+ getById: async (id: string): Promise<Workspace> => {
115
+ const { data } = await api.get<ApiResponse<Workspace>>(`/workspaces/${id}`);
116
+ return data.data!;
117
+ },
118
+
119
+ create: async (dto: CreateWorkspaceDto): Promise<Workspace> => {
120
+ const { data } = await api.post<ApiResponse<Workspace>>('/workspaces', dto);
121
+ return data.data!;
122
+ },
123
+
124
+ update: async (id: string, dto: UpdateWorkspaceDto): Promise<Workspace> => {
125
+ const { data } = await api.put<ApiResponse<Workspace>>(`/workspaces/${id}`, dto);
126
+ return data.data!;
127
+ },
128
+
129
+ delete: async (id: string): Promise<void> => {
130
+ await api.delete(`/workspaces/${id}`);
131
+ },
132
+
133
+ detectSkills: async (id: string): Promise<{ name: string; hasSkillMd: boolean; path: string }[]> => {
134
+ const { data } = await api.post<ApiResponse<{ name: string; hasSkillMd: boolean; path: string }[]>>(`/workspaces/${id}/detect-skills`);
135
+ return data.data || [];
136
+ },
137
+
138
+ migrateSkills: async (id: string, skillNames: string[]): Promise<{ success: boolean; migrated: string[]; errors: string[] }> => {
139
+ const { data } = await api.post<ApiResponse<{ success: boolean; migrated: string[]; errors: string[] }>>(`/workspaces/${id}/migrate-skills`, { skillNames });
140
+ return data.data!;
141
+ },
142
+ };
143
+
144
+ // Marketplace API
145
+ export const marketplaceApi = {
146
+ getSkills: async (
147
+ page = 1,
148
+ pageSize = 20,
149
+ search?: string
150
+ ): Promise<PaginatedResponse<MarketplaceSkill>> => {
151
+ const params = new URLSearchParams({
152
+ page: page.toString(),
153
+ pageSize: pageSize.toString(),
154
+ });
155
+ if (search) params.append('search', search);
156
+
157
+ const { data } = await api.get<ApiResponse<PaginatedResponse<MarketplaceSkill>>>(
158
+ `/marketplace/skills?${params}`
159
+ );
160
+ return data.data!;
161
+ },
162
+
163
+ getSkillById: async (id: string): Promise<MarketplaceSkill> => {
164
+ const { data } = await api.get<ApiResponse<MarketplaceSkill>>(`/marketplace/skills/${id}`);
165
+ return data.data!;
166
+ },
167
+
168
+ publish: async (dto: PublishSkillDto): Promise<MarketplaceSkill> => {
169
+ const { data } = await api.post<ApiResponse<MarketplaceSkill>>('/marketplace/publish', dto);
170
+ return data.data!;
171
+ },
172
+
173
+ install: async (id: string): Promise<Skill> => {
174
+ const { data } = await api.post<ApiResponse<Skill>>(`/marketplace/install/${id}`);
175
+ return data.data!;
176
+ },
177
+
178
+ unpublish: async (skillId: string): Promise<void> => {
179
+ await api.delete(`/marketplace/unpublish/${skillId}`);
180
+ },
181
+ };
182
+
183
+ // Dashboard API
184
+ export const dashboardApi = {
185
+ getStats: async (): Promise<DashboardStats> => {
186
+ const { data } = await api.get<ApiResponse<DashboardStats>>('/dashboard/stats');
187
+ return data.data!;
188
+ },
189
+
190
+ getActivity: async (): Promise<any[]> => {
191
+ const { data } = await api.get<ApiResponse<any[]>>('/dashboard/activity');
192
+ return data.data || [];
193
+ },
194
+ };
195
+
196
+ export default api;
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.tsx'
5
+
6
+ createRoot(document.getElementById('root')!).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,209 @@
1
+ import { useEffect } from 'react';
2
+ import { useAppStore } from '../stores/appStore';
3
+ import { Package, FolderOpen, Link2, Store, TrendingUp, Clock } from 'lucide-react';
4
+ import { LoadingPage } from '../components/LoadingSpinner';
5
+ import Tooltip from '../components/Tooltip';
6
+ import { clsx } from 'clsx';
7
+
8
+ export default function Dashboard() {
9
+ const { stats, statsLoading, fetchStats } = useAppStore();
10
+
11
+ useEffect(() => {
12
+ fetchStats();
13
+ }, [fetchStats]);
14
+
15
+ if (statsLoading && !stats) {
16
+ return <LoadingPage />;
17
+ }
18
+
19
+ const statCards = [
20
+ {
21
+ label: 'Total Skills',
22
+ value: stats?.totalSkills || 0,
23
+ icon: Package,
24
+ color: 'from-primary-500 to-primary-600',
25
+ bgColor: 'bg-primary-500/10',
26
+ },
27
+ {
28
+ label: 'Workspaces',
29
+ value: stats?.totalWorkspaces || 0,
30
+ icon: FolderOpen,
31
+ color: 'from-accent-500 to-accent-600',
32
+ bgColor: 'bg-accent-500/10',
33
+ },
34
+ {
35
+ label: 'Active Links',
36
+ value: stats?.totalLinks || 0,
37
+ icon: Link2,
38
+ color: 'from-green-500 to-green-600',
39
+ bgColor: 'bg-green-500/10',
40
+ },
41
+ {
42
+ label: 'Published',
43
+ value: stats?.marketplaceSkills || 0,
44
+ icon: Store,
45
+ color: 'from-orange-500 to-orange-600',
46
+ bgColor: 'bg-orange-500/10',
47
+ },
48
+ ];
49
+
50
+ return (
51
+ <div className="space-y-6 sm:space-y-8 animate-fade-in">
52
+ {/* Header */}
53
+ <div>
54
+ <h1 className="text-2xl sm:text-3xl font-bold text-dark-100">Dashboard</h1>
55
+ <p className="text-dark-400 mt-1 text-sm sm:text-base text-justified">
56
+ Overview of your skill management system
57
+ </p>
58
+ </div>
59
+
60
+ {/* Stats Grid - Responsive */}
61
+ <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 sm:gap-6">
62
+ {statCards.map((stat) => (
63
+ <div
64
+ key={stat.label}
65
+ className="card hover:scale-[1.02] transition-transform p-4 sm:p-6"
66
+ >
67
+ <div className="flex items-start justify-between">
68
+ <div className="min-w-0 flex-1">
69
+ <p className="text-xs sm:text-sm text-dark-400 truncate">{stat.label}</p>
70
+ <p className="text-xl sm:text-3xl font-bold text-dark-100 mt-1">
71
+ {stat.value}
72
+ </p>
73
+ </div>
74
+ <div
75
+ className={clsx(
76
+ 'w-10 h-10 sm:w-12 sm:h-12 rounded-xl flex items-center justify-center shrink-0 ml-2',
77
+ stat.bgColor
78
+ )}
79
+ >
80
+ <stat.icon
81
+ className={clsx(
82
+ 'w-5 h-5 sm:w-6 sm:h-6 bg-gradient-to-r bg-clip-text',
83
+ stat.color.replace('from-', 'text-').split(' ')[0]
84
+ )}
85
+ />
86
+ </div>
87
+ </div>
88
+ <div className="mt-3 sm:mt-4 flex items-center gap-1 text-xs text-dark-500">
89
+ <TrendingUp className="w-3.5 h-3.5 text-green-400" />
90
+ <span className="text-green-400">Active</span>
91
+ </div>
92
+ </div>
93
+ ))}
94
+ </div>
95
+
96
+ {/* Recent Skills */}
97
+ <div className="card">
98
+ <div className="flex items-center justify-between mb-4 sm:mb-6">
99
+ <h2 className="text-base sm:text-lg font-semibold text-dark-100">Recent Skills</h2>
100
+ <Clock className="w-5 h-5 text-dark-400" />
101
+ </div>
102
+
103
+ {stats?.recentSkills && stats.recentSkills.length > 0 ? (
104
+ <div className="space-y-2 sm:space-y-3">
105
+ {stats.recentSkills.map((skill: any) => (
106
+ <div
107
+ key={skill.id}
108
+ className="flex items-center justify-between p-3 sm:p-4 rounded-xl bg-dark-750 hover:bg-dark-700 transition-colors gap-3"
109
+ >
110
+ <div className="flex items-center gap-3 sm:gap-4 min-w-0 flex-1">
111
+ <div
112
+ className={clsx(
113
+ 'w-8 h-8 sm:w-10 sm:h-10 rounded-lg flex items-center justify-center shrink-0',
114
+ skill.source === 'git'
115
+ ? 'bg-primary-500/10 text-primary-400'
116
+ : 'bg-accent-500/10 text-accent-400'
117
+ )}
118
+ >
119
+ <Package className="w-4 h-4 sm:w-5 sm:h-5" />
120
+ </div>
121
+ <div className="min-w-0 flex-1">
122
+ <Tooltip content={skill.name} position="top">
123
+ <p className="font-medium text-dark-100 text-sm sm:text-base truncate max-w-[200px] sm:max-w-none">
124
+ {skill.name}
125
+ </p>
126
+ </Tooltip>
127
+ <p className="text-xs sm:text-sm text-dark-500">
128
+ {skill.source === 'git' ? 'Git Repository' : 'Local Upload'}
129
+ </p>
130
+ </div>
131
+ </div>
132
+ <div className="text-right shrink-0">
133
+ <p className="text-xs sm:text-sm text-dark-400">
134
+ {new Date(skill.installDate).toLocaleDateString()}
135
+ </p>
136
+ <p className="text-xs text-dark-500">
137
+ {skill.linkedWorkspaces?.length || 0} linked
138
+ </p>
139
+ </div>
140
+ </div>
141
+ ))}
142
+ </div>
143
+ ) : (
144
+ <div className="text-center py-6 sm:py-8">
145
+ <Package className="w-10 h-10 sm:w-12 sm:h-12 mx-auto text-dark-600 mb-3" />
146
+ <p className="text-dark-400 text-sm sm:text-base">No skills yet</p>
147
+ <p className="text-xs sm:text-sm text-dark-500 mt-1">
148
+ Add your first skill to get started
149
+ </p>
150
+ </div>
151
+ )}
152
+ </div>
153
+
154
+ {/* Quick Actions - Responsive */}
155
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
156
+ <a
157
+ href="/skills"
158
+ className="card group cursor-pointer hover:border-primary-500/50 transition-all"
159
+ >
160
+ <div className="flex items-center gap-3 sm:gap-4">
161
+ <div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-primary-500/10 flex items-center justify-center group-hover:scale-110 transition-transform shrink-0">
162
+ <Package className="w-5 h-5 sm:w-6 sm:h-6 text-primary-400" />
163
+ </div>
164
+ <div className="min-w-0 flex-1">
165
+ <p className="font-medium text-dark-100 group-hover:text-primary-400 transition-colors text-sm sm:text-base">
166
+ Manage Skills
167
+ </p>
168
+ <p className="text-xs sm:text-sm text-dark-500 truncate">View and organize your skills</p>
169
+ </div>
170
+ </div>
171
+ </a>
172
+
173
+ <a
174
+ href="/workspaces"
175
+ className="card group cursor-pointer hover:border-accent-500/50 transition-all"
176
+ >
177
+ <div className="flex items-center gap-3 sm:gap-4">
178
+ <div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-accent-500/10 flex items-center justify-center group-hover:scale-110 transition-transform shrink-0">
179
+ <FolderOpen className="w-5 h-5 sm:w-6 sm:h-6 text-accent-400" />
180
+ </div>
181
+ <div className="min-w-0 flex-1">
182
+ <p className="font-medium text-dark-100 group-hover:text-accent-400 transition-colors text-sm sm:text-base">
183
+ Workspaces
184
+ </p>
185
+ <p className="text-xs sm:text-sm text-dark-500 truncate">Configure your workspaces</p>
186
+ </div>
187
+ </div>
188
+ </a>
189
+
190
+ <a
191
+ href="/marketplace"
192
+ className="card group cursor-pointer hover:border-orange-500/50 transition-all sm:col-span-2 lg:col-span-1"
193
+ >
194
+ <div className="flex items-center gap-3 sm:gap-4">
195
+ <div className="w-10 h-10 sm:w-12 sm:h-12 rounded-xl bg-orange-500/10 flex items-center justify-center group-hover:scale-110 transition-transform shrink-0">
196
+ <Store className="w-5 h-5 sm:w-6 sm:h-6 text-orange-400" />
197
+ </div>
198
+ <div className="min-w-0 flex-1">
199
+ <p className="font-medium text-dark-100 group-hover:text-orange-400 transition-colors text-sm sm:text-base">
200
+ Marketplace
201
+ </p>
202
+ <p className="text-xs sm:text-sm text-dark-500 truncate">Browse and share skills</p>
203
+ </div>
204
+ </div>
205
+ </a>
206
+ </div>
207
+ </div>
208
+ );
209
+ }