@smartsoft001-mobilems/claude-plugins 2.66.0 → 2.68.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/package.json +1 -1
- package/plugins/flow/.claude-plugin/plugin.json +1 -15
- package/plugins/flow-legacy/.claude-plugin/README.md +143 -0
- package/plugins/flow-legacy/.claude-plugin/merge-permissions.js +80 -0
- package/plugins/flow-legacy/.claude-plugin/plugin.json +5 -0
- package/plugins/flow-legacy/.claude-plugin/settings.template.json +75 -0
- package/plugins/flow-legacy/agents/angular-component-scaffolder.md +323 -0
- package/plugins/flow-legacy/agents/angular-directive-builder.md +258 -0
- package/plugins/flow-legacy/agents/angular-guard-builder.md +322 -0
- package/plugins/flow-legacy/agents/angular-pipe-builder.md +227 -0
- package/plugins/flow-legacy/agents/angular-resolver-builder.md +332 -0
- package/plugins/flow-legacy/agents/angular-service-builder.md +271 -0
- package/plugins/flow-legacy/agents/angular-state-builder.md +473 -0
- package/plugins/flow-legacy/agents/shared-impl-orchestrator.md +161 -0
- package/plugins/flow-legacy/agents/shared-impl-reporter.md +204 -0
- package/plugins/flow-legacy/agents/shared-linear-subtask-iterator.md +187 -0
- package/plugins/flow-legacy/agents/shared-tdd-developer.md +304 -0
- package/plugins/flow-legacy/agents/shared-test-runner.md +131 -0
- package/plugins/flow-legacy/agents/shared-ui-classifier.md +137 -0
- package/plugins/flow-legacy/commands/commit.md +162 -0
- package/plugins/flow-legacy/commands/impl.md +495 -0
- package/plugins/flow-legacy/commands/plan.md +488 -0
- package/plugins/flow-legacy/commands/push.md +470 -0
- package/plugins/flow-legacy/skills/a11y-audit/SKILL.md +214 -0
- package/plugins/flow-legacy/skills/angular-patterns/SKILL.md +361 -0
- package/plugins/flow-legacy/skills/browser-capture/SKILL.md +238 -0
- package/plugins/flow-legacy/skills/debug-helper/SKILL.md +387 -0
- package/plugins/flow-legacy/skills/linear-suggestion/SKILL.md +132 -0
- package/plugins/flow-legacy/skills/maia-files-delete/SKILL.md +59 -0
- package/plugins/flow-legacy/skills/maia-files-upload/SKILL.md +57 -0
- package/plugins/flow-legacy/skills/nx-conventions/SKILL.md +371 -0
- package/plugins/flow-legacy/skills/test-unit/SKILL.md +494 -0
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: angular-patterns
|
|
3
|
+
description: Angular 14 legacy patterns including *ngIf/*ngFor directives, constructor DI, and @Input/@Output decorators
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Angular 14 Legacy Patterns
|
|
7
|
+
|
|
8
|
+
This skill provides guidance on Angular 14 patterns for legacy projects.
|
|
9
|
+
|
|
10
|
+
## CRITICAL: Use Legacy Patterns ONLY
|
|
11
|
+
|
|
12
|
+
**DO NOT use modern Angular features (Angular 16+):**
|
|
13
|
+
|
|
14
|
+
| Feature | CORRECT (Angular 14) | WRONG (Modern) |
|
|
15
|
+
|---------|---------------------|----------------|
|
|
16
|
+
| Control flow | `*ngIf`, `*ngFor`, `[ngSwitch]` | `@if`, `@for`, `@switch` |
|
|
17
|
+
| DI | Constructor injection | `inject()` |
|
|
18
|
+
| Inputs | `@Input()` decorator | `input()` function |
|
|
19
|
+
| Outputs | `@Output()` decorator | `output()` function |
|
|
20
|
+
| Two-way | `@Input()` + `@Output()` | `model()` |
|
|
21
|
+
| State | BehaviorSubject, Observable | signals, `signal()` |
|
|
22
|
+
| Derived | `pipe(map())` | `computed()` |
|
|
23
|
+
| Side effects | `subscribe()` | `effect()` |
|
|
24
|
+
| Components | NgModule-based | standalone |
|
|
25
|
+
|
|
26
|
+
## 1. Control Flow Syntax (`*ngIf`, `*ngFor`, `[ngSwitch]`)
|
|
27
|
+
|
|
28
|
+
### Conditional Rendering
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- *ngIf with else -->
|
|
32
|
+
<div *ngIf="isLoading; else loadedContent">
|
|
33
|
+
<app-spinner></app-spinner>
|
|
34
|
+
</div>
|
|
35
|
+
<ng-template #loadedContent>
|
|
36
|
+
<div *ngIf="error; else dataContent">
|
|
37
|
+
<app-error [message]="error?.message"></app-error>
|
|
38
|
+
</div>
|
|
39
|
+
</ng-template>
|
|
40
|
+
<ng-template #dataContent>
|
|
41
|
+
<ul>
|
|
42
|
+
<li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
|
|
43
|
+
</ul>
|
|
44
|
+
<p *ngIf="items.length === 0">No data</p>
|
|
45
|
+
</ng-template>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Iteration
|
|
49
|
+
|
|
50
|
+
```html
|
|
51
|
+
<!-- *ngFor with index and trackBy -->
|
|
52
|
+
<ul>
|
|
53
|
+
<li *ngFor="let item of items; let i = index; trackBy: trackById">
|
|
54
|
+
{{ i + 1 }}. {{ item.name }}
|
|
55
|
+
</li>
|
|
56
|
+
</ul>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
// trackBy function in component
|
|
61
|
+
trackById(index: number, item: Item): string {
|
|
62
|
+
return item.id;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### Switch Statement
|
|
67
|
+
|
|
68
|
+
```html
|
|
69
|
+
<div [ngSwitch]="status">
|
|
70
|
+
<span *ngSwitchCase="'pending'" class="text-yellow-500">Pending</span>
|
|
71
|
+
<span *ngSwitchCase="'active'" class="text-green-500">Active</span>
|
|
72
|
+
<span *ngSwitchDefault class="text-gray-500">Unknown</span>
|
|
73
|
+
</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## 2. BehaviorSubject for State Management
|
|
77
|
+
|
|
78
|
+
### When to Use
|
|
79
|
+
|
|
80
|
+
| Pattern | Use Case |
|
|
81
|
+
|---------|----------|
|
|
82
|
+
| `BehaviorSubject` | Local, mutable state with initial value |
|
|
83
|
+
| `ReplaySubject(1)` | State without initial value |
|
|
84
|
+
| `pipe(map())` | Derived/computed values |
|
|
85
|
+
| `combineLatest` | Combine multiple observables |
|
|
86
|
+
|
|
87
|
+
### BehaviorSubject Examples
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { BehaviorSubject } from 'rxjs';
|
|
91
|
+
import { map } from 'rxjs/operators';
|
|
92
|
+
|
|
93
|
+
@Injectable({
|
|
94
|
+
providedIn: 'root',
|
|
95
|
+
})
|
|
96
|
+
export class StateService {
|
|
97
|
+
// Private BehaviorSubject
|
|
98
|
+
private readonly _items = new BehaviorSubject<Item[]>([]);
|
|
99
|
+
private readonly _isLoading = new BehaviorSubject<boolean>(false);
|
|
100
|
+
private readonly _filter = new BehaviorSubject<string>('');
|
|
101
|
+
|
|
102
|
+
// Public Observable (read-only)
|
|
103
|
+
readonly items$ = this._items.asObservable();
|
|
104
|
+
readonly isLoading$ = this._isLoading.asObservable();
|
|
105
|
+
readonly filter$ = this._filter.asObservable();
|
|
106
|
+
|
|
107
|
+
// Derived state using pipe(map())
|
|
108
|
+
readonly filtered$ = combineLatest([this._items, this._filter]).pipe(
|
|
109
|
+
map(([items, filter]) => {
|
|
110
|
+
const f = filter.toLowerCase();
|
|
111
|
+
return items.filter(i => i.name.toLowerCase().includes(f));
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
readonly count$ = this._items.pipe(
|
|
116
|
+
map(items => items.length)
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// Methods to update state
|
|
120
|
+
setItems(items: Item[]): void {
|
|
121
|
+
this._items.next(items);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setLoading(loading: boolean): void {
|
|
125
|
+
this._isLoading.next(loading);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
setFilter(filter: string): void {
|
|
129
|
+
this._filter.next(filter);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get current value
|
|
133
|
+
get currentItems(): Item[] {
|
|
134
|
+
return this._items.getValue();
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 3. Dependency Injection via Constructor
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { Component, OnInit, OnDestroy } from '@angular/core';
|
|
143
|
+
import { Subject } from 'rxjs';
|
|
144
|
+
import { takeUntil } from 'rxjs/operators';
|
|
145
|
+
|
|
146
|
+
@Component({
|
|
147
|
+
selector: 'app-my-component',
|
|
148
|
+
templateUrl: './my-component.component.html',
|
|
149
|
+
styleUrls: ['./my-component.component.scss'],
|
|
150
|
+
})
|
|
151
|
+
export class MyComponent implements OnInit, OnDestroy {
|
|
152
|
+
items: Item[] = [];
|
|
153
|
+
|
|
154
|
+
private readonly destroy$ = new Subject<void>();
|
|
155
|
+
|
|
156
|
+
// Constructor injection (NOT inject())
|
|
157
|
+
constructor(
|
|
158
|
+
private readonly http: HttpClient,
|
|
159
|
+
private readonly router: Router,
|
|
160
|
+
private readonly myService: MyService,
|
|
161
|
+
@Optional() private readonly optionalService: OptionalService
|
|
162
|
+
) {}
|
|
163
|
+
|
|
164
|
+
ngOnInit(): void {
|
|
165
|
+
this.myService.items$
|
|
166
|
+
.pipe(takeUntil(this.destroy$))
|
|
167
|
+
.subscribe(items => {
|
|
168
|
+
this.items = items;
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
ngOnDestroy(): void {
|
|
173
|
+
this.destroy$.next();
|
|
174
|
+
this.destroy$.complete();
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Rules
|
|
180
|
+
|
|
181
|
+
- Place dependencies in constructor
|
|
182
|
+
- Use `private readonly` for services
|
|
183
|
+
- Use `@Optional()` for optional dependencies
|
|
184
|
+
- Always implement `OnDestroy` with `takeUntil` pattern
|
|
185
|
+
|
|
186
|
+
## 4. Input/Output APIs: `@Input()`, `@Output()`, `EventEmitter`
|
|
187
|
+
|
|
188
|
+
### Decorator Patterns
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import {
|
|
192
|
+
Component,
|
|
193
|
+
Input,
|
|
194
|
+
Output,
|
|
195
|
+
EventEmitter,
|
|
196
|
+
OnChanges,
|
|
197
|
+
SimpleChanges
|
|
198
|
+
} from '@angular/core';
|
|
199
|
+
|
|
200
|
+
@Component({
|
|
201
|
+
selector: 'app-form-field',
|
|
202
|
+
templateUrl: './form-field.component.html',
|
|
203
|
+
})
|
|
204
|
+
export class FormFieldComponent implements OnChanges {
|
|
205
|
+
// Required input (validate in ngOnChanges)
|
|
206
|
+
@Input() label!: string;
|
|
207
|
+
|
|
208
|
+
// Optional input with default
|
|
209
|
+
@Input() disabled = false;
|
|
210
|
+
|
|
211
|
+
// Input with setter for transformation
|
|
212
|
+
@Input()
|
|
213
|
+
set count(value: string | number) {
|
|
214
|
+
this._count = typeof value === 'string' ? parseInt(value, 10) : value;
|
|
215
|
+
}
|
|
216
|
+
get count(): number {
|
|
217
|
+
return this._count;
|
|
218
|
+
}
|
|
219
|
+
private _count = 0;
|
|
220
|
+
|
|
221
|
+
// Output event
|
|
222
|
+
@Output() changed = new EventEmitter<string>();
|
|
223
|
+
|
|
224
|
+
// Two-way binding (banana-in-a-box)
|
|
225
|
+
@Input() value = '';
|
|
226
|
+
@Output() valueChange = new EventEmitter<string>();
|
|
227
|
+
|
|
228
|
+
ngOnChanges(changes: SimpleChanges): void {
|
|
229
|
+
if (changes['label'] && !this.label) {
|
|
230
|
+
throw new Error('label is required');
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
onInputChange(newValue: string): void {
|
|
235
|
+
this.value = newValue;
|
|
236
|
+
this.valueChange.emit(newValue);
|
|
237
|
+
this.changed.emit(newValue);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Usage in Templates
|
|
243
|
+
|
|
244
|
+
```html
|
|
245
|
+
<!-- Parent template -->
|
|
246
|
+
<app-form-field
|
|
247
|
+
label="Name"
|
|
248
|
+
[disabled]="isDisabled"
|
|
249
|
+
[(value)]="name"
|
|
250
|
+
(changed)="onChanged($event)"
|
|
251
|
+
></app-form-field>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
## 5. NgModule-based Components
|
|
255
|
+
|
|
256
|
+
All components must be declared in a module:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// feature.module.ts
|
|
260
|
+
import { NgModule } from '@angular/core';
|
|
261
|
+
import { CommonModule } from '@angular/common';
|
|
262
|
+
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
|
263
|
+
|
|
264
|
+
import { FeatureComponent } from './feature.component';
|
|
265
|
+
import { FeatureListComponent } from './feature-list/feature-list.component';
|
|
266
|
+
import { FeatureItemComponent } from './feature-item/feature-item.component';
|
|
267
|
+
import { SharedModule } from '@shared/shared.module';
|
|
268
|
+
|
|
269
|
+
@NgModule({
|
|
270
|
+
declarations: [
|
|
271
|
+
FeatureComponent,
|
|
272
|
+
FeatureListComponent,
|
|
273
|
+
FeatureItemComponent,
|
|
274
|
+
],
|
|
275
|
+
imports: [
|
|
276
|
+
CommonModule,
|
|
277
|
+
FormsModule,
|
|
278
|
+
ReactiveFormsModule,
|
|
279
|
+
SharedModule,
|
|
280
|
+
],
|
|
281
|
+
exports: [
|
|
282
|
+
FeatureComponent,
|
|
283
|
+
],
|
|
284
|
+
})
|
|
285
|
+
export class FeatureModule {}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
```typescript
|
|
289
|
+
// feature.component.ts
|
|
290
|
+
@Component({
|
|
291
|
+
selector: 'app-feature',
|
|
292
|
+
templateUrl: './feature.component.html',
|
|
293
|
+
styleUrls: ['./feature.component.scss'],
|
|
294
|
+
})
|
|
295
|
+
export class FeatureComponent implements OnInit, OnDestroy {
|
|
296
|
+
// ... component logic
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## 6. Async Pipe Usage
|
|
301
|
+
|
|
302
|
+
Always prefer `async` pipe over manual subscriptions in templates:
|
|
303
|
+
|
|
304
|
+
```html
|
|
305
|
+
<!-- Using async pipe -->
|
|
306
|
+
<div *ngIf="isLoading$ | async; else content">
|
|
307
|
+
<app-spinner></app-spinner>
|
|
308
|
+
</div>
|
|
309
|
+
<ng-template #content>
|
|
310
|
+
<ul>
|
|
311
|
+
<li *ngFor="let item of items$ | async; trackBy: trackById">
|
|
312
|
+
{{ item.name }}
|
|
313
|
+
</li>
|
|
314
|
+
</ul>
|
|
315
|
+
</ng-template>
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
```typescript
|
|
319
|
+
@Component({...})
|
|
320
|
+
export class ListComponent {
|
|
321
|
+
readonly isLoading$ = this.stateService.isLoading$;
|
|
322
|
+
readonly items$ = this.stateService.items$;
|
|
323
|
+
|
|
324
|
+
constructor(private readonly stateService: StateService) {}
|
|
325
|
+
|
|
326
|
+
trackById(index: number, item: Item): string {
|
|
327
|
+
return item.id;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
## 7. Performance Guidelines
|
|
333
|
+
|
|
334
|
+
- Every `*ngFor` must have a `trackBy` function
|
|
335
|
+
- Extract complex logic from templates to component methods
|
|
336
|
+
- Use `OnPush` change detection when appropriate
|
|
337
|
+
- Avoid function calls in templates (use getters or properties)
|
|
338
|
+
|
|
339
|
+
```typescript
|
|
340
|
+
@Component({
|
|
341
|
+
selector: 'app-optimized',
|
|
342
|
+
templateUrl: './optimized.component.html',
|
|
343
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
344
|
+
})
|
|
345
|
+
export class OptimizedComponent {
|
|
346
|
+
// ...
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
## 8. PR Checklist (Angular 14 Legacy)
|
|
351
|
+
|
|
352
|
+
- [ ] Using `*ngIf`/`*ngFor` (NOT `@if`/`@for`)
|
|
353
|
+
- [ ] Using `[ngSwitch]` (NOT `@switch`)
|
|
354
|
+
- [ ] Every `*ngFor` has `trackBy` function
|
|
355
|
+
- [ ] Using constructor DI (NOT `inject()`)
|
|
356
|
+
- [ ] Using `@Input()`/`@Output()` decorators (NOT `input()`/`output()`)
|
|
357
|
+
- [ ] Using BehaviorSubject for state (NOT signals)
|
|
358
|
+
- [ ] Using `pipe(map())` for derived state (NOT `computed()`)
|
|
359
|
+
- [ ] Component declared in NgModule (NOT standalone)
|
|
360
|
+
- [ ] Using `async` pipe in templates
|
|
361
|
+
- [ ] Implementing `OnDestroy` with `takeUntil` pattern
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: browser-capture
|
|
3
|
+
description: Manage browser sessions and capture screenshots. Ensures dev server is running, navigates to pages, captures responsive screenshots, and uploads them to temporary storage.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Bash
|
|
6
|
+
- Read
|
|
7
|
+
- Glob
|
|
8
|
+
- mcp__playwright__browser_navigate
|
|
9
|
+
- mcp__playwright__browser_snapshot
|
|
10
|
+
- mcp__playwright__browser_take_screenshot
|
|
11
|
+
- mcp__playwright__browser_resize
|
|
12
|
+
- mcp__playwright__browser_close
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Browser Capture Skill
|
|
16
|
+
|
|
17
|
+
Manage browser sessions for capturing screenshots of web pages. Automatically ensures the development server is running before capturing.
|
|
18
|
+
|
|
19
|
+
## Capabilities
|
|
20
|
+
|
|
21
|
+
1. **Server Management**: Check if dev server is running, start if needed
|
|
22
|
+
2. **Page Navigation**: Navigate to specified URLs using Playwright
|
|
23
|
+
3. **Screenshot Capture**: Take screenshots at multiple viewport sizes
|
|
24
|
+
4. **Upload Screenshots**: Upload captured images to maia-api for use in reports
|
|
25
|
+
|
|
26
|
+
## Server Management
|
|
27
|
+
|
|
28
|
+
### Read Port Configuration
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Default port location (Nx 14 / Angular 14)
|
|
32
|
+
grep -o '"port":[^,}]*' apps/web/project.json | head -1
|
|
33
|
+
# Default: 4200
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Check if Server is Running
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
lsof -i :4200 -t
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
- If returns PID -> server is running
|
|
43
|
+
- If returns nothing -> server needs to be started
|
|
44
|
+
|
|
45
|
+
### Start Server if Needed
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Start in background (Nx 14 / @nrwl)
|
|
49
|
+
npm start &
|
|
50
|
+
|
|
51
|
+
# Wait for server to be ready (check every 2 seconds, max 30 seconds)
|
|
52
|
+
for i in {1..15}; do
|
|
53
|
+
curl -s http://localhost:4200 > /dev/null && break
|
|
54
|
+
sleep 2
|
|
55
|
+
done
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Screenshot Capture Process
|
|
59
|
+
|
|
60
|
+
### 1. Ensure Server is Running
|
|
61
|
+
|
|
62
|
+
Before any screenshot capture:
|
|
63
|
+
|
|
64
|
+
1. Read port from `apps/web/project.json` (default: 4200)
|
|
65
|
+
2. Check if port is occupied: `lsof -i :PORT -t`
|
|
66
|
+
3. If not running: `npm start &` and wait ~15 seconds
|
|
67
|
+
|
|
68
|
+
### 2. Navigate to Page
|
|
69
|
+
|
|
70
|
+
Use `mcp__playwright__browser_navigate` to open the target URL.
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
URL format: http://localhost:{port}/{path}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### 3. Capture at Multiple Viewports
|
|
77
|
+
|
|
78
|
+
Standard viewport sizes:
|
|
79
|
+
|
|
80
|
+
| Name | Width | Height |
|
|
81
|
+
| ------- | ----- | ------ |
|
|
82
|
+
| Desktop | 1920 | 1080 |
|
|
83
|
+
| Tablet | 768 | 1024 |
|
|
84
|
+
| Mobile | 375 | 667 |
|
|
85
|
+
|
|
86
|
+
Process for each viewport:
|
|
87
|
+
|
|
88
|
+
1. `mcp__playwright__browser_resize` -> set dimensions
|
|
89
|
+
2. `mcp__playwright__browser_take_screenshot` -> save to temp file
|
|
90
|
+
|
|
91
|
+
### 4. Upload Screenshots
|
|
92
|
+
|
|
93
|
+
After capturing, delegate upload to `maia-api` agent:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
Input: /tmp/screenshot-desktop.png
|
|
97
|
+
Output: { id: "abc123", url: "https://maia-ai-api.smartsoft.biz.pl/files/abc123" }
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Usage Examples
|
|
101
|
+
|
|
102
|
+
### Capture Single Page
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
Input: { path: "/articles", viewports: ["desktop", "mobile"] }
|
|
106
|
+
|
|
107
|
+
Process:
|
|
108
|
+
1. Check server running on port 4200
|
|
109
|
+
2. Start if needed, wait for ready
|
|
110
|
+
3. Navigate to http://localhost:4200/articles
|
|
111
|
+
4. Resize to 1920x1080, screenshot -> /tmp/articles-desktop.png
|
|
112
|
+
5. Resize to 375x667, screenshot -> /tmp/articles-mobile.png
|
|
113
|
+
6. Upload both via maia-api agent
|
|
114
|
+
7. Return URLs
|
|
115
|
+
|
|
116
|
+
Output:
|
|
117
|
+
{
|
|
118
|
+
"desktop": "https://maia-ai-api.smartsoft.biz.pl/files/abc123",
|
|
119
|
+
"mobile": "https://maia-ai-api.smartsoft.biz.pl/files/def456"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Capture Before/After
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
Input: {
|
|
127
|
+
path: "/profile",
|
|
128
|
+
phase: "before" | "after",
|
|
129
|
+
viewports: ["desktop", "mobile"]
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
Output:
|
|
133
|
+
{
|
|
134
|
+
"phase": "before",
|
|
135
|
+
"desktop": { "id": "abc123", "url": "https://..." },
|
|
136
|
+
"mobile": { "id": "def456", "url": "https://..." }
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Viewport Presets
|
|
141
|
+
|
|
142
|
+
| Preset | Dimensions | Use Case |
|
|
143
|
+
| ---------- | ---------- | ------------------------ |
|
|
144
|
+
| `desktop` | 1920x1080 | Full desktop view |
|
|
145
|
+
| `laptop` | 1366x768 | Common laptop resolution |
|
|
146
|
+
| `tablet` | 768x1024 | iPad portrait |
|
|
147
|
+
| `mobile` | 375x667 | iPhone SE/8 |
|
|
148
|
+
| `mobile-l` | 414x896 | iPhone XR/11 |
|
|
149
|
+
|
|
150
|
+
## Server Ready Check
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Check if server responds
|
|
154
|
+
check_server() {
|
|
155
|
+
local port=$1
|
|
156
|
+
local max_attempts=$2
|
|
157
|
+
local attempt=1
|
|
158
|
+
|
|
159
|
+
while [ $attempt -le $max_attempts ]; do
|
|
160
|
+
if curl -s "http://localhost:$port" > /dev/null 2>&1; then
|
|
161
|
+
echo "Server ready"
|
|
162
|
+
return 0
|
|
163
|
+
fi
|
|
164
|
+
echo "Waiting for server... ($attempt/$max_attempts)"
|
|
165
|
+
sleep 2
|
|
166
|
+
((attempt++))
|
|
167
|
+
done
|
|
168
|
+
|
|
169
|
+
echo "Server not ready after $max_attempts attempts"
|
|
170
|
+
return 1
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
check_server 4200 15
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Output Format
|
|
177
|
+
|
|
178
|
+
When capturing screenshots, return structured data:
|
|
179
|
+
|
|
180
|
+
```json
|
|
181
|
+
{
|
|
182
|
+
"serverStarted": true,
|
|
183
|
+
"port": 4200,
|
|
184
|
+
"screenshots": [
|
|
185
|
+
{
|
|
186
|
+
"viewport": "desktop",
|
|
187
|
+
"dimensions": "1920x1080",
|
|
188
|
+
"localPath": "/tmp/screenshot-desktop.png",
|
|
189
|
+
"uploadedId": "abc123",
|
|
190
|
+
"uploadedUrl": "https://maia-ai-api.smartsoft.biz.pl/files/abc123"
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
"viewport": "mobile",
|
|
194
|
+
"dimensions": "375x667",
|
|
195
|
+
"localPath": "/tmp/screenshot-mobile.png",
|
|
196
|
+
"uploadedId": "def456",
|
|
197
|
+
"uploadedUrl": "https://maia-ai-api.smartsoft.biz.pl/files/def456"
|
|
198
|
+
}
|
|
199
|
+
]
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
## Cleanup
|
|
204
|
+
|
|
205
|
+
After screenshots are used (embedded in Linear comments), cleanup local temp files:
|
|
206
|
+
|
|
207
|
+
```bash
|
|
208
|
+
rm /tmp/screenshot-*.png
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For API cleanup, delegate to `maia-api` agent with collected IDs.
|
|
212
|
+
|
|
213
|
+
## Error Handling
|
|
214
|
+
|
|
215
|
+
### Server Won't Start
|
|
216
|
+
|
|
217
|
+
If server fails to start after 30 seconds:
|
|
218
|
+
|
|
219
|
+
1. Check for port conflicts: `lsof -i :4200`
|
|
220
|
+
2. Check npm logs for errors
|
|
221
|
+
3. Report error and skip screenshot capture
|
|
222
|
+
|
|
223
|
+
### Screenshot Fails
|
|
224
|
+
|
|
225
|
+
If Playwright screenshot fails:
|
|
226
|
+
|
|
227
|
+
1. Verify navigation succeeded
|
|
228
|
+
2. Check for JavaScript errors: `mcp__playwright__browser_console_messages`
|
|
229
|
+
3. Try alternative viewport
|
|
230
|
+
4. Report partial success
|
|
231
|
+
|
|
232
|
+
### Upload Fails
|
|
233
|
+
|
|
234
|
+
If maia-api upload fails:
|
|
235
|
+
|
|
236
|
+
1. Verify file exists and has content
|
|
237
|
+
2. Retry upload once
|
|
238
|
+
3. Report error with local file path as fallback
|