Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,24 @@ jobs:
- name: Render test
run: python scripts/render_test.py ./_ci_themes/ci-smoke --output-dir ./_ci_output
if: matrix.engine == 'jinja2'

check-lib-sync:
name: Check lib/ is in sync with src/
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- name: Set up Node.js
uses: actions/setup-node@v5
with:
node-version: "20"
cache: npm

- name: Install dependencies
run: npm ci

- name: Compile TypeScript
run: npx tsc -p tsconfig.json

- name: Verify lib/ matches committed output
run: git diff --exit-code lib/
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,7 @@ themes/
# 本地示例/草稿(如需提交 examples/,请从 .gitignore 中移除对应路径)
scratch/
tmp/

# Node.js
node_modules/
npm-debug.log*
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

## [Unreleased]

### Added

**2026-08-20 · DSH (DeepSeek Harness) 插件支持**

- 新增 `src/index.ts`:基于官方 `skill-badge` 模式,通过 `ctx.skills.registerProvider()` 注册 `gridea-theme-builder` skill provider,`resourceBase` 指向 bundle 根目录,模型可解析 `references/`、`scripts/`、`assets/` 的相对路径。
- `description` 运行时从 `SKILL.md` frontmatter 自动提取,无需在代码中维护两份。
- 新增 `package.json`、`tsconfig.json`、`cordis.patch.yml`、`overlay.yml` 等插件配置文件。
- 新增 `src/README.md`:DSH 插件安装、测试、卸载的完整文档。
- CI 新增 `check-lib-sync` job:检查 `lib/` 编译产物与 `src/` 源码是否同步。
- 原有 Claude Skill 用法不受影响,所有 Skill 内容文件未改动。

### Fixed / Changed

**2026-08-20 · 开源协议由 GPL-3.0 改为 MIT**
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ CustomConfig 用 index 访问,跑通 validate 和 render 测试。

> `CLAUDE.md` 是 Claude Code 专属的元指令文件,其他 Agent 与人类用户可忽略。

## 作为 DSH 插件使用

本 Skill 也可作为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 插件运行,安装方式和说明详见 [src/README.md](src/README.md)。

## 开发环境

