@myop/cli 0.1.40 → 0.1.42
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/dist/myop-cli.js +1184 -833
- package/dist/skills/myop-component/SKILL.md +276 -0
- package/dist/skills/myop-component/references/best-practices.md +406 -0
- package/dist/skills/myop-component/references/component-api.md +457 -0
- package/dist/skills/myop-component/references/dev-workflow.md +218 -0
- package/dist/skills/myop-component/references/sizing-and-layout.md +270 -0
- package/dist/skills/myop-component/references/type-definitions.md +270 -0
- package/package.json +2 -2
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
# Component Public API
|
|
2
|
+
|
|
3
|
+
Myop components communicate with host applications through exactly 2 global functions defined on `window`. Both must be defined synchronously during top-level script execution.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
- [myop_init_interface](#myop_init_interface)
|
|
7
|
+
- [myop_cta_handler](#myop_cta_handler)
|
|
8
|
+
- [Preview Script](#preview-script)
|
|
9
|
+
- [Complete Example](#complete-example)
|
|
10
|
+
- [Host-Side Integration](#host-side-integration)
|
|
11
|
+
|
|
12
|
+
## myop_init_interface
|
|
13
|
+
|
|
14
|
+
**Direction:** Host Application -> Component
|
|
15
|
+
|
|
16
|
+
**Purpose:** Initialize or update the component with data. Also used to read current state.
|
|
17
|
+
|
|
18
|
+
### Dual Signature
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// Setter: Host sends data to component
|
|
22
|
+
window.myop_init_interface(data); // returns void
|
|
23
|
+
|
|
24
|
+
// Getter: Host reads current component state
|
|
25
|
+
const state = window.myop_init_interface(); // returns MyopInitData
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Implementation Pattern
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
(function() {
|
|
32
|
+
var currentState = {};
|
|
33
|
+
|
|
34
|
+
window.myop_init_interface = function(data) {
|
|
35
|
+
// Getter mode: return current state when called without arguments
|
|
36
|
+
if (!data) return currentState;
|
|
37
|
+
|
|
38
|
+
// Setter mode: store state and render synchronously
|
|
39
|
+
currentState = data;
|
|
40
|
+
render(data);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function render(data) {
|
|
44
|
+
var root = document.getElementById('app-root');
|
|
45
|
+
// Build HTML string and set innerHTML in one operation
|
|
46
|
+
root.innerHTML = '<h1>' + data.title + '</h1>';
|
|
47
|
+
}
|
|
48
|
+
})();
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Rules
|
|
52
|
+
|
|
53
|
+
1. **MUST be defined at top-level** - The host calls this function immediately after loading the component. If it's not yet defined, the component will not receive data.
|
|
54
|
+
|
|
55
|
+
2. **MUST render synchronously** - When called with data, the component must update the DOM before the function returns. No `setTimeout`, no `requestAnimationFrame`, no `await`.
|
|
56
|
+
|
|
57
|
+
3. **MUST handle getter mode** - When called without arguments (`myop_init_interface()`), return the current state object. The host uses this to read component state.
|
|
58
|
+
|
|
59
|
+
4. **MUST be idempotent** - Can be called multiple times with updated data. Each call should fully re-render.
|
|
60
|
+
|
|
61
|
+
### WRONG Examples
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// WRONG: Defined inside DOMContentLoaded (too late)
|
|
65
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
66
|
+
window.myop_init_interface = function(data) { render(data); };
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// WRONG: Async rendering (host expects synchronous update)
|
|
70
|
+
window.myop_init_interface = async function(data) {
|
|
71
|
+
const extra = await fetch('/api/data'); // NO!
|
|
72
|
+
render(data, extra);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// WRONG: Deferred rendering
|
|
76
|
+
window.myop_init_interface = function(data) {
|
|
77
|
+
setTimeout(() => render(data), 0); // NO!
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// WRONG: Missing getter mode
|
|
81
|
+
window.myop_init_interface = function(data) {
|
|
82
|
+
render(data); // What happens when called with no args?
|
|
83
|
+
};
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## myop_cta_handler
|
|
87
|
+
|
|
88
|
+
**Direction:** Component -> Host Application
|
|
89
|
+
|
|
90
|
+
**Purpose:** Send user actions (clicks, selections, form submissions) from the component to the host application. Also used for standard actions like requesting a size change.
|
|
91
|
+
|
|
92
|
+
### Signature
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
window.myop_cta_handler(action_id, payload);
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
| Parameter | Type | Description |
|
|
99
|
+
|-----------|------|-------------|
|
|
100
|
+
| `action_id` | `string` | Action identifier (kebab-case, e.g. `'item-clicked'`) |
|
|
101
|
+
| `payload` | `object` | Action-specific data |
|
|
102
|
+
|
|
103
|
+
### Implementation Pattern
|
|
104
|
+
|
|
105
|
+
```javascript
|
|
106
|
+
(function() {
|
|
107
|
+
// Define a no-op default (host will override this)
|
|
108
|
+
window.myop_cta_handler = function(action, payload) {
|
|
109
|
+
// Will be replaced by host application
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Call it when user interacts
|
|
113
|
+
document.getElementById('app-root').addEventListener('click', function(e) {
|
|
114
|
+
var button = e.target.closest('button');
|
|
115
|
+
if (button) {
|
|
116
|
+
window.myop_cta_handler('button-clicked', {
|
|
117
|
+
buttonId: button.dataset.id,
|
|
118
|
+
label: button.textContent
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
})();
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Standard Actions
|
|
126
|
+
|
|
127
|
+
These action IDs have special meaning and are handled by the Myop SDK:
|
|
128
|
+
|
|
129
|
+
| Action ID | Payload | Purpose |
|
|
130
|
+
|-----------|---------|---------|
|
|
131
|
+
| `'size-requested'` | `{ width?, height?, minWidth?, maxWidth?, minHeight?, maxHeight?, required? }` | Request the host to resize the component container |
|
|
132
|
+
|
|
133
|
+
```javascript
|
|
134
|
+
// Request more width
|
|
135
|
+
window.myop_cta_handler('size-requested', {
|
|
136
|
+
width: 400,
|
|
137
|
+
minWidth: 300,
|
|
138
|
+
maxWidth: 600,
|
|
139
|
+
required: true // true = content is clipped without this size
|
|
140
|
+
// false = preference only, host may ignore
|
|
141
|
+
});
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Custom Actions
|
|
145
|
+
|
|
146
|
+
Define any custom actions your component needs. Use kebab-case for action IDs:
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
// User selects an item
|
|
150
|
+
window.myop_cta_handler('item-selected', { itemId: 'abc-123' });
|
|
151
|
+
|
|
152
|
+
// Form submitted
|
|
153
|
+
window.myop_cta_handler('form-submitted', {
|
|
154
|
+
name: 'John',
|
|
155
|
+
email: 'john@example.com'
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
// Status changed
|
|
159
|
+
window.myop_cta_handler('status-changed', { from: 'draft', to: 'published' });
|
|
160
|
+
|
|
161
|
+
// No-payload action
|
|
162
|
+
window.myop_cta_handler('refresh-requested', {});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Rules
|
|
166
|
+
|
|
167
|
+
1. **Define at top-level** - Even though the host overrides it, defining a no-op default prevents errors if the component fires an action before the host sets up its handler.
|
|
168
|
+
|
|
169
|
+
2. **Use kebab-case** for action IDs: `'item-clicked'` not `'itemClicked'` or `'ITEM_CLICKED'`.
|
|
170
|
+
|
|
171
|
+
3. **Payload must be serializable** - Plain objects only. No DOM elements, functions, or class instances.
|
|
172
|
+
|
|
173
|
+
4. **Document all actions** in the `MyopCtaPayloads` type definition so the host developer knows what to expect.
|
|
174
|
+
|
|
175
|
+
## Preview Script
|
|
176
|
+
|
|
177
|
+
The `<script id="myop_preview">` block provides mock data for development. In production, the Myop SDK replaces this with a real data call.
|
|
178
|
+
|
|
179
|
+
```html
|
|
180
|
+
<!-- Runs during development, removed in production -->
|
|
181
|
+
<script id="myop_preview">
|
|
182
|
+
window.myop_init_interface({
|
|
183
|
+
title: 'Preview Mode',
|
|
184
|
+
items: [
|
|
185
|
+
{ id: '1', label: 'Sample item 1' },
|
|
186
|
+
{ id: '2', label: 'Sample item 2' },
|
|
187
|
+
{ id: '3', label: 'Sample item 3' }
|
|
188
|
+
]
|
|
189
|
+
});
|
|
190
|
+
</script>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Testing Delayed Load
|
|
194
|
+
|
|
195
|
+
```html
|
|
196
|
+
<script id="myop_preview">
|
|
197
|
+
// Simulate delayed data arrival
|
|
198
|
+
setTimeout(function() {
|
|
199
|
+
window.myop_init_interface({
|
|
200
|
+
title: 'Loaded after delay',
|
|
201
|
+
items: []
|
|
202
|
+
});
|
|
203
|
+
}, 1000);
|
|
204
|
+
</script>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Complete Example
|
|
208
|
+
|
|
209
|
+
```html
|
|
210
|
+
<!DOCTYPE html>
|
|
211
|
+
<html lang="en">
|
|
212
|
+
<head>
|
|
213
|
+
<meta charset="UTF-8">
|
|
214
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
215
|
+
<meta name="myop:size" content='{"width":350,"height":"100%","minWidth":250}'>
|
|
216
|
+
<title>Task List</title>
|
|
217
|
+
<script type="myop/types">
|
|
218
|
+
interface MyopInitData {
|
|
219
|
+
tasks: Array<{
|
|
220
|
+
id: string;
|
|
221
|
+
title: string;
|
|
222
|
+
completed: boolean;
|
|
223
|
+
}>;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
interface MyopCtaPayloads {
|
|
227
|
+
'task-toggled': { taskId: string; completed: boolean };
|
|
228
|
+
'task-deleted': { taskId: string };
|
|
229
|
+
'size-requested': {
|
|
230
|
+
width?: number | null;
|
|
231
|
+
height?: number | null;
|
|
232
|
+
minWidth?: number | null;
|
|
233
|
+
maxWidth?: number | null;
|
|
234
|
+
minHeight?: number | null;
|
|
235
|
+
maxHeight?: number | null;
|
|
236
|
+
required?: boolean;
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
declare function myop_init_interface(): MyopInitData;
|
|
241
|
+
declare function myop_init_interface(data: MyopInitData): void;
|
|
242
|
+
declare function myop_cta_handler<K extends keyof MyopCtaPayloads>(
|
|
243
|
+
action: K, payload: MyopCtaPayloads[K]
|
|
244
|
+
): void;
|
|
245
|
+
</script>
|
|
246
|
+
<style>
|
|
247
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
248
|
+
html, body { width: 100%; height: 100%; overflow: hidden;
|
|
249
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
250
|
+
color: #333;
|
|
251
|
+
}
|
|
252
|
+
#app-root { width: 100%; height: 100%; display: flex; flex-direction: column; }
|
|
253
|
+
.header { padding: 12px 16px; border-bottom: 1px solid #eee; font-weight: 600; }
|
|
254
|
+
.task-list { flex: 1; overflow-y: auto; min-height: 0; }
|
|
255
|
+
.task { display: flex; align-items: center; padding: 10px 16px;
|
|
256
|
+
border-bottom: 1px solid #f0f0f0; gap: 10px; }
|
|
257
|
+
.task.done .task-title { text-decoration: line-through; color: #999; }
|
|
258
|
+
.task-title { flex: 1; }
|
|
259
|
+
.delete-btn { cursor: pointer; color: #999; border: none;
|
|
260
|
+
background: none; font-size: 16px; }
|
|
261
|
+
.delete-btn:hover { color: #e74c3c; }
|
|
262
|
+
</style>
|
|
263
|
+
</head>
|
|
264
|
+
<body>
|
|
265
|
+
<div id="app-root">
|
|
266
|
+
<div class="header">Tasks</div>
|
|
267
|
+
<div class="task-list"></div>
|
|
268
|
+
</div>
|
|
269
|
+
|
|
270
|
+
<script>
|
|
271
|
+
(function() {
|
|
272
|
+
var state = { tasks: [] };
|
|
273
|
+
var taskList = document.querySelector('.task-list');
|
|
274
|
+
|
|
275
|
+
function render(data) {
|
|
276
|
+
state = data;
|
|
277
|
+
taskList.innerHTML = data.tasks.map(function(task) {
|
|
278
|
+
return '<div class="task' + (task.completed ? ' done' : '') + '" data-id="' + task.id + '">' +
|
|
279
|
+
'<input type="checkbox"' + (task.completed ? ' checked' : '') + '>' +
|
|
280
|
+
'<span class="task-title">' + task.title + '</span>' +
|
|
281
|
+
'<button class="delete-btn" data-action="delete">×</button>' +
|
|
282
|
+
'</div>';
|
|
283
|
+
}).join('');
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
window.myop_init_interface = function(data) {
|
|
287
|
+
if (!data) return state;
|
|
288
|
+
render(data);
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
window.myop_cta_handler = function(action, payload) {};
|
|
292
|
+
|
|
293
|
+
// Event delegation for all task interactions
|
|
294
|
+
taskList.addEventListener('click', function(e) {
|
|
295
|
+
var taskEl = e.target.closest('.task');
|
|
296
|
+
if (!taskEl) return;
|
|
297
|
+
var taskId = taskEl.dataset.id;
|
|
298
|
+
|
|
299
|
+
if (e.target.type === 'checkbox') {
|
|
300
|
+
window.myop_cta_handler('task-toggled', {
|
|
301
|
+
taskId: taskId,
|
|
302
|
+
completed: e.target.checked
|
|
303
|
+
});
|
|
304
|
+
} else if (e.target.dataset.action === 'delete') {
|
|
305
|
+
window.myop_cta_handler('task-deleted', { taskId: taskId });
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
})();
|
|
309
|
+
</script>
|
|
310
|
+
|
|
311
|
+
<script id="myop_preview">
|
|
312
|
+
window.myop_init_interface({
|
|
313
|
+
tasks: [
|
|
314
|
+
{ id: '1', title: 'Review pull request', completed: false },
|
|
315
|
+
{ id: '2', title: 'Update documentation', completed: true },
|
|
316
|
+
{ id: '3', title: 'Deploy to staging', completed: false }
|
|
317
|
+
]
|
|
318
|
+
});
|
|
319
|
+
</script>
|
|
320
|
+
</body>
|
|
321
|
+
</html>
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Host-Side Integration
|
|
325
|
+
|
|
326
|
+
For context on how the host application consumes these functions in React (useful when debugging or advising host developers):
|
|
327
|
+
|
|
328
|
+
### Option 1: Auto-Generated React Package (Recommended)
|
|
329
|
+
|
|
330
|
+
Myop auto-generates a fully typed React package for every component. Install it directly:
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
npm install https://cloud.myop.dev/npm/{component-id}/react
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Then import and use it like any React component — with full TypeScript types for `data` and all CTA event payloads:
|
|
337
|
+
|
|
338
|
+
```tsx
|
|
339
|
+
// Auto-generated package gives you a typed React component
|
|
340
|
+
// e.g. npm install https://cloud.myop.dev/npm/{component-id}/react
|
|
341
|
+
import { TaskList } from "@myop/task-list";
|
|
342
|
+
|
|
343
|
+
function App() {
|
|
344
|
+
const [tasks, setTasks] = useState(fetchedTasks);
|
|
345
|
+
|
|
346
|
+
return (
|
|
347
|
+
<TaskList
|
|
348
|
+
data={{ tasks }}
|
|
349
|
+
onTaskToggled={(payload) => {
|
|
350
|
+
// payload is typed as { taskId: string; completed: boolean }
|
|
351
|
+
updateTask(payload.taskId, { completed: payload.completed });
|
|
352
|
+
}}
|
|
353
|
+
onTaskDeleted={(payload) => {
|
|
354
|
+
// payload is typed as { taskId: string }
|
|
355
|
+
deleteTask(payload.taskId);
|
|
356
|
+
}}
|
|
357
|
+
/>
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Why auto-generated packages are preferred:**
|
|
363
|
+
- Fully typed — TypeScript types for `data` and all CTA event payloads are auto-generated
|
|
364
|
+
- Zero configuration — no `componentId` needed, component is loaded automatically
|
|
365
|
+
- Zero bundle impact — the package is a thin typed wrapper (~6KB total for `@myop/react`), actual component loads at runtime
|
|
366
|
+
|
|
367
|
+
### Option 2: MyopComponent with `data` and `on` Props
|
|
368
|
+
|
|
369
|
+
For cases where you don't want to install a per-component package, use `MyopComponent` directly:
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
import { MyopComponent } from "@myop/react";
|
|
373
|
+
|
|
374
|
+
function App() {
|
|
375
|
+
const [tasks, setTasks] = useState(fetchedTasks);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<MyopComponent
|
|
379
|
+
componentId="your-component-id"
|
|
380
|
+
data={{ tasks }}
|
|
381
|
+
on={(action, payload) => {
|
|
382
|
+
switch (action) {
|
|
383
|
+
case 'task-toggled':
|
|
384
|
+
updateTask(payload.taskId, { completed: payload.completed });
|
|
385
|
+
break;
|
|
386
|
+
case 'task-deleted':
|
|
387
|
+
deleteTask(payload.taskId);
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
}}
|
|
391
|
+
/>
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
You can also use typed specific handlers alongside the generic `on` handler:
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
<MyopComponent<TaskData, TaskCtaPayloads>
|
|
400
|
+
componentId="your-component-id"
|
|
401
|
+
data={{ tasks }}
|
|
402
|
+
onTaskToggled={(payload) => {
|
|
403
|
+
updateTask(payload.taskId, { completed: payload.completed });
|
|
404
|
+
}}
|
|
405
|
+
onTaskDeleted={(payload) => {
|
|
406
|
+
deleteTask(payload.taskId);
|
|
407
|
+
}}
|
|
408
|
+
/>
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Key Props
|
|
412
|
+
|
|
413
|
+
| Prop | Type | Description |
|
|
414
|
+
|------|------|-------------|
|
|
415
|
+
| `componentId` | `string` | The Myop component ID (not needed with auto-generated packages) |
|
|
416
|
+
| `data` | `TData` | Data passed to the component via `myop_init_interface` |
|
|
417
|
+
| `on` | `(action, payload) => void` | Generic handler for all CTA events |
|
|
418
|
+
| `on[ActionName]` | `(payload) => void` | Typed handler for a specific CTA (e.g., `onTaskToggled`) |
|
|
419
|
+
| `style` | `CSSProperties` | CSS styles for the container |
|
|
420
|
+
| `environment` | `string` | Load from a specific environment (e.g., `"staging"`) |
|
|
421
|
+
| `preview` | `boolean` | Load the unpublished preview version |
|
|
422
|
+
|
|
423
|
+
### Preloading Components
|
|
424
|
+
|
|
425
|
+
Preloading fetches and caches component configs before they render, eliminating the network delay when the component mounts. This is useful for components that appear on user navigation or behind tabs — preload them early so they render instantly when needed.
|
|
426
|
+
|
|
427
|
+
```tsx
|
|
428
|
+
import { preloadComponents, isPreloaded } from "@myop/react";
|
|
429
|
+
|
|
430
|
+
// Preload on app startup or route entry
|
|
431
|
+
await preloadComponents(["component-id-1", "component-id-2"]);
|
|
432
|
+
|
|
433
|
+
// Optionally preload for a specific environment
|
|
434
|
+
await preloadComponents(["component-id-1"], "staging");
|
|
435
|
+
|
|
436
|
+
// Check if a component is already cached
|
|
437
|
+
if (isPreloaded("component-id-1")) {
|
|
438
|
+
console.log("Ready — will render without loading delay");
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
**How it works:**
|
|
443
|
+
- `preloadComponents(ids, env?, preview?)` fetches each component config from the Myop cloud and caches it in memory. Uses `Promise.allSettled` so a single failure doesn't block the rest.
|
|
444
|
+
- When `<MyopComponent>` (or an auto-generated component) later mounts with a preloaded ID, it hits the cache and loads instantly — no network request, no loader flash.
|
|
445
|
+
- The cache is keyed by `{componentId}:{environment}:{preview|live}`, so the same component can be preloaded for different environments separately.
|
|
446
|
+
- If no explicit `environment`/`preview` props are passed to the component, it automatically uses the params from the preload call.
|
|
447
|
+
|
|
448
|
+
**When to preload:**
|
|
449
|
+
- App startup — preload components used on the landing page
|
|
450
|
+
- Route transitions — preload components for the next likely page
|
|
451
|
+
- Tab containers — preload all tab contents when the container mounts
|
|
452
|
+
|
|
453
|
+
| Function | Returns | Description |
|
|
454
|
+
|----------|---------|-------------|
|
|
455
|
+
| `preloadComponents(ids, env?, preview?)` | `Promise<PromiseSettledResult[]>` | Fetch and cache component configs ahead of time |
|
|
456
|
+
| `isPreloaded(id, env?, preview?)` | `boolean` | Check if a component is already cached |
|
|
457
|
+
| `getPreloadedParams(id)` | `{ env, preview } \| undefined` | Get the environment/preview params used during preload |
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Development Workflow & CLI Commands
|
|
2
|
+
|
|
3
|
+
The Myop CLI provides commands for creating, developing, and deploying components.
|
|
4
|
+
|
|
5
|
+
## Contents
|
|
6
|
+
- [CLI Installation](#cli-installation)
|
|
7
|
+
- [Commands Reference](#commands-reference)
|
|
8
|
+
- [Create Command](#create-command)
|
|
9
|
+
- [Dev Command](#dev-command)
|
|
10
|
+
- [Sync Command](#sync-command)
|
|
11
|
+
- [Authentication](#authentication)
|
|
12
|
+
- [Configuration](#configuration)
|
|
13
|
+
|
|
14
|
+
## CLI Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install -g @myop/cli
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
After installation, the `myop` command is available globally.
|
|
21
|
+
|
|
22
|
+
## Commands Reference
|
|
23
|
+
|
|
24
|
+
| Command | Description |
|
|
25
|
+
|---------|-------------|
|
|
26
|
+
| `myop create` | Create a new component (interactive, starts dev server after) |
|
|
27
|
+
| `myop dev` | Start dev server with file watching and HMR |
|
|
28
|
+
| `myop push` | Upload component to Myop platform |
|
|
29
|
+
| `myop sync` | Build and upload component to Myop platform |
|
|
30
|
+
| `myop login` | Authenticate with Myop (opens browser for OAuth) |
|
|
31
|
+
| `myop logout` | Clear stored credentials |
|
|
32
|
+
| `myop whoami` | Show current authenticated user |
|
|
33
|
+
| `myop --ci` | Output status as JSON (for CI/CD) |
|
|
34
|
+
| `myop -m` / `myop --monorepo` | Monorepo mode: scan and dev multiple components |
|
|
35
|
+
|
|
36
|
+
## Create Command
|
|
37
|
+
|
|
38
|
+
Creates a new Myop component in the current directory.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
myop create
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**What it does:**
|
|
45
|
+
1. Prompts for component name (defaults to directory name)
|
|
46
|
+
2. Checks that `index.html` and `myop.config.json` don't already exist
|
|
47
|
+
3. Creates `myop.config.json` with component metadata
|
|
48
|
+
4. Creates `index.html` with a complete component template
|
|
49
|
+
5. Automatically starts the dev server
|
|
50
|
+
|
|
51
|
+
**Files created (single-file mode):**
|
|
52
|
+
```
|
|
53
|
+
./index.html # Complete Myop component
|
|
54
|
+
./myop.config.json # Component metadata
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Files created (full project scaffold, when no package.json exists):**
|
|
58
|
+
```
|
|
59
|
+
./index.html
|
|
60
|
+
./myop.config.json
|
|
61
|
+
./package.json
|
|
62
|
+
./build.js # esbuild bundler
|
|
63
|
+
./src/index.js
|
|
64
|
+
./src/modules/app.js
|
|
65
|
+
./src/modules/myop.js
|
|
66
|
+
./src/styles/index.css
|
|
67
|
+
./src/styles/main.css
|
|
68
|
+
./.gitignore
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Important:** If `index.html` or `myop.config.json` already exists, the command exits with an error. Use `myop dev` for existing components.
|
|
72
|
+
|
|
73
|
+
## Dev Command
|
|
74
|
+
|
|
75
|
+
Starts a development server with file watching.
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
myop dev
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Features:**
|
|
82
|
+
- Main server on **port 9292**
|
|
83
|
+
- Management dashboard on **port 9293**
|
|
84
|
+
- Watches `.js` and `.css` files for changes
|
|
85
|
+
- Auto-rebuilds when files change (if `build.js` or `package.json` build script exists)
|
|
86
|
+
- Hot Module Replacement for instant preview updates
|
|
87
|
+
- Single HTML file mode (no build step needed for `index.html`-only components)
|
|
88
|
+
|
|
89
|
+
**URLs during development:**
|
|
90
|
+
| URL | Purpose |
|
|
91
|
+
|-----|---------|
|
|
92
|
+
| `http://localhost:9292` | Dev server dashboard |
|
|
93
|
+
| `http://localhost:9292/view/<componentId>/` | Preview your component |
|
|
94
|
+
|
|
95
|
+
**Monorepo mode:**
|
|
96
|
+
```bash
|
|
97
|
+
myop dev -m
|
|
98
|
+
```
|
|
99
|
+
Scans for all `myop.config.json` files in nested directories and lets you select which components to run simultaneously.
|
|
100
|
+
|
|
101
|
+
### Build Triggering
|
|
102
|
+
|
|
103
|
+
The dev server detects changes and triggers builds automatically:
|
|
104
|
+
- If `build.js` exists: runs `node build.js`
|
|
105
|
+
- If `package.json` has a `build` script: runs `npm run build`
|
|
106
|
+
- If only `index.html` exists (single-file mode): serves directly, no build needed
|
|
107
|
+
|
|
108
|
+
## Push Command
|
|
109
|
+
|
|
110
|
+
Uploads the component directly to the Myop platform (no build step).
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
myop push
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
**What it does:**
|
|
117
|
+
1. Reads `myop.config.json`
|
|
118
|
+
2. Finds the HTML file to upload (`index.html` in root for single-file mode, or `dist/index.html`)
|
|
119
|
+
3. Authenticates (prompts for login if needed)
|
|
120
|
+
4. Uploads HTML to Myop via presigned URL
|
|
121
|
+
5. Confirms upload
|
|
122
|
+
6. Updates `myop.config.json` with `componentId` (first push only)
|
|
123
|
+
|
|
124
|
+
**After first push:**
|
|
125
|
+
- `myop.config.json` gets a real `componentId` (UUID)
|
|
126
|
+
- `organization` field is added
|
|
127
|
+
- Component appears on the dashboard
|
|
128
|
+
|
|
129
|
+
**Dashboard URL after push:**
|
|
130
|
+
```
|
|
131
|
+
https://dashboard.myop.dev/dashboard/2.0/component/<componentId>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Sync Command
|
|
135
|
+
|
|
136
|
+
Builds and uploads the component to the Myop platform (for multi-file projects with a build step).
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
myop sync
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**What it does:**
|
|
143
|
+
1. Reads `myop.config.json`
|
|
144
|
+
2. Runs `npm run build`
|
|
145
|
+
3. Verifies `dist/index.html` exists
|
|
146
|
+
4. Authenticates (prompts for login if needed)
|
|
147
|
+
5. Uploads `dist/index.html` to Myop via presigned URL
|
|
148
|
+
6. Confirms upload
|
|
149
|
+
7. Updates `myop.config.json` with `componentId` (first sync only)
|
|
150
|
+
|
|
151
|
+
## Authentication
|
|
152
|
+
|
|
153
|
+
### Login
|
|
154
|
+
```bash
|
|
155
|
+
myop login
|
|
156
|
+
```
|
|
157
|
+
Opens a browser window for OAuth authentication. Tokens are stored locally at `~/.myop/credentials.json`.
|
|
158
|
+
|
|
159
|
+
### Logout
|
|
160
|
+
```bash
|
|
161
|
+
myop logout
|
|
162
|
+
```
|
|
163
|
+
Clears stored credentials.
|
|
164
|
+
|
|
165
|
+
### Check Status
|
|
166
|
+
```bash
|
|
167
|
+
myop whoami
|
|
168
|
+
```
|
|
169
|
+
Displays the email of the currently authenticated user, or indicates if not logged in.
|
|
170
|
+
|
|
171
|
+
### Token Storage
|
|
172
|
+
- Location: `~/.myop/credentials.json`
|
|
173
|
+
- Permissions: `0o600` (owner read/write only)
|
|
174
|
+
- Tokens refresh automatically when expired
|
|
175
|
+
|
|
176
|
+
## Configuration
|
|
177
|
+
|
|
178
|
+
### myop.config.json
|
|
179
|
+
|
|
180
|
+
| Field | Type | Description |
|
|
181
|
+
|-------|------|-------------|
|
|
182
|
+
| `name` | `string` | Component display name |
|
|
183
|
+
| `componentId` | `string` | `"DEV"` initially, UUID after first sync |
|
|
184
|
+
| `type` | `string` | Always `"html"` |
|
|
185
|
+
| `author` | `string` | `"@myop-cli"` by default |
|
|
186
|
+
| `HMR` | `boolean` | Hot Module Replacement enabled |
|
|
187
|
+
| `organization` | `string` | Organization ID (added after sync) |
|
|
188
|
+
|
|
189
|
+
### Environment Variables
|
|
190
|
+
|
|
191
|
+
| Variable | Description |
|
|
192
|
+
|----------|-------------|
|
|
193
|
+
| `MYOP_MCP_URL` | MCP server URL (default: `https://mcp.myop.dev`) |
|
|
194
|
+
|
|
195
|
+
### CI/CD Mode
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
myop --ci
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Outputs JSON with version, config status, and auth status. No interactive prompts.
|
|
202
|
+
|
|
203
|
+
```json
|
|
204
|
+
{
|
|
205
|
+
"version": "0.1.40",
|
|
206
|
+
"config": {
|
|
207
|
+
"found": true,
|
|
208
|
+
"path": "./myop.config.json",
|
|
209
|
+
"name": "My Component",
|
|
210
|
+
"componentId": "abc-123",
|
|
211
|
+
"organization": "org-456"
|
|
212
|
+
},
|
|
213
|
+
"auth": {
|
|
214
|
+
"loggedIn": true,
|
|
215
|
+
"email": "user@example.com"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
```
|