geo-ai-search-optimization 1.2.4 → 1.2.6

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 CHANGED
@@ -24,6 +24,23 @@ npx geo-ai-search-optimization
24
24
  - installs the bundled GEO skills into your Codex skills directory
25
25
  - ships a local resource folder with `SKILL.md`, references, and a scanner script
26
26
  - provides a CLI for installing, locating, and scanning projects for GEO signals
27
+ - now exposes the bundled skill package directly, so agents can discover which GEO sub-skill to use next
28
+
29
+ ## Skills 命令
30
+
31
+ 如果你想先看这个 npm 包里到底打包了哪些 skills,现在可以直接用:
32
+
33
+ ```bash
34
+ geo-ai-search-optimization skills
35
+ geo-ai-search-optimization skills --json
36
+ ```
37
+
38
+ 它会列出:
39
+
40
+ - 核心 GEO skill
41
+ - usage / onboarding skill
42
+ - agent 执行闭环相关 skills
43
+ - 分享 / 导出 / 最终交付相关 skills
27
44
 
28
45
  ## Quick Start
29
46
 
@@ -261,6 +278,24 @@ geo-ai-search-optimization html-pack https://example.com --out-dir ./exports/exa
261
278
  - `agent.html`
262
279
  - `share.html`
263
280
 
281
+ ## Publish Pack 命令
282
+
283
+ 如果你希望直接生成一份“最终可交付包”,同时给人和 agent 使用,现在可以用 `publish-pack`:
284
+
285
+ ```bash
286
+ geo-ai-search-optimization publish-pack ./your-site --out-dir ./exports/your-site-publish-pack
287
+ geo-ai-search-optimization publish-pack https://example.com --out-dir ./exports/example-publish-pack
288
+ ```
289
+
290
+ `publish-pack` 会一次生成:
291
+
292
+ - `site/`: 可浏览的静态 HTML 页面
293
+ - `documents/`: 给 PM、工程、管理层直接转发的 Markdown 文档
294
+ - `data/`: 给 agent 或自动化流程使用的 JSON 工件
295
+ - `START-HERE.md`: 给人的起始说明
296
+ - `AGENT-START.md`: 给 agent 的执行入口
297
+ - `manifest.json`: 机器可读总索引
298
+
264
299
  ## Fix Plan 命令
265
300
 
266
301
  如果你已经跑过 `audit`、`report` 或 `onboard-url`,下一步就可以直接把结果转成 PM 待办清单:
@@ -400,6 +435,7 @@ geo-ai-search-optimization onboard --url https://example.com --json --out ./repo
400
435
  geo-ai-search-optimization
401
436
  geo-ai-search-optimization install
402
437
  geo-ai-search-optimization install --target ./tmp/custom-skills --json
438
+ geo-ai-search-optimization skills
403
439
  geo-ai-search-optimization where
404
440
  geo-ai-search-optimization doctor
405
441
  geo-ai-search-optimization quick-start
@@ -418,6 +454,7 @@ geo-ai-search-optimization handoff-bundle ./my-site
418
454
  geo-ai-search-optimization share-pack ./my-site
419
455
  geo-ai-search-optimization export-pack ./my-site --out-dir ./exports/my-site-pack
420
456
  geo-ai-search-optimization html-pack ./my-site --out-dir ./exports/my-site-html
457
+ geo-ai-search-optimization publish-pack ./my-site --out-dir ./exports/my-site-publish-pack
421
458
  geo-ai-search-optimization exec-summary ./my-site
422
459
  geo-ai-search-optimization fix-plan ./my-site
423
460
  geo-ai-search-optimization owner-board ./my-site
@@ -433,6 +470,20 @@ geo-ai-search-optimization version
433
470
  geo-ai-search-optimization help
