zelune 1.6.2 → 1.6.4

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 (39) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/bin/cli.mjs +13 -54
  3. package/dist/assets/CheckoutScreen-6IdrpKgm.js +229 -0
  4. package/dist/assets/HomeScreen-Dxe9L4I2.js +316 -0
  5. package/dist/assets/LoginScreen-Dp1awI4F.js +189 -0
  6. package/dist/assets/ProductDetailScreen-Wa5_nlRP.js +196 -0
  7. package/dist/assets/ProfileScreen-riAU6vVh.js +185 -0
  8. package/dist/assets/index-hEzd4JVy.js +44697 -0
  9. package/{index.html → dist/index.html} +3 -7
  10. package/mcp/server.mjs +1370 -34
  11. package/package.json +10 -17
  12. package/plugin.json +1 -1
  13. package/socket.yml +11 -0
  14. package/mcp/server.ts +0 -446
  15. package/plugins/viteReactNativeFallbackPlugin.ts +0 -519
  16. package/plugins/viteRnSourceInject.ts +0 -117
  17. package/plugins/viteVirtualScreensPlugin.ts +0 -216
  18. package/src/canvas/CanvasApp.tsx +0 -1655
  19. package/src/canvas/FlowArrowsLayer.tsx +0 -239
  20. package/src/canvas/InspectorPanel.tsx +0 -1494
  21. package/src/canvas/ScreenArtboard.tsx +0 -1491
  22. package/src/canvas/types.ts +0 -23
  23. package/src/main.tsx +0 -80
  24. package/src/runtime/MockNavigationContext.tsx +0 -1795
  25. package/src/runtime/ScreenErrorBoundary.tsx +0 -86
  26. package/src/runtime/nativewindShim.ts +0 -997
  27. package/src/runtime/reactNativeShim.ts +0 -240
  28. package/src/screens/CheckoutScreen.tsx +0 -229
  29. package/src/screens/HomeScreen.tsx +0 -288
  30. package/src/screens/LoginScreen.tsx +0 -176
  31. package/src/screens/ProductDetailScreen.tsx +0 -207
  32. package/src/screens/ProfileScreen.tsx +0 -172
  33. package/src/screens/index.ts +0 -1
  34. package/src/server/apiMiddleware.ts +0 -375
  35. package/src/server/astEngine.ts +0 -2553
  36. package/src/vite-env.d.ts +0 -7
  37. package/tsconfig.json +0 -23
  38. package/vite.config.ts +0 -340
  39. /package/{src/styles.css → dist/assets/index-C7nrS82N.css} +0 -0