```bash
Expand Down
11 changes: 11 additions & 0 deletions cordis.patch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Register the gridea-theme-builder skill provider in the DSH profile.
#
# Install (from any DSH workspace):
# dsh plugin --profile web add "github:Gridea-Pro/theme-builder-skill"
#
# The plugin's apply() registers a skill provider on ctx.skills;
# tool-skill then publishes the skill to the model-facing catalog.

- insert:
- id: gridea-theme-builder
name: '@gridea-pro/dsh-skill-theme-builder'
112 changes: 112 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Gridea Pro theme builder skill provider for DeepSeek Harness.
*
* Registers the `gridea-theme-builder` skill from the bundled SKILL.md.
* The resourceBase points to the plugin bundle directory so the model can
* resolve references/, scripts/, and assets/ relative paths mentioned in
* the skill body.
*
* Pattern follows the official @deepseek-ai/dsh-skill-badge plugin:
* - registerProvider() with a static SkillProvider
* - list() and get() both read SKILL.md at call time, so edits to the
* frontmatter description and to the body are picked up without a rebuild
*
* @module @gridea-pro/dsh-skill-theme-builder
*/
import { readFile } from 'node:fs/promises';
import { fileURLToPath } from 'node:url';
import { BUNDLED_SKILL_RANK, } from '@deepseek-ai/dsh-skill';
/** Absolute URL to the bundled SKILL.md body file. */
const SKILL_BODY_URL = new URL('../SKILL.md', import.meta.url);
/**
* Directory base for relative resource resolution.
* The model receives this path in the <skill_resources> block and resolves
* references/scripts/assets paths against it.
*/
const RESOURCE_BASE = {
kind: 'directory',
path: fileURLToPath(new URL('../', import.meta.url)),
};
/** Skill is available on both model and user invocation surfaces. */
const INVOCATION = { modelInvocable: true, userInvocable: true };
/** Used only when SKILL.md is unreadable or carries no description. */
const FALLBACK_DESCRIPTION = 'Gridea Pro 博客主题开发专家';
/**
* Split a Markdown file into its frontmatter `description` and its body.
*
* Handles both YAML forms the description may take:
* - block scalar (`>` or `|`) followed by indented lines, folded into one line
* - plain single-line value
*
* The block-scalar branch must not depend on anything following it: in this
* skill `description` is the last frontmatter field, and the closing `---` is
* already consumed by the outer match.
*/
function parseFrontmatter(raw) {
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
if (!match)
return { description: FALLBACK_DESCRIPTION, body: raw };
const frontmatter = match[1];
const body = match[2];
const block = frontmatter.match(/^description:[ \t]*[>|][-+]?[ \t]*\r?\n((?:[ \t]+.*(?:\r?\n|$))+)/m);
if (block) {
const folded = block[1].split(/\r?\n/).map((line) => line.trim()).filter(Boolean).join(' ');
if (folded)
return { description: folded, body };
}
const plain = frontmatter.match(/^description:[ \t]*(\S.*?)[ \t]*$/m);
return { description: plain ? plain[1] : FALLBACK_DESCRIPTION, body };
}
/** Candidate shape shared by list() and get(); `description` is filled in from SKILL.md. */
const CANDIDATE = {
name: 'gridea-theme-builder',
invocation: INVOCATION,
provider: 'gridea-theme-builder',
source: 'bundled',
resourceBase: RESOURCE_BASE,
rank: BUNDLED_SKILL_RANK,
locator: SKILL_BODY_URL,
};
const provider = {
name: 'gridea-theme-builder',
/**
* The catalog description is the model's only routing signal — `get()` runs
* only after the model has already chosen this skill — so the full
* frontmatter description (trigger conditions and keywords included) has to
* be resolved here, not deferred to load time.
*
* An unreadable SKILL.md degrades to the fallback description instead of
* throwing, so one broken bundle cannot empty the whole catalog.
*/
async list() {
let description = FALLBACK_DESCRIPTION;
try {
description = parseFrontmatter(await readFile(SKILL_BODY_URL, 'utf8')).description;
}
catch {
// 保底:读不到就用兜底描述,不让整个 skill 目录塌掉
}
return [{ ...CANDIDATE, description }];
},
async get() {
const raw = await readFile(SKILL_BODY_URL, 'utf8');
const { description, body } = parseFrontmatter(raw);
return {
name: CANDIDATE.name,
description,
invocation: CANDIDATE.invocation,
provider: CANDIDATE.provider,
source: CANDIDATE.source,
resourceBase: RESOURCE_BASE,
content: body,
};
},
};
/** Cordis plugin name. */
export const name = 'gridea-theme-builder';
/** Required capability seam: the skills registry. */
export const inject = ['skills'];
/** Register the bundled gridea-theme-builder skill provider on ctx.skills. */
export function apply(ctx) {
ctx.skills.registerProvider(() => provider);
}
22 changes: 22 additions & 0 deletions lib/types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Gridea Pro theme builder skill provider for DeepSeek Harness.
*
* Registers the `gridea-theme-builder` skill from the bundled SKILL.md.
* The resourceBase points to the plugin bundle directory so the model can
* resolve references/, scripts/, and assets/ relative paths mentioned in
* the skill body.
*
* Pattern follows the official @deepseek-ai/dsh-skill-badge plugin:
* - registerProvider() with a static SkillProvider
* - list() and get() both read SKILL.md at call time, so edits to the
* frontmatter description and to the body are picked up without a rebuild
*
* @module @gridea-pro/dsh-skill-theme-builder
*/
import type { Context } from '@deepseek-ai/cordis';
/** Cordis plugin name. */
export declare const name = "gridea-theme-builder";
/** Required capability seam: the skills registry. */
export declare const inject: string[];
/** Register the bundled gridea-theme-builder skill provider on ctx.skills. */
export declare function apply(ctx: Context): void;
20 changes: 20 additions & 0 deletions overlay.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# 本地开发 overlay —— 用 --patch 把插件源码直接插入 Web UI
#
# 用法:
# 1. 复制本文件为 overlay.yml(不要改原文件,或改了也行)
# 2. 把下面 name 改成你机器上 src/index.ts 的绝对路径
# 3. 运行:
#
# Windows: npx @deepseek-ai/dsh web --patch D:/path/to/overlay.yml
# macOS: npx @deepseek-ai/dsh web --patch /Users/you/path/to/overlay.yml
# Linux: npx @deepseek-ai/dsh web --patch /home/you/path/to/overlay.yml
#
# name 格式:
# Windows: file:///D:/theme-builder-skill/src/index.ts (需要 file:// 前缀)
# macOS: /Users/you/theme-builder-skill/src/index.ts (裸路径即可)
# Linux: /home/you/theme-builder-skill/src/index.ts (裸路径即可)
#
# 首次使用前需在项目目录运行 npm install 安装依赖。
- insert:
- id: gridea-theme-builder
name: 'REPLACE_WITH_ABSOLUTE_PATH_TO_SRC/index.ts'
Loading