dsh-wkgeo-ops 0.2.2 → 0.2.4
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 +21 -10
- package/assets/icon.png +0 -0
- package/assets/icon.svg +3 -0
- package/client/client.js +355 -46
- package/lib/index.js +305 -2
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +8 -2
package/lib/index.js
CHANGED
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* dsh-wkgeo-ops · DSH 插件「网站运营」——服务端 cordis 插件
|
|
3
3
|
*
|
|
4
4
|
* 作用:把 wkgeo CLI(仓库内 cli/wkgeo.mjs,默认值见 DEFAULT_CLI_PATH)的站点运营能力,
|
|
5
|
-
* 以
|
|
5
|
+
* 以 21 个 DSH agent 工具 + 6 条同源 HTTP 路由(面板取数 / 建站向导 / 模板选项 / 根目录 / 浏览 / 站点文件夹)暴露给 DSH。
|
|
6
|
+
*
|
|
7
|
+
* v0.2.4(2026-10-30 老板「加 GEO/SEO CLI 工具」):新增 5 个工具 —— geo audit(只读体检)/ geo llms(默认 dry-run)/
|
|
8
|
+
* geo robots(show 只读,allow-ai / block-ai 需 confirm)/ geo brands(后台未配置时原样转述 not_configured)/ seo audit(只读)。
|
|
6
9
|
*
|
|
7
10
|
* v0.2.0(2026-10-30 老板「接收 dsh,完成 cli 后续能力对接,然后发布一个包」):
|
|
8
11
|
* 在原有 7 个工具上补齐 content apply / fill / check、templates list、template init / validate / pack / publish、domains list;
|
|
@@ -283,6 +286,59 @@ export function buildDomainRecordsArgs({ site } = {}) {
|
|
|
283
286
|
return args
|
|
284
287
|
}
|
|
285
288
|
|
|
289
|
+
// ---------------------------------------------------------------------------
|
|
290
|
+
// 1c. 纯函数:v0.2.4 新增 GEO / SEO 工具的 CLI 参数(同样只拼参数,不重写业务)
|
|
291
|
+
// ---------------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
/** `geo robots --mode` 的三个合法值(与 CLI 的 --mode 完全一致) */
|
|
294
|
+
export const GEO_ROBOTS_MODES = ['allow-ai', 'block-ai', 'show']
|
|
295
|
+
|
|
296
|
+
/** mode 归一:非法 / 缺省一律落到只读的 show(绝不因为写错参数就改成写盘) */
|
|
297
|
+
export function normalizeGeoRobotsMode(mode) {
|
|
298
|
+
const value = typeof mode === 'string' ? mode.trim().toLowerCase() : ''
|
|
299
|
+
return GEO_ROBOTS_MODES.includes(value) ? value : 'show'
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** `geo audit <dir> --json`(只读) */
|
|
303
|
+
export function buildGeoAuditArgs({ dir } = {}) {
|
|
304
|
+
return ['geo', 'audit', String(dir == null ? '' : dir), '--json']
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/** `geo llms <dir> [--check] [--base-url <url>] [--confirm] --json`(不加 --confirm 就是 CLI 自己的 dry-run) */
|
|
308
|
+
export function buildGeoLlmsArgs({ dir, check, baseUrl, confirm } = {}) {
|
|
309
|
+
const args = ['geo', 'llms', String(dir == null ? '' : dir)]
|
|
310
|
+
pushFlag(args, '--check', check)
|
|
311
|
+
if (baseUrl) args.push('--base-url', String(baseUrl))
|
|
312
|
+
pushFlag(args, '--confirm', confirm)
|
|
313
|
+
args.push('--json')
|
|
314
|
+
return args
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** `geo robots <dir> --mode <allow-ai|block-ai|show> [--confirm] --json` */
|
|
318
|
+
export function buildGeoRobotsArgs({ dir, mode, confirm } = {}) {
|
|
319
|
+
const args = ['geo', 'robots', String(dir == null ? '' : dir), '--mode', normalizeGeoRobotsMode(mode)]
|
|
320
|
+
pushFlag(args, '--confirm', confirm)
|
|
321
|
+
args.push('--json')
|
|
322
|
+
return args
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* `geo brands [--domain <域>] [--lang <语言>] --json`。
|
|
327
|
+
* 插件的 project 参数映射到 CLI 的 --domain(CLI 那个参数就叫 --domain)。
|
|
328
|
+
*/
|
|
329
|
+
export function buildGeoBrandsArgs({ project, lang } = {}) {
|
|
330
|
+
const args = ['geo', 'brands']
|
|
331
|
+
if (project) args.push('--domain', String(project))
|
|
332
|
+
if (lang) args.push('--lang', String(lang))
|
|
333
|
+
args.push('--json')
|
|
334
|
+
return args
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** `seo audit <dir> --json`(只读;CLI 这一层不联网,只认本地目录) */
|
|
338
|
+
export function buildSeoAuditArgs({ dir } = {}) {
|
|
339
|
+
return ['seo', 'audit', String(dir == null ? '' : dir), '--json']
|
|
340
|
+
}
|
|
341
|
+
|
|
286
342
|
/** 把用户给的站点路径规整成 CLI 认识的形式(前导斜杠、去空白、空则 "/") */
|
|
287
343
|
export function normalizePath(path) {
|
|
288
344
|
if (typeof path !== 'string') return '/'
|
|
@@ -1333,6 +1389,122 @@ export function renderDomainsText(value = {}) {
|
|
|
1333
1389
|
return renderCliText(value)
|
|
1334
1390
|
}
|
|
1335
1391
|
|
|
1392
|
+
/**
|
|
1393
|
+
* GEO / SEO 体检结果渲染:分数 + 等级 + 问题分布 + 关键 issue 前 5 条。
|
|
1394
|
+
* 两个 audit(geo / seo)共用;只读命令,所以不给「要不要写盘」这类提示。
|
|
1395
|
+
*/
|
|
1396
|
+
export function renderAuditText(value = {}, label = '体检') {
|
|
1397
|
+
if (!value || value.ok !== true) return renderCliText(value)
|
|
1398
|
+
const data = value.data || {}
|
|
1399
|
+
const lines = []
|
|
1400
|
+
const score = data.score
|
|
1401
|
+
const grade = data.grade ? '(' + data.grade + ')' : ''
|
|
1402
|
+
const pages = Number.isFinite(data.pages) ? data.pages : 0
|
|
1403
|
+
lines.push('✔ ' + label + ':' + (score != null ? score + ' / 100' + grade : '未给分数') + ' · ' + pages + ' 个页面' + (data.dir ? '\n 目录:' + data.dir : ''))
|
|
1404
|
+
const sum = data.summary || {}
|
|
1405
|
+
if (sum.total != null) {
|
|
1406
|
+
lines.push('问题分布:error ' + (sum.error || 0) + ' / warn ' + (sum.warn || 0) + ' / info ' + (sum.info || 0) + ',共 ' + (sum.total || 0) + ' 条' + (data.truncated ? '(已截断)' : ''))
|
|
1407
|
+
}
|
|
1408
|
+
const issues = Array.isArray(data.issues) ? data.issues : []
|
|
1409
|
+
const top = issues.slice(0, 5)
|
|
1410
|
+
top.forEach((it, i) => {
|
|
1411
|
+
const item = it || {}
|
|
1412
|
+
const where = item.file || item.page || item.url || ''
|
|
1413
|
+
lines.push(' ' + (i + 1) + '. [' + (item.severity || 'info') + '] ' + (where ? where + ' · ' : '') + (item.message || item.rule || ''))
|
|
1414
|
+
if (item.suggestion) lines.push(' → ' + item.suggestion)
|
|
1415
|
+
})
|
|
1416
|
+
if (issues.length > top.length) lines.push('… 其余 ' + (issues.length - top.length) + ' 条在工具返回值的 data.issues 里')
|
|
1417
|
+
else if (issues.length === 0) lines.push('没有发现问题。')
|
|
1418
|
+
return lines.join('\n')
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
/** 写盘清单渲染:CLI 里 written[] 可能是「路径字符串」也可能是 `{name,path,bytes}` 对象 */
|
|
1422
|
+
function writtenPaths(list) {
|
|
1423
|
+
if (!Array.isArray(list)) return []
|
|
1424
|
+
return list
|
|
1425
|
+
.map((item) => (typeof item === 'string' ? item : item && typeof item === 'object' ? item.path || item.name || '' : ''))
|
|
1426
|
+
.filter((item) => typeof item === 'string' && item !== '')
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
/** `geo llms` 渲染:写了哪两个文件(或 dry-run 预览)、覆盖多少页 */
|
|
1430
|
+
export function renderGeoLlmsText(value = {}) {
|
|
1431
|
+
if (!value || value.ok !== true) return renderCliText(value)
|
|
1432
|
+
const data = value.data || {}
|
|
1433
|
+
const dry = data.dry_run === true
|
|
1434
|
+
const lines = [dry ? '👀 geo llms 预览(dry-run,一个字节都没写)' : '✔ geo llms 已生成']
|
|
1435
|
+
const txt = data.llms_txt || {}
|
|
1436
|
+
const full = data.llms_full || {}
|
|
1437
|
+
lines.push(' - llms.txt:' + (txt.path || '(未给路径)') + (txt.chars != null ? '(' + txt.chars + ' 字符)' : ''))
|
|
1438
|
+
lines.push(' - llms-full.txt:' + (full.path || '(未给路径)') + (full.chars != null ? '(' + full.chars + ' 字符)' : ''))
|
|
1439
|
+
const written = writtenPaths(data.written)
|
|
1440
|
+
lines.push(written.length > 0 ? '写入:' + written.join(' / ') : '本次没有写任何文件' + (dry ? '(dry-run)' : ''))
|
|
1441
|
+
const pages = Array.isArray(data.pages) ? data.pages.length : 0
|
|
1442
|
+
const categories = Array.isArray(data.categories) ? data.categories : []
|
|
1443
|
+
lines.push('覆盖页面 ' + pages + ' 个' + (categories.length > 0 ? ',栏目:' + categories.join(' / ') : ''))
|
|
1444
|
+
const notes = Array.isArray(data.notes) ? data.notes : []
|
|
1445
|
+
notes.slice(0, 3).forEach((note) => lines.push('注意:' + (typeof note === 'string' ? note : note.message || previewJson(note, 200))))
|
|
1446
|
+
if (dry) lines.push('要真写盘:同一个工具带 confirm:true 重跑(写完仍是幂等的)。')
|
|
1447
|
+
return lines.join('\n')
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/** `geo robots` 渲染:show 看每个爬虫的状态;allow-ai / block-ai 看 diff 与是否写盘 */
|
|
1451
|
+
export function renderGeoRobotsText(value = {}) {
|
|
1452
|
+
if (!value || value.ok !== true) return renderCliText(value)
|
|
1453
|
+
const data = value.data || {}
|
|
1454
|
+
const mode = data.mode || 'show'
|
|
1455
|
+
const lines = []
|
|
1456
|
+
if (mode === 'show') {
|
|
1457
|
+
lines.push('✔ robots.txt 的 AI 爬虫规则(只读,未写盘):' + (data.path || ''))
|
|
1458
|
+
lines.push(data.exists === false ? ' robots.txt 不存在:目前没有任何 AI 策略(也没有被整站封禁)' : ' robots.txt 已存在;wkgeo 托管段' + (data.managed_block ? '存在' : '不存在(还没生成过)'))
|
|
1459
|
+
const bots = Array.isArray(data.bots) ? data.bots : []
|
|
1460
|
+
bots.forEach((bot) => {
|
|
1461
|
+
lines.push(' - ' + (bot.bot || bot.user_agent || '?') + ':' + (bot.status || '') + (bot.explicit ? '(显式规则)' : '(未显式列出)') + (bot.rules ? ' · ' + bot.rules : ''))
|
|
1462
|
+
})
|
|
1463
|
+
const counts = data.counts || {}
|
|
1464
|
+
if (counts.allowed != null) lines.push('汇总:放行 ' + (counts.allowed || 0) + ' / 禁止 ' + (counts.blocked || 0) + ' / 未列出 ' + (counts.unlisted || 0))
|
|
1465
|
+
return lines.join('\n')
|
|
1466
|
+
}
|
|
1467
|
+
const dry = data.dry_run === true
|
|
1468
|
+
lines.push((dry ? '👀 geo robots ' + mode + ' 预览(dry-run,未写盘)' : '✔ geo robots ' + mode + ' 已写入') + (data.path ? '\n ' + data.path : ''))
|
|
1469
|
+
const added = Array.isArray(data.added) ? data.added.length : 0
|
|
1470
|
+
const removed = Array.isArray(data.removed) ? data.removed.length : 0
|
|
1471
|
+
lines.push('改动:' + (data.action || '(未给)') + (added > 0 ? ' · 新增 ' + added + ' 行' : '') + (removed > 0 ? ' · 删除 ' + removed + ' 行' : ''))
|
|
1472
|
+
const bots = Array.isArray(data.bots) ? data.bots : []
|
|
1473
|
+
if (bots.length > 0) lines.push('涉及 UA:' + bots.map((bot) => (typeof bot === 'string' ? bot : (bot && (bot.bot || bot.user_agent)) || '?')).join(' / '))
|
|
1474
|
+
const written = writtenPaths(data.written)
|
|
1475
|
+
if (written.length > 0) lines.push('写入:' + written.join(' / '))
|
|
1476
|
+
if (dry) lines.push(mode === 'block-ai' ? '注意:block-ai 会真的挡住 AI 收录;确认后带 confirm:true 重跑。' : '要真写盘:同一个工具带 confirm:true 重跑。')
|
|
1477
|
+
return lines.join('\n')
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
/** `geo brands` 渲染:未配置后端时把 not_configured 翻成人话(绝不假装成功) */
|
|
1481
|
+
export function renderGeoBrandsText(value = {}) {
|
|
1482
|
+
const data = (value && value.data) || {}
|
|
1483
|
+
if (value && value.ok === true && data.status === 'not_configured') {
|
|
1484
|
+
const contract = data.contract || {}
|
|
1485
|
+
const env = contract.env || 'WKGEO_GEO_API'
|
|
1486
|
+
const req = contract.request || {}
|
|
1487
|
+
const lines = [
|
|
1488
|
+
'⚠ 品牌收录跟踪还没接上(CLI 返回 status: not_configured)',
|
|
1489
|
+
' 后台基址没配置:本次一个请求都没发,也没有任何本地假数据。',
|
|
1490
|
+
'要启用:在运行 DSH 的环境里配置环境变量 ' + env + '(后台基址),再重跑本工具。',
|
|
1491
|
+
]
|
|
1492
|
+
if (req.path) lines.push('接口契约:' + (req.method || 'GET') + ' ' + req.path + '?domain=<站点域名>&lang=<语言> → brands[].platforms[](status: none | listed | top | cited)')
|
|
1493
|
+
lines.push('说明:品牌数据只能来自后台,CLI 绝不本地伪造「看起来像真数据」的占位记录。')
|
|
1494
|
+
return lines.join('\n')
|
|
1495
|
+
}
|
|
1496
|
+
if (!value || value.ok !== true) return renderCliText(value)
|
|
1497
|
+
const brands = Array.isArray(data.brands) ? data.brands : []
|
|
1498
|
+
if (brands.length === 0) return '✔ geo brands 成功,但后台返回的品牌列表为空(暂时没有收录 / 引用记录)。'
|
|
1499
|
+
const lines = ['✔ 品牌收录:' + brands.length + ' 个(平台 ' + (data.platforms != null ? data.platforms : '?') + ' 个)']
|
|
1500
|
+
brands.slice(0, 5).forEach((brand) => {
|
|
1501
|
+
const platforms = Array.isArray(brand.platforms) ? brand.platforms : []
|
|
1502
|
+
lines.push(' - ' + (brand.name || '(未命名)') + ':' + (platforms.length > 0 ? platforms.map((p) => (p.platform || '?') + '=' + (p.status || '?') + (p.rank != null ? ' #' + p.rank : '')).join(' / ') : '无平台数据'))
|
|
1503
|
+
})
|
|
1504
|
+
if (brands.length > 5) lines.push('… 其余 ' + (brands.length - 5) + ' 个见 data.brands')
|
|
1505
|
+
return lines.join('\n')
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1336
1508
|
/**
|
|
1337
1509
|
* 造 4 个工具定义。deps.run 可注入(测试用假输出)。
|
|
1338
1510
|
* @returns {Array<object>} 工具定义数组(形状对齐 DSH 已装插件的 tools.register)
|
|
@@ -1972,7 +2144,138 @@ export function createTools(deps = {}) {
|
|
|
1972
2144
|
},
|
|
1973
2145
|
}
|
|
1974
2146
|
|
|
1975
|
-
|
|
2147
|
+
// -------------------------------------------------------------------------
|
|
2148
|
+
// v0.2.4:GEO / SEO 六件套里的 5 个(geo audit / llms / robots / brands + seo audit)
|
|
2149
|
+
// 全部是薄封装:spawn node cli/wkgeo.mjs … --json → 解析信封 → 结构化返回。
|
|
2150
|
+
// -------------------------------------------------------------------------
|
|
2151
|
+
|
|
2152
|
+
const geoAudit = {
|
|
2153
|
+
name: 'wkgeo_geo_audit',
|
|
2154
|
+
description:
|
|
2155
|
+
'本地站点产物 GEO(生成式引擎优化)体检,底层 `wkgeo geo audit`:20 项加权评分(0-100)+ 等级(good / fair / poor)+ issues[](error / warn / info,每条带位置与改法)。**只读**:不联网、不写任何文件;要补 llms.txt / robots.txt 用 wkgeo_geo_llms / wkgeo_geo_robots。有问题也返回 ok:true(看 score / issues),只有目录不存在或目录里没有 .html 才 ok:false。',
|
|
2156
|
+
parameters: paramsSchema(
|
|
2157
|
+
{
|
|
2158
|
+
dir: { type: 'string', description: '站点产物目录(含 index.html 等静态页面;建议给绝对路径,如 F:\\cloudbase\\dist)' },
|
|
2159
|
+
},
|
|
2160
|
+
['dir'],
|
|
2161
|
+
),
|
|
2162
|
+
output: { schema: RESULT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderAuditText(value, 'GEO 体检(geo audit)') }] },
|
|
2163
|
+
async execute(args = {}) {
|
|
2164
|
+
const dir = textOf(args.dir)
|
|
2165
|
+
if (!dir) return missingArg('dir', '给站点产物目录(含 index.html 的目录,建议绝对路径)', 'geo audit')
|
|
2166
|
+
const res = await runCli(buildGeoAuditArgs({ dir }))
|
|
2167
|
+
return normalizeCliResult(res, { command: 'geo audit' })
|
|
2168
|
+
},
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const geoLlms = {
|
|
2172
|
+
name: 'wkgeo_geo_llms',
|
|
2173
|
+
description:
|
|
2174
|
+
'按目录生成给 AI 读的两份索引:llms.txt(站点标题 + 描述 + 按栏目分节的「- [标题](URL):一句话摘要」)与 llms-full.txt(每页正文全文),底层 `wkgeo geo llms`。**不传 confirm 就是 dry-run**:只把两份文件内容算出来给你看,一个字节都不写;使用者确认后才带 confirm:true 真写盘(写到 <dir>/llms.txt 与 <dir>/llms-full.txt,幂等可重跑)。标题 / 摘要只从页面与站点清单里取,取不到就留空,不编造。',
|
|
2175
|
+
parameters: paramsSchema({
|
|
2176
|
+
dir: { type: 'string', description: '站点产物目录(写 <dir>/llms.txt 与 <dir>/llms-full.txt;建议绝对路径)' },
|
|
2177
|
+
confirm: { type: 'boolean', description: '确认真写盘才传 true;不传或 false = 只预览(dry-run)' },
|
|
2178
|
+
}),
|
|
2179
|
+
output: { schema: RESULT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderGeoLlmsText(value) }] },
|
|
2180
|
+
async execute(args = {}) {
|
|
2181
|
+
const dir = textOf(args.dir)
|
|
2182
|
+
if (!dir) return missingArg('dir', '给站点产物目录(llms.txt 会写在这个目录下)', 'geo llms')
|
|
2183
|
+
const confirm = args.confirm === true
|
|
2184
|
+
const res = await runCli(buildGeoLlmsArgs({ dir, confirm }))
|
|
2185
|
+
return normalizeCliResult(res, {
|
|
2186
|
+
command: 'geo llms',
|
|
2187
|
+
executed: confirm,
|
|
2188
|
+
needsConfirm: !confirm,
|
|
2189
|
+
reason: confirm ? '' : 'dry-run:只打印 llms.txt / llms-full.txt 的内容,一个字节都没写。看完无误后,同一个工具带 confirm:true 重跑才落盘。',
|
|
2190
|
+
})
|
|
2191
|
+
},
|
|
2192
|
+
}
|
|
2193
|
+
|
|
2194
|
+
const geoRobots = {
|
|
2195
|
+
name: 'wkgeo_geo_robots',
|
|
2196
|
+
description:
|
|
2197
|
+
'管理 robots.txt 里的 AI 爬虫策略段(GPTBot / OAI-SearchBot / ClaudeBot / PerplexityBot / Google-Extended / Bingbot),底层 `wkgeo geo robots`。mode=show(默认)**只读**:列出当前每个 AI 爬虫的放行 / 封禁状态,不写盘;mode=allow-ai / block-ai 是**会写文件**的策略生成——不传 confirm 只给 diff(dry-run),确认后带 confirm:true 才写。幂等:策略段用 `# >>> wkgeo:geo-ai >>>` 包起来整段替换,段外人工规则一个字节都不动。**block-ai 会真的挡住 AI 收录**,除非使用者明确要求,别用。',
|
|
2198
|
+
parameters: paramsSchema({
|
|
2199
|
+
dir: { type: 'string', description: '站点产物目录(读 / 写 <dir>/robots.txt;建议绝对路径)' },
|
|
2200
|
+
mode: { type: 'string', enum: GEO_ROBOTS_MODES, description: 'allow-ai = 6 个 AI 爬虫各写 Allow: /;block-ai = 换成 Disallow: /(会真挡住 AI 收录,慎用);show = 只看当前规则' },
|
|
2201
|
+
confirm: { type: 'boolean', description: 'allow-ai / block-ai 要真写盘才传 true;不传 = 只打印 diff。show 忽略此参数' },
|
|
2202
|
+
}),
|
|
2203
|
+
output: { schema: RESULT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderGeoRobotsText(value) }] },
|
|
2204
|
+
async execute(args = {}) {
|
|
2205
|
+
const dir = textOf(args.dir)
|
|
2206
|
+
if (!dir) return missingArg('dir', '给站点产物目录(robots.txt 在这个目录下)', 'geo robots')
|
|
2207
|
+
const mode = normalizeGeoRobotsMode(args.mode)
|
|
2208
|
+
const confirm = mode !== 'show' && args.confirm === true
|
|
2209
|
+
const res = await runCli(buildGeoRobotsArgs({ dir, mode, confirm }))
|
|
2210
|
+
const reason =
|
|
2211
|
+
mode === 'show'
|
|
2212
|
+
? 'show 只读:只列当前 robots.txt 的 AI 规则与每个 UA 的策略状态,不写盘。'
|
|
2213
|
+
: confirm
|
|
2214
|
+
? ''
|
|
2215
|
+
: 'dry-run:只打印 diff,没写盘。确认要改 robots.txt 后带 confirm:true 重跑' + (mode === 'block-ai' ? '(block-ai 会真的挡住 AI 收录)。' : '。')
|
|
2216
|
+
return normalizeCliResult(res, {
|
|
2217
|
+
command: 'geo robots --mode ' + mode,
|
|
2218
|
+
executed: mode === 'show' ? true : confirm,
|
|
2219
|
+
needsConfirm: mode !== 'show' && !confirm,
|
|
2220
|
+
reason,
|
|
2221
|
+
})
|
|
2222
|
+
},
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
const geoBrands = {
|
|
2226
|
+
name: 'wkgeo_geo_brands',
|
|
2227
|
+
description:
|
|
2228
|
+
'品牌在 AI 平台上的收录 / 引用跟踪,底层 `wkgeo geo brands`(只读,不写文件)。**后台 API 未就绪**:没配置环境变量 WKGEO_GEO_API 时,CLI 一个请求都不发,返回 status=not_configured + 接口契约,本工具会把这句话原样翻成人话,**不会假装查到了品牌**。project 传站点域名(映射到 CLI 的 --domain)。',
|
|
2229
|
+
parameters: paramsSchema({
|
|
2230
|
+
project: { type: 'string', description: '项目 / 站点域名(如 example.com,映射到 CLI 的 --domain;可留空,留空只看配置状态)' },
|
|
2231
|
+
}),
|
|
2232
|
+
output: { schema: RESULT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderGeoBrandsText(value) }] },
|
|
2233
|
+
async execute(args = {}) {
|
|
2234
|
+
const project = textOf(args.project)
|
|
2235
|
+
const res = await runCli(buildGeoBrandsArgs({ project }))
|
|
2236
|
+
return normalizeCliResult(res, { command: 'geo brands' })
|
|
2237
|
+
},
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
const seoAudit = {
|
|
2241
|
+
name: 'wkgeo_seo_audit',
|
|
2242
|
+
description:
|
|
2243
|
+
'本地站点产物 SEO 体检,底层 `wkgeo seo audit`:13 项加权评分(title / description / canonical / og / JSON-LD / H1 / 标题层级 / 图片 alt / 内链死链 / llms.txt / sitemap.xml / robots.txt)+ 等级 + issues[](每条带位置与改法)。**只读**:不联网、不写任何文件。有问题也返回 ok:true(看 score / issues),只有目录不存在或没有 .html 才 ok:false。**注意**:CLI 这一层不联网,只认本地目录;url 只是别名入口,填了 url 没有 dir 时会明确告诉你先落成本地目录,不会假装抓取成功。',
|
|
2244
|
+
parameters: paramsSchema({
|
|
2245
|
+
dir: { type: 'string', description: '本地站点产物目录(含 index.html 等静态页面;建议绝对路径)。dir 与 url 二选一,优先 dir' },
|
|
2246
|
+
url: { type: 'string', description: '站点网址(http / https,如 https://www.example.com)。可填网址,但 CLI 的 seo audit 只做本地产物目录体检、不联网;只给 url 时会提示先落成本地目录再传 dir' },
|
|
2247
|
+
}),
|
|
2248
|
+
output: { schema: RESULT_SCHEMA, render: (_args, value) => [{ type: 'text', text: renderAuditText(value, 'SEO 体检(seo audit)') }] },
|
|
2249
|
+
async execute(args = {}) {
|
|
2250
|
+
const dir = textOf(args.dir)
|
|
2251
|
+
const url = textOf(args.url)
|
|
2252
|
+
if (!dir) {
|
|
2253
|
+
if (!url) return missingArg('dir', '给本地站点产物目录(dir,推荐),或给网址(url)——注意 CLI 的 seo audit 只做本地目录体检、不联网', 'seo audit')
|
|
2254
|
+
return {
|
|
2255
|
+
ok: false,
|
|
2256
|
+
code: -1,
|
|
2257
|
+
command: 'seo audit',
|
|
2258
|
+
supported: false,
|
|
2259
|
+
executed: false,
|
|
2260
|
+
needsConfirm: false,
|
|
2261
|
+
reason: '',
|
|
2262
|
+
gap: {
|
|
2263
|
+
code: 'URL_AUDIT_UNSUPPORTED',
|
|
2264
|
+
message: '`wkgeo seo audit` 只接受本地产物目录:CLI 这一层不联网,拿到网址也不会去抓页面。',
|
|
2265
|
+
instead: '先把网址落成本地目录再传 dir:wkgeo template init --mode url --url ' + url + ' --key <key> --out <目录>(Chrome 抓站镜像),或直接用已发布的产物目录(通常是站点的 dist 目录)。',
|
|
2266
|
+
},
|
|
2267
|
+
stderr: '',
|
|
2268
|
+
error: '只给 url 不能体检:`wkgeo seo audit` 只做本地产物目录、不联网(没有假装抓取成功)',
|
|
2269
|
+
errorCode: 'URL_AUDIT_UNSUPPORTED',
|
|
2270
|
+
errorHint: '用 dir 指向本地站点产物目录;要按网址体检就先把网址落成本地目录(wkgeo_template_init 的 mode=url),再传 dir。',
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
const res = await runCli(buildSeoAuditArgs({ dir }))
|
|
2274
|
+
return normalizeCliResult(res, { command: 'seo audit' })
|
|
2275
|
+
},
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2278
|
+
return [sitesList, siteStatus, sitePreview, publish, siteCreate, templateFromUrl, templateFromDir, contentApply, contentFill, contentCheck, templatesList, templateInit, templateValidate, templatePack, templatePublish, domainsList, geoAudit, geoLlms, geoRobots, geoBrands, seoAudit]
|
|
1976
2279
|
}
|
|
1977
2280
|
|
|
1978
2281
|
// ---------------------------------------------------------------------------
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-wkgeo-ops",
|
|
3
|
-
"version": "0.2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.4",
|
|
4
|
+
"description": "AI 时代的一站式网站运营工具:AI 建站、老网站一键导入、GEO·SEO 结构自动生成。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
|
+
"icon": "assets/icon.png",
|
|
7
8
|
"files": [
|
|
8
9
|
"lib",
|
|
9
10
|
"client",
|
|
10
11
|
"skill",
|
|
11
12
|
"skills",
|
|
13
|
+
"assets",
|
|
14
|
+
"locale",
|
|
12
15
|
"cordis.patch.yml",
|
|
13
16
|
"README.md",
|
|
14
17
|
"LICENSE"
|
|
@@ -36,6 +39,9 @@
|
|
|
36
39
|
".": { "default": "./lib/index.js" },
|
|
37
40
|
"./client": "./client/client.js",
|
|
38
41
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
42
|
+
"./locale/*.json": "./locale/*.json",
|
|
43
|
+
"./lib/*": "./lib/*",
|
|
44
|
+
"./assets/*": "./assets/*",
|
|
39
45
|
"./package.json": "./package.json"
|
|
40
46
|
},
|
|
41
47
|
"peerDependencies": { "@deepseek-ai/cordis": "^4.0.1" }
|