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.
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "zelune": {
4
+ "command": "npx",
5
+ "args": ["-y", "zelune", "--mcp"]
6
+ }
7
+ }
8
+ }
package/package.json CHANGED
@@ -1,6 +1,79 @@
1
1
  {
2
2
  "name": "zelune",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.4.0",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "description": "Zelune — Figma-style Multi-Screen React Native Visual Canvas & AST Sync MCP Server for Google Antigravity",
8
+ "homepage": "https://github.com/woodfur/antigravity-rn-canvas#readme",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/woodfur/antigravity-rn-canvas.git"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/woodfur/antigravity-rn-canvas/issues"
15
+ },
16
+ "keywords": [
17
+ "zelune",
18
+ "google-antigravity",
19
+ "react-native",
20
+ "expo",
21
+ "visual-canvas",
22
+ "ast",
23
+ "mcp",
24
+ "figma"
25
+ ],
26
+ "bin": {
27
+ "zelune": "bin/cli.mjs",
28
+ "antigravity-rn-canvas": "bin/cli.mjs"
29
+ },
30
+ "files": [
31
+ "bin/",
32
+ "src/",
33
+ "plugins/",
34
+ "mcp/",
35
+ "skills/",
36
+ "rules/",
37
+ "assets/",
38
+ "index.html",
39
+ "vite.config.ts",
40
+ "tsconfig.json",
41
+ "plugin.json",
42
+ "mcp_config.json",
43
+ "PRODUCT.md",
44
+ "DESIGN.md",
45
+ "README.md",
46
+ "CHANGELOG.md"
47
+ ],
48
+ "scripts": {
49
+ "dev": "vite --host 127.0.0.1 --port 5173",
50
+ "canvas": "node ./bin/cli.mjs",
51
+ "build": "vite build",
52
+ "preview": "vite preview --host 127.0.0.1 --port 4173",
53
+ "test": "vitest run",
54
+ "mcp": "node ./bin/cli.mjs --mcp"
55
+ },
56
+ "dependencies": {
57
+ "@babel/generator": "^7.25.0",
58
+ "@babel/parser": "^7.25.0",
59
+ "@babel/traverse": "^7.25.0",
60
+ "@babel/types": "^7.25.0",
61
+ "@modelcontextprotocol/sdk": "^1.0.1",
62
+ "@vitejs/plugin-react": "^4.3.2",
63
+ "lucide-react": "^0.454.0",
64
+ "react": "^18.3.1",
65
+ "react-dom": "^18.3.1",
66
+ "react-native-web": "^0.19.12",
67
+ "tsx": "^4.19.1",
68
+ "vite": "^5.4.8"
69
+ },
70
+ "devDependencies": {
71
+ "@types/babel__generator": "^7.6.8",
72
+ "@types/babel__traverse": "^7.20.6",
73
+ "@types/node": "^22.0.0",
74
+ "@types/react": "^18.3.11",
75
+ "@types/react-dom": "^18.3.1",
76
+ "typescript": "^5.6.2",
77
+ "vitest": "^2.1.2"
78
+ }
79
+ }
package/plugin.json ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "zelune",
3
+ "displayName": "Zelune — React Native Visual Canvas",
4
+ "version": "1.3.0",
5
+ "description": "Zelune MCP & Figma-style multi-screen React Native visual canvas with live surgical AST patching and navigation flow visualization for Google Antigravity.",
6
+ "logo": "assets/logo.svg",
7
+ "suggestedPrompts": [
8
+ "Use Zelune to list all discovered React Native screens and navigation flows",
9
+ "Use Zelune to inspect the primary CTA button on HomeScreen and update its background color and copy",
10
+ "Audit all screens on the Zelune visual canvas for consistent typography and spacing"
11
+ ]
12
+ }
@@ -0,0 +1,117 @@
1
+ import { parse } from '@babel/parser';
2
+ import _traverse from '@babel/traverse';
3
+ import _generate from '@babel/generator';
4
+ import * as t from '@babel/types';
5
+ import path from 'node:path';
6
+ import type { Plugin } from 'vite';
7
+
8
+ // Handle ESM/CJS default export interop for @babel/* packages
9
+ const traverse = (_traverse as unknown as { default?: typeof _traverse }).default ?? _traverse;
10
+ const generate = (_generate as unknown as { default?: typeof _generate }).default ?? _generate;
11
+
12
+ const ALLOWED_SCREEN_PREFIXES = ['src/screens/', 'screens/', 'app/', 'src/app/'];
13
+
14
+ export function getJsxTagName(nameNode: t.JSXOpeningElement['name']): string {
15
+ if (t.isJSXIdentifier(nameNode)) {
16
+ return nameNode.name;
17
+ }
18
+ if (t.isJSXMemberExpression(nameNode)) {
19
+ return `${getJsxTagName(nameNode.object)}.${nameNode.property.name}`;
20
+ }
21
+ if (t.isJSXNamespacedName(nameNode)) {
22
+ return `${nameNode.namespace.name}:${nameNode.name.name}`;
23
+ }
24
+ return 'Unknown';
25
+ }
26
+
27
+ export function viteRnSourceInject(workspaceRoot: string): Plugin {
28
+ const normalizedRoot = path.resolve(workspaceRoot);
29
+
30
+ return {
31
+ name: 'vite-rn-source-inject',
32
+ enforce: 'pre',
33
+ transform(code: string, id: string) {
34
+ const cleanId = id.split('?')[0];
35
+ if (!cleanId.endsWith('.tsx') && !cleanId.endsWith('.jsx')) {
36
+ return null;
37
+ }
38
+
39
+ const resolvedFile = path.resolve(cleanId);
40
+ const relPath = path.relative(normalizedRoot, resolvedFile).replace(/\\/g, '/');
41
+
42
+ // Instrument screen files across src/screens/, screens/, app/, and src/app/
43
+ const isScreenFile = ALLOWED_SCREEN_PREFIXES.some((prefix) => relPath.startsWith(prefix));
44
+ if (!isScreenFile) {
45
+ return null;
46
+ }
47
+
48
+ try {
49
+ const ast = parse(code, {
50
+ sourceType: 'module',
51
+ plugins: ['typescript', 'jsx'],
52
+ });
53
+
54
+ let jsxIndex = 0;
55
+
56
+ traverse(ast, {
57
+ JSXOpeningElement(nodePath) {
58
+ const { node } = nodePath;
59
+ const tagName = getJsxTagName(node.name);
60
+
61
+ // Skip React.Fragment or Fragment since fragments do not accept DOM/dataSet props
62
+ if (tagName === 'Fragment' || tagName === 'React.Fragment' || tagName === '') {
63
+ return;
64
+ }
65
+
66
+ const currentIndex = String(jsxIndex++);
67
+ const line = String(node.loc?.start.line ?? 1);
68
+ const col = String(node.loc?.start.column ?? 0);
69
+
70
+ // Check if already injected
71
+ const hasAgyAttr = node.attributes.some(
72
+ (attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name, { name: 'data-agy-ast-path' })
73
+ );
74
+ if (hasAgyAttr) {
75
+ return;
76
+ }
77
+
78
+ // Build dataSet={{ agyFile, agyLine, agyCol, agyAstPath, agyTag }} for react-native-web
79
+ const dataSetProps: t.ObjectProperty[] = [
80
+ t.objectProperty(t.identifier('agyFile'), t.stringLiteral(relPath)),
81
+ t.objectProperty(t.identifier('agyLine'), t.stringLiteral(line)),
82
+ t.objectProperty(t.identifier('agyCol'), t.stringLiteral(col)),
83
+ t.objectProperty(t.identifier('agyAstPath'), t.stringLiteral(currentIndex)),
84
+ t.objectProperty(t.identifier('agyTag'), t.stringLiteral(tagName)),
85
+ ];
86
+
87
+ node.attributes.push(
88
+ t.jsxAttribute(
89
+ t.jsxIdentifier('dataSet'),
90
+ t.jsxExpressionContainer(t.objectExpression(dataSetProps))
91
+ ),
92
+ t.jsxAttribute(t.jsxIdentifier('data-agy-file'), t.stringLiteral(relPath)),
93
+ t.jsxAttribute(t.jsxIdentifier('data-agy-line'), t.stringLiteral(line)),
94
+ t.jsxAttribute(t.jsxIdentifier('data-agy-col'), t.stringLiteral(col)),
95
+ t.jsxAttribute(t.jsxIdentifier('data-agy-ast-path'), t.stringLiteral(currentIndex)),
96
+ t.jsxAttribute(t.jsxIdentifier('data-agy-tag'), t.stringLiteral(tagName))
97
+ );
98
+ },
99
+ });
100
+
101
+ const output = generate(ast, {
102
+ retainLines: true,
103
+ sourceMaps: true,
104
+ sourceFileName: relPath,
105
+ });
106
+
107
+ return {
108
+ code: output.code,
109
+ map: output.map,
110
+ };
111
+ } catch {
112
+ // Return untransformed code so Vite's error overlay or ScreenErrorBoundary can report it cleanly
113
+ return null;
114
+ }
115
+ },
116
+ };
117
+ }
@@ -0,0 +1,85 @@
1
+ import type { Plugin } from 'vite';
2
+ import { discoverScreenFilesSync } from '../src/server/astEngine';
3
+
4
+ const VIRTUAL_MODULE_ID = 'virtual:zelune-screens';
5
+ const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID;
6
+
7
+ const PREFERRED_ORDER = ['Login', 'Home', 'ProductDetail', 'Checkout', 'Profile'];
8
+
9
+ /**
10
+ * Vite Virtual Module plugin that dynamically discovers and imports React Native / Expo
11
+ * screen components from any target project (`--project /path/to/app`).
12
+ */
13
+ export function viteVirtualScreensPlugin(workspaceRoot: string): Plugin {
14
+ return {
15
+ name: 'vite-virtual-zelune-screens',
16
+ resolveId(id) {
17
+ if (id === VIRTUAL_MODULE_ID) {
18
+ return RESOLVED_VIRTUAL_MODULE_ID;
19
+ }
20
+ return null;
21
+ },
22
+ load(id) {
23
+ if (id !== RESOLVED_VIRTUAL_MODULE_ID) {
24
+ return null;
25
+ }
26
+
27
+ const discovered = discoverScreenFilesSync(workspaceRoot);
28
+ const importLines: string[] = [
29
+ `import React from 'react';`,
30
+ `function resolveScreenComponent(mod) {`,
31
+ ` if (!mod) return null;`,
32
+ ` if (typeof mod.default === 'function' || (typeof mod.default === 'object' && mod.default !== null)) {`,
33
+ ` return mod.default;`,
34
+ ` }`,
35
+ ` for (const key of Object.keys(mod)) {`,
36
+ ` if (typeof mod[key] === 'function') return mod[key];`,
37
+ ` }`,
38
+ ` return null;`,
39
+ `}`,
40
+ ];
41
+
42
+ const mapEntries: string[] = [];
43
+ const orderIds: string[] = [];
44
+
45
+ const sorted = [...discovered].sort((a, b) => {
46
+ const idxA = PREFERRED_ORDER.indexOf(a.id);
47
+ const idxB = PREFERRED_ORDER.indexOf(b.id);
48
+ if (idxA !== -1 && idxB !== -1) return idxA - idxB;
49
+ if (idxA !== -1) return -1;
50
+ if (idxB !== -1) return 1;
51
+ return a.id.localeCompare(b.id);
52
+ });
53
+
54
+ sorted.forEach((scr, index) => {
55
+ const varName = `_screenMod_${index}`;
56
+ const normalizedFullPath = scr.fullPath.replace(/\\/g, '/');
57
+ importLines.push(`import * as ${varName} from ${JSON.stringify(normalizedFullPath)};`);
58
+ mapEntries.push(` ${JSON.stringify(scr.id)}: resolveScreenComponent(${varName})`);
59
+ orderIds.push(scr.id);
60
+ });
61
+
62
+ return [
63
+ ...importLines,
64
+ `export const SCREEN_COMPONENTS = {`,
65
+ mapEntries.join(',\n'),
66
+ `};`,
67
+ `export const SCREEN_ORDER = ${JSON.stringify(orderIds)};`,
68
+ ].join('\n');
69
+ },
70
+ configureServer(server) {
71
+ server.watcher.on('add', (file) => {
72
+ if (file.endsWith('.tsx') || file.endsWith('.jsx')) {
73
+ const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
74
+ if (mod) server.moduleGraph.invalidateModule(mod);
75
+ }
76
+ });
77
+ server.watcher.on('unlink', (file) => {
78
+ if (file.endsWith('.tsx') || file.endsWith('.jsx')) {
79
+ const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);
80
+ if (mod) server.moduleGraph.invalidateModule(mod);
81
+ }
82
+ });
83
+ },
84
+ };
85
+ }
@@ -0,0 +1,21 @@
1
+ # Zelune React Native Visual Canvas & Craft Rules
2
+
3
+ When modifying React Native/Expo screens or the Zelune Studio Canvas:
4
+
5
+ 1. **Design System & Product Truth (`pbakaus/impeccable`)**:
6
+ - Always consult `PRODUCT.md` and `DESIGN.md` before modifying colors, typography, or layout.
7
+ - **Anti-Slop Enforcement**: Never introduce default `Inter` font stacks, purple-to-cyan/violet button gradients (`linear-gradient(135deg, #4F46E5, #7C3AED)`), untinted pure grays, or raw emoji icons (`🎧`, `⌨️`) inside rounded squares.
8
+ - Apply `font-variant-numeric: tabular-nums` (`.tabular-nums`) to all coordinates, line numbers, dimensions (`393 × 852`), zoom percentages, and prices.
9
+
10
+ 2. **Motion & Touch Ergonomics (`emilkowalski/skills`)**:
11
+ - Use `--ease-out: cubic-bezier(0.23, 1, 0.32, 1)` for interactive hover/press states and `--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1)` for programmatic camera focus/fit transitions.
12
+ - Maintain `0ms` transition override during direct pointer canvas panning and wheel zooming.
13
+ - Every React Native `<TouchableOpacity>` must include `activeOpacity={0.78}` and ergonomic vertical touch padding.
14
+
15
+ 3. **Parametric Taste Dials (`leonxlnx/taste-skill`)**:
16
+ - Keep Studio Chrome at `Design Variance: 2/10`, `Motion Intensity: 6/10`, `Visual Density: 8/10` (supporting reactive `data-density="compact" | "spacious"` tokens).
17
+ - Keep Mobile Reference Screens at `Design Variance: 7/10`, `Motion Intensity: 7/10`, `Visual Density: 4/10`.
18
+
19
+ 4. **AST Engine Invariants**:
20
+ - Preserve `StyleSheet.create` declarations at the bottom of screen `.tsx` files so Tier-1 AST inspection and surgical style patching remain deterministic.
21
+ - Use `useNavigation()` (`navigation.navigate('RouteName')`) or Expo Router (`router.push('/route')`) for screen transitions so the canvas automatically extracts SVG navigation flow arrows.
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: zelune
3
+ description: Launch, inspect, and surgically patch React Native / Expo screens on the Zelune Figma-style multi-screen visual canvas. Enforces pbakaus/impeccable anti-slop rules, emilkowalski/skills motion physics, and leonxlnx/taste-skill parametric dials via PRODUCT.md and DESIGN.md.
4
+ ---
5
+
6
+ # Zelune — React Native Visual Canvas & Craft Governance (`zelune`)
7
+
8
+ Use this skill when working with React Native or Expo mobile applications in Google Antigravity to provide a live multi-screen Figma-style visual canvas synchronized with `.tsx` source files via the **Zelune** MCP server.
9
+
10
+ ## 1. Mandatory Pre-Flight Context (`pbakaus/impeccable`)
11
+ Before generating or patching any UI component, read:
12
+ - **`PRODUCT.md`** — Durable product truth, target audience, and dual-surface architecture.
13
+ - **`DESIGN.md`** — Governed color tokens, typography hierarchy, motion curves, and anti-slop guardrails.
14
+
15
+ ## 2. Parametric Taste Dials (`leonxlnx/taste-skill`)
16
+ Configure your structural and visual choices according to the surface:
17
+ - **Studio Chrome (`src/canvas/`)**: `Design Variance: 2/10`, `Motion Intensity: 6/10`, `Visual Density: 8/10` (Cockpit precision, `1px` hairlines, `tabular-nums`, switchable `compact`/`spacious` density).
18
+ - **Mobile Artboards (`src/screens/`)**: `Design Variance: 7/10`, `Motion Intensity: 7/10`, `Visual Density: 4/10` (Editorial hardware hierarchy, technical spec monograms, generous negative space).
19
+
20
+ ## 3. Motion & Touch Ergonomics (`emilkowalski/skills`)
21
+ - **Easing Curves**: Never use `ease-in` for entering elements. Use `--ease-out: cubic-bezier(0.23, 1, 0.32, 1)` (`120ms–160ms`) for micro-interactions and `--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1)` (`380ms`) for camera focus transitions.
22
+ - **Tactile Feedback**: Apply `scale: 0.97` on `:active` (with base `scale: 1` to preserve stacking context) for web studio controls, and `activeOpacity={0.78}` with ≥44pt vertical targets on React Native `<TouchableOpacity>` elements.
23
+
24
+ ## 4. Available Zelune MCP Tools (`zelune`)
25
+ 1. **`list_canvas_screens`** — Lists all discovered React Native screens (`src/screens/`, `screens/`, or Expo Router `app/`), their JSX element counts, and outgoing navigation flow edges.
26
+ 2. **`inspect_screen_element`** — Given `{ file, astPath }`, returns the exact JSX element's tag name, line/column coordinates, static text copy, resolved `StyleSheet.create` or inline style properties, and JSX snippet.
27
+ 3. **`patch_screen_element`** — Surgically patches `{ file, astPath, text, styles }` directly in the `.tsx` source file while preserving comments, formatting, and dynamic expressions.
28
+
29
+ ## 5. Launching the Zelune Browser Canvas
30
+ ```bash
31
+ npx zelune --project /path/to/react-native-app
32
+ ```
33
+ Then open `http://127.0.0.1:5173`.