zelune 0.0.0-stage → 1.4.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/CHANGELOG.md ADDED
@@ -0,0 +1,57 @@
1
+ # Changelog
2
+
3
+ All notable changes to the **Zelune — React Native Visual Canvas & MCP** project are documented in this file.
4
+
5
+ ## [1.4.0] - 2026-10-21
6
+
7
+ ### Added
8
+ - **One-Command Project Installer (`zelune init` in `bin/cli.mjs`)**: Running `zelune init` inside any React Native or Expo repository automatically detects its screens directory (`src/screens/`, `screens/`, `app/`, `src/app/`), adds `"canvas": "zelune"` to `package.json`, configures the `zelune` MCP server in `mcp_config.json`, installs the AI agent skill to `.agents/skills/zelune/SKILL.md`, creates starter `DESIGN.md` and `PRODUCT.md` governance docs if missing, and adds `.canvas-layout.json` to `.gitignore`.
9
+ - **Smart Zero-Flag CLI Launch (`bin/cli.mjs`)**: Running `zelune` with no flags automatically probes both IPv4 (`127.0.0.1`) and IPv6 (`::1`) interfaces starting at port `5173` to select a conflict-free port and opens the browser automatically via `--open` (disable with `--no-open`).
10
+ - **Public npm Registry Metadata (`package.json`)**: Added `license`, `repository`, `homepage`, and `bugs` fields for publishing `zelune` to the public npm registry.
11
+
12
+ ## [1.3.0] - 2026-10-21
13
+
14
+ ### Added
15
+ - **Universal Vite Virtual Screen Loader (`plugins/viteVirtualScreensPlugin.ts`, `src/screens/index.ts`, `vite.config.ts`)**: Replaced static screen imports with `virtual:zelune-screens` so running `zelune --project /path/to/app` dynamically discovers and mounts `.tsx` and `.jsx` screens from any React Native or Expo project (`src/screens/`, `screens/`, `app/`, `src/app/`, including nested route folders like `app/(tabs)/index.tsx`).
16
+ - **Expo Router, React Navigation & Safe Area Browser Shims (`src/runtime/MockNavigationContext.tsx`, `vite.config.ts`)**: Added built-in web shims for `@react-navigation/native`, `expo-router` (`useRouter`, `useLocalSearchParams`, `Link`, `Stack`, `Tabs`), `react-native-safe-area-context` (`SafeAreaView`, `useSafeAreaInsets`), `expo-status-bar`, `lucide-react-native`, and automatic `tsconfig.json` `paths` alias (`@/`) resolution for external projects.
17
+
18
+ ## [1.2.1] - 2026-10-21
19
+
20
+ ### Added
21
+ - **Collapsible Studio Side Panels (`src/canvas/CanvasApp.tsx`, `src/canvas/InspectorPanel.tsx`, `src/styles.css`)**: Added collapsible left (Screens Navigator) and right (AST Visual Inspector) sidebars with top-bar toggles, inline panel close buttons, floating canvas restore pills when collapsed, `[` and `]` keyboard shortcuts, auto-expand on element selection, and `220ms` `--ease-out-expo` width choreography.
22
+ - **Minimal Studio Header Identity & Icon-First Hover-Reveal Toolbar (`src/canvas/CanvasApp.tsx`, `src/styles.css`, `src/server/apiMiddleware.ts`, `vite.config.ts`)**: Simplified the top-left studio brand header to display **Zelune** and the connected project name (`projectName`), set studio visual density permanently to `5/10` (`spacious`), and converted toolbar buttons (`Inspect & Edit`, `Interact & Navigate`, `Flow Graph`, `Fit`, `Grid`) into compact icon buttons that smoothly expand to reveal their text labels on hover/focus using `interpolate-size: allow-keywords` and `calc-size(max-content, size)`.
23
+
24
+ ### Fixed
25
+ - **Browser Whole-Page Trackpad Pinch-Zoom Leak (`src/canvas/CanvasApp.tsx`, `src/styles.css`)**: Replaced React's passive `onWheel` handler with non-passive (`{ passive: false }`) native `wheel` and WebKit `gesturestart`/`gesturechange`/`gestureend` listeners plus `touch-action: none` and `overscroll-behavior: none`, preventing native browser page zoom anywhere in the studio and routing two-finger trackpad pinches into cursor-centered focal canvas zooming.
26
+
27
+ ## [1.2.0] - 2026-10-20
28
+
29
+ ### Added
30
+ - **Design Craft & Product Truth System (`PRODUCT.md`, `DESIGN.md`)**: Established `pbakaus/impeccable` product truth and YAML-governed design tokens, `leonxlnx/taste-skill` dual-surface parametric dials (`Design Variance`, `Motion Intensity`, `Visual Density`), and `emilkowalski/skills` motion and touch ergonomics contracts.
31
+ - **Interactive Taste Density Switcher (`src/canvas/CanvasApp.tsx`, `src/styles.css`)**: Added a live `Density 8/10 (Cockpit)` vs. `Density 5/10 (Comfort)` toggle using CSS `data-density` + `@container style(--density: ...)` reactive tokens.
32
+ - **Physics-Inspired Camera Transitions & Micro-Interactions (`src/canvas/CanvasApp.tsx`, `src/canvas/ScreenArtboard.tsx`, `src/styles.css`)**: Added `--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1)` camera interpolation for screen focus/fit actions (with a `0ms` direct-manipulation override during pointer drag/wheel zoom), `scale: 0.97` tactile press feedback, `<dialog>` entry choreography, and `tabular-nums` alignment across all coordinate and zoom readouts.
33
+
34
+ ### Changed
35
+ - **Anti-Slop Visual Overhaul (`index.html`, `src/canvas/*`, `src/screens/*`)**: Replaced generic `Inter` typography with `Plus Jakarta Sans` + `JetBrains Mono`, removed cliché purple-to-cyan/violet button gradients in favor of tinted obsidian surfaces (`#08090C` / `#0D0F14` / `#13161E`) and `1px` hairlines, and replaced emoji card placeholders (`🎧`, `⌨️`) across the React Native reference screens with industrial hardware monograms (`CH-01`, `KS-75`) and `activeOpacity={0.78}` mobile touch ergonomics.
36
+
37
+ ## [1.1.1] - 2026-10-20
38
+
39
+ ### Changed
40
+ - **Zelune MCP & Package Branding (`mcp/server.ts`, `mcp_config.json`, `plugin.json`, `package.json`, `src/canvas/`)**: Renamed the MCP server, CLI binary (`npx zelune`), plugin manifest, and canvas studio header to **Zelune** (`zelune`).
41
+
42
+ ## [1.1.0] - 2026-10-20
43
+
44
+ ### Added
45
+ - **Universal CLI Entrypoint (`bin/cli.mjs`)**: Added CLI binary supporting `--project /path/to/app`, `--port`, and `--mcp` flags, plus automatic screen discovery across `src/screens/`, `screens/`, and Expo Router `app/` directories.
46
+ - **Google Antigravity Plugin Bundle (`plugin.json`, `mcp_config.json`, `skills/rn-canvas/SKILL.md`, `rules/AGENTS.md`, `assets/logo.svg`)**: Packaged the workspace as a complete installable Antigravity Plugin and public npm package.
47
+
48
+ ## [1.0.0] - 2026-10-20
49
+
50
+ ### Added
51
+ - **Browser Infinite Canvas (`src/canvas/CanvasApp.tsx`, `src/canvas/ScreenArtboard.tsx`)**: Built a 60fps Figma-style pan-and-zoom visual canvas rendering all React Native screens side-by-side on clean rectangular `393 × 852` artboards without bulky hardware device bezels.
52
+ - **Compile-Time JSX Source & AST Path Injector (`plugins/viteRnSourceInject.ts`)**: Created a custom Babel/Vite transform that injects deterministic `dataSet` and `data-agy-*` source coordinates into React Native elements so `react-native-web` DOM nodes map directly back to `.tsx` AST nodes.
53
+ - **Two-Tier Bi-Directional AST Engine (`src/server/astEngine.ts`, `src/server/apiMiddleware.ts`)**:
54
+ - **Tier 1 (Surgical AST Patching)**: Live inspection and mutation of `StyleSheet.create` rules, inline styles, and static `<Text>` copy with per-file write mutex locking and a 50-step Undo/Redo history stack (`Cmd+Z` / `Cmd+Shift+Z`).
55
+ - **Tier 2 (Google Antigravity Agent Bridge & MCP Server)**: Added an in-canvas "Ask Antigravity to Refactor" modal (`<dialog closedby="any">`) and a standalone MCP server (`mcp/server.ts`) exposing `list_canvas_screens`, `inspect_screen_element`, and `patch_screen_element`.
56
+ - **Automatic Navigation Flow Graph (`src/canvas/FlowArrowsLayer.tsx`)**: Extracts `navigation.navigate(...)` and `router.push(...)` calls from screen ASTs and renders interactive cubic-bezier SVG flow connectors across the infinite canvas.
57
+ - **Multi-Screen React Native Starter Suite (`src/screens/`)**: Added `LoginScreen.tsx`, `HomeScreen.tsx`, `ProductDetailScreen.tsx`, `CheckoutScreen.tsx`, and `ProfileScreen.tsx` wrapped in `MockNavigationProvider` and `ScreenErrorBoundary`.
package/DESIGN.md ADDED
@@ -0,0 +1,90 @@
1
+ ---
2
+ system: zelune-design-system
3
+ version: 1.2.0
4
+ frameworks:
5
+ - pbakaus/impeccable
6
+ - emilkowalski/skills
7
+ - leonxlnx/taste-skill
8
+ taste_dials:
9
+ studio_chrome:
10
+ design_variance: 2
11
+ motion_intensity: 6
12
+ visual_density: 5
13
+ mobile_reference_screens:
14
+ design_variance: 7
15
+ motion_intensity: 7
16
+ visual_density: 4
17
+ tokens:
18
+ fonts:
19
+ sans: "'Plus Jakarta Sans', -apple-system, BlinkMacSystemFont, sans-serif"
20
+ mono: "'JetBrains Mono', ui-monospace, SFMono-Regular, monospace"
21
+ colors:
22
+ studio_canvas_bg: "#08090C"
23
+ studio_surface_1: "#0D0F14"
24
+ studio_surface_2: "#13161E"
25
+ studio_surface_3: "#191D28"
26
+ studio_border_hairline: "rgba(255, 255, 255, 0.07)"
27
+ studio_border_subtle: "#1E2330"
28
+ studio_border_focus: "#38BDF8"
29
+ studio_text_primary: "#F1F5F9"
30
+ studio_text_secondary: "#94A3B8"
31
+ studio_text_muted: "#64748B"
32
+ studio_accent_primary: "#38BDF8"
33
+ studio_accent_Select: "#6366F1"
34
+ studio_accent_interact: "#10B981"
35
+ studio_accent_warning: "#F59E0B"
36
+ studio_accent_danger: "#F43F5E"
37
+ motion:
38
+ ease_out: "cubic-bezier(0.23, 1, 0.32, 1)"
39
+ ease_out_expo: "cubic-bezier(0.16, 1, 0.3, 1)"
40
+ ease_in_out: "cubic-bezier(0.65, 0, 0.35, 1)"
41
+ duration_press: "120ms"
42
+ duration_micro: "160ms"
43
+ duration_panel: "220ms"
44
+ duration_camera: "380ms"
45
+ press_scale: 0.97
46
+ ---
47
+
48
+ # DESIGN.md — Zelune Visual & Motion System
49
+
50
+ This specification governs visual craftsmanship, parametric taste dials, physics-based motion curves, and anti-slop guardrails across **Zelune**.
51
+
52
+ ---
53
+
54
+ ## 1. Parametric Taste Dials (`leonxlnx/taste-skill`)
55
+
56
+ Before modifying any component, configure the three gating dials according to the target surface:
57
+
58
+ | Surface | Design Variance (1–10) | Motion Intensity (1–10) | Visual Density (1–10) | Aesthetic Mandate |
59
+ | :--- | :---: | :---: | :---: | :--- |
60
+ | **Zelune Studio Chrome** (`src/canvas/`) | **2 / 10** | **6 / 10** | **8 / 10** | **Pilot Cockpit Precision**: Strict orthogonal alignment, `1px` hairline borders (`rgba(255,255,255,0.07)`), `tabular-nums` coordinate readouts, and switchable `compact` / `spacious` inspector density. |
61
+ | **Chronos Mobile Suite** (`src/screens/`) | **7 / 10** | **7 / 10** | **4 / 10** | **Industrial Editorial**: Asymmetric spec grids, technical model numbers (`CH-01 // BERYLLIUM`), high-contrast typographic hierarchy, and generous negative space. |
62
+
63
+ ---
64
+
65
+ ## 2. Motion & Touch Ergonomics (`emilkowalski/skills`)
66
+
67
+ ### 2.1 Easing & Timing Tokens
68
+ - **Never use `ease-in` on entering or interactive UI elements.** Built-in CSS `ease` and `linear` are prohibited for UI feedback.
69
+ - **`--ease-out: cubic-bezier(0.23, 1, 0.32, 1)`**: Default curve for button hovers, inspector pills, and bounding-box overlays (`140ms–180ms`).
70
+ - **`--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1)`**: High-velocity decelerating curve for programmatic canvas camera focus (`focusOnScreen`, `handleFitAllScreens`, `380ms`) and `<dialog>` modal entry (`200ms`).
71
+ - **Direct Manipulation Override (`0ms`)**: When the user is actively dragging the canvas (`isPanning`) or scrolling the wheel (`onWheel`), `transition` on the world transform container **must be `none`** so the canvas tracks the cursor with zero latency.
72
+
73
+ ### 2.2 Tactile Press Feedback & Stacking Context Stability
74
+ - All interactive studio buttons, swatches, and pills apply an identity transform (`scale: 1; translate: 0 0;`) at rest to prevent stacking-context shifts, and compress to `scale: 0.97` on `:active` over `120ms var(--ease-out)`.
75
+ - Respect `prefers-reduced-motion: reduce` by collapsing camera and micro-interaction durations.
76
+
77
+ ### 2.3 Mobile Touch Ergonomics (`src/screens/`)
78
+ - Every `<TouchableOpacity>` on the `393 × 852` artboards must provide a minimum `40px–48px` vertical touch target (`paddingVertical: 10–16`) and explicit `activeOpacity={0.78}` for tactile mobile responsiveness.
79
+
80
+ ---
81
+
82
+ ## 3. Anti-Slop Rules (`pbakaus/impeccable`)
83
+
84
+ The following AI-generated design clichés are **strictly banned** in this repository:
85
+
86
+ 1. **No Default `Inter` Monoculture**: Use `'Plus Jakarta Sans'` for UI/display copy paired with `'JetBrains Mono'` (`font-variant-numeric: tabular-nums`) for AST paths, line numbers, dimensions, zoom percentages, and prices.
87
+ 2. **No Cliché Purple-to-Cyan / Indigo-to-Violet Button Gradients**: Do not slap `linear-gradient(135deg, #4F46E5, #7C3AED)` or `#06B6D4` gradients onto studio logos or primary CTAs. Use solid, high-craft tinted surfaces (`#13161E`), crisp 1px hairlines, and deliberate single-hue signal accents (`#38BDF8` sky cyan for inspection, `#6366F1` precision indigo for AST selection, `#10B981` emerald for live navigation flows).
88
+ 3. **No Emoji-in-a-Rounded-Square Placeholders**: Never place raw emojis (`🎧`, `⌨️`) inside rounded colored squares as substitute product photography or feature icons. Use technical typographic monograms, model codes (`CH-01`, `KS-75`), or vector geometry.
89
+ 4. **No Untinted Pure Grays or Muddy Borders**: All dark neutrals must carry a subtle cool-obsidian tint (`#08090C` canvas, `#0D0F14` chrome, `#13161E` elevated controls) separated by `1px solid rgba(255, 255, 255, 0.07)` hairlines and soft diffusion shadows (`0 24px 48px -12px rgba(0, 0, 0, 0.72)`).
90
+ 5. **No Numeric Layout Jitter**: Every counter, zoom readout (`68%`), coordinate (`L142:8`), artboard dimension (`393 × 852`), and price (`$349.00`) must enforce `tabular-nums`.
package/PRODUCT.md ADDED
@@ -0,0 +1,31 @@
1
+ # PRODUCT.md — Zelune (`zelune`)
2
+
3
+ > Durable product truth anchored per the [`pbakaus/impeccable`](https://github.com/pbakaus/impeccable) context specification. All UI, motion, and agent-driven AST modifications must align with this document.
4
+
5
+ ## 1. Product Purpose
6
+ **Zelune** is a **Figma-style multi-screen React Native visual canvas** and **bi-directional Babel AST synchronization MCP server** built for **Google Antigravity**. It eliminates the blind spot between mobile codebases (`.tsx`) and visual spatial architecture by rendering every screen side-by-side as pure `393 × 852` artboards with live navigation flow graphs and surgical AST write-back.
7
+
8
+ ## 2. Target Audience
9
+ - **Design Engineers & Mobile Product Builders** working in React Native or Expo who demand Linear/Figma-grade precision tooling.
10
+ - **AI Coding Agents (Google Antigravity)** operating via the `zelune` MCP server (`list_canvas_screens`, `inspect_screen_element`, `patch_screen_element`) that need deterministic spatial and style context.
11
+
12
+ ## 3. Operating Context & Dual-Surface Architecture
13
+ Zelune contains two distinct visual surfaces that must never be conflated:
14
+
15
+ 1. **Surface A — Zelune Studio Chrome (`src/canvas/`)**
16
+ - **Context**: Desktop browser IDE/Canvas companion (`1280px–2560px+` viewports), used alongside a code editor for hours at a time.
17
+ - **Priority**: High information density, zero visual fatigue, instant spatial orientation, 1:1 pointer tracking during drag/zoom, and unambiguous AST source coordinates (`file:line:col`).
18
+ 2. **Surface B — Chronos Acoustics Reference Mobile Suite (`src/screens/`)**
19
+ - **Context**: `393 × 852` iOS/Android mobile artboards demonstrating multi-screen React Native inspection, live `StyleSheet.create` patching, and SVG navigation flow extraction.
20
+ - **Priority**: Industrial-grade hardware commerce aesthetic (inspired by Teenage Engineering / Bang & Olufsen), 44pt+ mobile touch ergonomics, and editorial typographic hierarchy.
21
+
22
+ ## 4. Core Constraints & Technical Invariants
23
+ - **No Hardware Bezels**: Screens render on clean rectangular `393 × 852` artboards with `1px` architectural frames—never inside skeuomorphic phone clay frames or fake notches.
24
+ - **Compile-Time AST Determinism**: Every React Native element maps back to its `.tsx` source node via `data-agy-ast-path`. Screen components must maintain a clean `StyleSheet.create` block at the bottom of the file.
25
+ - **Two-Tier Edit Safety**:
26
+ - **Tier 1**: Surgical AST mutations for static `<Text>` copy and `StyleSheet.create` / inline style properties with per-file mutex locking and a 50-step Undo/Redo stack.
27
+ - **Tier 2**: Native `<dialog closedby="any">` structural refactor bridge + stdio MCP server for complex stateful or multi-node changes.
28
+
29
+ ## 5. Voice & Craft Tone
30
+ - **Studio Chrome**: Clinical, technical, understated, and exact. Labels use concise engineering terminology (`AST Node`, `L42:8`, `styles.heroBanner`, `393 × 852`) rather than marketing fluff.
31
+ - **Reference Mobile App**: Tactile, acoustic-engineering precision, confident editorial restraint.
package/README.md CHANGED
@@ -1,3 +1,64 @@
1
- # Temporary Holding Version
1
+ # Zelune (`zelune`)
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ **Zelune** is a **Figma-style multi-screen visual canvas** and **bi-directional Babel AST sync MCP server** for React Native & Expo applications in **Google Antigravity**, Cursor, and Claude Code.
4
+
5
+ View all of your mobile screens side-by-side on clean `393 × 852` rectangular artboards in the browser, inspect any `<View>`, `<Text>`, or `<TouchableOpacity>` element, and edit copy or `StyleSheet.create` styles with instant write-back to your `.tsx` source code.
6
+
7
+ ---
8
+
9
+ ## Global Installation & Effortless Project Setup
10
+
11
+ ### 1. Install Globally Once (or via `npm link` locally)
12
+ ```bash
13
+ npm install -g github:woodfur/antigravity-rn-canvas
14
+ # Or once published to npm:
15
+ npm install -g zelune
16
+ ```
17
+
18
+ ### 2. Initialize & Launch in Any React Native / Expo Project
19
+ Inside any React Native or Expo project folder:
20
+
21
+ ```bash
22
+ cd /path/to/your/react-native-app
23
+
24
+ # Optional one-time project setup (adds "npm run canvas", mcp_config.json, AI skill, DESIGN.md & PRODUCT.md):
25
+ zelune init
26
+
27
+ # Launch the visual canvas (auto-selects a free port and opens your browser automatically):
28
+ zelune
29
+ ```
30
+
31
+ You can also run it without installing globally:
32
+ ```bash
33
+ npx github:woodfur/antigravity-rn-canvas
34
+ ```
35
+
36
+ ### Option B: Install the Zelune AI Agent Skill
37
+ Install the Zelune skill (`skills/rn-canvas/SKILL.md`), design system (`DESIGN.md`, `PRODUCT.md`), and AST rules (`rules/AGENTS.md`) into your AI coding agent:
38
+
39
+ ```bash
40
+ npx skills add woodfur/antigravity-rn-canvas
41
+ ```
42
+
43
+ ---
44
+
45
+ ## Google Antigravity Integration (Zelune MCP Server)
46
+
47
+ ### 1. MCP Server Setup
48
+ Add the **Zelune** MCP server to your `mcp_config.json`:
49
+
50
+ ```json
51
+ {
52
+ "mcpServers": {
53
+ "zelune": {
54
+ "command": "npx",
55
+ "args": ["-y", "github:woodfur/antigravity-rn-canvas", "--mcp"]
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### 2. Available Zelune MCP Tools
62
+ - `list_canvas_screens` — Lists all discovered screens (`src/screens/`, `screens/`, or Expo Router `app/`), element counts, and outgoing navigation flows.
63
+ - `inspect_screen_element` — Returns the resolved `StyleSheet.create` / inline styles, static text copy, and JSX snippet for any element by `file` and `astPath`.
64
+ - `patch_screen_element` — Surgically patches text copy or style rules in `.tsx` source files while preserving comments and formatting.
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" fill="none">
2
+ <rect x="14" y="14" width="100" height="100" rx="24" fill="#11141D" stroke="#4F46E5" stroke-width="4"/>
3
+ <rect x="28" y="30" width="28" height="56" rx="4" fill="#1E1B4B" stroke="#6366F1" stroke-width="2.5"/>
4
+ <rect x="72" y="42" width="28" height="56" rx="4" fill="#064E3B" stroke="#10B981" stroke-width="2.5"/>
5
+ <path d="M56 58 C64 58, 64 70, 72 70" stroke="#38BDF8" stroke-width="3" stroke-linecap="round"/>
6
+ <circle cx="56" cy="58" r="3.5" fill="#38BDF8"/>
7
+ <circle cx="72" cy="70" r="3.5" fill="#10B981"/>
8
+ </svg>
package/bin/cli.mjs ADDED
@@ -0,0 +1,243 @@
1
+ #!/usr/bin/env node
2
+ import { spawn } from 'node:child_process';
3
+ import fs from 'node:fs';
4
+ import net from 'node:net';
5
+ import path from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+ const defaultPackageRoot = path.resolve(__dirname, '..');
10
+
11
+ function isHostPortFree(port, host) {
12
+ return new Promise((resolve) => {
13
+ const server = net.createServer();
14
+ server.unref();
15
+ server.on('error', (err) => {
16
+ if (err && (err.code === 'EADDRNOTAVAIL' || err.code === 'EAFNOSUPPORT')) {
17
+ resolve(true);
18
+ return;
19
+ }
20
+ resolve(false);
21
+ });
22
+ server.listen({ port, host, exclusive: true }, () => {
23
+ server.close(() => resolve(true));
24
+ });
25
+ });
26
+ }
27
+
28
+ export async function isPortFree(port) {
29
+ const [ipv4Free, ipv6Free] = await Promise.all([
30
+ isHostPortFree(port, '127.0.0.1'),
31
+ isHostPortFree(port, '::1'),
32
+ ]);
33
+ return ipv4Free && ipv6Free;
34
+ }
35
+
36
+ export async function findAvailablePort(startPort = 5173, maxAttempts = 25) {
37
+ for (let offset = 0; offset < maxAttempts; offset++) {
38
+ const candidate = startPort + offset;
39
+ if (await isPortFree(candidate)) {
40
+ return candidate;
41
+ }
42
+ }
43
+ return startPort;
44
+ }
45
+
46
+ function detectScreensDirectory(targetProject) {
47
+ const candidates = ['src/screens', 'screens', 'app', 'src/app'];
48
+ for (const rel of candidates) {
49
+ const full = path.join(targetProject, rel);
50
+ if (fs.existsSync(full) && fs.statSync(full).isDirectory()) {
51
+ return rel;
52
+ }
53
+ }
54
+ return null;
55
+ }
56
+
57
+ export function initProject(targetProject, packageRoot = defaultPackageRoot) {
58
+ const resolvedTarget = path.resolve(targetProject);
59
+ const screensDir = detectScreensDirectory(resolvedTarget);
60
+
61
+ // 1. Update package.json with "canvas": "zelune" script if present
62
+ const pkgJsonPath = path.join(resolvedTarget, 'package.json');
63
+ let updatedPackageJson = false;
64
+ if (fs.existsSync(pkgJsonPath)) {
65
+ try {
66
+ const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
67
+ pkg.scripts = pkg.scripts || {};
68
+ if (pkg.name !== 'zelune' && !pkg.scripts.canvas) {
69
+ pkg.scripts.canvas = 'zelune';
70
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
71
+ updatedPackageJson = true;
72
+ }
73
+ } catch {
74
+ // Ignore malformed package.json
75
+ }
76
+ }
77
+
78
+ // 2. Create or merge mcp_config.json with the zelune MCP server entry
79
+ const mcpConfigPath = path.join(resolvedTarget, 'mcp_config.json');
80
+ let mcpConfig = { mcpServers: {} };
81
+ if (fs.existsSync(mcpConfigPath)) {
82
+ try {
83
+ const parsed = JSON.parse(fs.readFileSync(mcpConfigPath, 'utf8'));
84
+ if (parsed && typeof parsed === 'object') {
85
+ mcpConfig = parsed;
86
+ mcpConfig.mcpServers = mcpConfig.mcpServers || {};
87
+ }
88
+ } catch {
89
+ mcpConfig = { mcpServers: {} };
90
+ }
91
+ }
92
+ mcpConfig.mcpServers.zelune = {
93
+ command: 'npx',
94
+ args: ['-y', 'zelune', '--mcp', '--project', resolvedTarget],
95
+ };
96
+ fs.writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2) + '\n', 'utf8');
97
+
98
+ // 3. Install AI Agent Skill into .agents/skills/zelune/SKILL.md
99
+ const sourceSkillPath = path.join(packageRoot, 'skills/rn-canvas/SKILL.md');
100
+ const destSkillDir = path.join(resolvedTarget, '.agents/skills/zelune');
101
+ const destSkillPath = path.join(destSkillDir, 'SKILL.md');
102
+ if (fs.existsSync(sourceSkillPath)) {
103
+ fs.mkdirSync(destSkillDir, { recursive: true });
104
+ fs.copyFileSync(sourceSkillPath, destSkillPath);
105
+ }
106
+
107
+ // 4. Copy starter DESIGN.md & PRODUCT.md if not already present
108
+ let createdDesignMd = false;
109
+ const sourceDesignPath = path.join(packageRoot, 'DESIGN.md');
110
+ const destDesignPath = path.join(resolvedTarget, 'DESIGN.md');
111
+ if (fs.existsSync(sourceDesignPath) && !fs.existsSync(destDesignPath)) {
112
+ fs.copyFileSync(sourceDesignPath, destDesignPath);
113
+ createdDesignMd = true;
114
+ }
115
+
116
+ let createdProductMd = false;
117
+ const sourceProductPath = path.join(packageRoot, 'PRODUCT.md');
118
+ const destProductPath = path.join(resolvedTarget, 'PRODUCT.md');
119
+ if (fs.existsSync(sourceProductPath) && !fs.existsSync(destProductPath)) {
120
+ fs.copyFileSync(sourceProductPath, destProductPath);
121
+ createdProductMd = true;
122
+ }
123
+
124
+ // 5. Ensure .canvas-layout.json is listed in .gitignore
125
+ const gitignorePath = path.join(resolvedTarget, '.gitignore');
126
+ if (fs.existsSync(gitignorePath)) {
127
+ const currentIgnore = fs.readFileSync(gitignorePath, 'utf8');
128
+ if (!currentIgnore.split(/\r?\n/).includes('.canvas-layout.json')) {
129
+ const prefix = currentIgnore.endsWith('\n') || currentIgnore.length === 0 ? '' : '\n';
130
+ fs.writeFileSync(
131
+ gitignorePath,
132
+ `${currentIgnore}${prefix}.canvas-layout.json\n`,
133
+ 'utf8'
134
+ );
135
+ }
136
+ } else {
137
+ fs.writeFileSync(gitignorePath, '.canvas-layout.json\n', 'utf8');
138
+ }
139
+
140
+ return {
141
+ targetProject: resolvedTarget,
142
+ screensDir: screensDir ?? 'src/screens (or app/)',
143
+ updatedPackageJson,
144
+ mcpConfigPath,
145
+ skillPath: destSkillPath,
146
+ createdDesignMd,
147
+ createdProductMd,
148
+ };
149
+ }
150
+
151
+ async function runCli() {
152
+ const args = process.argv.slice(2);
153
+
154
+ if (args.includes('--help') || args.includes('-h')) {
155
+ console.log(`
156
+ Zelune — React Native Visual Canvas & AST Sync MCP Server
157
+
158
+ Usage:
159
+ zelune Launch visual canvas for current project (auto-selects free port & opens browser)
160
+ zelune init One-command setup for the current React Native / Expo project
161
+ zelune --project <dir> Target a specific React Native / Expo project directory
162
+ zelune --port <number> Use a specific port (default: auto-detects from 5173)
163
+ zelune --no-open Do not automatically open the browser on launch
164
+ zelune --mcp Start the Zelune stdio MCP server for Google Antigravity / Cursor / Claude Code
165
+ `);
166
+ process.exit(0);
167
+ }
168
+
169
+ const isMcpMode = args.includes('--mcp');
170
+ const isInitMode = args[0] === 'init' || args[0] === 'setup';
171
+
172
+ let targetProject = process.cwd();
173
+ const projectFlagIdx = args.indexOf('--project');
174
+ if (projectFlagIdx !== -1 && args[projectFlagIdx + 1]) {
175
+ targetProject = path.resolve(args[projectFlagIdx + 1]);
176
+ }
177
+
178
+ if (isInitMode) {
179
+ const result = initProject(targetProject, defaultPackageRoot);
180
+ console.log(`\n✨ Zelune initialized in ${result.targetProject}`);
181
+ console.log(` • Screens directory: ${result.screensDir}`);
182
+ if (result.updatedPackageJson) {
183
+ console.log(` • Added "canvas": "zelune" script to package.json`);
184
+ }
185
+ console.log(` • Configured MCP server in mcp_config.json`);
186
+ console.log(` • Installed AI skill in .agents/skills/zelune/SKILL.md`);
187
+ if (result.createdDesignMd || result.createdProductMd) {
188
+ console.log(` • Created starter DESIGN.md & PRODUCT.md governance docs`);
189
+ }
190
+ console.log(`\n👉 Run "npm run canvas" (or "zelune") to open your screens in the visual canvas!\n`);
191
+ process.exit(0);
192
+ }
193
+
194
+ const env = {
195
+ ...process.env,
196
+ ANTIGRAVITY_TARGET_PROJECT: targetProject,
197
+ };
198
+
199
+ if (isMcpMode) {
200
+ const mcpServerPath = path.join(defaultPackageRoot, 'mcp/server.ts');
201
+ const child = spawn('npx', ['tsx', mcpServerPath], {
202
+ cwd: defaultPackageRoot,
203
+ env,
204
+ stdio: 'inherit',
205
+ });
206
+ child.on('exit', (code) => process.exit(code ?? 0));
207
+ return;
208
+ }
209
+
210
+ const portFlagIdx = args.indexOf('--port');
211
+ let port;
212
+ if (portFlagIdx !== -1 && args[portFlagIdx + 1]) {
213
+ port = String(args[portFlagIdx + 1]);
214
+ } else {
215
+ const resolvedPort = await findAvailablePort(5173);
216
+ port = String(resolvedPort);
217
+ }
218
+
219
+ const shouldOpenBrowser = !args.includes('--no-open');
220
+ const viteArgs = ['vite', '--host', '127.0.0.1', '--port', port];
221
+ if (shouldOpenBrowser) {
222
+ viteArgs.push('--open');
223
+ }
224
+
225
+ console.log(`\n🚀 Zelune — React Native Visual Canvas & MCP`);
226
+ console.log(`📂 Target Project: ${targetProject}`);
227
+ console.log(`🌐 Studio URL: http://127.0.0.1:${port}\n`);
228
+
229
+ const child = spawn('npx', viteArgs, {
230
+ cwd: defaultPackageRoot,
231
+ env,
232
+ stdio: 'inherit',
233
+ });
234
+ child.on('exit', (code) => process.exit(code ?? 0));
235
+ }
236
+
237
+ const isDirectExecution =
238
+ process.argv[1] &&
239
+ import.meta.url === pathToFileURL(fs.realpathSync(process.argv[1])).href;
240
+
241
+ if (isDirectExecution) {
242
+ runCli();
243
+ }
package/index.html ADDED
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Zelune — React Native Visual Canvas &amp; AST Studio</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link
10
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700&family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap"
11
+ rel="stylesheet"
12
+ />
13
+ </head>
14
+ <body class="bg-[#08090C] text-[#F1F5F9] antialiased selection:bg-sky-500/25 overflow-hidden">
15
+ <div id="root"></div>
16
+ <script type="module" src="/src/main.tsx"></script>
17
+ </body>
18
+ </html>
package/mcp/server.ts ADDED
@@ -0,0 +1,140 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ } from '@modelcontextprotocol/sdk/types.js';
9
+ import {
10
+ discoverScreens,
11
+ inspectElement,
12
+ patchElement,
13
+ type StyleValue,
14
+ } from '../src/server/astEngine.js';
15
+
16
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
17
+ const WORKSPACE_ROOT = path.resolve(__dirname, '..');
18
+
19
+ const server = new Server(
20
+ {
21
+ name: 'zelune',
22
+ version: '1.3.0',
23
+ },
24
+ {
25
+ capabilities: {
26
+ tools: {},
27
+ },
28
+ }
29
+ );
30
+
31
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
32
+ return {
33
+ tools: [
34
+ {
35
+ name: 'list_canvas_screens',
36
+ description:
37
+ 'Lists all discovered React Native screens on the visual canvas along with their element counts and outgoing navigation flow edges.',
38
+ inputSchema: {
39
+ type: 'object',
40
+ properties: {},
41
+ },
42
+ },
43
+ {
44
+ name: 'inspect_screen_element',
45
+ description:
46
+ 'Inspects a specific JSX element inside a React Native screen by its relative file path and AST index path.',
47
+ inputSchema: {
48
+ type: 'object',
49
+ properties: {
50
+ file: {
51
+ type: 'string',
52
+ description: 'Relative path to the screen file (e.g. src/screens/HomeScreen.tsx)',
53
+ },
54
+ astPath: {
55
+ type: 'string',
56
+ description: 'Preorder AST index of the JSX element (e.g. "0", "4")',
57
+ },
58
+ },
59
+ required: ['file', 'astPath'],
60
+ },
61
+ },
62
+ {
63
+ name: 'patch_screen_element',
64
+ description:
65
+ 'Surgically patches the text copy or StyleSheet properties of a React Native JSX element.',
66
+ inputSchema: {
67
+ type: 'object',
68
+ properties: {
69
+ file: {
70
+ type: 'string',
71
+ description: 'Relative path to the screen file (e.g. src/screens/HomeScreen.tsx)',
72
+ },
73
+ astPath: {
74
+ type: 'string',
75
+ description: 'Preorder AST index of the JSX element',
76
+ },
77
+ text: {
78
+ type: 'string',
79
+ description: 'Optional new static text content for <Text> elements',
80
+ },
81
+ styles: {
82
+ type: 'object',
83
+ description: 'Optional map of React Native style properties to update',
84
+ },
85
+ },
86
+ required: ['file', 'astPath'],
87
+ },
88
+ },
89
+ ],
90
+ };
91
+ });
92
+
93
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
94
+ const { name, arguments: args } = request.params;
95
+
96
+ if (name === 'list_canvas_screens') {
97
+ const screens = await discoverScreens(WORKSPACE_ROOT);
98
+ return {
99
+ content: [{ type: 'text', text: JSON.stringify(screens, null, 2) }],
100
+ };
101
+ }
102
+
103
+ if (name === 'inspect_screen_element') {
104
+ const file = String(args?.file ?? '');
105
+ const astPath = String(args?.astPath ?? '0');
106
+ const element = await inspectElement(WORKSPACE_ROOT, { file, astPath });
107
+ return {
108
+ content: [{ type: 'text', text: JSON.stringify(element, null, 2) }],
109
+ };
110
+ }
111
+
112
+ if (name === 'patch_screen_element') {
113
+ const file = String(args?.file ?? '');
114
+ const astPath = String(args?.astPath ?? '0');
115
+ const text = typeof args?.text === 'string' ? args.text : undefined;
116
+ const styles = (args?.styles as Record<string, StyleValue> | undefined) ?? undefined;
117
+
118
+ const result = await patchElement(WORKSPACE_ROOT, {
119
+ file,
120
+ astPath,
121
+ text,
122
+ styles,
123
+ });
124
+ return {
125
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
126
+ };
127
+ }
128
+
129
+ throw new Error(`Unknown MCP tool: ${name}`);
130
+ });
131
+
132
+ async function main() {
133
+ const transport = new StdioServerTransport();
134
+ await server.connect(transport);
135
+ }
136
+
137
+ main().catch((err) => {
138
+ console.error('MCP Server Error:', err);
139
+ process.exit(1);
140
+ });