mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-28 12:49:48 +08:00
Compare commits
4 Commits
v2.5.2
...
c9a50cc908
| Author | SHA1 | Date | |
|---|---|---|---|
| c9a50cc908 | |||
| 348dc82612 | |||
| dba3d732c3 | |||
| 661bdd08ff |
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Jim Liu
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -1387,7 +1387,9 @@ This project was inspired by and builds upon the following open source projects:
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
Unless otherwise noted, this repository is licensed under the [MIT License](./LICENSE).
|
||||||
|
|
||||||
|
Published ClawHub skills follow ClawHub registry rules and are distributed under `MIT-0`. Third-party code and assets retain their original licenses where noted.
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -1388,7 +1388,9 @@ HTTP_PROXY=http://127.0.0.1:7890 HTTPS_PROXY=http://127.0.0.1:7890 /baoyu-danger
|
|||||||
|
|
||||||
## 许可证
|
## 许可证
|
||||||
|
|
||||||
MIT
|
除另有说明外,本仓库采用 [MIT License](./LICENSE) 授权。
|
||||||
|
|
||||||
|
发布到 ClawHub 的 skill 根据 ClawHub registry 规则以 `MIT-0` 分发。第三方代码和素材按其注明的原始许可授权。
|
||||||
|
|
||||||
## Star History
|
## Star History
|
||||||
|
|
||||||
|
|||||||
Generated
+5
@@ -5,6 +5,7 @@
|
|||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "baoyu-skills",
|
"name": "baoyu-skills",
|
||||||
|
"license": "MIT",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
],
|
],
|
||||||
@@ -5018,12 +5019,14 @@
|
|||||||
},
|
},
|
||||||
"packages/baoyu-chrome-cdp": {
|
"packages/baoyu-chrome-cdp": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"bun": ">=1.2.0"
|
"bun": ">=1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"packages/baoyu-codex-imagegen": {
|
"packages/baoyu-codex-imagegen": {
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"codex-imagegen": "src/main.ts"
|
"codex-imagegen": "src/main.ts"
|
||||||
},
|
},
|
||||||
@@ -5033,6 +5036,7 @@
|
|||||||
},
|
},
|
||||||
"packages/baoyu-fetch": {
|
"packages/baoyu-fetch": {
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mozilla/readability": "^0.6.0",
|
"@mozilla/readability": "^0.6.0",
|
||||||
"chrome-launcher": "^1.2.1",
|
"chrome-launcher": "^1.2.1",
|
||||||
@@ -5061,6 +5065,7 @@
|
|||||||
},
|
},
|
||||||
"packages/baoyu-md": {
|
"packages/baoyu-md": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fflate": "^0.8.2",
|
"fflate": "^0.8.2",
|
||||||
"front-matter": "^4.0.2",
|
"front-matter": "^4.0.2",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-skills",
|
"name": "baoyu-skills",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*"
|
"packages/*"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-chrome-cdp",
|
"name": "baoyu-chrome-cdp",
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-codex-imagegen",
|
"name": "baoyu-codex-imagegen",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Generate images via Codex CLI's built-in image_gen tool from non-Codex runtimes (Claude Code, Hermes, …).",
|
"description": "Generate images via Codex CLI's built-in image_gen tool from non-Codex runtimes (Claude Code, Hermes, …).",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-fetch",
|
"name": "baoyu-fetch",
|
||||||
"version": "0.1.2",
|
"version": "0.1.2",
|
||||||
|
"license": "MIT",
|
||||||
"description": "Read URLs into high-quality Markdown or JSON with Chrome CDP and site adapters.",
|
"description": "Read URLs into high-quality Markdown or JSON with Chrome CDP and site adapters.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "baoyu-md",
|
"name": "baoyu-md",
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/index.cjs",
|
"main": "./dist/index.cjs",
|
||||||
"module": "./dist/index.js",
|
"module": "./dist/index.js",
|
||||||
|
|||||||
@@ -456,19 +456,19 @@ export class GeminiClient extends GemMixin {
|
|||||||
const generated_images: GeneratedImage[] = [];
|
const generated_images: GeneratedImage[] = [];
|
||||||
const wants_generated =
|
const wants_generated =
|
||||||
get_nested_value(candidate, [12, 7, 0], null) != null ||
|
get_nested_value(candidate, [12, 7, 0], null) != null ||
|
||||||
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(text);
|
/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/.test(text) ||
|
||||||
|
// Gemini 3 no longer emits the legacy marker in the candidate text; the generated
|
||||||
|
// image arrives in a later response part. Detect it from the raw response instead.
|
||||||
|
/\/gg-dl\//.test(txt) ||
|
||||||
|
/image_generation_content\/\d+/.test(txt);
|
||||||
|
|
||||||
if (wants_generated) {
|
if (wants_generated) {
|
||||||
const image_part = find_generated_image_part(response_json as unknown[], body_index, candidate_index, 1);
|
const image_part = find_generated_image_part(response_json as unknown[], body_index, candidate_index, 1);
|
||||||
const img_body = image_part?.body ?? null;
|
const img_body = image_part?.body ?? null;
|
||||||
|
|
||||||
if (!img_body) {
|
// Not every candidate carries a generated image (e.g. multi-candidate responses).
|
||||||
throw new ImageGenerationError(
|
// If this candidate has no image part, skip extraction instead of failing the whole call.
|
||||||
'Failed to parse generated images. Please update gemini_webapi to the latest version. If the error persists and is caused by the package, please report it on GitHub.',
|
const img_candidate = img_body ? get_nested_value<unknown[]>(img_body, [4, candidate_index], []) : [];
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const img_candidate = get_nested_value<unknown[]>(img_body, [4, candidate_index], []);
|
|
||||||
const finished = get_nested_value<string | null>(img_candidate, [1, 0], null);
|
const finished = get_nested_value<string | null>(img_candidate, [1, 0], null);
|
||||||
if (finished) {
|
if (finished) {
|
||||||
text = finished.replace(/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/g, '').trimEnd();
|
text = finished.replace(/http:\/\/googleusercontent\.com\/image_generation_content\/\d+/g, '').trimEnd();
|
||||||
|
|||||||
@@ -98,3 +98,68 @@ test('parseMarkdown resolves encoded spaces and literal percent image paths', as
|
|||||||
assert.equal(result.contentImages[0]?.localPath, path.join(root, 'Pasted image.png'));
|
assert.equal(result.contentImages[0]?.localPath, path.join(root, 'Pasted image.png'));
|
||||||
assert.equal(result.contentImages[1]?.localPath, path.join(root, '100%.png'));
|
assert.equal(result.contentImages[1]?.localPath, path.join(root, '100%.png'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('parseMarkdown renders CJK-adjacent bold and italics (no literal asterisks)', async (t) => {
|
||||||
|
const root = await makeTempDir('x-md-to-html-cjk-bold-');
|
||||||
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
const markdownPath = path.join(root, 'post.md');
|
||||||
|
const tempDir = path.join(root, 'tmp');
|
||||||
|
await fs.mkdir(tempDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
markdownPath,
|
||||||
|
[
|
||||||
|
'# 标题',
|
||||||
|
'',
|
||||||
|
'分工在变细。**国际大厂卷基础设施,中文项目卷场景落地。**这其实是生态成熟的表现。',
|
||||||
|
'',
|
||||||
|
'半角场景 **Top 10 里平均有 8 个** 项目。',
|
||||||
|
'',
|
||||||
|
'斜体 *数据来源 GitHub* 收尾。',
|
||||||
|
'',
|
||||||
|
'参考 **[docs][d]** 了解更多。',
|
||||||
|
'',
|
||||||
|
'[d]: https://example.com',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await parseMarkdown(markdownPath, { tempDir });
|
||||||
|
|
||||||
|
// Bold directly adjacent to CJK (closing ** followed by CJK) must render.
|
||||||
|
assert.match(result.html, /<strong>国际大厂卷基础设施,中文项目卷场景落地。<\/strong>/);
|
||||||
|
assert.match(result.html, /<strong>Top 10 里平均有 8 个<\/strong>/);
|
||||||
|
// Italics.
|
||||||
|
assert.match(result.html, /<em>数据来源 GitHub<\/em>/);
|
||||||
|
// Reference-style links inside emphasis must render as links, not plain text.
|
||||||
|
assert.match(result.html, /<strong><a href="https:\/\/example\.com" rel="noopener noreferrer nofollow">docs<\/a><\/strong>/);
|
||||||
|
// No literal emphasis delimiters should leak into the output.
|
||||||
|
assert.doesNotMatch(result.html, /\*\*/);
|
||||||
|
assert.doesNotMatch(result.html, /(?<!\*)\*(?!\*)[^*\n]+\*(?!\*)/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('parseMarkdown does not decode author-written literal HTML entities into tags', async (t) => {
|
||||||
|
const root = await makeTempDir('x-md-to-html-entities-');
|
||||||
|
t.after(() => fs.rm(root, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
const markdownPath = path.join(root, 'post.md');
|
||||||
|
const tempDir = path.join(root, 'tmp');
|
||||||
|
await fs.mkdir(tempDir, { recursive: true });
|
||||||
|
await fs.writeFile(
|
||||||
|
markdownPath,
|
||||||
|
[
|
||||||
|
'# 标题',
|
||||||
|
'',
|
||||||
|
'正文中写 <b>literal</b> 想显示字面标签。**加粗**收尾。',
|
||||||
|
'',
|
||||||
|
'代码里写 `<b>` 同样保留。',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await parseMarkdown(markdownPath, { tempDir });
|
||||||
|
|
||||||
|
// CJK-adjacent bold still renders.
|
||||||
|
assert.match(result.html, /<strong>加粗<\/strong>/);
|
||||||
|
// Author-written literal entities must NOT be decoded into real tags.
|
||||||
|
assert.doesNotMatch(result.html, /<b>literal<\/b>/);
|
||||||
|
assert.match(result.html, /<b>literal<\/b>/);
|
||||||
|
});
|
||||||
|
|||||||
@@ -136,6 +136,21 @@ function highlightCode(code: string, lang: string): string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Normalize CJK-adjacent emphasis so `marked` renders it correctly.
|
||||||
|
//
|
||||||
|
// `marked`'s emphasis tokenizer treats a closing `**`/`*` directly followed by a
|
||||||
|
// CJK character as not right-flanking, so it leaves the delimiters literal
|
||||||
|
// (e.g. `**加粗**这` renders as plain text with the asterisks intact). We round-trip
|
||||||
|
// the markdown through `remark-cjk-friendly`, whose stringify serializes the
|
||||||
|
// boundary character as an HTML entity (`这`); the entity is treated as
|
||||||
|
// punctuation by `marked`'s flanking rules, so emphasis parses as expected.
|
||||||
|
//
|
||||||
|
// We deliberately do NOT decode the entities afterward. They are valid HTML
|
||||||
|
// character references that render correctly when the article HTML is pasted into
|
||||||
|
// the X editor, and `marked` only emits them for characters outside the emphasis
|
||||||
|
// span (the boundary char), never inside it. A blanket decode of the rendered
|
||||||
|
// HTML would risk turning author-written literal entities (e.g. `<b>`
|
||||||
|
// meant to display `<b>` as text) into real tags, so we leave them intact.
|
||||||
function preprocessCjkMarkdown(markdown: string): string {
|
function preprocessCjkMarkdown(markdown: string): string {
|
||||||
try {
|
try {
|
||||||
const processor = unified()
|
const processor = unified()
|
||||||
@@ -143,8 +158,7 @@ function preprocessCjkMarkdown(markdown: string): string {
|
|||||||
.use(remarkCjkFriendly)
|
.use(remarkCjkFriendly)
|
||||||
.use(remarkStringify);
|
.use(remarkStringify);
|
||||||
|
|
||||||
const result = String(processor.processSync(markdown));
|
return String(processor.processSync(markdown));
|
||||||
return result.replace(/&#x([0-9A-Fa-f]+);/g, (_, hex: string) => String.fromCodePoint(parseInt(hex, 16)));
|
|
||||||
} catch {
|
} catch {
|
||||||
return markdown;
|
return markdown;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user