@agent-foundry/studio 1.0.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 (88) hide show
  1. package/README.md +96 -0
  2. package/dist/db/client.d.ts +59 -0
  3. package/dist/db/client.d.ts.map +1 -0
  4. package/dist/db/client.js +51 -0
  5. package/dist/db/client.js.map +1 -0
  6. package/dist/db/deployments.d.ts +65 -0
  7. package/dist/db/deployments.d.ts.map +1 -0
  8. package/dist/db/deployments.js +249 -0
  9. package/dist/db/deployments.js.map +1 -0
  10. package/dist/db/index.d.ts +7 -0
  11. package/dist/db/index.d.ts.map +1 -0
  12. package/dist/db/index.js +7 -0
  13. package/dist/db/index.js.map +1 -0
  14. package/dist/db/projects.d.ts +48 -0
  15. package/dist/db/projects.d.ts.map +1 -0
  16. package/dist/db/projects.js +192 -0
  17. package/dist/db/projects.js.map +1 -0
  18. package/dist/index.d.ts +11 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +15 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/oss/client.d.ts +65 -0
  23. package/dist/oss/client.d.ts.map +1 -0
  24. package/dist/oss/client.js +146 -0
  25. package/dist/oss/client.js.map +1 -0
  26. package/dist/oss/index.d.ts +7 -0
  27. package/dist/oss/index.d.ts.map +1 -0
  28. package/dist/oss/index.js +7 -0
  29. package/dist/oss/index.js.map +1 -0
  30. package/dist/oss/types.d.ts +96 -0
  31. package/dist/oss/types.d.ts.map +1 -0
  32. package/dist/oss/types.js +5 -0
  33. package/dist/oss/types.js.map +1 -0
  34. package/dist/oss/uploader.d.ts +72 -0
  35. package/dist/oss/uploader.d.ts.map +1 -0
  36. package/dist/oss/uploader.js +185 -0
  37. package/dist/oss/uploader.js.map +1 -0
  38. package/dist/types/deployment.d.ts +112 -0
  39. package/dist/types/deployment.d.ts.map +1 -0
  40. package/dist/types/deployment.js +7 -0
  41. package/dist/types/deployment.js.map +1 -0
  42. package/dist/types/index.d.ts +8 -0
  43. package/dist/types/index.d.ts.map +1 -0
  44. package/dist/types/index.js +8 -0
  45. package/dist/types/index.js.map +1 -0
  46. package/dist/types/project.d.ts +90 -0
  47. package/dist/types/project.d.ts.map +1 -0
  48. package/dist/types/project.js +8 -0
  49. package/dist/types/project.js.map +1 -0
  50. package/dist/types/user.d.ts +71 -0
  51. package/dist/types/user.d.ts.map +1 -0
  52. package/dist/types/user.js +8 -0
  53. package/dist/types/user.js.map +1 -0
  54. package/dist/types/workspace.d.ts +88 -0
  55. package/dist/types/workspace.d.ts.map +1 -0
  56. package/dist/types/workspace.js +27 -0
  57. package/dist/types/workspace.js.map +1 -0
  58. package/dist/utils/build.d.ts +78 -0
  59. package/dist/utils/build.d.ts.map +1 -0
  60. package/dist/utils/build.js +148 -0
  61. package/dist/utils/build.js.map +1 -0
  62. package/dist/utils/index.d.ts +6 -0
  63. package/dist/utils/index.d.ts.map +1 -0
  64. package/dist/utils/index.js +6 -0
  65. package/dist/utils/index.js.map +1 -0
  66. package/dist/utils/manifest.d.ts +106 -0
  67. package/dist/utils/manifest.d.ts.map +1 -0
  68. package/dist/utils/manifest.js +109 -0
  69. package/dist/utils/manifest.js.map +1 -0
  70. package/package.json +62 -0
  71. package/src/db/client.ts +92 -0
  72. package/src/db/deployments.ts +316 -0
  73. package/src/db/index.ts +7 -0
  74. package/src/db/projects.ts +246 -0
  75. package/src/db/schema.sql +156 -0
  76. package/src/index.ts +18 -0
  77. package/src/oss/client.ts +183 -0
  78. package/src/oss/index.ts +7 -0
  79. package/src/oss/types.ts +126 -0
  80. package/src/oss/uploader.ts +254 -0
  81. package/src/types/deployment.ts +147 -0
  82. package/src/types/index.ts +8 -0
  83. package/src/types/project.ts +114 -0
  84. package/src/types/user.ts +91 -0
  85. package/src/types/workspace.ts +124 -0
  86. package/src/utils/build.ts +199 -0
  87. package/src/utils/index.ts +6 -0
  88. package/src/utils/manifest.ts +224 -0