434
471
  ```
435
472
 
473
+ ## New in 1.2.6
474
+
475
+ - 新增 `skills` 命令,直接列出整套 GEO 技能包
476
+ - 安装后会额外写入 `.skill-bundle.json`,方便 agent 自动发现技能
477
+ - 为缺失的 skill 补齐 `agents/openai.yaml`
478
+ - 主 skill 新增 `skill-bundle-map` 参考,usage skill 也同步纳入 `publish-pack` 和 `skills`
479
+
480
+ ## New in 1.2.5
481
+
482
+ - 新增 `publish-pack`
483
+ - 一次生成最终可交付目录,而不是手动拼 HTML、Markdown、JSON
484
+ - 增加 `START-HERE.md`、`AGENT-START.md`、`manifest.json`
485
+ - 新增 `geo-ai-search-optimization-publish-pack` skill,帮助 agent 直接接手最终交付包
486
+
436
487
  ## New in 1.2.4
437
488
 
438
489
  - 新增 `html-pack`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "geo-ai-search-optimization",
3
- "version": "1.2.4",
3
+ "version": "1.2.6",
4
4
  "description": "Install and run a Generative Engine Optimization (GEO)-first, SEO-supported Codex skill for website optimization.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -163,3 +163,4 @@ When the user asks for the latest vendor behavior or policy:
163
163
  - `references/quick-start.md`: from-0-to-1 startup sequence for using the CLI and the skill together
164
164
  - `references/geo-playbook.md`: content patterns and GEO-first page design heuristics
165
165
  - `references/implementation-checklist.md`: technical, content, and measurement checklist
166
+ - `references/skill-bundle-map.md`: bundled GEO sub-skills and when to use each one
@@ -0,0 +1,81 @@
1
+ # GEO Skill Bundle Map
2
+
3
+ ## Table of Contents
4
+
5
+ - Core skill
6
+ - Usage guide
7
+ - Agent execution loop
8
+ - Delivery chain
9
+
10
+ ## Core skill
11
+
12
+ ### `geo-ai-search-optimization`
13
+
14
+ Use this first when the task is still about diagnosing or improving GEO readiness itself.
15
+
16
+ Best for:
17
+
18
+ - auditing a website or codebase
19
+ - improving answerability, structure, and citation readiness
20
+ - deciding what the highest-impact GEO fixes are
21
+
22
+ ## Usage guide
23
+
24
+ ### `geo-ai-search-optimization-usage`
25
+
26
+ Use this when the next agent or user does not know which command or skill to use next.
27
+
28
+ Best for:
29
+
30
+ - onboarding new users
31
+ - explaining command order
32
+ - translating a GEO request into concrete CLI steps
33
+
34
+ ## Agent execution loop
35
+
36
+ Use these when the work is moving from analysis into action.
37
+
38
+ ### `geo-ai-search-optimization-agent-handoff`
39
+
40
+ Turn GEO findings into a package that another agent can continue from.
41
+
42
+ ### `geo-ai-search-optimization-repair-loop`
43
+
44
+ Continue from `apply-plan` style execution tasks and close the loop on actual fixes.
45
+
46
+ ### `geo-ai-search-optimization-completion-report`
47
+
48
+ Summarize what finished, what still blocks progress, and what should happen next.
49
+
50
+ ### `geo-ai-search-optimization-handoff-bundle`
51
+
52
+ Use when one artifact needs to contain handoff, execution, and closeout together.
53
+
54
+ ## Delivery chain
55
+
56
+ Use these when the output needs to be shared, exported, or handed off to humans and agents.
57
+
58
+ ### `geo-ai-search-optimization-share-pack`
59
+
60
+ Prepare role-specific views for PM, engineering, management, and the next agent.
61
+
62
+ ### `geo-ai-search-optimization-export-pack`
63
+
64
+ Generate a disk folder with ready-to-send markdown or JSON deliverables.
65
+
66
+ ### `geo-ai-search-optimization-html-pack`
67
+
68
+ Generate browsable HTML pages for quick sharing.
69
+
70
+ ### `geo-ai-search-optimization-publish-pack`
71
+
72
+ Generate the final delivery folder that combines:
73
+
74
+ - `site/` HTML pages
75
+ - `documents/` markdown files
76
+ - `data/` JSON artifacts
77
+ - `START-HERE.md`
78
+ - `AGENT-START.md`
79
+ - `manifest.json`
80
+
81
+ This is the best choice when the output should be directly handed to a PM, a stakeholder group, or another agent without further manual packaging.
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GEO Export Pack"
3
+ short_description: "Export GEO deliverables into a shareable folder"
4
+ default_prompt: "Use $geo-ai-search-optimization-export-pack to generate a folder of GEO deliverables for PM, engineering, management, and agent handoff."
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GEO Handoff Bundle"
3
+ short_description: "Continue from one bundled GEO execution artifact"
4
+ default_prompt: "Use $geo-ai-search-optimization-handoff-bundle to continue from this combined GEO handoff, execution, and closeout artifact."
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GEO HTML Pack"
3
+ short_description: "Generate browsable HTML pages from GEO outputs"
4
+ default_prompt: "Use $geo-ai-search-optimization-html-pack to convert these GEO deliverables into browsable HTML pages for sharing."
@@ -0,0 +1,26 @@
1
+ ---
2
+ name: geo-ai-search-optimization-publish-pack
3
+ description: Generate a final GEO delivery package that combines HTML pages, markdown documents, JSON artifacts, and agent start files. Use when a PM or agent wants one folder that can be shared directly with humans and handed off directly to another agent.
4
+ ---
5
+
6
+ # GEO Publish Pack
7
+
8
+ Use this skill when the user wants a single final package instead of separate GEO artifacts.
9
+
10
+ `GEO = Generative Engine Optimization`
11
+
12
+ ## What it generates
13
+
14
+ - `site/` for browsable HTML pages
15
+ - `documents/` for markdown files that can be sent to PM, engineering, or management
16
+ - `data/` for machine-readable JSON artifacts
17
+ - `START-HERE.md` for people
18
+ - `AGENT-START.md` for the next agent
19
+ - `manifest.json` for automation and indexing
20
+
21
+ ## Best use
22
+
23
+ - generate one delivery folder at the end of an analysis round
24
+ - share `site/index.html` for quick browsing
25
+ - point the next agent to `AGENT-START.md`
26
+ - use `manifest.json` when another tool needs to ingest the package
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GEO Publish Pack"
3
+ short_description: "Build a final delivery folder for humans and agents"
4
+ default_prompt: "Use $geo-ai-search-optimization-publish-pack to generate a final GEO delivery package with HTML, markdown, JSON, and an agent handoff entrypoint."
@@ -0,0 +1,4 @@
1
+ interface:
2
+ display_name: "GEO Share Pack"
3
+ short_description: "Prepare audience-specific GEO views for sharing"
4
+ default_prompt: "Use $geo-ai-search-optimization-share-pack to turn this GEO result into role-specific views for PM, engineering, management, and the next agent."
@@ -13,20 +13,21 @@ Treat this tool as a PM-friendly GEO workflow for websites.
13
13
 
14
14
  `GEO = Generative Engine Optimization`
15
15
 
16
- The package is best explained as twelve layers:
17
-
18
- 1. `onboard-url` / `onboard`: first look
19
- 2. `scan`: raw signal check
20
- 3. `audit` / `report`: diagnosis
21
- 4. `fix-plan` / `owner-board`: execution planning
22
- 5. `agent-handoff`: agent takeover package
23
- 6. `apply-plan`: execution loop
24
- 7. `completion-report`: closeout
25
- 8. `handoff-bundle`: all-in-one package
26
- 9. `share-pack`: audience-ready delivery
27
- 10. `export-pack`: folder export
28
- 11. `html-pack`: browsable output
29
- 12. `pm-brief` / `roadmap`: stakeholder alignment
16
+ The package is best explained as thirteen layers:
17
+
18
+ 1. `skills`: inspect the bundled skill package
19
+ 2. `onboard-url` / `onboard`: first look
20
+ 3. `scan`: raw signal check
21
+ 4. `audit` / `report`: diagnosis
22
+ 5. `fix-plan` / `owner-board`: execution planning
23
+ 6. `agent-handoff`: agent takeover package
24
+ 7. `apply-plan`: execution loop
25
+ 8. `completion-report`: closeout
26
+ 9. `handoff-bundle`: all-in-one package
27
+ 10. `share-pack`: audience-ready delivery
28
+ 11. `export-pack`: folder export
29
+ 12. `html-pack` / `publish-pack`: browsable and final delivery output
30
+ 13. `pm-brief` / `roadmap`: stakeholder alignment
30
31
 
31
32
  ## Recommended command order
32
33
 
@@ -51,6 +52,7 @@ npx geo-ai-search-optimization handoff-bundle ./your-site
51
52
  npx geo-ai-search-optimization share-pack ./your-site
52
53
  npx geo-ai-search-optimization export-pack ./your-site
53
54
  npx geo-ai-search-optimization html-pack ./your-site
55
+ npx geo-ai-search-optimization publish-pack ./your-site
54
56
  npx geo-ai-search-optimization owner-board ./your-site
55
57
  npx geo-ai-search-optimization roadmap ./your-site
56
58
  ```