@@ -1,172 +0,0 @@
1
- import React from 'react';
2
- import {
3
- View,
4
- Text,
5
- TouchableOpacity,
6
- StyleSheet,
7
- } from 'react-native';
8
- import { useNavigation } from '../runtime/MockNavigationContext';
9
-
10
- export default function ProfileScreen() {
11
- const navigation = useNavigation();
12
-
13
- return (
14
- <View style={styles.container}>
15
- <View style={styles.profileHeader}>
16
- <View style={styles.avatarLarge}>
17
- <Text style={styles.avatarInitials}>AX</Text>
18
- </View>
19
- <Text style={styles.memberName}>Alex Mercer</Text>
20
- <Text style={styles.memberTier}>LEAD ACOUSTIC ENGINEER · TIER 01</Text>
21
- </View>
22
-
23
- <View style={styles.statsRow}>
24
- <View style={styles.statCard}>
25
- <Text style={styles.statNumber}>14</Text>
26
- <Text style={styles.statCaption}>UNITS</Text>
27
- </View>
28
- <View style={styles.statCard}>
29
- <Text style={styles.statNumber}>4,820</Text>
30
- <Text style={styles.statCaption}>CREDITS</Text>
31
- </View>
32
- <View style={styles.statCard}>
33
- <Text style={styles.statNumber}>LAB</Text>
34
- <Text style={styles.statCaption}>ACCESS</Text>
35
- </View>
36
- </View>
37
-
38
- <View style={styles.menuList}>
39
- <TouchableOpacity
40
- activeOpacity={0.78}
41
- style={styles.menuItem}
42
- onPress={() => navigation.navigate('Home')}
43
- >
44
- <Text style={styles.menuItemTitle}>Reference Hardware Catalog</Text>
45
- <Text style={styles.menuItemArrow}>→</Text>
46
- </TouchableOpacity>
47
-
48
- <TouchableOpacity
49
- activeOpacity={0.78}
50
- style={styles.menuItem}
51
- onPress={() => navigation.navigate('Checkout')}
52
- >
53
- <Text style={styles.menuItemTitle}>Active Allocation &amp; Settlement</Text>
54
- <Text style={styles.menuItemArrow}>→</Text>
55
- </TouchableOpacity>
56
- </View>
57
-
58
- <TouchableOpacity
59
- activeOpacity={0.78}
60
- style={styles.signOutButton}
61
- onPress={() => navigation.navigate('Login')}
62
- >
63
- <Text style={styles.signOutText}>Terminate Studio Session</Text>
64
- </TouchableOpacity>
65
- </View>
66
- );
67
- }
68
-
69
- const styles = StyleSheet.create({
70
- container: {
71
- flex: 1,
72
- backgroundColor: '#0A0B0E',
73
- padding: 20,
74
- justifyContent: 'space-between',
75
- },
76
- profileHeader: {
77
- alignItems: 'center',
78
- marginTop: 14,
79
- gap: 6,
80
- },
81
- avatarLarge: {
82
- width: 68,
83
- height: 68,
84
- borderRadius: 16,
85
- backgroundColor: '#13161E',
86
- borderWidth: 1,
87
- borderColor: '#262D3D',
88
- alignItems: 'center',
89
- justifyContent: 'center',
90
- marginBottom: 6,
91
- },
92
- avatarInitials: {
93
- color: '#38BDF8',
94
- fontSize: 22,
95
- fontWeight: '700',
96
- letterSpacing: 1,
97
- },
98
- memberName: {
99
- color: '#F8FAFC',
100
- fontSize: 22,
101
- fontWeight: '700',
102
- letterSpacing: -0.3,
103
- },
104
- memberTier: {
105
- color: '#64748B',
106
- fontSize: 10,
107
- fontWeight: '700',
108
- letterSpacing: 1,
109
- },
110
- statsRow: {
111
- flexDirection: 'row',
112
- gap: 8,
113
- },
114
- statCard: {
115
- flex: 1,
116
- backgroundColor: '#11141C',
117
- borderRadius: 12,
118
- paddingVertical: 14,
119
- alignItems: 'center',
120
- borderWidth: 1,
121
- borderColor: '#1E2433',
122
- },
123
- statNumber: {
124
- color: '#F8FAFC',
125
- fontSize: 17,
126
- fontWeight: '700',
127
- },
128
- statCaption: {
129
- color: '#64748B',
130
- fontSize: 9,
131
- fontWeight: '700',
132
- letterSpacing: 0.8,
133
- marginTop: 3,
134
- },
135
- menuList: {
136
- gap: 10,
137
- },
138
- menuItem: {
139
- backgroundColor: '#11141C',
140
- borderRadius: 12,
141
- paddingHorizontal: 16,
142
- paddingVertical: 15,
143
- flexDirection: 'row',
144
- justifyContent: 'space-between',
145
- alignItems: 'center',
146
- borderWidth: 1,
147
- borderColor: '#1E2433',
148
- },
149
- menuItemTitle: {
150
- color: '#E2E8F0',
151
- fontSize: 13,
152
- fontWeight: '600',
153
- },
154
- menuItemArrow: {
155
- color: '#38BDF8',
156
- fontSize: 15,
157
- fontWeight: '700',
158
- },
159
- signOutButton: {
160
- backgroundColor: '#16131A',
161
- borderRadius: 10,
162
- paddingVertical: 14,
163
- alignItems: 'center',
164
- borderWidth: 1,
165
- borderColor: '#3B1D2A',
166
- },
167
- signOutText: {
168
- color: '#FB7185',
169
- fontSize: 13,
170
- fontWeight: '600',
171
- },
172
- });
@@ -1 +0,0 @@
1
- export { SCREEN_COMPONENTS, SCREEN_ORDER } from 'virtual:zelune-screens';
@@ -1,375 +0,0 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import type { IncomingMessage, ServerResponse } from 'node:http';
4
- import type { Plugin } from 'vite';
5
- import {
6
- discoverScreens,
7
- getActiveProjectRoot,
8
- inspectElement,
9
- patchElement,
10
- reorderElement,
11
- insertElement,
12
- duplicateElement,
13
- deleteElement,
14
- undoLastPatch,
15
- redoLastPatch,
16
- getHistoryStatus,
17
- type PatchPayload,
18
- type ReorderPayload,
19
- type InsertElementPayload,
20
- type DuplicateElementPayload,
21
- type DeleteElementPayload,
22
- } from './astEngine';
23
-
24
- const MAX_BODY_BYTES = 1_000_000;
25
- const LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '[::1]', '::1']);
26
- const FORBIDDEN_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
27
-
28
- export function isLoopbackHostHeader(hostHeader: string | undefined): boolean {
29
- if (!hostHeader) return false;
30
- const trimmed = hostHeader.trim().toLowerCase();
31
- if (trimmed.startsWith('[')) {
32
- const closeIdx = trimmed.indexOf(']');
33
- if (closeIdx === -1) return false;
34
- const ipv6Host = trimmed.slice(0, closeIdx + 1);
35
- return LOOPBACK_HOSTS.has(ipv6Host);
36
- }
37
- const hostname = trimmed.split(':')[0];
38
- return LOOPBACK_HOSTS.has(hostname);
39
- }
40
-
41
- export function verifyApiRequestSecurity(req: {
42
- method?: string;
43
- headers: Record<string, string | string[] | undefined>;
44
- }): { allowed: boolean; reason?: string } {
45
- const host = typeof req.headers.host === 'string' ? req.headers.host : undefined;
46
- if (!isLoopbackHostHeader(host)) {
47
- return {
48
- allowed: false,
49
- reason: 'Forbidden Host header: Local API only accepts loopback connections (127.0.0.1, localhost, [::1])',
50
- };
51
- }
52
-
53
- const secFetchSite =
54
- typeof req.headers['sec-fetch-site'] === 'string'
55
- ? req.headers['sec-fetch-site'].toLowerCase()
56
- : undefined;
57
- if (secFetchSite && secFetchSite !== 'same-origin' && secFetchSite !== 'none') {
58
- return {
59
- allowed: false,
60
- reason: `Forbidden cross-site request (Sec-Fetch-Site: ${secFetchSite})`,
61
- };
62
- }
63
-
64
- const origin = typeof req.headers.origin === 'string' ? req.headers.origin : undefined;
65
- if (origin) {
66
- try {
67
- const parsedOrigin = new URL(origin);
68
- if (!LOOPBACK_HOSTS.has(parsedOrigin.hostname.toLowerCase())) {
69
- return {
70
- allowed: false,
71
- reason: `Forbidden cross-origin request from ${origin}`,
72
- };
73
- }
74
- } catch {
75
- return {
76
- allowed: false,
77
- reason: 'Malformed Origin header',
78
- };
79
- }
80
- }
81
-
82
- if (req.method === 'POST') {
83
- const contentType =
84
- typeof req.headers['content-type'] === 'string'
85
- ? req.headers['content-type'].toLowerCase()
86
- : '';
87
- if (!contentType.startsWith('application/json')) {
88
- return {
89
- allowed: false,
90
- reason: 'POST requests to /api/* must specify Content-Type: application/json',
91
- };
92
- }
93
- }
94
-
95
- return { allowed: true };
96
- }
97
-
98
- export function sanitizeLayoutPayload(
99
- input: unknown
100
- ): Record<string, { x: number; y: number; width: number; height: number }> {
101
- if (!input || typeof input !== 'object' || Array.isArray(input)) {
102
- throw new Error('Invalid layout payload: expected an object map of screen coordinates');
103
- }
104
-
105
- const sanitized: Record<string, { x: number; y: number; width: number; height: number }> =
106
- Object.create(null);
107
- const entries = Object.entries(input as Record<string, unknown>);
108
- if (entries.length > 200) {
109
- throw new Error('Invalid layout payload: too many screen entries');
110
- }
111
-
112
- for (const [screenId, rawBox] of entries) {
113
- if (!screenId || FORBIDDEN_KEYS.has(screenId) || screenId.length > 120) {
114
- throw new Error(`Invalid screen key in layout payload: "${screenId}"`);
115
- }
116
- if (!rawBox || typeof rawBox !== 'object' || Array.isArray(rawBox)) {
117
- throw new Error(`Invalid layout coordinates for screen "${screenId}"`);
118
- }
119
- const box = rawBox as Record<string, unknown>;
120
- const x = Number(box.x);
121
- const y = Number(box.y);
122
- const width = Number(box.width);
123
- const height = Number(box.height);
124
-
125
- if (
126
- !Number.isFinite(x) ||
127
- !Number.isFinite(y) ||
128
- !Number.isFinite(width) ||
129
- !Number.isFinite(height) ||
130
- Math.abs(x) > 100_000 ||
131
- Math.abs(y) > 100_000 ||
132
- width < 100 ||
133
- width > 5_000 ||
134
- height < 100 ||
135
- height > 5_000
136
- ) {
137
- throw new Error(`Out-of-bounds layout coordinates for screen "${screenId}"`);
138
- }
139
-
140
- sanitized[screenId] = {
141
- x: Math.round(x),
142
- y: Math.round(y),
143
- width: Math.round(width),
144
- height: Math.round(height),
145
- };
146
- }
147
-
148
- return sanitized;
149
- }
150
-
151
- export async function readJsonBody<T>(req: IncomingMessage): Promise<T> {
152
- return new Promise((resolve, reject) => {
153
- let raw = '';
154
- let byteCount = 0;
155
- let aborted = false;
156
-
157
- req.on('data', (chunk: Buffer | string) => {
158
- if (aborted) return;
159
- const chunkBytes =
160
- typeof chunk === 'string' ? Buffer.byteLength(chunk, 'utf-8') : chunk.length;
161
- byteCount += chunkBytes;
162
-
163
- if (byteCount > MAX_BODY_BYTES) {
164
- aborted = true;
165
- req.destroy();
166
- reject(new Error('Request payload too large'));
167
- return;
168
- }
169
-
170
- raw += typeof chunk === 'string' ? chunk : chunk.toString('utf-8');
171
- });
172
-
173
- req.on('end', () => {
174
- if (aborted) return;
175
- try {
176
- resolve(raw ? (JSON.parse(raw) as T) : ({} as T));
177
- } catch (err) {
178
- reject(err);
179
- }
180
- });
181
-
182
- req.on('error', (err) => {
183
- if (!aborted) {
184
- aborted = true;
185
- reject(err);
186
- }
187
- });
188
- });
189
- }
190
-
191
- function sendJson(res: ServerResponse, status: number, payload: unknown) {
192
- res.statusCode = status;
193
- res.setHeader('Content-Type', 'application/json; charset=utf-8');
194
- res.setHeader('X-Content-Type-Options', 'nosniff');
195
- res.end(JSON.stringify(payload));
196
- }
197
-
198
- export function viteCanvasApiMiddleware(workspaceRoot: string): Plugin {
199
- async function resolveConnectedProjectName(): Promise<string> {
200
- const activeRoot = getActiveProjectRoot(workspaceRoot);
201
- const dirName = path.basename(activeRoot);
202
- try {
203
- const appJsonRaw = await fs.readFile(path.join(activeRoot, 'app.json'), 'utf-8');
204
- const appJson = JSON.parse(appJsonRaw) as {
205
- expo?: { name?: string };
206
- name?: string;
207
- };
208
- if (appJson?.expo?.name) return appJson.expo.name;
209
- if (appJson?.name) return appJson.name;
210
- } catch {
211
- // No app.json present
212
- }
213
- try {
214
- const pkgRaw = await fs.readFile(path.join(activeRoot, 'package.json'), 'utf-8');
215
- const pkg = JSON.parse(pkgRaw) as { name?: string };
216
- if (pkg?.name && pkg.name.toLowerCase() !== 'zelune') {
217
- return pkg.name;
218
- }
219
- } catch {
220
- // Fallback to directory name
221
- }
222
- return dirName;
223
- }
224
-
225
- return {
226
- name: 'vite-canvas-api-middleware',
227
- configureServer(server) {
228
- server.middlewares.use(async (req, res, next) => {
229
- if (!req.url || !req.url.startsWith('/api/')) {
230
- return next();
231
- }
232
-
233
- const securityCheck = verifyApiRequestSecurity(req);
234
- if (!securityCheck.allowed) {
235
- return sendJson(res, 403, { ok: false, error: securityCheck.reason });
236
- }
237
-
238
- const activeRoot = getActiveProjectRoot(workspaceRoot);
239
- const layoutFilePath = path.resolve(activeRoot, '.canvas-layout.json');
240
- const urlObj = new URL(req.url, 'http://127.0.0.1');
241
- const pathname = urlObj.pathname;
242
-
243
- try {
244
- // GET /api/screens
245
- if (req.method === 'GET' && pathname === '/api/screens') {
246
- const [screens, projectName] = await Promise.all([
247
- discoverScreens(workspaceRoot),
248
- resolveConnectedProjectName(),
249
- ]);
250
- const history = getHistoryStatus();
251
- return sendJson(res, 200, { ok: true, projectName, screens, history });
252
- }
253
-
254
- // GET /api/inspect?file=...&astPath=...&line=...
255
- if (req.method === 'GET' && pathname === '/api/inspect') {
256
- const file = urlObj.searchParams.get('file') ?? '';
257
- const astPath = urlObj.searchParams.get('astPath') ?? '';
258
- const lineParam = urlObj.searchParams.get('line');
259
- const line = lineParam ? Number.parseInt(lineParam, 10) : undefined;
260
-
261
- const element = await inspectElement(workspaceRoot, { file, astPath, line });
262
- const history = getHistoryStatus();
263
- return sendJson(res, 200, { ok: true, element, history });
264
- }
265
-
266
- // POST /api/patch
267
- if (req.method === 'POST' && pathname === '/api/patch') {
268
- const body = await readJsonBody<PatchPayload>(req);
269
- const result = await patchElement(workspaceRoot, body);
270
- return sendJson(res, 200, {
271
- ok: true,
272
- element: result.element,
273
- history: {
274
- undoAvailable: result.undoAvailable,
275
- redoAvailable: result.redoAvailable,
276
- },
277
- });
278
- }
279
-
280
- // POST /api/reorder
281
- if (req.method === 'POST' && pathname === '/api/reorder') {
282
- const body = await readJsonBody<ReorderPayload>(req);
283
- const result = await reorderElement(workspaceRoot, body);
284
- return sendJson(res, 200, {
285
- ok: true,
286
- element: result.element,
287
- history: {
288
- undoAvailable: result.undoAvailable,
289
- redoAvailable: result.redoAvailable,
290
- },
291
- });
292
- }
293
-
294
- // POST /api/insert
295
- if (req.method === 'POST' && pathname === '/api/insert') {
296
- const body = await readJsonBody<InsertElementPayload>(req);
297
- const result = await insertElement(workspaceRoot, body);
298
- return sendJson(res, 200, {
299
- ok: true,
300
- element: result.element,
301
- history: {
302
- undoAvailable: result.undoAvailable,
303
- redoAvailable: result.redoAvailable,
304
- },
305
- });
306
- }
307
-
308
- // POST /api/duplicate
309
- if (req.method === 'POST' && pathname === '/api/duplicate') {
310
- const body = await readJsonBody<DuplicateElementPayload>(req);
311
- const result = await duplicateElement(workspaceRoot, body);
312
- return sendJson(res, 200, {
313
- ok: true,
314
- element: result.element,
315
- history: {
316
- undoAvailable: result.undoAvailable,
317
- redoAvailable: result.redoAvailable,
318
- },
319
- });
320
- }
321
-
322
- // POST /api/delete
323
- if (req.method === 'POST' && pathname === '/api/delete') {
324
- const body = await readJsonBody<DeleteElementPayload>(req);
325
- const result = await deleteElement(workspaceRoot, body);
326
- return sendJson(res, 200, {
327
- ok: true,
328
- deletedAstPath: result.deletedAstPath,
329
- history: {
330
- undoAvailable: result.undoAvailable,
331
- redoAvailable: result.redoAvailable,
332
- },
333
- });
334
- }
335
-
336
- // POST /api/undo
337
- if (req.method === 'POST' && pathname === '/api/undo') {
338
- const result = await undoLastPatch(workspaceRoot);
339
- return sendJson(res, 200, { ok: true, ...result });
340
- }
341
-
342
- // POST /api/redo
343
- if (req.method === 'POST' && pathname === '/api/redo') {
344
- const result = await redoLastPatch(workspaceRoot);
345
- return sendJson(res, 200, { ok: true, ...result });
346
- }
347
-
348
- // GET /api/layout
349
- if (req.method === 'GET' && pathname === '/api/layout') {
350
- try {
351
- const raw = await fs.readFile(layoutFilePath, 'utf-8');
352
- const validated = sanitizeLayoutPayload(JSON.parse(raw));
353
- return sendJson(res, 200, { ok: true, layout: validated });
354
- } catch {
355
- return sendJson(res, 200, { ok: true, layout: null });
356
- }
357
- }
358
-
359
- // POST /api/layout
360
- if (req.method === 'POST' && pathname === '/api/layout') {
361
- const body = await readJsonBody<Record<string, unknown>>(req);
362
- const validated = sanitizeLayoutPayload(body);
363
- await fs.writeFile(layoutFilePath, JSON.stringify(validated, null, 2), 'utf-8');
364
- return sendJson(res, 200, { ok: true });
365
- }
366
-
367
- return sendJson(res, 404, { ok: false, error: `Unknown API route: ${pathname}` });
368
- } catch (err) {
369
- const message = err instanceof Error ? err.message : String(err);
370
- return sendJson(res, 400, { ok: false, error: message });
371
- }
372
- });
373
- },
374
- };
375
- }