wmcp-annotate 1.0.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.

Potentially problematic release.


This version of wmcp-annotate might be problematic. Click here for more details.

Files changed (72) hide show
  1. package/AGENTS.md +108 -0
  2. package/IMPLEMENTATION_PLAN.md +187 -0
  3. package/LAUNCH.md +217 -0
  4. package/PRD.md +199 -0
  5. package/PROMPT.md +62 -0
  6. package/README.md +140 -0
  7. package/dist/commands/generate.d.ts +3 -0
  8. package/dist/commands/generate.d.ts.map +1 -0
  9. package/dist/commands/generate.js +46 -0
  10. package/dist/commands/generate.js.map +1 -0
  11. package/dist/commands/scan.d.ts +3 -0
  12. package/dist/commands/scan.d.ts.map +1 -0
  13. package/dist/commands/scan.js +24 -0
  14. package/dist/commands/scan.js.map +1 -0
  15. package/dist/commands/suggest.d.ts +3 -0
  16. package/dist/commands/suggest.d.ts.map +1 -0
  17. package/dist/commands/suggest.js +36 -0
  18. package/dist/commands/suggest.js.map +1 -0
  19. package/dist/commands/validate.d.ts +3 -0
  20. package/dist/commands/validate.d.ts.map +1 -0
  21. package/dist/commands/validate.js +39 -0
  22. package/dist/commands/validate.js.map +1 -0
  23. package/dist/index.d.ts +3 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +44 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/lib/analyzer.d.ts +10 -0
  28. package/dist/lib/analyzer.d.ts.map +1 -0
  29. package/dist/lib/analyzer.js +80 -0
  30. package/dist/lib/analyzer.js.map +1 -0
  31. package/dist/lib/generator.d.ts +12 -0
  32. package/dist/lib/generator.d.ts.map +1 -0
  33. package/dist/lib/generator.js +136 -0
  34. package/dist/lib/generator.js.map +1 -0
  35. package/dist/lib/output.d.ts +6 -0
  36. package/dist/lib/output.d.ts.map +1 -0
  37. package/dist/lib/output.js +35 -0
  38. package/dist/lib/output.js.map +1 -0
  39. package/dist/lib/scanner.d.ts +19 -0
  40. package/dist/lib/scanner.d.ts.map +1 -0
  41. package/dist/lib/scanner.js +159 -0
  42. package/dist/lib/scanner.js.map +1 -0
  43. package/dist/lib/validator.d.ts +13 -0
  44. package/dist/lib/validator.d.ts.map +1 -0
  45. package/dist/lib/validator.js +178 -0
  46. package/dist/lib/validator.js.map +1 -0
  47. package/dist/types.d.ts +109 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +3 -0
  50. package/dist/types.js.map +1 -0
  51. package/docs/index.html +563 -0
  52. package/marketing/email-outreach.md +183 -0
  53. package/marketing/landing-page.md +165 -0
  54. package/marketing/social-posts.md +192 -0
  55. package/package.json +58 -0
  56. package/scripts/publish.sh +33 -0
  57. package/specs/generate-command.md +147 -0
  58. package/specs/scan-command.md +92 -0
  59. package/specs/suggest-command.md +120 -0
  60. package/specs/validate-command.md +108 -0
  61. package/src/commands/generate.ts +48 -0
  62. package/src/commands/scan.ts +28 -0
  63. package/src/commands/suggest.ts +39 -0
  64. package/src/commands/validate.ts +44 -0
  65. package/src/index.ts +51 -0
  66. package/src/lib/analyzer.ts +90 -0
  67. package/src/lib/generator.ts +149 -0
  68. package/src/lib/output.ts +40 -0
  69. package/src/lib/scanner.ts +185 -0
  70. package/src/lib/validator.ts +192 -0
  71. package/src/types.ts +124 -0
  72. package/tsconfig.json +20 -0
