antigravity-seo-kit 2.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 antigravity-seo-kit might be problematic. Click here for more details.

Files changed (135) hide show
  1. package/.agent/agent.md +96 -0
  2. package/.agent/skills/seo/SKILL.md +153 -0
  3. package/.agent/skills/seo/references/cwv-thresholds.md +108 -0
  4. package/.agent/skills/seo/references/eeat-framework.md +214 -0
  5. package/.agent/skills/seo/references/local-schema-types.md +230 -0
  6. package/.agent/skills/seo/references/local-seo-signals.md +218 -0
  7. package/.agent/skills/seo/references/maps-api-endpoints.md +160 -0
  8. package/.agent/skills/seo/references/maps-free-apis.md +176 -0
  9. package/.agent/skills/seo/references/maps-gbp-checklist.md +150 -0
  10. package/.agent/skills/seo/references/maps-geo-grid.md +154 -0
  11. package/.agent/skills/seo/references/quality-gates.md +155 -0
  12. package/.agent/skills/seo/references/schema-types.md +118 -0
  13. package/.agent/skills/seo/schema/templates.json +213 -0
  14. package/.agent/skills/seo/scripts/analyze_visual.py +217 -0
  15. package/.agent/skills/seo/scripts/capture_screenshot.py +181 -0
  16. package/.agent/skills/seo/scripts/fetch_page.py +196 -0
  17. package/.agent/skills/seo/scripts/parse_html.py +201 -0
  18. package/.agent/skills/seo-audit/SKILL.md +278 -0
  19. package/.agent/skills/seo-competitor-pages/SKILL.md +212 -0
  20. package/.agent/skills/seo-content/SKILL.md +230 -0
  21. package/.agent/skills/seo-dataforseo/SKILL.md +418 -0
  22. package/.agent/skills/seo-geo/SKILL.md +305 -0
  23. package/.agent/skills/seo-google/SKILL.md +405 -0
  24. package/.agent/skills/seo-google/assets/templates/cwv-audit-report.md +48 -0
  25. package/.agent/skills/seo-google/assets/templates/gsc-performance-report.md +44 -0
  26. package/.agent/skills/seo-google/assets/templates/indexation-status-report.md +43 -0
  27. package/.agent/skills/seo-google/references/auth-setup.md +154 -0
  28. package/.agent/skills/seo-google/references/ga4-data-api.md +184 -0
  29. package/.agent/skills/seo-google/references/indexing-api.md +107 -0
  30. package/.agent/skills/seo-google/references/keyword-planner-api.md +66 -0
  31. package/.agent/skills/seo-google/references/nlp-api.md +55 -0
  32. package/.agent/skills/seo-google/references/pagespeed-crux-api.md +204 -0
  33. package/.agent/skills/seo-google/references/rate-limits-quotas.md +75 -0
  34. package/.agent/skills/seo-google/references/search-console-api.md +156 -0
  35. package/.agent/skills/seo-google/references/supplementary-apis.md +99 -0
  36. package/.agent/skills/seo-google/references/youtube-api.md +49 -0
  37. package/.agent/skills/seo-google/scripts/crux_history.py +321 -0
  38. package/.agent/skills/seo-google/scripts/ga4_report.py +478 -0
  39. package/.agent/skills/seo-google/scripts/google_auth.py +795 -0
  40. package/.agent/skills/seo-google/scripts/google_report.py +2273 -0
  41. package/.agent/skills/seo-google/scripts/gsc_inspect.py +340 -0
  42. package/.agent/skills/seo-google/scripts/gsc_query.py +378 -0
  43. package/.agent/skills/seo-google/scripts/indexing_notify.py +313 -0
  44. package/.agent/skills/seo-google/scripts/keyword_planner.py +297 -0
  45. package/.agent/skills/seo-google/scripts/nlp_analyze.py +309 -0
  46. package/.agent/skills/seo-google/scripts/pagespeed_check.py +649 -0
  47. package/.agent/skills/seo-google/scripts/youtube_search.py +355 -0
  48. package/.agent/skills/seo-hreflang/SKILL.md +192 -0
  49. package/.agent/skills/seo-image-gen/SKILL.md +211 -0
  50. package/.agent/skills/seo-image-gen/references/cost-tracking.md +47 -0
  51. package/.agent/skills/seo-image-gen/references/gemini-models.md +200 -0
  52. package/.agent/skills/seo-image-gen/references/mcp-tools.md +115 -0
  53. package/.agent/skills/seo-image-gen/references/post-processing.md +192 -0
  54. package/.agent/skills/seo-image-gen/references/presets.md +69 -0
  55. package/.agent/skills/seo-image-gen/references/prompt-engineering.md +411 -0
  56. package/.agent/skills/seo-image-gen/references/seo-image-presets.md +137 -0
  57. package/.agent/skills/seo-image-gen/scripts/batch.py +97 -0
  58. package/.agent/skills/seo-image-gen/scripts/cost_tracker.py +191 -0
  59. package/.agent/skills/seo-image-gen/scripts/edit.py +141 -0
  60. package/.agent/skills/seo-image-gen/scripts/generate.py +149 -0
  61. package/.agent/skills/seo-image-gen/scripts/presets.py +153 -0
  62. package/.agent/skills/seo-image-gen/scripts/setup_mcp.py +151 -0
  63. package/.agent/skills/seo-image-gen/scripts/validate_setup.py +133 -0
  64. package/.agent/skills/seo-images/SKILL.md +176 -0
  65. package/.agent/skills/seo-local/SKILL.md +381 -0
  66. package/.agent/skills/seo-maps/SKILL.md +328 -0
  67. package/.agent/skills/seo-page/SKILL.md +86 -0
  68. package/.agent/skills/seo-plan/SKILL.md +118 -0
  69. package/.agent/skills/seo-plan/assets/agency.md +175 -0
  70. package/.agent/skills/seo-plan/assets/ecommerce.md +167 -0
  71. package/.agent/skills/seo-plan/assets/generic.md +144 -0
  72. package/.agent/skills/seo-plan/assets/local-service.md +160 -0
  73. package/.agent/skills/seo-plan/assets/publisher.md +153 -0
  74. package/.agent/skills/seo-plan/assets/saas.md +135 -0
  75. package/.agent/skills/seo-programmatic/SKILL.md +171 -0
  76. package/.agent/skills/seo-schema/SKILL.md +223 -0
  77. package/.agent/skills/seo-sitemap/SKILL.md +180 -0
  78. package/.agent/skills/seo-technical/SKILL.md +211 -0
  79. package/.agent/workflows/seo-audit.md +17 -0
  80. package/.agent/workflows/seo-competitor-pages.md +12 -0
  81. package/.agent/workflows/seo-content.md +14 -0
  82. package/.agent/workflows/seo-geo.md +12 -0
  83. package/.agent/workflows/seo-google.md +12 -0
  84. package/.agent/workflows/seo-hreflang.md +12 -0
  85. package/.agent/workflows/seo-images.md +13 -0
  86. package/.agent/workflows/seo-local.md +12 -0
  87. package/.agent/workflows/seo-maps.md +11 -0
  88. package/.agent/workflows/seo-page.md +13 -0
  89. package/.agent/workflows/seo-plan.md +13 -0
  90. package/.agent/workflows/seo-programmatic.md +12 -0
  91. package/.agent/workflows/seo-schema.md +11 -0
  92. package/.agent/workflows/seo-sitemap.md +9 -0
  93. package/.agent/workflows/seo-technical.md +18 -0
  94. package/LICENSE +88 -0
  95. package/README.md +122 -0
  96. package/bin/cli.js +117 -0
  97. package/docs/ARCHITECTURE.md +218 -0
  98. package/docs/COMMANDS.md +184 -0
  99. package/docs/INSTALLATION.md +100 -0
  100. package/docs/MCP-INTEGRATION.md +153 -0
  101. package/docs/TROUBLESHOOTING.md +151 -0
  102. package/docs/superpowers/plans/2026-03-13-github-audit-fixes.md +511 -0
  103. package/extensions/banana/README.md +95 -0
  104. package/extensions/banana/docs/BANANA-SETUP.md +86 -0
  105. package/extensions/banana/install.sh +170 -0
  106. package/extensions/banana/references/cost-tracking.md +47 -0
  107. package/extensions/banana/references/gemini-models.md +200 -0
  108. package/extensions/banana/references/mcp-tools.md +115 -0
  109. package/extensions/banana/references/post-processing.md +192 -0
  110. package/extensions/banana/references/presets.md +69 -0
  111. package/extensions/banana/references/prompt-engineering.md +411 -0
  112. package/extensions/banana/references/seo-image-presets.md +137 -0
  113. package/extensions/banana/scripts/batch.py +97 -0
  114. package/extensions/banana/scripts/cost_tracker.py +191 -0
  115. package/extensions/banana/scripts/edit.py +141 -0
  116. package/extensions/banana/scripts/generate.py +149 -0
  117. package/extensions/banana/scripts/presets.py +153 -0
  118. package/extensions/banana/scripts/setup_mcp.py +151 -0
  119. package/extensions/banana/scripts/validate_setup.py +133 -0
  120. package/extensions/banana/uninstall.sh +43 -0
  121. package/extensions/dataforseo/README.md +169 -0
  122. package/extensions/dataforseo/docs/DATAFORSEO-SETUP.md +74 -0
  123. package/extensions/dataforseo/field-config.json +280 -0
  124. package/extensions/dataforseo/install.ps1 +110 -0
  125. package/extensions/dataforseo/install.sh +161 -0
  126. package/extensions/dataforseo/uninstall.ps1 +35 -0
  127. package/extensions/dataforseo/uninstall.sh +39 -0
  128. package/lib/api.js +190 -0
  129. package/lib/fingerprint.js +68 -0
  130. package/lib/installer.js +486 -0
  131. package/lib/utils.js +254 -0
  132. package/package.json +40 -0
  133. package/pyproject.toml +11 -0
  134. package/requirements-google.txt +15 -0
  135. package/requirements.txt +11 -0
