Update: 将子项目从 submodule 转为完整内容

- 移除 GovAI, nomifun-tauri, 算力盒子 的 submodule 引用
- 添加所有子项目的完整源代码
- 保留原始 .git 为 .git.bak 备份
This commit is contained in:
freedak
2026-07-04 19:20:46 +08:00
parent 54d6465fa7
commit f7a720204a
3360 changed files with 802660 additions and 3 deletions
@@ -0,0 +1,391 @@
---
name: story-roleplay
description: Parse and apply character cards and world info files in multiple formats (PNG, WebP, JSON), fully compatible with SillyTavern format. Supports automatic parsing, keyword triggering, and dynamic updates.
---
# Story Roleplay Skills
Parse and apply character cards and world info files for immersive story roleplay experiences. Fully compatible with SillyTavern formats.
## ⚠️ Core Constraints (Must Follow)
1. **🚫 ABSOLUTELY FORBIDDEN**: Guessing PNG/WebP image content, must use parser tool to extract data
2. **✅ MUST EXECUTE**: Prioritize copying preset tool (`skills/story-roleplay/scripts/`), create only if not found
3. **⚠️ LAST RESORT**: When creating scripts, use complete code at end of file, version must be `1.0.0` (do not use `^`)
---
## Common: Parser Tool Usage Workflow
### Quick Path (Try First)
**Step 1: Copy Preset Tool**
Try in the following order until successful:
- **Method 1 (Highest Priority)**: Relative path (if workspace is under project root):
```bash
cp skills/story-roleplay/scripts/parse-character-card.js . && cp skills/story-roleplay/scripts/package.json .
```
- **Method 2**: Search upward for project root (up to 5 levels):
```bash
for dir in . .. ../.. ../../.. ../../../.. ../../../../..; do
if [ -f "$dir/skills/story-roleplay/scripts/parse-character-card.js" ]; then
cp "$dir/skills/story-roleplay/scripts/parse-character-card.js" .
cp "$dir/skills/story-roleplay/scripts/package.json" .
break
fi
done
```
- **Method 3**: Global search (exclude temp directories):
```bash
SCRIPT_PATH=$(find ~ -name "parse-character-card.js" -path "*/skills/story-roleplay/scripts/*" ! -path "*/temp*" ! -path "*/*-temp*" ! -path "*/.webpack/*" 2>/dev/null | head -1)
if [ -n "$SCRIPT_PATH" ]; then
SCRIPT_DIR=$(dirname "$SCRIPT_PATH")
cp "$SCRIPT_DIR/parse-character-card.js" .
cp "$SCRIPT_DIR/package.json" .
fi
```
- **Verify**: Confirm files copied
```bash
ls parse-character-card.js package.json
```
- If files don't exist, continue to next method or use fallback option
**Step 2: Install Dependencies**
```bash
npm install
```
- Wait for completion, check if `node_modules` was created
- If fails: Check network/permission/Node.js environment
**Step 3: Execute Parser**
```bash
# Character card
node parse-character-card.js <image-path> <output-json-path>
# World info
node parse-character-card.js <image-path> <output-json-path> --world-info
```
**Important**:
- ✅ Must provide output path as second argument
- ❌ Do not use stdout redirection (`>`)
- Use quotes if filename contains Chinese characters or spaces
**Examples**:
```bash
node parse-character-card.js character.png character.json
node parse-character-card.js "薇娜丽丝.png" character.json
node parse-character-card.js world-info.png world-info.json --world-info
```
**Step 4: Verify Results**
- Check output: Should see "Successfully extracted data to: <path>"
- Verify JSON file exists and format is correct
- **If fails**:
- Check error message (script will output specific error)
- Common errors:
- "PNG metadata does not contain any text chunks" → Image may not be a valid SillyTavern character card
- "Required dependencies not found" → Need to run `npm install` first
- "Image file not found" → Check if image path is correct
- **ABSOLUTELY CANNOT guess or fabricate information**, must report error clearly
### Fallback Option (Last Resort)
If all above methods fail (possibly due to system permission issues), creating script files is allowed:
1. **Create `package.json`** (see "Fallback Code" section at end of file)
2. **Create `parse-character-card.js`** (see "Fallback Code" section at end of file)
**Prerequisite**: Must have tried all finding methods and all failed.
---
## Character Card Parser
**Triggers**: character card, 角色卡, load character, 加载角色, parse character, 解析角色卡
**Description**: Parse character card files in multiple formats, including PNG, WebP image formats and JSON file format.
### Supported Formats
#### 1. PNG Image Format (SillyTavern Standard)
- Data location: In PNG tEXt chunks
- Keywords: `chara` (v2) or `ccv3` (v3)
- Encoding: Base64-encoded JSON string
**Processing**: Use common parser tool workflow (see above), use character card command format when executing.
#### 2. WebP Image Format (SillyTavern Compatible)
- Data location: In WebP EXIF/XMP metadata or text chunks
- Keywords: `chara` (v2) or `ccv3` (v3)
**Processing**:
- **Recommended**: Convert WebP to PNG first, then use PNG parser tool
- Or try using PNG parser tool (if WebP contains similar metadata structure)
- If conversion or parsing not possible, prompt user to provide JSON format or re-export from SillyTavern
#### 3. JSON File Format
**Standard Format** (Tavern Card V2/V3):
- Includes: `name`, `description`, `personality`, `scenario`, `first_mes`, `system_prompt`
- Optional: `character_book` (character knowledge base)
- Supports simplified format (direct fields, no nested structure)
**Processing**: Read and parse directly (simplest, preferred)
### Parsing Steps
**Important: PNG/WebP images must use parser tool, guessing content is forbidden**
1. **Detect file format**:
- **JSON files**: Read and parse directly (simplest, preferred)
- **PNG/WebP files**: Use common parser tool workflow (see above)
2. **Extract character information**:
- `name`: Character name
- `description`: Character description
- `personality`: Personality traits
- `scenario`: Scenario setting
- `first_mes`: First message (used as opening)
- `system_prompt`: System prompt (character behavior rules)
- `character_book`: Character knowledge base (similar to world info)
3. **Apply character information**:
- Use character's system_prompt as behavior rules
- Use first_mes as conversation opening
- Apply character_book entries to conversation
4. **Save as JSON format (after parsing images)**:
- After successfully parsing PNG/WebP images, **automatically convert to JSON format**
- Save as `character.json` in workspace
- Preserve all original data from image
### Best Practices
- **Prefer JSON format** (easiest to parse, no additional tools needed)
- **PNG format**: Must use parser tool, guessing content is forbidden
- **WebP format**: Convert to PNG then use parser tool, or use JSON format
- **Error handling**: When parsing fails, must provide clear error message, absolutely cannot guess or fabricate character information
- **Always save parsed images as JSON format** for easier viewing and editing
---
## World Info Parser
**Triggers**: world info, 世界信息, world tree, 世界树, load world info, 加载世界信息
**Description**: Parse and apply world info files, implementing keyword trigger mechanism. Supports JSON files and PNG/WebP image formats (with embedded world info data).
### Supported Formats
#### 1. JSON File Format
```json
{
"name": "World Name",
"entries": [
{
"keys": ["keyword1", "keyword2"],
"content": "Content to inject when triggered",
"priority": 100,
"enabled": true
}
]
}
```
#### 2. PNG Image Format (SillyTavern Compatible)
- Data location: In PNG tEXt chunks
- Keyword: `naidata` (SillyTavern standard)
- Encoding: Base64-encoded JSON string
**Processing**: Use common parser tool workflow (see above), use `--world-info` flag when executing.
#### 3. WebP Image Format (SillyTavern Compatible)
**Processing**:
- **Recommended**: Convert WebP to PNG first, then use PNG parser
- If conversion fails, suggest user use JSON format or re-export from SillyTavern
### Field Descriptions
- `keys`: Keyword array, triggers when these words appear in conversation
- `content`: Content to inject when triggered
- `priority`: Priority (higher number = higher priority)
- `enabled`: Whether enabled (true/false)
### Trigger Mechanism
1. **Keyword detection**: Monitor conversation content, detect if it contains world info keywords
2. **Content injection**: When keywords appear, integrate corresponding content into response, sorted by priority
3. **Natural integration**: Do not insert world info content awkwardly, naturally integrate into conversation and narrative
### Parsing Steps
1. **Detect file format**:
- **JSON files**: Read and parse directly (simplest, preferred)
- **PNG/WebP files**: Use common parser tool workflow (see above), use `--world-info` flag
2. **Extract world info**:
- Read output JSON file
- Parse JSON, extract `entries` array
- Verify JSON structure is correct (must contain `entries` field)
3. **Save as JSON format (after parsing images)**:
- After successfully parsing PNG/WebP images, automatically convert to JSON format
- Save as `world-info.json` in workspace
### Best Practices
- **Prefer JSON format** (easiest to parse, no additional tools needed)
- **PNG format**: Use same parser tool as character cards (with `--world-info` flag)
- **WebP format**: Recommend converting to PNG or using JSON format
- Keywords should be specific and meaningful, avoid overly broad keywords
- **Always save parsed images as JSON format** for easier viewing and editing
---
## Character Book Handler
**Triggers**: character book, 角色知识库, character entry, 角色条目
**Description**: Handle character_book (character knowledge base) in character cards, similar to world info but bound to specific character.
### Character Book Format
character_book field in character card:
- `name`: Character knowledge base name
- `entries`: Entry array, each entry contains `keys`, `content`, `priority`, `enabled`
### Processing
1. **When loading character card**: Extract character_book field, parse entries array, apply together with character information
2. **During conversation**: Detect keywords in character_book, inject relevant content when keywords appear
3. **Priority**: character_book entries usually have higher priority than world info
### Best Practices
- character_book is for character-specific knowledge
- World info is for general world settings
- Both can be used together, but pay attention to priority
---
## File Detection and Loading Workflow
### Automatic Detection
1. **Scan workspace**:
- Find character card files: `character.png`, `character.webp`, `character.json`, `*.character.json`
- Find world info files: `world-info.png`, `world-info.webp`, `world-info.json`, `world.json`
2. **Format recognition and parsing**:
- **JSON files**: Read directly
- **PNG/WebP images**: Use common parser tool workflow (see above)
3. **Apply parsed results**:
- Apply parsed results to conversation context
- After successfully parsing images, automatically save as JSON format
### Character Card and World Info Creation
**When no files exist** (Important: Must actively guide user):
1. **Active guidance process**:
- Step 1: Ask about story type and background setting
- Step 2: Ask about character details (type, personality, background, speaking style)
- Step 3: Ask about world setting (rules, locations, special settings, etc.)
2. **Confirm information**: Summarize user-provided information, wait for user confirmation before creating files
3. **Create files**:
- After confirmation, create `character.json` with all character information
- If world-building elements mentioned, create `world-info.json` with relevant entries
**Creation format**:
- Character card: Use Tavern Card V2/V3 standard format, include all essential fields
- World info: Create entries for key concepts, locations, rules, use meaningful keywords
### Manual Loading
Users can manually load via:
- "Load character card: character.png"
- "Read world info: world-info.json"
- "Use this character: [upload file]"
## Compatibility
This skill is fully compatible with SillyTavern formats:
- ✅ PNG image format (embedded JSON, SillyTavern standard) - for character cards and world info
- ✅ WebP image format (embedded JSON, SillyTavern compatible) - for character cards and world info
- ✅ JSON file format (Tavern Card V2/V3) - for character cards and world info
- ✅ World info format (PNG/WebP images use keyword `naidata`)
- ✅ Character Book (character knowledge base)
---
## Fallback Code (For Last Resort Use)
**Important**: The following code should only be used when all path finding methods have failed. Prioritize using copy methods.
### package.json
```json
{
"name": "story-roleplay-parser",
"version": "1.0.0",
"description": "Parser tools for character cards and world info",
"main": "parse-character-card.js",
"scripts": {
"parse": "node parse-character-card.js"
},
"dependencies": {
"png-chunks-extract": "1.0.0",
"png-chunk-text": "1.0.0"
}
}
```
### parse-character-card.js
Complete code reference: `skills/story-roleplay/scripts/parse-character-card.js`
**Key features**:
- Extract character card data from PNG images (keywords: `chara` or `ccv3`)
- Extract world info data from PNG images (keyword: `naidata`)
- Support Base64 decoding and JSON validation
- Complete error handling and user-friendly error messages
**If file is not accessible**, can create based on following logic:
- Use `png-chunks-extract` to extract PNG chunks
- Use `png-chunk-text` to decode tEXt chunks
- Find keywords (`chara`/`ccv3` for character cards, `naidata` for world info)
- Base64 decode and validate JSON format
@@ -0,0 +1,13 @@
{
"name": "story-roleplay-parser",
"version": "1.0.0",
"description": "Parser tools for character cards and world info",
"main": "parse-character-card.js",
"scripts": {
"parse": "node parse-character-card.js"
},
"dependencies": {
"png-chunk-text": "1.0.0",
"png-chunks-extract": "1.0.0"
}
}
@@ -0,0 +1,169 @@
/**
* Character Card & World Info Parser Tool
*
* This tool extracts character card or world info data from PNG images.
* Compatible with SillyTavern format.
*
* Usage: node parse-character-card.js <image-path> [output-path] [--world-info]
* Example: node parse-character-card.js character.png character.json
* Example: node parse-character-card.js world-info.png world-info.json --world-info
*/
const fs = require('fs');
const path = require('path');
const { Buffer } = require('buffer');
// Check if dependencies are installed
let extract, PNGtext;
try {
extract = require('png-chunks-extract');
PNGtext = require('png-chunk-text');
} catch (error) {
console.error('Error: Required dependencies not found.');
console.error('Please run: npm install png-chunks-extract png-chunk-text');
process.exit(1);
}
/**
* Extract character card data from PNG image
* Based on SillyTavern's implementation: src/character-card-parser.js
* @param {string} imagePath - Path to PNG image
* @returns {string} - Character card JSON string
*/
function extractFromPng(imagePath) {
try {
const buffer = fs.readFileSync(imagePath);
const chunks = extract(new Uint8Array(buffer));
const textChunks = chunks.filter((chunk) => chunk.name === 'tEXt').map((chunk) => PNGtext.decode(chunk.data));
if (textChunks.length === 0) {
console.error('PNG metadata does not contain any text chunks.');
throw new Error('PNG metadata does not contain any text chunks.');
}
// Try ccv3 first (v3 format) - V3 takes precedence as per SillyTavern
const ccv3Index = textChunks.findIndex((chunk) => chunk.keyword.toLowerCase() === 'ccv3');
if (ccv3Index > -1) {
return Buffer.from(textChunks[ccv3Index].text, 'base64').toString('utf8');
}
// Fallback to chara (v2 format)
const charaIndex = textChunks.findIndex((chunk) => chunk.keyword.toLowerCase() === 'chara');
if (charaIndex > -1) {
return Buffer.from(textChunks[charaIndex].text, 'base64').toString('utf8');
}
console.error('PNG metadata does not contain any character card data (chara or ccv3).');
throw new Error('PNG metadata does not contain any character card data (chara or ccv3).');
} catch (error) {
if (error.message.includes('PNG metadata')) {
throw error; // Re-throw PNG metadata errors as-is
}
throw new Error(`Failed to extract from PNG: ${error.message}`);
}
}
/**
* Extract world info data from PNG image
* @param {string} imagePath - Path to PNG image
* @returns {string} - World info JSON string
*/
function extractWorldInfoFromPng(imagePath) {
try {
const buffer = fs.readFileSync(imagePath);
const chunks = extract(new Uint8Array(buffer));
const textChunks = chunks.filter((chunk) => chunk.name === 'tEXt').map((chunk) => PNGtext.decode(chunk.data));
if (textChunks.length === 0) {
throw new Error('PNG metadata does not contain any text chunks.');
}
// Look for naidata (world info keyword)
const naidataIndex = textChunks.findIndex((chunk) => chunk.keyword.toLowerCase() === 'naidata');
if (naidataIndex > -1) {
return Buffer.from(textChunks[naidataIndex].text, 'base64').toString('utf8');
}
throw new Error('PNG metadata does not contain world info data (naidata).');
} catch (error) {
throw new Error(`Failed to extract world info from PNG: ${error.message}`);
}
}
// Main execution
if (require.main === module) {
const args = process.argv.slice(2);
if (args.length < 1) {
console.error('Usage: node parse-character-card.js <image-path> [output-path] [--world-info]');
console.error('Example: node parse-character-card.js character.png character.json');
console.error('Example: node parse-character-card.js world-info.png world-info.json --world-info');
process.exit(1);
}
const imagePath = args[0];
// Important: outputPath must be provided as second argument, not via stdout redirection
const outputPath = args[1] || imagePath.replace(/\.(png|webp)$/i, '.json');
const isWorldInfo = args.includes('--world-info');
if (!fs.existsSync(imagePath)) {
console.error(`Error: Image file not found: ${imagePath}`);
console.error('Please check if the file path is correct and the file exists.');
process.exit(1);
}
try {
const ext = path.extname(imagePath).toLowerCase();
let jsonData;
if (ext === '.png') {
if (isWorldInfo) {
jsonData = extractWorldInfoFromPng(imagePath);
} else {
jsonData = extractFromPng(imagePath);
}
} else if (ext === '.webp') {
// WebP support would require additional library
// For now, suggest converting to PNG or using JSON format
console.error('Error: WebP format parsing requires additional setup.');
console.error('Please convert to PNG or use JSON format.');
process.exit(1);
} else {
console.error(`Error: Unsupported file format: ${ext}`);
console.error('Supported formats: .png');
process.exit(1);
}
// Validate JSON
try {
JSON.parse(jsonData);
} catch (error) {
console.error('Error: Extracted data is not valid JSON.');
console.error('This might not be a valid character card or world info image.');
console.error('The image may not contain embedded character data, or the data format is corrupted.');
process.exit(1);
}
// Save to file (do not use stdout redirection, use file path argument)
fs.writeFileSync(outputPath, jsonData, 'utf8');
console.log(`Successfully extracted data to: ${outputPath}`);
} catch (error) {
console.error(`Error: ${error.message}`);
if (error.message.includes('PNG metadata')) {
console.error('This image may not be a valid SillyTavern character card.');
console.error('Please ensure the image was exported from SillyTavern with character data embedded.');
}
process.exit(1);
}
}
// Export for use as module
module.exports = {
extractFromPng,
extractWorldInfoFromPng,
};