chore: add shared skills catalog (19 skills), installers, manifest, validator

This commit is contained in:
2026-08-26 22:08:40 +07:00
parent 62f31484ea
commit a1050f5502
38 changed files with 1850 additions and 2 deletions

View File

@@ -0,0 +1,71 @@
---
name: lab-imagegen
description: Generate images using the user's home-lab ComfyUI server (SDXL-turbo on 2x AMD Radeon Pro VII). Use when the user asks to generate, render, or create an image or picture.
---
# Lab Image Generation (ComfyUI)
The user's home lab runs a ComfyUI server at `http://192.168.31.240:8188` (server1, 2x AMD Radeon Pro VII 16GB, SDXL-turbo). Use it to generate images on demand. No authentication is required — the API is open on the LAN.
## Workflow
Use `run_code` to generate: submit a JSON workflow, poll `/history/{prompt_id}`, then return the served image URL.
### 1. Submit the prompt
POST `/prompt` with `Content-Type: application/json` and body:
```json
{ "prompt": <workflow>, "client_id": "dsh" }
```
The workflow for SDXL-turbo (4 steps):
```json
{
"3": { "class_type": "KSampler", "inputs": { "seed": 123, "steps": 4, "cfg": 1.0, "sampler_name": "euler", "scheduler": "normal", "denoise": 1.0, "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0] } },
"4": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "sdxl_turbo.safetensors" } },
"5": { "class_type": "EmptyLatentImage", "inputs": { "width": 512, "height": 512, "batch_size": 1 } },
"6": { "class_type": "CLIPTextEncode", "inputs": { "text": "<POSITIVE>", "clip": ["4", 1] } },
"7": { "class_type": "CLIPTextEncode", "inputs": { "text": "<NEGATIVE>", "clip": ["4", 1] } },
"8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["4", 2] } },
"9": { "class_type": "SaveImage", "inputs": { "filename_prefix": "dsh", "images": ["8", 0] } }
}
```
Defaults: 512x512, steps 4, cfg 1.0, euler/normal, seed random. The response contains `prompt_id` and `node_errors`. **If `node_errors` is non-empty, the submission failed — report them.**
### 2. Poll for completion
Poll `GET http://192.168.31.240:8188/history/{prompt_id}` every ~1.5s. The result entry under `outputs["9"].images` contains `filename`. Stop polling when the image array appears.
### 3. Return the image
The served image URL is:
`http://192.168.31.240:8188/view?filename=<filename>&subfolder=&type=output`
Return this URL (a well-formed markdown image link if the user wants to view it inline).
## Policy
- **Timeout:** poll no longer than ~120s. SDXL-turbo at 4 steps typically finishes in well under 60s. If it exceeds the budget, report a timeout rather than looping forever.
- **Prompt quality:** SDXL-turbo responds best to a descriptive positive prompt and a modest negative prompt (e.g. `blurry, low quality, distorted`). High `cfg` (>1) and high `steps` are unnecessary for turbo.
- Keep the model `sdxl_turbo.safetensors` unless the user asks otherwise — it is the verified installed checkpoint.
- Do not write the PNG bytes to a local file unless asked; return the server URL.
### Example (run_code)
```js
const base = 'http://192.168.31.240:8188'
const wf = { "3": { "class_type": "KSampler", "inputs": { "seed": 42, "steps": 4, "cfg": 1.0, "sampler_name": "euler", "scheduler": "normal", "denoise": 1.0, "model": ["4", 0], "positive": ["6", 0], "negative": ["7", 0], "latent_image": ["5", 0] } }, "4": { "class_type": "CheckpointLoaderSimple", "inputs": { "ckpt_name": "sdxl_turbo.safetensors" } }, "5": { "class_type": "EmptyLatentImage", "inputs": { "width": 512, "height": 512, "batch_size": 1 } }, "6": { "class_type": "CLIPTextEncode", "inputs": { "text": "a red fox in a snowy forest, photorealistic", "clip": ["4", 1] } }, "7": { "class_type": "CLIPTextEncode", "inputs": { "text": "blurry, low quality, distorted", "clip": ["4", 1] } }, "8": { "class_type": "VAEDecode", "inputs": { "samples": ["3", 0], "vae": ["4", 2] } }, "9": { "class_type": "SaveImage", "inputs": { "filename_prefix": "dsh", "images": ["8", 0] } } }
const r = await (await fetch(base + '/prompt', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ prompt: wf, client_id: 'dsh' }) })).json()
if (r.node_errors && Object.keys(r.node_errors).length) throw new Error('node_errors: ' + JSON.stringify(r.node_errors))
let url = null
for (let i = 0; i < 160 && !url; i++) {
const h = await (await fetch(base + '/history/' + r.prompt_id)).json()
const imgs = h[r.prompt_id]?.outputs?.['9']?.images
if (imgs?.length) url = base + '/view?filename=' + encodeURIComponent(imgs[0].filename) + '&subfolder=&type=output'
else await new Promise(s => setTimeout(s, 1500))
}
return url
```