@vizzly-testing/cli 0.23.0 → 0.23.2
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/README.md +54 -586
- package/dist/api/client.js +3 -1
- package/dist/api/endpoints.js +6 -7
- package/dist/cli.js +15 -2
- package/dist/commands/finalize.js +12 -0
- package/dist/commands/preview.js +210 -28
- package/dist/commands/run.js +15 -0
- package/dist/commands/status.js +34 -8
- package/dist/commands/upload.js +13 -0
- package/dist/reporter/reporter-bundle.iife.js +19 -19
- package/dist/utils/ci-env.js +114 -16
- package/package.json +1 -2
- package/claude-plugin/.claude-plugin/README.md +0 -270
- package/claude-plugin/.claude-plugin/marketplace.json +0 -28
- package/claude-plugin/.claude-plugin/plugin.json +0 -14
- package/claude-plugin/.mcp.json +0 -12
- package/claude-plugin/CHANGELOG.md +0 -85
- package/claude-plugin/commands/setup.md +0 -137
- package/claude-plugin/commands/suggest-screenshots.md +0 -111
- package/claude-plugin/mcp/vizzly-docs-server/README.md +0 -95
- package/claude-plugin/mcp/vizzly-docs-server/docs-fetcher.js +0 -110
- package/claude-plugin/mcp/vizzly-docs-server/index.js +0 -283
- package/claude-plugin/mcp/vizzly-server/cloud-api-provider.js +0 -399
- package/claude-plugin/mcp/vizzly-server/index.js +0 -927
- package/claude-plugin/mcp/vizzly-server/local-tdd-provider.js +0 -455
- package/claude-plugin/mcp/vizzly-server/token-resolver.js +0 -185
- package/claude-plugin/skills/check-visual-tests/SKILL.md +0 -158
- package/claude-plugin/skills/debug-visual-regression/SKILL.md +0 -269
- package/docs/api-reference.md +0 -1003
- package/docs/authentication.md +0 -334
- package/docs/doctor-command.md +0 -44
- package/docs/getting-started.md +0 -131
- package/docs/internal/SDK-API.md +0 -1018
- package/docs/plugins.md +0 -557
- package/docs/tdd-mode.md +0 -594
- package/docs/test-integration.md +0 -523
- package/docs/tui-elements.md +0 -560
- package/docs/upload-command.md +0 -196
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Initialize Vizzly visual testing in a project
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Setup Vizzly in Project
|
|
6
|
-
|
|
7
|
-
Help the user set up Vizzly visual regression testing in their project.
|
|
8
|
-
|
|
9
|
-
## Setup Steps
|
|
10
|
-
|
|
11
|
-
**Execute steps 1-5 to complete the CLI setup, then proceed to step 6 for SDK recommendations.**
|
|
12
|
-
|
|
13
|
-
1. **Check if Vizzly is already configured**
|
|
14
|
-
- Look for `vizzly.config.js` in the project root
|
|
15
|
-
- Check if `@vizzly-testing/cli` is in package.json
|
|
16
|
-
- If already configured, inform the user and exit
|
|
17
|
-
|
|
18
|
-
2. **Install Vizzly CLI**
|
|
19
|
-
|
|
20
|
-
Run this command:
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
npm install --save-dev @vizzly-testing/cli
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
3. **Initialize configuration**
|
|
27
|
-
|
|
28
|
-
Run this command:
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
npx vizzly init
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
This creates `vizzly.config.js` with sensible defaults.
|
|
35
|
-
|
|
36
|
-
4. **Add .vizzly/ to .gitignore**
|
|
37
|
-
|
|
38
|
-
Add `.vizzly/` to the project's `.gitignore` file to avoid committing local TDD artifacts.
|
|
39
|
-
|
|
40
|
-
5. **Environment Variables**
|
|
41
|
-
|
|
42
|
-
Present the user with instructions to set up their API token:
|
|
43
|
-
|
|
44
|
-
**For local development:**
|
|
45
|
-
Create a `.env` file:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
VIZZLY_TOKEN=your-api-token-here
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
Add `.env` to `.gitignore`
|
|
52
|
-
|
|
53
|
-
**For CI/CD:**
|
|
54
|
-
Add `VIZZLY_TOKEN` as a secret in their CI provider
|
|
55
|
-
|
|
56
|
-
6. **Next Steps**
|
|
57
|
-
|
|
58
|
-
After CLI setup is complete, detect the project type and recommend the appropriate SDK:
|
|
59
|
-
|
|
60
|
-
**SDK Detection Priority (check in this order):**
|
|
61
|
-
- **Ruby**: Check for `Gemfile` → Recommend Ruby SDK
|
|
62
|
-
- **Storybook**: Check for `@storybook/*` in package.json or `.storybook/` directory → Recommend Storybook SDK
|
|
63
|
-
- **Static Site**: Check for static site generators (`astro`, `next`, `gatsby`, `vitepress`, `eleventy` in package.json) or build directories (`dist/`, `build/`, `.next/out/`, `_site/`) → Recommend Static Site SDK
|
|
64
|
-
- **JavaScript/Node.js**: Check for `package.json` → Recommend JavaScript SDK
|
|
65
|
-
|
|
66
|
-
**Present the detected SDK recommendation:**
|
|
67
|
-
|
|
68
|
-
For Ruby:
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
📦 Next: Install the Ruby SDK
|
|
72
|
-
gem install vizzly
|
|
73
|
-
|
|
74
|
-
Documentation: https://docs.vizzly.dev/integration/sdk/ruby/overview
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
For Storybook:
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
📦 Next: Install the Storybook SDK
|
|
81
|
-
npm install --save-dev @vizzly-testing/storybook
|
|
82
|
-
|
|
83
|
-
Documentation: https://docs.vizzly.dev/integration/sdk/storybook/overview
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
For Static Site:
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
📦 Next: Install the Static Site SDK
|
|
90
|
-
npm install --save-dev @vizzly-testing/static-site
|
|
91
|
-
|
|
92
|
-
Documentation: https://docs.vizzly.dev/integration/sdk/static-site/overview
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
For JavaScript:
|
|
96
|
-
|
|
97
|
-
```
|
|
98
|
-
📦 Next: Use the JavaScript SDK (already included with the CLI)
|
|
99
|
-
|
|
100
|
-
Documentation: https://docs.vizzly.dev/integration/sdk/javascript/overview
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
**End with this context summary:**
|
|
104
|
-
|
|
105
|
-
```
|
|
106
|
-
✅ Setup Complete
|
|
107
|
-
|
|
108
|
-
What was accomplished:
|
|
109
|
-
- Vizzly CLI installed
|
|
110
|
-
- Configuration file created (vizzly.config.js)
|
|
111
|
-
- .vizzly/ added to .gitignore
|
|
112
|
-
- Environment variable instructions provided
|
|
113
|
-
|
|
114
|
-
What's next:
|
|
115
|
-
- Install the recommended SDK (see above)
|
|
116
|
-
- Add screenshot capture to your tests (if needed)
|
|
117
|
-
- Run `vizzly tdd start` to test locally
|
|
118
|
-
|
|
119
|
-
[For Claude: The CLI is now installed and configured. When the user returns saying they installed the SDK, help them integrate it into their tests and start capturing screenshots. The workflow is: install SDK → add screenshot calls to tests → run vizzly tdd to verify locally.]
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## What NOT to Do
|
|
123
|
-
|
|
124
|
-
- Do not modify their test files
|
|
125
|
-
- Do not generate example test code
|
|
126
|
-
- Do not make assumptions about their test framework
|
|
127
|
-
- Do not create new test files
|
|
128
|
-
|
|
129
|
-
## What to Provide
|
|
130
|
-
|
|
131
|
-
- Installation commands
|
|
132
|
-
- Configuration file creation
|
|
133
|
-
- Environment setup guidance
|
|
134
|
-
- Links to documentation
|
|
135
|
-
- Next steps for integration
|
|
136
|
-
|
|
137
|
-
Let the user integrate Vizzly into their existing tests themselves. Vizzly works with any test framework that can capture screenshots.
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
description: Analyze test files and suggest where visual screenshots would be valuable
|
|
3
|
-
---
|
|
4
|
-
|
|
5
|
-
# Suggest Screenshot Opportunities
|
|
6
|
-
|
|
7
|
-
Analyze existing test files to identify ideal locations for visual regression testing.
|
|
8
|
-
|
|
9
|
-
## Process
|
|
10
|
-
|
|
11
|
-
1. **Detect SDK type** (same logic as setup command)
|
|
12
|
-
|
|
13
|
-
**If Storybook detected** (`@storybook/*` in package.json or `.storybook/` directory):
|
|
14
|
-
- Inform user that Storybook SDK auto-discovers all stories
|
|
15
|
-
- No manual screenshot calls needed
|
|
16
|
-
- Exit early
|
|
17
|
-
|
|
18
|
-
**If Static Site detected** (build directories like `dist/`, `build/`, `.next/out/` or static site generators):
|
|
19
|
-
- Inform user that Static Site SDK auto-discovers all pages
|
|
20
|
-
- No manual screenshot calls needed
|
|
21
|
-
- Exit early
|
|
22
|
-
|
|
23
|
-
**Otherwise continue** with test file analysis below.
|
|
24
|
-
|
|
25
|
-
2. **Ask user for test directory** if not obvious (e.g., `tests/`, `e2e/`, `__tests__/`, `spec/`)
|
|
26
|
-
|
|
27
|
-
3. **Find test files** using glob patterns:
|
|
28
|
-
- `**/*.test.{js,ts,jsx,tsx}`
|
|
29
|
-
- `**/*.spec.{js,ts,jsx,tsx}`
|
|
30
|
-
- `**/e2e/**/*.{js,ts}`
|
|
31
|
-
|
|
32
|
-
**IMPORTANT: Exclude these directories:**
|
|
33
|
-
- `node_modules/`
|
|
34
|
-
- `dist/`, `build/`, `out/`
|
|
35
|
-
- `.next/`, `.nuxt/`, `.vite/`
|
|
36
|
-
- `coverage/`, `.nyc_output/`
|
|
37
|
-
- `vendor/`
|
|
38
|
-
- Any hidden directories (`.*/`)
|
|
39
|
-
|
|
40
|
-
Use the Glob tool with explicit exclusion or filter results to avoid these directories.
|
|
41
|
-
|
|
42
|
-
4. **Analyze test files** looking for:
|
|
43
|
-
- Page navigations (`.goto()`, `.visit()`, `navigate()`)
|
|
44
|
-
- User interactions before assertions (`.click()`, `.type()`, `.fill()`)
|
|
45
|
-
- Component rendering (React Testing Library, etc.)
|
|
46
|
-
- Visual assertions or wait conditions
|
|
47
|
-
|
|
48
|
-
5. **Suggest screenshot points** where:
|
|
49
|
-
- After page loads or navigations
|
|
50
|
-
- After key user interactions
|
|
51
|
-
- Before visual assertions
|
|
52
|
-
- At different viewport sizes
|
|
53
|
-
- For different user states (logged in/out, etc.)
|
|
54
|
-
|
|
55
|
-
6. **Provide code examples** specific to their test framework:
|
|
56
|
-
|
|
57
|
-
**Playwright:**
|
|
58
|
-
|
|
59
|
-
```javascript
|
|
60
|
-
import { vizzlyScreenshot } from '@vizzly-testing/cli/client';
|
|
61
|
-
|
|
62
|
-
// After page navigation
|
|
63
|
-
await page.goto('/dashboard');
|
|
64
|
-
await vizzlyScreenshot('dashboard-logged-in', await page.screenshot(), {
|
|
65
|
-
browser: 'chrome',
|
|
66
|
-
viewport: '1920x1080'
|
|
67
|
-
});
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
**Cypress:**
|
|
71
|
-
|
|
72
|
-
```javascript
|
|
73
|
-
cy.visit('/login');
|
|
74
|
-
cy.screenshot('login-page');
|
|
75
|
-
// Then add vizzlyScreenshot in custom command
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
## Output Format
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
Found 8 potential screenshot opportunities in your tests:
|
|
82
|
-
|
|
83
|
-
tests/e2e/auth.spec.js:
|
|
84
|
-
Line 15: After login page navigation
|
|
85
|
-
Suggested screenshot: 'login-page'
|
|
86
|
-
Reason: Page load after navigation
|
|
87
|
-
|
|
88
|
-
Line 28: After successful login
|
|
89
|
-
Suggested screenshot: 'dashboard-authenticated'
|
|
90
|
-
Reason: User state change (logged in)
|
|
91
|
-
|
|
92
|
-
tests/e2e/checkout.spec.js:
|
|
93
|
-
Line 42: Shopping cart with items
|
|
94
|
-
Suggested screenshot: 'cart-with-items'
|
|
95
|
-
Reason: Visual state after user interaction
|
|
96
|
-
|
|
97
|
-
Line 67: Checkout confirmation page
|
|
98
|
-
Suggested screenshot: 'order-confirmation'
|
|
99
|
-
Reason: Final state of user flow
|
|
100
|
-
|
|
101
|
-
Example integration for Playwright:
|
|
102
|
-
[Provide code example specific to their test]
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Important Notes
|
|
106
|
-
|
|
107
|
-
- **Do NOT modify test files**
|
|
108
|
-
- **Do NOT create new test files**
|
|
109
|
-
- Only provide suggestions and examples
|
|
110
|
-
- Let the user decide where to add screenshots
|
|
111
|
-
- Respect their test framework and structure
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
# Vizzly Docs MCP Server
|
|
2
|
-
|
|
3
|
-
MCP server that provides Claude Code with easy access to Vizzly documentation.
|
|
4
|
-
|
|
5
|
-
## How It Works
|
|
6
|
-
|
|
7
|
-
This server fetches documentation from the deployed `docs.vizzly.dev` site, making it easy for LLMs to:
|
|
8
|
-
- Browse all available docs
|
|
9
|
-
- Search documentation by keyword
|
|
10
|
-
- Retrieve full markdown content
|
|
11
|
-
- Understand the documentation structure
|
|
12
|
-
|
|
13
|
-
## Tools
|
|
14
|
-
|
|
15
|
-
### `list_docs`
|
|
16
|
-
List all available documentation pages with optional category filtering.
|
|
17
|
-
|
|
18
|
-
**Arguments:**
|
|
19
|
-
- `category` (optional): Filter by category (e.g., "Integration > CLI", "Features")
|
|
20
|
-
|
|
21
|
-
**Example:**
|
|
22
|
-
```javascript
|
|
23
|
-
list_docs({ category: "CLI" })
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### `get_doc`
|
|
27
|
-
Get the full markdown content of a specific documentation page.
|
|
28
|
-
|
|
29
|
-
**Arguments:**
|
|
30
|
-
- `path` (required): The document path (e.g., "integration/cli/overview.mdx") or slug (e.g., "integration/cli/overview")
|
|
31
|
-
|
|
32
|
-
**Example:**
|
|
33
|
-
```javascript
|
|
34
|
-
get_doc({ path: "integration/cli/tdd-mode" })
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### `search_docs`
|
|
38
|
-
Search documentation by keyword. Searches in titles, descriptions, and categories.
|
|
39
|
-
|
|
40
|
-
**Arguments:**
|
|
41
|
-
- `query` (required): Search query
|
|
42
|
-
- `limit` (optional): Maximum number of results (default: 10)
|
|
43
|
-
|
|
44
|
-
**Example:**
|
|
45
|
-
```javascript
|
|
46
|
-
search_docs({ query: "parallel builds", limit: 5 })
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### `get_sidebar`
|
|
50
|
-
Get the complete sidebar navigation structure.
|
|
51
|
-
|
|
52
|
-
**Example:**
|
|
53
|
-
```javascript
|
|
54
|
-
get_sidebar()
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
## Architecture
|
|
58
|
-
|
|
59
|
-
The server works in a hybrid model:
|
|
60
|
-
1. **Index** - Fetches a pre-built JSON index from `docs.vizzly.dev/api/mcp-index.json`
|
|
61
|
-
2. **Content** - Fetches individual doc content on-demand from `docs.vizzly.dev/api/content/{path}`
|
|
62
|
-
3. **Caching** - Index is cached for 5 minutes to minimize network requests
|
|
63
|
-
|
|
64
|
-
This approach ensures:
|
|
65
|
-
- Fast browsing/searching (uses cached index)
|
|
66
|
-
- Always up-to-date content (fetched from live site)
|
|
67
|
-
- Minimal network overhead
|
|
68
|
-
- No local file dependencies
|
|
69
|
-
|
|
70
|
-
## Data Flow
|
|
71
|
-
|
|
72
|
-
```
|
|
73
|
-
Claude Code
|
|
74
|
-
↓
|
|
75
|
-
MCP Server (this)
|
|
76
|
-
↓
|
|
77
|
-
docs.vizzly.dev API
|
|
78
|
-
↓
|
|
79
|
-
Statically generated at build time (Astro)
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Development
|
|
83
|
-
|
|
84
|
-
To test the server locally:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
# The server is automatically loaded by Claude Code when the plugin is active
|
|
88
|
-
# Check .mcp.json for configuration
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Files
|
|
92
|
-
|
|
93
|
-
- `index.js` - Main MCP server implementation
|
|
94
|
-
- `docs-fetcher.js` - Utilities for fetching and searching docs
|
|
95
|
-
- `README.md` - This file
|
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fetches documentation from docs.vizzly.dev
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
let BASE_URL = 'https://docs.vizzly.dev';
|
|
6
|
-
let INDEX_URL = `${BASE_URL}/api/mcp-index.json`;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Fetch the documentation index
|
|
10
|
-
*/
|
|
11
|
-
export async function fetchDocsIndex() {
|
|
12
|
-
try {
|
|
13
|
-
let response = await fetch(INDEX_URL);
|
|
14
|
-
|
|
15
|
-
if (!response.ok) {
|
|
16
|
-
throw new Error(`Failed to fetch docs index: ${response.status} ${response.statusText}`);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
let index = await response.json();
|
|
20
|
-
return index;
|
|
21
|
-
} catch (error) {
|
|
22
|
-
throw new Error(`Failed to load documentation index: ${error.message}`);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Fetch the content of a specific document
|
|
28
|
-
*/
|
|
29
|
-
export async function fetchDocContent(path) {
|
|
30
|
-
// Normalize path (remove leading slash, ensure it doesn't end with .md/.mdx)
|
|
31
|
-
let cleanPath = path.replace(/^\//, '').replace(/\.(mdx?|md)$/, '');
|
|
32
|
-
|
|
33
|
-
let contentUrl = `${BASE_URL}/api/content/${cleanPath}`;
|
|
34
|
-
|
|
35
|
-
try {
|
|
36
|
-
let response = await fetch(contentUrl);
|
|
37
|
-
|
|
38
|
-
if (!response.ok) {
|
|
39
|
-
// Try with .mdx extension if not found
|
|
40
|
-
if (response.status === 404 && !path.endsWith('.mdx')) {
|
|
41
|
-
contentUrl = `${BASE_URL}/api/content/${cleanPath}.mdx`;
|
|
42
|
-
response = await fetch(contentUrl);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!response.ok) {
|
|
46
|
-
throw new Error(`Document not found: ${path} (${response.status})`);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
let content = await response.text();
|
|
51
|
-
return content;
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw new Error(`Failed to fetch document content: ${error.message}`);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Search documents by query
|
|
59
|
-
* Simple client-side search through titles and descriptions
|
|
60
|
-
*/
|
|
61
|
-
export function searchDocs(docs, query, limit = 10) {
|
|
62
|
-
let lowerQuery = query.toLowerCase();
|
|
63
|
-
let terms = lowerQuery.split(/\s+/);
|
|
64
|
-
|
|
65
|
-
let results = docs
|
|
66
|
-
.map(doc => {
|
|
67
|
-
let titleLower = doc.title.toLowerCase();
|
|
68
|
-
let descLower = (doc.description || '').toLowerCase();
|
|
69
|
-
let categoryLower = doc.category.toLowerCase();
|
|
70
|
-
|
|
71
|
-
let score = 0;
|
|
72
|
-
|
|
73
|
-
// Exact phrase match in title (highest score)
|
|
74
|
-
if (titleLower.includes(lowerQuery)) {
|
|
75
|
-
score += 10;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Exact phrase match in description
|
|
79
|
-
if (descLower.includes(lowerQuery)) {
|
|
80
|
-
score += 5;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Exact phrase match in category
|
|
84
|
-
if (categoryLower.includes(lowerQuery)) {
|
|
85
|
-
score += 3;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// Individual term matches
|
|
89
|
-
for (let term of terms) {
|
|
90
|
-
if (titleLower.includes(term)) score += 3;
|
|
91
|
-
if (descLower.includes(term)) score += 1;
|
|
92
|
-
if (categoryLower.includes(term)) score += 0.5;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return { doc, score };
|
|
96
|
-
})
|
|
97
|
-
.filter(result => result.score > 0)
|
|
98
|
-
.sort((a, b) => b.score - a.score)
|
|
99
|
-
.slice(0, limit);
|
|
100
|
-
|
|
101
|
-
// Normalize scores to 0-1 range
|
|
102
|
-
if (results.length > 0) {
|
|
103
|
-
let maxScore = results[0].score;
|
|
104
|
-
for (let result of results) {
|
|
105
|
-
result.score = result.score / maxScore;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return results;
|
|
110
|
-
}
|