@@ -0,0 +1,254 @@
1
+ /**
2
+ * Direct Uploader
3
+ *
4
+ * High-level interface for uploading build artifacts to OSS.
5
+ * Handles STS credential fetching, multipart upload, and progress tracking.
6
+ */
7
+
8
+ import { AliOSSClient } from './client';
9
+ import type {
10
+ UploadFile,
11
+ UploadProgress,
12
+ UploadResult,
13
+ UploadTokenResponse,
14
+ } from './types';
15
+
16
+ /**
17
+ * Configuration for DirectUploader
18
+ */
19
+ export interface DirectUploaderConfig {
20
+ /** BFF base URL (e.g., "http://localhost:11001") */
21
+ bffBaseUrl: string;
22
+
23
+ /** Supabase JWT token for authentication */
24
+ authToken: string;
25
+
26
+ /** Request timeout in milliseconds */
27
+ timeout?: number;
28
+ }
29
+
30
+ /**
31
+ * Upload options
32
+ */
33
+ export interface UploadOptions {
34
+ /** Project ID */
35
+ projectId: string;
36
+
37
+ /** Files to upload */
38
+ files: UploadFile[];
39
+
40
+ /** Optional: specific version string */
41
+ version?: string;
42
+
43
+ /** Progress callback */
44
+ onProgress?: (progress: UploadProgress) => void;
45
+ }
46
+
47
+ /**
48
+ * DirectUploader - handles the complete upload flow
49
+ *
50
+ * 1. Requests STS credentials from BFF
51
+ * 2. Uploads files directly to OSS
52
+ * 3. Notifies BFF of completion
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const uploader = new DirectUploader({
57
+ * bffBaseUrl: 'http://localhost:11001',
58
+ * authToken: 'eyJ...',
59
+ * });
60
+ *
61
+ * const result = await uploader.upload({
62
+ * projectId: 'project-uuid',
63
+ * files: [
64
+ * { path: 'index.html', content: indexHtml, contentType: 'text/html' },
65
+ * { path: 'assets/main.js', content: mainJs, contentType: 'application/javascript' },
66
+ * ],
67
+ * onProgress: (p) => console.log(`${p.percent}%`),
68
+ * });
69
+ * ```
70
+ */
71
+ export class DirectUploader {
72
+ private config: DirectUploaderConfig;
73
+
74
+ constructor(config: DirectUploaderConfig) {
75
+ this.config = config;
76
+ }
77
+
78
+ /**
79
+ * Upload files to OSS
80
+ */
81
+ async upload(options: UploadOptions): Promise<UploadResult> {
82
+ const { projectId, files, version, onProgress } = options;
83
+
84
+ // Report preparing stage
85
+ if (onProgress) {
86
+ onProgress({
87
+ currentFile: '',
88
+ uploadedCount: 0,
89
+ totalCount: files.length,
90
+ uploadedBytes: 0,
91
+ totalBytes: 0,
92
+ percent: 0,
93
+ stage: 'preparing',
94
+ });
95
+ }
96
+
97
+ try {
98
+ // Step 1: Get upload token from BFF
99
+ const tokenResponse = await this.getUploadToken(projectId, version);
100
+
101
+ // Step 2: Create OSS client with STS credentials
102
+ const ossClient = new AliOSSClient({
103
+ region: tokenResponse.region,
104
+ bucket: tokenResponse.bucket,
105
+ credentials: tokenResponse.credentials,
106
+ timeout: this.config.timeout,
107
+ });
108
+
109
+ // Calculate total bytes
110
+ let totalBytes = 0;
111
+ for (const file of files) {
112
+ if (file.content instanceof Blob) {
113
+ totalBytes += file.content.size;
114
+ } else if (file.content instanceof ArrayBuffer) {
115
+ totalBytes += file.content.byteLength;
116
+ } else if (typeof file.content === 'string') {
117
+ totalBytes += new Blob([file.content]).size;
118
+ }
119
+ }
120
+
121
+ // Step 3: Upload files with progress tracking
122
+ let uploadedBytes = 0;
123
+ let uploadedCount = 0;
124
+
125
+ for (const file of files) {
126
+ // Get file size
127
+ let fileSize = 0;
128
+ if (file.content instanceof Blob) {
129
+ fileSize = file.content.size;
130
+ } else if (file.content instanceof ArrayBuffer) {
131
+ fileSize = file.content.byteLength;
132
+ } else if (typeof file.content === 'string') {
133
+ fileSize = new Blob([file.content]).size;
134
+ }
135
+
136
+ // Report progress before upload
137
+ if (onProgress) {
138
+ onProgress({
139
+ currentFile: file.path,
140
+ uploadedCount,
141
+ totalCount: files.length,
142
+ uploadedBytes,
143
+ totalBytes,
144
+ percent: Math.round((uploadedBytes / totalBytes) * 100),
145
+ stage: 'uploading',
146
+ });
147
+ }
148
+
149
+ // Upload file
150
+ const key = `${tokenResponse.keyPrefix}/${file.path}`.replace(/\/+/g, '/');
151
+ await ossClient.uploadFile(key, file.content, file.contentType);
152
+
153
+ uploadedBytes += fileSize;
154
+ uploadedCount++;
155
+ }
156
+
157
+ // Step 4: Notify BFF of completion
158
+ if (onProgress) {
159
+ onProgress({
160
+ currentFile: '',
161
+ uploadedCount: files.length,
162
+ totalCount: files.length,
163
+ uploadedBytes: totalBytes,
164
+ totalBytes,
165
+ percent: 100,
166
+ stage: 'finalizing',
167
+ });
168
+ }
169
+
170
+ const finalResult = await this.completeUpload(
171
+ tokenResponse.deploymentId,
172
+ tokenResponse.bucket,
173
+ tokenResponse.keyPrefix,
174
+ totalBytes,
175
+ files.length
176
+ );
177
+
178
+ return {
179
+ success: true,
180
+ deploymentId: tokenResponse.deploymentId,
181
+ url: finalResult.url,
182
+ bucket: tokenResponse.bucket,
183
+ keyPrefix: tokenResponse.keyPrefix,
184
+ totalBytes,
185
+ fileCount: files.length,
186
+ };
187
+ } catch (error) {
188
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
189
+ return {
190
+ success: false,
191
+ deploymentId: '',
192
+ totalBytes: 0,
193
+ fileCount: 0,
194
+ error: errorMessage,
195
+ };
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Request upload token from BFF
201
+ */
202
+ private async getUploadToken(
203
+ projectId: string,
204
+ version?: string
205
+ ): Promise<UploadTokenResponse> {
206
+ const response = await fetch(`${this.config.bffBaseUrl}/studio/oss/token`, {
207
+ method: 'POST',
208
+ headers: {
209
+ 'Content-Type': 'application/json',
210
+ 'Authorization': `Bearer ${this.config.authToken}`,
211
+ },
212
+ body: JSON.stringify({ projectId, version }),
213
+ });
214
+
215
+ if (!response.ok) {
216
+ const error = await response.text();
217
+ throw new Error(`Failed to get upload token: ${error}`);
218
+ }
219
+
220
+ return response.json();
221
+ }
222
+
223
+ /**
224
+ * Notify BFF that upload is complete
225
+ */
226
+ private async completeUpload(
227
+ deploymentId: string,
228
+ bucket: string,
229
+ keyPrefix: string,
230
+ totalBytes: number,
231
+ fileCount: number
232
+ ): Promise<{ url: string }> {
233
+ const response = await fetch(`${this.config.bffBaseUrl}/studio/deployments/${deploymentId}/complete`, {
234
+ method: 'POST',
235
+ headers: {
236
+ 'Content-Type': 'application/json',
237
+ 'Authorization': `Bearer ${this.config.authToken}`,
238
+ },
239
+ body: JSON.stringify({
240
+ bucket,
241
+ keyPrefix,
242
+ totalBytes,
243
+ fileCount,
244
+ }),
245
+ });
246
+
247
+ if (!response.ok) {
248
+ const error = await response.text();
249
+ throw new Error(`Failed to complete upload: ${error}`);
250
+ }
251
+
252
+ return response.json();
253
+ }
254
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * Deployment types
3
+ *
4
+ * A Deployment represents a build and upload of a project to Alibaba Cloud OSS.
5
+ */
6
+
7
+ /**
8
+ * Deployment status enum
9
+ */
10
+ export type DeploymentStatus =
11
+ | 'pending' // Deployment created, waiting to start
12
+ | 'building' // Running pnpm build
13
+ | 'uploading' // Uploading to OSS
14
+ | 'published' // Successfully deployed
15
+ | 'failed'; // Build or upload failed
16
+
17
+ /**
18
+ * Deployment metadata stored as JSONB
19
+ */
20
+ export interface DeploymentMetadata {
21
+ /** Git commit hash if available */
22
+ commitHash?: string;
23
+
24
+ /** Git branch name */
25
+ branch?: string;
26
+
27
+ /** Build duration in milliseconds */
28
+ buildDurationMs?: number;
29
+
30
+ /** Upload duration in milliseconds */
31
+ uploadDurationMs?: number;
32
+
33
+ /** Number of files uploaded */
34
+ fileCount?: number;
35
+
36
+ /** Node.js version used for build */
37
+ nodeVersion?: string;
38
+
39
+ /** Custom metadata from user */
40
+ custom?: Record<string, unknown>;
41
+ }
42
+
43
+ /**
44
+ * Deployment record
45
+ */
46
+ export interface Deployment {
47
+ /** UUID primary key */
48
+ id: string;
49
+
50
+ /** Associated project ID */
51
+ projectId: string;
52
+
53
+ /** User who created the deployment */
54
+ userId: string;
55
+
56
+ /** Version string (semver or timestamp) */
57
+ version: string;
58
+
59
+ /** Current deployment status */
60
+ status: DeploymentStatus;
61
+
62
+ /** OSS bucket name */
63
+ ossBucket?: string;
64
+
65
+ /** Object key/path in OSS bucket */
66
+ ossKey?: string;
67
+
68
+ /** Public access URL for the deployed app */
69
+ ossUrl?: string;
70
+
71
+ /** Total bundle size in bytes */
72
+ bundleSizeBytes?: number;
73
+
74
+ /** Build output log */
75
+ buildLog?: string;
76
+
77
+ /** Error message if failed */
78
+ errorMessage?: string;
79
+
80
+ /** Additional metadata */
81
+ metadata: DeploymentMetadata;
82
+
83
+ /** ISO timestamp of creation */
84
+ createdAt: string;
85
+
86
+ /** ISO timestamp when published */
87
+ publishedAt?: string;
88
+ }
89
+
90
+ /**
91
+ * Input for creating a new deployment
92
+ */
93
+ export interface CreateDeploymentInput {
94
+ projectId: string;
95
+ version?: string; // Auto-generated if not provided
96
+ metadata?: Partial<DeploymentMetadata>;
97
+ }
98
+
99
+ /**
100
+ * Input for updating deployment status
101
+ */
102
+ export interface UpdateDeploymentInput {
103
+ status?: DeploymentStatus;
104
+ ossBucket?: string;
105
+ ossKey?: string;
106
+ ossUrl?: string;
107
+ bundleSizeBytes?: number;
108
+ buildLog?: string;
109
+ errorMessage?: string;
110
+ metadata?: Partial<DeploymentMetadata>;
111
+ publishedAt?: string;
112
+ }
113
+
114
+ /**
115
+ * Deployment list filters
116
+ */
117
+ export interface DeploymentListFilters {
118
+ /** Filter by project */
119
+ projectId?: string;
120
+
121
+ /** Filter by status */
122
+ status?: DeploymentStatus;
123
+
124
+ /** Limit number of results */
125
+ limit?: number;
126
+
127
+ /** Offset for pagination */
128
+ offset?: number;
129
+
130
+ /** Order by field */
131
+ orderBy?: 'createdAt' | 'publishedAt' | 'version';
132
+
133
+ /** Order direction */
134
+ orderDir?: 'asc' | 'desc';
135
+ }
136
+
137
+ /**
138
+ * Deployment progress event for real-time updates
139
+ */
140
+ export interface DeploymentProgress {
141
+ deploymentId: string;
142
+ status: DeploymentStatus;
143
+ stage: 'init' | 'install' | 'build' | 'bundle' | 'upload' | 'finalize';
144
+ percent: number;
145
+ message?: string;
146
+ timestamp: string;
147
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Type definitions for Agent Foundry Build Studio
3
+ */
4
+
5
+ export * from './project';
6
+ export * from './deployment';
7
+ export * from './workspace';
8
+ export * from './user';
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Studio Project types
3
+ *
4
+ * A StudioProject represents a React/Vite project created and managed
5
+ * within the Build Studio desktop application.
6
+ */
7
+
8
+ /**
9
+ * Supported frontend frameworks
10
+ */
11
+ export type ProjectFramework = 'vite-react' | 'next' | 'remix' | 'vite-vue' | 'vite-svelte';
12
+
13
+ /**
14
+ * Project configuration stored as JSONB in database
15
+ */
16
+ export interface ProjectConfig {
17
+ /** Build command to execute (default: "pnpm build") */
18
+ buildCommand?: string;
19
+
20
+ /** Output directory for build artifacts (default: "dist") */
21
+ outputDir?: string;
22
+
23
+ /** Environment variables to inject during build */
24
+ envVars?: Record<string, string>;
25
+
26
+ /** Base URL for the deployed app */
27
+ baseUrl?: string;
28
+
29
+ /** Custom Vite config overrides */
30
+ viteConfig?: Record<string, unknown>;
31
+ }
32
+
33
+ /**
34
+ * Studio Project - a React/Vite project managed by Build Studio
35
+ */
36
+ export interface StudioProject {
37
+ /** UUID primary key */
38
+ id: string;
39
+
40
+ /** Owner user ID (from Supabase Auth) */
41
+ userId: string;
42
+
43
+ /** Human-readable project name */
44
+ name: string;
45
+
46
+ /** URL-safe unique identifier (unique per user) */
47
+ slug: string;
48
+
49
+ /** Optional project description */
50
+ description?: string;
51
+
52
+ /** Local filesystem path to project root */
53
+ rootPath: string;
54
+
55
+ /** Frontend framework used */
56
+ framework: ProjectFramework;
57
+
58
+ /** Project configuration */
59
+ config: ProjectConfig;
60
+
61
+ /** Parent project ID if this is a fork/copy */
62
+ parentProjectId?: string;
63
+
64
+ /** ISO timestamp of creation */
65
+ createdAt: string;
66
+
67
+ /** ISO timestamp of last update */
68
+ updatedAt: string;
69
+ }
70
+
71
+ /**
72
+ * Input for creating a new project
73
+ */
74
+ export interface CreateProjectInput {
75
+ name: string;
76
+ slug: string;
77
+ description?: string;
78
+ rootPath: string;
79
+ framework?: ProjectFramework;
80
+ config?: ProjectConfig;
81
+ parentProjectId?: string;
82
+ }
83
+
84
+ /**
85
+ * Input for updating a project
86
+ */
87
+ export interface UpdateProjectInput {
88
+ name?: string;
89
+ description?: string;
90
+ config?: Partial<ProjectConfig>;
91
+ }
92
+
93
+ /**
94
+ * Project list filters
95
+ */
96
+ export interface ProjectListFilters {
97
+ /** Filter by framework */
98
+ framework?: ProjectFramework;
99
+
100
+ /** Search by name (case-insensitive) */
101
+ search?: string;
102
+
103
+ /** Limit number of results */
104
+ limit?: number;
105
+
106
+ /** Offset for pagination */
107
+ offset?: number;
108
+
109
+ /** Order by field */
110
+ orderBy?: 'createdAt' | 'updatedAt' | 'name';
111
+
112
+ /** Order direction */
113
+ orderDir?: 'asc' | 'desc';
114
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * User types
3
+ *
4
+ * StudioUser is linked to Supabase Auth user but may have
5
+ * additional Studio-specific metadata.
6
+ */
7
+
8
+ /**
9
+ * User preferences for Studio
10
+ */
11
+ export interface UserPreferences {
12
+ /** Default framework for new projects */
13
+ defaultFramework?: string;
14
+
15
+ /** Default build command */
16
+ defaultBuildCommand?: string;
17
+
18
+ /** Theme preference */
19
+ theme?: 'light' | 'dark' | 'system';
20
+
21
+ /** Editor font size */
22
+ editorFontSize?: number;
23
+
24
+ /** Show line numbers in editor */
25
+ showLineNumbers?: boolean;
26
+
27
+ /** Auto-save interval in seconds (0 = disabled) */
28
+ autoSaveInterval?: number;
29
+
30
+ /** Default chat panel width ratio (0.0 - 1.0) */
31
+ chatPanelRatio?: number;
32
+ }
33
+
34
+ /**
35
+ * Studio user - extends Supabase Auth user with Studio-specific data
36
+ */
37
+ export interface StudioUser {
38
+ /** User ID from Supabase Auth */
39
+ id: string;
40
+
41
+ /** Email address */
42
+ email: string;
43
+
44
+ /** Display name */
45
+ displayName?: string;
46
+
47
+ /** Avatar URL */
48
+ avatarUrl?: string;
49
+
50
+ /** User preferences */
51
+ preferences: UserPreferences;
52
+
53
+ /** ISO timestamp of account creation */
54
+ createdAt: string;
55
+
56
+ /** ISO timestamp of last login */
57
+ lastLoginAt?: string;
58
+ }
59
+
60
+ /**
61
+ * Authentication state
62
+ */
63
+ export interface AuthState {
64
+ /** Whether user is authenticated */
65
+ isAuthenticated: boolean;
66
+
67
+ /** Current user if authenticated */
68
+ user?: StudioUser;
69
+
70
+ /** Supabase access token */
71
+ accessToken?: string;
72
+
73
+ /** Token expiration timestamp */
74
+ expiresAt?: string;
75
+
76
+ /** Whether auth state is loading */
77
+ isLoading: boolean;
78
+ }
79
+
80
+ /**
81
+ * Login credentials
82
+ */
83
+ export interface LoginCredentials {
84
+ email: string;
85
+ password: string;
86
+ }
87
+
88
+ /**
89
+ * OAuth provider
90
+ */
91
+ export type OAuthProvider = 'google' | 'github' | 'apple';