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.
- package/.agent/agent.md +96 -0
- package/.agent/skills/seo/SKILL.md +153 -0
- package/.agent/skills/seo/references/cwv-thresholds.md +108 -0
- package/.agent/skills/seo/references/eeat-framework.md +214 -0
- package/.agent/skills/seo/references/local-schema-types.md +230 -0
- package/.agent/skills/seo/references/local-seo-signals.md +218 -0
- package/.agent/skills/seo/references/maps-api-endpoints.md +160 -0
- package/.agent/skills/seo/references/maps-free-apis.md +176 -0
- package/.agent/skills/seo/references/maps-gbp-checklist.md +150 -0
- package/.agent/skills/seo/references/maps-geo-grid.md +154 -0
- package/.agent/skills/seo/references/quality-gates.md +155 -0
- package/.agent/skills/seo/references/schema-types.md +118 -0
- package/.agent/skills/seo/schema/templates.json +213 -0
- package/.agent/skills/seo/scripts/analyze_visual.py +217 -0
- package/.agent/skills/seo/scripts/capture_screenshot.py +181 -0
- package/.agent/skills/seo/scripts/fetch_page.py +196 -0
- package/.agent/skills/seo/scripts/parse_html.py +201 -0
- package/.agent/skills/seo-audit/SKILL.md +278 -0
- package/.agent/skills/seo-competitor-pages/SKILL.md +212 -0
- package/.agent/skills/seo-content/SKILL.md +230 -0
- package/.agent/skills/seo-dataforseo/SKILL.md +418 -0
- package/.agent/skills/seo-geo/SKILL.md +305 -0
- package/.agent/skills/seo-google/SKILL.md +405 -0
- package/.agent/skills/seo-google/assets/templates/cwv-audit-report.md +48 -0
- package/.agent/skills/seo-google/assets/templates/gsc-performance-report.md +44 -0
- package/.agent/skills/seo-google/assets/templates/indexation-status-report.md +43 -0
- package/.agent/skills/seo-google/references/auth-setup.md +154 -0
- package/.agent/skills/seo-google/references/ga4-data-api.md +184 -0
- package/.agent/skills/seo-google/references/indexing-api.md +107 -0
- package/.agent/skills/seo-google/references/keyword-planner-api.md +66 -0
- package/.agent/skills/seo-google/references/nlp-api.md +55 -0
- package/.agent/skills/seo-google/references/pagespeed-crux-api.md +204 -0
- package/.agent/skills/seo-google/references/rate-limits-quotas.md +75 -0
- package/.agent/skills/seo-google/references/search-console-api.md +156 -0
- package/.agent/skills/seo-google/references/supplementary-apis.md +99 -0
- package/.agent/skills/seo-google/references/youtube-api.md +49 -0
- package/.agent/skills/seo-google/scripts/crux_history.py +321 -0
- package/.agent/skills/seo-google/scripts/ga4_report.py +478 -0
- package/.agent/skills/seo-google/scripts/google_auth.py +795 -0
- package/.agent/skills/seo-google/scripts/google_report.py +2273 -0
- package/.agent/skills/seo-google/scripts/gsc_inspect.py +340 -0
- package/.agent/skills/seo-google/scripts/gsc_query.py +378 -0
- package/.agent/skills/seo-google/scripts/indexing_notify.py +313 -0
- package/.agent/skills/seo-google/scripts/keyword_planner.py +297 -0
- package/.agent/skills/seo-google/scripts/nlp_analyze.py +309 -0
- package/.agent/skills/seo-google/scripts/pagespeed_check.py +649 -0
- package/.agent/skills/seo-google/scripts/youtube_search.py +355 -0
- package/.agent/skills/seo-hreflang/SKILL.md +192 -0
- package/.agent/skills/seo-image-gen/SKILL.md +211 -0
- package/.agent/skills/seo-image-gen/references/cost-tracking.md +47 -0
- package/.agent/skills/seo-image-gen/references/gemini-models.md +200 -0
- package/.agent/skills/seo-image-gen/references/mcp-tools.md +115 -0
- package/.agent/skills/seo-image-gen/references/post-processing.md +192 -0
- package/.agent/skills/seo-image-gen/references/presets.md +69 -0
- package/.agent/skills/seo-image-gen/references/prompt-engineering.md +411 -0
- package/.agent/skills/seo-image-gen/references/seo-image-presets.md +137 -0
- package/.agent/skills/seo-image-gen/scripts/batch.py +97 -0
- package/.agent/skills/seo-image-gen/scripts/cost_tracker.py +191 -0
- package/.agent/skills/seo-image-gen/scripts/edit.py +141 -0
- package/.agent/skills/seo-image-gen/scripts/generate.py +149 -0
- package/.agent/skills/seo-image-gen/scripts/presets.py +153 -0
- package/.agent/skills/seo-image-gen/scripts/setup_mcp.py +151 -0
- package/.agent/skills/seo-image-gen/scripts/validate_setup.py +133 -0
- package/.agent/skills/seo-images/SKILL.md +176 -0
- package/.agent/skills/seo-local/SKILL.md +381 -0
- package/.agent/skills/seo-maps/SKILL.md +328 -0
- package/.agent/skills/seo-page/SKILL.md +86 -0
- package/.agent/skills/seo-plan/SKILL.md +118 -0
- package/.agent/skills/seo-plan/assets/agency.md +175 -0
- package/.agent/skills/seo-plan/assets/ecommerce.md +167 -0
- package/.agent/skills/seo-plan/assets/generic.md +144 -0
- package/.agent/skills/seo-plan/assets/local-service.md +160 -0
- package/.agent/skills/seo-plan/assets/publisher.md +153 -0
- package/.agent/skills/seo-plan/assets/saas.md +135 -0
- package/.agent/skills/seo-programmatic/SKILL.md +171 -0
- package/.agent/skills/seo-schema/SKILL.md +223 -0
- package/.agent/skills/seo-sitemap/SKILL.md +180 -0
- package/.agent/skills/seo-technical/SKILL.md +211 -0
- package/.agent/workflows/seo-audit.md +17 -0
- package/.agent/workflows/seo-competitor-pages.md +12 -0
- package/.agent/workflows/seo-content.md +14 -0
- package/.agent/workflows/seo-geo.md +12 -0
- package/.agent/workflows/seo-google.md +12 -0
- package/.agent/workflows/seo-hreflang.md +12 -0
- package/.agent/workflows/seo-images.md +13 -0
- package/.agent/workflows/seo-local.md +12 -0
- package/.agent/workflows/seo-maps.md +11 -0
- package/.agent/workflows/seo-page.md +13 -0
- package/.agent/workflows/seo-plan.md +13 -0
- package/.agent/workflows/seo-programmatic.md +12 -0
- package/.agent/workflows/seo-schema.md +11 -0
- package/.agent/workflows/seo-sitemap.md +9 -0
- package/.agent/workflows/seo-technical.md +18 -0
- package/LICENSE +88 -0
- package/README.md +122 -0
- package/bin/cli.js +117 -0
- package/docs/ARCHITECTURE.md +218 -0
- package/docs/COMMANDS.md +184 -0
- package/docs/INSTALLATION.md +100 -0
- package/docs/MCP-INTEGRATION.md +153 -0
- package/docs/TROUBLESHOOTING.md +151 -0
- package/docs/superpowers/plans/2026-03-13-github-audit-fixes.md +511 -0
- package/extensions/banana/README.md +95 -0
- package/extensions/banana/docs/BANANA-SETUP.md +86 -0
- package/extensions/banana/install.sh +170 -0
- package/extensions/banana/references/cost-tracking.md +47 -0
- package/extensions/banana/references/gemini-models.md +200 -0
- package/extensions/banana/references/mcp-tools.md +115 -0
- package/extensions/banana/references/post-processing.md +192 -0
- package/extensions/banana/references/presets.md +69 -0
- package/extensions/banana/references/prompt-engineering.md +411 -0
- package/extensions/banana/references/seo-image-presets.md +137 -0
- package/extensions/banana/scripts/batch.py +97 -0
- package/extensions/banana/scripts/cost_tracker.py +191 -0
- package/extensions/banana/scripts/edit.py +141 -0
- package/extensions/banana/scripts/generate.py +149 -0
- package/extensions/banana/scripts/presets.py +153 -0
- package/extensions/banana/scripts/setup_mcp.py +151 -0
- package/extensions/banana/scripts/validate_setup.py +133 -0
- package/extensions/banana/uninstall.sh +43 -0
- package/extensions/dataforseo/README.md +169 -0
- package/extensions/dataforseo/docs/DATAFORSEO-SETUP.md +74 -0
- package/extensions/dataforseo/field-config.json +280 -0
- package/extensions/dataforseo/install.ps1 +110 -0
- package/extensions/dataforseo/install.sh +161 -0
- package/extensions/dataforseo/uninstall.ps1 +35 -0
- package/extensions/dataforseo/uninstall.sh +39 -0
- package/lib/api.js +190 -0
- package/lib/fingerprint.js +68 -0
- package/lib/installer.js +486 -0
- package/lib/utils.js +254 -0
- package/package.json +40 -0
- package/pyproject.toml +11 -0
- package/requirements-google.txt +15 -0
- 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
|
+
};
|