@myop/cli 0.1.45 → 0.1.47

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,407 @@
1
+ ---
2
+ name: myop-react-host
3
+ description: "Integrate Myop components into React applications using @myop/react. ALWAYS use the MyopComponent React component or auto-generated packages — NEVER create iframes manually. This skill covers MyopComponent props, typed event handlers, data binding, preloading, auto-generated packages, and local dev setup. Activate when the user is building a React app that hosts Myop components, or when you see @myop/react in package.json."
4
+ ---
5
+
6
+ # Myop React Host Integration
7
+
8
+ Embed Myop components in React applications using `@myop/react`.
9
+
10
+ ## CRITICAL: Always Use the SDK
11
+
12
+ **NEVER create `<iframe>` elements manually. NEVER call `myop_init_interface()` or wire `myop_cta_handler()` directly.**
13
+
14
+ The `@myop/react` SDK handles all iframe management, communication, loading, error handling, sizing, and caching. Always use `<MyopComponent>` or an auto-generated package.
15
+
16
+ ```tsx
17
+ // WRONG — never do this
18
+ <iframe ref={ref} src="/component.html" />
19
+ ref.current.contentWindow.myop_init_interface(data)
20
+
21
+ // CORRECT — always use the SDK
22
+ <MyopComponent componentId="abc-123" data={data} on={handler} />
23
+ ```
24
+
25
+ ## End-to-End Workflow: React App with Myop Components
26
+
27
+ Each Myop component is a **separate project** in its own directory. You create them, develop them, push them to get a `componentId`, then reference that ID in your React host app.
28
+
29
+ ### Step 1: Create component projects
30
+
31
+ Each component needs its own directory with `myop.config.json` + `index.html`:
32
+
33
+ ```bash
34
+ # Create first component
35
+ mkdir components/sidebar && cd components/sidebar
36
+ npx myop create # Scaffolds index.html + myop.config.json, starts dev server
37
+ # Build the component UI (see myop-component skill)
38
+ # Ctrl+C to stop dev server when done
39
+
40
+ # Create second component
41
+ cd ../
42
+ mkdir chart && cd chart
43
+ npx myop create
44
+ # Build this component too
45
+ ```
46
+
47
+ ### Step 2: Push components to get IDs
48
+
49
+ Each component must be pushed to the Myop platform to get a `componentId`:
50
+
51
+ ```bash
52
+ cd components/sidebar
53
+ npx myop push # Uploads and assigns a componentId (UUID)
54
+ # Output: componentId is now in myop.config.json
55
+
56
+ cd ../chart
57
+ npx myop push
58
+ ```
59
+
60
+ After push, each `myop.config.json` has a real `componentId` (UUID).
61
+
62
+ ### Step 3: Set up the React host app
63
+
64
+ ```bash
65
+ cd my-react-app
66
+ npm install @myop/react @myop/sdk
67
+ ```
68
+
69
+ ### Step 4: Use components in React
70
+
71
+ ```tsx
72
+ import { MyopComponent } from "@myop/react";
73
+
74
+ function App() {
75
+ return (
76
+ <div style={{ display: "flex", gap: 16 }}>
77
+ <MyopComponent
78
+ componentId="<sidebar-componentId-from-step-2>"
79
+ data={{ items: ["Home", "Settings"] }}
80
+ onItemSelected={({ itemId }) => console.log(itemId)}
81
+ style={{ width: 300, height: "100%" }}
82
+ />
83
+ <MyopComponent
84
+ componentId="<chart-componentId-from-step-2>"
85
+ data={{ values: [10, 20, 30] }}
86
+ style={{ flex: 1 }}
87
+ />
88
+ </div>
89
+ );
90
+ }
91
+ ```
92
+
93
+ ### Working locally on existing components (componentId already in code)
94
+
95
+ When you find `componentId` values already used in the codebase and the developer wants to modify those components locally:
96
+
97
+ **Step A: Pull the component source**
98
+
99
+ ```bash
100
+ # Create a directory for the component and pull it by ID
101
+ mkdir components/sidebar && cd components/sidebar
102
+ npx myop pull <componentId>
103
+ # Downloads index.html + creates myop.config.json with the componentId
104
+ ```
105
+
106
+ **Step B: Start the local dev server**
107
+
108
+ ```bash
109
+ npx myop dev # Serves component on port 9292 with HMR
110
+ ```
111
+
112
+ **Step C: Point the React app to local dev server**
113
+
114
+ ```tsx
115
+ import { enableLocalDev } from "@myop/react";
116
+ enableLocalDev(); // All <MyopComponent> instances load from localhost:9292 instead of cloud
117
+ ```
118
+
119
+ Now edits to `index.html` in the component directory are reflected instantly in the React app.
120
+
121
+ **Step D: Push changes when done**
122
+
123
+ ```bash
124
+ cd components/sidebar
125
+ npx myop push # Uploads updated component to the same componentId
126
+ ```
127
+
128
+ Remove or comment out `enableLocalDev()` to go back to loading from the Myop cloud.
129
+
130
+ **Full local dev flow (multiple components):**
131
+
132
+ ```bash
133
+ # Pull all components you need to work on
134
+ mkdir -p components && cd components
135
+ npx myop pull <sidebar-id> -o sidebar/index.html
136
+ npx myop pull <chart-id> -o chart/index.html
137
+
138
+ # Start dev server (serves all components in subdirectories)
139
+ npx myop dev -m # Monorepo mode — select which components to serve
140
+ ```
141
+
142
+ ## When This Skill Activates
143
+
144
+ - `@myop/react` is in `package.json` dependencies
145
+ - User asks to "add a Myop component to React", "integrate Myop", "use MyopComponent"
146
+ - Files import from `@myop/react`
147
+
148
+ ## Installation
149
+
150
+ ```bash
151
+ npm install @myop/react @myop/sdk
152
+ ```
153
+
154
+ ## Quick Start
155
+
156
+ ### Option 1: Auto-Generated Package (Recommended)
157
+
158
+ Every Myop component has an auto-generated, fully typed React package:
159
+
160
+ ```bash
161
+ npm install https://cloud.myop.dev/npm/{componentId}/react
162
+ ```
163
+
164
+ ```tsx
165
+ import { MyComponent } from "@myop/my-component";
166
+
167
+ function App() {
168
+ return (
169
+ <MyComponent
170
+ data={{ title: "Hello", items: ["a", "b"] }}
171
+ onItemSelected={(payload) => {
172
+ // payload is typed: { itemId: string }
173
+ console.log(payload.itemId);
174
+ }}
175
+ />
176
+ );
177
+ }
178
+ ```
179
+
180
+ **Why auto-generated packages:**
181
+ - Full TypeScript types for `data` and all CTA events — auto-generated from the component's `<script type="myop/types">` block
182
+ - No `componentId` prop needed — baked into the package
183
+ - Tiny bundle impact (~6KB for `@myop/react`, component loads at runtime)
184
+
185
+ ### Option 2: MyopComponent Directly
186
+
187
+ ```tsx
188
+ import { MyopComponent } from "@myop/react";
189
+
190
+ function App() {
191
+ return (
192
+ <MyopComponent
193
+ componentId="your-component-id"
194
+ data={{ title: "Hello" }}
195
+ on={(action, payload) => {
196
+ console.log(action, payload);
197
+ }}
198
+ />
199
+ );
200
+ }
201
+ ```
202
+
203
+ ## MyopComponent Props
204
+
205
+ | Prop | Type | Default | Description |
206
+ |------|------|---------|-------------|
207
+ | `componentId` | `string` | — | Myop component ID (UUID) |
208
+ | `data` | `TData` | — | Data passed to `myop_init_interface`. Reactive — updates trigger re-render in component |
209
+ | `on` | `(action, payload) => void` | — | Generic handler for all CTA events |
210
+ | `on[ActionName]` | `(payload) => void` | — | Typed handler for specific CTA (e.g., `onItemSelected`) |
211
+ | `onLoad` | `(component) => void` | — | Called when component finishes loading |
212
+ | `onError` | `(error: string) => void` | — | Called on load failure |
213
+ | `onSizeChange` | `(size) => boolean \| void` | — | Called when auto-sized component changes dimensions. Return `false` to reject |
214
+ | `style` | `CSSProperties` | — | CSS styles for the outer container |
215
+ | `loader` | `ReactNode` | — | Custom loading indicator (shown while component loads) |
216
+ | `fallback` | `ReactNode` | — | Custom error fallback UI |
217
+ | `fadeDuration` | `number` | `200` | Loader fade-out duration in ms |
218
+ | `autoSize` | `boolean` | `false` | Auto-size container to match iframe content |
219
+ | `environment` | `string` | — | Load from specific environment (e.g., `"staging"`) |
220
+ | `preview` | `boolean` | `false` | Load unpublished preview version |
221
+
222
+ ## Typed Event Handlers
223
+
224
+ CTA actions from the component are converted to `on[PascalCase]` props:
225
+
226
+ | Component CTA action | React prop |
227
+ |----------------------|------------|
228
+ | `'item-selected'` | `onItemSelected` |
229
+ | `'form-submitted'` | `onFormSubmitted` |
230
+ | `'row-clicked'` | `onRowClicked` |
231
+
232
+ ```tsx
233
+ // Both work — use typed handlers for type safety, `on` for catch-all
234
+ <MyopComponent<MyData, MyCtaPayloads>
235
+ componentId="..."
236
+ data={data}
237
+ onItemSelected={(payload) => {
238
+ // payload typed as MyCtaPayloads['item-selected']
239
+ }}
240
+ on={(action, payload) => {
241
+ // Generic catch-all
242
+ }}
243
+ />
244
+ ```
245
+
246
+ ## Data Binding
247
+
248
+ The `data` prop is reactive. When it changes, `myop_init_interface(data)` is called automatically:
249
+
250
+ ```tsx
251
+ function App() {
252
+ const [count, setCount] = useState(0);
253
+
254
+ return (
255
+ <>
256
+ <button onClick={() => setCount(c => c + 1)}>+1</button>
257
+ <MyopComponent
258
+ componentId="counter-display"
259
+ data={{ count }} // Component updates when count changes
260
+ />
261
+ </>
262
+ );
263
+ }
264
+ ```
265
+
266
+ ## Preloading
267
+
268
+ Preload components to eliminate loading delay when they mount:
269
+
270
+ ```tsx
271
+ import { preloadComponents, isPreloaded } from "@myop/react";
272
+
273
+ // Preload on app startup or route entry
274
+ await preloadComponents(["component-id-1", "component-id-2"]);
275
+
276
+ // Optionally preload for a specific environment
277
+ await preloadComponents(["component-id-1"], "staging");
278
+
279
+ // Check if a component is cached
280
+ if (isPreloaded("component-id-1")) {
281
+ console.log("Will render instantly");
282
+ }
283
+ ```
284
+
285
+ **When to preload:**
286
+ - App startup — preload components on the landing page
287
+ - Route transitions — preload components for the next page
288
+ - Tab containers — preload all tab contents when container mounts
289
+
290
+ ## Configuration Functions
291
+
292
+ ```tsx
293
+ import {
294
+ enableLocalDev,
295
+ setCloudRepositoryUrl,
296
+ setEnvironment,
297
+ } from "@myop/react";
298
+
299
+ // Point to local dev server (myop dev on port 9292)
300
+ enableLocalDev();
301
+
302
+ // Custom cloud URL
303
+ setCloudRepositoryUrl("https://custom.myop.dev");
304
+
305
+ // Set default environment for all components
306
+ setEnvironment("staging");
307
+ ```
308
+
309
+ ## Accessing the Component Instance
310
+
311
+ Use `onLoad` to get direct access to the loaded component:
312
+
313
+ ```tsx
314
+ <MyopComponent
315
+ componentId="..."
316
+ onLoad={(component) => {
317
+ // component.props.myop_init_interface(data) — update data
318
+ // component.props.myop_cta_handler — read current handler
319
+ // component.dispose() — cleanup
320
+ // component.hide() / component.show()
321
+ }}
322
+ />
323
+ ```
324
+
325
+ ## Auto-Size Mode
326
+
327
+ When `autoSize={true}`, the container dimensions follow the iframe content:
328
+
329
+ ```tsx
330
+ <MyopComponent
331
+ componentId="..."
332
+ autoSize={true}
333
+ onSizeChange={(size) => {
334
+ console.log(size.width, size.height);
335
+ // Return false to reject the size change
336
+ }}
337
+ style={{ maxWidth: 600, maxHeight: 400 }}
338
+ />
339
+ ```
340
+
341
+ ## Complete Example
342
+
343
+ ```tsx
344
+ import { MyopComponent, preloadComponents } from "@myop/react";
345
+ import { useEffect, useState } from "react";
346
+
347
+ // Preload on module load
348
+ preloadComponents(["sidebar-abc123", "chart-def456"]);
349
+
350
+ function Dashboard() {
351
+ const [tasks, setTasks] = useState([
352
+ { id: "1", title: "Review PR", completed: false },
353
+ { id: "2", title: "Deploy", completed: true },
354
+ ]);
355
+
356
+ return (
357
+ <div style={{ display: "flex", height: "100vh" }}>
358
+ <MyopComponent
359
+ componentId="sidebar-abc123"
360
+ data={{ tasks }}
361
+ onTaskToggled={({ taskId, completed }) => {
362
+ setTasks(prev =>
363
+ prev.map(t => t.id === taskId ? { ...t, completed } : t)
364
+ );
365
+ }}
366
+ onTaskDeleted={({ taskId }) => {
367
+ setTasks(prev => prev.filter(t => t.id !== taskId));
368
+ }}
369
+ loader={<div>Loading sidebar...</div>}
370
+ fallback={<div>Failed to load sidebar</div>}
371
+ style={{ width: 300, height: "100%" }}
372
+ />
373
+ <MyopComponent
374
+ componentId="chart-def456"
375
+ data={{ tasks }}
376
+ style={{ flex: 1 }}
377
+ />
378
+ </div>
379
+ );
380
+ }
381
+ ```
382
+
383
+ ## TypeScript Types
384
+
385
+ ```typescript
386
+ import type {
387
+ IPropTypes, // Full props type (base + event handlers)
388
+ ITypedMyopComponent, // Component instance with typed props
389
+ IMyopComponentProps, // Props interface (myop_init_interface + myop_cta_handler)
390
+ EventHandlerProps, // Generated on[Action] props type
391
+ KebabToPascal, // Utility: 'item-selected' -> 'ItemSelected'
392
+ } from "@myop/react";
393
+ ```
394
+
395
+ ## Common Mistakes — WRONG vs CORRECT
396
+
397
+ | WRONG | CORRECT |
398
+ |-------|---------|
399
+ | Creating `<iframe>` elements manually | Using `<MyopComponent>` from `@myop/react` |
400
+ | Calling `myop_init_interface()` on iframe contentWindow | Passing `data` prop to `<MyopComponent>` |
401
+ | Wiring `myop_cta_handler` via refs | Using `on` or `onActionName` props |
402
+ | Loading component HTML from local file | Using `componentId` — SDK fetches from Myop cloud |
403
+ | Managing iframe lifecycle with useEffect | SDK handles load, error, cleanup automatically |
404
+
405
+ ## Reference
406
+
407
+ - [Auto-Generated Packages](references/auto-generated-packages.md)
@@ -0,0 +1,70 @@
1
+ # Auto-Generated Packages
2
+
3
+ Myop auto-generates a fully typed npm package for every component, for each framework.
4
+
5
+ ## Install URL Pattern
6
+
7
+ ```
8
+ https://cloud.myop.dev/npm/{componentId}/react
9
+ https://cloud.myop.dev/npm/{componentId}/vue
10
+ https://cloud.myop.dev/npm/{componentId}/angular
11
+ https://cloud.myop.dev/npm/{componentId}/react-native
12
+ https://cloud.myop.dev/npm/{componentId}/typescript
13
+ ```
14
+
15
+ ## How It Works
16
+
17
+ 1. The server reads the component's `<script type="myop/types">` block
18
+ 2. Extracts `MyopInitData` and `MyopCtaPayloads` interfaces
19
+ 3. Generates a typed wrapper component with the `componentId` baked in
20
+ 4. Returns a `.tgz` package installable via `npm install <url>`
21
+
22
+ ## What Gets Generated (React)
23
+
24
+ ```
25
+ package/
26
+ package.json # @myop/{component-name}, peerDeps: react, @myop/react
27
+ index.tsx # forwardRef component wrapping MyopComponent
28
+ index.d.ts # TypeScript declarations
29
+ index.js # Compiled CommonJS
30
+ ```
31
+
32
+ ### Generated Component Code
33
+
34
+ ```tsx
35
+ import { MyopComponent, IPropTypes } from '@myop/react';
36
+ import { forwardRef } from 'react';
37
+
38
+ // Types extracted from the component's <script type="myop/types">
39
+ export interface MyopInitData { /* ... */ }
40
+ export interface MyopCtaPayloads { /* ... */ }
41
+
42
+ export type MyComponentProps = Omit<IPropTypes<MyopInitData, MyopCtaPayloads>, 'componentId'>;
43
+
44
+ export const MyComponent = forwardRef<any, MyComponentProps>((props, ref) => (
45
+ <MyopComponent<MyopInitData, MyopCtaPayloads>
46
+ {...props}
47
+ componentId="baked-in-uuid"
48
+ ref={ref}
49
+ />
50
+ ));
51
+ ```
52
+
53
+ ### What You Get
54
+
55
+ - `data` prop is typed as `MyopInitData`
56
+ - Individual CTA handlers are typed (e.g., `onItemSelected: (payload: { itemId: string }) => void`)
57
+ - No need to pass `componentId` — it's baked in
58
+ - Full IntelliSense / autocomplete in IDEs
59
+
60
+ ## Info Endpoint
61
+
62
+ Get install instructions and component info:
63
+
64
+ ```
65
+ https://cloud.myop.dev/npm/{componentId}/react/info
66
+ ```
67
+
68
+ ## Updating
69
+
70
+ Re-run `npm install <url>` to get the latest types after updating the component's type definitions.