antigravity-seo-kit 1.2.7 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/downloader.js +18 -1
- package/lib/installer.js +20 -6
- package/lib/utils.js +81 -4
- package/package.json +1 -1
package/lib/downloader.js
CHANGED
|
@@ -4,6 +4,7 @@ const https = require('https');
|
|
|
4
4
|
const zlib = require('zlib');
|
|
5
5
|
const fs = require('fs');
|
|
6
6
|
const path = require('path');
|
|
7
|
+
const { smartMergeJsonFile } = require('./utils');
|
|
7
8
|
|
|
8
9
|
// ─── Download ───────────────────────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -145,8 +146,24 @@ function extractTarGz(gzBuffer, targetDir) {
|
|
|
145
146
|
fs.mkdirSync(targetPath, { recursive: true });
|
|
146
147
|
} else if (typeFlag === ASCII_0 || typeFlag === 0) {
|
|
147
148
|
// Regular file
|
|
149
|
+
const isConfigJson = fullName.endsWith('.json') &&
|
|
150
|
+
(fullName.startsWith('config/') ||
|
|
151
|
+
fullName.startsWith('.agent/config/') ||
|
|
152
|
+
fullName.includes('/config/'));
|
|
153
|
+
|
|
148
154
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
149
|
-
|
|
155
|
+
|
|
156
|
+
if (isConfigJson && fs.existsSync(targetPath)) {
|
|
157
|
+
const tempPath = targetPath + '.tmp';
|
|
158
|
+
fs.writeFileSync(tempPath, tarBuffer.subarray(offset, offset + size));
|
|
159
|
+
smartMergeJsonFile(tempPath, targetPath);
|
|
160
|
+
try {
|
|
161
|
+
fs.unlinkSync(tempPath);
|
|
162
|
+
} catch (e) {}
|
|
163
|
+
} else {
|
|
164
|
+
fs.writeFileSync(targetPath, tarBuffer.subarray(offset, offset + size));
|
|
165
|
+
}
|
|
166
|
+
|
|
150
167
|
files.push(fullName);
|
|
151
168
|
count++;
|
|
152
169
|
}
|
package/lib/installer.js
CHANGED
|
@@ -457,12 +457,14 @@ async function installPlugin(licenseKey) {
|
|
|
457
457
|
version: PACKAGE_VERSION,
|
|
458
458
|
description: "Professional SEO Analysis Toolkit for Google Antigravity AI Agent — 44 specialized skills covering technical audit, E-E-A-T, schema, GEO, local SEO & more",
|
|
459
459
|
author: {
|
|
460
|
-
name: "Antigravity SEO Kit"
|
|
460
|
+
name: "Antigravity SEO Kit",
|
|
461
|
+
email: "admin@solann.io",
|
|
461
462
|
},
|
|
463
|
+
repository: "https://solann.io/antigravity-seo-kit",
|
|
462
464
|
license: "SEE LICENSE IN LICENSE",
|
|
463
465
|
keywords: [
|
|
464
466
|
"seo",
|
|
465
|
-
"
|
|
467
|
+
"agentic-seo",
|
|
466
468
|
"ai-agent",
|
|
467
469
|
"antigravity",
|
|
468
470
|
"seo-geo",
|
|
@@ -524,16 +526,28 @@ function setupWorkspace(cwd) {
|
|
|
524
526
|
const spin = spinner('Replicating .agent directory to local workspace...').start();
|
|
525
527
|
try {
|
|
526
528
|
fs.mkdirSync(localAgentDir, { recursive: true });
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
529
|
+
|
|
530
|
+
// Only copy necessary directories. agents and rules are loaded globally via the plugin.
|
|
531
|
+
// skills are copied but filtered to exclude .md files to save space and avoid duplicate prompts/instructions.
|
|
532
|
+
const directoriesToCopy = ['.shared', 'config', 'dashboard', 'docs', 'scripts', 'workflows'];
|
|
533
|
+
const filesToCopy = ['seo-architecture.md'];
|
|
530
534
|
|
|
531
535
|
let count = 0;
|
|
532
536
|
for (const dir of directoriesToCopy) {
|
|
533
537
|
const src = path.join(globalDir, dir);
|
|
534
538
|
const dest = path.join(localAgentDir, dir);
|
|
535
539
|
if (fs.existsSync(src)) {
|
|
536
|
-
|
|
540
|
+
if (dir === 'skills') {
|
|
541
|
+
// Keep scripts and assets, but exclude instruction/documentation markdown files
|
|
542
|
+
count += copyRecursive(src, dest, {
|
|
543
|
+
overwrite: true,
|
|
544
|
+
filter: (filePath) => !filePath.endsWith('.md')
|
|
545
|
+
});
|
|
546
|
+
} else if (dir === 'config') {
|
|
547
|
+
count += copyRecursive(src, dest, { overwrite: true, mergeJson: true });
|
|
548
|
+
} else {
|
|
549
|
+
count += copyRecursive(src, dest, { overwrite: true });
|
|
550
|
+
}
|
|
537
551
|
}
|
|
538
552
|
}
|
|
539
553
|
for (const file of filesToCopy) {
|
package/lib/utils.js
CHANGED
|
@@ -88,12 +88,82 @@ function spinner(message) {
|
|
|
88
88
|
|
|
89
89
|
// ─── File Operations ────────────────────────────────────────────────────────
|
|
90
90
|
|
|
91
|
+
/**
|
|
92
|
+
* Check if a value is a plain object.
|
|
93
|
+
*/
|
|
94
|
+
function isObject(item) {
|
|
95
|
+
return (item && typeof item === 'object' && !Array.isArray(item));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Recursively merge two JSON objects (deep merge).
|
|
100
|
+
* Prioritizes properties from sourceObj (user config) while preserving/appending new properties from targetObj (template config).
|
|
101
|
+
*/
|
|
102
|
+
function deepMergeJson(targetObj, sourceObj) {
|
|
103
|
+
const output = { ...targetObj };
|
|
104
|
+
if (isObject(targetObj) && isObject(sourceObj)) {
|
|
105
|
+
for (const key of Object.keys(sourceObj)) {
|
|
106
|
+
if (isObject(sourceObj[key])) {
|
|
107
|
+
if (!(key in targetObj)) {
|
|
108
|
+
output[key] = sourceObj[key];
|
|
109
|
+
} else {
|
|
110
|
+
output[key] = deepMergeJson(targetObj[key], sourceObj[key]);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
output[key] = sourceObj[key];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return output;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Merges JSON file from srcPath (new template) into destPath (existing user config) safely.
|
|
122
|
+
*/
|
|
123
|
+
function smartMergeJsonFile(srcPath, destPath) {
|
|
124
|
+
if (!fs.existsSync(srcPath)) return;
|
|
125
|
+
|
|
126
|
+
if (!fs.existsSync(destPath)) {
|
|
127
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
128
|
+
fs.copyFileSync(srcPath, destPath);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let newJson;
|
|
133
|
+
try {
|
|
134
|
+
newJson = JSON.parse(fs.readFileSync(srcPath, 'utf-8'));
|
|
135
|
+
} catch (err) {
|
|
136
|
+
fs.copyFileSync(srcPath, destPath);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
let oldJson;
|
|
141
|
+
try {
|
|
142
|
+
oldJson = JSON.parse(fs.readFileSync(destPath, 'utf-8'));
|
|
143
|
+
} catch (err) {
|
|
144
|
+
const bakPath = destPath + '.bak';
|
|
145
|
+
try {
|
|
146
|
+
fs.copyFileSync(destPath, bakPath);
|
|
147
|
+
warn(`File cấu hình bị lỗi cú pháp JSON và đã được sao lưu thành: ${path.basename(bakPath)}`);
|
|
148
|
+
} catch (bakErr) {
|
|
149
|
+
warn(`Không thể sao lưu file cấu hình lỗi: ${bakErr.message}`);
|
|
150
|
+
}
|
|
151
|
+
fs.copyFileSync(srcPath, destPath);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const merged = deepMergeJson(newJson, oldJson);
|
|
156
|
+
fs.writeFileSync(destPath, JSON.stringify(merged, null, 2), 'utf-8');
|
|
157
|
+
}
|
|
158
|
+
|
|
91
159
|
/**
|
|
92
160
|
* Recursively copy a directory, merging with existing content.
|
|
93
161
|
* Does not overwrite existing files unless `overwrite` is true.
|
|
162
|
+
* Supports smart JSON merging if `mergeJson` is enabled.
|
|
94
163
|
*/
|
|
95
|
-
function copyRecursive(src, dest, { overwrite = false, fileCallback = null } = {}) {
|
|
164
|
+
function copyRecursive(src, dest, { overwrite = false, fileCallback = null, filter = null, mergeJson = false } = {}) {
|
|
96
165
|
if (!fs.existsSync(src)) return 0;
|
|
166
|
+
if (filter && !filter(src)) return 0;
|
|
97
167
|
|
|
98
168
|
let count = 0;
|
|
99
169
|
|
|
@@ -104,12 +174,17 @@ function copyRecursive(src, dest, { overwrite = false, fileCallback = null } = {
|
|
|
104
174
|
count += copyRecursive(
|
|
105
175
|
path.join(src, entry),
|
|
106
176
|
path.join(dest, entry),
|
|
107
|
-
{ overwrite, fileCallback }
|
|
177
|
+
{ overwrite, fileCallback, filter, mergeJson }
|
|
108
178
|
);
|
|
109
179
|
}
|
|
110
180
|
} else {
|
|
111
181
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
112
|
-
|
|
182
|
+
const isJson = src.endsWith('.json');
|
|
183
|
+
if (isJson && mergeJson && fs.existsSync(dest)) {
|
|
184
|
+
smartMergeJsonFile(src, dest);
|
|
185
|
+
count = 1;
|
|
186
|
+
if (fileCallback) fileCallback(dest);
|
|
187
|
+
} else if (overwrite || !fs.existsSync(dest)) {
|
|
113
188
|
fs.copyFileSync(src, dest);
|
|
114
189
|
count = 1;
|
|
115
190
|
if (fileCallback) fileCallback(dest);
|
|
@@ -298,5 +373,7 @@ module.exports = {
|
|
|
298
373
|
writeLicenseFile,
|
|
299
374
|
removeLicenseFile,
|
|
300
375
|
LICENSE_FILE,
|
|
301
|
-
PACKAGE_VERSION: '1.
|
|
376
|
+
PACKAGE_VERSION: '1.3.0',
|
|
377
|
+
deepMergeJson,
|
|
378
|
+
smartMergeJsonFile,
|
|
302
379
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "antigravity-seo-kit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Professional SEO Analysis Toolkit for Google Antigravity AI Agent — 44 specialized skills covering technical audit, E-E-A-T, schema, GEO, local SEO & more",
|
|
5
5
|
"main": "lib/installer.js",
|
|
6
6
|
"bin": {
|