@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,124 @@
1
+ /**
2
+ * Workspace types
3
+ *
4
+ * Based on AF-studio-SPEC.md - Workspace is the runtime representation
5
+ * of a project being actively worked on in the Studio.
6
+ */
7
+
8
+ /**
9
+ * Dev server status
10
+ */
11
+ export type DevServerStatus = 'stopped' | 'starting' | 'running' | 'error';
12
+
13
+ /**
14
+ * Dev server runtime state
15
+ */
16
+ export interface DevServerState {
17
+ /** Current server status */
18
+ status: DevServerStatus;
19
+
20
+ /** Port number if running */
21
+ port?: number;
22
+
23
+ /** Full URL to access dev server */
24
+ url?: string;
25
+
26
+ /** Process ID of dev server */
27
+ pid?: number;
28
+
29
+ /** Last error message if status is 'error' */
30
+ lastError?: string;
31
+
32
+ /** Timestamp when server started */
33
+ startedAt?: string;
34
+ }
35
+
36
+ /**
37
+ * Workspace - runtime state of an active project in Studio
38
+ *
39
+ * This extends StudioProject with runtime-specific fields that are
40
+ * not persisted to the database.
41
+ */
42
+ export interface Workspace {
43
+ /** Unique identifier (ULID recommended) */
44
+ id: string;
45
+
46
+ /** Human-readable workspace name */
47
+ name: string;
48
+
49
+ /** Local filesystem root path */
50
+ rootPath: string;
51
+
52
+ /** ISO timestamp of creation */
53
+ createdAt: string;
54
+
55
+ /** ISO timestamp of last update */
56
+ updatedAt: string;
57
+
58
+ /** Parent workspace ID if this is a fork */
59
+ parentWorkspaceId?: string;
60
+
61
+ /** Associated project ID in database (optional - may be local-only) */
62
+ projectId?: string;
63
+
64
+ /** Dev server runtime state */
65
+ devServer?: DevServerState;
66
+ }
67
+
68
+ /**
69
+ * Session - an agent interaction session within a workspace
70
+ * Based on OpenCode session model
71
+ */
72
+ export interface Session {
73
+ /** Session ID */
74
+ id: string;
75
+
76
+ /** Associated workspace ID */
77
+ workspaceId: string;
78
+
79
+ /** Active agent (plan or build) */
80
+ agentId: 'plan' | 'build' | string;
81
+
82
+ /** Parent session ID if forked */
83
+ parentId?: string;
84
+
85
+ /** ISO timestamp of creation */
86
+ createdAt: string;
87
+
88
+ /** ISO timestamp of last activity */
89
+ lastActivityAt: string;
90
+ }
91
+
92
+ /**
93
+ * Viewport preset for responsive preview
94
+ */
95
+ export interface ViewportPreset {
96
+ name: string;
97
+ width: number;
98
+ height: number;
99
+ deviceScaleFactor?: number;
100
+ }
101
+
102
+ /**
103
+ * Built-in viewport presets from SPEC
104
+ */
105
+ export const VIEWPORT_PRESETS: ViewportPreset[] = [
106
+ { name: 'Desktop', width: 1280, height: 800 },
107
+ { name: 'iPhone SE', width: 375, height: 667, deviceScaleFactor: 2 },
108
+ { name: 'iPhone 14 Pro', width: 390, height: 844, deviceScaleFactor: 3 },
109
+ { name: 'iPad', width: 768, height: 1024, deviceScaleFactor: 2 },
110
+ ];
111
+
112
+ /**
113
+ * Workspace runner command whitelist (for security)
114
+ */
115
+ export const ALLOWED_SCRIPTS = [
116
+ 'install',
117
+ 'dev',
118
+ 'build',
119
+ 'test',
120
+ 'lint',
121
+ 'preview',
122
+ ] as const;
123
+
124
+ export type AllowedScript = typeof ALLOWED_SCRIPTS[number];
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Build utilities for Vite projects
3
+ *
4
+ * Provides helpers for running builds and collecting output files.
5
+ */
6
+
7
+ import type { UploadFile } from '../oss/types';
8
+ import type { ProjectConfig } from '../types/project';
9
+
10
+ /**
11
+ * MIME type mapping for common file extensions
12
+ */
13
+ const MIME_TYPES: Record<string, string> = {
14
+ '.html': 'text/html',
15
+ '.css': 'text/css',
16
+ '.js': 'application/javascript',
17
+ '.mjs': 'application/javascript',
18
+ '.json': 'application/json',
19
+ '.svg': 'image/svg+xml',
20
+ '.png': 'image/png',
21
+ '.jpg': 'image/jpeg',
22
+ '.jpeg': 'image/jpeg',
23
+ '.gif': 'image/gif',
24
+ '.webp': 'image/webp',
25
+ '.ico': 'image/x-icon',
26
+ '.woff': 'font/woff',
27
+ '.woff2': 'font/woff2',
28
+ '.ttf': 'font/ttf',
29
+ '.eot': 'application/vnd.ms-fontobject',
30
+ '.otf': 'font/otf',
31
+ '.map': 'application/json',
32
+ '.txt': 'text/plain',
33
+ '.xml': 'application/xml',
34
+ '.webmanifest': 'application/manifest+json',
35
+ };
36
+
37
+ /**
38
+ * Get MIME type for a file based on extension
39
+ */
40
+ export function getMimeType(filename: string): string {
41
+ const ext = filename.substring(filename.lastIndexOf('.')).toLowerCase();
42
+ return MIME_TYPES[ext] || 'application/octet-stream';
43
+ }
44
+
45
+ /**
46
+ * Build command configuration
47
+ */
48
+ export interface BuildConfig {
49
+ /** Working directory (project root) */
50
+ cwd: string;
51
+
52
+ /** Build command (default: "pnpm build") */
53
+ command?: string;
54
+
55
+ /** Environment variables to set */
56
+ env?: Record<string, string>;
57
+
58
+ /** Timeout in milliseconds (default: 5 minutes) */
59
+ timeout?: number;
60
+ }
61
+
62
+ /**
63
+ * Build result
64
+ */
65
+ export interface BuildResult {
66
+ /** Whether build was successful */
67
+ success: boolean;
68
+
69
+ /** Output directory path */
70
+ outputDir: string;
71
+
72
+ /** Build output log */
73
+ log: string;
74
+
75
+ /** Error message if failed */
76
+ error?: string;
77
+
78
+ /** Build duration in milliseconds */
79
+ durationMs: number;
80
+ }
81
+
82
+ /**
83
+ * Get the default build command based on project config
84
+ */
85
+ export function getDefaultBuildCommand(config: ProjectConfig): string {
86
+ return config.buildCommand || 'pnpm build';
87
+ }
88
+
89
+ /**
90
+ * Get the output directory based on project config
91
+ */
92
+ export function getOutputDir(config: ProjectConfig): string {
93
+ return config.outputDir || 'dist';
94
+ }
95
+
96
+ /**
97
+ * Convert a File object to UploadFile format
98
+ *
99
+ * @param file - File object from file input or FileList
100
+ * @param relativePath - Relative path within the bundle
101
+ */
102
+ export function fileToUploadFile(file: File, relativePath: string): UploadFile {
103
+ return {
104
+ path: relativePath,
105
+ content: file,
106
+ contentType: file.type || getMimeType(file.name),
107
+ };
108
+ }
109
+
110
+ /**
111
+ * Calculate total size of files
112
+ */
113
+ export function calculateTotalSize(files: UploadFile[]): number {
114
+ let total = 0;
115
+ for (const file of files) {
116
+ if (file.content instanceof Blob) {
117
+ total += file.content.size;
118
+ } else if (file.content instanceof ArrayBuffer) {
119
+ total += file.content.byteLength;
120
+ } else if (typeof file.content === 'string') {
121
+ total += new Blob([file.content]).size;
122
+ }
123
+ }
124
+ return total;
125
+ }
126
+
127
+ /**
128
+ * Format file size for display
129
+ */
130
+ export function formatFileSize(bytes: number): string {
131
+ if (bytes === 0) return '0 B';
132
+
133
+ const units = ['B', 'KB', 'MB', 'GB'];
134
+ const k = 1024;
135
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
136
+
137
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${units[i]}`;
138
+ }
139
+
140
+ /**
141
+ * Validate that required files exist in the bundle
142
+ */
143
+ export function validateBundle(files: UploadFile[]): { valid: boolean; errors: string[] } {
144
+ const errors: string[] = [];
145
+ const paths = new Set(files.map(f => f.path));
146
+
147
+ // Check for index.html
148
+ if (!paths.has('index.html')) {
149
+ errors.push('Missing index.html - required for web deployment');
150
+ }
151
+
152
+ // Warn about large files
153
+ for (const file of files) {
154
+ let size = 0;
155
+ if (file.content instanceof Blob) {
156
+ size = file.content.size;
157
+ } else if (file.content instanceof ArrayBuffer) {
158
+ size = file.content.byteLength;
159
+ }
160
+
161
+ // Warn if file is larger than 10MB
162
+ if (size > 10 * 1024 * 1024) {
163
+ errors.push(`Large file detected: ${file.path} (${formatFileSize(size)})`);
164
+ }
165
+ }
166
+
167
+ return {
168
+ valid: errors.length === 0 || !errors.some(e => e.includes('Missing')),
169
+ errors,
170
+ };
171
+ }
172
+
173
+ /**
174
+ * Files to exclude from upload (common patterns)
175
+ */
176
+ export const EXCLUDE_PATTERNS = [
177
+ /^\.git\//,
178
+ /^node_modules\//,
179
+ /^\.DS_Store$/,
180
+ /^Thumbs\.db$/,
181
+ /\.map$/, // Source maps (optional)
182
+ /^\.env/,
183
+ ];
184
+
185
+ /**
186
+ * Check if a file should be excluded from upload
187
+ */
188
+ export function shouldExclude(path: string, includeSourceMaps = false): boolean {
189
+ for (const pattern of EXCLUDE_PATTERNS) {
190
+ if (pattern.test(path)) {
191
+ // Allow source maps if requested
192
+ if (pattern.source === '\\.map$' && includeSourceMaps) {
193
+ continue;
194
+ }
195
+ return true;
196
+ }
197
+ }
198
+ return false;
199
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Utility functions for Studio
3
+ */
4
+
5
+ export * from './build';
6
+ export * from './manifest';
@@ -0,0 +1,224 @@
1
+ /**
2
+ * WebApp Manifest utilities
3
+ *
4
+ * Generates manifest files for deployed web applications,
5
+ * compatible with Agent Foundry's app registry.
6
+ */
7
+
8
+ import type { StudioProject } from '../types/project';
9
+ import type { Deployment } from '../types/deployment';
10
+
11
+ /**
12
+ * WebApp manifest structure for Agent Foundry
13
+ */
14
+ export interface WebAppManifest {
15
+ /** Schema version */
16
+ schema: string;
17
+
18
+ /** App ID (e.g., "com.agentfoundry.myapp") */
19
+ id: string;
20
+
21
+ /** Display name */
22
+ name: string;
23
+
24
+ /** Short name for icons */
25
+ shortName?: string;
26
+
27
+ /** App description */
28
+ description?: string;
29
+
30
+ /** App version */
31
+ version: string;
32
+
33
+ /** Entry URL (index.html) */
34
+ entryUrl: string;
35
+
36
+ /** Icon URLs */
37
+ icons?: {
38
+ src: string;
39
+ sizes: string;
40
+ type?: string;
41
+ }[];
42
+
43
+ /** Theme color */
44
+ themeColor?: string;
45
+
46
+ /** Background color */
47
+ backgroundColor?: string;
48
+
49
+ /** Display mode */
50
+ display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
51
+
52
+ /** Orientation preference */
53
+ orientation?: 'any' | 'portrait' | 'landscape';
54
+
55
+ /** Categories */
56
+ categories?: string[];
57
+
58
+ /** Agent Foundry specific metadata */
59
+ agentFoundry?: {
60
+ /** Creator user ID */
61
+ createdBy: string;
62
+
63
+ /** Deployment ID */
64
+ deploymentId: string;
65
+
66
+ /** Project ID */
67
+ projectId: string;
68
+
69
+ /** Framework used */
70
+ framework: string;
71
+
72
+ /** Build timestamp */
73
+ builtAt: string;
74
+
75
+ /** Bundle size in bytes */
76
+ bundleSize?: number;
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Options for generating a manifest
82
+ */
83
+ export interface ManifestOptions {
84
+ /** App ID prefix (default: "com.agentfoundry.studio") */
85
+ idPrefix?: string;
86
+
87
+ /** Theme color */
88
+ themeColor?: string;
89
+
90
+ /** Background color */
91
+ backgroundColor?: string;
92
+
93
+ /** Icon URL */
94
+ iconUrl?: string;
95
+
96
+ /** Categories */
97
+ categories?: string[];
98
+ }
99
+
100
+ /**
101
+ * Generate a WebApp manifest for a deployed project
102
+ *
103
+ * @param project - The Studio project
104
+ * @param deployment - The deployment record
105
+ * @param options - Optional manifest configuration
106
+ */
107
+ export function generateManifest(
108
+ project: StudioProject,
109
+ deployment: Deployment,
110
+ options: ManifestOptions = {}
111
+ ): WebAppManifest {
112
+ const idPrefix = options.idPrefix ?? 'com.agentfoundry.studio';
113
+ const appId = `${idPrefix}.${project.slug}`;
114
+
115
+ const manifest: WebAppManifest = {
116
+ schema: 'agent-foundry.webapp.v1',
117
+ id: appId,
118
+ name: project.name,
119
+ shortName: project.slug,
120
+ description: project.description,
121
+ version: deployment.version,
122
+ entryUrl: deployment.ossUrl ?? `https://placeholder/${deployment.id}/index.html`,
123
+ display: 'standalone',
124
+ orientation: 'any',
125
+ themeColor: options.themeColor ?? '#000000',
126
+ backgroundColor: options.backgroundColor ?? '#ffffff',
127
+ categories: options.categories ?? ['tools', 'productivity'],
128
+ agentFoundry: {
129
+ createdBy: project.userId,
130
+ deploymentId: deployment.id,
131
+ projectId: project.id,
132
+ framework: project.framework,
133
+ builtAt: deployment.publishedAt ?? deployment.createdAt,
134
+ bundleSize: deployment.bundleSizeBytes,
135
+ },
136
+ };
137
+
138
+ if (options.iconUrl) {
139
+ manifest.icons = [
140
+ { src: options.iconUrl, sizes: '192x192', type: 'image/png' },
141
+ { src: options.iconUrl, sizes: '512x512', type: 'image/png' },
142
+ ];
143
+ }
144
+
145
+ return manifest;
146
+ }
147
+
148
+ /**
149
+ * Generate a standard web app manifest (PWA compatible)
150
+ */
151
+ export function generateWebManifest(
152
+ project: StudioProject,
153
+ options: ManifestOptions = {}
154
+ ): Record<string, unknown> {
155
+ return {
156
+ name: project.name,
157
+ short_name: project.slug,
158
+ description: project.description,
159
+ start_url: '/',
160
+ display: 'standalone',
161
+ background_color: options.backgroundColor ?? '#ffffff',
162
+ theme_color: options.themeColor ?? '#000000',
163
+ icons: options.iconUrl
164
+ ? [
165
+ { src: options.iconUrl, sizes: '192x192', type: 'image/png' },
166
+ { src: options.iconUrl, sizes: '512x512', type: 'image/png' },
167
+ ]
168
+ : [],
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Serialize manifest to JSON string
174
+ */
175
+ export function serializeManifest(manifest: WebAppManifest): string {
176
+ return JSON.stringify(manifest, null, 2);
177
+ }
178
+
179
+ /**
180
+ * Parse manifest from JSON string
181
+ */
182
+ export function parseManifest(json: string): WebAppManifest {
183
+ const parsed = JSON.parse(json);
184
+
185
+ // Validate schema
186
+ if (!parsed.schema || !parsed.schema.startsWith('agent-foundry.webapp.')) {
187
+ throw new Error('Invalid manifest: missing or invalid schema');
188
+ }
189
+
190
+ if (!parsed.id || !parsed.name || !parsed.version) {
191
+ throw new Error('Invalid manifest: missing required fields (id, name, version)');
192
+ }
193
+
194
+ return parsed as WebAppManifest;
195
+ }
196
+
197
+ /**
198
+ * Generate deployment URL path
199
+ *
200
+ * @param userId - User ID
201
+ * @param projectSlug - Project slug
202
+ * @param version - Deployment version
203
+ */
204
+ export function generateDeploymentPath(
205
+ userId: string,
206
+ projectSlug: string,
207
+ version: string
208
+ ): string {
209
+ return `${userId}/${projectSlug}/${version}`;
210
+ }
211
+
212
+ /**
213
+ * Generate a unique deployment version based on timestamp
214
+ */
215
+ export function generateVersionFromTimestamp(date: Date = new Date()): string {
216
+ const year = date.getFullYear();
217
+ const month = String(date.getMonth() + 1).padStart(2, '0');
218
+ const day = String(date.getDate()).padStart(2, '0');
219
+ const hours = String(date.getHours()).padStart(2, '0');
220
+ const minutes = String(date.getMinutes()).padStart(2, '0');
221
+ const seconds = String(date.getSeconds()).padStart(2, '0');
222
+
223
+ return `${year}.${month}.${day}-${hours}${minutes}${seconds}`;
224
+ }