@@ -59,6 +61,7 @@ npx geo-ai-search-optimization roadmap ./your-site
59
61
 
60
62
  - `onboard-url`: first-time website check from a live URL
61
63
  - `onboard`: interactive first-time onboarding
64
+ - `skills`: list the bundled skills and decide which skill or command chain fits the task
62
65
  - `scan`: check raw technical signals across files
63
66
  - `audit`: get GEO score, issue areas, blockers, and PM action list
64
67
  - `report`: export audit/onboarding/scan into readable files
@@ -70,6 +73,7 @@ npx geo-ai-search-optimization roadmap ./your-site
70
73
  - `share-pack`: prepare role-specific shareable outputs for PM, engineering, management, and agents
71
74
  - `export-pack`: generate a folder with multiple ready-to-send files
72
75
  - `html-pack`: generate static HTML pages for sharing and browsing
76
+ - `publish-pack`: generate the final delivery folder for humans and agents together
73
77
  - `owner-board`: group tasks by PM / engineering / SEO / content
74
78
  - `pm-brief`: prepare a PM summary for meetings and prioritization
75
79
  - `roadmap`: turn tasks into a 2-week / 4-week execution sequence
@@ -88,6 +92,7 @@ When explaining the tool to a user:
88
92
  - if the user wants something ready to forward to multiple audiences, move them to `share-pack`
89
93
  - if the user wants actual files on disk for each audience, move them to `export-pack`
90
94
  - if the user wants browsable HTML pages, move them to `html-pack`
95
+ - if the user wants one final package with HTML, markdown, JSON, and an agent entrypoint, move them to `publish-pack`
91
96
  - if the user already has a report, move them to `fix-plan`, `owner-board`, or `roadmap`
92
97
 
93
98
  Read [references/usage-patterns.md](references/usage-patterns.md) when you need response patterns and command selection examples.
package/src/cli.js CHANGED
@@ -22,6 +22,7 @@ import { renderHtmlPackMarkdown, writeHtmlPack } from "./html-pack.js";
22
22
  import { createOwnerBoard, renderOwnerBoardMarkdown, writeOwnerBoardOutput } from "./owner-board.js";
23
23
  import { createMeetingPack, renderMeetingPackMarkdown, writeMeetingPackOutput } from "./meeting-pack.js";
24
24
  import { createPmBrief, renderPmBriefMarkdown, writePmBriefOutput } from "./pm-brief.js";
25
+ import { renderPublishPackMarkdown, writePublishPack } from "./publish-pack.js";
25
26
  import { generateReport, writeReportOutput } from "./report.js";
26
27
  import { createRoadmap, renderRoadmapMarkdown, writeRoadmapOutput } from "./roadmap.js";