@@ -0,0 +1,161 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # DataForSEO Extension Installer for SEO Kit
5
+ # Wraps everything in main() to prevent partial execution on network failure
6
+
7
+ main() {
8
+ SKILL_DIR=".agent/skills/seo-dataforseo"
9
+ AGENT_DIR=".agent/skills"
10
+ SEO_SKILL_DIR=".agent/skills/seo"
11
+ SETTINGS_FILE="MCP_settings_path"
12
+
13
+ echo "════════════════════════════════════════"
14
+ echo "║ DataForSEO Extension - Installer ║"
15
+ echo "║ For SEO Kit ║"
16
+ echo "════════════════════════════════════════"
17
+ echo ""
18
+
19
+ # Check prerequisites
20
+ if [ ! -d "${SEO_SKILL_DIR}" ]; then
21
+ echo "✗ SEO Kit is not installed."
22
+ echo " Install it first: curl -fsSL https://raw.githubusercontent.com/AgriciDaniel/seo-kit/main/install.sh | bash"
23
+ exit 1
24
+ fi
25
+ echo "✓ SEO Kit detected"
26
+
27
+ if ! command -v node >/dev/null 2>&1; then
28
+ echo "✗ Node.js is required but not installed."
29
+ echo " Install Node.js 20+: https://nodejs.org/"
30
+ exit 1
31
+ fi
32
+
33
+ NODE_VERSION=$(node -v | sed 's/v//' | cut -d. -f1)
34
+ if [ "${NODE_VERSION}" -lt 20 ]; then
35
+ echo "✗ Node.js 20+ required (found v${NODE_VERSION})."
36
+ echo " Update: https://nodejs.org/"
37
+ exit 1
38
+ fi
39
+ echo "✓ Node.js v$(node -v | sed 's/v//') detected"
40
+
41
+ if ! command -v npx >/dev/null 2>&1; then
42
+ echo "✗ npx is required but not found (comes with npm)."
43
+ exit 1
44
+ fi
45
+ echo "✓ npx detected"
46
+
47
+ # Prompt for credentials
48
+ echo ""
49
+ echo "DataForSEO API credentials required."
50
+ echo "Sign up at: https://app.dataforseo.com/register"
51
+ echo ""
52
+
53
+ read -rp "DataForSEO username (email): " DFSE_USERNAME
54
+ if [ -z "${DFSE_USERNAME}" ]; then
55
+ echo "✗ Username cannot be empty."
56
+ exit 1
57
+ fi
58
+
59
+ read -rsp "DataForSEO password: " DFSE_PASSWORD
60
+ echo ""
61
+ if [ -z "${DFSE_PASSWORD}" ]; then
62
+ echo "✗ Password cannot be empty."
63
+ exit 1
64
+ fi
65
+
66
+ # Determine script directory (works for both ./install.sh and curl|bash)
67
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
68
+
69
+ # Check if running from repo or standalone
70
+ if [ -f "${SCRIPT_DIR}/skills/seo-dataforseo/SKILL.md" ]; then
71
+ SOURCE_DIR="${SCRIPT_DIR}"
72
+ elif [ -f "${SCRIPT_DIR}/extensions/dataforseo/skills/seo-dataforseo/SKILL.md" ]; then
73
+ SOURCE_DIR="${SCRIPT_DIR}/extensions/dataforseo"
74
+ else
75
+ echo "✗ Cannot find extension source files."
76
+ echo " Run this script from the seo-kit repo: ./extensions/dataforseo/install.sh"
77
+ exit 1
78
+ fi
79
+
80
+ # Install skill
81
+ echo ""
82
+ echo "→ Installing DataForSEO skill..."
83
+ mkdir -p "${SKILL_DIR}"
84
+ cp "${SOURCE_DIR}/skills/seo-dataforseo/SKILL.md" "${SKILL_DIR}/SKILL.md"
85
+
86
+ # Install agent
87
+ echo "→ Installing DataForSEO agent..."
88
+ mkdir -p "${AGENT_DIR}"
89
+ cp "${SOURCE_DIR}/agents/seo-dataforseo.md" "${AGENT_DIR}/seo-dataforseo.md"
90
+
91
+ # Install field config
92
+ echo "→ Installing field config..."
93
+ cp "${SOURCE_DIR}/field-config.json" "${SEO_SKILL_DIR}/dataforseo-field-config.json"
94
+
95
+ # Merge MCP config into settings.json
96
+ echo "→ Configuring MCP server..."
97
+ FIELD_CONFIG_PATH="${SEO_SKILL_DIR}/dataforseo-field-config.json"
98
+
99
+ python3 -c "
100
+ import json, os, sys
101
+
102
+ settings_path = '${SETTINGS_FILE}'
103
+ username = '''${DFSE_USERNAME}'''
104
+ password = '''${DFSE_PASSWORD}'''
105
+ field_config = '${FIELD_CONFIG_PATH}'
106
+
107
+ # Read existing settings or create new
108
+ if os.path.exists(settings_path):
109
+ with open(settings_path, 'r') as f:
110
+ settings = json.load(f)
111
+ else:
112
+ settings = {}
113
+
114
+ # Ensure mcpServers key exists
115
+ if 'mcpServers' not in settings:
116
+ settings['mcpServers'] = {}
117
+
118
+ # Add DataForSEO server config
119
+ settings['mcpServers']['dataforseo'] = {
120
+ 'command': 'npx',
121
+ 'args': ['-y', 'dataforseo-mcp-server'],
122
+ 'env': {
123
+ 'DATAFORSEO_USERNAME': username,
124
+ 'DATAFORSEO_PASSWORD': password,
125
+ 'ENABLED_MODULES': 'SERP,KEYWORDS_DATA,ONPAGE,DATAFORSEO_LABS,BACKLINKS,DOMAIN_ANALYTICS,BUSINESS_DATA,CONTENT_ANALYSIS,AI_OPTIMIZATION',
126
+ 'FIELD_CONFIG_PATH': field_config
127
+ }
128
+ }
129
+
130
+ # Write back
131
+ os.makedirs(os.path.dirname(settings_path), exist_ok=True)
132
+ with open(settings_path, 'w') as f:
133
+ json.dump(settings, f, indent=2)
134
+
135
+ print(' ✓ MCP server configured in settings.json')
136
+ " || {
137
+ echo " ⚠ Could not auto-configure MCP server."
138
+ echo " Add the dataforseo server manually to MCP settings"
139
+ echo " See: extensions/dataforseo/docs/DATAFORSEO-SETUP.md"
140
+ }
141
+
142
+ # Pre-warm npx package
143
+ echo "→ Pre-downloading dataforseo-mcp-server..."
144
+ npx -y dataforseo-mcp-server --help >/dev/null 2>&1 || true
145
+
146
+ echo ""
147
+ echo "✓ DataForSEO extension installed successfully!"
148
+ echo ""
149
+ echo "Usage:"
150
+ echo " 1. Open your workspace in your AI agent"
151
+ echo " 2. Run commands:"
152
+ echo " /seo dataforseo serp best coffee shops"
153
+ echo " /seo dataforseo keywords seo tools"
154
+ echo " /seo dataforseo backlinks example.com"
155
+ echo " /seo dataforseo ai-mentions your brand"
156
+ echo ""
157
+ echo "All 22 commands: see extensions/dataforseo/README.md"
158
+ echo "To uninstall: ./extensions/dataforseo/uninstall.sh"
159
+ }
160
+
161
+ main "$@"
@@ -0,0 +1,35 @@
1
+ # DataForSEO Extension Uninstaller for SEO Kit (Windows)
2
+ # PowerShell uninstall script
3
+
4
+ $ErrorActionPreference = "Stop"
5
+
6
+ Write-Host "════════════════════════════════════════" -ForegroundColor Cyan
7
+ Write-Host "║ DataForSEO Extension - Uninstall ║" -ForegroundColor Cyan
8
+ Write-Host "╣ For SEO Kit (Antigravity) ║" -ForegroundColor Cyan
9
+ Write-Host "════════════════════════════════════════" -ForegroundColor Cyan
10
+ Write-Host ""
11
+
12
+ # Determine workspace root
13
+ $WorkspaceRoot = Split-Path -Parent (Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path))
14
+
15
+ # Remove skill
16
+ $SkillDir = "$WorkspaceRoot\.agent\skills\seo-dataforseo"
17
+ if (Test-Path $SkillDir) {
18
+ Remove-Item -Recurse -Force $SkillDir
19
+ Write-Host "✓ Removed DataForSEO skill" -ForegroundColor Green
20
+ } else {
21
+ Write-Host " ⚠ Skill directory not found (already removed?)" -ForegroundColor Yellow
22
+ }
23
+
24
+ # Remove field config
25
+ $FieldConfig = "$WorkspaceRoot\.agent\skills\seo\dataforseo-field-config.json"
26
+ if (Test-Path $FieldConfig) {
27
+ Remove-Item -Force $FieldConfig
28
+ Write-Host "✓ Removed field config" -ForegroundColor Green
29
+ }
30
+
31
+ Write-Host ""
32
+ Write-Host "✓ DataForSEO extension uninstalled." -ForegroundColor Green
33
+ Write-Host ""
34
+ Write-Host "Note: MCP server configuration must be removed manually from your"
35
+ Write-Host "agent platform settings if configured." -ForegroundColor Yellow
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ main() {
5
+ echo "→ Uninstalling DataForSEO extension..."
6
+
7
+ # Remove skill
8
+ rm -rf ".agent/skills/seo-dataforseo"
9
+
10
+ # Remove agent
11
+ rm -f ".agent/skills/seo-dataforseo.md"
12
+
13
+ # Remove field config
14
+ rm -f ".agent/skills/seo/dataforseo-field-config.json"
15
+
16
+ # Remove MCP server entry from settings.json
17
+ SETTINGS_FILE="MCP_settings_path"
18
+ if [ -f "${SETTINGS_FILE}" ]; then
19
+ python3 -c "
20
+ import json, os
21
+ settings_path = '${SETTINGS_FILE}'
22
+ with open(settings_path, 'r') as f:
23
+ settings = json.load(f)
24
+ if 'mcpServers' in settings and 'dataforseo' in settings['mcpServers']:
25
+ del settings['mcpServers']['dataforseo']
26
+ if not settings['mcpServers']:
27
+ del settings['mcpServers']
28
+ with open(settings_path, 'w') as f:
29
+ json.dump(settings, f, indent=2)
30
+ print(' ✓ Removed dataforseo from settings.json')
31
+ else:
32
+ print(' ✓ No dataforseo entry in settings.json')
33
+ " 2>/dev/null || echo " ⚠ Could not auto-remove MCP config. Remove 'dataforseo' from MCP settings manually."
34
+ fi
35
+
36
+ echo "✓ DataForSEO extension uninstalled."
37
+ }
38
+
39
+ main "$@"
package/lib/api.js ADDED
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ const https = require('https');
4
+ const http = require('http');
5
+ const { PACKAGE_VERSION } = require('./utils');
6
+
7
+ // ─── Configuration ──────────────────────────────────────────────────────────
8
+
9
+ // TODO: Replace with your actual license server URL
10
+ const DEFAULT_API_URL = process.env.SEO_KIT_API_URL || 'https://api.example.com/api/seo-kit';
11
+
12
+ function getApiUrl() {
13
+ return DEFAULT_API_URL.replace(/\/+$/, ''); // Remove trailing slash
14
+ }
15
+
16
+ function getUserAgent() {
17
+ return `antigravity-seo-kit/${PACKAGE_VERSION} node/${process.version} ${process.platform}`;
18
+ }
19
+
20
+ // ─── HTTP Client ────────────────────────────────────────────────────────────
21
+
22
+ /**
23
+ * Make an HTTP(S) request. Returns parsed JSON response.
24
+ * Uses only Node.js built-in modules (no dependencies).
25
+ */
26
+ function request(method, urlPath, { body = null, licenseKey = null, timeout = 15000 } = {}) {
27
+ return new Promise((resolve, reject) => {
28
+ const baseUrl = getApiUrl();
29
+ const fullUrl = new URL(urlPath, baseUrl);
30
+
31
+ const isHttps = fullUrl.protocol === 'https:';
32
+ const transport = isHttps ? https : http;
33
+
34
+ const headers = {
35
+ 'User-Agent': getUserAgent(),
36
+ 'Accept': 'application/json',
37
+ };
38
+
39
+ if (licenseKey) {
40
+ headers['X-License-Key'] = licenseKey;
41
+ }
42
+
43
+ if (body) {
44
+ headers['Content-Type'] = 'application/json';
45
+ }
46
+
47
+ const options = {
48
+ hostname: fullUrl.hostname,
49
+ port: fullUrl.port || (isHttps ? 443 : 80),
50
+ path: fullUrl.pathname + fullUrl.search,
51
+ method: method.toUpperCase(),
52
+ headers,
53
+ timeout,
54
+ };
55
+
56
+ const req = transport.request(options, (res) => {
57
+ let data = '';
58
+ res.on('data', (chunk) => { data += chunk; });
59
+ res.on('end', () => {
60
+ try {
61
+ const parsed = data ? JSON.parse(data) : {};
62
+ if (res.statusCode >= 200 && res.statusCode < 300) {
63
+ resolve({ status: res.statusCode, data: parsed });
64
+ } else {
65
+ reject(new ApiError(
66
+ parsed.error?.message || parsed.message || `HTTP ${res.statusCode}`,
67
+ res.statusCode,
68
+ parsed
69
+ ));
70
+ }
71
+ } catch (e) {
72
+ reject(new ApiError(`Invalid JSON response: ${data.substring(0, 200)}`, res.statusCode));
73
+ }
74
+ });
75
+ });
76
+
77
+ req.on('error', (err) => {
78
+ if (err.code === 'ECONNREFUSED') {
79
+ reject(new ApiError('Cannot connect to license server. Please check your internet connection.', 0));
80
+ } else if (err.code === 'ENOTFOUND') {
81
+ reject(new ApiError('License server not found. DNS resolution failed.', 0));
82
+ } else {
83
+ reject(new ApiError(`Network error: ${err.message}`, 0));
84
+ }
85
+ });
86
+
87
+ req.on('timeout', () => {
88
+ req.destroy();
89
+ reject(new ApiError('Request timed out. Please try again.', 0));
90
+ });
91
+
92
+ if (body) {
93
+ req.write(JSON.stringify(body));
94
+ }
95
+
96
+ req.end();
97
+ });
98
+ }
99
+
100
+ // ─── API Error ──────────────────────────────────────────────────────────────
101
+
102
+ class ApiError extends Error {
103
+ constructor(message, statusCode, responseData = null) {
104
+ super(message);
105
+ this.name = 'ApiError';
106
+ this.statusCode = statusCode;
107
+ this.responseData = responseData;
108
+ }
109
+
110
+ get isNetworkError() { return this.statusCode === 0; }
111
+ get isNotFound() { return this.statusCode === 404; }
112
+ get isForbidden() { return this.statusCode === 403; }
113
+ get isUnauthorized() { return this.statusCode === 401; }
114
+ }
115
+
116
+ // ─── License API Methods ────────────────────────────────────────────────────
117
+
118
+ /**
119
+ * Verify a license key and activate the current device.
120
+ *
121
+ * POST /licenses/verify
122
+ * Body: { licenseKey, deviceId, deviceName }
123
+ *
124
+ * Response: {
125
+ * valid: true,
126
+ * license: { plan, expiresAt, ... },
127
+ * devicesUsed: 2,
128
+ * maxDevices: 3,
129
+ * devices: [{ deviceId, deviceName, activatedAt }]
130
+ * }
131
+ */
132
+ async function verifyLicense(licenseKey, deviceId, deviceName) {
133
+ const { data } = await request('POST', '/licenses/verify', {
134
+ body: { licenseKey, deviceId, deviceName },
135
+ licenseKey,
136
+ });
137
+ return data;
138
+ }
139
+
140
+ /**
141
+ * Get license status and info.
142
+ *
143
+ * GET /licenses/status
144
+ */
145
+ async function getLicenseStatus(licenseKey) {
146
+ const { data } = await request('GET', '/licenses/status', { licenseKey });
147
+ return data;
148
+ }
149
+
150
+ /**
151
+ * List all activated devices for a license.
152
+ *
153
+ * GET /licenses/devices
154
+ */
155
+ async function listDevices(licenseKey) {
156
+ const { data } = await request('GET', '/licenses/devices', { licenseKey });
157
+ return data;
158
+ }
159
+
160
+ /**
161
+ * Remove a device from the license.
162
+ *
163
+ * DELETE /licenses/devices/:deviceId
164
+ */
165
+ async function removeDevice(licenseKey, deviceId) {
166
+ const { data } = await request('DELETE', `/licenses/devices/${deviceId}`, { licenseKey });
167
+ return data;
168
+ }
169
+
170
+ /**
171
+ * Check for the latest release version.
172
+ *
173
+ * GET /releases/latest
174
+ */
175
+ async function getLatestRelease(licenseKey) {
176
+ const { data } = await request('GET', '/releases/latest', { licenseKey });
177
+ return data;
178
+ }
179
+
180
+ // ─── Exports ────────────────────────────────────────────────────────────────
181
+
182
+ module.exports = {
183
+ verifyLicense,
184
+ getLicenseStatus,
185
+ listDevices,
186
+ removeDevice,
187
+ getLatestRelease,
188
+ ApiError,
189
+ getApiUrl,
190
+ };
@@ -0,0 +1,68 @@
1
+ 'use strict';
2
+
3
+ const os = require('os');
4
+ const crypto = require('crypto');
5
+
6
+ /**
7
+ * Generate a unique device fingerprint based on hardware characteristics.
8
+ *
9
+ * Combines hostname, platform, architecture, CPU model, total memory,
10
+ * and primary MAC address into a SHA-256 hash (truncated to 16 chars).
11
+ *
12
+ * This is not tamper-proof but sufficient for license device tracking.
13
+ */
14
+ function generateFingerprint() {
15
+ const components = [
16
+ os.hostname(),
17
+ os.platform(),
18
+ os.arch(),
19
+ getCpuModel(),
20
+ os.totalmem().toString(),
21
+ getPrimaryMacAddress(),
22
+ ];
23
+
24
+ const raw = components.join('|');
25
+ return crypto.createHash('sha256').update(raw).digest('hex').substring(0, 16);
26
+ }
27
+
28
+ /**
29
+ * Get a human-readable device name for display purposes.
30
+ * Example: "DESKTOP-ABC (win32 x64)"
31
+ */
32
+ function getDeviceName() {
33
+ const hostname = os.hostname();
34
+ const platform = os.platform();
35
+ const arch = os.arch();
36
+ const release = os.release().split('.').slice(0, 2).join('.');
37
+ return `${hostname} (${platform} ${arch}, ${release})`;
38
+ }
39
+
40
+ /**
41
+ * Get CPU model string.
42
+ */
43
+ function getCpuModel() {
44
+ const cpus = os.cpus();
45
+ return cpus.length > 0 ? cpus[0].model.trim() : 'unknown-cpu';
46
+ }
47
+
48
+ /**
49
+ * Get the MAC address of the first non-internal network interface.
50
+ * Falls back to 'no-mac' if none found.
51
+ */
52
+ function getPrimaryMacAddress() {
53
+ const interfaces = os.networkInterfaces();
54
+ for (const name of Object.keys(interfaces)) {
55
+ for (const iface of interfaces[name]) {
56
+ // Skip internal (loopback) and link-local interfaces
57
+ if (!iface.internal && iface.mac && iface.mac !== '00:00:00:00:00:00') {
58
+ return iface.mac;
59
+ }
60
+ }
61
+ }
62
+ return 'no-mac';
63
+ }
64
+
65
+ module.exports = {
66
+ generateFingerprint,
67
+ getDeviceName,
68
+ };