mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-15 23:19:48 +08:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bb4f0dc52c |
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
"description": "Skills shared by Baoyu for improving daily work efficiency",
|
||||||
"version": "0.6.0"
|
"version": "0.6.1"
|
||||||
},
|
},
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,6 +71,30 @@ npx -y bun skills/baoyu-gemini-web/scripts/main.ts --promptfiles system.md conte
|
|||||||
2. Add TypeScript in `skills/baoyu-<name>/scripts/`
|
2. Add TypeScript in `skills/baoyu-<name>/scripts/`
|
||||||
3. Add prompt templates in `skills/baoyu-<name>/prompts/` if needed
|
3. Add prompt templates in `skills/baoyu-<name>/prompts/` if needed
|
||||||
4. Register in `marketplace.json` plugins[0].skills array as `./skills/baoyu-<name>`
|
4. Register in `marketplace.json` plugins[0].skills array as `./skills/baoyu-<name>`
|
||||||
|
5. **Add Script Directory section** to SKILL.md (see template below)
|
||||||
|
|
||||||
|
### Script Directory Template
|
||||||
|
|
||||||
|
Every SKILL.md with scripts MUST include this section after Usage:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Script Directory
|
||||||
|
|
||||||
|
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
|
||||||
|
|
||||||
|
**Agent Execution Instructions**:
|
||||||
|
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
|
||||||
|
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
|
||||||
|
3. Replace all `${SKILL_DIR}` in this document with the actual path
|
||||||
|
|
||||||
|
**Script Reference**:
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `scripts/main.ts` | Main entry point |
|
||||||
|
| `scripts/other.ts` | Other functionality |
|
||||||
|
```
|
||||||
|
|
||||||
|
When referencing scripts in workflow sections, use `${SKILL_DIR}/scripts/<name>.ts` so agents can resolve the correct path.
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,20 @@ Style × Layout can be freely combined.
|
|||||||
| Wine, food, business, lifestyle, professional | realistic | cinematic |
|
| Wine, food, business, lifestyle, professional | realistic | cinematic |
|
||||||
| Biography, balanced | classic | mixed |
|
| Biography, balanced | classic | mixed |
|
||||||
|
|
||||||
|
## Script Directory
|
||||||
|
|
||||||
|
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
|
||||||
|
|
||||||
|
**Agent Execution Instructions**:
|
||||||
|
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
|
||||||
|
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
|
||||||
|
3. Replace all `${SKILL_DIR}` in this document with the actual path
|
||||||
|
|
||||||
|
**Script Reference**:
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `scripts/merge-to-pdf.ts` | Merge comic pages into PDF |
|
||||||
|
|
||||||
## File Structure
|
## File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -48,7 +62,8 @@ Style × Layout can be freely combined.
|
|||||||
│ ├── 00-cover.md
|
│ ├── 00-cover.md
|
||||||
│ └── XX-page.md
|
│ └── XX-page.md
|
||||||
├── 00-cover.png
|
├── 00-cover.png
|
||||||
└── XX-page.png
|
├── XX-page.png
|
||||||
|
└── {topic-slug}.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
**Target directory**:
|
**Target directory**:
|
||||||
@@ -99,12 +114,13 @@ For each page (cover + pages):
|
|||||||
|
|
||||||
**Image Generation Skill Selection**:
|
**Image Generation Skill Selection**:
|
||||||
- Check available image generation skills in the environment
|
- Check available image generation skills in the environment
|
||||||
- Adapt parameters based on skill capabilities:
|
|
||||||
- If supports `--promptfiles`: pass prompt files
|
|
||||||
- If supports reference image: pass `characters/characters.png`
|
|
||||||
- If text-only: concatenate prompts into single text
|
|
||||||
- If multiple skills available, ask user preference
|
- If multiple skills available, ask user preference
|
||||||
|
|
||||||
|
**Character Reference Handling**:
|
||||||
|
- If skill supports reference image: pass `characters/characters.png` as reference image
|
||||||
|
- If skill does NOT support reference image: include `characters/characters.md` content in the prompt
|
||||||
|
- This ensures character visual consistency across all pages
|
||||||
|
|
||||||
**Session Management**:
|
**Session Management**:
|
||||||
If the image generation skill supports `--sessionId`:
|
If the image generation skill supports `--sessionId`:
|
||||||
1. Generate a unique session ID at the start (e.g., `comic-{topic-slug}-{timestamp}`)
|
1. Generate a unique session ID at the start (e.g., `comic-{topic-slug}-{timestamp}`)
|
||||||
@@ -113,7 +129,17 @@ If the image generation skill supports `--sessionId`:
|
|||||||
|
|
||||||
3. Report progress after each generation
|
3. Report progress after each generation
|
||||||
|
|
||||||
### Step 5: Completion Report
|
### Step 5: Merge to PDF
|
||||||
|
|
||||||
|
After all images are generated, merge them into a PDF file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npx -y bun ${SKILL_DIR}/scripts/merge-to-pdf.ts <comic-dir>
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates `{topic-slug}.pdf` in the comic directory with all pages as full-page images.
|
||||||
|
|
||||||
|
### Step 6: Completion Report
|
||||||
|
|
||||||
```
|
```
|
||||||
Comic Complete!
|
Comic Complete!
|
||||||
@@ -121,6 +147,7 @@ Title: [title] | Style: [style] | Pages: [count]
|
|||||||
Location: [path]
|
Location: [path]
|
||||||
✓ characters.png
|
✓ characters.png
|
||||||
✓ 00-cover.png ... XX-page.png
|
✓ 00-cover.png ... XX-page.png
|
||||||
|
✓ {topic-slug}.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Style-Specific Guidelines
|
## Style-Specific Guidelines
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
||||||
|
import { join, basename } from "path";
|
||||||
|
import { PDFDocument } from "pdf-lib";
|
||||||
|
|
||||||
|
interface PageInfo {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
index: number;
|
||||||
|
promptPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(): { dir: string; output?: string } {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
let dir = "";
|
||||||
|
let output: string | undefined;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === "--output" || args[i] === "-o") {
|
||||||
|
output = args[++i];
|
||||||
|
} else if (!args[i].startsWith("-")) {
|
||||||
|
dir = args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dir) {
|
||||||
|
console.error("Usage: bun merge-to-pdf.ts <comic-dir> [--output filename.pdf]");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dir, output };
|
||||||
|
}
|
||||||
|
|
||||||
|
function findComicPages(dir: string): PageInfo[] {
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
console.error(`Directory not found: ${dir}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = readdirSync(dir);
|
||||||
|
const pagePattern = /^(\d+)-(cover|page)\.(png|jpg|jpeg)$/i;
|
||||||
|
const promptsDir = join(dir, "prompts");
|
||||||
|
const hasPrompts = existsSync(promptsDir);
|
||||||
|
|
||||||
|
const pages: PageInfo[] = files
|
||||||
|
.filter((f) => pagePattern.test(f))
|
||||||
|
.map((f) => {
|
||||||
|
const match = f.match(pagePattern);
|
||||||
|
const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");
|
||||||
|
const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
filename: f,
|
||||||
|
path: join(dir, f),
|
||||||
|
index: parseInt(match![1], 10),
|
||||||
|
promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.index - b.index);
|
||||||
|
|
||||||
|
if (pages.length === 0) {
|
||||||
|
console.error(`No comic pages found in: ${dir}`);
|
||||||
|
console.error("Expected format: 00-cover.png, 01-page.png, etc.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPdf(pages: PageInfo[], outputPath: string) {
|
||||||
|
const pdfDoc = await PDFDocument.create();
|
||||||
|
pdfDoc.setAuthor("baoyu-comic");
|
||||||
|
pdfDoc.setSubject("Generated Comic");
|
||||||
|
|
||||||
|
for (const page of pages) {
|
||||||
|
const imageData = readFileSync(page.path);
|
||||||
|
const ext = page.filename.toLowerCase();
|
||||||
|
const image = ext.endsWith(".png")
|
||||||
|
? await pdfDoc.embedPng(imageData)
|
||||||
|
: await pdfDoc.embedJpg(imageData);
|
||||||
|
|
||||||
|
const { width, height } = image;
|
||||||
|
const pdfPage = pdfDoc.addPage([width, height]);
|
||||||
|
|
||||||
|
pdfPage.drawImage(image, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Added: ${page.filename}${page.promptPath ? " (prompt available)" : ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
await Bun.write(outputPath, pdfBytes);
|
||||||
|
|
||||||
|
console.log(`\nCreated: ${outputPath}`);
|
||||||
|
console.log(`Total pages: ${pages.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { dir, output } = parseArgs();
|
||||||
|
const pages = findComicPages(dir);
|
||||||
|
|
||||||
|
const dirName = basename(dir) === "comic" ? basename(join(dir, "..")) : basename(dir);
|
||||||
|
const outputPath = output || join(dir, `${dirName}.pdf`);
|
||||||
|
|
||||||
|
console.log(`Found ${pages.length} pages in: ${dir}\n`);
|
||||||
|
|
||||||
|
await createPdf(pages, outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Error:", err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -12,6 +12,21 @@ Supports:
|
|||||||
- Multi-turn conversations within the same executor instance (`keepSession`)
|
- Multi-turn conversations within the same executor instance (`keepSession`)
|
||||||
- Experimental video generation (`generateVideo`) — Gemini may return an async placeholder; download might require Gemini web UI
|
- Experimental video generation (`generateVideo`) — Gemini may return an async placeholder; download might require Gemini web UI
|
||||||
|
|
||||||
|
## Script Directory
|
||||||
|
|
||||||
|
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
|
||||||
|
|
||||||
|
**Agent Execution Instructions**:
|
||||||
|
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
|
||||||
|
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
|
||||||
|
3. Replace all `${SKILL_DIR}` in this document with the actual path
|
||||||
|
|
||||||
|
**Script Reference**:
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `scripts/main.ts` | CLI entry point for text/image generation |
|
||||||
|
| `scripts/executor.ts` | Programmatic Gemini executor API |
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -37,6 +37,21 @@ Transform content into professional slide deck images with flexible style option
|
|||||||
/baoyu-slide-deck path/to/content.md --style storytelling --audience experts --slides 15
|
/baoyu-slide-deck path/to/content.md --style storytelling --audience experts --slides 15
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Script Directory
|
||||||
|
|
||||||
|
**Important**: All scripts are located in the `scripts/` subdirectory of this skill.
|
||||||
|
|
||||||
|
**Agent Execution Instructions**:
|
||||||
|
1. Determine this SKILL.md file's directory path as `SKILL_DIR`
|
||||||
|
2. Script path = `${SKILL_DIR}/scripts/<script-name>.ts`
|
||||||
|
3. Replace all `${SKILL_DIR}` in this document with the actual path
|
||||||
|
|
||||||
|
**Script Reference**:
|
||||||
|
| Script | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `scripts/merge-to-pptx.ts` | Merge slides into PowerPoint |
|
||||||
|
| `scripts/merge-to-pdf.ts` | Merge slides into PDF |
|
||||||
|
|
||||||
## Options
|
## Options
|
||||||
|
|
||||||
| Option | Description |
|
| Option | Description |
|
||||||
@@ -105,7 +120,9 @@ content-dir/
|
|||||||
│ └── ...
|
│ └── ...
|
||||||
├── 01-slide-cover.png
|
├── 01-slide-cover.png
|
||||||
├── 02-slide-{slug}.png
|
├── 02-slide-{slug}.png
|
||||||
└── ...
|
├── ...
|
||||||
|
├── {topic-slug}.pptx
|
||||||
|
└── {topic-slug}.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
### Without Content Path
|
### Without Content Path
|
||||||
@@ -121,7 +138,9 @@ slide-outputs/
|
|||||||
│ ├── 01-slide-cover.md
|
│ ├── 01-slide-cover.md
|
||||||
│ └── ...
|
│ └── ...
|
||||||
├── 01-slide-cover.png
|
├── 01-slide-cover.png
|
||||||
└── ...
|
├── ...
|
||||||
|
├── ai-future-trends.pptx
|
||||||
|
└── ai-future-trends.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Workflow
|
## Workflow
|
||||||
@@ -282,17 +301,18 @@ If the image generation skill supports `--sessionId`:
|
|||||||
3. Report progress: "Generated X/N"
|
3. Report progress: "Generated X/N"
|
||||||
4. Continue to next
|
4. Continue to next
|
||||||
|
|
||||||
### Step 6: Merge to PPTX
|
### Step 6: Merge to PPTX and PDF
|
||||||
|
|
||||||
After all images are generated, merge them into a PowerPoint file:
|
After all images are generated, merge them into PowerPoint and PDF files:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npx -y bun skills/baoyu-slide-deck/scripts/merge-to-pptx.ts <slide-deck-dir>
|
npx -y bun ${SKILL_DIR}/scripts/merge-to-pptx.ts <slide-deck-dir>
|
||||||
|
npx -y bun ${SKILL_DIR}/scripts/merge-to-pdf.ts <slide-deck-dir>
|
||||||
```
|
```
|
||||||
|
|
||||||
This creates `{topic-slug}.pptx` in the slide deck directory with:
|
This creates:
|
||||||
- All images as full-bleed 16:9 slides
|
- `{topic-slug}.pptx` - PowerPoint with all images as full-bleed 16:9 slides and prompt content as speaker notes
|
||||||
- Prompt content added as speaker notes (from `prompts/` directory)
|
- `{topic-slug}.pdf` - PDF with all images as full-page slides
|
||||||
|
|
||||||
### Step 7: Output Summary
|
### Step 7: Output Summary
|
||||||
|
|
||||||
@@ -313,6 +333,7 @@ Slides: N total
|
|||||||
|
|
||||||
Outline: outline.md
|
Outline: outline.md
|
||||||
PPTX: {topic-slug}.pptx
|
PPTX: {topic-slug}.pptx
|
||||||
|
PDF: {topic-slug}.pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Content Rules
|
## Content Rules
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { existsSync, readdirSync, readFileSync } from "fs";
|
||||||
|
import { join, basename } from "path";
|
||||||
|
import { PDFDocument, rgb } from "pdf-lib";
|
||||||
|
|
||||||
|
interface SlideInfo {
|
||||||
|
filename: string;
|
||||||
|
path: string;
|
||||||
|
index: number;
|
||||||
|
promptPath?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseArgs(): { dir: string; output?: string } {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
let dir = "";
|
||||||
|
let output: string | undefined;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === "--output" || args[i] === "-o") {
|
||||||
|
output = args[++i];
|
||||||
|
} else if (!args[i].startsWith("-")) {
|
||||||
|
dir = args[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dir) {
|
||||||
|
console.error("Usage: bun merge-to-pdf.ts <slide-deck-dir> [--output filename.pdf]");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { dir, output };
|
||||||
|
}
|
||||||
|
|
||||||
|
function findSlideImages(dir: string): SlideInfo[] {
|
||||||
|
if (!existsSync(dir)) {
|
||||||
|
console.error(`Directory not found: ${dir}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const files = readdirSync(dir);
|
||||||
|
const slidePattern = /^(\d+)-slide-.*\.(png|jpg|jpeg)$/i;
|
||||||
|
const promptsDir = join(dir, "prompts");
|
||||||
|
const hasPrompts = existsSync(promptsDir);
|
||||||
|
|
||||||
|
const slides: SlideInfo[] = files
|
||||||
|
.filter((f) => slidePattern.test(f))
|
||||||
|
.map((f) => {
|
||||||
|
const match = f.match(slidePattern);
|
||||||
|
const baseName = f.replace(/\.(png|jpg|jpeg)$/i, "");
|
||||||
|
const promptPath = hasPrompts ? join(promptsDir, `${baseName}.md`) : undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
filename: f,
|
||||||
|
path: join(dir, f),
|
||||||
|
index: parseInt(match![1], 10),
|
||||||
|
promptPath: promptPath && existsSync(promptPath) ? promptPath : undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.index - b.index);
|
||||||
|
|
||||||
|
if (slides.length === 0) {
|
||||||
|
console.error(`No slide images found in: ${dir}`);
|
||||||
|
console.error("Expected format: 01-slide-*.png, 02-slide-*.png, etc.");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return slides;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createPdf(slides: SlideInfo[], outputPath: string) {
|
||||||
|
const pdfDoc = await PDFDocument.create();
|
||||||
|
pdfDoc.setAuthor("baoyu-slide-deck");
|
||||||
|
pdfDoc.setSubject("Generated Slide Deck");
|
||||||
|
|
||||||
|
for (const slide of slides) {
|
||||||
|
const imageData = readFileSync(slide.path);
|
||||||
|
const ext = slide.filename.toLowerCase();
|
||||||
|
const image = ext.endsWith(".png")
|
||||||
|
? await pdfDoc.embedPng(imageData)
|
||||||
|
: await pdfDoc.embedJpg(imageData);
|
||||||
|
|
||||||
|
const { width, height } = image;
|
||||||
|
const page = pdfDoc.addPage([width, height]);
|
||||||
|
|
||||||
|
page.drawImage(image, {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Added: ${slide.filename}${slide.promptPath ? " (prompt available)" : ""}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfBytes = await pdfDoc.save();
|
||||||
|
await Bun.write(outputPath, pdfBytes);
|
||||||
|
|
||||||
|
console.log(`\nCreated: ${outputPath}`);
|
||||||
|
console.log(`Total pages: ${slides.length}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const { dir, output } = parseArgs();
|
||||||
|
const slides = findSlideImages(dir);
|
||||||
|
|
||||||
|
const dirName = basename(dir) === "slide-deck" ? basename(join(dir, "..")) : basename(dir);
|
||||||
|
const outputPath = output || join(dir, `${dirName}.pdf`);
|
||||||
|
|
||||||
|
console.log(`Found ${slides.length} slides in: ${dir}\n`);
|
||||||
|
|
||||||
|
await createPdf(slides, outputPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
console.error("Error:", err.message);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user