27
28
  import {
@@ -32,6 +33,7 @@ import {
32
33
  import { createQuickStartPlan, renderQuickStartMarkdown, writeQuickStartOutput } from "./quick-start.js";
33
34
  import { createSchemaTemplate } from "./schema.js";
34
35
  import { createSharePack, renderSharePackMarkdown, writeSharePackOutput } from "./share-pack.js";
36
+ import { listBundledSkills, renderBundledSkillsMarkdown } from "./skills.js";
35
37
  import { analyzeWebsiteUrl, renderWebsiteOnboardingMarkdown, writeWebsiteOnboardingOutput } from "./url-onboarding.js";
36
38
 
37
39
  let cachedVersion;
@@ -55,6 +57,7 @@ function printHelp() {
55
57
  "Usage:",
56
58
  " geo-ai-search-optimization",
57
59
  " geo-ai-search-optimization install [--target <dir>] [--json]",
60
+ " geo-ai-search-optimization skills [--json]",
58
61
  " geo-ai-search-optimization where",
59
62
  " geo-ai-search-optimization doctor [--json]",
60
63
  " geo-ai-search-optimization quick-start [--json] [--out <file>]",
@@ -70,6 +73,7 @@ function printHelp() {
70
73
  " geo-ai-search-optimization share-pack <input> [--task <id>] [--format <markdown|json>] [--out <file>]",
71
74
  " geo-ai-search-optimization export-pack <input> [--task <id>] [--format <markdown|json>] [--out-dir <dir>]",
72
75
  " geo-ai-search-optimization html-pack <input> [--task <id>] [--out-dir <dir>]",
76
+ " geo-ai-search-optimization publish-pack <input> [--task <id>] [--out-dir <dir>]",
73
77
  " geo-ai-search-optimization exec-summary <input> [--format <markdown|json>] [--out <file>]",
74
78
  " geo-ai-search-optimization fix-plan <input> [--format <markdown|json>] [--out <file>]",
75
79
  " geo-ai-search-optimization owner-board <input> [--format <markdown|json>] [--out <file>]",
@@ -134,6 +138,16 @@ function handleWhere() {
134
138
  );
135
139
  }
136
140
 
141
+ async function handleSkills(args) {
142
+ const bundle = await listBundledSkills();
143
+ if (hasFlag(args, "--json")) {
144
+ process.stdout.write(`${JSON.stringify(bundle, null, 2)}\n`);
145
+ return;
146
+ }
147
+
148
+ process.stdout.write(renderBundledSkillsMarkdown(bundle));
149
+ }
150
+
137
151
  async function handleDoctor(args) {
138
152
  const report = await runDoctor();
139
153
  if (hasFlag(args, "--json")) {
@@ -506,6 +520,20 @@ async function handleHtmlPack(args) {
506
520
  process.stdout.write(renderHtmlPackMarkdown(pack));
507
521
  }
508
522
 
523
+ async function handlePublishPack(args) {
524
+ const input = args.find((value) => !value.startsWith("-"));
525
+ if (!input) {
526
+ throw new Error("publish-pack 需要一个输入值,可以是项目路径、网站网址或已导出的 JSON 工件");
527
+ }
528
+
529
+ const pack = await writePublishPack(input, {
530
+ taskId: getFlagValue(args, "--task"),
531
+ outputDir: getFlagValue(args, "--out-dir")
532
+ });
533
+
534
+ process.stdout.write(renderPublishPackMarkdown(pack));
535
+ }
536
+
509
537
  async function handleExecSummary(args) {
510
538
  const input = args.find((value) => !value.startsWith("-"));
511
539
  if (!input) {
@@ -639,6 +667,11 @@ export async function runCli(args = []) {
639
667
  return;
640
668
  }
641
669
 
670
+ if (command === "skills") {
671
+ await handleSkills(rest);
672
+ return;
673
+ }
674
+
642
675
  if (command === "where") {
643
676
  handleWhere();
644
677
  return;
@@ -714,6 +747,11 @@ export async function runCli(args = []) {
714
747
  return;
715
748
  }
716
749
 
750
+ if (command === "publish-pack") {
751
+ await handlePublishPack(rest);
752
+ return;
753
+ }
754
+
717
755
  if (command === "exec-summary") {
718
756
  await handleExecSummary(rest);
719
757
  return;
package/src/index.js CHANGED
@@ -20,10 +20,12 @@ export { createExportPack, renderExportPackMarkdown, writeExportPack } from "./e
20
20
  export { createMeetingPack, renderMeetingPackMarkdown, writeMeetingPackOutput } from "./meeting-pack.js";
21
21
  export { createOwnerBoard, renderOwnerBoardMarkdown, writeOwnerBoardOutput } from "./owner-board.js";
22
22
  export { createPmBrief, renderPmBriefMarkdown, writePmBriefOutput } from "./pm-brief.js";
23
+ export { renderPublishPackMarkdown, writePublishPack } from "./publish-pack.js";
23
24
  export { createQuickStartPlan, renderQuickStartMarkdown, writeQuickStartOutput } from "./quick-start.js";
24
25
  export { generateReport, writeReportOutput } from "./report.js";
25
26
  export { createRoadmap, renderRoadmapMarkdown, writeRoadmapOutput } from "./roadmap.js";
26
27
  export { createSchemaTemplate } from "./schema.js";
27
28
  export { scanProject, renderScanMarkdown, writeScanOutput } from "./scan.js";
28
29
  export { createSharePack, renderSharePackMarkdown, writeSharePackOutput } from "./share-pack.js";
30
+ export { listBundledSkills, renderBundledSkillsMarkdown } from "./skills.js";
29
31
  export { analyzeWebsiteUrl, renderWebsiteOnboardingMarkdown, writeWebsiteOnboardingOutput } from "./url-onboarding.js";
@@ -1,6 +1,7 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { getBundledSkillPath, getInstalledSkillPath, getSkillName } from "./paths.js";
4
+ import { listBundledSkills } from "./skills.js";
4
5
 
5
6
  async function pathExists(targetPath) {
6
7
  try {
@@ -11,10 +12,18 @@ async function pathExists(targetPath) {
11
12
  }
12
13
  }
13
14
 
15
+ function resolveInstalledSkillPath(skillName, targetDir) {
16
+ if (skillName === getSkillName()) {
17
+ return targetDir;
18
+ }
19
+ return path.join(path.dirname(targetDir), skillName);
20
+ }
21
+
14
22
  export async function installSkill(options = {}) {
15
23
  const sourceDir = getBundledSkillPath();
16
24
  const targetDir = options.targetDir || getInstalledSkillPath();
17
25
  const silent = Boolean(options.silent);
26
+ const bundle = await listBundledSkills();
18
27
 
19
28
  if (!(await pathExists(sourceDir))) {
20
29
  throw new Error(`Bundled skill folder not found: ${sourceDir}`);
@@ -48,6 +57,14 @@ export async function installSkill(options = {}) {
48
57
  const manifest = {
49
58
  skill: getSkillName(),
50
59
  installedSkills,
60
+ installedSkillDetails: bundle.skills
61
+ .filter((skill) => installedSkills.includes(skill.name))
62
+ .map((skill) => ({
63
+ name: skill.name,
64
+ displayName: skill.displayName,
65
+ shortDescription: skill.shortDescription,
66
+ installPath: resolveInstalledSkillPath(skill.name, targetDir)
67
+ })),
51
68
  installedAt: new Date().toISOString(),
52
69
  installedFrom: sourceDir,
53
70
  packageName: "geo-ai-search-optimization",
@@ -60,6 +77,30 @@ export async function installSkill(options = {}) {
60
77
  "utf8"
61
78
  );
62
79
 
80
+ const bundleManifestPath = path.join(targetDir, ".skill-bundle.json");
81
+ await fs.writeFile(
82
+ bundleManifestPath,
83
+ `${JSON.stringify(
84
+ {
85
+ kind: bundle.kind,
86
+ totalSkills: bundle.totalSkills,
87
+ groups: bundle.groups.map((group) => ({
88
+ category: group.category,
89
+ label: group.label,
90
+ skills: group.skills.map((skill) => ({
91
+ name: skill.name,
92
+ displayName: skill.displayName,
93
+ shortDescription: skill.shortDescription,
94
+ installPath: resolveInstalledSkillPath(skill.name, targetDir)
95
+ }))
96
+ }))
97
+ },
98
+ null,
99
+ 2
100
+ )}\n`,
101
+ "utf8"
102
+ );
103
+
63
104
  if (!silent) {
64
105
  process.stdout.write(`Installed skills (${installedSkills.join(", ")}) to ${path.dirname(targetDir)}\n`);
65
106
  }
@@ -69,6 +110,7 @@ export async function installSkill(options = {}) {
69
110
  sourceDir,
70
111
  skillName: getSkillName(),
71
112
  installedSkills,
72
- manifest
113
+ manifest,
114
+ bundleManifestPath
73
115
  };
74
116
  }
@@ -0,0 +1,420 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createExecSummary, renderExecSummaryMarkdown } from "./exec-summary.js";
4
+ import { createHandoffBundle, renderHandoffBundleMarkdown } from "./handoff-bundle.js";
5
+ import { createOwnerBoard, renderOwnerBoardMarkdown } from "./owner-board.js";
6
+ import { createPmBrief, renderPmBriefMarkdown } from "./pm-brief.js";
7
+ import { createSharePack, renderSharePackMarkdown } from "./share-pack.js";
8
+
9
+ function sanitizeSegment(value) {
10
+ return String(value)
11
+ .replace(/[^a-z0-9-_]+/gi, "-")
12
+ .replace(/^-+|-+$/g, "")
13
+ .toLowerCase();
14
+ }
15
+
16
+ function buildBaseName(sourceType, source) {
17
+ if (sourceType === "url") {
18
+ try {
19
+ const hostname = new URL(source).hostname || "website";
20
+ return sanitizeSegment(hostname) || "website";
21
+ } catch {
22
+ return "website";
23
+ }
24
+ }
25
+
26
+ return sanitizeSegment(path.basename(source)) || "site";
27
+ }
28
+
29
+ function escapeHtml(value) {
30
+ return String(value)
31
+ .replaceAll("&", "&amp;")
32
+ .replaceAll("<", "&lt;")
33
+ .replaceAll(">", "&gt;")
34
+ .replaceAll('"', "&quot;")
35
+ .replaceAll("'", "&#39;");
36
+ }
37
+
38
+ function wrapHtml(title, body) {
39
+ return [
40
+ "<!doctype html>",
41
+ '<html lang="zh-Hant">',
42
+ "<head>",
43
+ '<meta charset="utf-8">',
44
+ '<meta name="viewport" content="width=device-width, initial-scale=1">',
45
+ `<title>${escapeHtml(title)}</title>`,
46
+ "<style>",
47
+ "body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;margin:0;background:#f5f7fb;color:#16324f;}",
48
+ ".shell{max-width:1100px;margin:0 auto;padding:32px 20px 64px;}",
49
+ ".hero,.card{background:#fff;border:1px solid #d9e2ec;border-radius:16px;box-shadow:0 8px 24px rgba(15,23,42,.06);}",
50
+ ".hero{padding:28px;margin-bottom:24px;}",
51
+ ".grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px;}",
52
+ ".card{padding:20px;}",
53
+ "h1,h2,h3{margin-top:0;color:#102a43;}",
54
+ "ul{padding-left:20px;line-height:1.7;}",
55
+ "a{color:#0b7285;text-decoration:none;}",
56
+ "code,pre{background:#f4f7fb;border-radius:10px;}",
57
+ "pre{padding:16px;white-space:pre-wrap;overflow:auto;}",
58
+ ".meta{display:flex;gap:12px;flex-wrap:wrap;color:#486581;}",
59
+ "</style>",
60
+ "</head>",
61
+ "<body>",
62
+ body,
63
+ "</body>",
64
+ "</html>"
65
+ ].join("");
66
+ }
67
+
68
+ function buildDocumentHtml(title, content) {
69
+ return wrapHtml(
70
+ title,
71
+ `
72
+ <main class="shell">
73
+ <section class="hero">
74
+ <h1>${escapeHtml(title)}</h1>
75
+ </section>
76
+ <section class="card">
77
+ <pre>${escapeHtml(content)}</pre>
78
+ </section>
79
+ </main>
80
+ `
81
+ );
82
+ }
83
+
84
+ function buildIndexHtml(pack) {
85
+ const cards = pack.htmlFiles
86
+ .map(
87
+ (file) => `
88
+ <article class="card">
89
+ <h3>${escapeHtml(file.label)}</h3>
90
+ <p>${escapeHtml(file.description)}</p>
91
+ <a href="./${escapeHtml(file.filename)}">打开 ${escapeHtml(file.filename)}</a>
92
+ </article>
93
+ `
94
+ )
95
+ .join("");
96
+
97
+ return wrapHtml(
98
+ "GEO Publish Pack",
99
+ `
100
+ <main class="shell">
101
+ <section class="hero">
102
+ <h1>GEO Publish Pack</h1>
103
+ <div class="meta">
104
+ <span>来源:${escapeHtml(pack.source)}</span>
105
+ <span>类型:${escapeHtml(pack.sourceType)}</span>
106
+ <span>目录:${escapeHtml(pack.outputDir)}</span>
107
+ </div>
108
+ <p>这是一个适合直接交付、分发与交接的 GEO 最终发布包,覆盖 PM、工程、管理层与下一位 agent。</p>
109
+ </section>
110
+ <section class="grid">
111
+ ${cards}
112
+ </section>
113
+ </main>
114
+ `
115
+ );
116
+ }
117
+
118
+ function buildStartHereContent(pack) {
119
+ return [
120
+ "# GEO Publish Pack",
121
+ "",
122
+ `- 输入来源:\`${pack.source}\``,
123
+ `- 来源类型:\`${pack.sourceType}\``,
124
+ `- 生成时间:${pack.generatedAt}`,
125
+ "",
126
+ "## 这份包怎么用",
127
+ "",
128
+ "1. 人先打开 `site/index.html` 看总览。",
129
+ "2. PM 看 `documents/pm-brief.md` 或 `site/pm.html`。",
130
+ "3. 工程 / 执行团队看 `documents/owner-board.md` 或 `site/engineering.html`。",
131
+ "4. 管理层看 `documents/exec-summary.md` 或 `site/exec.html`。",
132
+ "5. 下一位 agent 从 `AGENT-START.md` 和 `data/agent-handoff-bundle.json` 开始。",
133
+ "",
134
+ "## 目录结构",
135
+ "",
136
+ "- `site/`: 可浏览的静态 HTML 页面。",
137
+ "- `documents/`: 可直接分享的 Markdown 文档。",
138
+ "- `data/`: 给 agent 或自动化流程使用的 JSON 工件。",
139
+ "- `START-HERE.md`: 给人看的起始说明。",
140
+ "- `AGENT-START.md`: 给 agent 的执行入口。",
141
+ "- `manifest.json`: 机器可读的总索引。",
142
+ "",
143
+ "## 快速入口",
144
+ "",
145
+ `- 总览页:\`${pack.quickLinks.indexHtml}\``,
146
+ `- PM:\`${pack.quickLinks.pmDocument}\``,
147
+ `- 工程:\`${pack.quickLinks.engineeringDocument}\``,
148
+ `- 管理层:\`${pack.quickLinks.execDocument}\``,
149
+ `- Agent:\`${pack.quickLinks.agentData}\``,
150
+ "",
151
+ "## 推荐分发顺序",
152
+ ""
153
+ ].concat(pack.sharePack.audienceOrder.map((item) => `- ${item}`)).join("\n") + "\n";
154
+ }
155
+
156
+ function buildAgentStartContent(pack) {
157
+ const firstPacket = pack.handoffBundle.applyPlan.packets[0];
158
+ const firstValidation = firstPacket?.validationCommands ?? [];
159
+ const nextRoundTasks = pack.handoffBundle.completionReport.nextRoundTasks.slice(0, 3);
160
+
161
+ const lines = [
162
+ "# AGENT START",
163
+ "",
164
+ "你现在接手这份 GEO 发布包。",
165
+ "",
166
+ `- 输入来源:\`${pack.source}\``,
167
+ `- 来源类型:\`${pack.sourceType}\``,
168
+ `- 执行模式:\`${pack.handoffBundle.summary.executionMode}\``,
169
+ `- 执行类型:\`${pack.handoffBundle.summary.executionType}\``,
170
+ "",
171
+ "## 先读什么",
172
+ "",
173
+ "1. `data/agent-handoff-bundle.json`",
174
+ "2. `documents/share-pack.md`",
175
+ "3. `documents/owner-board.md`",
176
+ "4. `documents/pm-brief.md`",
177
+ "",
178
+ "## 你现在该做什么",
179
+ ""
180
+ ];
181
+
182
+ if (firstPacket) {
183
+ lines.push(`- 先从任务包 \`${firstPacket.id}\` 开始:${firstPacket.title}`);
184
+ lines.push(`- 负责人建议:${firstPacket.owner}`);
185
+ } else {
186
+ lines.push("- 当前没有具体任务包,请先根据 share-pack 和 owner-board 重新确认优先级。");
187
+ }
188
+
189
+ if (pack.handoffBundle.summary.executionMode === "implementation-ready") {
190
+ lines.push("- 当前已有本地项目上下文,可以直接检查模板、metadata、schema 或内容文件并实施修复。");
191
+ } else if (pack.handoffBundle.summary.executionMode === "advice-only") {
192
+ lines.push("- 当前更适合先给结构化建议,不要假设你已经拥有代码仓库写入权限。");
193
+ lines.push("- 如果用户能提供仓库或 CMS 上下文,请在拿到后重新生成 publish-pack,再进入直接执行。");
194
+ } else {
195
+ lines.push("- 当前输入主要来自已有工件,先根据工件整理修复顺序,再等待更多上下文。");
196
+ }
197
+
198
+ lines.push("", "## 建议先跑的验证命令", "");
199
+
200
+ for (const command of firstValidation) {
201
+ lines.push(`- \`${command}\``);
202
+ }
203
+
204
+ if (firstValidation.length === 0) {
205
+ lines.push("- 当前没有额外验证命令,先检查 handoff bundle 中的任务描述。");
206
+ }
207
+
208
+ lines.push("", "## 下一轮任务", "");
209
+
210
+ for (const task of nextRoundTasks) {
211
+ lines.push(`- ${task.id}|${task.title}|${task.nextAction}`);
212
+ }
213
+
214
+ if (nextRoundTasks.length === 0) {
215
+ lines.push("- 暂无下一轮任务,先根据当前工件确认缺口。");
216
+ }
217
+
218
+ lines.push(
219
+ "",
220
+ "## 完成后怎么回报",
221
+ "",
222
+ "- 完成后更新 completion-report 风格的总结。",
223
+ "- 说明你检查了哪些文件、修改了什么、如何验证、还剩什么风险。",
224
+ "- 如果无法直接修复,要明确说明缺少哪些上下文或权限。"
225
+ );
226
+
227
+ return `${lines.join("\n")}\n`;
228
+ }
229
+
230
+ function buildManifest(pack) {
231
+ return {
232
+ kind: "geo-publish-pack",
233
+ generatedAt: pack.generatedAt,
234
+ source: pack.source,
235
+ sourceType: pack.sourceType,
236
+ outputDir: pack.outputDir,
237
+ directories: {
238
+ site: pack.siteDir,
239
+ documents: pack.documentsDir,
240
+ data: pack.dataDir
241
+ },
242
+ quickLinks: pack.quickLinks,
243
+ shareAudienceOrder: pack.sharePack.audienceOrder,
244
+ executionMode: pack.handoffBundle.summary.executionMode,
245
+ executionType: pack.handoffBundle.summary.executionType
246
+ };
247
+ }
248
+
249
+ export async function writePublishPack(input, options = {}) {
250
+ const [sharePack, pmBrief, ownerBoard, execSummary, handoffBundle] = await Promise.all([
251
+ createSharePack(input, { format: "json", taskId: options.taskId }),
252
+ createPmBrief(input, { format: "json" }),
253
+ createOwnerBoard(input, { format: "json" }),
254
+ createExecSummary(input, { format: "json" }),
255
+ createHandoffBundle(input, { format: "json", taskId: options.taskId })
256
+ ]);
257
+
258
+ const baseName = buildBaseName(sharePack.sourceType, sharePack.source);
259
+ const outputDir = path.resolve(options.outputDir || `./exports/${baseName}-geo-publish-pack`);
260
+ const documentsDir = path.join(outputDir, "documents");
261
+ const dataDir = path.join(outputDir, "data");
262
+ const siteDir = path.join(outputDir, "site");
263
+
264
+ await fs.mkdir(documentsDir, { recursive: true });
265
+ await fs.mkdir(dataDir, { recursive: true });
266
+ await fs.mkdir(siteDir, { recursive: true });
267
+
268
+ const documentFiles = [
269
+ { key: "share_pack", filename: "share-pack.md", content: renderSharePackMarkdown(sharePack) },
270
+ { key: "pm", filename: "pm-brief.md", content: renderPmBriefMarkdown(pmBrief) },
271
+ { key: "engineering", filename: "owner-board.md", content: renderOwnerBoardMarkdown(ownerBoard) },
272
+ { key: "exec", filename: "exec-summary.md", content: renderExecSummaryMarkdown(execSummary) },
273
+ { key: "agent", filename: "agent-handoff-bundle.md", content: renderHandoffBundleMarkdown(handoffBundle) }
274
+ ];
275
+
276
+ const dataFiles = [
277
+ { key: "share_pack", filename: "share-pack.json", content: `${JSON.stringify(sharePack, null, 2)}\n` },
278
+ { key: "pm", filename: "pm-brief.json", content: `${JSON.stringify(pmBrief, null, 2)}\n` },
279
+ { key: "engineering", filename: "owner-board.json", content: `${JSON.stringify(ownerBoard, null, 2)}\n` },
280
+ { key: "exec", filename: "exec-summary.json", content: `${JSON.stringify(execSummary, null, 2)}\n` },
281
+ { key: "agent", filename: "agent-handoff-bundle.json", content: `${JSON.stringify(handoffBundle, null, 2)}\n` }
282
+ ];
283
+
284
+ const htmlFiles = [
285
+ {
286
+ key: "index",
287
+ label: "总览页",
288
+ description: "适合直接打开给团队看的入口。",
289
+ filename: "index.html",
290
+ content: ""
291
+ },
292
+ {
293
+ key: "pm",
294
+ label: "PM Brief",
295
+ description: "给 PM 的优先级和推进摘要。",
296
+ filename: "pm.html",
297
+ content: documentFiles.find((file) => file.key === "pm")?.content || ""
298
+ },
299
+ {
300
+ key: "engineering",
301
+ label: "Owner Board",
302
+ description: "给工程与执行团队的任务视图。",
303
+ filename: "engineering.html",
304
+ content: documentFiles.find((file) => file.key === "engineering")?.content || ""
305
+ },
306
+ {
307
+ key: "exec",
308
+ label: "Exec Summary",
309
+ description: "给管理层的高层摘要。",
310
+ filename: "exec.html",
311
+ content: documentFiles.find((file) => file.key === "exec")?.content || ""
312
+ },
313
+ {
314
+ key: "agent",
315
+ label: "Agent Bundle",
316
+ description: "给下一位 agent 的交接入口。",
317
+ filename: "agent.html",
318
+ content: documentFiles.find((file) => file.key === "agent")?.content || ""
319
+ },
320
+ {
321
+ key: "share",
322
+ label: "Share Pack",
323
+ description: "给协调者的总览文档。",
324
+ filename: "share.html",
325
+ content: documentFiles.find((file) => file.key === "share_pack")?.content || ""
326
+ }
327
+ ];
328
+
329
+ const generatedAt = new Date().toISOString();
330
+ const quickLinks = {
331
+ startHere: "START-HERE.md",
332
+ agentStart: "AGENT-START.md",
333
+ manifest: "manifest.json",
334
+ indexHtml: "site/index.html",
335
+ pmDocument: "documents/pm-brief.md",
336
+ engineeringDocument: "documents/owner-board.md",
337
+ execDocument: "documents/exec-summary.md",
338
+ agentData: "data/agent-handoff-bundle.json"
339
+ };
340
+
341
+ const pack = {
342
+ kind: "geo-publish-pack",
343
+ generatedAt,
344
+ source: sharePack.source,
345
+ sourceType: sharePack.sourceType,
346
+ outputDir,
347
+ documentsDir,
348
+ dataDir,
349
+ siteDir,
350
+ quickLinks,
351
+ sharePack,
352
+ handoffBundle,
353
+ htmlFiles
354
+ };
355
+
356
+ await Promise.all(
357
+ documentFiles.map((file) => fs.writeFile(path.join(documentsDir, file.filename), file.content, "utf8"))
358
+ );
359
+ await Promise.all(dataFiles.map((file) => fs.writeFile(path.join(dataDir, file.filename), file.content, "utf8")));
360
+
361
+ const indexHtml = buildIndexHtml(pack);
362
+ await fs.writeFile(path.join(siteDir, "index.html"), indexHtml, "utf8");
363
+
364
+ for (const file of htmlFiles.filter((file) => file.key !== "index")) {
365
+ await fs.writeFile(path.join(siteDir, file.filename), buildDocumentHtml(file.label, file.content), "utf8");
366
+ }
367
+
368
+ await fs.writeFile(path.join(outputDir, "START-HERE.md"), buildStartHereContent(pack), "utf8");
369
+ await fs.writeFile(path.join(outputDir, "AGENT-START.md"), buildAgentStartContent(pack), "utf8");
370
+
371
+ const manifest = buildManifest(pack);
372
+ await fs.writeFile(path.join(outputDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
373
+
374
+ return {
375
+ ...manifest,
376
+ files: {
377
+ root: [
378
+ path.join(outputDir, "START-HERE.md"),
379
+ path.join(outputDir, "AGENT-START.md"),
380
+ path.join(outputDir, "manifest.json")
381
+ ],
382
+ documents: documentFiles.map((file) => path.join(documentsDir, file.filename)),
383
+ data: dataFiles.map((file) => path.join(dataDir, file.filename)),
384
+ site: htmlFiles.map((file) => path.join(siteDir, file.filename))
385
+ }
386
+ };
387
+ }
388
+
389
+ export function renderPublishPackMarkdown(pack) {
390
+ const lines = [
391
+ "# GEO Publish Pack",
392
+ "",
393
+ `- 输入来源:\`${pack.source}\``,
394
+ `- 来源类型:\`${pack.sourceType}\``,
395
+ `- 输出目录:\`${pack.outputDir}\``,
396
+ `- 生成时间:${pack.generatedAt}`,
397
+ "",
398
+ "## 快速入口",
399
+ "",
400
+ `- 人类入口:\`${path.join(pack.outputDir, pack.quickLinks.startHere)}\``,
401
+ `- Agent 入口:\`${path.join(pack.outputDir, pack.quickLinks.agentStart)}\``,
402
+ `- 静态页面:\`${path.join(pack.outputDir, pack.quickLinks.indexHtml)}\``,
403
+ `- 机器索引:\`${path.join(pack.outputDir, pack.quickLinks.manifest)}\``,
404
+ "",
405
+ "## 目录",
406
+ "",
407
+ `- 文档:\`${pack.directories.documents}\``,
408
+ `- 数据:\`${pack.directories.data}\``,
409
+ `- 页面:\`${pack.directories.site}\``,
410
+ "",
411
+ "## 适合怎么用",
412
+ ""
413
+ ];
414
+
415
+ lines.push("- 先把 `site/index.html` 发给需要快速了解现状的人。");
416
+ lines.push("- 把 `documents/pm-brief.md`、`documents/owner-board.md`、`documents/exec-summary.md` 分别发给不同角色。");
417
+ lines.push("- 让下一位 agent 从 `AGENT-START.md` 和 `data/agent-handoff-bundle.json` 继续接手。");
418
+
419
+ return `${lines.join("\n")}\n`;
420
+ }
package/src/skills.js ADDED
@@ -0,0 +1,169 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { getPackageRoot } from "./paths.js";
4
+
5
+ const SKILL_ORDER = [
6
+ "geo-ai-search-optimization",
7
+ "geo-ai-search-optimization-usage",
8
+ "geo-ai-search-optimization-agent-handoff",
9
+ "geo-ai-search-optimization-repair-loop",
10
+ "geo-ai-search-optimization-completion-report",
11
+ "geo-ai-search-optimization-handoff-bundle",
12
+ "geo-ai-search-optimization-share-pack",
13
+ "geo-ai-search-optimization-export-pack",
14
+ "geo-ai-search-optimization-html-pack",
15
+ "geo-ai-search-optimization-publish-pack"
16
+ ];
17
+
18
+ const SKILL_CATEGORY = {
19
+ "geo-ai-search-optimization": "core",
20
+ "geo-ai-search-optimization-usage": "guidance",
21
+ "geo-ai-search-optimization-agent-handoff": "execution",
22
+ "geo-ai-search-optimization-repair-loop": "execution",
23
+ "geo-ai-search-optimization-completion-report": "execution",
24
+ "geo-ai-search-optimization-handoff-bundle": "execution",
25
+ "geo-ai-search-optimization-share-pack": "delivery",
26
+ "geo-ai-search-optimization-export-pack": "delivery",
27
+ "geo-ai-search-optimization-html-pack": "delivery",
28
+ "geo-ai-search-optimization-publish-pack": "delivery"
29
+ };
30
+
31
+ const CATEGORY_LABELS = {
32
+ core: "核心 GEO 能力",
33
+ guidance: "使用引导",
34
+ execution: "Agent 执行闭环",
35
+ delivery: "交付与分享"
36
+ };
37
+
38
+ function stripQuotes(value) {
39
+ return String(value).trim().replace(/^['"]|['"]$/g, "");
40
+ }
41
+
42
+ function parseYamlBlock(raw) {
43
+ const record = {};
44
+ for (const line of raw.split(/\r?\n/)) {
45
+ const trimmed = line.trim();
46
+ if (!trimmed || trimmed.startsWith("#")) {
47
+ continue;
48
+ }
49
+ const separatorIndex = trimmed.indexOf(":");
50
+ if (separatorIndex === -1) {
51
+ continue;
52
+ }
53
+ const key = trimmed.slice(0, separatorIndex).trim();
54
+ const value = stripQuotes(trimmed.slice(separatorIndex + 1));
55
+ record[key] = value;
56
+ }
57
+ return record;
58
+ }
59
+
60
+ async function readSkillMetadata(skillDir) {
61
+ const skillPath = path.join(skillDir, "SKILL.md");
62
+ const raw = await fs.readFile(skillPath, "utf8");
63
+ const frontmatterMatch = raw.match(/^---\n([\s\S]*?)\n---/);
64
+ const frontmatter = frontmatterMatch ? parseYamlBlock(frontmatterMatch[1]) : {};
65
+ const yamlPath = path.join(skillDir, "agents", "openai.yaml");
66
+
67
+ let interfaceMetadata = {};
68
+ try {
69
+ const yamlRaw = await fs.readFile(yamlPath, "utf8");
70
+ const interfaceMatch = yamlRaw.match(/interface:\n([\s\S]*)/);
71
+ if (interfaceMatch) {
72
+ interfaceMetadata = parseYamlBlock(interfaceMatch[1]);
73
+ }
74
+ } catch {
75
+ interfaceMetadata = {};
76
+ }
77
+
78
+ const directoryName = path.basename(skillDir);
79
+ const name = frontmatter.name || directoryName;
80
+
81
+ return {
82
+ name,
83
+ directoryName,
84
+ description: frontmatter.description || "",
85
+ displayName: interfaceMetadata.display_name || name,
86
+ shortDescription: interfaceMetadata.short_description || frontmatter.description || "",
87
+ defaultPrompt: interfaceMetadata.default_prompt || "",
88
+ category: SKILL_CATEGORY[name] || "delivery",
89
+ skillPath
90
+ };
91
+ }
92
+
93
+ function sortSkills(skills) {
94
+ return [...skills].sort((left, right) => {
95
+ const leftIndex = SKILL_ORDER.indexOf(left.name);
96
+ const rightIndex = SKILL_ORDER.indexOf(right.name);
97
+ if (leftIndex === -1 && rightIndex === -1) {
98
+ return left.name.localeCompare(right.name);
99
+ }
100
+ if (leftIndex === -1) {
101
+ return 1;
102
+ }
103
+ if (rightIndex === -1) {
104
+ return -1;
105
+ }
106
+ return leftIndex - rightIndex;
107
+ });
108
+ }
109
+
110
+ export async function listBundledSkills() {
111
+ const resourcesDir = path.join(getPackageRoot(), "resources");
112
+ const entries = await fs.readdir(resourcesDir, { withFileTypes: true });
113
+ const skillDirs = entries
114
+ .filter((entry) => entry.isDirectory())
115
+ .map((entry) => path.join(resourcesDir, entry.name));
116
+
117
+ const skills = [];
118
+ for (const skillDir of skillDirs) {
119
+ try {
120
+ await fs.access(path.join(skillDir, "SKILL.md"));
121
+ } catch {
122
+ continue;
123
+ }
124
+ skills.push(await readSkillMetadata(skillDir));
125
+ }
126
+
127
+ const ordered = sortSkills(skills);
128
+ const grouped = Object.entries(CATEGORY_LABELS).map(([category, label]) => ({
129
+ category,
130
+ label,
131
+ skills: ordered.filter((skill) => skill.category === category)
132
+ }));
133
+
134
+ return {
135
+ kind: "geo-skill-bundle",
136
+ totalSkills: ordered.length,
137
+ skills: ordered,
138
+ groups: grouped.filter((group) => group.skills.length > 0)
139
+ };
140
+ }
141
+
142
+ export function renderBundledSkillsMarkdown(bundle) {
143
+ const lines = [
144
+ "# GEO Skill Bundle",
145
+ "",
146
+ `- 技能数量:\`${bundle.totalSkills}\``,
147
+ "",
148
+ "## 推荐理解顺序",
149
+ "",
150
+ "- 先看核心 GEO skill。",
151
+ "- 再看 usage skill,知道什么时候该跑哪个命令。",
152
+ "- 如果要交给 agent 执行,再进入 handoff / apply / completion 这一条执行链。",
153
+ "- 如果要产出给团队分发,再进入 share / export / html / publish 这一条交付链。",
154
+ ""
155
+ ];
156
+
157
+ for (const group of bundle.groups) {
158
+ lines.push(`## ${group.label}`, "");
159
+ for (const skill of group.skills) {
160
+ lines.push(`- ${skill.name}`);
161
+ lines.push(` - 显示名:${skill.displayName}`);
162
+ lines.push(` - 说明:${skill.shortDescription}`);
163
+ lines.push(` - 路径:\`${skill.skillPath}\``);
164
+ }
165
+ lines.push("");
166
+ }
167
+
168
+ return `${lines.join("\n")}\n`;
169
+ }