@@ -0,0 +1,147 @@
1
+ # Spec: Generate Command
2
+
3
+ ## JTBD
4
+ As a developer, I want to generate ready-to-use JavaScript/TypeScript code that implements WebMCP tool registrations, so I can copy-paste it into my project.
5
+
6
+ ## Command
7
+ ```bash
8
+ wmcp-annotate generate <url> [options]
9
+
10
+ Options:
11
+ --suggest-file, -s Use existing suggestions instead of live analysis
12
+ --output, -o Output file
13
+ --format, -f Output format: js, ts, react, vue, svelte
14
+ --module, -m Module format: esm, cjs
15
+ ```
16
+
17
+ ## Behavior
18
+
19
+ ### Input
20
+ - URL to analyze (or existing suggestions file)
21
+ - Output format preference
22
+
23
+ ### Process
24
+ 1. Run suggest (or load suggestions file)
25
+ 2. For each tool:
26
+ - Generate registration code
27
+ - Add execute handler stub
28
+ - Include comments with implementation hints
29
+ 3. Wrap in appropriate module format
30
+ 4. Add TypeScript types if ts format
31
+
32
+ ### Output Examples
33
+
34
+ #### JavaScript (ESM)
35
+ ```javascript
36
+ // WebMCP Tool Registration
37
+ // Generated by wmcp-annotate
38
+ // URL: https://example.com
39
+
40
+ // Tool: searchProducts
41
+ // Search for products by keyword, category, or filters
42
+ navigator.modelContext.registerTool({
43
+ name: "searchProducts",
44
+ description: "Search for products by keyword, category, or filters",
45
+ readOnly: true,
46
+ inputSchema: {
47
+ type: "object",
48
+ properties: {
49
+ query: {
50
+ type: "string",
51
+ description: "Search keywords"
52
+ },
53
+ category: {
54
+ type: "string",
55
+ enum: ["electronics", "clothing", "home"]
56
+ }
57
+ },
58
+ required: ["query"]
59
+ },
60
+ async execute({ query, category }) {
61
+ // TODO: Implement search logic
62
+ // Suggested implementation:
63
+ // 1. Find form: document.querySelector('#search-form')
64
+ // 2. Fill inputs with provided values
65
+ // 3. Submit form or make API call
66
+ // 4. Return results
67
+
68
+ const form = document.querySelector('#search-form');
69
+ const queryInput = form.querySelector('input[name="query"]');
70
+ const categorySelect = form.querySelector('select[name="category"]');
71
+
72
+ queryInput.value = query;
73
+ if (category) categorySelect.value = category;
74
+
75
+ // Trigger search
76
+ form.dispatchEvent(new Event('submit'));
77
+
78
+ // Wait for results (implement based on your app)
79
+ await new Promise(r => setTimeout(r, 1000));
80
+
81
+ const results = document.querySelectorAll('.product-item');
82
+ return {
83
+ content: [{
84
+ type: "text",
85
+ text: JSON.stringify(Array.from(results).map(el => ({
86
+ name: el.querySelector('.name')?.textContent,
87
+ price: el.querySelector('.price')?.textContent
88
+ })))
89
+ }]
90
+ };
91
+ }
92
+ });
93
+ ```
94
+
95
+ #### TypeScript
96
+ ```typescript
97
+ // WebMCP Tool Registration
98
+ // Generated by wmcp-annotate
99
+
100
+ interface SearchProductsInput {
101
+ query: string;
102
+ category?: "electronics" | "clothing" | "home";
103
+ }
104
+
105
+ navigator.modelContext.registerTool({
106
+ name: "searchProducts",
107
+ description: "Search for products by keyword, category, or filters",
108
+ readOnly: true,
109
+ inputSchema: { /* ... */ },
110
+ async execute(input: SearchProductsInput) {
111
+ // Implementation
112
+ }
113
+ });
114
+ ```
115
+
116
+ #### React Hook
117
+ ```typescript
118
+ // useWebMCPTools.ts
119
+ import { useEffect } from 'react';
120
+
121
+ export function useWebMCPTools() {
122
+ useEffect(() => {
123
+ if (!navigator.modelContext) return;
124
+
125
+ const cleanup = navigator.modelContext.registerTool({
126
+ name: "searchProducts",
127
+ // ...
128
+ });
129
+
130
+ return () => cleanup?.();
131
+ }, []);
132
+ }
133
+ ```
134
+
135
+ ## Tests
136
+ 1. Generate JS → valid JavaScript that runs
137
+ 2. Generate TS → valid TypeScript that compiles
138
+ 3. Generate React → valid React hook
139
+ 4. Multiple tools → all registered
140
+ 5. Complex schemas → properly typed
141
+
142
+ ## Templates
143
+ Store templates in `templates/` directory:
144
+ - `templates/js-esm.hbs`
145
+ - `templates/ts.hbs`
146
+ - `templates/react.hbs`
147
+ - `templates/vue.hbs`
@@ -0,0 +1,92 @@
1
+ # Spec: Scan Command
2
+
3
+ ## JTBD
4
+ As a developer, I want to scan any website and identify elements that could become WebMCP tools, so I can understand what annotations my site needs.
5
+
6
+ ## Command
7
+ ```bash
8
+ wmcp-annotate scan <url> [options]
9
+
10
+ Options:
11
+ --depth <n> How many pages deep to crawl (default: 1)
12
+ --output, -o Output file
13
+ --format, -f Output format: json, table, markdown
14
+ --verbose, -v Show detailed progress
15
+ ```
16
+
17
+ ## Behavior
18
+
19
+ ### Input
20
+ - URL of website to scan
21
+ - Optional: crawl depth, output preferences
22
+
23
+ ### Process
24
+ 1. Launch headless browser (Playwright)
25
+ 2. Navigate to URL
26
+ 3. Wait for page load (network idle)
27
+ 4. Identify actionable elements:
28
+ - Forms (with inputs, selects, textareas)
29
+ - Buttons (submit, action buttons)
30
+ - Links (navigation, actions)
31
+ - AJAX/fetch calls (intercepted)
32
+ 5. For each element, extract:
33
+ - Type (form, button, link, api)
34
+ - Selector (CSS/XPath)
35
+ - Visible text/label
36
+ - Input fields (for forms)
37
+ - Current values
38
+ 6. If depth > 1, follow links and repeat
39
+
40
+ ### Output
41
+ ```json
42
+ {
43
+ "url": "https://example.com",
44
+ "scannedAt": "2026-02-24T02:45:00Z",
45
+ "elements": [
46
+ {
47
+ "type": "form",
48
+ "id": "search-form",
49
+ "selector": "#search-form",
50
+ "label": "Search",
51
+ "inputs": [
52
+ {
53
+ "name": "query",
54
+ "type": "text",
55
+ "label": "Search query",
56
+ "required": true
57
+ }
58
+ ],
59
+ "submitButton": {
60
+ "selector": "#search-form button[type=submit]",
61
+ "label": "Search"
62
+ }
63
+ },
64
+ {
65
+ "type": "button",
66
+ "selector": ".add-to-cart",
67
+ "label": "Add to Cart",
68
+ "context": "product-page"
69
+ }
70
+ ],
71
+ "apiCalls": [
72
+ {
73
+ "method": "GET",
74
+ "url": "/api/products",
75
+ "params": ["category", "limit"]
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ## Tests
82
+ 1. Scan a simple static site → finds forms and links
83
+ 2. Scan a SPA (React) → finds dynamic elements
84
+ 3. Scan with depth=2 → follows links
85
+ 4. Scan site with API calls → intercepts fetch/XHR
86
+ 5. Handle errors (404, timeout, blocked)
87
+
88
+ ## Edge Cases
89
+ - Sites that block headless browsers → retry with stealth
90
+ - SPAs that need interaction to reveal elements → basic interaction
91
+ - Infinite scroll → limit to visible content
92
+ - Auth-required pages → accept cookies/headers option
@@ -0,0 +1,120 @@
1
+ # Spec: Suggest Command
2
+
3
+ ## JTBD
4
+ As a developer, I want to get AI-powered suggestions for WebMCP tool definitions based on my site's elements, so I can quickly understand what tools to implement.
5
+
6
+ ## Command
7
+ ```bash
8
+ wmcp-annotate suggest <url> [options]
9
+
10
+ Options:
11
+ --scan-file, -s Use existing scan output instead of live scan
12
+ --output, -o Output file
13
+ --format, -f Output format: json, yaml
14
+ ```
15
+
16
+ ## Behavior
17
+
18
+ ### Input
19
+ - URL to analyze (or existing scan file)
20
+
21
+ ### Process
22
+ 1. Run scan (or load scan file)
23
+ 2. For each identified element:
24
+ - Analyze semantic meaning using AI
25
+ - Determine appropriate tool name (camelCase, descriptive)
26
+ - Generate inputSchema (JSON Schema)
27
+ - Write human-readable description
28
+ - Identify if read-only (no confirmation needed)
29
+ 3. Group tools logically
30
+ 4. Add metadata (version, author hints)
31
+
32
+ ### Output
33
+ ```json
34
+ {
35
+ "version": "1.0.0",
36
+ "tools": [
37
+ {
38
+ "name": "searchProducts",
39
+ "description": "Search for products by keyword, category, or filters",
40
+ "readOnly": true,
41
+ "inputSchema": {
42
+ "type": "object",
43
+ "properties": {
44
+ "query": {
45
+ "type": "string",
46
+ "description": "Search keywords"
47
+ },
48
+ "category": {
49
+ "type": "string",
50
+ "enum": ["electronics", "clothing", "home"],
51
+ "description": "Product category filter"
52
+ },
53
+ "maxPrice": {
54
+ "type": "number",
55
+ "description": "Maximum price filter"
56
+ }
57
+ },
58
+ "required": ["query"]
59
+ },
60
+ "sourceElement": {
61
+ "type": "form",
62
+ "selector": "#search-form"
63
+ }
64
+ },
65
+ {
66
+ "name": "addToCart",
67
+ "description": "Add a product to the shopping cart",
68
+ "readOnly": false,
69
+ "inputSchema": {
70
+ "type": "object",
71
+ "properties": {
72
+ "productId": {
73
+ "type": "string",
74
+ "description": "Product identifier"
75
+ },
76
+ "quantity": {
77
+ "type": "integer",
78
+ "default": 1,
79
+ "minimum": 1
80
+ }
81
+ },
82
+ "required": ["productId"]
83
+ },
84
+ "sourceElement": {
85
+ "type": "button",
86
+ "selector": ".add-to-cart"
87
+ }
88
+ }
89
+ ]
90
+ }
91
+ ```
92
+
93
+ ## AI Analysis Prompt
94
+ ```
95
+ Given this website element:
96
+ - Type: {type}
97
+ - Label: {label}
98
+ - Inputs: {inputs}
99
+ - Context: {context}
100
+
101
+ Generate a WebMCP tool definition:
102
+ 1. Tool name (camelCase, action-oriented verb)
103
+ 2. Description (1-2 sentences, what it does)
104
+ 3. Is it read-only? (search/view = yes, create/update/delete = no)
105
+ 4. Input schema (JSON Schema with descriptions)
106
+
107
+ Follow WebMCP spec conventions.
108
+ ```
109
+
110
+ ## Tests
111
+ 1. Form → generates tool with input schema matching form fields
112
+ 2. Button → generates action tool
113
+ 3. Search form → marks as readOnly: true
114
+ 4. Checkout button → marks as readOnly: false
115
+ 5. Complex form → handles nested fields
116
+
117
+ ## Rate Limiting
118
+ - Free tier: 10 suggestions/day
119
+ - Pro tier: unlimited
120
+ - Cache results for same URL (1 hour TTL)
@@ -0,0 +1,108 @@
1
+ # Spec: Validate Command
2
+
3
+ ## JTBD
4
+ As a developer, I want to validate my existing WebMCP implementation against the W3C spec, so I can ensure compatibility with AI agents.
5
+
6
+ ## Command
7
+ ```bash
8
+ wmcp-annotate validate <url> [options]
9
+
10
+ Options:
11
+ --output, -o Output file
12
+ --format, -f Output format: json, table, markdown
13
+ --strict Fail on warnings (not just errors)
14
+ --ci Exit code 1 on any issues (for CI)
15
+ ```
16
+
17
+ ## Behavior
18
+
19
+ ### Input
20
+ - URL of site with WebMCP implementation
21
+
22
+ ### Process
23
+ 1. Launch browser with WebMCP flag enabled
24
+ 2. Navigate to URL
25
+ 3. Query `navigator.modelContext.tools` or equivalent
26
+ 4. For each registered tool, validate:
27
+ - Name follows conventions (camelCase, no spaces)
28
+ - Description is present and meaningful
29
+ - inputSchema is valid JSON Schema
30
+ - Required fields are specified
31
+ - Types are correct
32
+ - Execute handler is async function
33
+ - ReadOnly hint is appropriate
34
+ 5. Test tool execution (optional, with --test flag)
35
+ 6. Generate report
36
+
37
+ ### Output
38
+ ```json
39
+ {
40
+ "url": "https://example.com",
41
+ "validatedAt": "2026-02-24T03:00:00Z",
42
+ "summary": {
43
+ "total": 5,
44
+ "valid": 4,
45
+ "warnings": 1,
46
+ "errors": 0
47
+ },
48
+ "tools": [
49
+ {
50
+ "name": "searchProducts",
51
+ "status": "valid",
52
+ "checks": {
53
+ "nameConvention": "pass",
54
+ "descriptionPresent": "pass",
55
+ "schemaValid": "pass",
56
+ "handlerAsync": "pass"
57
+ }
58
+ },
59
+ {
60
+ "name": "checkout",
61
+ "status": "warning",
62
+ "checks": {
63
+ "nameConvention": "pass",
64
+ "descriptionPresent": "warning",
65
+ "schemaValid": "pass",
66
+ "handlerAsync": "pass"
67
+ },
68
+ "issues": [
69
+ {
70
+ "level": "warning",
71
+ "code": "DESCRIPTION_TOO_SHORT",
72
+ "message": "Description should be at least 20 characters",
73
+ "suggestion": "Add more detail about what this tool does"
74
+ }
75
+ ]
76
+ }
77
+ ]
78
+ }
79
+ ```
80
+
81
+ ### Validation Rules
82
+
83
+ | Rule | Level | Description |
84
+ |------|-------|-------------|
85
+ | NAME_FORMAT | error | Name must be camelCase, alphanumeric |
86
+ | NAME_LENGTH | warning | Name should be 3-50 characters |
87
+ | DESCRIPTION_MISSING | error | Description is required |
88
+ | DESCRIPTION_TOO_SHORT | warning | Description < 20 chars |
89
+ | SCHEMA_INVALID | error | inputSchema must be valid JSON Schema |
90
+ | SCHEMA_NO_DESCRIPTION | warning | Properties should have descriptions |
91
+ | HANDLER_NOT_ASYNC | error | Execute must be async function |
92
+ | HANDLER_MISSING | error | Execute handler is required |
93
+ | READONLY_HINT | warning | Consider marking read-only tools |
94
+
95
+ ## Tests
96
+ 1. Valid implementation → all pass
97
+ 2. Missing description → error reported
98
+ 3. Invalid schema → error reported
99
+ 4. Sync handler → error reported
100
+ 5. --ci flag → exits 1 on errors
101
+ 6. --strict flag → exits 1 on warnings
102
+
103
+ ## CI Integration Example
104
+ ```yaml
105
+ # .github/workflows/webmcp.yml
106
+ - name: Validate WebMCP
107
+ run: npx wmcp-annotate validate ${{ env.DEPLOY_URL }} --ci
108
+ ```
@@ -0,0 +1,48 @@
1
+ import type { GenerateOptions, SuggestResult } from '../types.js';
2
+ import { generator } from '../lib/generator.js';
3
+ import { analyzer } from '../lib/analyzer.js';
4
+ import { scanner } from '../lib/scanner.js';
5
+ import { writeOutput, readInput } from '../lib/output.js';
6
+ import ora from 'ora';
7
+ import chalk from 'chalk';
8
+
9
+ export async function generateCommand(url: string | undefined, options: GenerateOptions): Promise<void> {
10
+ const spinner = ora('Generating...').start();
11
+
12
+ try {
13
+ // Get suggestions (from file or live analysis)
14
+ let suggestions: SuggestResult;
15
+ if (options.suggestFile) {
16
+ spinner.text = 'Loading suggestions file...';
17
+ suggestions = await readInput<SuggestResult>(options.suggestFile);
18
+ } else if (url) {
19
+ spinner.text = `Scanning ${url}...`;
20
+ const scanResult = await scanner.scan(url, { depth: 1 });
21
+
22
+ spinner.text = 'Generating tool suggestions...';
23
+ suggestions = await analyzer.suggest(scanResult);
24
+ } else {
25
+ throw new Error('Either URL or --suggest-file is required');
26
+ }
27
+
28
+ spinner.text = `Generating ${options.format.toUpperCase()} code...`;
29
+ const code = await generator.generate(suggestions, {
30
+ format: options.format as 'js' | 'ts' | 'react' | 'vue',
31
+ module: options.module as 'esm' | 'cjs',
32
+ });
33
+
34
+ spinner.succeed(`Generated code for ${suggestions.tools.length} tools`);
35
+
36
+ if (options.output) {
37
+ const fs = await import('fs/promises');
38
+ await fs.writeFile(options.output, code, 'utf-8');
39
+ console.log(chalk.green(`\\nSaved to ${options.output}`));
40
+ } else {
41
+ console.log('\\n' + code);
42
+ }
43
+ } catch (error) {
44
+ spinner.fail('Generation failed');
45
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
46
+ process.exit(1);
47
+ }
48
+ }
@@ -0,0 +1,28 @@
1
+ import type { ScanOptions, ScanResult } from '../types.js';
2
+ import { scanner } from '../lib/scanner.js';
3
+ import { writeOutput } from '../lib/output.js';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+
7
+ export async function scanCommand(url: string, options: ScanOptions): Promise<void> {
8
+ const spinner = ora(`Scanning ${url}...`).start();
9
+
10
+ try {
11
+ const result = await scanner.scan(url, {
12
+ depth: parseInt(options.depth, 10),
13
+ verbose: options.verbose,
14
+ });
15
+
16
+ spinner.succeed(`Found ${result.elements.length} elements and ${result.apiCalls.length} API calls`);
17
+
18
+ await writeOutput(result, options);
19
+
20
+ if (!options.output) {
21
+ console.log(chalk.dim('\\nUse --output to save results to a file'));
22
+ }
23
+ } catch (error) {
24
+ spinner.fail('Scan failed');
25
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
26
+ process.exit(1);
27
+ }
28
+ }
@@ -0,0 +1,39 @@
1
+ import type { SuggestOptions, SuggestResult, ScanResult } from '../types.js';
2
+ import { analyzer } from '../lib/analyzer.js';
3
+ import { scanner } from '../lib/scanner.js';
4
+ import { writeOutput, readInput } from '../lib/output.js';
5
+ import ora from 'ora';
6
+ import chalk from 'chalk';
7
+
8
+ export async function suggestCommand(url: string | undefined, options: SuggestOptions): Promise<void> {
9
+ const spinner = ora('Analyzing...').start();
10
+
11
+ try {
12
+ // Get scan results (from file or live scan)
13
+ let scanResult: ScanResult;
14
+ if (options.scanFile) {
15
+ spinner.text = 'Loading scan file...';
16
+ scanResult = await readInput<ScanResult>(options.scanFile);
17
+ } else if (url) {
18
+ spinner.text = `Scanning ${url}...`;
19
+ scanResult = await scanner.scan(url, { depth: 1 });
20
+ } else {
21
+ throw new Error('Either URL or --scan-file is required');
22
+ }
23
+
24
+ spinner.text = 'Generating tool suggestions with AI...';
25
+ const result = await analyzer.suggest(scanResult);
26
+
27
+ spinner.succeed(`Generated ${result.tools.length} tool suggestions`);
28
+
29
+ await writeOutput(result, options);
30
+
31
+ if (!options.output) {
32
+ console.log(chalk.dim('\\nUse --output to save results to a file'));
33
+ }
34
+ } catch (error) {
35
+ spinner.fail('Suggestion failed');
36
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
37
+ process.exit(1);
38
+ }
39
+ }
@@ -0,0 +1,44 @@
1
+ import type { ValidateOptions, ValidationResult } from '../types.js';
2
+ import { validator } from '../lib/validator.js';
3
+ import { writeOutput } from '../lib/output.js';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+
7
+ export async function validateCommand(url: string, options: ValidateOptions): Promise<void> {
8
+ const spinner = ora(`Validating ${url}...`).start();
9
+
10
+ try {
11
+ const result = await validator.validate(url);
12
+
13
+ const { summary } = result;
14
+
15
+ if (summary.errors > 0) {
16
+ spinner.fail(`Validation failed: ${summary.errors} errors, ${summary.warnings} warnings`);
17
+ } else if (summary.warnings > 0) {
18
+ spinner.warn(`Validation passed with ${summary.warnings} warnings`);
19
+ } else {
20
+ spinner.succeed(`Validation passed: ${summary.total} tools validated`);
21
+ }
22
+
23
+ await writeOutput(result, options);
24
+
25
+ // Handle CI mode
26
+ if (options.ci) {
27
+ if (summary.errors > 0 || (options.strict && summary.warnings > 0)) {
28
+ process.exit(1);
29
+ }
30
+ }
31
+
32
+ // Print summary
33
+ console.log('\\n' + chalk.bold('Summary:'));
34
+ console.log(` Total tools: ${summary.total}`);
35
+ console.log(` Valid: ${chalk.green(summary.valid)}`);
36
+ console.log(` Warnings: ${chalk.yellow(summary.warnings)}`);
37
+ console.log(` Errors: ${chalk.red(summary.errors)}`);
38
+
39
+ } catch (error) {
40
+ spinner.fail('Validation failed');
41
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
42
+ process.exit(1);
43
+ }
44
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from 'commander';
4
+ import { scanCommand } from './commands/scan.js';
5
+ import { suggestCommand } from './commands/suggest.js';
6
+ import { generateCommand } from './commands/generate.js';
7
+ import { validateCommand } from './commands/validate.js';
8
+
9
+ const program = new Command();
10
+
11
+ program
12
+ .name('wmcp-annotate')
13
+ .description('Make any website AI-agent ready with WebMCP annotations')
14
+ .version('1.0.0');
15
+
16
+ program
17
+ .command('scan <url>')
18
+ .description('Analyze a website for WebMCP opportunities')
19
+ .option('-d, --depth <number>', 'How many pages deep to crawl', '1')
20
+ .option('-o, --output <file>', 'Output file')
21
+ .option('-f, --format <format>', 'Output format: json, table, markdown', 'json')
22
+ .option('-v, --verbose', 'Show detailed progress')
23
+ .action(scanCommand);
24
+
25
+ program
26
+ .command('suggest [url]')
27
+ .description('Generate AI-powered WebMCP tool suggestions')
28
+ .option('-s, --scan-file <file>', 'Use existing scan output')
29
+ .option('-o, --output <file>', 'Output file')
30
+ .option('-f, --format <format>', 'Output format: json, yaml', 'json')
31
+ .action(suggestCommand);
32
+
33
+ program
34
+ .command('generate [url]')
35
+ .description('Generate WebMCP registration code')
36
+ .option('-s, --suggest-file <file>', 'Use existing suggestions')
37
+ .option('-o, --output <file>', 'Output file')
38
+ .option('-f, --format <format>', 'Output format: js, ts, react, vue', 'js')
39
+ .option('-m, --module <type>', 'Module format: esm, cjs', 'esm')
40
+ .action(generateCommand);
41
+
42
+ program
43
+ .command('validate <url>')
44
+ .description('Validate WebMCP implementation')
45
+ .option('-o, --output <file>', 'Output file')
46
+ .option('-f, --format <format>', 'Output format: json, table, markdown', 'json')
47
+ .option('--strict', 'Fail on warnings')
48
+ .option('--ci', 'Exit code 1 on any issues')
49
+ .action(validateCommand);
50
+
51
+ program.parse();